#include <setjmp.h>
#include <math.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "microc.h"



struct internal_function intern_func[] = {
  "getche", call_getche, 0, 0, "",
  "putch", call_putch, 0, 0, "",
  "puts", call_puts, 0, 0, "",
  "print", print, 0, 0, "",
  "getnum", getnum, 0, 0, "",
  "", 0, 0, 0, ""                       // null terminate the list
};



// entry point into parser
void eval_exp(struct variable *value) {

  get_token();
  if (!*token) {
     sntx_err(E_NO_EXP);
     return;
   }
   if (*token == ';') {
     value->type = TOKEN_INT;
     value->value.i = 0;                // empty expression
     return;
   }
   eval_exp0(value);
   putback();                           // return last token read to input stream
}

// Process an assignment expression
void eval_exp0(struct variable *value) {

  char temp[ID_LEN];                    // holds name of var receiving the assignment
  register int temp_tok;
  int varindex;

  if (token_type == IDENTIFIER) {
    varindex = is_var(token);
    if (varindex) {                     // if a var, see if assignment
      strcpy(temp, token);
      temp_tok = token_type;
      get_token();
      if (*token == '=') {              // is an assignment
        struct variable *var;

        get_token();
        eval_exp0(value);               // get value to assign

        if ((varindex &15) == 1)
          var = &local_var_stack[varindex>>4];
        else if ((varindex &15) == 2)
          var = &global_vars[varindex>>4];

        var->value.i = value->value.i;
        return;

      } else if (*prog == '=') {
        if (*token == '+' || *token == '-' || *token == '*' || *token == '/' ||
            *token == '|' || *token == '&' || *token == '^') {
          struct variable *var;
          char op;

          op = *token;
          prog++;                       // skip '='
          get_token();
          eval_exp0(value);             // get value to assign

          if ((varindex &15) == 1)
            var = &local_var_stack[varindex>>4];
          else if ((varindex &15) == 2)
            var = &global_vars[varindex>>4];

          switch (op) {
          case '+':  var->value.i += value->value.i;  break;
          case '-':  var->value.i -= value->value.i;  break;
          case '*':  var->value.i *= value->value.i;  break;
          case '/':  var->value.i /= value->value.i;  break;
          case '&':  var->value.i &= value->value.i;  break;
          case '|':  var->value.i |= value->value.i;  break;
          case '^':  var->value.i ^= value->value.i;  break;
          }
          return;

        }

      } else {                          // not an assignment
        putback();                      // restore original token
        strcpy(token, temp);
        token_type = temp_tok;
      }
    }
  }
  eval_exp1(value);
}


// Process relational operators.
void eval_exp1(struct variable *value) {
  struct variable partial_value;
  register char op;
  int partval, val;

  eval_exp2(value);
  op = *token;
  if (op==LT || op==LE || op==GT || op==GE || op==EQ || op==NE || op==AND || op==OR) { // if it is a comparison
    get_token();
    partial_value.type = value->type;
    eval_exp2(&partial_value);

    if (partial_value.type == TOKEN_CHAR)
      partval = partial_value.value.c;
    else if (partial_value.type == TOKEN_INT)
      partval = partial_value.value.i;

    if (value->type == TOKEN_CHAR)
      val = value->value.c;
    else if (value->type == TOKEN_INT)
      val = value->value.i;

    switch(op) {                      // perform the relational operation
      case LT:
        value->value.i = val < partval;
        break;
      case LE:
        value->value.i = val <= partval;
        break;
      case GT:
        value->value.i = val > partval;
        break;
      case GE:
        value->value.i = val >= partval;
        break;
      case EQ:
        value->value.i = val == partval;
        break;
      case NE:
        value->value.i = val != partval;
        break;
      case OR:
        value->value.i = val || partval;
        break;
      case AND:
        value->value.i = val && partval;
        break;
    }
  }
}

//  Add or subtract two terms
void eval_exp2(struct variable *value) {
  register char  op;
  struct variable partial_value;

  eval_exp3(value);

  while ((op = *token) == '+' || op == '-') {
    get_token();
    partial_value.type = value->type;

    eval_exp3(&partial_value);

    if (value->type == TOKEN_INT) {
      switch(op) {                      // add or subtract
        case '-':
          if (partial_value.type == TOKEN_INT)
            value->value.i = value->value.i - partial_value.value.i;
          else if (partial_value.type == TOKEN_CHAR)
            value->value.i = value->value.i - partial_value.value.c;
          break;
        case '+':
          if (partial_value.type == TOKEN_INT)
            value->value.i = value->value.i + partial_value.value.i;
          else if (partial_value.type == TOKEN_CHAR)
            value->value.i = value->value.i + partial_value.value.c;
          break;
      }

    } else if (value->type == TOKEN_CHAR) {
      switch(op) {                      // add or subtract
        case '-':
          if (partial_value.type == TOKEN_INT)
            value->value.c = value->value.c - partial_value.value.i;
          else if (partial_value.type == TOKEN_CHAR)
            value->value.c = value->value.c - partial_value.value.c;
          break;
        case '+':
          if (partial_value.type == TOKEN_INT)
            value->value.c = value->value.c + partial_value.value.i;
          else if (partial_value.type == TOKEN_CHAR)
            value->value.c = value->value.c + partial_value.value.c;
          break;
      }

    }

  }
}

