/*
    parser.c - Parser for Little C interpreter
    Recursive descent parser for integer expressions
    which may include variables and function calls.

    Herbert Schildt
    Dr. Dobb's Journal v14, n8, August 1989
    http://www.ddj.com/184408184
*/
#include <setjmp.h>
#include <math.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "littlec.h"

extern char *prog;  /* current location in source code */
extern char *p_buf;  /* points to start of program buffer */
extern jmp_buf e_buf; /* hold environment for longjmp() */

/* An array of these structures will hold the info
   associated with global variables.
*/
extern struct var_type global_vars[NUM_GLOBAL_VARS];

/*  This is the function call stack. */
extern struct func_type func_stack[NUM_FUNC];

/* Keyword table */
extern struct commands table[];

struct intern_func_type intern_func[] = {
    INTERN_FUNC_TABLE
    {"", 0}  /* null terminate the list */
};

extern char token[TOK_SIZE]; /* string representation of token */
extern char token_type; /* contains type of token */
extern char tok; /* internal representation of token */

extern int ret_value; /* function return value */

void eval_exp0(int *value);
void eval_exp(int *value);
void eval_exp1(int *value);
void eval_exp2(int *value);
void eval_exp3(int *value);
void eval_exp4(int *value);
void eval_exp5(int *value);
void atom(int *value);
void assign_var(char *var_name, int value);
int isdelim(char c), look_up(char *s), iswhite(char c);
int find_var(char *s);
int internal_func(char *s);
int is_var(char *s);
char *find_func(char *name);
void call(void);

/* Entry point into parser. */
void eval_exp(int *value)
{
    get_token();
    if (!*token) {
        sntx_err(NO_EXP);
        return;
    }
    if (*token == ';') {
        *value = 0; /* empty expression */
        return;
    }
    eval_exp0(value);
    putback(); /* return last token read to input stream */
}

