#!/usr/bin/env python
#
# Simple Ply-based parser for IMP
#

import sys, os
import ast
import ply.yacc
from impLexer import *

# By default, the first rule found in a 
# yacc specification defines the starting grammar rule (top level rule).

## -------------------
# program ::= stmt_list EOF
def p_program(p):
    'program : stmt_list'
    p[0] = p[1]
    
## -------------------
# stmt_list ::= 
#    | { stmt_list stmt }

def p_stmt_list_empty(p):
    'stmt_list : '
    p[0] = []

def p_stmt_list_1(p):
    'stmt_list : stmt'
    p[0] = [p[1]]
    
def p_stmt_list_2(p):
    'stmt_list : stmt_list stmt'
    p[1].append(p[2])
    p[0] = p[1]
    
## ----------------------
# block ::= { stmt_list }

def p_block(p):
    'block : LBRACE stmt_list RBRACE'
    p[0] = p[2]
    
## ---------------
# stmt ::= assignstmt
#    | ifstmt
#    | whilestmt

def p_stmt(p):
    '''stmt : assignstmt
            | ifstmt
            | whilestmt
    '''
    p[0] = p[1]

## -------------------
# whilestmt ::= while (expression) block

def p_whilestmt(p):
    'whilestmt : WHILE LPAREN expression RPAREN block'
    p[0] = ast.WhileStmt(p[3], p[5], p.lineno(1))

## -------------------
# ifstmt ::= if (expression) block else block

def p_ifstmt(p):
    'ifstmt : IF LPAREN expression RPAREN block ELSE block'
    p[0] = ast.IfStmt(p[3], p[5], p[7], p.lineno(1))
    
## -------------------
# assignstmt ::= identifier = expression;
def p_assignstmt(p):
    'assignstmt : ID EQUALS expression SEMI'
    p[0] = ast.AssignStmt(p[1], p[3], p.lineno(1))
    
##-------------------------------------------------------------------
# Expressions
#

## -------------------
# binaryop ::= - | * | && | <=
def p_binaryop(p):
    '''binaryop : MINUS 
    	| TIMES 
    	| PLUS
    	| DIVIDE
		| AND 
		| LE
		| GE
		| MOD
		| EQ
    '''
    p[0] = p[1]

## -------------------
# unaryop ::= ! 
def p_unaryop(p):
    'unaryop : NOT '
    p[0] = p[1]

## -------------------
# expression ::= number
#    | identifier
#    | expression binaryop expression
#    | unaryop expression
def p_expression_1(p):
    'expression : ICONST'
    val = int(p[1])
    p[0] = ast.IntLiteral(val, p.lineno(1))

def p_expression_2(p):
    'expression : ID'
    p[0] = ast.Identifier(p[1], p.lineno(1))

def p_expression_3(p):
    'expression : expression binaryop expression'
    if p[2] == '-':
        p[0] = ast.BinOpExp(p[1], p[3], ast.BinOpExp.SUB, p.lineno(1))
    elif p[2] == '*':
        p[0] = ast.BinOpExp(p[1], p[3], ast.BinOpExp.MUL, p.lineno(1))
    elif p[2] == '&&':
        p[0] = ast.BinOpExp(p[1], p[3], ast.BinOpExp.AND, p.lineno(1))
    elif p[2] == '<=':
        p[0] = ast.BinOpExp(p[1], p[3], ast.BinOpExp.LE, p.lineno(1))
    elif p[2] == '>=':
        p[0] = ast.BinOpExp(p[1], p[3], ast.BinOpExp.GE, p.lineno(1))
    elif p[2] == '+':
        p[0] = ast.BinOpExp(p[1], p[3], ast.BinOpExp.PLUS, p.lineno(1))
    elif p[2] == '/':
        p[0] = ast.BinOpExp(p[1], p[3], ast.BinOpExp.DIVIDE, p.lineno(1))
    elif p[2] == '%':
        p[0] = ast.BinOpExp(p[1], p[3], ast.BinOpExp.MOD, p.lineno(1))
    elif p[2] == '==':
        p[0] = ast.BinOpExp(p[1], p[3], ast.BinOpExp.EQ, p.lineno(1))    
    else:
        line,col = find_column(p.lexer.lexdata,p)
        print 'IMP Parser error: Unrecognized binary operator %s at %s,%s' \
            % (p[2],line,col)
        exit(1)

def p_expression_4(p):
    'expression : unaryop expression'
    if p[1] == '!':
        p[0] = ast.UnaryExp(p[2], ast.UnaryExp.NOT, p.lineno(1))
    else:
        line,col = find_column(p.lexer.lexdata,p)
        print 'IMP Parser error: Unrecognized unary operator %s at %s,%s' \
            % (p[2],line,col)
        exit(1)

# grammatical error
def p_error(p):
    line,col = find_column(p.lexer.lexdata,p)
    pos = (col-1)*' '
    print "IMP parser error: unexpected symbol '%s' at line %s, column %s:\n\t%s\n\t%s^" \
        % (p.value, p.lexer.lineno, col, line, pos)
   
# Helper method to compute the line and column numbers
#     inputdata is the input text string
#     token is a token instance
def find_column(inputdata,token):
    i = token.lexpos
    startline = inputdata[:i].rfind('\n')
    endline = startline + inputdata[startline+1:].find('\n') 
    line = inputdata[startline+1:endline+1]
    while i > 0:
        if inputdata[i] == '\n': break
        i -= 1
    column = (token.lexpos - i)
    return line, column
 
#------------------------------------------------

# Build the grammar
if __name__ == "__main__":
    outputFilename = "impParser_output.txt"
    sourceFilename = None
    if len(sys.argv) > 1: sourceFilename = sys.argv[1]
    if not sourceFilename or not os.path.exists(sourceFilename):
        print "Usage: impParser filename.imp"
        exit(1)
        
    #impParser = ply.yacc.yacc(method='SLR', debug=1, optimize=0, write_tables=1)
    impParser = ply.yacc.yacc(debug=1, optimize=0, write_tables=0)
    stmtlist = impParser.parse(open(sourceFilename).read(),lexer=lexer)   # Test it
    print stmtlist
    
    # Interpret the statements we just parsed
    for statement in stmtlist:
        statement.eval()
        
    # Print the final result (symbol table)
    print ast.symtab


