/* expr.c -- STAR Expression Evaluator

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, 1991 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. */


#include <stdio.h>
#include <math.h>
#include "sects.h"
#include "star.h"
#include "symbols.h"
#include "ctt.h"


/* External functions/data */
extern SYM_ROOT
  *symtbl;

extern
  mtstr(), errcnt, pass;


/* Global functions/data */
CTT_ROOT
  *reg_table,			/* Register transition tree */
  *opx_table,			/* OPX transition tree */
  *xopy_table;			/* XOPY transition tree */

struct val
  (*defrdx)();			/* Default radix parser */

struct fstruct
  *fhit;			/* Last operator called */

/* A few constants */
struct val
  val_zero,			/* INT 0 */
  val_one,			/* INT 1 */
  val_real0,			/* REAL 0 */
  val_real1,			/* REAL 1 */
  val_nullstr;			/* STRING `' */


/* Local data (externally accessible) */
char
  expr_strings[32768],		/* 32kB of expression data space */
  *expr_allp;			/* Allocation pointer */


/* Initialize constants, etc */
void expr_init()
{
  expr_allp = expr_strings;

  val_zero.type = VT_INT;
  val_zero.vint = 0;

  val_one.type  = VT_INT;
  val_one.vint  = 1;

  val_nullstr.type = VT_STR;
  val_nullstr.vstr = "";

  val_real0.type = val_real1.type = VT_REAL;
  val_real0.vdouble = 0.0;
  val_real1.vdouble = 1.0;
}


/* Wrap integer into value */
#ifndef __GNUC__
struct val intval(i)
  INT i;
{
  struct val tmp;

  tmp.type = VT_INT;
  tmp.vint = i;

  return(tmp);
}
#endif


/* True if data is in expression space */
isexpr_data(p)
  char *p;
{
  return(p >= expr_strings &&
	 p <= expr_strings + sizeof expr_strings);
}


/* Allocate data in expression space */
char *expr_alloc(nbytes)
  int nbytes;
{
  register char *tmp = expr_allp;

  if(nbytes & 15)
    nbytes = (nbytes & ~15) + 16;

  if((expr_allp += nbytes) >= expr_strings + sizeof expr_strings)
    fatal("Can't allocate %d bytes: expression data space exhausted",
	  nbytes);

  return(tmp);
}


/* Duplicate string in space reclaimable by expr_gc  */
char *expr_strdup(str)
  char *str;
{
  register char *tmp;
  int n = strlen(str)+1;

  tmp = expr_alloc(n);

  bcopy(str, tmp, n+1);
  return(tmp);
}


/* Duplicate macro in space reclaimable by expr_gc */
struct macro *expr_dupmacro(mp)
  struct macro *mp;
{
  int msize = sizeof *mp + (mp->nlines - 1) * sizeof(char *);
  char **l;
  struct marg *ma;
  struct macro *m;

  /* Start out with current block data */
  m = (struct macro *) expr_alloc(msize);
  bcopy(mp, m, msize);

  /* Duplicate lines */
  for(l = m->lines; l < m->lines + m->nlines; l++)
    *l = expr_strdup(*l);

  /* Duplicate argument default values, if any*/
  for(ma = m->args; ma < m->args + m->nargs; ma++)
    ma->def  = (ma->def ? expr_strdup(ma->def) : NULL);

  return(m);
}


/* Duplicate macro */
struct macro *dupmacro(mp)
  struct macro *mp;
{
  int msize = sizeof *mp + (mp->nlines - 1) * sizeof(char *);
  char **l;
  struct marg *ma;
  struct macro *m;
  extern char *malloc(), *strdup();

  /* Start out with current block data */
  if(!(m = (struct macro *) malloc(msize)))
    fatal("Can't allocate %d bytes for macro definition", msize);
      
  bcopy(mp, m, msize);

  /* Duplicate lines */
  for(l = m->lines; l < m->lines + m->nlines; l++)
    *l = strdup(*l);

  /* Duplicate argument defaults */
  for(ma = m->args; ma < m->args + m->nargs; ma++)
    {
      ma->def  = (ma->def ? strdup(ma->def) : NULL);
    }

  return(m);
}


/* Reclaim expr alloc space */
void expr_gc()
{
  expr_allp = expr_strings;
}


/* Make recoverable copy of value */
struct val localize(v)
  struct val v;
{
  switch(v.type)
    {
    case VT_INT:
    case VT_REAL: return(v);
    case VT_STR:

      v.vstr = expr_strdup(v.vstr);
      return(v);

    case VT_MAC:

      v.vmacro = expr_dupmacro(v.vmacro);

      return(v);

    case VT_SECT:

    default:

      return(v);
    }
}


