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

#include "microc.h"


char *prog;                             // current location in source code
char token[1024];
char token_type, tok;

char *p_buf;                            // points to start of program buffer
jmp_buf e_buf;                          // hold environment for longjmp()

struct variable global_vars[NUM_GLOBAL_VARS];
struct variable local_var_stack[NUM_LOCAL_VARS];
struct function func_table[NUM_FUNC];
int call_stack[NUM_FUNC];

struct commands table[] = {             // lowercase
  "if",             TOKEN_IF,
  "else",           TOKEN_ELSE,
  "for",            TOKEN_FOR,
  "do",             TOKEN_DO,
  "while",          TOKEN_WHILE,
  "char",           TOKEN_CHAR,
  "int",            TOKEN_INT,
  "return",         TOKEN_RETURN,
  "break",          TOKEN_BREAK,
  "continue",       TOKEN_CONTINUE,
  "end",            TOKEN_END,
  "",               TOKEN_END           // mark end of table
};

int functos;                            // index to top of function call stack
int func_index;                         // index into function table
int gvar_index;                         // index into global variable table
int lvartos;                            // index into local variable stack
struct variable ret_value;              // function return value


int pollcounter = 0;
int (* pollcode)(char *);



int microc_start(char *p_buf, int (* unknown_function)(char *), int (* poll)(char *)) {

  struct function *mainfunc;
  int errcode;

  pollcode = poll;

  errcode = setjmp(e_buf);
  if (errcode)  return errcode;         // initialize long jump buffer

  gvar_index = 0;                       // initialize global variable index

  // set program pointer to start of program buffer
  prog = p_buf;
  prescan();                            // find the location of all functions
                                        // and global variables in the program

  lvartos = 0;                          // initialize local variable stack index
  functos = 0;                          // initialize the CALL stack index

  // setup call to main()
  mainfunc = find_func("main");         // find program starting point
  if (!mainfunc)  sntx_err(E_NO_MAIN);
  prog = mainfunc->loc-1;               // back up to opening (
  strcpy(token, "main");
  call(mainfunc);                       // call main() to start interpreting

  return 0;                             // no error
}

// Interpret a single statement or block of code. When
// interp_block() returns from its initial call, the final
// brace (or a return) in main() has been encountered.
int interp_block(void) {
  struct variable value;
  int retcode;
  char block = 0;

  do {
    token_type = get_token();

    if (pollcode) {
      pollcounter = (pollcounter+1) &31;
      if (!pollcounter)  pollcode(p_buf);
    }

    // If interpreting single statement, return on first semicolon.
    // see what kind of token is up
    if (token_type == IDENTIFIER) {
      // Not a keyword, so process expression.
      putback();                        // restore token to input stream for
                                        // further processing by eval_exp()
      eval_exp(&value);                 // process the expression
      if (*token != ';')  sntx_err(E_SEMI_EXPECTED);

    } else if (token_type == BLOCK) {   // if block delimiter
      if (*token == '{')                // is a block
        block = 1;                      // interpreting block, not statement
      else
        return _GO_ON;                  // is a }, so return

    } else                              // is keyword
      switch(tok) {
        case TOKEN_CHAR:
        case TOKEN_INT:                 // declare local variables
          putback();
          decl_local();
          break;
        case TOKEN_RETURN:              // return from function call
          func_ret();
          return _RETURN;
        case TOKEN_BREAK:
          return _BREAK;
        case TOKEN_CONTINUE:
          return _CONTINUE;
        case TOKEN_IF:                  // process an if statement
          retcode = exec_if();
          if (retcode != _GO_ON)  return retcode;
          break;
        case TOKEN_ELSE:
          sntx_err(E_SYNTAX);
          break;
        case TOKEN_WHILE:               // process a while loop
          retcode = exec_while();
          if (retcode != _GO_ON)  return retcode;
          break;
        case TOKEN_DO:                  // process a do-while loop
          retcode = exec_do();
          if (retcode != _GO_ON)  return retcode;
          break;
        case TOKEN_FOR:                 // process a for loop
          retcode = exec_for();
          if (retcode != _GO_ON)  return retcode;
          break;
        case TOKEN_END:
          exit(0);
      }
  } while (tok != TOKEN_FINISHED && block);

  return _GO_ON;
}


