
/* C pre-processor functions */

#include <stdio.h>
#include "cc65.h"

#define	ch()	(*lptr)

#ifndef M6502
#include <errno.h>
extern int debug;
#endif

char *ExpandIncludePath();

/* extern char	ascii_tab[]; */

char * findmac();
char * findarg();
int keepch();
int keepstr();
int replmac();

#ifdef old_cruft
char a_pp0[] = {
		 0x68, 0x68, 0x85, 0xf7, 0x68, 0x85, 0xf6, 0xa2,
		 0x00, 0xa0, 0xff, 0xc8, 0xb1, 0xf6, 0xf0, 0x20,
		 0xc9, 0x7f, 0xd0, 0x06, 0xa9, 0x20, 0x91, 0xf6,
		 0xd0, 0xf1, 0xc9, 0x27, 0xf0, 0x0f, 0xc9, 0x22,
		 0xf0, 0x0b, 0xc9, 0x2f, 0xd0, 0xe5, 0xc8, 0xb1,
		 0xf6, 0xc9, 0x2a, 0xd0, 0xde, 0xe8, 0xd0, 0xdb,
		 0x60
};
#endif

/* predicate for whitespace */
#ifdef M6502
#asm
_white_p:
	jsr	popax		; get char
	ldx	#0		; zap x
	cmp	#$20		; space?
	beq	white_1
	cmp	#$7F		; ATASCII tab?
	beq	white_1
	lda	#0
	rts			; return 0
white_1:
	lda	#1
	rts
#endasm
#else
int white_p(c)
char c;
{
  return ((c == ' ') || (c == TABCHAR));
}

#endif

/* predicate for quote char */
#ifdef M6502
#asm
_quote_p:
	jsr	popax		; get the char
	ldx	#0
	cmp	#34		; double quote?
	beq	quote_1
	cmp	#39		; single quote?
	beq	quote_1
	lda	#0
	rts
quote_1:
	lda	#1
	rts
#endasm
#else
int quote_p(c)
char c;
{
  return ((c == '"') || (c == 39));
}

#endif

/*
	doinclude()
	Open an include file.
*/
doinclude()
{
  char angflag;
  char c;
  char * p;
  FILE * f;

  if (ifile >= MAXFILES - 1)
    {
      Error("too many open files");
      goto done;
    }

  mptr = mline;
  skipblank();
  if (!strchr("\"<", (angflag = cgch())))
    {
      Missing("quote or <");
      goto done;
    }
  if (angflag == '<')
    angflag = '>';		/* get right terminator */

  filetab[ifile].f_ln = ln;
  strcpy(p = Gmalloc(strlen(fin) + 1), fin); /* save existing file name */
  filetab[ifile].f_name = p;
  filetab[ifile].f_iocb = inp;
  ++ifile;

  fin = p = fname;

  /* if we've got an angle frob, try to insert include dir */
  if ((angflag == '>') && (incl_dir))
    {
      strcpy(p, incl_dir);
      p += strlen(p);
    }

  while ((c = *lptr) && (c != angflag))
    {
      *p = c;
      ++p;
      ++lptr;
    }
  if (c == '\0')
    {
      Missing("quote or >");
    }
  *p = '\0';

#ifdef M6502
  /* make sure it's got a D: in front of it */
  fin = frob_name(fname, NULL, fname);
#else
  if(debug)
   fprintf(stderr, "incl_dir: %s\n", incl_dir);
  
   if (angflag == '>' &&  !incl_dir)
    {
	fin = ExpandIncludePath(fin);  /* -Intruder */
    } 

#endif
  if (verbose)
    printmsg("including '%s'\n", fin);

  if (!(inp = (FILE *)fopen(fin, "r")))
    {
      inp = filetab[--ifile].f_iocb; /* oops! restore old file */
      fin = filetab[ifile].f_name;
      Error("open failure on include file");
#ifndef M6502
      printf("error %d\n", errno);
#endif
    }
  else
    {
      ln = 0;
    }

 done:
  /*
    clear rest of line so next read will come from new file (if open)
  */
  do_kill();
}