// Multiply or divide two factors.
void eval_exp3(struct variable *value) {
  register char op;
  struct variable partial_value;

  eval_exp4(value);

  while ((op = *token) == '*' || op == '/' || op == '%') {
    get_token();
    partial_value.type = value->type;
    eval_exp4(&partial_value);

    if (value->type == TOKEN_INT) {
      switch(op) {                      // add or subtract
        case '*':
          if (partial_value.type == TOKEN_INT)
            value->value.i = value->value.i * partial_value.value.i;
          else if (partial_value.type == TOKEN_CHAR)
            value->value.i = value->value.i * partial_value.value.c;
          break;
        case '/':
          if (partial_value.type == TOKEN_INT)
            value->value.i = value->value.i / partial_value.value.i;
          else if (partial_value.type == TOKEN_CHAR)
            value->value.i = value->value.i / partial_value.value.c;
          break;
        case '%':
          if (partial_value.type == TOKEN_INT)
            value->value.i = value->value.i % partial_value.value.i;
          else if (partial_value.type == TOKEN_CHAR)
            value->value.i = value->value.i % partial_value.value.c;
          break;
      }

    } else if (value->type == TOKEN_CHAR) {
      switch(op) {                      // add or subtract
        case '*':
          if (partial_value.type == TOKEN_INT)
            value->value.c = value->value.c * partial_value.value.i;
          else if (partial_value.type == TOKEN_CHAR)
            value->value.c = value->value.c * partial_value.value.c;
          break;
        case '/':
          if (partial_value.type == TOKEN_INT)
            value->value.c = value->value.c / partial_value.value.i;
          else if (partial_value.type == TOKEN_CHAR)
            value->value.c = value->value.c / partial_value.value.c;
          break;
        case '%':
          if (partial_value.type == TOKEN_INT)
            value->value.c = value->value.c % partial_value.value.i;
          else if (partial_value.type == TOKEN_CHAR)
            value->value.c = value->value.c % partial_value.value.c;
          break;
      }
    }


  }
}

// Is a unary + or -.
void eval_exp4(struct variable *value) {
  register char op;

  op = '\0';
  if (*token=='+' || *token=='-') {
    op = *token;
    get_token();
  }

  eval_exp5(value);

  if (op == '-') {
    if (value->type == TOKEN_INT)
      value->value.i = -value->value.i;
    else if (value->type == TOKEN_CHAR)
      value->value.c = -value->value.c;
  }
}

// Process parenthesized expression.
void eval_exp5(struct variable *value) {

  if ((*token == '(')) {
    get_token();
    eval_exp0(value);         // get subexpression
    if (*token != ')')  sntx_err(E_PAREN_EXPECTED);
    get_token();
  } else {
    atom(value);
  }
}

// Find value of number, variable, or function.
void atom(struct variable *value) {
  int i;
  struct variable *result;
  struct function *func;

  switch(token_type) {
  case IDENTIFIER:
    i = internal_func(token);
    if (i != -1) {                      // call "standard library" function
      (*intern_func[i].p)(value);

    } else {
      func = find_func(token);
      if (func) {                       // call script-defined function
        call(func);
        value->type = ret_value.type;
        value->value.i = ret_value.value.i;

      } else {
        result = find_var(token);         // get var's value
        value->type = result->type;
        value->value.i = result->value.i;
      }
    }
    get_token();
    return;
  case NUMBER:                          // is numeric constant
    // convert value and set var_type
    value->type = TOKEN_INT;
    value->value.i = atoi(token); // HENRIK - hex
    get_token();
    return;
  case DELIMITER:                       // see if character constant
    if (*token == '\'') {
      value->type = TOKEN_CHAR;
      value->value.c = *prog;
      prog++;
      if (*prog != '\'')  sntx_err(E_QUOTE_EXPECTED);
      prog++;
      get_token();
      return ;

    } else if (*token == ')')
      return;                           // process empty expression

    else
      sntx_err(E_SYNTAX);               // syntax error

  default:
    sntx_err(E_SYNTAX);                 // syntax error
  }
}


// return an error code
void sntx_err(int error) {
  char *p;
  int linecount, linestart;

  p = p_buf;
  linestart = linecount = 0;
  while (p != prog) {                   // find line number of error
    p++;
    if (*p == '\n') {
      linestart = (int)(p-p_buf);
      linecount++;
    }
  }

  longjmp(e_buf, error + (linestart<<8)); // return to safe point
}

