/*
  G W A R T  --  GNU version of Wart

  A small subset of "lex" sufficient for converting the Kermit
  protocol state table from lex notation to C.

  Authors:
    Jeff Damens, Frank da Cruz
    The Kermit Project, Columbia University
    http://www.columbia.edu/kermit/
    kermit@columbia.edu

  Copyright (C) 1984, 1999,
  The Trustees of Columbia University in the City of New York.

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/*
 * input format is:
 *  lines to be copied | %state <state names...>
 *  %%
 * <state> | <state,state,...> CHAR  { actions }
 * ...
 *  %%
 *  more lines to be copied
 */

#include <stdio.h>
#include <ctype.h>
#include "gkermit.h"

#define TBL_TYPE "short"		/* C data type of state table */

#define C_L 014				/* Formfeed */

#define SEP 1				/* Token types */
#define LBRACK 2
#define RBRACK 3
#define WORD 4
#define COMMA 5

/* Storage sizes */

#define MAXSTATES 50			/* max number of states */
#define MAXWORD 50			/* max # of chars/word */
#define SBYTES ((MAXSTATES+6)/8)	/* # of bytes for state bitmask */

/* Name of gwart function in generated program */

#ifndef FNAME
#define FNAME "gwart"
#endif /* FNAME */

/* Structure for state information */

struct transx {
    CHAR states[SBYTES];		/* included states */
    int anyst;				/* true if this good from any state */
    CHAR inchr;				/* input character */
    int actno;				/* associated action */
    struct transx *nxt;
};					/* next transition */
typedef struct transx *trans;

/* Function prototypes */

_MYPROTOTYPE( VOID fatal, (char *) );
_MYPROTOTYPE( VOID setwstate, (int, trans) );
_MYPROTOTYPE( int teststate, (int, trans) );
_MYPROTOTYPE( trans rdinput, (FILE *, FILE *) );
_MYPROTOTYPE( VOID initial, (FILE *, FILE *) );
_MYPROTOTYPE( int isin, (char *, int) );
_MYPROTOTYPE( int isword, (int) );
_MYPROTOTYPE( VOID rdword, (FILE *, char *) );
_MYPROTOTYPE( VOID rdstates, (FILE *, FILE *) );
_MYPROTOTYPE( trans newtrans, (void) );
_MYPROTOTYPE( trans rdrules, (FILE *, FILE *) );
_MYPROTOTYPE( VOID statelist, (FILE *, trans) );
_MYPROTOTYPE( VOID copyact, (FILE *, FILE *, int) );
_MYPROTOTYPE( int faction, (trans, int, int) );
_MYPROTOTYPE( VOID emptytbl, (void) );
_MYPROTOTYPE( VOID addaction, (int, int, int) );
_MYPROTOTYPE( VOID writetbl, (FILE *) );
_MYPROTOTYPE( VOID warray, (FILE *, char *, int [], int, char *) );
_MYPROTOTYPE( VOID prolog, (FILE *) );
_MYPROTOTYPE( VOID epilogue, (FILE *) );
_MYPROTOTYPE( VOID copyrest, (FILE *, FILE *) );
_MYPROTOTYPE( int gettoken, (FILE *) );
_MYPROTOTYPE( VOID rdcmnt, (FILE *) );
_MYPROTOTYPE( VOID clrhash, (void) );
_MYPROTOTYPE( int hash, (char *) );
_MYPROTOTYPE( VOID enter, (char *, int) );
_MYPROTOTYPE( int lkup, (char *) );
_MYPROTOTYPE( static char* copy, (char *s) );

/* Variables and tables */

int lines, nstates, nacts;
int tbl[MAXSTATES*96];
char tokval[MAXWORD];
char *tbl_type = TBL_TYPE;

char *txt1 = "\n#define BEGIN state =\n\nint state = 0;\n\nint\n";

char *fname = FNAME;			/* Generated function name goes here */

/* Rest of program... */

char *txt2 = "()\n\
{\n\
    int c,actno;\n\
    extern ";

/* Data type of state table is inserted here (short or int) */

char *txt2a =
" tbl[];\n\
    while (1) {\n\
	c = input() - 32;\n\
	if (c < 0 || c > 95) c = 0;\n";

char *txt2b = "	if ((actno = tbl[c + state*96]) != -1)\n\
	    switch(actno) {\n";

/* this program's output goes here, followed by final text... */

char *txt3 = "\n	    }\n    }\n}\n\n";


/*
 * turn on the bit associated with the given state
 *
 */
