/****************************************************************************
    Copyright (C) 1987-2005 by Jeffery P. Hansen

    This program 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 of the License, or
    (at your option) any later version.

    This program 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 this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
****************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "yybasic.h"
#include "vgrammar.h"
#include "hash.h"
#include "ycmalloc.h"

int expr_error;
const char *expr_errsym = 0;

static int Expr_eval_aux(SHash *H,Expr *e,int *rval,EValueLookup *f,void *d);

static int *intdup(int n)
{
  int *p = (int*)malloc(sizeof(int));
  *p = n;
  return p;
}

static Expr *new_Expr()
{
  Expr *E = (Expr*) malloc(sizeof(Expr));

  memset(E,0,sizeof(*E));

  return E;
}

void delete_Expr(Expr *E)
{
  if (!E) return;
  if (E->lit) free(E->lit);
  if (E->l) delete_Expr(E->l);
  if (E->r) delete_Expr(E->r);
  if (E->x) delete_Expr(E->x);
  free(E);
}


Expr *Expr_lit(const char *l)
{
  Expr *E = new_Expr();

  E->op = LITERAL;
  E->lit = strdup(l);

  return E;
}

Expr *Expr_num(int n)
{
  Expr *E = new_Expr();

  E->op = NUMBER;
  E->value = n;

  return E;
}

Expr *Expr_op(int op,Expr *l,Expr *r)
{
  Expr *E = new_Expr();

  E->op = op;
  E->l = l;
  E->r = r;

  return E;
}

Expr *Expr_op3(int op,Expr *l,Expr *r,Expr *x)
{
  Expr *E = new_Expr();

  E->op = op;
  E->l = l;
  E->r = r;
  E->x = x;

  return E;
}

Expr *Expr_case(int op,int tag)
{
  Expr *E = new_Expr();

  E->op = op;
  E->value = tag;

  return E;
}
Expr *Expr_func(const char *f,Expr *l,Expr *r)
{
  Expr *E = new_Expr();

  E->op = FUNC;
  E->lit = strdup(f);
  E->l = l;
  E->r = r;

  return E;
}

static int apply_op(int op,int a,int b,int *err)
{
  *err = 0;

  switch (op) {
  case ADD :
    return a+b;
  case SUB :
    return a-b;
  case POW :
    {
      int p = 1;
      if (b > 32) *err = EE_OVERFLOW;
      while (b-- > 0) p *= a; 
      return p;
    }
  case MUL :
    return a*b;
  case DIV :
    if (b == 0) *err = EE_DIV0;
    return a/b;
  case OR :
    return a||b;
  case AND :
    return a&&b;
  case GT :
    return a>b;
  case GE :
    return a>=b;
  case LT :
    return a<b;
  case LE :
    return a<=b;
  case EQ :
    return a==b;
  case NE :
    return a!=b;
  case NOT :
    return !b;
  case UNEG :
    return -b;
  }
  return 0;
}

static int Expr_eval_default(SHash *H,Expr *e,int *rval,EValueLookup *f,void *d)
{
  int rr;

  if (!e) return EE_NOCASE;


  if (e->op == DEFAULT) return 0;
  if (e->op != NEXT) {
    return EE_NOCASE;
  }

  rr = Expr_eval_default(H,e->l,rval,f,d);
  if (rr == EE_NOCASE)
    rr = Expr_eval_default(H,e->r,rval,f,d);
  else if (!rr)
    rr = Expr_eval_aux(H,e->r,rval,f,d);

  return rr;
}

static int Expr_eval_switch(int s,SHash *H,Expr *e,int *rval,EValueLookup *f,void *d)
{
  int rr;

  if (!e) return EE_NOCASE;


  if (e->op == CASE && e->value == s) return 0;
  if (e->op != NEXT) {
    return EE_NOCASE;
  }

  rr = Expr_eval_switch(s,H,e->l,rval,f,d);
  if (rr == EE_NOCASE)
    rr = Expr_eval_switch(s,H,e->r,rval,f,d);
  else if (!rr)
    rr = Expr_eval_aux(H,e->r,rval,f,d);

  return rr;
}

static int Expr_eval_aux(SHash *H,Expr *e,int *rval,EValueLookup *f,void *d)
{
  if (!e) {
    *rval = 0;
    return 0;
  }

  switch (e->op) {
  case LITERAL :
    {
      int *h = 0;

      if (H)
	h = SHash_find(H,e->lit);

      if (!h) {
	expr_errsym = e->lit;
	return EE_NOTDEF;
      }

      *rval = *h;
      return 0;
    }
    break;
  case FUNC :
    {
      int nl = 0,nr = 0,nx = 0,rr;

      if (strcmp(e->lit,"bits") == 0
	  || strcmp(e->lit,"inv") == 0
	  || strcmp(e->lit,"num") == 0) {
	if (e->l->op != LITERAL) return  EE_NOTLIT;
	if (!f) {
	  expr_errsym = e->l->lit;
	  return EE_NOTDEF;
	}
	return (*f)(e->lit,e->l->lit,d,rval);
      }

      if (e->l && (rr = Expr_eval_aux(H,e->l,&nl,f,d))) return rr;
      if (e->r && (rr = Expr_eval_aux(H,e->r,&nr,f,d) < 0)) return rr;
      if (e->x && (rr = Expr_eval_aux(H,e->x,&nx,f,d) < 0)) return rr;

      if (strcmp(e->lit,"log") == 0) {
	int n = 0;
	int a = 0;
	while (nl) {
	  if ((nl != 1) && (nl & 1)) a = 1;
	  nl >>= 1;
	  n++;
	}
	n += a;
	*rval = n;
	return 0;
      } else {
	expr_errsym = e->lit;
	return EE_BADFUNC;
      }
    }
    break;
  case NUMBER :
    *rval = e->value;
    return 0;
    break;
  case ADD :
  case SUB :
  case MUL :
  case POW :
  case DIV :
  case OR :
  case AND :
  case GT :
  case GE :
  case LT :
  case LE :
  case EQ :
  case NE :
    {
      int nl,nr,rr,err;

      if (!e->l || !e->r) return EE_INTERNAL;
      if ((rr = Expr_eval_aux(H,e->l,&nl,f,d))) return rr;
      if ((rr = Expr_eval_aux(H,e->r,&nr,f,d))) return rr;
      
      *rval = apply_op(e->op,nl,nr,&err);
      return err;
    }
    break;
  case NOT :
  case UNEG :
    {
      int nr,rr,err;

      if (!e->r) return EE_INTERNAL;
      if ((rr = Expr_eval_aux(H,e->r,&nr,f,d))) return rr;

      *rval = apply_op(e->op,0,nr,&err);
      return err;
    }
    break;
  case NEXT :
    {
      int rr;

      if (e->l && (rr = Expr_eval_aux(H,e->l,rval,f,d))) return rr;
      if (e->r && (rr = Expr_eval_aux(H,e->r,rval,f,d))) return rr;
      return 0;
    }
    break;
  case ASGN :
    {
      int rr;
      int *h;

      if (!H) return EE_INTERNAL;
      if (!e->l || e->l->op != LITERAL) return EE_INTERNAL;
      if ((rr = Expr_eval_aux(H,e->r,rval,f,d))) return rr;

      if ((h = SHash_find(H,e->l->lit))) {
	SHash_remove(H,e->l->lit);
	free(h);
      }
      SHash_insert(H,e->l->lit,intdup(*rval));
      return 0;
    }
    break;
  case IF :
    {
      int rr,c;
 
      if ((rr = Expr_eval_aux(H,e->l,&c,f,d))) return rr;

      if (c) {
	if ((rr = Expr_eval_aux(H,e->r,rval,f,d))) return rr;
      } else {
	if ((rr = Expr_eval_aux(H,e->x,rval,f,d))) return rr;
      }

      return 0;
    }
    break;
  case BREAK :
    *rval = 0;
    return EE_BREAK;
  case RETURN :
    {
      int rr;

      if ((rr = Expr_eval_aux(H,e->l,rval,f,d))) return rr;
      return EE_RETURN;
    }
    break;
  case DEFAULT :
  case CASE :
    return 0;
  case SWITCH :
    {
      int rr,s;

      if ((rr = Expr_eval_aux(H,e->l,&s,f,d))) return rr;

      rr = Expr_eval_switch(s,H,e->r,rval,f,d);
      if (rr == EE_OK) return 0;
      if (rr == EE_BREAK) return 0;
      if (rr == EE_RETURN) return rr;
      if (rr != EE_NOCASE) return rr;

      rr = Expr_eval_default(H,e->r,rval,f,d);
      if (rr == EE_BREAK) return 0;
      if (rr == EE_NOCASE) return 0;
      return rr;
    }
  }
  return EE_INTERNAL;
}

int Expr_eval(Expr *e,int *rval,EValueLookup *f,void *d)
{
  if (e && e->op == NEXT) {
    int ec;
    SHash *H = new_SHash();

    ec = Expr_eval_aux(H,e,rval,f,d);
    delete_SHash(H);

    if (ec == EE_RETURN) ec = EE_OK; 
    return ec;
  } else
    return Expr_eval_aux(0,e,rval,f,d);


}

int Expr_print(Expr *e)
{
  if (!e) {
    printf("; ");
    return 0;
  }

  switch (e->op) {
  case LITERAL :
    printf("%s",e->lit);
    break;
  case FUNC :
    printf("%s(%s)",e->lit,e->l->lit);
    break;
  case NUMBER :
    printf("%d",e->value);
    break;
  case ADD :
    printf("("); Expr_print(e->l);  printf("+");  Expr_print(e->r);  printf(")");
    break;
  case SUB :
    printf("("); Expr_print(e->l);  printf("-");  Expr_print(e->r);  printf(")");
    break;
  case MUL :
    printf("("); Expr_print(e->l);  printf("*");  Expr_print(e->r);  printf(")");
    break;
  case POW :
    printf("("); Expr_print(e->l);  printf("**");  Expr_print(e->r);  printf(")");
    break;
  case DIV :
    printf("("); Expr_print(e->l);  printf("/");  Expr_print(e->r);  printf(")");
    break;
  case OR :
    printf("("); Expr_print(e->l);  printf("||");  Expr_print(e->r);  printf(")");
    break;
  case AND :
    printf("("); Expr_print(e->l);  printf("&&");  Expr_print(e->r);  printf(")");
    break;
  case GT :
    printf("("); Expr_print(e->l);  printf(">");  Expr_print(e->r);  printf(")");
    break;
  case GE :
    printf("("); Expr_print(e->l);  printf(">=");  Expr_print(e->r);  printf(")");
    break;
  case LT :
    printf("("); Expr_print(e->l);  printf("<");  Expr_print(e->r);  printf(")");
    break;
  case LE :
    printf("("); Expr_print(e->l);  printf("<=");  Expr_print(e->r);  printf(")");
    break;
  case EQ :
    printf("("); Expr_print(e->l);  printf("==");  Expr_print(e->r);  printf(")");
    break;
  case NE :
    printf("("); Expr_print(e->l);  printf("!=");  Expr_print(e->r);  printf(")");
    break;
  case NOT :
    printf("!");
    Expr_print(e->l); 
  case UNEG :
    printf("-");
    Expr_print(e->l); 
    break;
  case NEXT :
    printf("{");
    if (e->l) {
      Expr_print(e->l); 
    }
    if (e->r) {
      Expr_print(e->r); 
    }
    printf("}");
    break;
  case ASGN :
    printf("%s = ",e->l->lit);
    Expr_print(e->r); 
    printf("; ");
    break;
  case IF :
    printf("if (");
    Expr_print(e->l); 
    printf(")");
    Expr_print(e->r); 
    if (e->x) {
      printf(" else ");
      Expr_print(e->x); 
    }
    break;
  case BREAK :
    printf(" break;");
    break;
  case RETURN :
    printf(" return;");
    break;
  case DEFAULT :
    printf(" default :");
    break;
  case CASE :
    printf(" case %d :",e->value);
    break;
  case SWITCH :
    printf("switch (");
    Expr_print(e->l); 
    printf(")");
    Expr_print(e->r); 
  }
  return 0;
}