// Find the location of all functions in the program
// and store global variables.
void prescan(void) {
  char *p;
  char temp[32];
  int brace = 0;                        // When 0, this var tells us that
                                        // current source position is outside
                                        // of any function.
  p = prog;
  func_index = 0;
  do {

    while(brace) {                      // bypass code inside functions
      get_token();
      if (token_type == BLOCK) {
        if (*token == '{')
          brace++;
        else if (*token == '}')
          brace--;
      }
    }

    get_token();                        // get start of global var or start of func

    if (tok==TOKEN_CHAR || tok==TOKEN_INT) {      // is global var
      putback();
      decl_global();

    } else if (token_type==IDENTIFIER) {          // is it a function?
      strcpy(temp, token);
      get_token();

      if (*token == '(') {                        // must be a function
        func_table[func_index].loc = prog;        // save start of function
        strcpy(func_table[func_index].func_name, temp);
        func_index++;
        while (*prog != ')')  prog++;             // skip to end of args
        prog++;
        // prog points to opening curly brace of function

      } else
        putback();

    } else if (*token == '{') {
      brace++;
    }

  } while (tok != TOKEN_FINISHED);

  prog = p;
}

// Return the entry point of the specified function.
// Return NULL if not found.
struct function *find_func(char *name) {
  register int i;

  for (i = 0; i < func_index; i++)
    if (!strcmp(name, func_table[i].func_name))
      return &func_table[i];

  return NULL;
}

// Declare a global variable.
void decl_global(void) {
  int vartype;

  get_token();                          // get type

  vartype = tok;                        // save var type

  do {                                  // process comma-separated list
    global_vars[gvar_index].type = vartype;
    global_vars[gvar_index].value.all[0] = global_vars[gvar_index].value.all[1] = 0;
    get_token();                        // get name
    strcpy(global_vars[gvar_index].name, token);
    get_token();    // HENRIK - get value or dimensions
    gvar_index++;
  } while (*token == ',');

  if (*token != ';')  sntx_err(E_SEMI_EXPECTED);
}

// Declare a local variable.
void decl_local(void) {
  struct variable i;

  get_token();                          // get type

  i.type = tok;
  i.value.all[0] = i.value.all[1] = 0;

  do {                                  // process comma-separated list
    get_token();                        // get var name
    strcpy(i.name, token);
    local_push(&i);
    get_token();
  } while (*token == ',');

  if (*token != ';')  sntx_err(E_SEMI_EXPECTED);
}

// Call a function.
void call(struct function *func) {
  char *temp;
  int lvartemp, retcode;

  lvartemp = lvartos;                 // save local var stack index
  get_args();                         // get function arguments
  temp = prog;                        // save return location
  func_push(lvartemp);                // save local var stack index
  prog = func->loc;                   // reset prog to start of function
  get_params();                       // load the function's parameters with
                                      // the values of the arguments
  retcode = interp_block();           // interpret the function
  if (retcode != _RETURN && retcode != _GO_ON)
    sntx_err(E_SYNTAX);

  prog = temp;                        // reset the program pointer
  lvartos = func_pop();               // reset the local var stack
}

// Push the arguments to a function onto the local variable stack.
void get_args(void) {
  int count;
  struct variable i, temp[NUM_PARAMS];

  count = 0;
  get_token();
  if (*token != '(')  sntx_err(E_PAREN_EXPECTED);

  // process a comma-separated list of values
  do {
    eval_exp(&temp[count]);
    get_token();
    count++;
  } while (*token == ',');
  count--;

  // now, push on local_var_stack in reverse order
  for(; count>=0; count--) {
    i.value.all[0] = temp[count].value.all[0];
    i.value.all[1] = temp[count].value.all[1];
    i.type = temp[count].type;
    local_push(&i);
  }
}

// Get function parameters.
void get_params(void) {
  struct variable *p;
  int i;

  i = lvartos-1;
  do {                                  // process comma-separated list of parameters
    get_token();
    p = &local_var_stack[i];
    if (*token != ')') {
      if (tok != TOKEN_INT && tok != TOKEN_CHAR)
         sntx_err(E_TYPESPEC_EXPECTED);
      p->type = tok;
      get_token();

      // link parameter name with argument already on local var stack
      strcpy(p->name, token);
      get_token();
      i--;

    } else
      break;

  } while (*token == ',');

  if (*token != ')') sntx_err(E_PAREN_EXPECTED);
}

// Return from a function.
void func_ret(void) {

  ret_value.type = TOKEN_INT;
  ret_value.value.i = 0;

  eval_exp(&ret_value);                 // get return value, if any
  // HENRIK - cast to function return type
}

// Push a local variable.
void local_push(struct variable *i) {

  struct variable *lcl;

  if (lvartos > NUM_LOCAL_VARS)
    sntx_err(E_TOO_MANY_LVARS);

  lcl = &local_var_stack[lvartos];

  lcl->type = i->type;
  strcpy(lcl->name, i->name);
  lcl->value.i = i->value.i;
  lvartos++;
}

// Pop index into local variable stack.
int func_pop(void) {

  functos--;
  if (functos < 0)  sntx_err(E_RET_NOCALL);
  return(call_stack[functos]);
}