VOID
setwstate(state,t) int state; trans t; {
    int idx,msk;
    idx = state/8;			/* byte associated with state */
    msk = 0x80 >> (state % 8);		/* bit mask for state */
    t->states[idx] |= msk;
}

/*
 * see if the state is involved in the transition
 *
 */
int
teststate(state,t) int state; trans t; {
    int idx,msk;
    idx = state/8;
    msk = 0x80 >> (state % 8);
    return(t->states[idx] & msk);
}


/*
 * read input from here...
 *
 */

trans
rdinput(infp,outfp) FILE *infp, *outfp; {
    trans x;
    lines = 1;				/* line counter */
    nstates = 0;			/* no states */
    nacts = 0;				/* no actions yet */
    fprintf(outfp,"\n%c* WARNING -- This C source program generated by ",'/');
    fprintf(outfp,"gwart preprocessor. */\n");
    fprintf(outfp,"%c* Do not edit this file; edit the gwart-format ",'/');
    fprintf(outfp,"source file instead, */\n");
    fprintf(outfp,"%c* and then run it through gwart to produce a new ",'/');
    fprintf(outfp,"C source file.     */\n\n");

    initial(infp,outfp);		/* read state names, initial defs */
    prolog(outfp);			/* write out our initial code */
    x = rdrules(infp,outfp);		/* read rules */
    epilogue(outfp);			/* write out epilogue code */
    return(x);
}


/*
 * initial - read initial definitions and state names.  Returns
 * on EOF or %%.
 *
 */
VOID
initial(infp,outfp) FILE *infp, *outfp; {
    int c;
    char wordbuf[MAXWORD];
    while ((c = getc(infp)) != EOF) {
	if (c == '%') {
	    rdword(infp,wordbuf);
	    if (strcmp(wordbuf,"states") == 0)
	      rdstates(infp,outfp);
	    else if (strcmp(wordbuf,"%") == 0) return;
	    else fprintf(outfp,"%%%s",wordbuf);
	}
	else putc(c,outfp);
	if (c == '\n') lines++;
    }
}

/*
 * boolean function to tell if the given character can be part of
 * a word.
 *
 */
int
isin(s,c) char *s; int c; {
    for (; *s != '\0'; s++)
      if (*s == (char) c) return(1);
    return(0);
}
int
isword(c) int c; {
    static char special[] = ".%_-$@";	/* these are allowable */
    return(isalnum(c) || isin(special,c));
}

/*
 * read the next word into the given buffer.
 *
 */
VOID
rdword(fp,buf) FILE *fp; char *buf; {
    int len = 0,c;
    while (isword(c = getc(fp)) && ++len < MAXWORD) *buf++ = (char) c;
    *buf++ = '\0';			/* tie off word */
    ungetc(c,fp);			/* put break char back */
}

/*
 * read state names, up to a newline.
 *
 */
VOID
rdstates(fp,ofp) FILE *fp,*ofp; {
    int c;
    char wordbuf[MAXWORD];
    while ((c = getc(fp)) != EOF && c != '\n') {
	if (isspace(c) || c == C_L) continue;	/* skip whitespace */
	ungetc(c,fp);			/* put char back */
	rdword(fp,wordbuf);		/* read the whole word */
	enter(wordbuf,++nstates);	/* put into symbol tbl */
	fprintf(ofp,"#define %s %d\n",wordbuf,nstates);
    }
    lines++;
}

/*
 * allocate a new, empty transition node
 *
 */
trans
newtrans() {
    trans new;
    int i;
    new = (trans) malloc(sizeof (struct transx));
    for (i=0; i<SBYTES; i++) new->states[i] = 0;
    new->anyst = 0;
    new->nxt = NULL;
    return(new);
}


/*
 * read all the rules.
 *
 */

trans
rdrules(fp,out) FILE *fp,*out; {
    trans head,cur,prev;
    int curtok;
    head = cur = prev = NULL;
    while ((curtok = gettoken(fp)) != SEP)

      switch(curtok) {
	case LBRACK:
	  if (cur == NULL)
	    cur = newtrans();
	  else
	    fatal("duplicate state list");
	  statelist(fp,cur);		/* set states */
	  continue;			/* prepare to read char */

	case WORD:
	  if ((int)strlen(tokval) != 1)
	    fatal("multiple chars in state");
	  if (cur == NULL) {
	      cur = newtrans();
	      cur->anyst = 1;
	  }
	  cur->actno = ++nacts;
	  cur->inchr = (char) (tokval[0] - 32);
	  if (head == NULL)
	    head = cur;
	  else
	    prev->nxt = cur;
	  prev = cur;
	  cur = NULL;
	  copyact(fp,out,nacts);
	  break;
	default: fatal("bad input format");
      }
    return(head);
}

