/*
 * scanner.c
 *
 *  Created May 29, 1990 by Raymie Stata
 *  C++ port July 15, 1990 by Raymie Stata
 *  Port back to C for Spec September 13, 1990 by Raymie Stata
 *
 *  (c) 1990 Raymie Stata, All Rights Reserved
 */

/*

  Token language for Spec:

   a comment runs from a % to the end of line (or file)

   id's, including keywords and reserved id's, are a letter, followed
     by a sequence of letters, digits and underscores, followed by any
     number of "primes" (').
   
   punctuation is , ; : ( ) [ ] { } . $ ^ | [] [*] := => -> < and >
     Note that the three punctuations , < > are also operators.

   literals are standard (i.e., C-like) integer, string, and
     character literals.

   operators are the reserved word IN plus any sequence of the characters
     ~ ! @ # $ ^ & * - + = . < > ? / \ | except : . $ ^ | := => ->
     Note that some operators (e.g., ++) have special precedence.

   case sensitive reserved words are: ANY BOOL CHAR INT NULL
     STRING true false nil

   case insensitive reserved words are: ALL APROC AS BEGIN DO END ENUM
     EXCEPT EXCEPTION EXISTS FORK FUNC HAVOC IN IS LAMBDA MODULE
     OD PROC RAISE RAISES RECORD  RET SEQ SET SKIP TYPE UNION
     VAR WITH

   special prefix operators are: - ~
   special infix operators are: ** // / * + - ++ -- ! <= < > >= = #
     IN /\ \/ ==> ,
*/

#define LOCAL static
#define EXPORT /* */

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>

#include "type.h"    /* -- parser.h needs this. */
#include "misc1.h"   /* -- parser.h needs this. */
#include "list.h"    /* -- parser.h needs this. */
#include "setexpr.h" /* -- parser.h needs this. */
#include "parser.h"
#include "symmap.h"
#include "scantab.h"

#define STRINDEX index

/* Imported variables. */
extern YYSTYPE yylval;

/* Exported variables and procedures. */
extern int yylex(), scantest(), scaninit();
int lineno = 1; char *fname;


LOCAL int mylex();
LOCAL int nr_anycase = (sizeof(anycase_words) / sizeof(struct restab_entry));
LOCAL int nr_respunct = (sizeof(respunct) / sizeof(struct restab_entry));
LOCAL int fn_count;
LOCAL char **fnames;
LOCAL FILE *infile = NULL;


#define TOKENBUFSIZE 256
LOCAL char token[TOKENBUFSIZE];  /* Some inputs could cause overflow! */
LOCAL char token2[TOKENBUFSIZE];

#define SCANTABSIZE 128
LOCAL int scantab[SCANTABSIZE];



LOCAL int isresword(), isrespunct();

EXPORT int yylex()
{
  int t;

  for(;;) {
    if (t = mylex()) return(t);
    if (! fn_count) return(0); /* No more files. */

    /* Open next file. */
    fname = *fnames++; fn_count--;
    if (! (infile = fopen(fname, "r"))) {
      fprintf(stderr, "File %s: can't open.", fname);
      perror(fname);
      assert(0);
    }
    lineno = 1;
  }
}