/*
  keepch( c )
  Put character c into translation buffer.
*/
#ifdef M6502
#asm
_keepch:
	jsr	popax		; get the char
keep_0:				; for use by keepstr, below
	ldy	#0
	sta	(_mptr),y	; store it.  assumes mptr on page 0
	inc	_mptr
	bne	keep_1
	inc	_mptr+1
keep_1:
	rts
#endasm
#else
keepch(c)
char c;
{
  /*
  if (mptr <= mline + mpmax) *mptr++ = c;
  */
  return (*mptr++ = c);
}

#endif

/*
  keepstr( str )
  Put string str into translation buffer.
*/
#ifdef M6502
#asm
_keepstr:
	jsr	popax		; get the string
	sta	ptr1
	stx	ptr1+1
	ldy	#0
keeps_1:
	lda	(ptr1),y	; get a char
	beq	keeps_2
	jsr	keep_0		; stash it, bump lptr
	inc	ptr1
	bne	keeps_1
	inc	ptr1+1
	bne	keeps_1
keeps_2:
	rts
#endasm
#else
keepstr(str)
char * str;
{
  while (*str)
    *mptr++ = *str++;
}

#endif

/* added by jrd, as we do asm stuff the easy way */
static char * endasm = "#endasm";
doasm()
{
  do_kill(); /* zap the line with the #asm on it */
  asm_kludge = 0;		/* say we're done with this one */
  while (1)
    {
      if (readline() == 0)	/* off eof?? */
	{
	  Missing(endasm);
	  break;
	}
      if (matchstr(lptr, endasm))	/* end of asm sequence? */
	break;			/* yup, stop here */
      else
	{
	  ol(lptr);		/* output the line directly */
	  do_kill();		/* zap this line */
	}
    }
  do_kill();			/* so we start reading at next line */
}

/*
  preprocess()
  C preprocessor.
*/

/* stuff used to bum the keyword dispatching stuff */
#define D_ILGL	0
#define D_DEF	1
#define D_INC	2
#define D_UND	3
#define	D_IFD	4
#define D_IFND	5
#define D_IF	6
#define D_END	7
#define	D_ELS	8
#define D_ASM	9

struct tok_elt pre_toks[] =
{
  {"define", D_DEF},
  {"include", D_INC},
  {"undef", D_UND},
  {"ifdef", D_IFD},
  {"ifndef", D_IFND},
  {"if", D_IF},
  {"endif", D_END},
  {"else", D_ELS},
  {"asm", D_ASM},	/* jrd added this one */
  {0, 0}
};

int tok_assoc(sym, toks)
char * sym;
struct tok_elt * toks;
{
  while (toks->toknam && strcmp(toks->toknam, sym))
    ++toks;
  return (toks->toknbr);
}

