/* args.c -- STAR Argument Parser

This file is part of STAR, the Saturn Macro Assembler.

   STAR is not distributed by the Free Software Foundation. Do not ask
them for a copy or how to obtain new releases. Instead, send e-mail to
the address below. STAR is merely covered by the GNU General Public
License.

Please send your comments, ideas, and bug reports to
Jan Brittenson <bson@ai.mit.edu>

*/


/* Copyright (C) 1990 Jan Brittenson.

   STAR 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 1, or (at your option) any
later version.

   STAR 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 STAR; see the file COPYING. If not, to obtain a copy, write
to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
USA, or send e-mail to bson@ai.mit.edu. */

/*
 *	dcdarg(cp,narg);
 *		char *cp;	Argument string
 *		int narg;	Number of arguements expected
 *
 */

#include <stdio.h>
#include "star.h"
#include "code.h"
#include "symbols.h"
#include "ctt.h"


/* Globals */
extern long loc0;
extern errcnt, pass;

struct astruct arg[MAXARG];


/* Locals */
static struct astruct
  *ap;

/* String?
 * If so, then move pointer past it and return true,
 * else restore pointer and return false.
 * Note: It will not work properly if the 'strp' points to an empty string,
 *	 in which case it will always return false. All matching is done
 *	 after uppercase conversion of the stream string.
 *
 */
#ifndef DEC11
#ifndef FAST_MTSTR

mtstr(strp, stat)
  char **strp;
  register char *stat;
{
  register char *dyn;

  for(dyn = *strp; *dyn && *stat && toupper(*dyn) == *stat; dyn++, stat++);

  if(!*stat)
    {
      *strp = dyn;
      return(TRUE);
    }

  return(FALSE);
}
#endif
#endif



/* Expression-class characters */
#ifndef isexpr
static isexpr(c)
  char c;
{
  return(c == '\'' ||
	 c == '-' ||
	 c == '+' ||
	 c == '=' ||
	 c == '*' ||
	 c == '/' ||
	 c == '~' ||
	 c == '!' ||
	 c == '^' ||
	 c == '%' ||
	 c == '(' ||
	 c == ')' ||
	 c == '<' ||
	 c == '>' ||
	 c == '?' ||
	 c == '`');
}
#endif


/* Register table */
struct fstruct regtbl[] =
{
  { "D0", REG, NULL, R_D0 },
  { "D1", REG, NULL, R_D1 },
  { "A", REG, NULL, R_A },
  { "B", REG, NULL, R_B },
  { "C", REG, NULL, R_C },
  { "D", REG, NULL, R_D },
  { "R0", REG, NULL, R_R0 },
  { "R1", REG, NULL, R_R1 },
  { "R2", REG, NULL, R_R2 },
  { "R3", REG, NULL, R_R3 },
  { "R4", REG, NULL, R_R4 },
  { "PC", REG, NULL, R_PC },
  { "ST", REG, NULL, R_ST },
  { "P", REG, NULL, R_P },
  { NULL, REG, NULL, 0 } };

  
/* Test if register */
mtreg(cpp, vp)
  char **cpp;
  int *vp;
{
  char *tmp;
  SYM_NODE *regsym;
  extern CTT_ROOT *reg_table;

  if(!cpp)
    return(FALSE);

  tmp = *cpp;

  if((regsym = ctt_find(reg_table, cpp)) && (regsym->value.type == VT_OP))
    {
      if(issym(**cpp) || isexpr(**cpp))
	{
	  *cpp = tmp;
	  return(FALSE);
	}

      *vp = regsym->value.vop->auxval;
      return(TRUE);
    }

  return(FALSE);
}


/* Test if ID */
mtid(cpp)
  register char **cpp;
{
  char *tmp, *s;

  tmp = *cpp;
  
  if(!mtstr(cpp, "ID"))
    return(FALSE);

  s = byspace(*cpp);

  if(*s && (issym(**cpp) || isexpr(**cpp)))
    {
      *cpp = tmp;
      return(FALSE);
    }

  return(TRUE);
}


/* Test if HST */
mthst(cpp)
  register char **cpp;
{
  char *tmp, *s;

  tmp = *cpp;
  
  if(!mtstr(cpp, "HST"))
    return(FALSE);

  s = byspace(*cpp);

  if(*s && (issym(**cpp) || isexpr(**cpp)))
    {
      *cpp = tmp;
      return(FALSE);
    }

  return(TRUE);
}