// Push index of local variable stack.
void func_push(int i) {

  if (functos > NUM_FUNC)   sntx_err(E_NEST_FUNC);
  call_stack[functos] = i;
  functos++;
}

// assign a value to a variable
void assign_var(char *name, struct variable *value) {
  register int i;
  struct variable *var;

  // first, see if it's a local variable
  for (i = lvartos-1; i >= call_stack[functos-1]; i--)  {
    var = &local_var_stack[i];
    if (!strcmp(var->name, name)) {
      var->value.i = value->value.i;
      return;
    }
  }

  // if not local, try global var table
  if (i < call_stack[functos-1])
    for (i=0; i<NUM_GLOBAL_VARS; i++) {
      var = &global_vars[i];
      if (!strcmp(var->name, name)) {
        var->value.i = value->value.i;
        return;
      }
    }

  sntx_err(E_NOT_VAR);                  // variable not found
}

// return the value of a variable.
struct variable *find_var(char *s) {
  register int i;

  // first, see if it's a local variable
  for (i = lvartos-1; i >= call_stack[functos-1]; i--)
    if (!strcmp(local_var_stack[i].name, s))
      return &local_var_stack[i];

  // otherwise, try global vars
  for (i = 0; i<NUM_GLOBAL_VARS; i++) {
    if (!strcmp(global_vars[i].name, s))
      return &global_vars[i];
  }

  sntx_err(E_NOT_VAR);                  // variable not found
  return NULL;
}

// determine if an identifier is a variable
// return index if variable is found; 0 otherwise
int is_var(char *s) {
  register int i;

  // first, see if it's a local variable
  for (i = lvartos-1; i >= call_stack[functos-1]; i--)
    if (!strcmp(local_var_stack[i].name, s))
      return 1+(i*16);

  // otherwise, try global vars
  for (i = 0; i < NUM_GLOBAL_VARS; i++)
    if (!strcmp(global_vars[i].name, s))
      return 2+(i*16);

  return 0;
}

// execute an if() statement
int exec_if(void) {
  int retcode;
  struct variable cond;

  eval_exp(&cond);                      // get left expression

  if (cond.value.i) {                   // comparisons always return int
    retcode = interp_block();           // is true so process target of IF
    if (retcode == _RETURN)
      return _RETURN;
    else if (retcode == _CONTINUE)
      return _CONTINUE;
    else if (retcode == _BREAK) {
      return _BREAK;
    }

    get_token();                        // should be either ; or right after }
    if (token_type == DELIMITER && *token == ';')
      get_token();                      // skip ;

    if (tok == TOKEN_ELSE)              // if else, skip  else block
      find_eob();
    else                                // if not else, put token back
      putback();

  } else {                              // otherwise skip around IF block and

    find_eob();                         // find start of 'if() {...}'
    get_token();                        // we hope for 'else'
    if (tok != TOKEN_ELSE) {
      putback();                        // restore token if no ELSE is present
      return _GO_ON;
    }
    retcode = interp_block();
    if (retcode == _RETURN)   return _RETURN;
  }

  return _GO_ON;
}

// execute a while() loop
// return _RETURN if a return is encountered
int exec_while(void) {
  struct variable cond;
  char *temp;
  int retcode;

  putback();
  temp = prog;                          // save location of top of while loop
  get_token();
  eval_exp(&cond);                      // check the conditional expression

  if (cond.value.i) {                   // if true, interpret
    char *blockstart;

    blockstart = prog;                  // save start of block
    retcode = interp_block();
    if (retcode == _RETURN)
      return _RETURN;

    else if (retcode == _BREAK) {
      prog = blockstart;                // back to start of block
      find_eob();                       // skip to eob
      return _GO_ON;                    // return OK
    }

  } else {                              // otherwise, skip around loop
    find_eob();
    return _GO_ON;
  }
  prog = temp;                          // loop back to top
  return _GO_ON;
}

// execute a do-while() loop.
int exec_do(void) {
  struct variable cond;
  char *temp, *blockstart;
  int retcode;

  putback();
  temp = prog;                          // save location of top of do loop
  get_token();                          // get start of loop
  blockstart = prog;
  retcode = interp_block();             // interpret loop
  if (retcode == _RETURN)               // exit from function
    return _RETURN;

  else if (retcode == _BREAK) {         // exit from do {...}
    prog = blockstart;                  // back to 'do{'
    find_eob();                         // skip {...}
    get_token();                        // get the 'while'
    if (tok != TOKEN_WHILE)
      sntx_err(E_WHILE_EXPECTED);
    while (token_type != DELIMITER || *token != ';')
      get_token();
    return _GO_ON;

  } else if (retcode == _CONTINUE) {    // skip until end of do {...}
    prog = blockstart;                  // back to 'do{'
    find_eob();                         // skip {...}
  }

  get_token();                          // get the 'while'
  if (tok != TOKEN_WHILE)
    sntx_err(E_WHILE_EXPECTED);
  eval_exp(&cond);                      // check the loop condition
  if (cond.value.i)  prog = temp;       // if true, loop; otherwise, continue on

  return _GO_ON;
}


