#
#  comp7
#
#  Class CompilerRuntime models the runtime system. Its method 'error'
#  signals a error at runtime and terminates the program.
#
#  Class Expr and its subclasses now have a method 'eval' to evaluate the
#  expression. An expression can be a letter whose value was previously
#  assigned before the ':' --- see the grammar. Then it is necessary to use
#  a symbol table to keep the variables with its values. The Symbol Table is
#  pointed by variable symbolTable
#
#  Grammar:
#      Program ::= VarDecList ':' Expr
#      VarDecList ::= | VarDec VarDecList
#      VarDec ::= Letter '=' Number
#      Expr ::= '(' Oper Expr Expr ')' | Number | Letter
#      Oper ::= '+' | '-' | '*' | '/'
#      Number ::= '0' | '1' | ... | '9'
#      Letter ::= 'A' | 'B' | ... | 'Z' | 'a'| 'b' | ... | 'z'
 
import sys
 
from AST import (Expr, CompositeExpr, NumberExpr,
                 Variable, VariableExpr, Program)
 
class Compiler:
 
    symbolTable = {}
 
    def compile(self, p_input):
        self.input = p_input
        self.tokenPos = 0
        self.nextToken()
        result = self.program()
        if self.token != '\0':
            self.error("End of file expected")
        return result
 
    def program(self):
        arrayVariable = self.varDecList()
        if self.token != ':':
            self.error(': expected')
            return None
        else:
            self.nextToken()
            e = self.expr()
            return Program(arrayVariable, e)
 
    def varDecList(self):
        ''' See how the repetition in the grammar reflects in the code.
            Since VarDec always begin with a letter, if token is NOT a
            letter, then VarDecList is empty and None is returned'''
        if not self.token.isalpha():
            return None
        else:
            arrayVariable = []
            while self.token.isalpha():
                arrayVariable.append( self.varDec() )
            return arrayVariable
 
    def varDec(self):
        name = self.letter()
        if self.token != '=':
            self.error("=  expected")
            return None
        else:
            self.nextToken()
            n = self.number()
            # semantic analysis
            # inserts the variable in the Symbol Table. It will be used to
            # retrieve the value of the variable when it is found inside the
            # expression and to check if the variable found in the expression
            # was 'declared' before the ':'
            try:
                self.symbolTable[name]
            except KeyError:
                self.symbolTable[name] = n
            else:
                self.error("Variable " + name + " has already been declared")
            return Variable(name, n.getValue())
 
    def letter(self):
        if not self.token.isalpha():
            self.error("Letter expected")
        else:
            ch = self.token
            self.nextToken()
            return ch
 
    def expr(self):
        if self.token == '(':
            self.nextToken()
            op = self.oper()
            e1 = self.expr()
            e2 = self.expr()
            ce = CompositeExpr(e1, op, e2)
            if self.token == ')':
                self.nextToken()
            else:
                self.error(") expected")
            return ce
        else:
            # Note we test the token to decide which production to use
            if self.token.isdigit():
                return self.number()
            else:
                return self.letterExpr()
 
    def number(self):
        if self.token >= '0' and self.token <= '9':
            e = NumberExpr(self.token)
            self.nextToken()
            return e
        else:
            self.error()
 
    def letterExpr(self):
        ch = self.letter()
        # semantic analysis
        # was the variable declared?
        try:
            e = self.symbolTable[ch]
        except KeyError:
            self.error("Variable was not declared")
        else:
            return VariableExpr(ch, e.getValue())
 
    def oper(self):
        op = self.token
        if (self.token == '+' or self.token == '-'
            or self.token == '*' or self.token == '/'):
            self.nextToken()
            return op
        else:
            self.error()
 
    def nextToken(self):
        while self.tokenPos < len(self.input) and self.input[self.tokenPos] == " ":
            self.tokenPos += 1
        if self.tokenPos < len(self.input):
            self.token = self.input[self.tokenPos]
            self.tokenPos += 1
        else:
            self.token = '\0'
 
    def error(self, message):
        if self.tokenPos == 0:
            self.tokenPos = 1
        else:
            if self.tokenPos >= len(self.input):
                self.tokenPos = len(self.input)
 
            strInput = self.input[self.tokenPos - 1:self.tokenPos]
            strError = 'Error at "' + strInput + '"'
 
            raise Exception(strError + " -> " + message)