LOCAL int mylex()
{
  register int next;
  int state;
  char *tok = token;

 try_again:

  /* Get next non-space character */
  do {
    next = getc(infile);
    if (next == '\n') lineno++;
  } while(isspace(next));

  if (next == EOF) return(0);

  state = (next > 128 ? BAD : scantab[next]);
  
  yylval.lineno = lineno; /* Default semantic value is line number. */

  switch(state) {
  case PERCENT: /* Comment */
    do { next = getc(infile); } while(next != '\n' && next != EOF);
    ungetc('\n', infile);
    goto try_again;

  case ALPHA: /* Symbol or reserved word. */
    do { 
      *tok++ = next; next = getc(infile);
    } while( isalnum(next) || next == '_' );
    while( next == '\'' ) { *tok++ = next; next = getc(infile); }
    *tok = '\0';  ungetc(next, infile);
    if (next = isresword(token)) return(next);
    yylval.symbol = sm_internalize(token);
    return( ID );

  case DIGIT: /* Integer literal (no real lit's yet). */
    do { *tok++ = next; } while( isdigit(next = getc(infile)) );
    *tok = '\0';  ungetc(next, infile);
    return( INTEGER );

  case SQUOTE: /* Char literal */
    next = getc(infile);
    if (next == '\\') { /* Then we have a quote character. */
      /* WARNING: Assumes octal and hex codes aren't used! */
      next = getc(infile);
    }
    next = getc(infile);
    if (next != '\'') yyerror("Bad character literal.\n");
    return( CHARACTER );
      
  case DQUOTE: /* String literal */
    do {
      next = getc(infile);
      if ( next == '\\') getc(infile);
    } while ( next != '\"' && next != '\n' && next != EOF );
    if ( next == '\n' || next == EOF) 
      { ungetc('\n', infile); yyerror("Runaway string literal.\n"); }
    return( STRINGLIT );

  case SIMPLE_PUNCT:  /* Simple, one-character punctuation */
    return( next );

  case OPENB: /* Open bracked stuff: could be [ [] or [*] */
    if ( (next = getc(infile)) == ']') return( BOX );
    else if (next != '*') { ungetc(next, infile); return( '[' ); }
    else { /* WARNING: unimplemented corner case exists here!! */
      if ( (next = getc(infile)) != ']' ) yyerror("XBOX corner case.\n");
      return( XBOX );
    }

  case COMPLEX_PUNCT: /* Collect into string and tread like resevered word. */
    do {
      *tok++ = next; next = getc(infile);
    } while( scantab[next] == COMPLEX_PUNCT );
    *tok = '\0';  ungetc(next, infile);
    if (next = isrespunct(token)) return(next);
    yylval.symbol = sm_internalize(token);
    return( OP );

  case BAD:
    yyerror("Bad character in file.\n");
    goto try_again;
  }
}

LOCAL int isresword(s)
 char *s;
{
  char *s2;
  int i;

  /* Turn token to uppercase and try upper-case words. */
  for(i = 0; s[i]; i++) token2[i] =  (islower(s[i]) ? toupper(s[i]) : s[i]);
  token2[i] = '\0';
  for(i = 0; i < nr_anycase; i++)
    if (! strcmp( anycase_words[i].token, token2))
      return(anycase_words[i].value);
  return(0);
}

LOCAL int isrespunct(s)
 char *s;
{
  static char *singles = ",:.$|-~*+-!<>=#";
  int i;

  /* For some one character operators, the ASCII code is the toknr */
  if (s[1] == '\0' && STRINDEX(singles, *s)) return(*s);

  /* Search in our table. */
  for(i = 0; i < nr_respunct; i++)
    if (! strcmp( respunct[i].token, s )) return(respunct[i].value);
  return(0);
}

EXPORT int scaninit(argc, argv)
  int argc;
  char **argv;
{
  static char *simple = "^,;(){}]", *complex = "~!@#$&*-+=.<>?/\\|:";
  char *p;
  int c;

  /* Open first input file. */
  fname = *argv++; argc--;
  if (! (infile = fopen(fname, "r"))) {
    fprintf(stderr, "File %s: can't open.", fname);
    perror(fname);
    exit(1);
  }
  lineno = 1;
  fn_count = argc; fnames = argv;

  /* Initialize scanner table. */
  for(c = 0; c < SCANTABSIZE; c++) scantab[c] = BAD;
  scantab['%'] = PERCENT;
  for(c = 'a'; c <= 'z'; c++ ) scantab[c] = ALPHA;
  for(c = 'A'; c <= 'Z'; c++ ) scantab[c] = ALPHA;
  scantab['_'] = ALPHA;
  for(c = '0'; c <= '9'; c++ ) scantab[c] = DIGIT;
  scantab['\''] = SQUOTE; scantab['\"'] = DQUOTE;
  scantab['['] = OPENB;
  for(p = simple; *p; p++) scantab[*p] = SIMPLE_PUNCT;
  for(p = complex; *p; p++) scantab[*p] = COMPLEX_PUNCT;

  /* Insert reserved identifiers into symbol map. */
  rid_any = sm_internalize("ANY");
  rid_bool = sm_internalize("BOOL");
  rid_char = sm_internalize("CHAR");
  rid_int = sm_internalize("INT");
  rid_null = sm_internalize("NULL");
  rid_string = sm_internalize("STRING");
  rid_true = sm_internalize("true");
  rid_false = sm_internalize("false");
  rid_nil = sm_internalize("nil");

  return(0);
}

EXPORT scantest()
{
  int t;
  int oldlineno = 1;

  infile = stdin;
  do {
    t = yylex();
    if (oldlineno != lineno) 
      for( ; oldlineno < lineno; oldlineno++) putchar('\n');
    printf("%d ", t);
  } while( t );
}