preprocess()
{
int c;
int k;
int skip;
char sname[NAMESIZE];

  /*
    process compiler directives
  */
  lptr = line;
  skip = 0;
  while ((c = ch()) == '\0' || c == '#' || skip)
    {
      if (c == '#')
	{
	  ++lptr;
	  skipblank();
	  if (!issym(sname))
	    {
	      Missing("compiler directive");
	      do_kill();
	    }
	  else
	    switch (tok_assoc(sname, pre_toks))
	      {
/*	    if (strcmp(sname, "define") == 0) 	*/
	      case D_DEF:
		{
		  if (skip == 0)
		    {
		      addmac();
		      macdef = 1;
		    }
		}
/*	      else 	*/
		break;
/*	    if (strcmp(sname, "include") == 0) 	*/
	      case D_INC:
		{
		  if (skip == 0)
		    doinclude();
		}
/*	      else 	*/
		break;
/*	    if (strcmp(sname, "undef") == 0) 	*/
	      case D_UND:
		{
		  if (skip == 0)
		    doundef();
		}
/*	      else 	*/
		break;
/*	    if (strcmp(sname, "ifdef") == 0) 	*/
	      case D_IFD:
		{
		  skip = doifdef(skip, 1);
		}
/*	      else 	*/
		break;
/*	    if (strcmp(sname, "ifndef") == 0) 	*/
	      case D_IFND:
		{
		  skip = doifdef(skip, 0);
		}
/*	      else 	*/
		break;
/*	    if (strcmp(sname, "if") == 0) 	*/
	      case D_IF:
		{
		  skip = doiff(skip);
		}
/*	      else 	*/
		break;
/*	    if (strcmp(sname, "endif") == 0) 	*/
	      case D_END:
		{
		  if (i_ifdef >= 0)
		    {
		      skip = s_ifdef[i_ifdef--] & 1;
		    }
		  else
		    {
		      Error("#endif without matching #if");
		    }
		}
/*	      else 	*/
		break;
/*	    if (strcmp(sname, "else") == 0)	*/
	      case D_ELS:
		{
		  if (s_ifdef[i_ifdef] & 2)
		    {
		      if (s_ifdef[i_ifdef] & 4)
			{
			  skip = !skip;
			}
		      s_ifdef[i_ifdef] ^= 2;
		    }
		  else
		    {
		      Error("#else without matching #if");
		    }
		}
/*	      else 	*/
		break;
	      case D_ASM:
		{
/* Oops!  Can't doasm here, as we might be prefetching
   a token while in the middle of something that can't be
   interrupted.  return from preprocess, and let the
   kludge in gettok deal with it.
   			doasm();
			break;
*/
		  lptr = line;
		  return;
		}
	      default:
		{
		  Illegal("compiler directive");
		  do_kill();
		}
	      }			/* end of case on directive */
	}
      if (readline() == 0)
	{
	  if (i_ifdef >= 0)
	    Missing("#endif");
	  return;
	}
    }
  xlateline();
#ifndef M6502
  if (debug || verbose)
    printf("LINE: %s\n", line);
#else
  if (verbose > 1)
    {
      printmsg("line: %s\n", line);
    }
#endif
}

/*
  xlateline()
  Translate line.
*/
xlateline()
{
int cnt;
int done;
char * p;

  if ((macdef == 0) && (pp0(line) == 0))
    {
      return;
    }
  done = pp1(line, mline);
  cnt = 5;
do
{
  p = line;
  line = mline;
  mline = p;
  if (done)
    break;
  done = pp2(line, mline, findmac, replmac);
  keepch('\0');
} while (--cnt);
  lptr = line;
}

/*
  pp0( curlin )
  Convert tabs to blanks and determine whether further pre-processing
  is necessary
*/
/* old stuff, from const, above...
#ifdef M6502
asm	a_pp0;
	pla			; 0x68,
	pla			; 0x68,
	sta	$F7		; 0x85, 0xf7,
	pla			; 0x68,
	sta	$F6		; 0x85, 0xf6,
	ldx	#0		; 0xa2, 0x00,
	ldy	#$FF		; 0xa0, 0xff,
lab1:
	iny			; 0xc8,
	lda	($F6),y		; 0xb1, 0xf6,
	beq	lab2		; 0xf0, 0x20,
	cmp	#$7F		; 0xc9, 0x7f,
	bne	lab3		; 0xd0, 0x06,
	lda	#$20		; 0xa9, 0x20,
	sta	($F6),y		; 0x91, 0xf6,
	bne	lab1		; 0xd0, 0xf1,
lab3:
	cmp	#$27		; 0xc9, 0x27,
	beq	lab4		; 0xf0, 0x0f,
	cmp	#$22		; 0xc9, 0x22,
	beq	lab4		; 0xf0, 0x0b,
	cmp	#$2F		; 0xc9, 0x2f,
	bne	lab1		; 0xd0, 0xe5,
	iny			; 0xc8,
	lda	($F6),y		; 0xb1, 0xf6,
	cmp	#$2A		; 0xc9, 0x2a,
	bne	lab1		; 0xd0, 0xde,
lab4:
	inx			; 0xe8,
	bne	lab1		; 0xd0, 0xdb,
lab2:
	rts			; 0x60
#else
*/

