%{
/* $Id: menutoc.l,v 1.6 93/03/29 00:00:00 lee Exp $
 * $Log:	menutoc.l,v $
 * Revision 1.9  93/03/29  00:00:00  lee
 * more label alignment; convention for designating envelope parameters
 *
 * Revision 1.6  89/05/06  17:13:37  lee
 * rel. to comp.sources.misc
 * 
 * original version:
 *	menutoc.l	-- Greg Lee, lee@uhccux.uhcc.hawaii.edu
 *			   February 1989
 * Scott Snyder added cmd line args and fixed a bug (see comment below)
 * Mark Rinfret made a couple changes for Aztec C

	Generate C language menu module for Glib from menu specification.

Compile:
	flex menutoc.l
	cc -o menutoc lex.yy.c
or
	lex menutoc.l
	cc -o menutoc lex.yy.c -ll

Usage:
	menutoc <file.menu >file.c

Description:

Menutoc simplifies constructing and modifying synthesizer-specific
modules for Tim Thompson's Glib.  It allows the form of the on-screen
menu to be specified as a picture in the menu source file, and generates
'setval' and 'getval' calls automatically.  This version works with
the version of Glib modified by Michael Kesti.

The following characterization of how menutoc works is probably
not entirely clear.  Look at the example k1single.menu and the
source code to get a more exact idea.  The source k1single.menu
gives the C file k1single.c after processing by menutoc.

The menu source file should contain five special preprocessor
directives: '#MENU' (once), '#END' (once), '#O' (one for each
label name), '#GETVAL' (once), and '#SETVAL' (once).  The source
file has four sections:
	(1) the portion before the '#MENU' directive is passed
	    through unchanged to the output,
	(2) a picture of the screen menu is given between the
	    '#MENU' and '#END' directives,
	(3) after the '#END' directive is C code with '#O' directives
	    which specify the data offsets of variables and with
	    '%%' or '@@' references to the screen locations for
	    variable values, and
	(4) more C code containing somewhere the '#SETVAL' and
	    '#GETVAL' directives which cause a series of calls
	    to the 'setval' and 'getval' functions to be generated,
	    making use of the offset information given by the '#O'
	    directives in the preceding section.

The picture of the screen menu has constant strings in the position
they will be output to the screen and '%' or '@' in each position
where the current value of some variable is to be displayed by
Glib.  The locations of the '%'s and '@'s are remembered in the
order in which they are encountered (scanning left-to-right and
top-to-bottom in the picture), then the corresponding row/column
coordinates are substituted for '%%'s and '@@'s encountered in
section 3 of the source file.  The '%'s and '@'s are stored
independently so as to make it more convenient to specify menus
that have two columns of values.

The '#O' specifications are given in the form:

#O <NAME> <TYPE> <POS> <MAX> <OFFSET> <MASK> <SHIFT> <ADHOC>

Such a line generates one "paraminfo" entry for an array, and some of
the information is saved and made use of in generating 'setval' and
'getval' calls.  NAME is the name of a variable label (no caps); TYPE is
the name of a vis-function used to display the current value on the
screen; POS should be either '%%' or '@@', which will be replaced by row
and column coordinates of the position to display the value; MAX is the
maximum value the glib editor is to accept (the minimum is assumed to be
0); OFFSET is the offset of the byte containing the value in the data
array.  (If OFFSET is negative, the (positive) value is taken to be the
initial value rather than the offset.)

The NAME argument may begin with a digit specifying a repetition
count.  It's a shortcut which is like giving several consecutive
#O-lines for <NAME>1, <NAME>2, ...

The last three #O arguments are optional.  MASK is a number with 1-bits
in positions holding the value for those cases where part of a byte is
used for some other purpose.  SHIFT is the number of bits the bitfield
is shifted left in the byte.  So, for instance, if the synth keeps a
value in the 4th, 5th, and 6th bits, one would give '0x1C' for the MASK
and '3' for SHIFT.  (Use caps for hex digits A-F.)

There is no systematic provision for values some of whose bits are found
in one byte, some in another.  The ADHOC argument has the form
'*<digit>' and is used in the example to provide for such a case.  The
code below in this file was just made up ad hoc for two such cases I
encountered with the Kawai K1.

(added 3/29/93)
The X version of Glib uses a proportional font for screen labels, and
so there is a potential problem getting labels lined up to the appropriate
column.  To help with this, strings preceded by two or more spaces are
now treated as separate labels, and a "|" always starts a new label.

A "%" or "@" in the menu screen portion of the .mnu file can be immediately
followed by a three digit number designating the corresponding parameter
as a waveform-envelope parameter.  This information will be put into the
parameter struct so it can be used by the X version to construct a graphic
image of the envelope on the editing screen.  The first digit says which
of several envelopes the parameter goes with, and the next two digits
tell which specific envelope parameter it is.  There is currently provision
for 1 to 4 envelopes and precisely 4 parameters for each.  The parameters
and identifying digits are:
	02 -- attack time
	03 -- decay time
	04 -- sustain level
	05 -- release time
Thus a "%302" in the screen picture identifies the parameter that will be
displayed at this position as the attack time of envelope number 3.
Xgl has to get the values of these parameters from their string values
using atoi(), and that will only work if the string display delivered
by the vis functions has a reasonably straightforward relationship to
to the numeric value.

For the two new members of the parameter struct, menutoc now generates
a "XV_NULL" and a "0" (see paraminfo in glib.h).  Except for this last
change, the modifications in this version made for the sake of the X
interface are (it is my belief) more or less downward compatible.  The
"XV_NULL" and the "0" have to be edited in by hand to the four lines
of paraminfo data not generated by menutoc (the lines for "autodur",
etc.).
					-- gl

*/

/*
 * Modified 4-13-89 by Scott Snyder:
 *  1. Increased MAXVAL from 100 to 200 (for DX7).
 *  2. Changed type of temps from 'unsigned char' to 'unsigned' (for DX7).
 *  3. Added command line processing (since TMAKE doesn't support redirection).
 *
 * 4-19-18
 *  Fixed bug with zero offsets.
 */

#ifdef AZTEC_C
long atol();
# define strtol(a, b, c) atol(a)
#endif
#define MAXLINE 133
#define MAXVAL 200
#define MAXVSTRINGS 200
#define MAXBUF 2000

	char *infname = "file.mnu";
	int lcnt = 1;
	int i, j, k, wcol=0, col=0, row=0;
	char word[MAXLINE];
	int wordp = 0;
	int valpos[MAXVAL][2];
	int valpos2[MAXVAL][2];
	int vwpos[MAXVAL];
	int vwpos2[MAXVAL];
	int valcnt = 0;
	int valcnt2= 0;
	int offset;
	int nlist[MAXVSTRINGS][6];
	int np = 0;
	char buf[MAXBUF];
	int bufp=0;
	int multiple, special;
        int vmin, vmax, vval, mask, vshift, vrow, vcol, vwave, iflag;
	char visfunc[20];
	int state;
%}

