/* $VER: pasm eval.c V0.7 (01.01.98)
 *
 * This file is part of pasm, a portable PowerPC assembler.
 * Copyright (c) 1997-98  Frank Wille
 *
 * pasm is freeware and part of the portable and retargetable ANSI C
 * compiler vbcc, copyright (c) 1995-98 by Volker Barthelmann.
 * pasm may be freely redistributed as long as no modifications are
 * made and nothing is charged for it. Non-commercial usage is allowed
 * without any restrictions.
 * EVERY PRODUCT OR PROGRAM DERIVED DIRECTLY FROM MY SOURCE MAY NOT BE
 * SOLD COMMERCIALLY WITHOUT PERMISSION FROM THE AUTHOR.
 *
 *
 * v0.7 (01.01.98) phx
 *      @ha, @h, @l are only allowed at the end of an operand, according
 *      to GNU-as notation. '@' is only allowed as the first character in
 *      a symbol (for @function and @object, for example).
 *      Don't waste memory in makereloc() and makexref() by adding new
 *      nodes to the current section if another pass is required
 *      anyway.
 * v0.5 (11.10.97) phx
 *      getarg() works with \' and \" now.
 * v0.4 (05.07.97) phx
 *      R_PPC_TOC16 and R_PPC_REL14 support in makereloc() and makexref().
 *      "(<term>)@l/h/ha" was not recognized.
 *      A comment introducer '#' was erroneously treated as a macro
 *      parameter in read_macro_params().
 *      Undefined symbols are only automatically declared as externally
 *      defined, if the -x option was given. Otherwise display an
 *      error message.
 *      Implementing backwards-evaluation of an expression in v0.3 was not
 *      very smart (e.g. 4-1+2 => 1(!)) - changed to forward again... ;)
 * v0.3 (20.04.97) phx
 *      Bug in eval_expression() fixed, which sometimes caused the loss
 *      of the last argument of an expression.
 * v0.2 (25.03.97) phx
 *      Writes ELF object for 32-bit PowerPC big-endian. Either absolute
 *      or ELF output format may be selected. ELF is default for all
 *      currently supported platforms. PPCasm supports nine different
 *      relocation types (there are much more...).
 *      Compiles and works also under NetBSD/amiga (68k).
 *      Changed function declaration to 'new style' in all sources
 *      (to avoid problems with '...' for example).
 *      makexref() and makereloc() used wrong offset.
 * v0.1 (11.03.97) phx
 *      First test version with all PowerPC instructions and most
 *      important directives. Only raw, absolute output.
 * v0.0 (21.02.97) phx
 *      File created.
 */


#define EVAL_C
#include "ppcasm.h"


char *getsymbol(struct GlobalVars *,char *);
char *getarg(struct GlobalVars *,char *);
char *skipspaces(char *);
char *remquotes(char *);
void checkEOL(char *);
char *skipexpression(struct GlobalVars *,char *);
void read_macro_params(struct GlobalVars *,struct ParsedLine *,
                       struct MacroParams *,char *);
char *getexp(struct GlobalVars *,char *,uint32 *,uint8);
uint32 makereloc(struct GlobalVars *,struct Expression *);
uint32 makexref(struct GlobalVars *,struct Expression *,uint8);
char *getintexp(struct GlobalVars *,char *,uint32 *);
char *eval_expression(struct GlobalVars *,struct Expression *,char *);

static uint32 hihalf(struct GlobalVars *,uint32);
static uint32 lohalf(struct GlobalVars *,uint32);
static uint32 read_hex(char *);
static uint32 read_dec(char *);
static uint32 read_oct(char *);
static uint32 read_bin(char *);
static uint32 read_str(char *);



/* table of valid symbol characters, 
   0=invalid, 1=valid in whole symbol, 2=valid, but not as first char */
static uint8 valid_symchars[256] = {
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,  /* $ . */
  2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,1,  /* 0-9 ? */
  0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  /* A-O */
  1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,  /* P-Z _ */
  0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  /* a-o */
  1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,  /* p-z */
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};

/* table of valid argument characters, 
   0=invalid, 1=valid, 2=valid, but indicates string */
static uint8 valid_argchars[256] = {
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,2,0,1,0,0,2,0,0,0,0,0,0,1,0,  /* " $ ' . */
  1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,  /* 0-9 ? */
  0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  /* A-O */
  1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,  /* P-Z _ */
  0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  /* a-o */
  1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,  /* p-z */
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};