/*
 * read a list of (comma-separated) states, set them in the
 * given transition.
 *
 */
VOID
statelist(fp,t) FILE *fp; trans t; {
    int curtok,sval;
    curtok = COMMA;
    while (curtok != RBRACK) {
	if (curtok != COMMA) fatal("missing comma");
	if ((curtok = gettoken(fp)) != WORD) fatal("missing state name");
	if ((sval = lkup(tokval)) == -1) {
	    fprintf(stderr,"state %s undefined\n",tokval);
	    fatal("undefined state");
	}
	setwstate(sval,t);
	curtok = gettoken(fp);
    }
}

/*
 * copy an action from the input to the output file
 *
 */
VOID
copyact(inp,outp,actno) FILE *inp,*outp; int actno; {
    int c,bcnt;
    fprintf(outp,"case %d:\n",actno);
    while (c = getc(inp), (isspace(c) || c == C_L))
      if (c == '\n') lines++;
    if (c == '{') {
	bcnt = 1;
	fputs("    {",outp);
	while (bcnt > 0 && (c = getc(inp)) != EOF) {
	    if (c == '{') bcnt++;
	    else if (c == '}') bcnt--;
	    else if (c == '\n') lines++;
	    putc(c,outp);
	}
	if (bcnt > 0) fatal("action doesn't end");
    } else {
	while (c != '\n' && c != EOF) {
	    putc(c,outp);
	    c = getc(inp);
	}
	lines++;
    }
    fprintf(outp,"\n    break;\n");
}

/*
 * find the action associated with a given character and state.
 * returns -1 if one can't be found.
 *
 */
int
faction(hd,state,chr) trans hd; int state,chr; {
    while (hd != NULL) {
	if (hd->anyst || teststate(state,hd))
	  if (hd->inchr == ('.' - 32) || hd->inchr == (char) chr)
	    return(hd->actno);
	hd = hd->nxt;
    }
    return(-1);
}

/*
 * empty the table...
 *
 */
VOID
emptytbl() {
    int i;
    for (i=0; i<nstates*96; i++) tbl[i] = -1;
}

/*
 * add the specified action to the output for the given state and chr.
 *
 */
VOID
addaction(act,state,chr) int act,state,chr; {
    tbl[state*96 + chr] = act;
}

VOID
writetbl(fp) FILE *fp; {
    warray(fp,"tbl",tbl,96*(nstates+1),TBL_TYPE);
}


/*
 * write an array to the output file, given its name and size.
 *
 */
VOID
warray(fp,nam,cont,siz,typ) FILE *fp; char *nam; int cont[],siz; char *typ; {
    int i;
    fprintf(fp,"%s %s[] = {\n",typ,nam);
    for (i = 0; i < siz - 1; ) {
	fprintf(fp," %2d,",cont[i]);
	if ((++i % 16) == 0) putc('\n',fp);
    }
    fprintf(fp,"%2d\n};\n",cont[siz-1]);
}

int
main(argc,argv) int argc; char **argv; {
    trans head;
    int state,c;
    FILE *infile,*outfile;

    if (argc > 1) {
	if ((infile = fopen(argv[1],"r")) == NULL) {
	    fprintf(stderr,"Can't open %s\n",argv[1]);
	    fatal("unreadable input file");
	}
    } else infile = stdin;

    if (argc > 2) {
	if ((outfile = fopen(argv[2],"w")) == NULL) {
	    fprintf(stderr,"Can't write to %s\n",argv[2]);
	    fatal("bad output file");
	}
    } else outfile = stdout;

    clrhash();				/* empty hash table */
    head = rdinput(infile,outfile);	/* read input file */
    emptytbl();				/* empty our tables */
    for (state = 0; state <= nstates; state++)
      for (c = 1; c < 96; c++)		/* find actions, */
	addaction(faction(head,state,c),state,c); /* add to tbl */
    writetbl(outfile);
    copyrest(infile,outfile);
    printf("%d states, %d actions\n",nstates,nacts);
    exit(0);
}


/*
 * fatal error handler
 *
 */

VOID
fatal(msg) char *msg; {
    fprintf(stderr,"error in line %d: %s\n",lines,msg);
    exit(1);
}

