/* evaluates variables
   Copyright (C) 2002, 2003, 2004, 2005
   Craig Franklin

This file is part of gputils.

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

gputils 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 gputils; see the file COPYING.  If not, write to
the Free Software Foundation, 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.  */

#include "stdhdr.h"

#include "libgputils.h"
#include "gpasm.h"
#include "evaluate.h"
#include "directive.h"
#include "gperror.h"
#include "parse.h"
#include "coff.h"

extern int _16bit_core;

int enforce_arity(int arity, int must_be)
{
  if (arity == must_be)
    return 1;
  else {
    if (arity < must_be) {
      gperror(GPE_MISSING_ARGU, NULL);
    } else {
      gperror(GPE_TOO_MANY_ARGU, NULL);
    }
    return 0;
  }
}

int enforce_simple(struct pnode *p)
{
  if (p->tag == symbol) {
    return 1;
  } else {
    gperror(GPE_ILLEGAL_ARGU, NULL);
    return 0;
  }
}

int list_length(struct pnode *L)
{
  if (L == NULL) {
    return 0;
  } else {
    return 1 + list_length(TAIL(L));
  }
}

int can_evaluate_concatenation(struct pnode *p)
{
  char buf[BUFSIZ];

  switch (p->tag) {
  case constant:
    return 1;
  case offset:
    return can_evaluate_concatenation(p->value.offset);
  case symbol:
    return 1;
  case unop:
    return can_evaluate_concatenation(p->value.unop.p0);
  case binop:
    return can_evaluate_concatenation(p->value.binop.p0) &&
           can_evaluate_concatenation(p->value.binop.p1);
  case string:
    snprintf(buf, sizeof(buf), "Illegal argument (%s).", p->value.string);
    gperror(GPE_ILLEGAL_ARGU, buf);
    return 0;
  default:
    assert(0);
  }

  return 0;
}

char *evaluate_concatenation(struct pnode *p)
{
  switch (p->tag) {
  case symbol:
    return p->value.symbol;
  case binop:
    assert(p->value.binop.op == CONCAT);
    {
      char *s[2], *new;
      size_t sizeof_new;

      s[0] = evaluate_concatenation(p->value.binop.p0);
      s[1] = evaluate_concatenation(p->value.binop.p1);
      sizeof_new =strlen(s[0]) + 1 + strlen(s[1]) + 1;
      new = malloc(sizeof_new);
      if (new) {
        strncpy(new, s[0], sizeof_new);
        strncat(new, s[1], sizeof_new);
      }
      return new;
    }
  case unop:
    assert(p->value.unop.op == VAR);
    {
      char buf[80];
      snprintf(buf, sizeof(buf), "%d", maybe_evaluate(p->value.unop.p0));
      return (strdup(buf));
    }
  default:
    assert(0);
  }

  return NULL;
}

/* Attempt to evaluate concatenation 'p'.  Return its value if
 * successful, otherwise generate an error message and return NULL.  */

char *maybe_evaluate_concat(struct pnode *p)
{
  char *r = NULL;

  if (((p->tag == unop) && (p->value.unop.op != VAR)) ||
      ((p->tag == binop) && (p->value.binop.op != CONCAT))) {
    gperror(GPE_ILLEGAL_ARGU, NULL);
  } else if (p && can_evaluate_concatenation(p)) {
    r = evaluate_concatenation(p);
  }

  return r;
}