%s M IG VAL O P Q

%%

<IG>^#[ \t]*MENU.*\n	{ BEGIN(M); lcnt++; }
<IG>\n			{ ECHO; lcnt++; }
<IG>.			ECHO;

<M>"@"[0-9]*	{
		writeword();
		if (valcnt2+1 >= MAXVAL) {
		    fprintf(stderr,"menutoc: too many @s in menu\n");
		    exit(1);
		}
		if (yyleng > 1) vwpos2[valcnt2] = atoi(yytext+1);
		valpos2[valcnt2][0] = row;
		valpos2[valcnt2++][1] = col;
		col += yyleng;
};

<M>"%"[0-9]*	{
		writeword();
		if (valcnt+1 >= MAXVAL) {
		    fprintf(stderr,"menutoc: too many %%s in menu\n");
		    exit(1);
		}
		if (yyleng > 1) vwpos[valcnt] = atoi(yytext+1);
		valpos[valcnt][0] = row;
		valpos[valcnt++][1] = col;
		col += yyleng;
};

<M>"|"  {	writeword();
		append(yytext[0]); col += 1;
};

<M>[ ]*	{	if (yyleng > 1) writeword();
		else if (wordp) for (i=0; i<yyleng; i++) append(' ');
		col += yyleng;
};

<M>\t  {	writeword(); col = ((col + 8)/8)*8;
};

<M>\n  {	writeword(); col = 0; row += 1; lcnt++;
};

<M>.  {		append(yytext[0]); col += 1;
};

<M>^#[ \t]*END.*\n  {
		BEGIN(VAL); i = 0; j = 0; lcnt++;
		printf("# line %d \"%s\"\n", lcnt, infname);
};

<VAL>^"#O"[ \t]+  {
		if (np+1 >= MAXVSTRINGS) {
		    fprintf(stderr,"menutoc: too many VSTRINGs\n");
		    exit(1);
		}
		BEGIN(O);
		multiple = special = 0;
		vmax = offset = mask = vshift = vmin = vrow = vcol = vwave = iflag = 0;
		state = 0;
};