#ifdef M6502
#asm
_pp0:
	jsr	popax		; get ptr to line
	sta	ptr1
	stx	ptr1+1
	ldx	#0
	ldy	#0
pp0_0:
	lda	(ptr1),y	; get a char
	beq	pp0_9		; eol!  done
	cmp	$#7F		; atascii tab?
	bne	pp0_1
	lda	#$20		; subst space
	sta	(ptr1),y
	bne	pp0_7		; keep going
pp0_1:
	cmp	#'/'		; slash?
	bne	pp0_2
	iny			; look at next char
	lda	(ptr1),y
	beq	pp0_9		; eol! done
	cmp	#'*'		; comment?
	bne	pp0_7		; nope, keep going
	beq	pp0_8		; yes, return 1
pp0_2:
	cmp	#39		; single quote?
	beq	pp0_8		; yes, return 1
	cmp	#34		; double quote?
	beq	pp0_8		; yes, return 1
pp0_7:
	iny			; bump y
	bne	pp0_0		; round again
pp0_8:
	lda	#1
	rts
pp0_9:
	lda	#0
	rts
#endasm
#else
pp0(curlin)
char * curlin;
{
int flag;

  flag = 0;
  while (*curlin)
    {
      switch (*curlin)
	{
	case TABCHAR:
	  *curlin = ' ';
	  break;
	case '\'':
	case '"':
	  ++flag;
	  break;
	case '/':
	  if (curlin[1] == '*')
	    {
	      ++flag;
	      ++curlin;
	    }
	  break;
	}
      ++curlin;
    }
  return (flag);
}

#endif

/*
  pp1( from, to )
  Preprocessor pass 1.  Remove whitespace and comments.
*/
pp1(from, to)
char * from;
char * to;
{
int c;
int done;
char sname[NAMESIZE];

  lptr = from;
  mptr = to;
  done = 1;
  while (c = ch())
    {
      if (white_p(c))
	{
	  keepch(' ');
	  skipblank();
	}
      else if (macdef && is_alpha(c))
	{
	  symname(sname);
	  if (macltab[sname[0]])
	    {
	      done = 0;
	    }
	  keepstr(sname);
	}
      else if (quote_p(c))
	{
	  skipquote(c);
	}
      else if ((c == '/') && (nch() == '*'))
	{
	  keepch(' ');
	  comment();
	}
      else
	{
	  /*
          keepch(gch());
          */
	  *mptr++ = *lptr++;
	}
    }
  keepch('\0');
  return (done);
}

/*
  pp2( from, to , pfind, prepl )
  Preprocessor pass 2.  Perform macro substitution.
*/
pp2(from, to, pfind, prepl)
char * from;
char * to;
char * (*pfind) ();
int (*prepl) ();
{
int c;
int no_chg;
char * s;
char sname[NAMESIZE];

  lptr = from;
  mptr = to;
  no_chg = 1;
  while (c = ch())
    {
      if (is_alpha(c))
	{
	  symname(sname);
	  if (s = (*pfind) (sname))
	    {
	      (*prepl) (s);
	      no_chg = 0;
	    }
	  else
	    {
	      keepstr(sname);
	    }
	}
      else if (quote_p(c))
	{
	  skipquote(c);
	}
      else
	{
	  /*
          keepch(gch());
          */
	  *mptr++ = *lptr++;
	}
    }
  return (no_chg);
}

