mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-06 08:55:34 +00:00
1647 lines
37 KiB
C
1647 lines
37 KiB
C
/* kmflcomp.c
|
|
* Copyright (C) 2005 SIL International
|
|
*
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <ctype.h>
|
|
#include <sys/stat.h>
|
|
#include <setjmp.h>
|
|
|
|
#include "kmflutfconv.h"
|
|
#include "compiler.h"
|
|
#include "yacc.h"
|
|
#include <kmflcomp.h>
|
|
#include "memman.h"
|
|
|
|
#ifdef _WIN32
|
|
#include <io.h>
|
|
#include <conio.h>
|
|
#include "getopt.h"
|
|
#define strcasecmp _stricmp
|
|
#define rindex strrchr
|
|
#define seek _lseek
|
|
#define DIRDELIM '\\'
|
|
char *GetInputFile(void);
|
|
#else
|
|
#include <X11/Xlib.h>
|
|
#include <getopt.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#define O_BINARY 0
|
|
#define DIRDELIM '/'
|
|
#endif
|
|
|
|
#include <fcntl.h>
|
|
|
|
|
|
// Compiler options
|
|
int opt_debug=0;
|
|
int opt_force=0;
|
|
int opt_verbose=0;
|
|
|
|
// Fatal error variables
|
|
jmp_buf fatal_error_buf;
|
|
|
|
// The keyboard being compiled, and version string
|
|
KEYBOARD keyboard, *kbp=&keyboard;
|
|
char Version[6]=BASE_VERSION FILE_VERSION; // Concatenate keyboard version and file version
|
|
|
|
int warncount=0, warnlimit=10, errlimit=10, file_format=KF_UNICODE;
|
|
|
|
DEADKEY *last_deadkey=NULL;
|
|
STORE *last_store=NULL;
|
|
|
|
extern FILE *yyin;
|
|
static const char * fname=NULL;
|
|
static int firstkeyboard = 1;
|
|
|
|
// forward function declarations
|
|
unsigned long create_keyboard_buffer(const char *infile, void ** kb_buf);
|
|
char * checked_strcpy(char * dst, char * src, int len, char * type, int line);
|
|
int get_char_shift_state(ITEM q);
|
|
|
|
|
|
// Write the compiled keyboard to the output file
|
|
long save_keyboard(const char *infile, void * keyboard_buffer, unsigned long size)
|
|
{
|
|
char *outfile, *pdot;
|
|
unsigned long n, out;
|
|
struct stat fstat;
|
|
|
|
// Create file names from input file name
|
|
n = strlen(infile)+6;
|
|
|
|
if(!(outfile=(char *)mem_alloc(n))) return(-1);
|
|
strcpy(outfile,infile); pdot = rindex(outfile,'.');
|
|
if(pdot) strcpy(pdot, ".kmfl"); else strcat(outfile,".kmfl");
|
|
|
|
|
|
// Open output file, rule table and string table
|
|
if((out=open(outfile,O_WRONLY|O_BINARY|O_CREAT|O_TRUNC,00666)) < 0) return(-2);
|
|
|
|
|
|
if (write(out, keyboard_buffer, size) < 0)
|
|
fail(1, "cannot write compiled keyboard file %s", outfile);
|
|
|
|
close(out);
|
|
|
|
stat(outfile,&fstat);
|
|
mem_free(outfile);
|
|
|
|
size = fstat.st_size;
|
|
return size;
|
|
}
|
|
|
|
void write_keyboard(char * infile, void *keyboard_buffer, int keyboard_buffer_size)
|
|
{
|
|
long filesize;
|
|
|
|
// Save the output file
|
|
if((filesize=save_keyboard(infile, keyboard_buffer, keyboard_buffer_size)) > 0)
|
|
{
|
|
if(errcount > 0 || warncount > 0)
|
|
{
|
|
if(errcount > 0 )
|
|
fprintf(stderr," Warning: %d error%s ignored - compiled keyboard may fail!\n",
|
|
errcount,(errcount==1?"":"s"));
|
|
|
|
if(warncount > warnlimit)
|
|
fprintf(stderr," Total warnings: %d\n",warncount);
|
|
}
|
|
|
|
if(Version[3] > '0')
|
|
fprintf(stderr,"Keyboard '%s' (Version %c%c.%c%c) compiled to %ld bytes\n",
|
|
kbp->name,Version[0],Version[1],Version[2],Version[3],filesize);
|
|
else
|
|
fprintf(stderr,"Keyboard '%s' (Version %c%c.%c) compiled to %ld bytes\n",
|
|
kbp->name,Version[0],Version[1],Version[2],filesize);
|
|
}
|
|
else
|
|
fail(3,"unable to save output file!");
|
|
}
|
|
|
|
unsigned long compile_keyboard_to_buffer(const char * infile, void ** keyboard_buffer)
|
|
{
|
|
GROUP *gp;
|
|
BYTE BOM[4]={0};
|
|
unsigned
|
|
long size;
|
|
|
|
fname = infile;
|
|
// Open input file
|
|
yyin = fopen(infile,"r");
|
|
if(!yyin)
|
|
{
|
|
char *ftmp;
|
|
ftmp = (char *)checked_alloc(strlen(infile)+6,1);
|
|
strcpy(ftmp,infile); strcat(ftmp,".kmn");
|
|
yyin = fopen(ftmp,"r");
|
|
mem_free(ftmp);
|
|
}
|
|
if(!yyin) fail(1,"cannot open %s",infile);
|
|
|
|
// Initialize defaults and parameters
|
|
errcount=0;
|
|
warncount=0;
|
|
lineno=0;
|
|
done=0;
|
|
last_deadkey=NULL;
|
|
last_store=NULL;
|
|
kbp->deadkeys = NULL;
|
|
kbp->mode = KF_ANSI; // Must be ANSI if not specified
|
|
|
|
// Check for BOM at start of file
|
|
if (fread(BOM,3,1,yyin) != 1)
|
|
fail(1, "Cannot read byte order mark");
|
|
|
|
if(BOM[0] == 0xEF && BOM[1] == 0xBB && BOM[2] == 0xBF)
|
|
{
|
|
file_format = KF_UNICODE; // Set file format to Unicode if file is UTF-8 or UTF-16
|
|
}
|
|
else
|
|
{
|
|
fseek(yyin,0,SEEK_SET);
|
|
file_format = KF_ANSI; // Set file format to ansi
|
|
}
|
|
|
|
if(BOM[0] == 0xFF && BOM[1] == 0xFE) // Is it UTF-16?
|
|
{
|
|
yyin = UTF16toUTF8(yyin); // Make a temporary UTF-8 copy of the file
|
|
file_format = KF_UNICODE; // And set file format to Unicode
|
|
}
|
|
|
|
// Define the reserved-name stores as the first numbered stores
|
|
initialize_special_stores();
|
|
|
|
// Parse the input file with yacc
|
|
if (firstkeyboard)
|
|
firstkeyboard = 0;
|
|
else
|
|
yyrestart(yyin);
|
|
|
|
yyparse();
|
|
yycleanup();
|
|
|
|
fflush(stdout);
|
|
fclose(yyin);
|
|
|
|
// Complete keyboard header and and check it for validity
|
|
check_keyboard(kbp);
|
|
|
|
// Exit on error (unless forced)
|
|
if (errcount > 0 && !opt_force)
|
|
{
|
|
fail(2,"%d error%s and %d warning%s",errcount,(errcount==1?"":"s"),
|
|
warncount,(warncount==1?"":"s"));
|
|
}
|
|
|
|
// Sort the rules in each group
|
|
for(gp=kbp->groups; gp; gp=gp->next) sort_rules(gp);
|
|
|
|
size = create_keyboard_buffer(infile, keyboard_buffer);
|
|
// cleanup memory
|
|
mem_free_all();
|
|
return size;
|
|
|
|
}
|
|
|
|
// Complete keyboard header, and check for validity
|
|
void check_keyboard(KEYBOARD *kbp)
|
|
{
|
|
UTF32 *p1;
|
|
UTF8 *p2;
|
|
STORE *sp;
|
|
ITEM *p;
|
|
const char *np;
|
|
unsigned int n;
|
|
|
|
// Has keyboard name been defined?
|
|
sp = &kbp->stores[SS_NAME];
|
|
if(sp->items == NULL || *sp->items == 0)
|
|
{
|
|
np = rindex(fname,'/');
|
|
if(np == NULL) np = rindex(fname, '\\');
|
|
if(np == NULL) np = rindex(fname, ':');
|
|
if(np == NULL) np = fname-1;
|
|
|
|
for(n=0,np++; n<NAMELEN && *np && *np!='.'; n++,np++)
|
|
*(kbp->name+n) = *np;
|
|
*(kbp->name+n) = 0;
|
|
|
|
kmflcomp_warn(0,"Keyboard name set by default to '%s'",kbp->name);
|
|
}
|
|
else
|
|
{
|
|
// There should be no MS bytes set, but make sure!
|
|
for(n=0,p=sp->items; n<sp->len && *p!=0; n++,p++) *p &= 0xffffff;
|
|
|
|
p1 = (UTF32 *)sp->items; p2 = (UTF8 *)kbp->name;
|
|
IConvertUTF32toUTF8((const UTF32 **)&p1,(const UTF32 *)(sp->items+sp->len),&p2,(UTF8 *)(kbp->name+NAMELEN));
|
|
*p2 = 0;
|
|
}
|
|
}
|
|
|
|
void * append_to_buffer(void * buffer, long * bufsize, void * chunk, int chunk_size)
|
|
{
|
|
// don't use memory management for buffers because they will be used outside of kmflcomp
|
|
buffer=realloc(buffer, (*bufsize)+chunk_size);
|
|
if (buffer)
|
|
{
|
|
memcpy(buffer+(*bufsize), chunk, chunk_size);
|
|
*bufsize+=chunk_size;
|
|
} else {
|
|
fail(4, "Out of memory\n");
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
// Create the keyboard buffer.
|
|
unsigned long create_keyboard_buffer(const char *infile, void ** kb_buf)
|
|
{
|
|
XKEYBOARD xkbd={{0}};
|
|
XSTORE xstore;
|
|
XGROUP xgroup;
|
|
XRULE xrule;
|
|
STORE *sp, *sp1;
|
|
GROUP *gp, *gp1;
|
|
RULE *rp;
|
|
DEADKEY *dp, *dp1;
|
|
void * keyboard_buffer = NULL;
|
|
void * rtt_buffer = NULL;
|
|
void * stt_buffer = NULL;
|
|
long keyboard_buffer_size = 0;
|
|
long rtt_buffer_size = 0;
|
|
long stt_buffer_size = 0;
|
|
unsigned long i, j, n, index, offset;
|
|
|
|
// Fill the compiled keyboard header structure
|
|
memcpy(&xkbd,kbp,sizeof(XKEYBOARD));
|
|
|
|
// Set tag and version
|
|
memcpy(&xkbd.id,"KMFL",4);
|
|
memcpy(&xkbd.version,Version,5);
|
|
|
|
// add the keyboard header
|
|
keyboard_buffer = append_to_buffer(keyboard_buffer, &keyboard_buffer_size, &xkbd, sizeof(XKEYBOARD));
|
|
|
|
// Save each store, saving its contents in the string table (no nulls)
|
|
for(n=0,index=0,sp=kbp->stores; n<kbp->nstores; n++,sp=sp->next)
|
|
{
|
|
if(sp->len > 0)
|
|
{
|
|
stt_buffer = append_to_buffer(stt_buffer, &stt_buffer_size,sp->items,sp->len*ITEMSIZE);
|
|
}
|
|
if(sp->items) {
|
|
mem_free(sp->items); // free string memory
|
|
sp->items = NULL;
|
|
}
|
|
xstore.len = sp->len;
|
|
xstore.items = index;
|
|
keyboard_buffer = append_to_buffer(keyboard_buffer, &keyboard_buffer_size, &xstore,sizeof(XSTORE));
|
|
index += sp->len;
|
|
}
|
|
|
|
// Save each group, saving rules in the rule table and rule strings in the string table
|
|
for(j=0,gp=kbp->groups,offset=0; j<kbp->ngroups; j++,gp=gp->next)
|
|
{
|
|
xgroup.flags = gp->flags;
|
|
xgroup.nrules = gp->nrules;
|
|
xgroup.rule1 = offset;
|
|
xgroup.mrlen = gp->mrlen;
|
|
xgroup.nmrlen = gp->nmrlen;
|
|
|
|
if(gp->mrlen > 0)
|
|
{
|
|
stt_buffer = append_to_buffer(stt_buffer, &stt_buffer_size, gp->match, gp->mrlen*ITEMSIZE);
|
|
mem_free(gp->match); // free string memory
|
|
xgroup.match = index;
|
|
index += gp->mrlen;
|
|
}
|
|
else xgroup.match = UNDEFINED;
|
|
|
|
if(gp->nmrlen > 0)
|
|
{
|
|
stt_buffer = append_to_buffer(stt_buffer, &stt_buffer_size, gp->nomatch, gp->nmrlen*ITEMSIZE);
|
|
mem_free(gp->nomatch); // free string memory
|
|
xgroup.nomatch = index;
|
|
index += gp->nmrlen;
|
|
}
|
|
else xgroup.nomatch = UNDEFINED;
|
|
|
|
keyboard_buffer = append_to_buffer(keyboard_buffer, &keyboard_buffer_size, &xgroup, sizeof(XGROUP));
|
|
|
|
for(i=0,rp=gp->rules; i<gp->nrules; i++,rp=rp->next)
|
|
{
|
|
xrule.ilen = rp->ilen;
|
|
xrule.olen = rp->olen;
|
|
stt_buffer = append_to_buffer(stt_buffer, &stt_buffer_size, rp->lhs, rp->ilen*ITEMSIZE);
|
|
mem_free(rp->lhs); // free string memory
|
|
xrule.lhs = index;
|
|
index += rp->ilen;
|
|
stt_buffer = append_to_buffer(stt_buffer, &stt_buffer_size, rp->rhs,rp->olen*ITEMSIZE);
|
|
mem_free(rp->rhs); // free string memory
|
|
xrule.rhs = index;
|
|
index += rp->olen;
|
|
|
|
rtt_buffer = append_to_buffer(rtt_buffer, &rtt_buffer_size, &xrule, sizeof(XRULE));
|
|
offset++;
|
|
}
|
|
}
|
|
|
|
keyboard_buffer = append_to_buffer(keyboard_buffer, &keyboard_buffer_size, rtt_buffer, rtt_buffer_size);
|
|
|
|
keyboard_buffer = append_to_buffer(keyboard_buffer, &keyboard_buffer_size, stt_buffer, stt_buffer_size);
|
|
|
|
// don't use memory management for buffers because they will be used outside of kmflcomp
|
|
free(rtt_buffer);
|
|
free(stt_buffer);
|
|
|
|
// Free deadkey memory
|
|
for(dp=kbp->deadkeys; dp!=NULL; dp=dp1)
|
|
{
|
|
dp1=dp->next; mem_free(dp);
|
|
}
|
|
|
|
kbp->deadkeys = NULL;
|
|
|
|
// Check and free stores
|
|
for(sp=kbp->stores; sp!=NULL; sp=sp1)
|
|
{
|
|
// only warn for undefined user stores
|
|
if (sp->len == 0 && sp->name[0] != '&')
|
|
kmflcomp_warn(sp->lineno,"store (%s) is undefined!",sp->name);
|
|
|
|
sp1=sp->next; mem_free(sp);
|
|
}
|
|
|
|
kbp->stores = NULL;
|
|
|
|
// Free groups and rules
|
|
for(gp=kbp->groups; gp!=NULL; gp=gp1)
|
|
{
|
|
if(gp->rules) mem_free(gp->rules);
|
|
gp1=gp->next;
|
|
mem_free(gp);
|
|
}
|
|
kbp->groups = NULL;
|
|
*kb_buf = keyboard_buffer;
|
|
return keyboard_buffer_size;
|
|
}
|
|
|
|
|
|
// Routines for manipulating rule groups
|
|
|
|
// Create a new group
|
|
GROUP *new_group(char *name, int line)
|
|
{
|
|
GROUP *gp, *gp1;
|
|
|
|
if((gp=find_group(name)) == NULL)
|
|
{
|
|
gp = (GROUP *)checked_alloc(sizeof(GROUP),1);
|
|
checked_strcpy(gp->name,name,NAMELEN, "group", line);
|
|
|
|
// and add it to the linked list of groups
|
|
if(kbp->groups)
|
|
{
|
|
for(gp1=kbp->groups; gp1->next; gp1=gp1->next);
|
|
gp1->next = gp;
|
|
kbp->ngroups++;
|
|
}
|
|
else
|
|
{
|
|
kbp->groups = gp;
|
|
kbp->ngroups = 1;
|
|
}
|
|
}
|
|
|
|
return gp;
|
|
}
|
|
|
|
// Find a group by name
|
|
GROUP *find_group(char *name)
|
|
{
|
|
GROUP *gp;
|
|
for(gp=kbp->groups; gp!=NULL; gp=gp->next)
|
|
{
|
|
if(strcasecmp(name,gp->name) == 0) return gp;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// Find the group number of a named group, and create a new group if necessary
|
|
int group_number(char *name, int line)
|
|
{
|
|
GROUP *gp;
|
|
int n;
|
|
|
|
for(n=0,gp=kbp->groups; gp!=NULL; gp=gp->next,n++)
|
|
{
|
|
if(strcasecmp(name,gp->name) == 0) return n;
|
|
}
|
|
|
|
// Create a new group and save the name
|
|
gp = new_group(name, line);
|
|
|
|
return n;
|
|
}
|
|
|
|
// Count the groups in a linked list of groups
|
|
int count_groups(GROUP *gp)
|
|
{
|
|
int n;
|
|
|
|
for(n=0; gp!=NULL; gp=gp->next) n++;
|
|
|
|
return n;
|
|
}
|
|
|
|
// Save the name of the starting group and set the mode flag
|
|
void set_start_group(char *name, int mode, int line)
|
|
{
|
|
kbp->group1 = group_number(name, line);
|
|
kbp->mode = mode;
|
|
}
|
|
|
|
// Routines for manipulating rules
|
|
|
|
// Create a new rule for this group
|
|
RULE *new_rule(GROUP *gp, ITEM *lhs, ITEM *rhs, int line)
|
|
{
|
|
RULE *rp;
|
|
|
|
// Check first for match and nomatch rules
|
|
switch(ITEM_TYPE(*lhs))
|
|
{
|
|
case ITEM_MATCH:
|
|
if(count_items(lhs) == 1)
|
|
{
|
|
gp->match = rhs;
|
|
gp->mrlen = count_items(rhs);
|
|
check_rhs(gp->match,gp->mrlen,gp,line);
|
|
}
|
|
else
|
|
{
|
|
kmflcomp_error(line,"'match' must be the only item on the input side of a rule");
|
|
}
|
|
return NULL;
|
|
case ITEM_NOMATCH:
|
|
if(count_items(lhs) == 1)
|
|
{
|
|
gp->nomatch = rhs;
|
|
gp->nmrlen = count_items(rhs);
|
|
check_rhs(gp->nomatch,gp->nmrlen,gp,line);
|
|
}
|
|
else
|
|
{
|
|
kmflcomp_error(line,"'nomatch' must be the only item on the input side of a rule");
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// Otherwise allocate memory for a rule and initialize the rule
|
|
rp = (RULE *)checked_alloc(sizeof(RULE),1);
|
|
rp->lhs = lhs;
|
|
rp->rhs = rhs;
|
|
rp->ilen = count_items(rp->lhs);
|
|
rp->olen = count_items(rp->rhs);
|
|
rp->next = NULL;
|
|
rp->line = line;
|
|
|
|
// Check the rule for validity
|
|
check_rule(rp,gp);
|
|
return rp;
|
|
}
|
|
|
|
// Add a rule to a rule list
|
|
RULE *add_rule(RULE *rp, RULE *prules)
|
|
{
|
|
if(rp) // must allow for null rules (blank lines)
|
|
{
|
|
rp->next = prules;
|
|
return rp;
|
|
}
|
|
else
|
|
{
|
|
return prules;
|
|
}
|
|
}
|
|
|
|
// Count the rules in a linked list of rules
|
|
int count_rules(RULE *rp)
|
|
{
|
|
int n;
|
|
|
|
for(n=0; rp!=NULL; rp=rp->next) n++;
|
|
|
|
return n;
|
|
}
|
|
|
|
// Check the left hand side of a rule, remove any '+' keystroke marker items,
|
|
// and expand any outs() keywords found
|
|
ITEM *check_lhs(ITEM *lhs, unsigned int ilen, GROUP *gp, int line)
|
|
{
|
|
STORE *sp;
|
|
ITEM *p, *p1, *ip, *ip0;
|
|
unsigned int i, j, k, newlen, goodplus=0, badplus=0;
|
|
char *fmt="%s cannot be used on the left hand side of a rule";
|
|
|
|
// Check for and remove any TOK_PLUS items, and warn if appropriate
|
|
for(i=0,p=lhs; i<ilen; i++, p++)
|
|
{
|
|
if(ITEM_TYPE(*p) == ITEM_PLUS)
|
|
{
|
|
if((gp->flags & GF_USEKEYS) && (i == ilen-2))
|
|
{
|
|
goodplus = 1;
|
|
}
|
|
else
|
|
{
|
|
badplus = 1;
|
|
}
|
|
for(j=i, p1=p; j<ilen; j++) *p1 = *(p1+1); //include trailing null
|
|
ilen = ilen-1;
|
|
i--, p--;
|
|
}
|
|
}
|
|
*p = 0; // ensure rule string terminated after compacting
|
|
|
|
if((gp->flags & GF_USEKEYS) && !goodplus && (Version[1] > '3' || Version[0] >= '1')) // needs review
|
|
{
|
|
kmflcomp_warn(line,"'+' should be used before the keystroke");
|
|
}
|
|
|
|
if(badplus)
|
|
{
|
|
if(Version[1] > '5' || Version[0] >= '1') // needs review
|
|
kmflcomp_error(line,"use '+' only immediately before keystroke");
|
|
else
|
|
kmflcomp_warn(line,"'+' used incorrectly (but ignored)");
|
|
}
|
|
|
|
// Make keystroke character explicitly a keysym (unless any, etc.)
|
|
if((gp->flags & GF_USEKEYS) == GF_USEKEYS)
|
|
{
|
|
p = lhs+ilen-1;
|
|
if(ITEM_TYPE(*p) == ITEM_CHAR)
|
|
{
|
|
*p = MAKE_ITEM(ITEM_KEYSYM,*p);
|
|
}
|
|
}
|
|
|
|
if(ilen != count_items(lhs)) fail(1,"fatal compiler error");
|
|
|
|
// Expand any outs() keywords on the left hand side of rules
|
|
for(i=0, p=lhs; i<ilen; i++, p++)
|
|
{
|
|
if(ITEM_TYPE(*p) == ITEM_OUTS)
|
|
{
|
|
sp = find_store(store_name(*p & 0xffff));
|
|
if(sp && (sp->len > 0))
|
|
{
|
|
newlen = ilen + sp->len - 1;
|
|
ip = ip0 = (ITEM *)checked_alloc(newlen,sizeof(ITEM));
|
|
for(j=0; j<i; j++) *ip++ = *(lhs+j);
|
|
for(k=0; k<sp->len; k++) *ip++ = *(sp->items+k);
|
|
for(++j; j<ilen; j++) *ip++ = *(lhs+j);
|
|
*ip = 0; mem_free(lhs); lhs = ip0;
|
|
ilen = newlen;
|
|
i--; p = lhs + i;
|
|
}
|
|
else
|
|
{
|
|
kmflcomp_error(line,"illegal use of 'outs()' keyword");
|
|
}
|
|
}
|
|
}
|
|
|
|
if(ilen != count_items(lhs)) fail(1,"fatal compiler error");
|
|
|
|
// Check for illegal keywords
|
|
for(i=0, p=lhs; i<ilen; i++, p++)
|
|
{
|
|
switch(ITEM_TYPE(*p))
|
|
{
|
|
case ITEM_RETURN:
|
|
kmflcomp_error(line,fmt,"'return'");
|
|
break;
|
|
case ITEM_BEEP:
|
|
kmflcomp_error(line,fmt,"'beep'");
|
|
break;
|
|
case ITEM_USE:
|
|
kmflcomp_error(line,fmt,"'use()'");
|
|
break;
|
|
case ITEM_CALL:
|
|
kmflcomp_error(line,fmt,"'call()'");
|
|
break;
|
|
}
|
|
}
|
|
return lhs; // return the pointer to the (possibly new) string
|
|
}
|
|
|
|
// Check the right hand side of a rule for illegal keywords
|
|
void check_rhs(ITEM *rhs, unsigned int olen, GROUP *gp, int line)
|
|
{
|
|
ITEM *p;
|
|
unsigned int i;
|
|
char *fmt="%s cannot be used on the right hand side of a rule";
|
|
|
|
for(i=0, p=rhs; i<olen; i++, p++)
|
|
{
|
|
switch(ITEM_TYPE(*p))
|
|
{
|
|
case ITEM_MATCH:
|
|
kmflcomp_error(line,fmt,"'match'");
|
|
break;
|
|
case ITEM_NOMATCH:
|
|
kmflcomp_error(line,fmt,"'nomatch'");
|
|
break;
|
|
case ITEM_ANY:
|
|
kmflcomp_error(line,fmt,"'any()'");
|
|
break;
|
|
case ITEM_CALL:
|
|
kmflcomp_error(line,"call() keyword is not implemented");
|
|
break;
|
|
case ITEM_USE: // test for and warn if group used recursively
|
|
if((unsigned)group_number(gp->name, line) == (*p & 0xffff))
|
|
kmflcomp_warn(line,"rule group used recursively (use() refers to the containing group)");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check that the match and output strings only contain legal items
|
|
void check_rule(RULE *rp, GROUP *gp)
|
|
{
|
|
if((rp->ilen != count_items(rp->lhs)) || (rp->olen != count_items(rp->rhs)))
|
|
fail(1,"fatal compiler error");
|
|
|
|
// Check and adjust the lhs of the rule
|
|
rp->lhs = check_lhs(rp->lhs,rp->ilen,gp,rp->line);
|
|
rp->ilen = count_items(rp->lhs); // must recalculate, as string may have been changed
|
|
|
|
// Check the rhs of the rule
|
|
check_rhs(rp->rhs,rp->olen,gp,rp->line);
|
|
}
|
|
|
|
// Rule comparison
|
|
int compare_rules(const void *arg1, const void *arg2)
|
|
{
|
|
RULE *rp1, *rp2;
|
|
|
|
rp1 = (RULE *)arg1;
|
|
rp2 = (RULE *)arg2;
|
|
|
|
// Check rule length first
|
|
if(rp1->ilen > rp2->ilen) return -1;
|
|
if(rp1->ilen < rp2->ilen) return 1;
|
|
|
|
// Then compare line numbers - process according to source code sequence
|
|
if(rp1->line < rp2->line) return -1;
|
|
if(rp1->line > rp2->line) return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Sort the rules in each group
|
|
void sort_rules(GROUP *gp)
|
|
{
|
|
RULE *rp, *rp1, *rptemp, *rpnext;
|
|
UINT n;
|
|
|
|
// Don't sort trivial groups!
|
|
if(gp->nrules < 2) return;
|
|
|
|
// Create a single linear array of rules and free the existing linked list
|
|
rptemp = (RULE *)checked_alloc(2*gp->nrules,sizeof(RULE));
|
|
for(n=0,rp=gp->rules,rp1=rptemp; n<gp->nrules; n++,rp=rpnext,rp1++)
|
|
{
|
|
*rp1 = *rp; rpnext = rp->next; mem_free(rp);
|
|
}
|
|
|
|
// Sort the array
|
|
qsort((void *)rptemp,(size_t)gp->nrules,sizeof(RULE),compare_rules);
|
|
|
|
// Relink the list
|
|
gp->rules = rptemp;
|
|
for(n=0,rp=rptemp; n<gp->nrules; n++,rp++) rp->next = rp+1;
|
|
rp->next = NULL;
|
|
}
|
|
|
|
// Routines for manipulating deadkeys
|
|
|
|
// Create a deadkey (I don't think this is ever needed - see deadkey_number)
|
|
DEADKEY *new_deadkey(char *name, int line)
|
|
{
|
|
DEADKEY *dp;
|
|
|
|
if((dp=find_deadkey(name)) == NULL)
|
|
{
|
|
dp = (DEADKEY *)checked_alloc(sizeof(DEADKEY),1);
|
|
if(last_deadkey != NULL) last_deadkey->next = dp;
|
|
last_deadkey = dp;
|
|
if(kbp->deadkeys == NULL) kbp->deadkeys = dp; // initialize pointer to list of deadkeys
|
|
}
|
|
checked_strcpy(dp->name,name,NAMELEN, "deadkey", line);
|
|
|
|
return dp;
|
|
}
|
|
|
|
// Find a deadkey by name
|
|
DEADKEY *find_deadkey(char *name)
|
|
{
|
|
DEADKEY *p;
|
|
for(p=kbp->deadkeys; p!=NULL; p=p->next)
|
|
{
|
|
if(strcasecmp(name,p->name) == 0) return p;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// Find a deadkey number
|
|
int deadkey_number(char *name, int line)
|
|
{
|
|
DEADKEY *dp, *dp0=NULL, *dp1;
|
|
int n;
|
|
for(n=0,dp=kbp->deadkeys; dp!=NULL; dp0=dp,dp=dp->next,n++)
|
|
{
|
|
if(strcasecmp(name,dp->name) == 0) return n;
|
|
}
|
|
// Create the deadkey if this is the first reference
|
|
dp1 = (DEADKEY *)checked_alloc(sizeof(DEADKEY),1);
|
|
if(dp0) dp0->next = dp1; else kbp->deadkeys = dp1;
|
|
|
|
kbp->ndeadkeys++;
|
|
checked_strcpy(dp1->name,name,NAMELEN, "deadkey", line);
|
|
|
|
return n;
|
|
}
|
|
|
|
|
|
// Routines for manipulating stores
|
|
|
|
// Add a store to the list of stores and set its contents
|
|
STORE *new_store(char *name, ITEM *ip0, int line)
|
|
{
|
|
STORE *sp, *osp;
|
|
ITEM *ip, *ipt, *osip;
|
|
int len;
|
|
|
|
if((sp=find_store(name)) != NULL)
|
|
{
|
|
if(sp->items != NULL)
|
|
{ mem_free(sp->items);
|
|
if(sp->len > 0)
|
|
kmflcomp_warn(line,"overwriting previous contents of store %s",name);
|
|
sp->items = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sp = (STORE *)checked_alloc(sizeof(STORE), 1);
|
|
if(kbp->stores == NULL) kbp->stores = sp; // initialize pointer to list of stores
|
|
checked_strcpy(sp->name,name,NAMELEN, "store", line);
|
|
sp->lineno=line;
|
|
if(last_store != NULL) last_store->next = sp;
|
|
last_store = sp;
|
|
sp->next = NULL;
|
|
}
|
|
|
|
// Allocate memory for this store, expanding any outs()
|
|
if(ip0)
|
|
{
|
|
for(len=0,ip=ip0; *ip; ip++)
|
|
{
|
|
switch(ITEM_TYPE(*ip))
|
|
{
|
|
case ITEM_CHAR:
|
|
case ITEM_KEYSYM:
|
|
case ITEM_DEADKEY:
|
|
case ITEM_BEEP:
|
|
len++;
|
|
break;
|
|
case ITEM_OUTS:
|
|
if((osp=find_store(store_name(*ip & 0xffff))))
|
|
len += osp->len;
|
|
break;
|
|
}
|
|
}
|
|
|
|
sp->items = checked_alloc(len+1,4);
|
|
sp->len = len;
|
|
|
|
for(ip=ip0,ipt=sp->items; *ip; ip++)
|
|
{
|
|
switch(ITEM_TYPE(*ip))
|
|
{
|
|
case ITEM_CHAR:
|
|
case ITEM_KEYSYM:
|
|
case ITEM_DEADKEY:
|
|
case ITEM_BEEP:
|
|
*ipt++ = *ip;
|
|
break;
|
|
case ITEM_OUTS:
|
|
if((osp=find_store(store_name(*ip & 0xffff))))
|
|
{
|
|
for(osip=osp->items;osip && *osip; osip++)
|
|
*ipt++ = *osip;
|
|
}
|
|
break;
|
|
default:
|
|
kmflcomp_error(line,"illegal item in store");
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Process special store commands and copy to header as required
|
|
if(*name == '&') process_special_store(name,sp,line);
|
|
|
|
// Free the item list (probably OK to do this here, but must check...)
|
|
mem_free(ip0);
|
|
}
|
|
else // allow for empty stores, just in case
|
|
{
|
|
sp->items = checked_alloc(1,4);
|
|
sp->len = 0;
|
|
}
|
|
|
|
return sp;
|
|
}
|
|
|
|
STORE *new_store_from_string(char *name, char *string,int line)
|
|
{
|
|
return new_store(name,items_from_string(string,line),line);
|
|
}
|
|
|
|
// Find a store by name
|
|
STORE *find_store(char *name)
|
|
{
|
|
STORE *p;
|
|
|
|
if(name != NULL)
|
|
{
|
|
for(p=kbp->stores; p!=NULL; p=p->next)
|
|
{
|
|
if(strcasecmp(name,p->name) == 0) return p;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// Find a store number
|
|
int store_number(char *name, int line)
|
|
{
|
|
STORE *p;
|
|
int n;
|
|
for(n=0,p=kbp->stores; p!=NULL; p=p->next,n++)
|
|
{
|
|
if(strcasecmp(name,p->name) == 0) return n;
|
|
}
|
|
|
|
// create the store
|
|
if (new_store(name, NULL, line) != NULL)
|
|
return n;
|
|
|
|
return UNDEFINED;
|
|
}
|
|
|
|
// Find a store name
|
|
char *store_name(int number)
|
|
{
|
|
STORE *sp;
|
|
int n;
|
|
for(n=0,sp=kbp->stores; sp!=NULL; sp=sp->next,n++)
|
|
{
|
|
if(n == number) return sp->name;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// Count the stores in a linked list of stores
|
|
int count_stores(STORE *sp)
|
|
{
|
|
int n;
|
|
|
|
for(n=0; sp!=NULL; sp=sp->next) n++;
|
|
|
|
return n;
|
|
}
|
|
|
|
// Create a new itemlist
|
|
ITEM *new_list(ITEM q)
|
|
{
|
|
ITEM *p;
|
|
p = (ITEM *)checked_alloc(2,4);
|
|
*p = q;
|
|
return p;
|
|
}
|
|
|
|
// Add an item to an itemlist
|
|
ITEM *add_item_to_list(ITEM *s1, ITEM q)
|
|
{
|
|
ITEM *p, *p0;
|
|
int i, n;
|
|
|
|
if((n=count_items(s1)) == 0) return new_list(q);
|
|
|
|
p = p0 = (ITEM *)checked_alloc(n+1,4);
|
|
*p++ = q;
|
|
for(i=0; i<n; i++) *p++ = *(s1+i);
|
|
*p = 0;
|
|
mem_free(s1);
|
|
return p0;
|
|
}
|
|
|
|
// Count items in an item list
|
|
unsigned int count_items(ITEM *p)
|
|
{
|
|
int n;
|
|
|
|
for(n=0; *p; p++, n++);
|
|
|
|
return n;
|
|
}
|
|
|
|
// Concatenate two item lists
|
|
ITEM *add_lists(ITEM *s1, ITEM *s2)
|
|
{
|
|
ITEM *p, *p0;
|
|
int i, n1, n2;
|
|
n1 = count_items(s1); n2 = count_items(s2);
|
|
p = p0 = (ITEM *)checked_alloc(n1+n2+1,4);
|
|
for(i=0; i<n2; i++) *p++ = *(s2+i);
|
|
for(i=0; i<n1; i++) *p++ = *(s1+i);
|
|
*p = 0;
|
|
mem_free(s1); mem_free(s2);
|
|
return p0;
|
|
}
|
|
|
|
// Create a new string
|
|
char *new_string(int q)
|
|
{
|
|
char *p;
|
|
p = (char *)checked_alloc(2,1);
|
|
*p = (char)q;
|
|
return p;
|
|
}
|
|
|
|
// Add a character to the start of a string
|
|
char *add_char(char *sp, int q)
|
|
{
|
|
char *p;
|
|
int n;
|
|
|
|
if(!sp) return new_string(q);
|
|
|
|
n = strlen(sp);
|
|
p = (char *)checked_alloc(n+2,1);
|
|
*p = (char)q; strcpy(p+1, sp);
|
|
return p;
|
|
}
|
|
|
|
// Convert a string to an item list, and allocate memory for the list
|
|
ITEM *items_from_string(char *sp, int line)
|
|
{
|
|
ITEM *p, *p0;
|
|
int i, n;
|
|
char * s0;
|
|
size_t result;
|
|
|
|
n = strlen(sp);
|
|
p = p0 = (ITEM *)checked_alloc(n+1,sizeof(ITEM));
|
|
// Convert UTF-8 strings to Unicode if and only if file format is UTF-8
|
|
// Note: The allocated memory will be longer than needed,but the string will be
|
|
// released again as soon as the string is copied to a store or rule
|
|
if(file_format == KF_UNICODE)
|
|
{
|
|
s0=sp;
|
|
result = IConvertUTF8toUTF32((const UTF8 **)&sp,(const UTF8 *)(sp+n),(UTF32 **)&p,(UTF32 *)(p0+n+1));
|
|
// Use ANSI conversion if UTF-8 conversion failed (for compatibility with old source files)
|
|
if(result != 0)
|
|
{
|
|
p=p0;
|
|
kmflcomp_warn(line,"file format is UTF-8, but non-UTF-8 characters found and converted as ANSI");
|
|
for(i=0; i<n; i++) *p++ = (*(s0+i))&255;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(i=0; i<n; i++) *p++ = *(BYTE *)(sp+i);
|
|
}
|
|
*p = 0;
|
|
|
|
return p0;
|
|
}
|
|
|
|
// Convert an item list to a simple string (mainly for debugging)
|
|
char *items_to_string(ITEM *p)
|
|
{
|
|
static char temp[256];
|
|
|
|
char *sp, *sp1;
|
|
//int n;
|
|
//n = count_items(p);
|
|
|
|
for(sp=temp,sp1=temp+240,*sp=0; (*p) && (sp<sp1); p++)
|
|
{
|
|
switch(ITEM_TYPE(*p))
|
|
{
|
|
case ITEM_CHAR:
|
|
if(*p&0xffff80)
|
|
sp += sprintf(sp,"[0x%x]",(*p&0xffffff));
|
|
else
|
|
*sp++ = (char)*p;
|
|
break;
|
|
case ITEM_KEYSYM:
|
|
sp += sprintf(sp,"[key %x,0x%x]",(*p&0xff0000)>>16,(*p&0xff));
|
|
break;
|
|
case ITEM_ANY:
|
|
sp += sprintf(sp,"[any %u]",(unsigned)(*p&0xffff));
|
|
break;
|
|
case ITEM_INDEX:
|
|
sp += sprintf(sp,"[index %d,%u]",(*p&0xff0000)>>16,(*p&0xffff));
|
|
break;
|
|
case ITEM_OUTS:
|
|
sp += sprintf(sp,"[outs %u]",(*p&0xffff));
|
|
break;
|
|
case ITEM_DEADKEY:
|
|
sp += sprintf(sp,"[dk %u]",(*p&0xffff));
|
|
break;
|
|
case ITEM_CONTEXT:
|
|
if(*p & 0xff)
|
|
sp += sprintf(sp,"[context %u]",(*p&0xff));
|
|
else
|
|
sp += sprintf(sp,"[context]");
|
|
break;
|
|
case ITEM_NUL:
|
|
sp += sprintf(sp,"[nul]");
|
|
break;
|
|
case ITEM_RETURN:
|
|
sp += sprintf(sp,"[return]");
|
|
break;
|
|
case ITEM_BEEP:
|
|
sp += sprintf(sp,"[beep]");
|
|
break;
|
|
case ITEM_USE:
|
|
sp += sprintf(sp,"[use %u]",(*p&0xffff));
|
|
break;
|
|
case ITEM_MATCH:
|
|
sp += sprintf(sp,"[match]");
|
|
break;
|
|
case ITEM_NOMATCH:
|
|
sp += sprintf(sp,"[nomatch]");
|
|
break;
|
|
case ITEM_PLUS:
|
|
sp += sprintf(sp,"[+]");
|
|
break;
|
|
case ITEM_CALL:
|
|
sp += sprintf(sp,"[use %u]",(*p&0xffff));
|
|
break;
|
|
}
|
|
}
|
|
*sp = 0;
|
|
|
|
return temp;
|
|
}
|
|
|
|
// List of special stores with reserved names
|
|
char *special_stores[] = {
|
|
"&name","&version","&hotkey","&language","&layout","©right","&message",
|
|
"&bitmap","&mnemoniclayout","ðnologuecode",
|
|
"&capsalwaysoff","&capsononly","&shiftfreescaps","&author",
|
|
"&targets","&visualkeyboard","&keyboardversion"};
|
|
|
|
// stores new from keyman 7.0 onwards that can safely be ignored for kmfl 11 for now
|
|
char *ignored_stores[] = {
|
|
"&layoutfile","&kmw_helpfile","&kmw_embedjs",
|
|
"&windowslanguages","&kmw_rtl",
|
|
"&kmw_embedcss","&kmw_helptext","&includecodes"};
|
|
|
|
// Initialize special stores (create dummy entries)
|
|
void initialize_special_stores(void)
|
|
{
|
|
int i;
|
|
for(i=0; i<sizeof(special_stores)/sizeof(char *); i++)
|
|
{
|
|
new_store(special_stores[i],NULL,0);
|
|
}
|
|
}
|
|
|
|
// Copy special store contents to keyboard header as required
|
|
void process_special_store(char *name, STORE *sp, int line)
|
|
{
|
|
int n, p, kbver;
|
|
|
|
// Identify store by name
|
|
for(n=0; n<sizeof(special_stores)/sizeof(char *); n++)
|
|
{
|
|
if(strcasecmp(name,special_stores[n]) == 0 ) break;
|
|
}
|
|
|
|
// Specific action required by header
|
|
switch(n)
|
|
{
|
|
case SS_NAME:
|
|
break;
|
|
case SS_HOTKEY:
|
|
kbp->hotkey = sp->items[0];
|
|
break;
|
|
case SS_MNEMONIC:
|
|
kbp->layout = (sp->items[0] != '0');
|
|
break;
|
|
case SS_CAPSOFF:
|
|
kbp->capsoff = 1;
|
|
break;
|
|
case SS_CAPSON:
|
|
kbp->capson = 1;
|
|
break;
|
|
case SS_CAPSFREE:
|
|
kbp->capsfree = 1;
|
|
break;
|
|
case SS_VERSION:
|
|
kbver = (int)(100.0*atof(items_to_string(sp->items))+0.5);
|
|
sprintf(Version,"%4.4d%1.1s",kbver,FILE_VERSION);
|
|
break;
|
|
case SS_BITMAP:
|
|
check_bitmap_file(sp,line);
|
|
break;
|
|
case SS_TARGETS:
|
|
if (!check_linux_target(sp,line))
|
|
fail(1, "Line %d: No valid linux target in %s", line, items_to_string(sp->items));
|
|
//kmflcomp_warn(line, "No valid linux target.");
|
|
break;
|
|
case SS_COPYRIGHT:
|
|
case SS_MESSAGE:
|
|
case SS_LAYOUT:
|
|
case SS_AUTHOR:
|
|
case SS_LANGUAGE:
|
|
case SS_ETHNOLOGUE:
|
|
case SS_KEYBOARDVERSION:
|
|
case SS_VISUALKEYBOARD:
|
|
break;
|
|
default:
|
|
for(p=0; p<sizeof(ignored_stores)/sizeof(char *); p++)
|
|
{
|
|
if(strcasecmp(name,ignored_stores[p]) == 0 ) break;
|
|
}
|
|
if (p==sizeof(ignored_stores)/sizeof(char *))
|
|
kmflcomp_warn(line-1,"unrecognized special store '&%s'",name);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Error, warning and debug messages
|
|
|
|
void fail(int errcode, char *s, ...)
|
|
{
|
|
char t[512];
|
|
va_list v1;
|
|
|
|
va_start(v1,s);
|
|
vsnprintf(t,511,s,v1);
|
|
va_end(v1);
|
|
|
|
fprintf(stderr, "*** Compilation failed: %s ***\n", t);
|
|
fflush(stderr);
|
|
|
|
#ifdef _WIN32
|
|
if(opt_debug) getch();
|
|
#endif
|
|
longjmp(fatal_error_buf, -errcode);
|
|
}
|
|
|
|
void kmflcomp_error(int lineno,char *s, ...)
|
|
{
|
|
char t[512];
|
|
va_list v1;
|
|
|
|
if(++errcount > errlimit) return;
|
|
|
|
va_start(v1,s);
|
|
vsnprintf(t,511,s,v1);
|
|
va_end(v1);
|
|
|
|
if(lineno)
|
|
fprintf(stderr, " Error: %s (line %d)\n", t, lineno);
|
|
else
|
|
fprintf(stderr, " Error: %s\n", t);
|
|
|
|
if(errcount == errlimit)
|
|
fprintf(stderr, " -------(remaining errors unreported)-------\n");
|
|
}
|
|
|
|
void dwarn(int lineno,char *fmt,...)
|
|
{
|
|
va_list args;
|
|
|
|
fprintf(stderr,"debug: ");
|
|
va_start(args,fmt);
|
|
vfprintf(stderr,fmt,args);
|
|
va_end(args);
|
|
}
|
|
void kmflcomp_warn(int lineno,char *s, ...)
|
|
{
|
|
va_list v1;
|
|
|
|
if(++warncount > warnlimit) return;
|
|
|
|
fprintf(stderr, " Warning: ");
|
|
va_start(v1,s);
|
|
vfprintf(stderr,s,v1);
|
|
va_end(v1);
|
|
|
|
if(lineno)
|
|
fprintf(stderr, " (line %d)\n", lineno);
|
|
else
|
|
fprintf(stderr, " \n");
|
|
|
|
if(warncount == warnlimit)
|
|
fprintf(stderr, " -------(remaining warnings unreported)-------\n");
|
|
}
|
|
void debug(int lineno,char *s, ...)
|
|
{
|
|
char t[512];
|
|
va_list v1;
|
|
|
|
if(!opt_debug) return;
|
|
|
|
va_start(v1,s);
|
|
vsnprintf(t,511,s,v1);
|
|
va_end(v1);
|
|
|
|
if(lineno)
|
|
fprintf(stderr, "Debug: %s (line %d)\n", t, lineno);
|
|
else
|
|
fprintf(stderr, "Debug: %s\n", t);
|
|
}
|
|
|
|
// Checked memory allocation, always allocating one more element than requested
|
|
void *checked_alloc(size_t n, size_t sz)
|
|
{
|
|
void *p;
|
|
|
|
if(!(p=mem_calloc(n+1,sz))) fail(4,"out of memory!");
|
|
|
|
return p;
|
|
}
|
|
|
|
char * checked_strcpy(char * dst, char * src, int len, char * type, int line)
|
|
{
|
|
strncpy(dst, src, len);
|
|
|
|
dst[len]='\0';
|
|
|
|
if (strlen(src) > len)
|
|
kmflcomp_warn(line,"%s name %s exceeds %d characters, truncating to %s", type, src, len, dst);
|
|
|
|
return dst;
|
|
}
|
|
|
|
// Convert the first (Unicode) character of a UTF-8 (or ANSI) text string to a keysym
|
|
ITEM string_to_keysym(char *sp, int line)
|
|
{
|
|
ITEM *ip, keysym=0;
|
|
unsigned int iplen;
|
|
|
|
ip = items_from_string(sp,line);
|
|
if(ip)
|
|
{
|
|
iplen = count_items(ip);
|
|
if(iplen > 0) keysym = *ip;
|
|
if(iplen > 1) kmflcomp_warn(line,"only the first character can be used in a keysym");
|
|
mem_free(ip);
|
|
}
|
|
if(keysym == 0) kmflcomp_error(line,"illegal keysym/virtual character key");
|
|
|
|
return keysym;
|
|
}
|
|
|
|
// Convert an virtual keysym string to an actual keysym (XK_HYPHEN, etc)
|
|
ITEM text_to_keysym(char * str)
|
|
{
|
|
ITEM keysym = -1;
|
|
#ifndef _WIN32
|
|
if (strlen(str) > 3)
|
|
{
|
|
keysym = XStringToKeysym(str+3);
|
|
|
|
if (keysym == NoSymbol)
|
|
keysym = -1;
|
|
}
|
|
#endif
|
|
return keysym;
|
|
}
|
|
// Combine shift state and key code as required for Linux
|
|
ITEM make_keysym(int lineno, ITEM state, ITEM q)
|
|
{
|
|
// Mask character and state
|
|
|
|
q &= 0xffff; state &= 0xff;
|
|
|
|
if ((q & 0xff00) == 0)
|
|
{
|
|
int keycode;
|
|
int shifted=((state & KS_SHIFT) == 0) ^ ((state & KS_CAPS) == 0);
|
|
#ifndef _WIN32
|
|
Display * display;
|
|
display = XOpenDisplay(NULL);
|
|
|
|
if (display)
|
|
{
|
|
keycode = XKeysymToKeycode (display, q & 0x7F);
|
|
q=XKeycodeToKeysym(display, keycode, !shifted ? 0 : 1);
|
|
XCloseDisplay(display);
|
|
}
|
|
else
|
|
#endif
|
|
if (isalpha(q))
|
|
{
|
|
if (!shifted)
|
|
{
|
|
q += 0x20;
|
|
}
|
|
}
|
|
else if (((state & KS_SHIFT) == 0) || ((state & KS_CAPS) == 0))
|
|
{
|
|
kmflcomp_warn(lineno, "Non-alphabetic virtual key sequence used with K_SHIFT or K_CAPS outside of the X environment.\n"
|
|
" KMFLCOMP cannot determined correct shifted keysym");
|
|
}
|
|
state &= ~KS_CAPS;
|
|
}
|
|
return q | (state<<16) | (ITEM_KEYSYM<<24);
|
|
}
|
|
|
|
ITEM make_xkeysym(int lineno, ITEM state, ITEM q)
|
|
{
|
|
// Mask character and state
|
|
q &= 0xffff; state &= 0xff;
|
|
return q | (state<<16) | (ITEM_KEYSYM<<24);
|
|
}
|
|
|
|
// Find the first file that matches the given path, ignoring case
|
|
// Substitute the actual name, and return (or return NULL)
|
|
#ifdef _WIN32
|
|
// Windows version
|
|
char *find_first_match(char *path)
|
|
{
|
|
char *p=NULL;
|
|
struct stat fstat;
|
|
|
|
if(stat(path,&fstat) == 0)
|
|
{
|
|
p = rindex(path,DIRDELIM);
|
|
return (p==NULL ? path : p+1);
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
#else
|
|
// Linux version: needs to be researched later
|
|
char *find_first_match(char *path)
|
|
{
|
|
char *p=NULL;
|
|
struct stat fstat;
|
|
|
|
if(stat(path,&fstat) == 0)
|
|
{
|
|
p = rindex(path,DIRDELIM);
|
|
return (p==NULL ? path : p+1);
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// valid targets that should compile for linux
|
|
char *valid_targets[] = {
|
|
"any","linux","desktop","windows","macosx"};
|
|
int check_linux_target(STORE *sp, int line)
|
|
{
|
|
char *p, *ptr, tname[128];
|
|
char delim[] = " ";
|
|
UTF32 *p1;
|
|
UTF8 *p2;
|
|
int n;
|
|
|
|
p1 = (UTF32*)sp->items;
|
|
p2 = (UTF8*)tname;
|
|
IConvertUTF32toUTF8((const UTF32 **)&p1,(const UTF32 *)(sp->items+sp->len),&p2,(UTF8 *)(tname+127));
|
|
*p2 = 0;
|
|
p = (char *)tname;
|
|
ptr = strtok(p, delim);
|
|
while(ptr != NULL)
|
|
{
|
|
for(n=0; n<sizeof(valid_targets)/sizeof(char *); n++)
|
|
{
|
|
if(strcasecmp(ptr,valid_targets[n]) == 0 ) {
|
|
return 1;
|
|
}
|
|
}
|
|
ptr = strtok(NULL, delim);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Check that the bitmap file exists as specified, or else that a file with an accepted variation
|
|
// of the name exists (different case, .bmp or .png suffixes)
|
|
int check_bitmap_file(STORE *sp, int line)
|
|
{
|
|
char *p,*bmp_path=NULL,*icons_path=NULL,tname[256];
|
|
struct stat fstat;
|
|
UINT i;
|
|
UTF32 *p1,*titems=NULL;
|
|
UTF8 *p2;
|
|
|
|
p1 = (UTF32*)sp->items;
|
|
p2 = (UTF8*)tname;
|
|
IConvertUTF32toUTF8((const UTF32 **)&p1,(const UTF32 *)(sp->items+sp->len),&p2,(UTF8 *)(tname+255));
|
|
*p2 = 0;
|
|
|
|
if((p=rindex(fname,DIRDELIM)) != NULL)
|
|
{
|
|
bmp_path = (char *)checked_alloc((p-fname+1)+strlen(tname)+10,1);
|
|
strncpy(bmp_path,fname,p-fname+1);
|
|
strcpy(bmp_path+(p-fname+1),tname);
|
|
}
|
|
else
|
|
{
|
|
bmp_path = (char *)checked_alloc(strlen(tname)+10,1);
|
|
strcpy(bmp_path,tname);
|
|
}
|
|
|
|
if (strncmp(bmp_path+strlen(bmp_path)-4, ".ico", 4) == 0)
|
|
{
|
|
strcat(bmp_path,".bmp");
|
|
}
|
|
// First test if the file exists exactly as in BITMAP statement
|
|
else if(stat(bmp_path,&fstat) == 0)
|
|
{
|
|
mem_free(bmp_path); // file exists
|
|
return 0;
|
|
}
|
|
|
|
// Search for a case-variation (irrelevant for Windows)
|
|
p = find_first_match(bmp_path);
|
|
|
|
// Search in icons dir
|
|
if(p == NULL)
|
|
{
|
|
icons_path = (char *)checked_alloc(strlen(bmp_path)+12,1);
|
|
if((p=rindex(fname,DIRDELIM)) != NULL)
|
|
{
|
|
strncpy(icons_path,fname,p-fname+1);
|
|
strcpy(icons_path+(p-fname+1),"icons/");
|
|
strcpy(icons_path+(p-fname+1+6),tname);
|
|
}
|
|
else
|
|
{
|
|
strcpy(icons_path,"icons/");
|
|
strcat(icons_path, bmp_path);
|
|
}
|
|
p = find_first_match(icons_path);
|
|
}
|
|
|
|
// If no extension specified, search next for files with extensions .bmp and .png
|
|
if((p == NULL) && (strchr(tname,'.') == NULL))
|
|
{
|
|
// Add .bmp
|
|
strcat(bmp_path,".bmp");
|
|
p = find_first_match(bmp_path);
|
|
if ((p == NULL) && icons_path)
|
|
{
|
|
// Add .bmp in icons dir
|
|
strcat(icons_path,".bmp");
|
|
p = find_first_match(icons_path);
|
|
}
|
|
if(p == NULL)
|
|
{
|
|
// Add .png
|
|
strcpy(bmp_path+strlen(bmp_path)-4,".png");
|
|
p = find_first_match(bmp_path);
|
|
}
|
|
if ((p == NULL) && icons_path)
|
|
{
|
|
// Add .png in icons dir
|
|
strcpy(icons_path+strlen(icons_path)-4,".png");
|
|
p = find_first_match(icons_path);
|
|
}
|
|
if(p == NULL)
|
|
{
|
|
// Add .ico.bmp (ico converted to bmp)
|
|
strcpy(bmp_path+strlen(bmp_path)-4,".ico.bmp");
|
|
p = find_first_match(bmp_path);
|
|
}
|
|
}
|
|
|
|
// If there is an extension but not found, try .bmp added (converted from .ico)
|
|
if(p == NULL)
|
|
{
|
|
strcat(bmp_path, ".bmp");
|
|
p = find_first_match(bmp_path);
|
|
}
|
|
|
|
// Issue appropriate warnings and replace file name in store
|
|
if(p == NULL)
|
|
{
|
|
kmflcomp_warn(line,"The bitmap file '%s' was not found. Create a suitable bitmap of that name "
|
|
"and copy it with the compiled keyboard",tname);
|
|
}
|
|
else
|
|
{
|
|
if (icons_path)
|
|
{
|
|
p = p - 6; // include 'icons/' in name
|
|
}
|
|
kmflcomp_warn(line,"A bitmap named '%s' was found and will be referred to in the compiled keyboard "
|
|
"instead of '%s'",p,tname);
|
|
|
|
if(sp->len)mem_free(sp->items);
|
|
// First allocate more than will be needed
|
|
titems = (UTF32 *)checked_alloc(strlen(p)+1,sizeof(UTF32));
|
|
p2 = (UTF8*)p; p1 = titems;
|
|
IConvertUTF8toUTF32((const UTF8 **)&p2,(UTF8*)(p+strlen(p)),(UTF32 **)&p1,p1+strlen(p));
|
|
sp->len = (UINT)(p1 - titems); // Then reallocate to exact length
|
|
sp->items = (ITEM *)checked_alloc(sp->len,sizeof(ITEM));
|
|
for(i=0; i<sp->len; i++) *(sp->items+i) = *(titems+i);
|
|
mem_free(titems);
|
|
}
|
|
|
|
if(bmp_path) mem_free(bmp_path);
|
|
if(icons_path) mem_free(icons_path);
|
|
return 1;
|
|
}
|
|
|
|
// Create a temporary file with a UTF-8 copy of a UTF-16 input file
|
|
FILE *UTF16toUTF8(FILE *fp)
|
|
{
|
|
FILE *fp8;
|
|
unsigned short t16[512];
|
|
unsigned char t8[2048];
|
|
const UTF16 *p16,*p16a;
|
|
UTF8 *p8;
|
|
|
|
if((fp8=tmpfile()) == NULL) return NULL;
|
|
|
|
#ifdef _WIN32
|
|
_setmode(_fileno(fp),_O_BINARY);
|
|
#endif
|
|
|
|
fseek(fp,2,SEEK_SET);
|
|
while(fread(t16,2,1,fp))
|
|
{
|
|
p16 = t16; p8 = t8; p16a = p16+1;
|
|
if(IConvertUTF16toUTF8(&p16,p16a,&p8,t8+2047) == 0)
|
|
{
|
|
if (fwrite(t8,1,(size_t)(p8-t8),fp8) == 0)
|
|
fail(1, "unable to write to temporary file %s", fp8);
|
|
}
|
|
else
|
|
fail(1,"unable to convert Unicode file, illegal or malformed UTF16 sequence");
|
|
}
|
|
|
|
fseek(fp8,0,SEEK_SET);
|
|
|
|
return fp8;
|
|
}
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
#include <windows.h>
|
|
|
|
#define OFN_DONTADDTORECENT 0x2000000
|
|
|
|
char *GetInputFile(void)
|
|
{
|
|
OPENFILENAME OFN={0};
|
|
static char FileName[MAX_PATH], KeyboardName[32];
|
|
|
|
OFN.lStructSize = sizeof(OPENFILENAME);
|
|
OFN.hwndOwner = NULL;
|
|
OFN.lpstrFilter = "Uncompiled Keyboard Files (*.kmn)\0*.kmn\0";
|
|
OFN.lpstrCustomFilter = NULL;
|
|
OFN.nFilterIndex = 1;
|
|
OFN.lpstrFile = FileName;
|
|
OFN.nMaxFile = MAX_PATH-1;
|
|
OFN.lpstrFileTitle = KeyboardName;
|
|
OFN.nMaxFileTitle = 32;
|
|
OFN.lpstrInitialDir = NULL;
|
|
|
|
OFN.lpstrTitle = "Choose keyboard to compile";
|
|
OFN.Flags = OFN_EXPLORER | OFN_HIDEREADONLY | OFN_NOCHANGEDIR
|
|
| OFN_DONTADDTORECENT | OFN_FILEMUSTEXIST;
|
|
OFN.lpstrDefExt = "kmn";
|
|
OFN.lpfnHook = NULL;
|
|
return(GetOpenFileName(&OFN) != 0 ? FileName : NULL);
|
|
}
|
|
#endif
|