/*
 * loadkeys.y
 *
 * Changes for 0.82:
 * Merged from version 0.81 of loadkeys.y and mktable.c - aeb@cwi.nl
 * Reason for change:
 *   The original version of mktable would use the current kernel
 *   for getting at the entries. However, this does not work if
 *   e.g. NR_FUNC in keyboard.h has changed. So, instead of getting
 *   the information from the current kernel, we must get it out of
 *   defkeymap.map, just like loadkeys. Thus, mktable is now an
 *   option of loadkeys.
 * (Other advantage: we first do the parsing, and then the key binding.
 *  No bindings are changed in case of a syntax error.)
 * Fix: contrary to the documentation in keytables.5 it was assumed
 * by default that the AltGr modifier had no effect. This assumption
 * has been removed.
 *
 * Changes for 0.83:
 * Added the intl_con patch by Eugene G. Crosser:
 * The modifier + in front of a keysym means that it is a letter,
 * and susceptible for change under CapsLock. For ASCII 'a'-'z'
 * and 'A'-'Z' no + is required (when given as a single entry).
 *
 * Changes for 0.84:
 * More compose key support. Default search path. Option -d.
 *
 * Change for 0.85:
 * Do not change compose key table when no definitions given.
 * Option -c to override this.
 *
 * Changes for 0.86:
 * Added a few synonyms. Comment may now contain non-ASCII symbols.
 *
 * Changes for 0.87:
 * Accept the "charset iso-8859-x" directive.
 */

%token EOL NUMBER LITERAL CHARSET KEYCODE EQUALS SHIFT CONTROL ALT ALTGR STRING STRLITERAL COMPOSE TO CCHAR ERROR PLUS

%{
#include <stdio.h>
#include <getopt.h>
#include <string.h>
#include <fcntl.h>
#include <linux/kd.h>
#include <linux/keyboard.h>
#include <sys/ioctl.h>
#include <ctype.h>
#include "paths.h"

#ifndef KT_LETTER
#define KT_LETTER KT_LATIN
#endif

/* the kernel structures we want to set or print */
u_short key_map[NR_KEYMAPS][NR_KEYS];
char func_buf[FUNC_BUFSIZE];
char *func_table[NR_FUNC];
struct kbdiacr accent_table[MAX_DIACR];
unsigned int accent_table_size = 0;

char keymap_was_set[NR_KEYMAPS][NR_KEYS];
char *fp = func_buf;

#undef ECHO
#include "analyze.c"

#define VERSION "0.87"

static void addkey(int index, int table, int keycode);
static void addfunc(struct kbsentry kbs_buf);
static void compose(int diacr, int base, int res);
static void loadkeys(void);
static void mktable(void);
extern void set_charset(char *charset);
int key_buf[16];
int mod;
%}

%%
keytable	:
		| keytable line
		;
line		: EOL
		| charsetline
		| fullline
		| singleline
		| strline
                | compline
		;
charsetline	: CHARSET STRLITERAL EOL
			{
			    set_charset(kbs_buf.kb_string);
			}
strline		: STRING LITERAL EQUALS STRLITERAL EOL
			{
			    if (KTYP($2) != KT_FN) {
				printf("'%s' is not a function key symbol\n",
				       syms[KTYP($2)].table[KVAL($2)]);
				exit(1);
			    }
			    kbs_buf.kb_func = KVAL($2);
			    addfunc(kbs_buf);
			}
		;
compline        : COMPOSE CCHAR CCHAR TO CCHAR EOL
                        {
			    compose($2, $3, $5);
			}
                ;
singleline	:	{ mod = 0; }
		  modifiers KEYCODE NUMBER EQUALS rvalue EOL
			{
			    addkey($4, mod, $6);
			}
		;
modifiers	: modifiers modifier
		| modifier
		;