/* valid operators */
#define NUMOPS 10
static char valid_operators[NUMOPS] = {
  '+','-','*','/','%','<','>','&','|','^'
};
#define MAXOPPRI 5
static uint8 op_priority[NUMOPS] = {
  4,4,5,5,5,3,3,2,0,1
};


char *getsymbol(struct GlobalVars *gv,char *s)
/* read a symbol string into gv->strbuf */
{
  uint8 *vsc = valid_symchars;
  char *b = gv->strbuf;
  int bsize = STRBUFSIZE-2;

  if (*s == '@') {  /* '@' is allowed as first character */
    *b++ = *s++;
    bsize--;
  }
  if (vsc[(unsigned char)*s] == 1) {  /* first character valid? */
    *b++ = *s++;
    while (vsc[(unsigned char)*s] && bsize--)
      *b++ = *s++;
  }
  *b = 0;
  return (s);
}


char *getarg(struct GlobalVars *gv,char *s)
/* read next argument into gv->strbuf */
{
  uint8 v,*vac = valid_argchars;
  char c,*b = gv->strbuf;
  int bsize = STRBUFSIZE-1;

  if (*s == '@') {  /* '@' is allowed as first character */
    *b++ = *s++;
    bsize--;
  }
  while ((v = vac[(unsigned char)*s]) && bsize--) {
    *b++ = *s++;
    if (v==2) {  /* string? */
      c = *(s-1);
      for (;;) {
        if (bsize--) {
          if (!(*b++ = *s))  /* string can only be terminated by EOL */
            return (s);
        }
        else
          return (s);
        if (*s++ == c) {
          if (*s==c || *(s-2)=='\\') {
            if (bsize--)  /* "", '' \" or \' don't terminate the string */
              *b++ = *s++;
             else
              return (s);
          }
          else
            break;
        }
      }
    }
  }
  *b = 0;
  return (s);
}


char *skipspaces(char *s)
/* advance string pointer to the first character, which is no white space */
{
  while (*s==' ' || *s=='\t')
    ++s;
  return (s);
}


char *remquotes(char *s)
/* remove " or ', if present and return new strbuf-pointer */
{
  int len;
  char c = *s;

  if (c=='\"' || c=='\'')  /* string is in quotes */
    if (len = strlen(++s) - 1)
      if (s[len] == c)
        s[len] = 0;
  return (s);
}


void checkEOL(char *s)
/* check for illegal extra characters on line */
{
  s = skipspaces(s);
  if (*s && *s!='#')  /* only comment is allowed as an extra character */
    error(18);  /* extra characters on line */
}


char *skipexpression(struct GlobalVars *gv,char *s)
{
  char c;

  do {
    s = getarg(gv,s);
    s = skipspaces(s);
    c = *s++;
  }
  while (c!=0 && c!='#' && c!=',');
  return (--s);
}


void read_macro_params(struct GlobalVars *gv,struct ParsedLine *pl,
                       struct MacroParams *mp,char *op)
/* read parameters and store pointers to their first character */
{
  char c;

  mp->param[0] = mp->param0;
  if (pl->branch_hint)
    mp->param0[0] = pl->branch_hint>0 ? '+' : '-';
  for (;;) {
    op = skipspaces(op);
    c = *op;
    if (c!=0 && c!='#') {  /* another parameter? */
      if (mp->narg < (MAX_MACPARAMS-1)) {
        mp->param[++mp->narg] = op;
        op = skipexpression(gv,op);
        c = *op;
        if (c==0 || c=='#') {
          *op = 0;  /* end of line / start of comment */
          return;
        }
        *op++ = 0;  /* next parameter */
      }
      else {
        error(11);  /* too many macro parameters */
        return;
      } 
    }
    else
      return;
  }
}


char *getexp(struct GlobalVars *gv,char *s,uint32 *val,uint8 size)
/* evaluate expression, create relocations or x-references, if required */
{
  struct Expression exp;

  s = eval_expression(gv,&exp,s);
  if (exp.type != SYM_UNDEF) {
    if (exp.reloctype==R_PPC_ADDR32 && size==2)
      exp.reloctype = R_PPC_ADDR16;
    switch (exp.type) {
      case SYM_RELOC:
        *val = makereloc(gv,&exp);  /* make relocation entry */
        break;
      case SYM_EXTERN:  /* reference to external symbol */
        *val = makexref(gv,&exp,size);
        break;
      default:  /* SYM_ABS */
        *val = exp.value;
        break;
    }
  }
  else
    error(19);  /* undefined symbol */
  return (s);
}


