| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 |
- #!/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()
|