VOID
prolog(outfp) FILE *outfp; {
    int c;
    while ((c = *txt1++)     != '\0') putc(c,outfp);
    while ((c = *fname++)    != '\0') putc(c,outfp);
    while ((c = *txt2++)     != '\0') putc(c,outfp);
    while ((c = *tbl_type++) != '\0') putc(c,outfp);
    while ((c = *txt2a++)    != '\0') putc(c,outfp);
    while ((c = *txt2b++)    != '\0') putc(c,outfp);
}

VOID
epilogue(outfp) FILE *outfp; {
    int c;
    while ((c = *txt3++) != '\0') putc(c,outfp);
}

VOID
copyrest(in,out) FILE *in,*out; {
    int c;
    while ((c = getc(in)) != EOF) putc(c,out);
}

/*
 * gettoken - returns token type of next token, sets tokval
 * to the string value of the token if appropriate.
 *
 */

int
gettoken(fp) FILE *fp; {
    int c;
    while (1) {				/* loop if reading comments... */
	do {
	    c = getc(fp);
	    if (c == '\n') lines++;
	} while ((isspace(c) || c == C_L)); /* skip whitespace */
	switch(c) {
	  case EOF:
	    return(SEP);
	  case '%':
	    if ((c = getc(fp)) == '%') return(SEP);
	    tokval[0] = '%';
	    tokval[1] = (char) c;
	    rdword(fp,tokval+2);
	    return(WORD);
	  case '<':
	    return(LBRACK);
	  case '>':
	    return(RBRACK);
	  case ',':
	    return(COMMA);
	  case '/':
	    if ((c = getc(fp)) == '*') {
		rdcmnt(fp);		/* skip over the comment */
		continue;
	    } else {			/* and keep looping */
		ungetc(c,fp);		/* put this back into input */
		c = '/';		/* put character back, fall thru */
	    }

	  default:
	    if (isword(c)) {
		ungetc(c,fp);
		rdword(fp,tokval);
		return(WORD);
	    } else fatal("Invalid character in input");
	}
    }
}

/*
 * skip over a comment
 *
 */

VOID
rdcmnt(fp) FILE *fp; {
    int c,star,prcnt;
    prcnt = star = 0;			/* no star seen yet */
    while (!((c = getc(fp)) == '/' && star)) {
	if (c == EOF || (prcnt && c == '%')) fatal("Unterminated comment");
	prcnt = (c == '%');
	star = (c == '*');
	if (c == '\n') lines++;
    }
}

/*
 * symbol table management for gwart
 *
 * entry points:
 *   clrhash - empty hash table.
 *   enter - enter a name into the symbol table
 *   lkup - find a name's value in the symbol table.
 */
#define HASHSIZE 101			/* # of entries in hash table */

struct sym {
    char *name;				/* symbol name */
    int val;				/* value */
    struct sym *hnxt;			/* next on collision chain */
} *htab[HASHSIZE];			/* the hash table */

/*
 * empty the hash table before using it...
 *
 */
VOID
clrhash() {
    int i;
    for (i=0; i<HASHSIZE; i++) htab[i] = NULL;
}

/*
 * compute the value of the hash for a symbol
 *
 */
int
hash(name) char *name; {
    int sum;
    for (sum = 0; *name != '\0'; name++) sum += (sum + *name);
    sum %= HASHSIZE;			/* take sum mod hashsize */
    if (sum < 0) sum += HASHSIZE;	/* disallow negative hash value */
    return(sum);
}

/*
 * make a private copy of a string...
 *
 */
static char*
copy(s) char *s; {
    char *new;
    new = (char *) malloc((int)strlen(s) + 1);
    strcpy(new,s);
    return(new);
}

/*
 * enter state name into the hash table
 *
 */
VOID
enter(name,svalue) char *name; int svalue; {
    int h;
    struct sym *cur;
    if (lkup(name) != -1) {
	fprintf(stderr,"state \"%s\" appears twice...\n", name);
	exit(1);
    }
    h = hash(name);
    cur = (struct sym *)malloc(sizeof (struct sym));
    cur->name = copy(name);
    cur->val = svalue;
    cur->hnxt = htab[h];
    htab[h] = cur;
}

/*
 * find name in the symbol table, return its value.  Returns -1
 * if not found.
 *
 */
int
lkup(name) char *name; {
    struct sym *cur;
    for (cur = htab[hash(name)]; cur != NULL; cur = cur->hnxt)
      if (strcmp(cur->name,name) == 0) return(cur->val);
    return(-1);
}