/*
  replmac( pdef )
  Function copies definition (pointed to by pdef) of current macro
  symbol to mline.
*/
replmac(pdef)
char * pdef;
{
  pdef += strlen(pdef) + 1;
  if (*pdef & 0x80)
    {
      if (domcall(pdef + 1) == 0)
	{
	  do_kill();
	}
    }
  else
    {
      keepstr(pdef);
    }
}

/*
  doundef()
  Process #undef directive
*/
doundef()
{
char * s;
char sname[NAMESIZE];

  skipblank();
  if (macname(sname) && (s = findmac(sname)))
    {
      *s = '\0';
    }
}

/*
  doiff()
  Process #if directive
*/
doiff(skip)
int skip;
{
struct expent lval;
int sv1;
int sv2;

  sv1 = curtok;
  sv2 = nxttok;
  strcat(line, ";;");
  gettok();
  gettok();
  constexp(&lval);
  curtok = sv1;
  nxttok = sv2;
  return (setmflag(skip, 1, lval.e_const != 0));
}

/*
  doifdef( flag )
  Process #ifdef if flag == 1, or #ifndef if flag == 0.
*/
doifdef(skip, flag)
int skip;
int flag;
{
char sname[NAMESIZE];

  skipblank();
  if (macname(sname) == 0)
    {
      return (0);
    }
  else
    {
      return (setmflag(skip, flag, findmac(sname) != 0));
    }
}

/*
  setmflag( skip, flag, cond )
*/
setmflag(skip, flag, cond)
int skip;
int flag;
int cond;
{
int f;

  if (skip)
    {
      s_ifdef[++i_ifdef] = 3;
      return (1);
    }
  else
    {
      s_ifdef[++i_ifdef] = 6;
      return (flag ^ cond);
    }
}

/*
  addmac()
  Add a macro to the macro table.
*/
addmac()
{
int h;
char * p;
char * pcnt;
char * saveptr;
char sname[NAMESIZE];
char * sptr;

  skipblank();
  if (macname(sname) == 0)
    {
      return;
    }

  /*
   * Save macro name
   */
  h = hash(sname);
  sptr = Gmalloc(100);
  strcpy(sptr + sizeof(char *), sname);
  p = sptr + sizeof(char *) + strlen(sname) +1;
  macltab[sname[0]] = 1;

  if (ch() == '(')
    {
      /*
       * save dummy arguments
       */
      *p++ = 0x80;
      *(pcnt = p++) = 0;
      gch();
      while (1)
	{
	  skipblank();
	  if (ch() == ')')
	    break;
	  if (macname(sname) == 0)
	    {
	      return;
	    }
	  strcpy(p, sname);
	  ++*pcnt;
	  p += strlen(sname) + 1;
	  skipblank();
	  if (ch() != ',')
	    break;
	  gch();
	}
      if (ch() != ')')
	{
	  Illegal("macro function");
	  do_kill();
	  return;
	}
      gch();
    }

  /*
   * Save macro value
   */
  *((char **) sptr) = machtab[h];
  machtab[h] = sptr;
  skipblank();
  saveptr = mptr;
  if (pp0(lptr))
    {
      pp1(lptr, p);
    }
  else
    {
      strcpy(p, lptr);
    }
  mptr = saveptr;
  gsptr = p + strlen(p) + 1;
#ifndef M6502
/* make sure we push it up to an even boundary! */
/*  gsptr += (((long) gsptr) & 0x1); */
  gsptr += sizeof( char *) - ((long)gsptr & (sizeof(char *) -1));
#endif
}

/*
  findmac( sname )
  Look up sname in macro table.
*/
char *
     findmac(sname)
char * sname;
{
int h;
char * p;

  h = hash(sname);
  for (p = machtab[h]; p != NULL; p = *((char **) p))
    {
      if (strcmp(sname, p + sizeof(char *)) == 0)
	{
	  return (p + sizeof(char *));
	}
    }
  return (NULL);
}

/*
  findarg( sname )
  Look up sname in formal argument table, macarg.  There are fcnt
  symbols in the table.
*/
char *
     findarg(sname)
