/***********************************************************************
     "token.o": TOKENizer and auxiliary functions.
***********************************************************************/

/***********************************
  (C) Copyright 1992-1993; dit/upm
   Distributed under the conditions stated in the
   TOPO General Public License (see file LICENSE)
 ***********************************
 $Log: token.c,v $
 * Revision 1.2  1993/03/29  18:14:34  lotos
 * tokenizer redesigned for (future) control lines
 *
 * Revision 1.1  1993/03/18  11:14:24  lotos
 * Initial revision
 *
 ***********************************/

/* KJT 29/10/04: changed to use "stdarg" instead of "varargs" */

#ifndef lint
static char rcsid[]= "$Id: token.c,v 1.2 1993/03/29 18:14:34 lotos Exp $";
#endif

#define token_IMP

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <stdarg.h>
#include "glad.hh"
#include "input.hh"
#include "support.hh"
#include "token.hh"


/* pseudo-tokens */
#define ANTEOF		-1
#define CMTIGN		-2
#define CMTEOF		-3
#define CTLLINE		-4
#define CTLINCLUDE	-5
#define CTLBAD		-6

typedef struct
{ int type;
  char *text;
  int size;
  int line;
  char *file;
} sTKN;

PRIVATE sTKN TKN= { 0, NULL, 0, 0, NULL };

PRIVATE int
new_token ()
{
  static int first= TRUE;

  if (first)
  { FILE* fp;

    TKN.file= prog_flag.src_file;
    TKN.line= 1;
    first= FALSE;
    fp= fopen(TKN.file, "r");
    if (fp == NULL)
      fail("cannot open file \"%s\"", TKN.file);
    ILBinit(fp);
    if (!ILBfill())
      return FALSE;
  }
  else
  { ILBcont();
    TKN.type= 0;
    if (TKN.size != 0)
    { TKN.line+= TKN.size;
      TKN.size= 0;
    }
  }
  for (;;)
  { if (ILBchar() == '\0')
    { ILBclear();
      if (!ILBfill())
      { TKN.text= NULL;
	return FALSE;
      }
    }
    if (!isascii(ILBchar()))
      break;
    if (!isspace(ILBchar()))
      break;
    if (ILBchar() == '\n')
      ++TKN.line;
    ILBforth(1);
  }
  TKN.text= ILBpoint();
  return TRUE;
}

PRIVATE int
isspec (c)
  char c;
{
  switch (c)
  { case '#':
    case '%':
    case '&':
    case '*':
    case '+':
    case '-':
    case '.':
    case '/':
    case '<':
    case '=':
    case '>':
    case '@':
    case '\\':
    case '^':
    case '~':
    case '{':
    case '}':
      return TRUE;
  }
  return FALSE;
}

PRIVATE tSYM key_tbl[]= { "specification",	SPECIFICATION,
			  "sorts",		SORTS,
			  "opns",		OPNS,
			  "any",		ANY,
			  "forall",		FORALL,
			  "->",			ARROW,
			  "=>",			IMPLICATION,
			  "endspec",		ENDSPEC,
			  NULL,			0
			};

PRIVATE int
get_ident ()
{
  static int first= TRUE;

  if (first)
  { sort_sym(key_tbl, FALSE);
    first= FALSE;
  }
  if (isascii(ILBchar()) &&
      isalnum(ILBchar()))
  { do
      ILBforth(1);
    while ((isascii(ILBchar()) &&
	    isalnum(ILBchar())) ||
	   ILBchar() == '_');
    while (ILBnext(-1) == '_')
      ILBforth(-1);
  }
  else if (isspec(ILBchar()))
  { do
      ILBforth(1);
    while (isspec(ILBchar()));
  }
  else
    return FALSE;
  ILBstop();
  TKN.type= find_sym(TKN.text, key_tbl);
  if (TKN.type == 0)
    if (isascii(TKN.text[0]) &&
	isalpha(TKN.text[0]))
      TKN.type= IDENTIFIER;
    else
      TKN.type= SPECIAL;
  return TRUE;
}

PRIVATE int
ctl_line ()
{
  /* TKN.line= <LINE> - 1; */
  /* TKN.file= newid(<FILE>); */
  return FALSE;
}

PRIVATE int
ctl_include ()
{
  /* push_file(...); */
  /* pop_file(...); */
  return FALSE;
}

PRIVATE int
get_control ()
{
  if (ctl_line())
    ; /* ... */
  else if (ctl_include())
    ; /* ... */
  else
    ; /* ERROR */
  return FALSE;
}

PRIVATE tSYM *ant_tbl= NULL;

