#!/usr/bin/python
##
## (C) 2007, 2008 Muthiah Annamalai,
## Licensed under GPL Version 3
## 
## parser & AST  use the parsing frontend and 
## AST elements to build the parse tree.
## Class Parser

from math import *
import copy
import os
import sys
import string
import inspect

PYTHON3 = (sys.version[0] == '3')
if PYTHON3:
    str = str

## scanner for exprs language
from .scanner import Token, Lexeme, Lex

## exceptions
from .errors import RuntimeException, ParseException

## runtime elements
from .runtime import  Environment, BuiltinFunction, \
 BlindBuiltins, DebugUtils

## AST elements
from .ast import Expr, ExprCall, ExprList, Stmt, ReturnStmt, \
 BreakStmt, ContinueStmt, ElseStmt, IfStmt, WhileStmt, \
 ForStmt, AssignStmt, PrintStmt, EvalStmt, ArgList, \
 ValueList, Function, StmtList, Identifier, Number, \
 String

## Parser implementes the grammar for 'exprs' language.
## Entry point is parse(), after appropriate ctor-setup.
class Parser(DebugUtils):
    """ when you add new language feature, add a AST class 
    and its evaluate methods. Also add a parser method """
    def __init__(self,lexer,fcn_map, builtin_map, dbg = False):
        DebugUtils.__init__(self,dbg)
        self.parsing_function = False
        self.lex=lexer
        self.ast=None
        self.currently_parsing = [] # stack, just in case we should have parse errors
        self.function_map = fcn_map #parsed functions
        self.builtin_map = builtin_map #pointers to builtin functions
        self.if_stack = [] #parsing if-statements
        self.loop_stack = [] #parsing while-statements
        self.last_tok = None ## handle to last token dequeued
        self.inside_if = False
    
    def reset(self):
        """reset parser, and lexer, when stuff gets messed up"""
        self.inside_if = False
        self.loop_stack = []
        self.if_stack = []
        self.currently_parsing = []
        self.lex.reset()
        return
	
    def check_loop_stack(self):
        if ( len(self.loop_stack) == 0 ):
            raise ParseException(u"break/continue statement outside any loop, near" + str(self.last_token()));
        return len(self.loop_stack);

    def check_if_stack(self):
        if ( len(self.if_stack) == 0 ):
            raise ParseException(u"unmatched else statement, near" + str(self.last_token()))
        return len(self.if_stack)

    def last_token(self):
        return self.last_tok

    def peek(self):
        ptok = self.lex.peek()        
        self.dbg_msg(u"peek: " + str(ptok))
        return ptok

    def dequeue(self):
        tok = self.lex.dequeue()
        self.last_tok = tok
        self.dbg_msg( u"deqeue: " + str(tok) )
        return tok

    def match(self,kind):
        ## if match return token, else ParseException
        tok = self.dequeue()
        if ( tok.kind != kind ):
            raise ParseException(u"cannot find token "+ \
                                Token.get_name(kind) + u" got " \
                                + str(tok) + u" instead!")
        return tok

    def __repr__(self):
        rval =  u"[Interpreter: "
        rval = rval + u"[Functions["
        for k in list(self.function_map.keys()):
            rval = rval + u"\n "+ str(self.function_map[k]) 
        rval = rval + u"]] "+ str(self.ast) + u"]\n"
        return rval

    def warn_function_overrides( self, func_name ):
        ## used in defining user-functions to see
        ## if they shadow builtins.
        val =  ( func_name in self.function_map )
        if ( val ):
            raise Exception(u"ERROR: function %s is multiply defined"%(func_name))
        if func_name in self.builtin_map :
            raise Exception(u"ERROR: function %s overrides builtin"%(func_name))
        return val
    
    def parse(self):
        """ parser routine """
        self.ast = StmtList()
        while ( not self.lex.end_of_tokens() ):
            self.dbg_msg( "AST length = %d"%len(self.ast) )
            if ( self.lex.peek().kind == Token.DEF ):
                self.dbg_msg ( "parsing for function" )
                ## save function in a global table.
                func = self.function()
                self.warn_function_overrides(func.name)
                self.function_map[func.name]=func
            else:
                self.dbg_msg( "parsing for stmt" )
                st = self.stmt()
                if ( not self.parsing_function ):
                    self.ast.append(st)
        return self.ast


    def stmtlist(self):
        """ parse a bunch of statements """
        self.dbg_msg(" STMTLIST ")
        stlist = StmtList()
        while( not self.lex.end_of_tokens() ):
            self.dbg_msg("STMTLIST => STMT")
            ptok = self.peek()            
            if ( ptok.kind == Token.END ):
                break
            if ( not self.inside_if and 
                 ( ptok.kind == Token.ELSE
                   or ptok.kind == Token.ELSEIF ) ):
                break
            st = self.stmt()
            stlist.append( st )
        return stlist
    
    def stmt(self):
        """ try an assign, print, return, if or eval statement """
        self.dbg_msg(" STMT ")
        ptok = self.peek()
        self.dbg_msg("stmt: peeking at "+str(ptok))
        if ( ptok.kind == Token.RETURN ): 
            ## return <expression>
            ret_tok = self.dequeue()
            [l,c]=ret_tok.get_line_col();
            rstmt = ReturnStmt(self.expr(),l,c,self.debug)
            self.dbg_msg("return statement parsed")
            return rstmt
        elif ( ptok.kind == Token.PRINT ):
            ## print <expression>
            print_tok = self.dequeue()
            [l,c]=print_tok.get_line_col();
            return PrintStmt(self.exprlist(),l,c,self.debug)
        elif ( ptok.kind == Token.IF ):
            ## if <expression> stmtlist end
            if_tok = self.dequeue()
            [l,c]=if_tok.get_line_col();
            exp = self.expr()
            ifstmt = IfStmt( exp, None, None, l, c, self.debug)
            self.if_stack.append(ifstmt)
            body = self.stmtlist()
            ifstmt.set_body( body )
            ptok = self.peek()
            if ( ptok.kind in [Token.ELSEIF, Token.ELSE] ):
                self.inside_if = True
                next_stmt = self.stmtlist()
                self.inside_if = False
                ifstmt.append_stmt( next_stmt )
            self.match(Token.END)
            return ifstmt
        elif ( ptok.kind == Token.ELSEIF ):
            ## elseif <expression> stmtlist
            elseif_tok = self.dequeue()
            [l,c]=elseif_tok.get_line_col();
            self.check_if_stack()
            exp = self.expr()
            elseif_stmt = IfStmt( exp, None, None, l, c, self.debug )
            ifstmt = self.if_stack[-1]
            ifstmt.append_stmt( elseif_stmt )
            self.if_stack.pop()
            self.if_stack.append( elseif_stmt )
            body = self.stmtlist( )
            elseif_stmt.set_body ( body )
            return elseif_stmt
        elif ( ptok.kind == Token.ELSE ):
            ## else stmtlist
            self.check_if_stack()
            ifstmt = self.if_stack.pop()
            self.dbg_msg("stmt-else: ")
            else_tok = self.dequeue()
            [l,c]=else_tok.get_line_col()
            body = self.stmtlist()
            else_stmt = ElseStmt( body , l, c, self.debug)
            ifstmt.append_stmt( else_stmt )
            return else_stmt
        elif ( ptok.kind == Token.FOR ):
            ## Fixme : empty for loops not allowed.
            """ For ( exp1 ; exp2 ; exp3 ) stmtlist  end"""
            self.loop_stack.append(True)
            self.dbg_msg("for-statement")
            for_tok = self.dequeue()
            self.match(Token.LPAREN)

            lhs = self.expr()
            init = lhs 
            ptok = self.peek()
            if ( ptok.kind in Token.ASSIGNOP ):
                assign_tok = self.dequeue()
                [l,c]=assign_tok.get_line_col();
                rhs = self.expr()
                init = AssignStmt( lhs, assign_tok, rhs, l, c, self.debug)

            self.match(Token.COMMA )

            cond = self.expr();
            self.match(Token.COMMA )

            lhs = self.expr()
            update = lhs 
            ptok = self.peek()
            if ( ptok.kind in Token.ASSIGNOP ):
                assign_tok = self.dequeue()
                [l,c]=assign_tok.get_line_col()
                rhs = self.expr()
                update = AssignStmt( lhs, assign_tok, rhs, l, c, self.debug)

            
            self.match(Token.RPAREN);
            body = self.stmtlist( )
            self.match(Token.END)
            [l,c]= for_tok.get_line_col();
            forstmt = ForStmt(init, cond, update, body, l, c, self.debug);
            self.loop_stack.pop();
            return forstmt
        elif ( ptok.kind == Token.WHILE ):
            ## while ( expr ) body end
            self.loop_stack.append(True);
            self.dbg_msg("while-statement");
            while_tok = self.dequeue();
            [l,c]=while_tok.get_line_col()
            wexpr = self.expr();
            body = self.stmtlist( )
            self.match(Token.END)
            whilestmt = WhileStmt(wexpr, body, l, c, self.debug);
            self.loop_stack.pop();
            return whilestmt
        elif ( ptok.kind == Token.BREAK ):
            ## break, must be in loop-environment
            self.dbg_msg("break-statement");
            break_tok = self.dequeue();
            [l,c]=break_tok.get_line_col()
            self.check_loop_stack(); ##raises a parse error
            brkstmt = BreakStmt( l, c, self.debug);
            return brkstmt

        elif ( ptok.kind == Token.CONTINUE ):
            ## continue, must be in loop-environment
            self.dbg_msg("continue-statement");
            cont_tok = self.dequeue();
            [l,c]=cont_tok.get_line_col()
            self.check_loop_stack(); ##raises a parse error
            cntstmt = ContinueStmt( l, c, self.debug);
            return cntstmt

        else:
            ## lval := rval
            ptok = self.peek()
            [l,c] = ptok.get_line_col()
            lhs = self.expr()
            self.dbg_msg("parsing expr: "+str(lhs))
            ptok = self.peek()
            if ( ptok.kind in Token.ASSIGNOP ):
                assign_tok = self.dequeue()
                rhs = self.expr()
                [l,c]=assign_tok.get_line_col()
                return AssignStmt( lhs, assign_tok, rhs, l, c, self.debug)
            return EvalStmt( lhs, l, c, self.debug )
        raise ParseException("parsing Statement, unkown operators" + str(ptok))
    
    def function(self):
        """ def[kw] fname[id] (arglist) {body} end[kw] """
        if ( self.parsing_function ):
            raise ParseException(" Nested functions not allowed! ")

        self.parsing_function = True
        def_tok = self.dequeue()
        if ( def_tok.kind != Token.DEF ):
            raise ParseException("unmatched 'def'  in function " +str(def_tok))
        
        id_tok = self.dequeue()
        if ( id_tok.kind != Token.ID ):
            raise ParseException("expected identifier in function"+str(id_tok))
        
        arglist = self.arglist()
        self.dbg_msg( "finished parsing arglist" )
        body = self.stmtlist()

        self.match( Token.END )
        [l,c] = def_tok.get_line_col()
        fval = Function( id_tok.val, arglist, body, l, c, self.debug )
        self.parsing_function = False
        self.dbg_msg( "finished parsing function" ) 
        return fval

    def valuelist(self):
        """parse: ( expr_1 , expr_2, ... ) """
        valueList = list()
        self.dbg_msg("valuelist: ")
        lparen_tok = self.match( Token.LPAREN )
        while ( self.peek().kind != Token.RPAREN ):
            val = self.expr()
            self.dbg_msg("valuelist-expr: "+str(val))
            valueList.append( val )
            ptok = self.peek()
            if  ( ptok.kind == Token.RPAREN ):
                break
            elif ( ptok.kind == Token.COMMA ):
                self.match( Token.COMMA )
            else:
                raise ParseException(" function call argument list "+str(ptok))
        self.match( Token.RPAREN )
        [l,c] = lparen_tok.get_line_col()
        return ValueList(valueList, l, c, self.debug )


    def arglist(self):
        """parse: ( arg_1, arg_2, ... ) """
        self.dbg_msg( " ARGLIST " )
        args = list()
        lparen_tok = self.match( Token.LPAREN )
        while ( self.peek().kind != Token.RPAREN ):
            arg_name = self.dequeue()
            args.append( arg_name.val )
            ptok = self.peek()
            if  ( ptok.kind == Token.RPAREN ):
                break
            elif ( ptok.kind == Token.COMMA ):
                self.match( Token.COMMA )
            else:
                raise ParseException(" function definition argument list "
                                     +str(ptok))
        self.match( Token.RPAREN )
        [l,c] = lparen_tok.get_line_col()
        return ArgList(args , l, c, self.debug )
        
    def exprlist(self):
        """   EXPRLIST : EXPR, EXPRLIST        
        ##  EXPRLIST : EXPR """
        self.dbg_msg( " EXPRLIST " )
        exprs=[]
        comma_tok = None
        l = 0; c = 0
        while ( not self.lex.end_of_tokens() ):
            exprs.append(self.expr())
            if self.lex.peek().kind != Token.COMMA:
                break            
            tok = self.match(Token.COMMA)
            if ( not comma_tok ):
                comma_tok = tok 

        if ( comma_tok ):
            [l,c] = comma_tok.get_line_col()
        self.dbg_msg("finished expression list")
        return ExprList(exprs, l, c, self.debug)


    def expr(self):
        self.dbg_msg( " EXPR " )
        val1=self.term()
        res=val1
        ptok = self.peek()
        if ptok.kind in Token.ADDSUB:
            binop=self.dequeue()
            val2=self.expr()
            [l,c] = binop.get_line_col()
            res=Expr(val1,binop,val2, l, c, self.debug )
        elif ptok.kind == Token.LPAREN:
            ## function call -OR- array type.
            if ( res.__class__ != Identifier ):
                raise ParseException("invalid function call"+str(ptok))
            [l,c] = ptok.get_line_col()
            vallist = self.valuelist()
            res=ExprCall( res, vallist, l, c, self.debug )
        return res

    def term(self):
        """ this is a grammar abstraction; 
        but AST only has Expr elements"""
        self.dbg_msg( "term" )
        val1=self.factor()
        res=val1

        tok = self.peek()
        if ( tok.kind in Token.MULDIV 
             or  tok.kind in Token.COMPARE 
             or tok.kind in Token.EXPMOD  ):
            binop=self.dequeue()
            val2=self.expr()
            [l,c] = binop.get_line_col()
            res=Expr(val1,binop,val2, l, c, self.debug)

        return res
    
    def factor(self):
        self.dbg_msg( "factor" )
        tok=self.peek()
        if tok.kind == Token.LPAREN:
            lparen_tok = self.dequeue()
            val=self.expr()
            if self.dequeue().kind!=Token.RPAREN:
                raise SyntaxError("Missing Parens")
        elif tok.kind == Token.NUMBER:
            tok_num = self.dequeue()
            [l, c] = tok_num.get_line_col()
            val = Number( tok.val , l, c, self.debug )
        elif tok.kind == Token.ID:
            tok_id = self.dequeue()
            [l, c] = tok_id.get_line_col()
            val = Identifier( tok.val , l, c, self.debug )
            ptok = self.peek()
            self.dbg_msg("factor: "+str(ptok) + " / "+str(tok) )
            if ( ptok.kind == Token.LPAREN ):
                ## function call
                [l, c] = ptok.get_line_col()
                vallist = self.valuelist()
                val=ExprCall( val, vallist, l, c, self.debug )
            elif ( ptok.kind == Token.LSQRBRACE ):
                ## array type
                val=None
                raise ParseException("arrays not implemented"+str(ptok));
        elif tok.kind == Token.STRING :
            str_tok = self.dequeue()
            [l,c] = str_tok.get_line_col()
            val = String( tok.val , l, c, self.debug )
        else:
            raise ParseException("Expected Number, found something "+str(tok))
        
        self.dbg_msg( "factor-returning: "+str(val) )
        return val