int can_evaluate(struct pnode *p)
{
  char buf[BUFSIZ];

  if ((p->tag == binop) && (p->value.binop.op == CONCAT)) {
    return can_evaluate_concatenation(p);
  }
  switch (p->tag) {
  case constant:
    return 1;
  case offset:
    if (state.extended_pic16e == false) {
      gperror(GPE_BADCHAR, "Illegal character ([)");
    }
    return can_evaluate(p->value.offset);
  case symbol:
    {
      struct symbol *s;

      /* '$' means current org, which we can always evaluate */
      if (strcmp(p->value.symbol, "$") == 0) {
	return 1;
      } else {
        struct variable *var = NULL;

	/* Otherwise look it up */
	s = get_symbol(state.stTop, p->value.symbol);

        if (s == NULL) {
          snprintf(buf,
                   sizeof(buf),
                   "Symbol not previously defined (%s).",
                   p->value.symbol);
          gperror(GPE_NOSYM, buf);    
	} else {
          var = get_symbol_annotation(s);

          if (var == NULL) {
            snprintf(buf,
                     sizeof(buf),
                     "Symbol not assigned a value (%s).",
                     p->value.symbol);
            gpwarning(GPW_UNKNOWN, buf);    
          }
        }  
 
	return ((s != NULL) && (var != NULL));
      }
    }
  case unop:
    return can_evaluate(p->value.unop.p0);
  case binop:
    return can_evaluate(p->value.binop.p0) && can_evaluate(p->value.binop.p1);
  case string:
    snprintf(buf, sizeof(buf), "Illegal argument (%s).", p->value.string);
    gperror(GPE_ILLEGAL_ARGU, buf);
    return 0;
  default:
    assert(0);
  }

  return 0;
}

gpasmVal evaluate(struct pnode *p)
{
  struct variable *var;
  gpasmVal p0, p1;

  if (((p->tag == binop) && (p->value.binop.op == CONCAT)) ||
      ((p->tag == unop) && (p->value.unop.op == VAR))) {
    char *string = evaluate_concatenation(p);
    struct symbol *s;

    s = get_symbol(state.stTop, string);
    if (s == NULL) {
      char buf[BUFSIZ];
      snprintf(buf, sizeof(buf), "Symbol not previously defined (%s).", string);
      gperror(GPE_NOSYM, buf); 
      return 0;
    } else {
      var = get_symbol_annotation(s);
      assert(var != NULL);
      return var->value;
    }
  }
  
  switch (p->tag) {
  case constant:
    return p->value.constant;
  case offset:
    return evaluate(p->value.offset);
  case symbol:
    {
      struct symbol *s;

      if (strcmp(p->value.symbol, "$") == 0) {
	return state.org << _16bit_core;
      } else {
	s = get_symbol(state.stTop, p->value.symbol);
	var = get_symbol_annotation(s);
	assert(var != NULL);
	return var->value;
      }
    }
  case unop:
    switch (p->value.unop.op) {
    case '!':
      return !evaluate(p->value.unop.p0);
    case '+':
      return  evaluate(p->value.unop.p0);
    case '-':
      return -evaluate(p->value.unop.p0);
    case '~':
      return ~evaluate(p->value.unop.p0);
    case UPPER:
      return (evaluate(p->value.unop.p0) >> 16) & 0xff;
    case HIGH:
      return (evaluate(p->value.unop.p0) >> 8) & 0xff;
    case LOW:
      return evaluate(p->value.unop.p0) & 0xff;
    case INCREMENT:  
      return evaluate(p->value.unop.p0) + 1;
    case DECREMENT:          
      return evaluate(p->value.unop.p0) - 1;
    default:
      assert(0);
    }
  case binop:
    p0 = evaluate(p->value.binop.p0);
    p1 = evaluate(p->value.binop.p1);
    switch (p->value.binop.op) {
    case '+':      return p0 + p1;
    case '-':      return p0 - p1;
    case '*':      return p0 * p1;
    case '/':
      if (p1 == 0){
        gperror(GPE_DIVBY0, NULL);
        return 0;
      } else {
        return p0 / p1;
      }
    case '%':
      if (p1 == 0){
        gperror(GPE_DIVBY0, NULL);
        return 0;
      } else {
        return p0 % p1;
      }
    case '&':      return p0 & p1;
    case '|':      return p0 | p1;
    case '^':      return p0 ^ p1;
    case LSH:      return p0 << p1;
    case RSH:      return p0 >> p1;
    case EQUAL:    return p0 == p1;
    case '<':      return p0 < p1;
    case '>':      return p0 > p1;
    case NOT_EQUAL:          return p0 != p1;
    case GREATER_EQUAL:      return p0 >= p1;
    case LESS_EQUAL:         return p0 <= p1;
    case LOGICAL_AND:        return p0 && p1;
    case LOGICAL_OR:         return p0 || p1;
    case '=': 
      gperror(GPE_BADCHAR, "Illegal character (=)");     
      return 0;
    default:
      assert(0); /* Unhandled binary operator */
    }
  default:
    assert(0); /* Unhandled parse node tag */
  }
  return (0); /* Should never reach here */
}