<O>[0-9]	multiple = atoi(yytext);

<O>[a-z][a-z_0-9]*  {
		if (bufp+yyleng >= MAXBUF) {
		    fprintf(stderr,"menutoc: out of storage room\n");
		    exit(1);
		}
		nlist[np][0] = bufp;
		strcpy(buf+bufp, yytext);
		bufp += yyleng+1;
		BEGIN(P);
};

<P>[ \t]*	;
<P>[a-z0-9_]+  {
		strcpy(visfunc, yytext);
		if (yyleng >= 20) {
		    fprintf(stderr,"menutoc: vis name too long\n");
		    exit(1);
		}
		BEGIN(Q);
};

<Q>[ \t]*	;
<Q>\n  {	lcnt++;
		if (special == 5) vmin = 1;
		else vmin = 0;
		if (offset < 0) vval = -offset;
		else vval = 0;

		if (multiple) for (k=0; k<multiple; k++) {
			printf("\"%s%d\",NULL,-1,-1,%d,%d,%d,XV_NULL,vis%s,%d,%d,%d,0,\n",
				buf+nlist[np][0], k+1, vrow, vcol, vwave, visfunc,
				vmin, vmax, vval);
			if (k+1 < multiple) {
			if (iflag) {
			    if (i < valcnt) {
				vwave = vwpos[i];
				vrow = valpos[i][0]; vcol = valpos[i++][1];
			    } else
			    fprintf(stderr,"menutoc: too many %%%%s\n");
			} else
			    if (j < valcnt2) {
				vwave = vwpos2[j];
				vrow = valpos2[j][0]; vcol = valpos2[j++][1];
			    } else
			    fprintf(stderr,"menutoc: too many @@s\n");
			}
		}
		else
			printf("\"%s\",NULL,-1,-1,%d,%d,%d,XV_NULL,vis%s,%d,%d,%d,0,\n",
			    buf+nlist[np][0], vrow, vcol, vwave, visfunc,
			    vmin, vmax, vval);
		if (multiple)
			printf("# line %d \"%s\"\n", lcnt, infname);
		nlist[np][1] = offset;
		nlist[np][2] = mask;
		nlist[np][3] = vshift;
		nlist[np][4] = multiple;
		nlist[np][5] = special;
		np++;
		BEGIN(VAL);
};

<Q>"-"*[0-9A-Fx]+  {
		k = (int)strtol(yytext, (char **)NULL, 0);
		switch (state) {
		  case 0: vmax = k;   break;
		  case 1: offset = k; break;
		  case 2: mask = k;   break;
		  case 3: vshift = k; break;
		  case 4: vmin = k;   break;
                }
                ++state;
};

<Q>"%%"  {	if (i < valcnt) {
			vrow = valpos[i][0];
			vcol = valpos[i][1];
			vwave = vwpos[i];
			i++;
			iflag++;
		}
		else fprintf(stderr,"\nmenutoc: too many %%%%s\n");
};
<Q>"@@"  {	if (j < valcnt2) {
			vrow = valpos2[j][0];
			vcol = valpos2[j][1];
			vwave = vwpos2[j];
			j++;
		}
		else fprintf(stderr,"\nmenutoc: too many @@s\n");
};
<Q>"--"  {	vrow = -1;
		vcol = -1;
};
<Q>"*"[1-9]  {
		special = yytext[1] - '0';
};
<VAL>.		ECHO;
<VAL>\n		{ ECHO; lcnt++; }

<VAL>^#[ \t]*SETVAL.*\n  {
		lcnt++;
		/* make sure i,k will be used before declaring them */
		for (k=0; k<np; k++)
			if (nlist[k][1]>=0 && (nlist[k][5] || nlist[k][2])) {
				printf("\tINT16 i, k;\n\n");
				break;
			}
		for (k=0; k<np; k++) dosetval(k);
		printf("# line %d \"%s\"\n", lcnt, infname);
};

<VAL>^#[ \t]*GETVAL.*\n  {
		lcnt++;
		/* make sure i,j,k will be used before declaring them */
		for (k=0; k<np; k++)
			if (nlist[k][1]>=0 && (nlist[k][5] || nlist[k][2])) {
				printf("\tINT16 i, j, k;\n\n");
				break;
			}
		for (k=0; k<np; k++) dogetval(k);
		printf("# line %d \"%s\"\n", lcnt, infname);
};

<VAL>\\%%+	printf("%s", yytext+1);
<VAL>\\@@+	printf("%s", yytext+1);