static uint32 hihalf(struct GlobalVars *gv,uint32 x)
{
  if (gv->signedexp)
    return ((int32)x/0x10000);
  else
    return (x>>16);
}


static uint32 lohalf(struct GlobalVars *gv,uint32 x)
{
  if (gv->signedexp)
    return ((int16)x);
  else
    return (x&0xffff);
}


uint32 makereloc(struct GlobalVars *gv,struct Expression *exp)
/* make new relocation entry for current section */
{
  struct Reloc dummy,*reloc;

  if (gv->anotherpass) {
    /* don't waste memory, if another pass is required anyway */
    reloc = &dummy;
  }
  else {
    reloc = alloc(sizeof(struct Reloc));
    addtail(&gv->csect->reloclist,&reloc->n);
  }
  reloc->relocsect = exp->symbol->relsect;
  reloc->offset = gv->csect->pc;
  reloc->type = exp->reloctype;
  reloc->addend = exp->value;
  switch (exp->reloctype) {
    case R_PPC_ADDR16_HA:
    case R_PPC_ADDR16_HI:
      reloc->offset += 2;
      return (hihalf(gv,exp->value));
    case R_PPC_ADDR16_LO:
      reloc->offset += 2;
      return (lohalf(gv,exp->value));
    case R_PPC_TOC16:
      reloc->offset += 2;
      return (exp->value);
    case R_PPC_REL14:
    case R_PPC_REL14_BRTAKEN:
    case R_PPC_REL14_BRNTAKEN:
      if (gv->output == OFMT_EHF) {
        reloc->offset += 2;
        return (exp->value + 2);
      }
    default:
      return (exp->value);
  }
}


uint32 makexref(struct GlobalVars *gv,struct Expression *exp,uint8 size)
{
  struct XReference dummy,*xref;

  if (gv->anotherpass) {
    /* don't waste memory, if another pass is required anyway */
    xref = &dummy;
  }
  else {
    xref = alloc(sizeof(struct XReference));
    addtail(&gv->csect->xreflist,&xref->n);
  }
  xref->xsymbol = exp->symbol;
  xref->offset = gv->csect->pc;
  xref->addend = exp->value;
  xref->type = exp->reloctype;
  xref->size = size;
  switch (exp->reloctype) {
    case R_PPC_ADDR16_HA:
    case R_PPC_ADDR16_HI:
      xref->offset += 2;
      return (hihalf(gv,exp->value));
    case R_PPC_ADDR16_LO:
      xref->offset += 2;
      return (lohalf(gv,exp->value));
    case R_PPC_TOC16:
      xref->offset += 2;
      return (exp->value);
    case R_PPC_REL14:
    case R_PPC_REL14_BRTAKEN:
    case R_PPC_REL14_BRNTAKEN:
      if (gv->output == OFMT_EHF) {
        xref->offset += 2;
        return (exp->value + 2);
      }
    default:
      return (exp->value);
  }
}


char *getintexp(struct GlobalVars *gv,char *s,uint32 *val)
/* evaluate expression and check if absolute integer */
{
  struct Expression exp;

  s = eval_expression(gv,&exp,s);
  if (exp.type != SYM_UNDEF) {
    if (exp.type == SYM_ABS)
      *val = exp.value;
    else
      error(24);  /* constant integer expression required */
  }
  else
    error(19);  /* undefined symbol */
  return (s);
}


char *eval_expression(struct GlobalVars *gv,struct Expression *exp_result,
                      char *s)