// skip ()
void skip_paren() {

  int brace = 1;

  get_token();
  if (*token != '(' && token_type != DELIMITER)
    sntx_err(E_PAREN_EXPECTED);

  do {
    get_token();
    if (token_type == DELIMITER) {
      if (*token == '(')
        brace++;
      else if (*token == ')')
        brace--;
    }
  } while (brace);
}

// find the end of a block
void find_eob(void) {
  int brace;

  get_token();
  if (token_type == IDENTIFIER) {
    // HENRIK - is this a problem??
    while (*token != ';') {
      get_token();
    }

  } else if (token_type == KEYWORD) {
    if (tok == TOKEN_FOR) {             // for () block
      skip_paren();                     // skip ()
      find_eob();                       // skip block

    } else if (tok == TOKEN_IF) {       // if () block1 [else block2]
      skip_paren();                     // skip ()
      find_eob();                       // skip block1
      get_token();                      // get else
      if (token_type == KEYWORD && tok == TOKEN_ELSE)
        find_eob();                     // skip block2
      else
        putback();                      // not an else, so put back token

    } else if (tok == TOKEN_DO) {       // do block while();
      find_eob();                       // skip block
      get_token();                      // get while
      if (token_type == KEYWORD && tok == TOKEN_WHILE) {
        skip_paren();                   // skip ()
        get_token();                    // get ;
        if (*token != ';' || token_type != DELIMITER)
          sntx_err(E_SEMI_EXPECTED);

      } else
        sntx_err(E_WHILE_EXPECTED);

    } else if (tok == TOKEN_WHILE) {    // while () block
      skip_paren();                     // skip ()
      find_eob();                       // skip block

    } else if (tok == TOKEN_CONTINUE) {
      get_token();

    } else if (tok == TOKEN_BREAK) {
      get_token();

    } else if (tok == TOKEN_RETURN) {
      // HENRIK - tricky!!
      // get token
      // if ( skip parentheses
      // else skip until ;

    } else {
      sntx_err(E_SYNTAX);
    }

  } else if ((token_type == BLOCK) && (*token == '{')) {
    brace = 1;
    do {
      get_token();
      if (token_type == BLOCK) {
        if (*token == '{')
          brace++;
        else if (*token == '}')
          brace--;
      }
    } while (brace);

  } else if ((token_type == DELIMITER) && (*token == ';')) {
    return;

  } else {
    printf("find_eob() error:  token=%s \n", token);
    sntx_err(E_SYNTAX);

  }
}

// execute a for() loop
int exec_for(void) {
  struct variable cond;
  char *condptr, *incrptr;
  int brace, retcode;

  get_token();                          // read up to and including '('

  if (*prog != ';') {                   // only if there is a token, ie. not a ;
    eval_exp(&cond);                    // initialization expression
    if (*token != ';')                  // ; after init-expr
      sntx_err(E_SEMI_EXPECTED);
  }

  prog++;                               // skip the ; after condition
  condptr = prog;

  cond.type = TOKEN_INT;
  cond.value.i = 1;                     // in case condition is empty

  for(;;) {

    if (*prog != ';') {                 // only if there is a token, ie. not a ;
      eval_exp(&cond);                  // check the condition
      if (*token != ';')
        sntx_err(E_SEMI_EXPECTED);
    }

    prog++;
    incrptr = prog;

    // find the start of the for block
    brace = 1;
    while (brace) {
      get_token();
      if (*token == '(')
        brace++;
      else if (*token == ')')
        brace--;
    }

    if (cond.value.i) {                 // if true, interpret
      char *blockstart;
      blockstart = prog;                // save start of block
      retcode = interp_block();
      if (retcode == _RETURN)
        return _RETURN;
      else if (retcode == _BREAK) {
        prog = blockstart;              // skip to end of block...
        find_eob();
        return _GO_ON;                  // and continue after for() {}
      } // continue is handled automatically

    } else {                            // otherwise, skip around loop
      find_eob();
      return _GO_ON;
    }
// IDEA - save prog here so that find_eob() after _BREAK can be skipped
    prog = incrptr;
    eval_exp(&cond);                    // do the increment
    prog = condptr;                     // loop back to top
  }

  return _GO_ON;
}