/* Duplicate string */
char *strdup(s)
  char *s;
{
  register char *tmp;
  extern char *malloc();

  if(!(tmp = malloc(strlen(s)+1)))
    fatal("Can't allocate %d characters for string data", strlen(s)+1);

  strcpy(tmp, s);
  return(tmp);
}


/* Make value independent of expr recoverable areas */
struct val solidify(v)
  struct val v;
{
  switch(v.type)
    {
    case VT_INT:
    case VT_REAL: return(v);
    case VT_STR:

      v.vstr = strdup(v.vstr);
      return(v);

    case VT_MAC:

      v.vmacro = dupmacro(v.vmacro);
      return(v);

    case VT_SECT:
    default:
      return(v);
    }
}


/* Convert to string, output is localized */
struct val tostr(v)
  struct val v;
{
  char bf[64];

  switch(v.type)
    {
    case VT_STR: return(v);
    case VT_REAL:

#ifdef __GNUC__
      sprintf(bf, "%lg", (double) v.vdouble);
#else
      sprintf(bf, "%g", (double) v.vdouble);
#endif
      v.type = VT_STR;
      v.vstr = expr_strdup(bf);
      return(v);

    case VT_INT:

      sprintf(bf, "%ld", (INT) v.vint);
      v.type = VT_STR;
      v.vstr = expr_strdup(bf);
      return(v);

    case VT_SECT:
    case VT_MAC:
    default:
      v.type = VT_STR;
      v.vstr = expr_strdup("");
      return(v);
    }
}


/* Wrap double as real */
#ifndef __GNUC__
struct val realval(d)
  REAL d;
{
  struct val dval;

  dval.type = VT_REAL;
  dval.vdouble = d;
  return(dval);
}
#endif


/* Convert to real */
struct val toreal(v)
  struct val v;
{
  switch(v.type)
    {
    case VT_REAL: return(v);
    case VT_INT:

      v.type = VT_REAL;
      v.vdouble = (REAL) v.vint;
      return(v);

    case VT_STR:

      return(toreal(toint(v)));

    case VT_SECT:

      return(toreal(intval(v.vsect->sc_reloc)));

    case VT_MAC:
    default:

      return(val_real0);
    }
}


/* Convert to integer */
struct val toint(v)
  struct val v;
{
  register unsigned char *cp;
  register l;
  register INT acc;

  switch(v.type)
    {
    case VT_REAL:

      v.type = VT_INT;
      v.vint = (INT) v.vdouble;

      /* Drop */

    case VT_INT: 

      return(v);

    case VT_STR:

      v.type = VT_INT;

      if((l = strlen(v.vstr)) > sizeof v.vint)
	l = sizeof v.vint;

      for(acc = 0, cp = (unsigned char *) v.vstr+l;
	  cp > (unsigned char *) v.vstr; acc += *--cp)
	acc <<= 8;
      
      v.vint = acc;
      return(v);

    case VT_SECT:

      return(intval(v.vsect->sc_reloc));

    default:
      return(val_zero);
    }
}


/* Test if end of expression
 * Arguments are terminated if end of string, ';', ',' or '>'
 * Spaces are bypassed.
 */
#ifndef eoe
static eoe(cp)
  char *cp;
{
  /* Does it match the termination condition? */
  return(!cp ||
	 *cp < '\040' || *cp == ';' || *cp == '(' || *cp == ',' ||
	 *cp == ':' || *cp == '[' || *cp == ')' || *cp == ']');
}
#endif


/* Initialize parse transition tables */
void init_op_tables()
{
  struct fstruct *opp;
  SYM_NODE *opsym;
  int isopx;
  char opname[64];
  struct val opval;
  extern struct fstruct
    funtbl[], regtbl[];


  /* Initialize tables */
  opx_table = ctt_new();
  xopy_table = ctt_new();
  reg_table = ctt_new();

  /* Loop tables and add */
  for(opp = funtbl; opp->name; opp++)
    {
      isopx = opp->type & OPX;

      sprintf(opname, "%c%s", (isopx ? ';' : ','), opp->name);

      opval.type = VT_OP;
      opval.vop = opp;

      opsym = sm_enter_sym(symtbl, opname, opval, F_HID);
      ctt_add((isopx ? opx_table : xopy_table), opp->name, opsym);
    }

  for(opp = regtbl; opp->name; opp++)
    {
      sprintf(opname, ":%s", opp->name);

      opval.type = VT_OP;
      opval.vop = opp;

      opsym = sm_enter_sym(symtbl, opname, opval, F_HID);
      ctt_add(reg_table, opp->name, opsym);
    }
}  


