# Flexinode expression parser.
#
# Copyright (C) 2009 David Vrabel
#
import ply.lex as lex
import ply.yacc as yacc

class flexiexpr_error:
    def __init__(self, expr, err):
        self.str = "'" + expr.variable + "' in " + expr.fnode.path() + ": " + err

class flexiexpr_lexer:
    tokens = (
        'NUMBER',
        'PLUS',
        'MINUS',
        'MULT',
        'DIVIDE',
        'LPAREN',
        'RPAREN',
        'VARIABLE',
        )

    t_PLUS    = r'\+'
    t_MINUS   = r'-'
    t_MULT    = r'\*'
    t_DIVIDE  = r'/'
    t_LPAREN  = r'\('
    t_RPAREN  = r'\)'

    def t_NUMBER(self, t):
        r'\d+'
        t.value = int(t.value)    
        return t

    def t_VARIABLE(self, t):
        r'[a-zA-Z_][a-zA-Z0-9_]*'
        return t

    t_ignore  = ' \t'

    def t_error(self, t):
        raise flexiexpr_error(self.fexpr, "invalid character '" + t.value + "'")

    def __init__(self, fexpr):
        self. fexpr = fexpr
        self.lexer = lex.lex(module=self)

class flexiexpr_parser:
    tokens = flexiexpr_lexer.tokens

    precedence = (
        ('left', 'PLUS', 'MINUS'),
        ('left', 'MULT', 'DIVIDE'),
        ('right', 'UMINUS'),
        )

    def p_expression_plus(self, p):
        'expression : expression PLUS expression'
        p[0] = p[1] + p[3]

    def p_expression_minus(self, p):
        'expression : expression MINUS expression'
        p[0] = p[1] - p[3]

    def p_expression_mult(self, p):
        'expression : expression MULT expression'
        p[0] = p[1] * p[3]

    def p_expression_div(self, p):
        'expression : expression DIVIDE expression'
        if p[3] == 0:
            raise flexiexpr_error(self.fexpr, "division by zero")
        p[0] = p[1] / p[3]

    def p_expression_uminus(self, p):
        'expression : MINUS expression %prec UMINUS'
        p[0] = -p[2]

    def p_expression_paren(self, p):
        'expression : LPAREN expression RPAREN'
        p[0] = p[2]

    def p_expression_num(self, p):
        'expression : NUMBER'
        p[0] = p[1]

    def p_expression_var(self, p):
        'expression : VARIABLE'
        var = p[1]
        expr = self.fexpr.fnode.find_expr(var)
        if not expr:
            raise flexiexpr_error(self.fexpr, "'" + var + "' not found")
        if expr.parser.parsing:
            raise flexiexpr_error(self.fexpr, "circular reference to '" + var + "'")
        p[0] = expr.eval()

    def p_error(self, p):
        raise flexiexpr_error(self.fexpr, "syntax error")

    def __init__(self, fexpr):
        self.fexpr = fexpr
        self.parsing = False

        self.lexer = flexiexpr_lexer(fexpr)
        self.parser = yacc.yacc(module=self)

    def parse(self, text):
        self.parsing = True
        try:
            return self.parser.parse(text, lexer=self.lexer.lexer)
        finally:
            self.parsing = False