/* Process an assignment expression */
void eval_exp0(int *value)
{
    char temp[ID_LEN];  /* holds name of var receiving
                         the assignment */
    register int temp_tok;

    if (token_type == IDENTIFIER) {
        if (is_var(token)) {  /* if a var, see if assignment */
            strcpy(temp, token);
            temp_tok = token_type;
            get_token();
            if (*token == '=') {  /* is an assignment */
                get_token();
                eval_exp0(value);  /* get value to assign */
                assign_var(temp, *value);  /* assign the value */
                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(int *value)
{
    int partial_value;
    register char op;
    char relops[7] = {
        LT, LE, GT, GE, EQ, NE, 0
    };

    eval_exp2(value);
    op = *token;
    if (strchr(relops, op)) {
        get_token();
        eval_exp2(&partial_value);
        switch(op) {  /* perform the relational operation */
        case LT:
            *value = *value < partial_value;
            break;
        case LE:
            *value = *value <= partial_value;
            break;
        case GT:
            *value = *value > partial_value;
            break;
        case GE:
            *value = *value >= partial_value;
            break;
        case EQ:
            *value = *value == partial_value;
            break;
        case NE:
            *value = *value != partial_value;
            break;
        }
    }
}

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

    eval_exp3(value);
    while((op = *token) == '+' || op == '-') {
        get_token();
        eval_exp3(&partial_value);
        switch(op) { /* add or subtract */
        case '-':
            *value = *value - partial_value;
            break;
        case '+':
            *value = *value + partial_value;
            break;
        }
    }
}

/* Multiply or divide two factors. */
void eval_exp3(int *value)
{
    register char  op;
    int partial_value, t;

    eval_exp4(value);
    while((op = *token) == '*' || op == '/' || op == '%') {
        get_token();
        eval_exp4(&partial_value);
        switch(op) { /* mul, div, or modulus */
        case '*':
            *value = *value * partial_value;
            break;
        case '/':
            if (partial_value == 0) sntx_err(DIV_BY_ZERO);       
            *value = (*value) / partial_value;
            break;
        case '%':
            t = (*value) / partial_value;
            *value = *value-(t * partial_value);
            break;
        }
    }
}

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

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

/* Process parenthesized expression. */
void eval_exp5(int *value)
{
    if ((*token == '(')) {
        get_token();
        eval_exp0(value);   /* get subexpression */
        if (*token != ')') sntx_err(PAREN_EXPECTED);
        get_token();
    }
    else
        atom(value);
}

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

    switch(token_type) {
    case IDENTIFIER:
        i = internal_func(token);
        if (i!= -1) {  /* call "standard library" function */
            *value = (*intern_func[i].p)();
        }
        else
        if (find_func(token)) { /* call user-defined function */
            call();
            *value = ret_value;
        }
        else *value = find_var(token); /* get var's value */
        get_token();
        return;
    case NUMBER: /* is numeric constant */
        *value = atoi(token);
        get_token();
        return;
    case DELIMITER: /* see if character constant */
        if (*token == '\'') {
            *value = *prog;
            prog++;
            if (*prog!='\'') sntx_err(QUOTE_EXPECTED);
            prog++;
            get_token();
            return ;
        }
        if (*token==')') return; /* process empty expression */
        else sntx_err(SYNTAX); /* syntax error */
    default:
        sntx_err(SYNTAX); /* syntax error */
    }
}

/* Display an error message. */
void sntx_err(int error)
{
    char *p, *temp;
    int linecount = 0;
    register int i;

    static char *e[]= {
        "syntax error",
        "unbalanced parentheses",
        "no expression present",
        "equals sign expected",
        "not a variable",
        "parameter error",
        "semicolon expected",
        "unbalanced braces",
        "function undefined",
        "type specifier expected",
        "too many nested function calls",
        "return without call",
        "parentheses expected",
        "while expected",
        "closing quote expected",
        "not a string",
        "too many local variables",
        "division by zero"
    };
    printf("\n%s", e[error]);
    p = p_buf;
    while(p != prog) {  /* find line number of error */
        p++;
        if (*p == '\r') {
            linecount++;
        }
    }
    printf(" in line %d\n", linecount);

    temp = p;
    for (i=0; i < 20 && p > p_buf && *p != '\n'; i++, p--);
    for (i=0; i < 30 && p <= temp; i++, p++) printf("%c", *p);

    longjmp(e_buf, 1); /* return to safe point */
}

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

    register char *temp;

    token_type = 0; tok = 0;

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

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

    if (*prog == '\r' || *prog == '\n') {
        ++prog;
        /* ++prog; */
        /* skip over white space */
        while(iswhite(*prog) && *prog) ++prog;
    }

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

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

    /* look for comments */
    if (*prog == '/')
        if (*(prog+1) == '*') { /* is a comment */
            prog += 2;
            do { /* find end of comment */
                while(*prog != '*') prog++;
                prog++;
            } while (*prog != '/');
            prog++;
        }

    if (strchr("!<>=", *prog)) { /* is or might be
                                 a relational operator */
        switch(*prog) {
        case '=':
            if (*(prog+1) == '=') {
                prog++; prog++;
                *temp = EQ;
                temp++; *temp = EQ; temp++;
                *temp = '\0';
            }
            break;
        case '!':
            if (*(prog+1) == '=') {
                prog++; prog++;
                *temp = NE;
                temp++; *temp = NE; temp++;
                *temp = '\0';
            }
            break;
        case '<':
            if (*(prog+1) == '=') {
                prog++; prog++;
                *temp = LE; temp++; *temp = LE;
            }
            else {
                prog++;
                *temp = LT;
            }
            temp++;
            *temp = '\0';
            break;
        case '>':
            if (*(prog+1) == '=') {
                prog++; prog++;
                *temp = GE; temp++; *temp = GE;
            }
            else {
                prog++;
                *temp = GT;
            }
            temp++;
            *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 (*prog=='"') { /* quoted string */
        prog++;
        while(*prog != '"'&& *prog != '\r') *temp++ = *prog++;
        if (*prog == '\r') sntx_err(SYNTAX);
        prog++; *temp = '\0';
        return (token_type = STRING);
    }

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

    if (isalpha(*prog)) { /* var or command */
        while(!isdelim(*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--;
}

/* If using multiple arguments, check first token */
void get_first_token()
{
  get_token();
  if(*token!='(')  sntx_err(PAREN_EXPECTED);
}

/* If using multiple arguments, check last token */
void get_last_token()
{
  get_token();
  if(*token!=')') sntx_err(PAREN_EXPECTED);
  get_token();
  if(*token!=';') sntx_err(SEMI_EXPECTED);
  putback();
}

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

    /* convert to lowercase */
    p = s;
    while(*p) { *p = tolower(*p); p++; }

    /* 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 (strchr(" !;,+-<>'/*%^=()", c) || c == 9 ||
        c == '\r' || c == 0) return 1;
    return 0;
}

/* Return 1 if c is space or tab. */
int iswhite(char c)
{
    if (c == ' ' || c == '\t') return 1;
    else return 0;
}
