#!/usr/bin/env python
"""
A more advanced calculator example, with variable storage and scientific
functions (courtesy of python 'math' module)
"""
import sys, math, readline

from bison import BisonParser, BisonNode, BisonError

class Parser(BisonParser):
    """
    Implements the calculator parser. Grammar rules are defined in the method docstrings.
    Scanner rules are in the 'lexscript' attribute.
    """
    # ----------------------------------------------------------------
    # lexer tokens - these must match those in your lex script (below)
    # ----------------------------------------------------------------
    tokens = ['NUMBER',
              'PLUS', 'MINUS', 'TIMES', 'DIVIDE', 'MOD', 'POW',
              'LPAREN', 'RPAREN',
              'NEWLINE', 'QUIT',
              'EQUALS', 'PI', 'E',
              'IDENTIFIER',
              'HELP']
    
    # ------------------------------
    # precedences
    # ------------------------------
    precedences = (
        ('left', ('MINUS', 'PLUS')),
        ('left', ('TIMES', 'DIVIDE', 'MOD')),
        ('left', ('NEG', )),
        ('right', ('POW', )),
        )
    
    # --------------------------------------------
    # basename of binary parser engine dynamic lib
    # --------------------------------------------
    bisonEngineLibName = "calc1-engine"

    # ------------------------------------------------------------------
    # override default read method with a version that prompts for input
    # ------------------------------------------------------------------
    def read(self, nbytes):
        try:
            return raw_input("> ") + "\n"
        except EOFError:
            return ''

    # -----------------------------------------------------------
    # override default run method to set up our variables storage
    # -----------------------------------------------------------
    def run(self, *args, **kw):
        self.vars = {}
        BisonParser.run(self, *args, **kw)

    # ---------------------------------------------------------------
    # These methods are the python handlers for the bison targets.
    # (which get called by the bison code each time the corresponding
    # parse target is unambiguously reached)
    #
    # WARNING - don't touch the method docstrings unless you know what
    # you are doing - they are in bison rule syntax, and are passed
    # verbatim to bison to build the parser engine library.
    # ---------------------------------------------------------------
    
    # Declare the start target here (by name)
    start = "input"
    
    def on_input(self, target, option, names, values):
        """
        input :
              | input line
        """
        if option == 1:
            return values[0]

    def on_line(self, target, option, names, values):
        """
        line : NEWLINE
             | exp NEWLINE
             | IDENTIFIER EQUALS exp NEWLINE
             | HELP
             | error
        """
        if option == 1:
            print values[0]
            return values[0]
        elif option == 2:
            self.vars[values[0]] = values[2]
            return values[2]
        elif option == 3:
            self.show_help()
        elif option == 4:
            line, msg, near = self.lasterror
            print "Line %s: \"%s\" near %s" % (line, msg, repr(near))

    def on_exp(self, target, option, names, values):
        """
        exp : number | plusexp | minusexp | timesexp | divexp       | modexp
            | negexp | powexp  | parenexp | varexp   | functioncall | constant
        """
        return values[0]

    def on_number(self, target, option, names, values):
        """
        number : NUMBER
        """
        return float(values[0])

    def on_plusexp(self, target, option, names, values):
        """
        plusexp : exp PLUS exp
        """
        return values[0] + values[2]

    def on_minusexp(self, target, option, names, values):
        """
        minusexp : exp MINUS exp
        """
        return values[0] - values[2]

    def on_timesexp(self, target, option, names, values):
        """
        timesexp : exp TIMES exp
        """
        return values[0] * values[2]

    def on_divexp(self, target, option, names, values):
        """
        divexp : exp DIVIDE exp
        """
        try:
            return values[0] / values[2]
        except:
            return self.error("Division by zero error")

    def on_modexp(self, target, option, names, values):
        """
        modexp : exp MOD exp
        """
        try:
            return values[0] % values[2]
        except:
            return self.error("Modulus by zero error")

    def on_powexp(self, target, option, names, values):
        """
        powexp : exp POW exp
        """
        return values[0] ** values[2]

    def on_negexp(self, target, option, names, values):
        """
        negexp : MINUS exp %prec NEG
        """
        return values[1]

    def on_parenexp(self, target, option, names, values):
        """
        parenexp : LPAREN exp RPAREN
        """
        return values[1]

    def on_varexp(self, target, option, names, values):
        """
        varexp : IDENTIFIER
        """
        if self.vars.has_key(values[0]):
            return self.vars[values[0]]
        else:
            return self.error("No such variable '%s'" % values[0])

    def on_functioncall(self, target, option, names, values):
        """
        functioncall : IDENTIFIER LPAREN exp RPAREN
        """
        func = getattr(math, values[0], None)
        if not callable(func):
            return self.error("No such function '%s'" % values[0])
        try:
            return func(values[2])
        except Exception, e:
            return self.error(e.args[0])

    def on_constant(self, target, option, names, values):
        """
        constant : PI
                 | E
        """
        return getattr(math, values[0])

    # -----------------------------------------
    # Display help
    # -----------------------------------------
    def show_help(self):
        print "This PyBison parser implements a basic scientific calculator"
        print " * scientific notation now works for numbers, eg '2.3e+12'"
        print " * you can assign values to variables, eg 'x = 23.2'"
        print " * the constants 'pi' and 'e' are supported"
        print " * all the python 'math' module functions are available, eg 'sin(pi/6)'"
        print " * errors, such as division by zero, are now reported"

    # -----------------------------------------
    # raw lex script, verbatim here
    # -----------------------------------------
    lexscript = r"""
    %{
    int yylineno = 0;
    #include <stdio.h>
    #include <string.h>
    #include "Python.h"
    #define YYSTYPE void *
    #include "tokens.h"
    extern void *py_parser;
    extern void (*py_input)(PyObject *parser, char *buf, int *result, int max_size);
    #define returntoken(tok) yylval = PyString_FromString(strdup(yytext)); return (tok);
    #define YY_INPUT(buf,result,max_size) { (*py_input)(py_parser, buf, &result, max_size); }
    %}
    
    %%
    
    ([0-9]*\.?)([0-9]+)(e[-+]?[0-9]+)? { returntoken(NUMBER); }
    ([0-9]+)(\.?[0-9]*)(e[-+]?[0-9]+)? { returntoken(NUMBER); }
    "("    { returntoken(LPAREN); }
    ")"    { returntoken(RPAREN); }
    "+"    { returntoken(PLUS); }
    "-"    { returntoken(MINUS); }
    "*"    { returntoken(TIMES); }
    "**"   { returntoken(POW); }
    "/"    { returntoken(DIVIDE); }
    "%"    { returntoken(MOD); }
    "quit" { printf("lex: got QUIT\n"); yyterminate(); returntoken(QUIT); }
    "="    { returntoken(EQUALS); }
    "e"    { returntoken(E); }
    "pi"   { returntoken(PI); }
    "help" { returntoken(HELP); }
    [a-zA-Z_][0-9a-zA-Z_]* { returntoken(IDENTIFIER); }
    
    [ \t\v\f]             {}
    [\n]		{yylineno++; returntoken(NEWLINE); }
    .       { printf("unknown char %c ignored, yytext=0x%lx\n", yytext[0], yytext); /* ignore bad chars */}
    
    %%
    
    yywrap() { return(1); }
    """

if __name__ == '__main__':
    p = Parser(keepfiles=0)
    print "Scientific calculator example. Type 'help' for help"
    p.run()


syntax highlighted by Code2HTML, v. 0.9.1