/* True if decimal integer */
mtdec(cpp, ip)
  char **cpp;
  int *ip;
{
  if(!cpp || !*cpp || **cpp < '0' || **cpp > '9')
    return(FALSE);

  *ip = toint(evdec(cpp)).vint;
  return(TRUE);
}


/* Match for instruction field */
mtfield(cpp, vp, ftype)
  register char **cpp;
  register int *vp;
  int ftype;
{
  char *tmp;
  register char **fp;
  int fx, ix;
  static char *fields[] = {
    "P", "WP", "XS", "X", "S", "M", "B", "W", "A", NULL };
  
  if(!cpp || !*cpp || **cpp != '.')
    return(FALSE);

  tmp = *cpp;
  
  (*cpp)++;

  for(fp = fields, fx = 0; *fp && !mtstr(cpp, *fp); fp++, fx++);

  /* Any of the fixed fields or Pn? */
  if(*fp)
    {
      if(!(ftype & (FT_FIX|FT_PN)))
	return(FALSE);

      /* Is it followed by an integer? */
      if(mtdec(cpp, &ix))
	{
	  *cpp = byspace(*cpp);

	  if(fx != F_P)
	    sgnerr("Bad suffix");

	  if(!(ftype & FT_PN))
	    return(FALSE);

	  if(ix <= 0 || ix > 16)
	    return(FALSE);

	  *vp = F_PN + (ix & 077);
	  return(TRUE);
	}

      *cpp = byspace(*cpp);

      *vp = fx;
      return(TRUE);
    }
  
  /* No, check for .n */
  if(mtdec(cpp, &ix))
    {
      *cpp = byspace(*cpp);

      if(!(ftype & FT_N))
	return(FALSE);

      *vp = F_N + (ix & 077);
      return(TRUE);
    }

  *cpp = tmp;
  return(FALSE);
}


/* Decode field, signal error if incorrect.
 * If ftype is 0, make sure there is no field at all.
 */
void dcdfield(cpp, vp, ftype)
  register char **cpp;
  register int *vp;
  int ftype;
{
  extern void fielderr();

  if(mtfield(cpp, vp, ftype))
    {
      if(!ftype)
	sgnerr("Found field where none was expected");

      *cpp = byspace(*cpp);

      return;
    }

  if(ftype & FT_DEFLT)
    *vp = ftype & 07777;
  else  
    if(ftype)
      fielderr();

  *cpp = byspace(*cpp);
}

      
/* Get next character from stream */
char nextch(cpp)
  register char **cpp;
{
  if(cpp == NULL || *cpp == NULL)
    return(NULL);
  
  *cpp = byspace(*cpp);
  if(**cpp < '\040' || **cpp == ';')
    return((char)(*cpp = NULL));
  
  return(*(*cpp)++);
}


/* Satisfy next character; output error if not what expected.
 * This test is always true.
 */
mustbe(cpp,c)
  register char **cpp;
  int c;
{
  register char tmpc;
  
  if((tmpc = nextch(cpp)) != c)
    if(!tmpc)
      sgnerr("Premature end-of-line, expected `%c'", c);
    else
      sgnerr("Found `%c' when expecting `%c'", tmpc, c);
}


/* Signal bad field error */
void fielderr()
{
  sgnerr("Bad instruction field");
}


/* Write addressing syntax error message */
static stxerr()
{
  sgnerr("Bad operand syntax");
}


/* Validify character pointer */
static char *align(cp)
  register char *cp;
{
  if(!cp)
    return(NULL);

  if(!*(cp = byspace(cp)) || *cp == ';')
    return(NULL);
  
  return(cp);
}


/* Decode one argument.
 * Returns first character following argument.
 */
