/* Hey, Emacs, this is not -*- C -*-, but it should look like as it were...
 *
 *		ADSP 2181 Assembler -- Lexical Analysis
 *
 *		(c) 1996 Martin Mares, <mj@k332.feld.cvut.cz>
 *
 *		This software may be freely distributed and used according to the terms
 *		of the GNU General Public License. See file COPYING in any of the GNU utilities.
 */

%option nounput
%option noinput

%{
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>

#include "as2181.h"
#include "syn.tab.h"

#define YY_NO_UNPUT

/* Internal functions */

static void map_lines(ulg, char *);
static void upcase_it(char *);

/* Scanner input */

FILE *infile;
#define yyin infile

ulg lino;								/* Line number */
int err_max = 10;						/* Maximal number of errors before stopping */
%}

%x PCOMM CCOMM

ALPHA [a-zA-Z_]
DIGIT [0-9]
ODIGIT [0-7]
HDIGIT [0-9a-fA-F]
ALNUM [0-9a-zA-Z_@]
WHITE [ \t]

%%

0{ODIGIT}+ {							/* Numbers */
  yylval.i = strtoul(yytext+1, NULL, 8);
  return NUM;
}

0x{HDIGIT}+ {
  yylval.i = strtoul(yytext+2, NULL, 16);
  return NUM;
}

{DIGIT}+ {
  yylval.i = strtoul(yytext, NULL, 10);
  return NUM;
}

"H#"{HDIGIT}+ {
  yylval.i = strtoul(yytext+2, NULL, 16);
  return NUM;
}

"B#"[01]+ {
  yylval.i = strtoul(yytext+2, NULL, 2);
  return NUM;
}

"(SS)" { yylval.i = MC_SS; return MACCLASS; } /* Classes */
"(SU)" { yylval.i = MC_SU; return MACCLASS; }
"(US)" { yylval.i = MC_US; return MACCLASS; }
"(UU)" { yylval.i = MC_UU; return MACCLASS; }
"(RND)" { yylval.i = MC_RND; return MACCLASS; }

"(HI)" { yylval.i = SC_HI; return SHCLASS; }
"(LO)" { yylval.i = SC_LO; return SHCLASS; }
"(HIX)" { yylval.i = SC_HIX; return SHCLASS; }

"MR" { return MR; }
"MF" { return MF; }
"SR" { return SR; }

"<<" return LL;							/* Operators */
">>" return RR;
[-+*()/\[\],;:=%^] return yytext[0];

'.' {									/* Character constants */
  yylval.i = yytext[1];
  return NUM;
}

{ALPHA}{ALNUM}* {						/* Symbols */
  const struct as_kwd *k;
  char buf[64];

  if (yyleng > 63)
	{
	  cerr("Symbol too long");
	  yytext[63] = 0;
	  yyleng = 63;
	}
  upcase_it(buf);
  k = is_kwd(buf, yyleng);
  if (k)
	{
	  yylval.i = k->value;
	  return k->class;
	}
  yylval.s = find_symbol(case_insensitive ? buf : yytext, yyleng, thismod);
  return SYM;
}

"."{ALNUM}+ {							/* Directives */
  const struct as_kwd *k;

  upcase_it(yytext);
  k = is_kwd(yytext, yyleng);
  if (k)
	{
	  yylval.i = k->value;
	  return k->class;
	}
  err("Invalid compiler directive");
}

<INITIAL><<EOF>> {						/* End of file */
  return GOT_EOF;
}

"# ".*\n {								/* CPP line info */
  char *c = yytext + 2;
  char *d = c;
  char *e = yytext + yyleng - 2;
  ulg xxx;

  while (*d >= '0' && *d <= '9')
	d++;
  while (c != e && *e != '"')
	e--;
  if (c == d || *d != ' ' || d[1] != '"' || *e != '"')
	err("Invalid CPP file / line number information");
  *e = *d = 0;
  xxx = strtoul(c, NULL, 10);
  map_lines(xxx, d+2);
}

<*>\n lino++;