PRIVATE int
get_ant ()
{
  static int first= TRUE;
  static int state= 0;
  static int end= 0;

  if (first)
  { int i;

    talloc(ant_tbl, annot_list_size() + 1);
    for (i= 0; i < annot_list_size(); ++i)
    { ant_tbl[i].name= annot_list(i)->name;
      ant_tbl[i].type= annot_list(i)->token;
    }
    ant_tbl[i].name= NULL;
    ant_tbl[i].type= 0;
    sort_sym(ant_tbl, TRUE);
    first= FALSE;
  }
  if (state == 0)
  { int found;

    if (ILBchar() != '(' ||
	ILBnext(1) != '*' ||
	ILBnext(2) != '|')
      return FALSE;
    found= TRUE;
    for (end= 3;; ++end)
    { if (ILBnext(end) == '\0' &&
	  !ILBfill())
      { found= FALSE;
	break;
      }
      if (ILBnext(end) == '|' &&
	  ILBnext(end + 1) == '*' &&
	  ILBnext(end + 2) == ')')
	break;
    }
    end+= ILBforth(0);
    TKN.text= ILBpoint();
    if (found)
    { ILBforth(3);
      ILBstop();
      TKN.type= ANTBEGIN;
      state= 1;
    }
    else
    { ILBforth(3);
      do
	if (ILBchar() == '\n')
	  ++TKN.size;
      while (ILBforth(1) < end);
      ILBstop();
      TKN.type= ANTEOF;
    }
    return TRUE;
  }
  if (ILBforth(0) == end)
  { ILBforth(3);
    ILBstop();
    TKN.type= ANTEND;
    state= 0;
    return TRUE;
  }
  switch (state)
  { case 1:
      while (ILBforth(1) < end)
	if (isascii(ILBchar()) &&
	    isspace(ILBchar()))
	  break;
      ILBstop();
      TKN.type= find_sym(TKN.text, ant_tbl);
      if (TKN.type == 0)
	TKN.type= ANTWORD;
      state= 2;
      break;
    case 2:
    { int word_cnt= 0;

      do
      { ILBforth(1);
	if (isascii(ILBchar()) &&
	    isspace(ILBchar()))
	{ ++word_cnt;
	  do
	  { if (ILBchar() == '\n')
	      ++TKN.size;
	    ILBforth(1);
	  } while (isascii(ILBchar()) &&
		   isspace(ILBchar()));
	}
      } while (ILBforth(0) < end);
      if (isascii(ILBnext(-1)) &&
	  isspace(ILBnext(-1)))
	do
	{ ILBforth(-1);
	  if (ILBchar() == '\n')
	    --TKN.size;
	} while (isascii(ILBnext(-1)) &&
		 isspace(ILBnext(-1)));
      else
	++word_cnt;
      abort_if(word_cnt == 0)
      ILBstop();
      if (word_cnt == 1)
	TKN.type= ANTWORD;
      else
	TKN.type= ANTTEXT;
      state= 3;
      break;
    }
    default:
      abort_if(TRUE)
  }
  return TRUE;
}

PRIVATE int
get_cmt ()
{
  int level;
  int shift;

  if (ILBchar() != '(' ||
      ILBnext(1) != '*')
    return FALSE;
  level= 1;
  shift= 2;
  while (level != 0)
  { if (ILBnext(shift) == '\0' &&
	!ILBfill())
      break;
    switch (ILBnext(shift))
    { case '(':
	if (ILBnext(shift + 1) == '*')
	{ ++level;
	  ++shift;
	}
	break;
      case '*':
	if (ILBnext(shift + 1) == ')')
	{ --level;
	  ++shift;
	}
	break;
      case '\n':
	++TKN.size;
	break;
    }
    ++shift;
  }
  TKN.text= ILBpoint();
  ILBforth(shift);
  ILBstop();
  if (level == 0)
    TKN.type= CMTIGN;
  else
    TKN.type= CMTEOF;
  return TRUE;
}

PRIVATE int
get_char ()
{
  ILBforth(1);
  ILBstop();
  TKN.type= *TKN.text;
  return TRUE;
}

PRIVATE int
get_eof ()
{
  abort_if(TKN.text != NULL)
  TKN.text= "EOF";
  return TRUE;
}

PUBLIC token *
gtk ()
{
  static token tkn;
  static int file= -1;

  for (;;)
  { if (!new_token())
    { (void) get_eof();
      break;
    }
    if (get_ant())
      ;
    else if (get_cmt())
      ;
    else if (get_control())
      ;
    else if (get_ident())
      ;
    else
      (void) get_char();
    switch (TKN.type)
    { case ANTEOF:
	SYNmiss("EOF found while scanning annotation");
	continue;
      case CMTIGN:
	continue;
      case CMTEOF:
	SYNmiss("EOF found while scanning comment");
	continue;
      case CTLLINE:
      case CTLINCLUDE:
	file= -1;
	continue;
      case CTLBAD:
	SYNmiss("bad control line");
	continue;
    }
    break;
  }
  abort_if(TKN.type < 0)
  if (file < 0)
    file= STHadd(TKN.file, SymbolTable, HashTable, FALSE);
  switch (TKN.type)
  { case ANTWORD:
    case ANTTEXT:
      talloc(tkn.yylval.antv, strlen(TKN.text) + 1);
      (void) strcpy(tkn.yylval.antv, TKN.text);
      tkn.yylval.line= newI2(TKN.line, file);
      break;
    case SPECIFICATION:
    case ANY:
    case FORALL:
    case ENDSPEC:
      tkn.yylval.name= -7007;
      tkn.yylval.line= newI2(TKN.line, file);
      break;
    case IDENTIFIER:
    case SPECIAL:
      tkn.yylval.name= STHadd(TKN.text, SymbolTable, HashTable, FALSE);
      tkn.yylval.line= newI2(TKN.line, file);
      break;
  }
  tkn.type= TKN.type;
  return &tkn;
}

PUBLIC void
SYNmiss (char *msg, ...)
{
  va_list args;

  va_start(args, msg);
  if (TKN.text == NULL)
    varargs_error(start_error, "\"%s\", line %d: ",
		  TKN.file, TKN.line);
  else
  { char *ptr;

    for (ptr= TKN.text; *ptr != '\0'; ++ptr)
      if (isascii(*ptr) &&
	  isspace(*ptr))
	break;
    if (*ptr == '\0')
      varargs_error(start_error, "%s(%d): at \"%s\", ",
		    TKN.file, TKN.line, TKN.text);
    else
      varargs_error(start_error, "%s(%d): at \"%.*s ...\", ",
		    TKN.file, TKN.line, ptr - TKN.text, TKN.text);
  }
  complete_error(msg, args);
  ++errorcount;
  va_end(args);
}
