#######################################################################
# Name: calc.py
# Purpose: Simple expression evaluator example
# Author: Igor R. Dejanovic <igor DOT dejanovic AT gmail DOT com>
# Copyright: (c) 2009-2014 Igor R. Dejanovic <igor DOT dejanovic AT gmail DOT com>
# License: MIT License
#
# This example demonstrates grammar definition using python constructs as
# well as using semantic actions to evaluate simple expression in infix
# notation.
#######################################################################

from arpeggio import Optional, ZeroOrMore, OneOrMore, EndOfFile, SemanticAction, ParserPython
from arpeggio.export import PMDOTExporter, PTDOTExporter
from arpeggio import RegExMatch as _

def number():     return _(r'\d*\.\d*|\d+')
def factor():     return Optional(["+","-"]), [number,
                          ("(", expression, ")")]
def term():       return factor, ZeroOrMore(["*","/"], factor)
def expression(): return term, ZeroOrMore(["+", "-"], term)
def calc():       return OneOrMore(expression), EndOfFile


# Semantic actions
class ToFloat(SemanticAction):
    """
    Converts node value to float.
    """
    def first_pass(self, parser, node, children):
        print("Converting {}.".format(node.value))
        return float(node.value)

class Factor(SemanticAction):
    """
    Removes parenthesis if exists and returns what was contained inside.
    """
    def first_pass(self, parser, node, children):
        print("Factor {}".format(children))
        if len(children) == 1:
            return children[0]
        sign = -1 if children[0] == '-' else 1
        next_chd = 0
        if children[0] in ['+', '-']:
            next_chd = 1
        if children[next_chd] == '(':
            return sign * children[next_chd+1]
        else:
            return sign * children[next_chd]

class Term(SemanticAction):
    """
    Divides or multiplies factors.
    Factor nodes will be already evaluated.
    """
    def first_pass(self, parser, node, children):
        print("Term {}".format(children))
        term = children[0]
        for i in range(2, len(children), 2):
            if children[i-1]=="*":
                term *= children[i]
            else:
                term /= children[i]
        print("Term = {}".format(term))
        return term

class Expr(SemanticAction):
    """
    Adds or substracts terms.
    Term nodes will be already evaluated.
    """
    def first_pass(self, parser, node, children):
        print("Expression {}".format(children))
        expr = 0
        start = 0
        # Check for unary + or - operator
        if str(children[0]) in "+-":
            start = 1

        for i in range(start, len(children), 2):
            if i and children[i-1]=="-":
                expr -= children[i]
            else:
                expr += children[i]

        print("Expression = {}".format(expr))
        return expr

class Calc(SemanticAction):
    def first_pass(self, parser, node, children):
        return children[0]

# Connecting rules with semantic actions
number.sem = ToFloat()
factor.sem = Factor()
term.sem = Term()
expression.sem = Expr()
calc.sem = Calc()

if __name__ == "__main__":

    # First we will make a parser - an instance of the calc parser model.
    # Parser model is given in the form of python constructs therefore we
    # are using ParserPython class.
    parser = ParserPython(calc)

    # Then we export it to a dot file in order to visualise it.
    # This step is optional but it is handy for debugging purposes.
    # We can make a png out of it using dot (part of graphviz) like this
    # dot -O -Tpng calc_parse_tree_model.dot
    PMDOTExporter().exportFile(parser.parser_model, "calc_parse_tree_model.dot")

    # An expression we want to evaluate
    input_expr = "-(4-1)*5+(2+4.67)+5.89/(.2+7)"

    # We create a parse tree out of textual input_expr
    parse_tree = parser.parse(input_expr)

    # Then we export it to a dot file in order to visualise it.
    # This is also optional.
    PTDOTExporter().exportFile(parse_tree, "calc_parse_tree.dot")

    # getASG will start semantic analysis.
    # In this case semantic analysis will evaluate expression and
    # returned value will be the result of the input_expr expression.
    print("{} = {}".format(input_expr, parser.getASG()))