/* Match OPX op */
mtopx(cpp, funp)
  char **cpp;
  struct fstruct **funp;
{
  SYM_NODE *opsym;

  if(!cpp)
    return(FALSE);

#ifdef NEVER
  printf("mtopx(`%s'): called\n", *cpp); /* DEBUG */
#endif

  if((opsym = ctt_find(opx_table, cpp)) && (opsym->value.type == VT_OP))
    {
#ifdef NEVER
      printf("mtopx: OPX match, rest=`%s'\n", *cpp); /* DEBUG */
#endif

      fhit = *funp = opsym->value.vop;
      return(TRUE);
    }

#ifdef NEVER
  printf("mtopx: no match, rest=`%s'\n", *cpp);	/* DEBUG */
#endif

  return(FALSE);
}


/* Match XOPY op */
mtxopy(cpp, funp)
  char **cpp;
  struct fstruct **funp;
{
  SYM_NODE *opsym;

  if((opsym = ctt_find(xopy_table, cpp)) && (opsym->value.type == VT_OP))
    {
      fhit = *funp = opsym->value.vop;
      return(TRUE);
    }
      
  return(FALSE);
}	



/* Evaluate a simple term.
 * Parse symbol names and default radix numbers.
 */
evsimple(cpp, lp)
  char **cpp;
  struct val *lp;
{
  struct sstruct *symp;
  char csav, *sp;
  
  sp = *cpp;
  
  /* Parse numeric if numerical
   * Must be called before 'issym' because
   * '0'-'9' are valid symbol characters.
   */
  if(**cpp >= '0' && **cpp <= '9')
    {
      /* Call default radix parser */
      *lp = (*defrdx)(cpp);
      return(TRUE);
    }
  
  /* Is it a symol? */
  if(!issym(*sp))
    {
      *lp = val_zero;
      return(FALSE);
    }
  
  /* Assume symbol. Set sp to point at first invalid character */
  for(sp = *cpp; issym(*sp); sp++);
  
  /* Save old character and zero terminate */
  csav = *sp;
  *sp  = '\0';

  if(!(symp = sm_find_sym(symtbl, *cpp)) || (symp->flags & F_UDF))
    {
      *lp = localize(val_nullstr);
      sgnerr("Undefined symbol `%s'", *cpp);
    }
  else
    {
      *lp  = localize(symp->value);
      symp->flags |= F_REF;
    }

  /* Skip past symbol and return copy of its value */
  *cpp = sp;
  *sp  = csav;
  
  *cpp = byspace(*cpp);

  return(TRUE);
}


/* Evaluate term */
static struct val evterm(cpp)
  char **cpp;
{
  struct fstruct *funp;
  struct val tmpacc;
  
  *cpp = byspace(*cpp);

  /* Look if we have an OPX op */
  if(mtopx(cpp, &funp))

    /* Fast evaluation? */
    if(funp->type & F_FAST)

      /* Yes */
      return((*funp->feval)(cpp));
    else
      /* No, recurse until there is no more left */
      return((*funp->feval)(cpp, evterm(cpp)));
  
  /* No, go for simple term */
  if(!evsimple(cpp, &tmpacc))
    sgnerr("Bad expression syntax");
  
  /* Return value of term */
  return(tmpacc);
}


/* Evaluate an 'op term' pair */
static int evalop(cpp, accp)
  char **cpp;
  struct val *accp;
{
  struct fstruct *funp;
  struct val termacc;
  
  /* Make sure we have an XOPY op */
  if(!mtxopy(cpp, &funp))
    {
      sgnerr("Bad expression, operator expected");
      return(FALSE);
    }
  
  /* Now evaluate term */
  termacc = evterm(cpp);
  
  /* Evaluate XOPY and update accumulator */
  *accp = (*funp->feval)(cpp, *accp, termacc);
  return(TRUE);
}


/* Evaluate an expression */
struct val evexpr(cpp)
  char **cpp;
{
  struct val acc;
  
  
  /* Evaluate term */
  acc = evterm(cpp);
  
  /* Test if end of expression */
  if(eoe(*cpp))
    return(acc);
  
  /* The following is: op term op term ... op term */
  for(;;)
    {
      *cpp = byspace(*cpp);
      
      /* End of expression? */
      if(eoe(*cpp))
	return(acc);
      
      /* No - XOPY TERM follows */
      if(!evalop(cpp, &acc))
	return(acc);
    }
}