"{" BEGIN(PCOMM);						/* Comments */
<PCOMM>"}" BEGIN(INITIAL);
<PCOMM>"{" warn("'{' in comment");

"/*" BEGIN(CCOMM);
<CCOMM>"*/" BEGIN(INITIAL);
<CCOMM>"/*" warn("/* in comment");

<PCOMM,CCOMM>. ;
<PCOMM,CCOMM><<EOF>> err("Non-terminated comment");

{WHITE}+ ;								/* Eat up white space */

. err("Invalid character (`%c')", yytext[0]); /* "Catch all" rule */

%%

/* LEX Wrapper Function */

int
yywrap(void)
{
  return 1;
}

/* Line lookup */

struct line_info {
  struct line_info *next;
  ulg abs, rel;
  char filename[1];
};

static struct line_info *first_li;
static struct line_info **last_li = &first_li;

static void
map_lines(ulg rel, char *name)
{
  struct line_info *l = xmalloc(sizeof(struct line_info) + strlen(name));

  *last_li = l;
  last_li = &l->next;
  l->next = NULL;
  l->abs = lino;
  l->rel = rel;
  strcpy(l->filename, name);
}

ulg
lookup_line(ulg abs, char **name)
{
  struct line_info *l;
  static struct line_info *cache = NULL;

  if (cache && cache->abs <= abs)
	l = cache;
  else
	l = first_li;
  if (!l)
	fatal_error("Corrupted line list");
  while (l->next && l->next->abs <= abs)
	l = l->next;
  *name = l->filename;
  cache = l;
  return abs - l->abs + l->rel;
}

/* Error messages */

static int err_count, warn_count;

static void
repnum(char *form, int count)
{
  fprintf(stderr, form, count, (count == 1) ? "" : "s");
}

void
report_warns(void)
{
  if (warn_count)
	repnum("%d warning%s detected.\n", warn_count);
}

static void errhalt(void) NONRET;
static void
errhalt(void)
{
  if (warn_count)
	repnum("%d warning%s, ", warn_count);
  repnum("%d error%s detected.\n", err_count);
  exit(1);
}

void
errstop(void)
{
  if (err_count)
	errhalt();
}

static void
vmsg(char *msg, va_list args, char *start, char *finish, ulg line)
{
  char *fn;

  if (line == ~0)
	fprintf(stderr, "%s: ", start);
  else
	{
	  line = lookup_line(line, &fn);
	  fprintf(stderr, "%s in file %s, line %d: ", start, fn, line);
	}
  vfprintf(stderr, msg, args);
  fputs(finish, stderr);
}

static void
errcheck(void)
{
  if (++err_count >= err_max)
	{
	  if (err_max > 1)
		fprintf(stderr, "That makes %d errors, try again...\n", err_count);
	  errstop();
	}
}

static char emsg[] = "Error", wmsg[] = "Warning", imsg[] = "Internal error",
            etrail[] = "!\n", wtrail[] = ".\n", nmsg[] = "Note";

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

  va_start(args, msg);
  vmsg(msg, args, emsg, etrail, lino);
  errcheck();
  errhalt();
}

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

  va_start(args, msg);
  vmsg(msg, args, emsg, etrail, lino);
  errcheck();
  va_end(args);
}

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

  va_start(args, msg);
  vmsg(msg, args, imsg, etrail, lino);
  errcheck();
  errhalt();
}

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

  va_start(args, msg);
  if (!hide_warns)
	{
	  vmsg(msg, args, wmsg, wtrail, lino);
	  warn_count++;
	}
  va_end(args);
}

void
lerr(ulg li, char *msg, ...)
{
  va_list args;

  va_start(args, msg);
  vmsg(msg, args, emsg, etrail, li);
  errcheck();
  errhalt();
}

void
clerr(ulg li, char *msg, ...)
{
  va_list args;

  va_start(args, msg);
  vmsg(msg, args, emsg, etrail, li);
  errcheck();
  va_end(args);
}

void
ilerr(ulg li, char *msg, ...)
{
  va_list args;

  va_start(args, msg);
  vmsg(msg, args, imsg, etrail, li);
  errcheck();
  errhalt();
}