/* evaluate an integer expression and determine its type */
{
  struct Expression exp[EXPSTACKSIZE];
  uint8 ops[EXPSTACKSIZE-1],op,ta,tb;
  int i,j,k,exp_args=0;
  uint8 unary;  /* unary operator, 0=none, 1=plus, 2=minus, 3=not */
  char c;
  char *arg=gv->strbuf;
  struct Symbol *sym;

  exp_result->value = 0;
  exp_result->type = SYM_UNDEF;

  /* build expression stack */
  for (;;) {
    unary = 0;
    exp[exp_args].reloctype = R_PPC_ADDR32;

    for (;;) {
      s = skipspaces(s);
      s = getarg(gv,s);  /* try to get next argument */
      if (c = *arg)
        break;  /* got it */

      if ((c=*s++) == '(') {  /* beginning of a term? */
        s = eval_expression(gv,&exp[exp_args],s);
        if (*s++ != ')') {
          error(21);  /* missing closing parenthesis */
          s--;
        }
        *arg = 0;  /* expression in term is already evaluated */
        break;
      }

      if (c==0 || c=='#' || c==')') {
        error(17);  /* missing argument */
        s--;
        c = 0;
        break;
      }

      if (unary)
        error(20);  /* double unary operator */
      else
        switch (c) {
          case '+':
            unary = 1;
            break;
          case '-':
            unary = 2;
            break;
          case '~':
            unary = 3;
            break;
          default:
            error(13);  /* syntax error */
            break;
        }
    }

    if (!c)
      break;  /* evaluate */

    if (c = *arg) {

      if (c>='0' && c<='9') {  /* numeric constant */
        if (c=='0')
          switch (arg[1]) {
            case 'x':
              exp[exp_args].value = read_hex(&arg[2]);
              break;
            case 'b':
              exp[exp_args].value = read_bin(&arg[2]);
              break;
            case 0:
              exp[exp_args].value = 0;
              break;
            default:
              exp[exp_args].value = read_oct(&arg[1]);
              break;
          }
        else
          exp[exp_args].value = read_dec(arg);
        exp[exp_args].type = SYM_ABS;
      }

      else if (c=='\"' || c=='\'') {  /* string constant */
        exp[exp_args].value = read_str(arg);
        exp[exp_args].type = SYM_ABS;
      }

      else {  /* get symbol value */
        if (sym = search_symbol(gv,arg)) {
          exp[exp_args].value = sym->value;
          exp[exp_args].type = sym->type;
          exp[exp_args].symbol = sym;
        }
        else {
          if (gv->pass && gv->autoextern) {  /* pass 2? auto. decl. extern */
            sym = add_symbol(gv,arg,SYM_EXTERN,0);
            exp[exp_args].value = sym->value;
            exp[exp_args].type = sym->type;
            exp[exp_args].symbol = sym;
          }
          else {
            exp[exp_args].value = 0;
            exp[exp_args].type = SYM_UNDEF;
          }
        }
      }
    }

    if (unary) {
      if (exp[exp_args].type >= SYM_RELOC)
        error(22);  /* illegal operation for a relocatable expression */
      else
        switch (unary) {
          case 2:  /* negate */
            exp[exp_args].value = -exp[exp_args].value;
            break;
          case 3:  /* not */
            exp[exp_args].value = ~exp[exp_args].value;
            break;
        }
    }
    if (exp[exp_args].type == SYM_ABS)
      exp[exp_args].reloctype = R_NONE;

    /* check for operator */
    s = skipspaces(s);
    c = *s;
    i = 0;
    while (i<NUMOPS) {
      if (c == valid_operators[i])
        break;
      i++;
    }
    if (i==NUMOPS)
      break;  /* end of expression - evaluate */

    ops[exp_args++] = i;
    if (*(++s) == c)
      if (i==5 || i==6)  /* <<, >> */
        ++s;
  }

  /* evaluate expression stack */
  for (i=MAXOPPRI; i>=0; i--) {
    for (j=0; j<exp_args; ) {
      op = ops[j];
      if (op_priority[op] == i) {
        if (!(ta = exp[j].type) || !(tb = exp[j+1].type)) {
          exp[j].value = 0;
          exp[j].type = SYM_UNDEF;
          exp[j].reloctype = R_PPC_ADDR32;
        }
        else switch(op) {

          case 0:  /* + */
            if (ta != tb) {
              if (ta == SYM_ABS) {
                exp[j].type = tb;  /* reloc + abs = reloc */
                exp[j].reloctype = exp[j+1].reloctype;
                exp[j].symbol = exp[j+1].symbol;
              }
              else if (tb != SYM_ABS) {
                error(22);  /* illegal reloc operation */
                break;
              }          
            }
            else if (ta >= SYM_RELOC) {
              error(22);
              break;
            }
            exp[j].value += exp[j+1].value;
            break;

          case 1:  /* - */
            if (ta != tb) {  /* reloc - abs = reloc */
              if (tb != SYM_ABS) {
                error(22);
                break;
              }
            }
            else {  /* reloc - reloc = abs  |  abs - abs = abs */
              if (ta != SYM_EXTERN) {  /* extern - extern  not supported */
                exp[j].type = SYM_ABS;
                if (ta == SYM_RELOC)
                  if (exp[j].symbol->relsect != exp[j+1].symbol->relsect)
                    error(23);  /* symbols reside in different sections */
              }
              else {
                error(22);
                break;
              }
            }
            exp[j].value -= exp[j+1].value;
            break;

          case 2:  /* * */
            if (ta == tb == SYM_ABS)
              exp[j].value *= exp[j+1].value;
            else
              error(22);
            break;

          case 3:  /* / */
            if (ta == tb == SYM_ABS)
              exp[j].value /= exp[j+1].value;
            else
              error(22);
            break;

          case 4:  /* % */
            if (ta == tb == SYM_ABS)
              exp[j].value %= exp[j+1].value;
            else
              error(22);
            break;

          case 5:  /* << */
            if (ta == tb == SYM_ABS)
              exp[j].value <<= exp[j+1].value;
            else
              error(22);
            break;

          case 6:  /* << */
            if (ta == tb == SYM_ABS)
              exp[j].value >>= exp[j+1].value;
            else
              error(22);
            break;

          case 7:  /* & */
            if (ta == tb == SYM_ABS)
              exp[j].value &= exp[j+1].value;
            else
              error(22);
            break;

          case 8:  /* | */
            if (ta == tb == SYM_ABS)
              exp[j].value |= exp[j+1].value;
            else
              error(22);
            break;

          case 9:  /* ^ */
            if (ta == tb == SYM_ABS)
              exp[j].value ^= exp[j+1].value;
            else
              error(22);
            break;
        }

        exp_args--;
        for (k=j; k<exp_args; k++) {
          memcpy(&exp[k+1],&exp[k+2],sizeof(struct Expression));
          ops[k] = ops[k+1];
        }
      }
      else
        j++;
    }
    if (exp_args==0)
      break;
  }

  memcpy(exp_result,&exp[0],sizeof(struct Expression));
  if (*s == '@') {  /* check for @ha, @h or @l */
    s++;
    switch (tolower((unsigned char)*s++)) {
      case 'l':  /* symbol@l : lower half-word */
        if (exp_result->type == SYM_ABS) {
          exp_result->reloctype = R_NONE;
          exp_result->value = lohalf(gv,exp_result->value);
        }
        else
          exp_result->reloctype = R_PPC_ADDR16_LO;
        break;
      case 'h':
        if (tolower((unsigned char)*s) == 'a') {
          s++;  /* symbol@ha : higher (addi) */
          if (exp_result->type == SYM_ABS) {
            exp_result->reloctype = R_NONE;
            if (exp_result->value & 0x8000)
              exp_result->value = hihalf(gv,exp_result->value + 0x10000);
            else
              exp_result->value = hihalf(gv,exp_result->value);
          }
          else
            exp_result->reloctype = R_PPC_ADDR16_HA;
        }
        else {  /* symbol@h : higher half-word */
          if (exp_result->type == SYM_ABS) {
            exp_result->reloctype = R_NONE;
            exp_result->value = hihalf(gv,exp_result->value);
          }
          else
            exp_result->reloctype = R_PPC_ADDR16_HI;
        }
        break;
      default:
        s -= 2;
        break;
    }
  }

  return (s);
}


static uint32 read_hex(char *s)
{
  uint32 x=0,y;

  while (y = (unsigned char)*s++) {
    if (y > '9')
      y = (y & 0x5f) - 7;
    x <<= 4;
    x += y - '0';
  }
  return (x);
}


static uint32 read_dec(char *s)
{
  uint32 x=0,y;

  while (y = (unsigned char)*s++) {
    x *= 10;
    x += y - '0';
  }
  return (x);
}


static uint32 read_oct(char *s)
{
  uint32 x=0,y;

  while (y = (unsigned char)*s++) {
    x <<= 3;
    x += y - '0';
  }
  return (x);
}


static uint32 read_bin(char *s)
{
  uint32 x=0,y;

  while (y = (unsigned char)*s++) {
    x <<= 1;
    x += y & 1;
  }
  return (x);
}


static uint32 read_str(char *s)
{
  uint32 x=0;
  char c,sc=*s++;

  while (c = *s++) {
    if (c==sc) {
      if (*s==sc)   /* "" is converted into " */
        s++;
      else
        break;
    }
    else if (c=='\\') {
      if (!(c = escchar(*s++)))
        break;
    }
    x <<= 8;
    x += (unsigned char)c;
  }
  return (x);
}