/* Attempt to evaluate expression 'p'.  Return its value if
 * successful, otherwise generate an error message and return 0.  */

gpasmVal maybe_evaluate(struct pnode *p)
{
  gpasmVal r;

  if (p && can_evaluate(p)) {
    r = evaluate(p);
  } else {
    r = 0;
  }

  return r;
}

/* count the number of relocatable addesses in the expression */

int count_reloc(struct pnode *p)
{
  struct symbol *s;
  struct variable *var;
  char *string;

  if (state.mode == absolute)
    return 0;

  if ((p->tag == binop) && (p->value.binop.op == CONCAT)) {
    string = evaluate_concatenation(p);
    s = get_symbol(state.stTop, string);
    if (s != NULL) {
      var = get_symbol_annotation(s);
      assert(var != NULL);
      switch(var->type) {
      case gvt_extern:
      case gvt_global:
      case gvt_static:
      case gvt_address:
        return 1;
      default:
        return 0;        
      }      
    }
    return 0;
  }
  switch (p->tag) {
  case constant:
    return 0;
  case offset:
    return count_reloc(p->value.offset);
  case symbol:
    if (strcmp(p->value.symbol, "$") == 0) {
      return 1;
    } else {
      s = get_symbol(state.stTop, p->value.symbol);
      if (s != NULL) {
        var = get_symbol_annotation(s);
        if (var != NULL) {
          switch(var->type) {
          case gvt_extern:
          case gvt_global:
          case gvt_static:
          case gvt_address:
            return 1;
          default:
            return 0;        
          }
        }
      }
    }  
    return 0;
  case unop:
    return count_reloc(p->value.unop.p0);
  case binop:
    return count_reloc(p->value.binop.p0) + count_reloc(p->value.binop.p1);
  default:
    assert(0);
  }

  return 0;
}

/* When generating object files, operands with relocatable addresses can only be 
   [UPPER|HIGH|LOW]([<relocatable symbol>] + [<offset>]) */

static void
add_reloc(struct pnode *p, short offset, unsigned short type)
{
  char *string = NULL;
  struct symbol *s = NULL;
  struct variable *var = NULL;

  if ((p->tag == binop) && (p->value.binop.op == CONCAT)) {
    string = evaluate_concatenation(p);
    s = get_symbol(state.stTop, string);
    if (s != NULL) {
      var = get_symbol_annotation(s);
      assert(var != NULL);
      switch(var->type) {
      case gvt_extern:
      case gvt_global:
      case gvt_static:
      case gvt_address:
        coff_reloc(var->coff_num, offset, type);
        return;
      default:
        return;        
      }      
    }
    return;
  }
  switch (p->tag) {
  case symbol:
    if (strcmp(p->value.symbol, "$") == 0) {
      char buffer[BUFSIZ];
      
      snprintf(buffer, sizeof(buffer), "_$_%06x", state.org << _16bit_core);
      set_global(buffer, state.org << _16bit_core, PERMANENT, gvt_static);
      s = get_symbol(state.stTop, buffer);
    } else {
      s = get_symbol(state.stTop, p->value.symbol);
    }
    if (s != NULL) {
      var = get_symbol_annotation(s);
      if (var != NULL) {
        switch(var->type) {
        case gvt_extern:
        case gvt_global:
        case gvt_static:
        case gvt_address:
          coff_reloc(var->coff_num, offset, type);
          return;
        default:
          return;        
        }
      }
    }  
    return;
  case unop:
    switch (p->value.unop.op) {
    case UPPER:
      add_reloc(p->value.unop.p0, offset, RELOCT_UPPER);
      return;
    case HIGH:
      add_reloc(p->value.unop.p0, offset, RELOCT_HIGH);
      return;
    case LOW:
      add_reloc(p->value.unop.p0, offset, RELOCT_LOW);
      return;
    case '!':
    case '+':
    case '-':
    case '~':
    case INCREMENT:  
    case DECREMENT:          
      gperror(GPE_UNRESOLVABLE, NULL);
      return;
    default:
      assert(0);
    }
  case binop:
    switch (p->value.binop.op) {
    case '+':
      /* The symbol can be in either position */
      if (count_reloc(p->value.binop.p0) == 1) {
        add_reloc(p->value.binop.p0, offset + maybe_evaluate(p->value.binop.p1), type);
      } else {
        add_reloc(p->value.binop.p1, offset + maybe_evaluate(p->value.binop.p0), type);
      }
      return;
    case '-':
      /* The symbol has to be first */
      if (count_reloc(p->value.binop.p0) == 1) {
        add_reloc(p->value.binop.p0, offset - maybe_evaluate(p->value.binop.p1), type);
      } else {
        gperror(GPE_UNRESOLVABLE, NULL);
      }
      return;
    case '*':
    case '/':
    case '%':
    case '&':
    case '|':
    case '^':
    case LSH:
    case RSH:
    case EQUAL:
    case '<':
    case '>':
    case NOT_EQUAL:
    case GREATER_EQUAL:
    case LESS_EQUAL:
    case LOGICAL_AND:
    case LOGICAL_OR:
    case '=': 
      gperror(GPE_UNRESOLVABLE, NULL);
      return;
    default:
      assert(0); /* Unhandled binary operator */
    }
    return;
  case constant:
  default:
    assert(0);
  }

  return;
}