void
lwarn(ulg li, char *msg, ...)
{
  va_list args;

  va_start(args, msg);
  if (!hide_warns)
	{
	  vmsg(msg, args, wmsg, wtrail, li);
	  warn_count++;
	}
  va_end(args);
}

void
lnote(ulg li, char *msg, ...)
{
  va_list args;

  va_start(args, msg);
  vmsg(msg, args, nmsg, wtrail, li);
  va_end(args);
}

char *
syserr(void)
{
  return strerror(errno);
}

/* Symbols */

#define HASH_SIZE 1024

static struct symbol *hash[HASH_SIZE];

ulg
hash_value(char *c, ulg len)
{
  ulg h;
  char *d;

  h = len;
  for(d=c; *d; d++)
	h = 13*h + *d;
  h = h % HASH_SIZE;
  return h;
}

struct symbol *
find_symbol(char *c, ulg len, struct module *context)
{
  struct symbol *s, *b;
  ulg h = hash_value(c, len);

  b = NULL;
  for(s=hash[h]; s; s=s->next)
	if (!strcmp(s->name, c) && (!s->scope || s->scope == context) && (!b || b->scope))
	  b = s;
  if (b)
	return b;

  s = xmalloc(sizeof(struct symbol) + len);
  strcpy(s->name, c);
  s->next = hash[h];
  s->value = NULL;
  s->scope = context;
  hash[h] = s;
  return s;
}

void
expose_symbol(struct symbol *s)
{
  ulg h;
  struct symbol *k;
  ulg coll = 0;

  h = hash_value(s->name, strlen(s->name));
  for(k=hash[h]; k; k=k->next)
	if (!strcmp(k->name, s->name) && k != s)
	  {
		if (!k->scope)
		  {
			coll++;
			cerr("`%s' already defined as public");
		  }
		else if (k->value)
		  {
			if (!coll++)
			  cerr("`%s' already defined elsewhere", s->name);
			lnote(LINE(k->value), "See this line for previous definition");
		  }
		else
		  {
			struct symref *m = NEW_NODE(symref, N_SYMREF);
			m->sym = s;
			k->value = NODE m;
		  }
	  }
  if (!coll)
	s->scope = NULL;
}

/* Init */

void
init_lex(char *src, int preprocess)
{
  lino = ~0;
  if (!preprocess)
	{
	  infile = fopen(src, "r");
	  if (!infile)
		err("Unable to open `%s': %s", src, syserr());
	}
  else
	{
	  pid_t pid;
	  int fh;
	  int pip[2];

	  fh = open(src, O_RDONLY);			/* Test if the file exists */
	  if (fh < 0)
		err("Unable to open `%s': %s", src, syserr());
	  close(fh);
	  if (pipe(pip) < 0)
		err("Unable to create pipe: %s", syserr());
	  pid = fork();
	  if (pid < 0)
		err("Fork failed: %s", src, syserr());
	  if (!pid)    /* child CPP process */
		{
		  dup2(pip[1], STDOUT_FILENO); /* duplicate pipe write end into stdout */
		  close(pip[0]);
		  close(pip[1]);

                  /* Call cpp and direct output to pipe */
		  execlp("cpp", "cpp",
                         "-Wall", "-nostdinc", "-undef", "-DADSP2181",
                         "-traditional-cpp", src, NULL);
		  err("Cannot execute preprocessor: %s", syserr());
		}
	  close(pip[1]); /* close write end of the pipe */
          wait(NULL);    /* wait for preprocessor to finish */
	  infile = fdopen(pip[0], "r"); /* open preprocessed buffer for reading */

	  if (!infile)
		err("Error opening cpp pipe: %s", syserr());
	}
  lino = 0;
  map_lines(1, src);
}

static void
upcase_it(char *d)
{
  char *c;

  for(c=yytext; *c; c++)
	if (*c >= 'a' && *c <= 'z')
	  *d++ = *c - 0x20;
    else
	  *d++ = *c;
  *d = 0;
}