modifier	: SHIFT		{ mod |= (1 << KG_SHIFT);	}
		| CONTROL	{ mod |= (1 << KG_CTRL);	}
		| ALT		{ mod |= (1 << KG_ALT);		}
		| ALTGR		{ mod |= (1 << KG_ALTGR);	}
		;
fullline	: KEYCODE NUMBER EQUALS rvalue0 EOL
			{
			int i;

			for (i = 0; i < $4; i++)
			  addkey($2, i, key_buf[i]);
#if 0
			if ($4 == 2) {
			    for (i = 2; i < NR_KEYMAPS; i++)
			      addkey($2, i, key_buf[i%2]);
			}
#endif
			if ($4 == 1) {
			    int typ0 = KTYP(key_buf[0]);
			    int val0 = KVAL(key_buf[0]);
			    if (typ0 != KT_LATIN &&
				typ0 != KT_LETTER)
			      for (i = 1; i < NR_KEYMAPS; i++)
				addkey($2, i, key_buf[0]);
			    else if (
				     (val0 >= 'a' && val0 <= 'z') ||
				     (val0 >= 'A' && val0 <= 'Z')
				     ) {
				int j;
				key_buf[0] = K(KT_LETTER, val0);
       /* shift		*/      key_buf[1] = K(KT_LETTER, val0 ^ 32);
       /* altgr		*/      key_buf[2] = key_buf[0];
       /* altgr+shift	*/      key_buf[3] = key_buf[1];
				for(j=4; j<8; j++)
       /* control ...	*/	  key_buf[j] = K(KT_LATIN, val0 & ~96);
				for(j=8; j<16; j++)
       /* alt ...	*/	  key_buf[j] = K(KT_META, KVAL(key_buf[j-8]));
				for(i=0; i<16; i++)
				  addkey($2, i, key_buf[i]);
			    }
		        }
			for (; i < NR_KEYMAPS; i++)
			  addkey($2, i, K_HOLE);
			}
		;

rvalue0		: { $$ = 0; }
		| rvalue rvalue1
			{
			    key_buf[0] = $1;
			    $$ = $2 + 1;
			}
		;
rvalue1		: { $$ = 0; }
		| rvalue rvalue2
			{
			    key_buf[1] = $1;
			    $$ = $2 + 1;
			}
		;
rvalue2		: { $$ = 0; }
		| rvalue rvalue3
			{
			    key_buf[2] = $1;
			    $$ = $2 + 1;
			}
		;
rvalue3		: { $$ = 0; }
		| rvalue rvalue4
			{
			    key_buf[3] = $1;
			    $$ = $2 + 1;
			}
		;
rvalue4		: { $$ = 0; }
		| rvalue rvalue5
			{
			    key_buf[4] = $1;
			    $$ = $2 + 1;
			}
		;
rvalue5		: { $$ = 0; }
		| rvalue rvalue6
			{
			    key_buf[5] = $1;
			    $$ = $2 + 1;
			}
		;
rvalue6		: { $$ = 0; }
		| rvalue rvalue7
			{
			    key_buf[6] = $1;
			    $$ = $2 + 1;
			}
		;
rvalue7		: { $$ = 0; }
		| rvalue rvalue8
			{
			    key_buf[7] = $1;
			    $$ = $2 + 1;
			}
		;
rvalue8		: { $$ = 0; }
		| rvalue rvalue9
			{
			    key_buf[8] = $1;
			    $$ = $2 + 1;
			}
		;
rvalue9		: { $$ = 0; }
		| rvalue rvalue10
			{
			    key_buf[9] = $1;
			    $$ = $2 + 1;
			}
		;
rvalue10	: { $$ = 0; }
		| rvalue rvalue11
			{
			    key_buf[10] = $1;
			    $$ = $2 + 1;
			}
		;
rvalue11	: { $$ = 0; }
		| rvalue rvalue12
			{
			    key_buf[11] = $1;
			    $$ = $2 + 1;
			}
		;
