# note - use python3
# note - example from https://github.com/dabeaz/sly
# note - type ctrl-d as the end of input.

# notes on lexer
# - tokens are compiled with re.verbose flag
#   (comments with #, gets rid of white space).
#   If you need to match # use \#
# - tokens matched in order in your file.
#   If you want to use == and =, put the rule for == first.
# - sometimes there is more than one way to do something in the
#   lexer.  for example, can have a separate rule for each keyword
#   or catch them under a generic "ID" or "NAME" rule and use "token remapping"

# -----------------------------------------------------------------------------
# calc.py
# -----------------------------------------------------------------------------

# package isn't installed at the moment, so add the path so python can find it
import sys
slyPath = "/u1/h0/jkinne/public_html/cs420-s2019/code/sly-0.4"
sys.path.append(slyPath)


from sly import Lexer, Parser

class CalcLexer(Lexer):
    tokens = { NAME, NUMBER, PLUS, TIMES, MINUS, DIVIDE, ASSIGN, LPAREN, RPAREN,
               IF, THEN, WHILE, DO, PRINT, BEGIN, END}
    ignore = ' \t'
    ignore_comment = r'\#.*'

    # Tokens
    NAME = r'[a-zA-Z_][a-zA-Z0-9_]*'
    NUMBER = r'\d+'

    NAME['if'] = IF 
    NAME['then'] = THEN
    NAME['while'] = WHILE
    NAME['do'] = DO
    NAME['begin'] = BEGIN
    NAME['end'] = END
    NAME['print'] = PRINT

    # Special symbols
    PLUS = r'\+'
    MINUS = r'-'
    TIMES = r'\*'
    DIVIDE = r'/'
    ASSIGN = r'='
    LPAREN = r'\('
    RPAREN = r'\)'
    
    # Ignored pattern
    ignore_newline = r'\n+'

    # Extra action for newlines
    def ignore_newline(self, t):
        self.lineno += t.value.count('\n')

    def error(self, t):
        print("Illegal character '%s'" % t.value[0])
        self.index += 1

class CalcParser(Parser):
    tokens = CalcLexer.tokens

    precedence = (
        # ('left', IF, THEN), # note - if-then will be in sly-calc2.py
        ('left', PLUS, MINUS),
        ('left', TIMES, DIVIDE),
        ('right', UMINUS),
        )

    def __init__(self):
        self.names = { }
        self.prompt = True

    @_('expr')
    def statement(self, p):
        return ('statement-expr', p.expr)

    @_('BEGIN statement_list END')
    def statement(self, p):
        return ('statement-compound', p.statement_list)

    @_('statement statement_list')
    def statement_list(self, p):
        return ('statement-list', p.statement, p.statement_list)

    @_('')
    def statement_list(self, p):
        return ('statement-list-end')

    @_('NAME ASSIGN expr')
    def statement(self, p):
        return ('assign', p.NAME, p.expr)

    @_('IF expr THEN statement')
    def statement(self, p):
        return ('if-then', p.expr, p.statement)

    @_('WHILE expr DO statement')
    def statement(self, p):
        return ('while', p.expr, p.statement)

    @_('PRINT expr') # for printing, since we're not always printing the result any more
    def statement(self, p):
        return ('print', p.expr)

    @_('') # basically epsilon, for comment lines that don't parse to anything
    def statement(self, p):
        return
    
    @_('expr PLUS expr')
    def expr(self, p):
        return ('plus', p.expr0, p.expr1)

    @_('expr MINUS expr')
    def expr(self, p):
        return ('minus', p.expr0, p.expr1)

    @_('expr TIMES expr')
    def expr(self, p):
        return ('times', p.expr0, p.expr1)

    @_('expr DIVIDE expr')
    def expr(self, p):
        return ('divide', p.expr0, p.expr1)

    @_('MINUS expr %prec UMINUS')
    def expr(self, p):
        return ('uminus', p.expr)

    @_('LPAREN expr RPAREN')
    def expr(self, p):
        return ('paren', p.expr)

    @_('NUMBER')
    def expr(self, p):
        return ('number', p.NUMBER)

    @_('NAME')
    def expr(self, p):
        return ('name', p.NAME)

def evaluate(tree):
    global names

    if tree == None: return
    
    rule = tree[0]
    if rule == 'statement-expr':
        value = evaluate(tree[1])
        return value
    elif rule == 'assign':
        value = evaluate(tree[2])
        name = tree[1]
        names[name] = value
        return value
    elif rule == 'print':
        value = evaluate(tree[1])
        print(value)
        return value
    elif rule == 'times':
        return evaluate(tree[1]) * evaluate(tree[2])
    elif rule == 'plus':
        return evaluate(tree[1]) + evaluate(tree[2])
    elif rule == 'minus':
        return evaluate(tree[1]) - evaluate(tree[2])
    elif rule == 'divide':
        return evaluate(tree[1]) / evaluate(tree[2])
    elif rule == 'uminus':
        return -evaluate(tree[1])
    elif rule == 'number':
        return int(tree[1])
    elif rule == 'name':
        return names[tree[1]]
    elif rule == 'paren':
        return evaluate(tree[1])
    elif rule == 'if-then':
        value = evaluate(tree[1])
        if value:
            return evaluate(tree[2])
        else:
            return 0
    elif rule == 'while':
        while evaluate(tree[1]):
            evaluate(tree[2])
    
if __name__ == '__main__':
    lexer = CalcLexer()
    parser = CalcParser()
    names = {}
    text = sys.stdin.read()
    tree = parser.parse(lexer.tokenize(text))
    evaluate(tree)