// Get a token.
int get_token(void) {

  register char *temp;
  register char p;

  token_type = 255;
  tok = 255;

  temp = token;
  *temp = '\0';


 // skip over white space
  while (iswhite(*prog) && *prog)  ++prog;
  p = *prog;

  if (p == '\0') {                      // end of file
    *token = '\0';
    tok = TOKEN_FINISHED;
    return(token_type=DELIMITER);
  }

  // look for comments
  if (p == '/') {
    if (prog[1] == '*') {               // is a comment
      prog += 2;
      do {                              // find end of comment
        while (*prog != '*')  prog++;
        prog++;
      } while (*prog != '/');
      prog++;
    } else if (prog[1] == '/') {
      prog += 2;
      while ((*prog != '\n') && (*prog != '\r'))  prog++;
    }
    p = *prog;
  }

  if (p=='{' || p=='}') {               // block delimiters
    *temp++ = *prog;
    *temp = '\0';
    prog++;
    return (token_type = BLOCK);
  }

  if (p=='!' || p=='<' || p=='>' || p=='=' || p=='|' || p=='&') {
    // is or might be a relation operator
    switch (p) {
      case '=': if (*(prog+1) == '=') {
         prog += 2;
         *temp++ = EQ;
         *temp++ = EQ;
         *temp = '\0';
       }
       break;
      case '!': if (*(prog+1) == '=') {
         prog += 2;
         *temp++ = NE;
         *temp++ = NE;
         *temp = '\0';
       }
       break;
      case '<': if (*(prog+1) == '=') {
         prog += 2;
         *temp++ = LE;
         *temp++ = LE;
       } else {
         prog++;
         *temp++ = LT;
       }
       *temp = '\0';
       break;
      case '>': if (*(prog+1) == '=') {
         prog += 2;
         *temp++ = GE;
         *temp++ = GE;
       } else {
         prog++;
         *temp++ = GT;
       }
       *temp = '\0';
       break;
      case '|': if (*(prog+1) == '|') {
         prog += 2;
         *temp++ = OR;
         *temp++ = OR;
         *temp = '\0';
        }
       break;
      case '&': if (*(prog+1) == '&') {
         prog += 2;
         *temp++ = AND;
         *temp++ = AND;
         *temp = '\0';
      }
       break;
    }
    if (*token)  return(token_type = DELIMITER);
  }

  if (strchr("+-*^/%=;(),'&|", *prog)) {  // delimiter
    *temp = *prog;
    prog++;                             // advance to next position
    temp++;
    *temp = '\0';
    return (token_type=DELIMITER);
  }

  if (p == '"') {                       // quoted string
    prog++;
    while (*prog != '"' && *prog != '\r' && *prog != '\n')
      *temp++ = *prog++;
    if (*prog == '\r' || *prog == '\n')  sntx_err(E_SYNTAX);
    prog++;
    *temp = '\0';
    return (token_type = STRING);
  }

  if (isdigit(p)) {                     // number
    while (!isdelim(*prog))  *temp++ = *prog++;
    *temp = '\0';
    return(token_type = NUMBER);
  }

  if (isalpha(p)) {                     // var or command
    while (!isdelim(*prog) && *prog!='{')  *temp++ = *prog++;
    token_type = TEMP;
  }

  *temp = '\0';

  // see if a string is a command or a variable
  if (token_type == TEMP) {
    tok = look_up(token);               // convert to internal rep
    if (tok)
      token_type = KEYWORD;             // is a keyword
    else
      token_type = IDENTIFIER;
  }

  return token_type;
}

// Return a token to input stream.
void putback(void) {
  char *t;

  t = token;
  for(; *t; t++)  prog--;
}

// Look up a token's internal representation in the token table.
int look_up(char *s) {
  register int i;

  // see if token is in table
  for (i = 0; *table[i].command; i++)
      if (!strcmp(table[i].command, s))  return table[i].tok;

  return 0;                             // unknown command
}

// Return index of internal library function or -1 if not found.
int internal_func(char *s) {
  int i;

  for (i = 0; intern_func[i].f_name[0]; i++)
    if (!strcmp(intern_func[i].f_name, s))  return i;

  return -1;
}

// Return true if c is a delimiter
int isdelim(char c) {

  if (c==' ' || c=='(' || c==')' || c=='\r' || c=='\n')
    return 1;
  if (c=='!' || c==';' || c==',' || c=='+' || c=='-' || c=='<' || c=='>' || c=='/' || c=='*' )
    return 1;
  if (c=='\'' || c=='&' || c=='|' || c==9 || c=='%' || c=='^' || c=='=' || c==0)
    return 1;

  return 0;
}

// Return 1 if c is space or tab.
int iswhite(char c) {

  if (c==' ' || c=='\t' || c=='\n' || c=='\r')
    return 1;

  return 0;
}
