text: /*
    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 <stdlib.h>
  #include <string.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);
  }
  