/* Determine if the expression is the difference between two symbols in 
   the same section.  If so, calculate the offset and don't generate a
   relocation.

   [UPPER|HIGH|LOW]([<relocatable symbol>] - [<relocatable symbol>])   
   
*/

static int
same_section(struct pnode *p)
{
  struct pnode *p0;
  struct pnode *p1;
  struct symbol *sym0;
  struct symbol *sym1;
  struct variable *var0;
  struct variable *var1;

  if(!state.obj.enabled)
    return 0;

  if ((p->tag == unop) &&
      ((p->value.unop.op == UPPER) || 
       (p->value.unop.op == HIGH) || 
       (p->value.unop.op == LOW))) {
    p = p->value.unop.p0;
  }

  if ((p->tag != binop) ||
      (p->value.binop.op != '-') ||
      (count_reloc(p->value.binop.p0) != 1))
    return 0;

  p0 = p->value.binop.p0;
  p1 = p->value.binop.p1;
 
  if ((p0->tag != symbol) ||
      (p1->tag != symbol))
    return 0;

  sym0 = get_symbol(state.stTop, p0->value.symbol);
  sym1 = get_symbol(state.stTop, p1->value.symbol);
  var0 = get_symbol_annotation(sym0);
  var1 = get_symbol_annotation(sym1);

  /* They must come from the same section. Debug symbols are not placed
     in the global symbol table, so don't worry about symbol type.  */  
  if (var0->coff_section_num != var1->coff_section_num)
    return 0;

  return 1;

}

gpasmVal reloc_evaluate(struct pnode *p, unsigned short type)
{
  gpasmVal r = 0;
  int count = 0;

  if (state.mode == absolute) {
    r = maybe_evaluate(p);
  } else {
    count = count_reloc(p);
    if (count == 0) {
      /* no relocatable addresses */
      r = maybe_evaluate(p);
    } else if (count > 1) {
      if ((count == 2) && (same_section(p))) {
        /* It is valid to take the difference between two symbols in the same 
           section.  Evaluate, but don't add a relocation. */
        r = maybe_evaluate(p);
      } else {
        /* too many relocatable addresses */
        gperror(GPE_UNRESOLVABLE, NULL);
        r = 0;
      }
    } else {
      /* add the coff relocation */
      add_reloc(p, 0, type);
      r = 0;
    }
  }
  
  return r;
}

/* evaluate the number of passes for the "fill" directive*/
int eval_fill_number(struct pnode *p)
{
  int number;

  number = maybe_evaluate(p);
  if(_16bit_core) {
    /* For 16 bit core devices number is bytes not words */ 
    if ((number & 0x1) == 1){
      /* The number is divided by two, so it can't be odd */
      gperror(GPE_FILL_ODD, NULL);
    } else {
      number = number / 2;
    }	
  }

  return number;
}


syntax highlighted by Code2HTML, v. 0.9.1