char *dcd1arg(cp0)
  register char *cp0;
{
  char *cp, *tmp, c;
  int val, stepbits;
  
  
  cp = cp0;
  
  /* Is it end of string? */
  if(!(c = nextch(&cp)))
    return(NULL);
  
  cp--;
  
  /* Initialize new argument */
  ap->type = 0;
  ap->pres = 0;
  ap->reg  = 0;
  ap->disp1= 0;
  
  switch(*cp)
    {
    case '@':		/* @D[01], @[AC] */
      
      cp++;
      
      /* Is it followed by D[01]? */
      if(mtreg(&cp, &val))
	{
	  ap->type  = T_IND;	/* @Dn */
	  ap->reg   = val;

	  if(val != R_D0 && val != R_D1 && val != R_A && val != R_C)
	    {
	      sgnerr("Bad indirection register");
	      ap->reg = R_D0;
	    }

	  if(val == R_A || val == R_C)
	    ap->type = T_INDAC;
	}
      else
	{
	  sgnerr("Bad register name");
	  ap->type = T_IND;
	  ap->reg = R_D0;
	}

      break;
      
    default:
      
      /* P+1? */
      if(mtstr(&cp, "P+1"))
	{
	  ap->type = T_PPLUS1;
	  break;
	}

      /* Register? */
      if(mtreg(&cp, &val))
	{
	  ap->type= T_REG;
	  ap->reg = val;

	  /* C.n? */
	  if(val == R_C && mtstr(&cp, "."))
	    {
	      val = 0;

	      if(!mtdec(&cp, &val) || val < 0 || val > 15)
		stxerr();

	      ap->disp1 = val & 0xf;
	      ap->type  = T_CN;
	    }
	  break;
	}

      /* Current unit ID? */
      if(mtid(&cp))
	{
	  ap->type = T_ID;
	  break;
	}

      /* HST? */
      if(mthst(&cp))
	{
	  ap->type = T_HST;
	  break;
	}

      /* None of the above, assume expression */
      ap->type = T_IMM;
      ap->disp1= toint(evexpr(&cp)).vint;
      break;
    }
  
  if((c = nextch(&cp)) == NULL)
    return(align(cp));
  
  return(align(--cp));
}


/* Decode general arguments
 * Number of arguments may range 1 - (MAXARG-1)
 */
dcdarg(cp0, args)
  char *cp0;
  int args;
{
  int i;
  register char *cpx;
  register struct astruct *apx;
  
  
  /* Initialize all arguments to 'A' */
  for(apx = arg; apx <= &arg[MAXARG-1]; apx++)
    {
      apx->type = T_REG;
      apx->reg  = R_A;
      apx->pres = NULL;
    }
  
  
  ap = arg;
  cpx = cp0;
  
  /* Scan the specified number of arguments */
  for(i=1; i<=args; i++)
    {
      if(!(cpx = dcd1arg(cpx)) && i < args)
	{
	  sgnerr("Further operands expected");
	  return;
	}
      else
	if(cpx != NULL && i == args)
	  {
	    sgnerr("Too many arguments");
	    return;
	  }
      ap++;
      
      if(i == args)
	return;
      
      /* Was the terminating character a comma? */
      if(*(cpx = byspace(cpx)) != ',')
	{
	  sgnerr("Found '%c' instead of comma after argument %d",
		 *cpx, i);
	  return;
	}
      
      cpx++;
      cpx = byspace(cpx);
    }
}


/* Check type */
type(ap, typebits)
  struct astruct *ap;
  int typebits;
{
  switch(typebits & ~AP_PMASK)
    {
    case AP_REG:		/* Specific register */

      return(ap->type == T_REG && ap->reg == (typebits & AP_PMASK));

    case AP_RABCD:		/* Register A,B,C, or D */

      return(ap->type == T_REG &&
	     ap->reg >= R_A && ap->reg <= R_D);

    case AP_D01:		/* Register D0 or D1 */

      return(ap->type == T_REG &&
	     (ap->reg == R_D0 || ap->reg == R_D1));

    case AP_TYPE:		/* Specific type */

      return(ap->type == (typebits & AP_PMASK));

    case AP_ID01:		/* @D0 or @D1 */

      return(ap->type == T_IND &&
	     (ap->reg == R_D0 || ap->reg == R_D1));

    case AP_RN:			/* R0-R4 */

      return(ap->type == T_REG &&
	     ap->reg >= R_R0 && ap->reg <= R_R4);

    case AP_RAC:		/* Register A or C */

      return(ap->type == T_REG &&
	     (ap->reg == R_A || ap->reg == R_C));

    default:

      fatal("Invalid argument pattern mask %d", typebits);
    }
}


/* Test types of first two arguments */
types2(tb1, tb2)
  int tb1, tb2;
{
  return(type(&arg[0], tb1) && type(&arg[1], tb2));
}


/* Test types against table */
types_table(table, nargs, nelem)
  int *table, nargs, nelem;
{
  register i;
  int ent;

  /* Loop each row */
  for(ent = 0; nelem--; ent++)
    {
      /* Loop each arg */
      for(i = 0; i < nargs; i++)
	if(!type(&arg[i], table[i]))
	  {
	    table += nargs;
	    goto iter_outer;
	  }

      return(ent);

    iter_outer:
      ;
    }

  return(-1);
}




