#!/usr/bin/python3
import ply.yacc as yacc
import sys
import lexer
from myast import *

class Parser(object):
    """parser = miniCParser()
       parser.parse(data, lexer)
       Parses the data with a lexer according to the miniC grammar. Outputs a AST"""
    def __init__(self):
        super().__init__()
        self.lexer = lexer.Lexer()
        self.tokens = self.lexer.get_tokens()
        self.parser = yacc.yacc(module=self)
        self.b_error = False

    def parse(self, data):
        return self.parser.parse(data, lexer=self.lexer, debug=0)

    def parse_file(self, filename):
        try:
            f = open(filename, 'r')
            data = f.read()
            return self.parse(data)
        except OSError as err:
            print("OS error: {0}".format(err))
        except:
            print("Unexpected error:", sys.exc_info()[0])
            raise

    start = 'program'

    precedence = (
    ('right', 'ASSIGN'),
    ('right', 'ELSE'),
    ('left', 'EQOP'),
    ('left', 'RELOP'),
    ('left', '+', 'MINUS'),
    ('left', '*', '/'),
    ('right', 'UMINUS'),
    ('left', 'CALL'),
    )
    def p_program(self, p):
        '''program : declList funList
                   | declList
                   | funList
                   | '''

        if len(p)==3:
            p[0] = ASTProgram('program',children=[p[1], p[2]])
        elif len(p) == 2 and p[1].node_type == 'declList':
            p[0] = ASTProgram('program',children=[p[1], None])
        elif len(p) == 2 and p[1].node_type == 'funList':
            p[0] = ASTProgram('program',children=[None, p[1]])
        else:
            p[0] = ASTProgram('program', children =[None, None])

    def p_declList(self, p):
        '''declList : declList declaration
                    | declaration'''
        if len(p) == 3:
            p[1].declList.append(p[2])
            p[0] = p[1]
        else:
            p[0] = ASTDeclList('declList', children = [p[1]])

    def p_funList(self, p):
        '''funList : funList function
                    | function'''
        if len(p) == 3:
            p[1].funList.append(p[2])
            p[0] = p[1]
        else:
            p[0] = ASTFunList('funList', children = [p[1]])

    def p_declaration(self, p):
        """declaration : type identList ';'
                       | type identList error"""
        p[0] = ASTDeclaration('declaration', children = [p[1],p[2]])

    def p_identList(self, p):
        '''identList : identList ',' identifier
                     | identifier'''
        if len(p) == 4:
            p[1].identList.append(p[3])
            p[0] = p[1]
        else:
            p[0] = ASTIdentList('identList', children = [p[1]])

    def p_identifier(self, p):
        '''identifier : ID '[' INTNUM ']'
                      | ID'''
        newint = None
        if len(p) == 5: # Set Listsize if necessary
            newint = ASTINTNUM('id', value = p[3], lineno = p.lineno(3), lexpos = p.lexpos(3))
        p[0] = ASTIdentifier('identifier', children = [p[1], newint], lineno = p.lineno(1), lexpos = p.lexpos(1))

    def p_function(self, p):
        '''function : type ID '(' ')' compoundStmt
                    | type ID '(' paramList ')' compoundStmt
                    | ID '(' ')' compoundStmt
                    | ID '(' paramList ')' compoundStmt
                    '''
        if len(p) == 5: #ID '(' ')' compoundStmt
            p[0] = ASTFunction('function', children = [None, p[1], None, p[4]], lineno = p.lineno(1), lexpos = p.lexpos(1))
        elif len(p) == 6 and type(p[1]) != str: #type ID '(' ')' compoundStmt
            p[0] = ASTFunction('function', children = [p[1], p[2], None, p[5]], lineno = p.lineno(2), lexpos = p.lexpos(2))
        elif len(p) == 6 and type(p[1]) == str: #ID '(' paramList ')' compoundStmt
            p[0] = ASTFunction('function', children = [None, p[1], p[3], p[5]], lineno = p.lineno(1), lexpos = p.lexpos(1))
        else: #type ID '(' paramList ')' compoundStmt
            p[0] = ASTFunction('function', children = [p[1], p[2], p[4], p[6]], lineno = p.lineno(2), lexpos = p.lexpos(2))

    def p_paramList(self, p):
        '''paramList : paramList ',' type identifier
                     | type identifier'''
        if len(p) == 5:
            p[1].paramList.append(ASTParam('param', children = [p[3], p[4]]))
            p[0] = p[1]
        else:
            newparam = ASTParam('param', children = [p[1], p[2]])
            p[0] = ASTParamList('paramList', children = [newparam])

    def p_printf(self,p):
        '''printf : PRINTF '(' expr ')' '''
        p[0] = ASTCallStmt('callStmt', children = [ASTCall('call', children = ["printf", ASTArgList('argList',children = [p[3]])], lineno = p.lineno(1), lexpos = p.lexpos(1))])

    def p_scanf(self,p):
        '''scanf : SCANF '(' ID ')'
                 | SCANF '(' ID '[' expr ']' ')'
        '''
        if len(p) == 5:
            ref = ASTReference('reference', children = [p[3], None], lineno = p.lineno(3), lexpos = p.lexpos(3) )
        else:
            ref = ASTReference('reference', children = [p[3], p[5]], lineno = p.lineno(3), lexpos = p.lexpos(3) )
        p[0] = ASTCallStmt('callStmt', children = [ASTCall('call', children = ["scanf", ASTArgList('argList',children = [ref] )] , lineno = p.lineno(1), lexpos = p.lexpos(1))])

    def p_type_typeint(self, p):
        '''type : TYPEINT'''
        p[0] = ASTTYPE('type', value = int, lineno = p.lineno(1), lexpos = p.lexpos(1))

    def p_type_typefloat(self, p):
        '''type : TYPEFLOAT'''
        p[0] = ASTTYPE('type', value = float, lineno = p.lineno(1), lexpos = p.lexpos(1))

    def p_compoundStmt(self, p):
        """compoundStmt : '{' declList stmtList '}'
                        | '{' stmtList '}'
                        | '{' declList '}'
                        | '{' '}'"""
        if len(p) == 5:
            p[0] = ASTCompoundStmt('compoundStmt', children = [p[2], p[3]])
        elif len(p) == 3:
            p[0] = ASTCompoundStmt('compoundStmt', children = [None, None])
        elif isinstance(p[2], ASTStmtList):
            p[0] = ASTCompoundStmt('compoundStmt', children = [None, p[2]])
        else:
            p[0] = ASTCompoundStmt('compoundStmt', children = [p[2], None])

    def p_stmtList(self, p):
        """stmtList : stmtList stmt
                    | stmt """
        if len(p) == 3:
            if p[1] != None:
                p[1].stmtList.append(p[2])
                p[0] = p[1]
        elif len(p) == 2:
            p[0] = ASTStmtList('stmtList', children = [p[1]])

    def p_stmt(self, p):
        """stmt : assign ';'
                | callStmt
                | returnStmt
                | whileStmt
                | forStmt
                | ifStmt
                | switchStmt
                | compoundStmt
                | printf ';'
                | scanf ';'
                | ';'"""
        if p[1] == ';':
            p[0] = None
        else:
            p[0] = p[1]

    def p_assign(self, p):
        """assign : ID '[' expr ']' '=' expr %prec ASSIGN
                  | ID '=' expr %prec ASSIGN"""
        if p[2] == '[':
            p[0] = ASTAssign('assign', children = [ASTID("id", value = p[1], lineno = p.lineno(1), lexpos = p.lexpos(1)), p[3], p[6]], lineno = p.lineno(5), lexpos = p.lexpos(5))
        else:
            p[0] = ASTAssign('assign', children = [ASTID("id", value = p[1], lineno = p.lineno(1), lexpos = p.lexpos(1)), None, p[3]], lineno = p.lineno(2), lexpos = p.lexpos(2))

    def p_call_stmt(self, p):
        """callStmt : call ';'"""
        p[0] = ASTCallStmt('callStmt', children = [p[1]])

    def p_call(self, p):
        """call : ID '(' argList ')'
                | ID '(' ')'"""
        if len(p) == 5:
            p[0] = ASTCall('call', children = [p[1], p[3]], lineno = p.lineno(1), lexpos = p.lexpos(1))
        else:
            p[0] = ASTCall('call', children = [p[1], None], lineno = p.lineno(1), lexpos = p.lexpos(1))

    def p_returnStmt(self, p):
        """returnStmt : RETURN expr ';'
                      | RETURN ';'"""
        if len(p) == 4:
            p[0] = ASTReturnStmt('returnStmt', children = [p[2]], lineno = p.lineno(1), lexpos = p.lexpos(1))
        else:
            p[0] = ASTReturnStmt('returnStmt', children = [None], lineno = p.lineno(1), lexpos = p.lexpos(1))

    def p_whileStmt(self, p):
        """whileStmt : WHILE '(' expr ')' stmt
                     | DO stmt WHILE '(' expr ')' ';'"""
        if p[1] == 'while':
            p[0] = ASTWhileStmt('while', value = False, children = [p[3],p[5]], lineno = p.lineno(1), lexpos = p.lexpos(1))
        else:
            p[0] = ASTWhileStmt('while', value = True, children = [p[5],p[2]], lineno = p.lineno(1), lexpos = p.lexpos(1))

    def p_forStmt(self, p):
        "forStmt : FOR '(' assign ';' expr ';' assign ')' stmt"
        p[0] = ASTForStmt('forStmt', children = [p[3], p[5], p[7], p[9]], lineno = p.lineno(1), lexpos = p.lexpos(1))

    def p_ifStmt(self, p):
        """ifStmt : IF '(' expr ')' stmt ELSE stmt
                  | IF '(' expr ')' stmt"""
        if len(p) == 8:
            p[0] = ASTIfStmt('ifStmt', children = [p[3], p[5], p[7]], lineno = p.lineno(1), lexpos = p.lexpos(1))
        else:
            p[0] = ASTIfStmt('ifStmt', children = [p[3], p[5], None], lineno = p.lineno(1), lexpos = p.lexpos(1))

    def p_switchStmt(self, p):
        "switchStmt : SWITCH '(' identifier ')' '{' cases '}'"
        p[0] = ASTSwitchStmt('switch', children = [p[3], p[6]])

    def p_cases(self, p):
        """cases : caseList default
                 | default
                 | caseList
                 |"""
        if len(p) == 3:
            p[1].children.append(p[2])
            #Last case is the default case
            p[1].value = True
            p[0] = p[1]
        elif len(p) == 1: # No cases
            p[0] = ASTCaseList('caseList', children = [])
        elif p[1].node_type == 'case': # If the case is a default case
            l_case = ASTCaseList('caseList', children = [p[1]])
            l_case.value = True
            p[0] = l_case
        elif p[1].node_type == 'caseList': # case is not a default case
            p[0] = p[1]

    def p_default(self, p):
        """default : DEFAULT ':' stmtList BREAK ';'
                   | DEFAULT ':' stmtList"""
        if len(p) == 6:
            p[0] = ASTCase('case', children = [None, p[3], True])
        else:
            p[0] = ASTCase('case', children = [None, p[3], False])

    def p_default_empty(self, p):
        """default : DEFAULT ':' BREAK ';'
                   | DEFAULT ':'"""
        if len(p) == 5:
            p[0] = ASTCase('case', children = [None, ASTStmtList('stmtList', children = []), True])
        else:
            p[0] = ASTCase('case', children = [None, ASTStmtList('stmtList', children = []), False])

    def p_caseList(self, p):
        '''caseList : caseList case
                    | case'''
        if len(p) == 3:
            p[1].children.append(p[2])
            p[0] = p[1]
        else:
            p[0] = ASTCaseList('caseList', children = [p[1]])

    def p_case(self, p):
        """case : CASE INTNUM ':' stmtList BREAK ';'
                | CASE INTNUM ':' stmtList """
        if len(p) == 7:
            p[0] = ASTCase('case', children = [p[2], p[4], True])
        elif len(p) == 5:
            p[0] = ASTCase('case', children = [p[2], p[4], False])

    def p_case_empty(self, p):
        """case : CASE INTNUM ':' BREAK ';'
                | CASE INTNUM ':' """
        if len(p) == 6:
            p[0] = ASTCase('case', children = [p[2], ASTStmtList('stmtList', children = []), True])
        else:
            p[0] = ASTCase('case', children = [p[2], ASTStmtList('stmtList', children = []), False])

    def p_expr_binOp(self, p):
        """expr : expr '*' expr
                | expr '/' expr
                | expr '+' expr
                | expr MINUS expr
                | expr RELOP expr
                | expr EQOP expr"""
        p[0] = ASTBinOp('binOp', children = [p[2], p[1], p[3]], lineno = p.lineno(2), lexpos = p.lexpos(2))

    def p_expr_unOp(self, p):
        """expr : MINUS expr %prec UMINUS"""
        p[0] = ASTUnOp('unOp', children = [p[1], p[2]], lineno = p.lineno(1), lexpos = p.lexpos(1))

    def p_expr_call(self, p):
        """expr : call %prec CALL"""
        p[0] = p[1]

    def p_expr_intNum(self, p):
        """expr : INTNUM"""
        p[0] = ASTINTNUM('intNum', value = p[1], lineno = p.lineno(1), lexpos = p.lexpos(1))

    def p_expr_floatNum(self, p):
        """expr : FLOATNUM"""
        p[0] = ASTFLOATNUM('floatNum', value = p[1], lineno = p.lineno(1), lexpos = p.lexpos(1))

    def p_expr_id(self, p):
        """expr : ID"""
        p[0] = ASTID('id', value = p[1], lineno = p.lineno(1), lexpos = p.lexpos(1))

    def p_expr_list_deref(self, p):
        """expr : ID '[' expr ']'"""
        p[0] = ASTListDeref('listDeref', children = [ASTID("id", value = p[1], lineno = p.lineno(1), lexpos = p.lexpos(1)), p[3]], lineno = p.lineno(2), lexpos = p.lexpos(2))

    def p_expr_paren(self, p):
        """expr : '(' expr ')'"""
        p[0] = p[2]

    def p_argList(self, p):
        """argList : argList ',' expr
                   | expr"""
        if len(p) == 4:
            p[1].children.append(p[3])
            p[0] = p[1]
        else:
            p[0] = ASTArgList('argList', children = [p[1]])

    def p_error(self, p):
        if p == None:
            print ("ERROR: unexpected EOF")
        print("ERROR: syntax error in line %d, column %d" % (p.lineno, lexer.Lexer.find_column(p.lexpos)))
        self.b_error = True

def main(filename):
    try:
        parser = Parser()
        print(parser.parse_file(filename))
    except:
        print("Unexpected error:", sys.exc_info()[0])
        raise

if __name__ == '__main__':
    main(sys.argv[1])