rvalue12	: { $$ = 0; }
		| rvalue rvalue13
			{
			    key_buf[12] = $1;
			    $$ = $2 + 1;
			}
		;
rvalue13	: { $$ = 0; }
		| rvalue rvalue14
			{
			    key_buf[13] = $1;
			    $$ = $2 + 1;
			}
		;
rvalue14	: { $$ = 0; }
		| rvalue rvalue15
			{
			    key_buf[14] = $1;
			    $$ = $2 + 1;
			}
		;
rvalue15	: { $$ = 0; }
		| rvalue
			{
			    key_buf[15] = $1;
			    $$ = 1;
			}
		;
rvalue		: NUMBER
			{$$=$1;}
                | PLUS NUMBER
                        {$$=K(KT_LETTER, KVAL($2));}
		| LITERAL
			{$$=$1;}
                | PLUS LITERAL
                        {$$=K(KT_LETTER, KVAL($2));}
		;
%%			

void
usage(void) {
	fprintf(stderr, "\
loadkeys version " VERSION "\

Usage: loadkeys [option...] [mapfile...]

valid options are:

        -c --clearcompose clear kernel compose table
	-d --default	  load \"" DEFMAP "\"
	-h --help	  display this help text
        -m --mktable      output a \"defkeymap.c\" to stdout
        -v --verbose      report the changes
");
	exit(1);
}

char **args;
char *progname;
int optd = 0;
int optm = 0;
int verbose = 0;
int nocompose = 0;

int
main(unsigned int argc, char *argv[]) {
	const char *short_opts = "cdhmv";
	const struct option long_opts[] = {
		{ "clearcompose", no_argument, NULL, 'c' },
	        { "default",    no_argument, NULL, 'd' },
		{ "help",	no_argument, NULL, 'h' },
		{ "mktable",    no_argument, NULL, 'm' },
		{ "verbose",    no_argument, NULL, 'v' },
		{ NULL, 0, NULL, 0 }
	};
	int c;

	progname = argv[0];

	while ((c = getopt_long(argc, argv,
		short_opts, long_opts, NULL)) != -1) {
		switch (c) {
		        case 'c':
		                nocompose = 1;
				break;
		        case 'd':
		    		optd = 1;
				break;
		        case 'm':
		                optm = 1;
				break;
			case 'v':
				verbose = 1;
				break;
			case 'h':
			case '?':
				usage();
		}
	}

	args = argv + optind - 1;
	yywrap();	/* set up the first input file, if any */
	if (yyparse()) {
		fprintf(stderr, "syntax error in map file\n");
		if(!optm)
		  fprintf(stderr, "key bindings not changed\n");
		exit(1);
	}
	if(optm)
	        mktable();
	else
	        loadkeys();
	exit(0);
}

int
yyerror(char *s) {
	fprintf(stderr, "%s:%d: %s\n",
		*args && strcmp(*args, "-") ? *args : "stdin", line_nr, s);
	return(0);
}

char *dirpath[] = { "", DATADIR "/" KEYMAPDIR "/", KERNDIR "/", 0 };
char *suffixes[] = { "", ".map", 0 };
extern char pathname[];
extern FILE *findfile(char *fnam);

#undef yywrap
int
yywrap(void) {
	FILE *f;
	static int first_file = 1; /* ugly kludge flag */

	line_nr = 1;
	if (optd) {
	        /* first read default map */
	        optd = 0;
	        if((f = findfile(DEFMAP)) == NULL) {
		    fprintf(stderr, "Cannot find %s\n", DEFMAP);
		    exit(1);
		}
		goto gotf;
	}
	if (*args)
		args++;
	if (!*args)
		return 1;
	if (!strcmp(*args, "-")) {
		f = stdin;
		strcpy(pathname, "<stdin>");
	} else if ((f = findfile(*args)) == NULL) {
		fprintf(stderr, "cannot open file %s\n", *args);
		exit(1);
	}
	/*
		Can't use yyrestart if this is called before entering yyparse()
		I think assigning directly to yyin isn't necessarily safe in
		other situations, hence the flag
	*/
      gotf:
	fprintf(stderr, "Loading %s\n", pathname);
	if (first_file) {
		yyin = f;
		first_file = 0;
	} else
		yyrestart(f);
	return 0;
}

static void
addkey(int index, int table, int keycode) {

	if (keycode == -1)
		return;
        if (index < 0 || index >= NR_KEYS) {
	        fprintf(stderr, "%s: addkeys called with bad index %d\n",
			progname, index);
		exit(1);
	}
        if (table < 0 || table >= NR_KEYMAPS) {
	        fprintf(stderr, "%s: addkeys called with bad table %d\n",
			progname, table);
		exit(1);
	}

	key_map[table][index] = keycode;
	keymap_was_set[table][index] = 1;
}

static void
addfunc(struct kbsentry kbs) {
        int sh, i;
	char *p, *q, *r;

        if (kbs.kb_func >= NR_FUNC) {
	        fprintf(stderr, "%s: addfunc called with bad func %d\n",
			progname, kbs.kb_func);
		exit(1);
	}
	if ((q = func_table[kbs.kb_func])) { /* throw out old previous def */
	        sh = strlen(q) + 1;
		p = q + sh;
		while (p < fp)
		        *q++ = *p++;
		fp -= sh;
	}
	p = func_buf;                        /* find place for new def */
	for (i = 0; i < kbs.kb_func; i++)
	        if (func_table[i]) {
		        p = func_table[i];
			while(*p++);
		}
	func_table[kbs.kb_func] = p;
        sh = strlen(kbs.kb_string) + 1;
	if (fp + sh > func_buf + FUNC_BUFSIZE) {
	        fprintf(stderr, "%s: addfunc: func_buf overflow\n", progname);
		exit(1);
	}
	q = fp;
	fp += sh;
	r = fp;
	while (q > p)
	        *--r = *--q;
	strcpy(p, kbs.kb_string);
	for (i++; i < NR_FUNC; i++)
	        if (func_table[i])
		        func_table[i] += sh;
}

static void
compose(int diacr, int base, int res) {
        struct kbdiacr *p;
        if (accent_table_size == MAX_DIACR) {
	        fprintf(stderr, "compose table overflow\n");
		exit(1);
	}
	p = &accent_table[accent_table_size++];
	p->diacr = diacr;
	p->base = base;
	p->result = res;
}

static int
defkeys(int fd) {
	struct kbentry ke;
	int ct = 0;
	int i,j,fail;

	for(i=0; i<NR_KEYMAPS; i++) for(j=0; j<NR_KEYS; j++)
	  if (keymap_was_set[i][j]) {
	      ke.kb_index = j;
	      ke.kb_table = i;
	      ke.kb_value = key_map[i][j];

	      fail = ioctl(fd, KDSKBENT, (unsigned long)&ke);
	      if (!fail)
		ct++;
	      if(verbose)
		printf("keycode %d, table %d = %d%s\n", j, i, key_map[i][j],
		       fail ? "    FAILED" : "");
	      else if (fail)
		fprintf(stderr, "failed to bind key %d to value %d\n",
			j, key_map[i][j]);
	  }
	return ct;
}

static int
deffuncs(int fd){
        int i, ct = 0;
	char *p;

        for (i = 0; i < NR_FUNC; i++)
	  if ((p = func_table[i])) {
	        kbs_buf.kb_func = i;
		strcpy(kbs_buf.kb_string, p);
		if (ioctl(fd, KDSKBSENT, (unsigned long)&kbs_buf))
		  fprintf(stderr, "failed to bind string '%s' to function %s\n",
			  kbs_buf.kb_string, syms[KT_FN].table[kbs_buf.kb_func]);
		else
		  ct++;
	    }
	return ct;
}

static int
defdiacs(int fd){
        struct kbdiacrs kd;
	int i;

	kd.kb_cnt = accent_table_size;
	if (kd.kb_cnt > MAX_DIACR) {
	    kd.kb_cnt = MAX_DIACR;
	    fprintf(stderr, "too many compose definitions\n");
	}
	for (i = 0; i < kd.kb_cnt; i++)
	    kd.kbdiacr[i] = accent_table[i];

	if(ioctl(fd, KDSKBDIACR, (unsigned long) &kd)) {
	    fprintf(stderr, "KDSKBDIACR failed\n");
	    perror("");
	    exit(1);
	}
	return kd.kb_cnt;
}

static void
loadkeys () {
        int fd;
        int keyct, funcct, diacct;

	if ((fd = open("/dev/console", O_RDONLY)) < 0) {
	    fprintf(stderr, "cannot open console for ioctl, using stdin\n");
	    fd = 0;
	}

	keyct = defkeys(fd);
	funcct = deffuncs(fd);
	if (accent_table_size > 0 || nocompose)
	  diacct = defdiacs(fd);
	if (verbose) {
	        printf("\nChanged %d key%s and %d string%s.\n",
		       keyct, (keyct == 1) ? "" : "s",
		       funcct, (funcct == 1) ? "" : "s");
		if (accent_table_size > 0 || nocompose)
		  printf("Loaded %d compose definition%s.\n",
			 diacct, (diacct == 1) ? "" : "s");
		else
		  printf("(No change in compose definitions.)\n");
	}
}

/*
 * mktable.c
 *
 */
static void
outchar (unsigned char c, int comma) {
        printf("'");
        printf((c == '\'' || c == '\\') ? "\\%c" : isgraph(c) ? "%c"
	       : "\\%03o", c);
	printf(comma ? "', " : "'");
}

static void
mktable () {
	int i, j;

	struct kbsentry kbs;
	u_char *p;

	printf(
"/* Do not edit this file! It was automatically generated by   */\n");
	printf(
"/*    loadkeys --mktable defkeymap.map > defkeymap.c          */\n\n");
	printf("#include <linux/types.h>\n");
	printf("#include <linux/keyboard.h>\n");
	printf("#include <linux/kd.h>\n\n");

	printf("u_short key_map[NR_KEYMAPS][NR_KEYS] = {\n\t");
	for (i = 0; i < NR_KEYMAPS; i++) {
		printf("{");
		for (j = 0; j < NR_KEYS; j++) {
			if (!(j % 8))
				printf("\n\t");
			printf("0x%04x,\t", key_map[i][j]);
		}
		printf("\n\t}, ");
	}
	printf("\n};\n\n");

	/* make sure func_table is filled */
	for (i = 0; i < NR_FUNC; i++) if(!func_table[i]) {
	        kbs.kb_func = i;
		kbs.kb_string[0] = 0;
		addfunc(kbs);
	}

	printf("char func_buf[FUNC_BUFSIZE] = {\n");
	for (i = 0; i < NR_FUNC; i++) {
		printf("\t");
		for (p = func_table[i]; *p; p++)
		        outchar(*p, 1);
		printf("0, \n");
	}
	printf("};\n\n");

	printf("char *func_table[NR_FUNC] = {\n");
	for (i = 0; i < NR_FUNC; i++)
		printf("\tfunc_buf + %d,\n", func_table[i] - func_buf);
	printf("};\n");

	printf("\nstruct kbdiacr accent_table[MAX_DIACR] = {\n");
	for (i = 0; i < accent_table_size; i++) {
	        printf("\t{");
	        outchar(accent_table[i].diacr, 1);
		outchar(accent_table[i].base, 1);
		outchar(accent_table[i].result, 0);
		printf("},");
		if(i%2) printf("\n");
	}
	if(i%2) printf("\n");
	printf("};\n\n");
	printf("unsigned int accent_table_size = %d;\n",
	       accent_table_size);

	exit(0);
}