char * sname;
{
int i;
char * p;

  for (i = 0, p = tblptr; i < tbllen; ++i)
    {
      if (strcmp(sname, p) == 0)
	{
	  return (macarg[i]);
	}
      p += strlen(p) + 1;
    }
  return (NULL);
}

/*
  domcall( macptr )
  Process a macro call.
*/
domcall(macptr)
char * macptr;
{
int acnt;
int c;
int k;
int nparen;
char * p;
char * q;
char * saveptr;
char * sv;
char sname[NAMESIZE];

  if (ch() != '(')
    {
      Illegal("macro call");
      return (0);
    }
  gch();
  sv = p = q = Gmalloc(100);
  acnt = 0;
  nparen = 0;
  while (1)
    {
      if ((c = ch()) == '(')
	{
	  *q++ = gch();
	  ++nparen;
	}
      else if (quote_p(c))
	{
	  saveptr = mptr;
	  mptr = q;
	  skipquote(c);
	  q = mptr;
	  mptr = saveptr;
	}
      else if ((c == ',') || (c == ')'))
	{
	  if (nparen == 0)
	    {
	      gch();
	      *q++ = '\0';
	      macarg[acnt++] = p;
	      p = q;
	      if (c == ')')
		break;
	    }
	  else
	    {
	      *q++ = gch();
	      if (c == ')')
		--nparen;
	    }
	}
      else if (c == '\0')
	{
	  if (readline() == 0)
	    {
	      return;
	    }
	}
      else
	{
	  *q++ = gch();
	}
    }

  /*
   * compare formal argument count with actual
   */
#ifdef M6502
  if (acnt != (tbllen = *macptr++))
    {
#else
  if (acnt != (tbllen = *macptr++ & 0xFF))
    {
#endif
      if ((tbllen != 0) || (acnt != 1) || (*macarg[0]))
	{
	  Error("wrong number of arguments");
	  while (acnt < tbllen)
	    macarg[acnt++] = "";
	}
    }

  /*
   * move pointer past formal argument list
   */
  tblptr = macptr;
  for (k = 0; k < tbllen; ++k)
    {
      while (*macptr++)
	;
    }

  saveptr = lptr;
  pp2(macptr, mptr, findarg, keepstr);
  lptr = saveptr;
  gsptr = sv; /* free the temp buffer */
  return (1);
}

/*
  comment()
  Remove comment from line.
*/
comment()
{
  gch();
  gch();
  while ((ch() != '*') ||(nch() != '/'))
    {
      if (ch() == '\0')
	{
	  if (readline() == 0)
	    {
	      Error("EOF in comment");
	      return;
	    }
	}
      else
	{
	  ++lptr;
	}
    }
  gch();
  gch();
}


/*
  skipblank()
  Skip blanks and tabs in the input stream.
*/
skipblank()
{
  while (white_p(ch()))
    {
      gch();
    }
}

/*
  skipquote()
  Copy single or double quoted string from lptr to mptr.
*/
skipquote(qchar)
int qchar;
{
char c;

  keepch(gch());
  while (((c = ch()) !=qchar) && (c != '\0'))
    {
      /* keep escaped char */
      if (c == '\\')
	keepch(gch());
      keepch(cgch());
    }
  if (c)
    {
      keepch(gch());
    }
}

#ifdef why_bother
/*
  IllSym()
  Print "illegal symbol name" error.
*/
IllSym()
{
  Illegal("symbol name");
}

#endif

/*
  macname( sname )
  Get macro symbol name.  If error, print message and do_kill line.
*/
macname(sname)
char * sname;
{
#ifdef old_way
  if (issym(sname) == 0)
    {
      IllSym();
      do_kill();
      return (0);
    }
  else
    {
      return (1);
    }
#else
  return ((issym(sname) ||
	   (Illegal("symbol name"), do_kill(), 0)));
#endif
}