<VAL>"@@"  {	if (j < valcnt2) {
			printf("%d, %d", valpos2[j][0], valpos2[j][1]);
			j++;
		}
		else fprintf(stderr,"\nmenutoc: too many @@s\n");
};

<VAL>"%%"  {	if (i < valcnt) {
			printf("%d, %d", valpos[i][0], valpos[i][1]);
			i++;
		}
		else fprintf(stderr,"\nmenutoc: too many %%%%s\n");
};

<VAL>.		ECHO;
<VAL>\n		{ ECHO; lcnt++; }

%%
dosetval(k)
int k;
{	int m;

	special  = nlist[k][5];

	if (special == 1 || special == 3) k++;
	else if (special == 2 || special == 4) k--;

	bufp     = nlist[k][0];
	offset   = nlist[k][1];
	mask     = nlist[k][2];
	vshift   = nlist[k][3];
	multiple = nlist[k][4];

	if (offset < 0) return;

	if (multiple) for (m = 1; m <= multiple; m++)
	{	outsetval(m); offset++;		}
	else	outsetval(0);
}

outsetval(m)
int m;
{
	if (!mask && !special) {
		printf("setval(\"%s", buf+bufp);
		if (m) printf("%d", m);
		printf("\",(INT16)data[RESERVESIZE+%d]);\n",offset);
		return;
	}

	printf("i = (INT16)data[RESERVESIZE+%d]; ", offset);
	if (mask) {
		printf("i &= %d; ", mask);
		if (vshift) printf("i >>= %d; ", vshift);
	}

	if (special == 2) printf("i += k << 1;\n");
	else if (special == 4) printf("i += k << 7;\n");

	if (special == 1 || special == 3) printf("k = i;\n");
	else {
		printf("setval(\"%s", buf+bufp);
		if (m) printf("%d", m);
		printf("\",i);\n");
	}
}

dogetval(k)
int k;
{	int m;

	bufp     = nlist[k][0];
	offset   = nlist[k][1];
	mask     = nlist[k][2];
	vshift   = nlist[k][3];
	multiple = nlist[k][4];
	special  = nlist[k][5];

	if (offset < 0) return;

	if (multiple) for (m = 1; m <= multiple; m++)
	{	outgetval(m); offset++;		}
	else	outgetval(0);
}

outgetval(m)
int m;
{
	if (!mask && !special) {
		printf("data[RESERVESIZE+%d] = ", offset);
		printf("getval(\"%s", buf+bufp);
		if (m) printf("%d", m);
		printf("\");\n");
		return;
	}

	if (special == 2 || special == 4)
		printf("i = k; ");
	else {
		printf("i = getval(\"%s", buf+bufp);
		if (m) printf("%d", m);
		printf("\"); ");
	}

	if (special == 1) printf("k = i >> 1; i &= 1;\n");
	else if (special == 3) printf("k = i >> 7; i &= 0x7f;\n");

	if (mask) {
		printf("j = data[RESERVESIZE+%d]; ", offset);
		if (vshift) printf("i <<= %d; ", vshift);
		printf("j &= ~%d; ", mask);
		printf("i = j|i; ");
	}
	printf("data[RESERVESIZE+%d] = i;\n", offset);
}

main(argc, argv)
int argc;
char **argv;
{	char *basename();

  if (argc > 1) {
    infname = basename(argv[1]);
    if (freopen(argv[1], "r", stdin) == NULL) {
      perror("Can't open input file");
      exit(1);
    }
  }
  if (argc > 2) {
    if (freopen(argv[2], "w", stdout) == NULL) {
      perror("Can't open output file");
      exit(1);
    }
  }

	word[0] = 0;
	BEGIN(IG);
	yylex();
	if (i < valcnt) fprintf(stderr,"\nmenutoc: not enough %%%%s\n");
	if (j < valcnt2) fprintf(stderr,"\nmenutoc: not enough @@s\n");
	exit(0);
}

char   *basename(s)
char   *s;
{
	char   *p;
	char   *strrchr();

	if (p = strrchr (s, '/'))
		return (++p);
	else
		return (s);
}

append(c)
char c;
{
	if (wordp+1 >= MAXLINE) {
	    fprintf(stderr,"menutoc: string in menu too long\n");
	    exit(1);
	}
	if (!wordp) wcol = col;
	word[wordp++] = c;
	word[wordp] = 0;
}

writeword()
{
	if (wordp) {
		while (word[--wordp] == ' ') ;
		word[++wordp] = 0;
		printf("%d,%d,\"%s\",\n", row, wcol, word);
		wordp = 0;
		word[wordp] = 0;
	}
}
