calc1.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. #!/usr/bin/env python
  2. """
  3. A more advanced calculator example, with variable storage and scientific
  4. functions (courtesy of python 'math' module)
  5. """
  6. import math
  7. from bison import BisonParser
  8. class Parser(BisonParser):
  9. """
  10. Implements the calculator parser. Grammar rules are defined in the method docstrings.
  11. Scanner rules are in the 'lexscript' attribute.
  12. """
  13. # ----------------------------------------------------------------
  14. # lexer tokens - these must match those in your lex script (below)
  15. # ----------------------------------------------------------------
  16. tokens = ['NUMBER',
  17. 'PLUS', 'MINUS', 'TIMES', 'DIVIDE', 'MOD', 'POW',
  18. 'LPAREN', 'RPAREN',
  19. 'NEWLINE', 'QUIT',
  20. 'EQUALS', 'PI', 'E',
  21. 'IDENTIFIER',
  22. 'HELP']
  23. # ------------------------------
  24. # precedences
  25. # ------------------------------
  26. precedences = (
  27. ('left', ('MINUS', 'PLUS')),
  28. ('left', ('TIMES', 'DIVIDE', 'MOD')),
  29. ('left', ('NEG', )),
  30. ('right', ('POW', )),
  31. )
  32. # --------------------------------------------
  33. # basename of binary parser engine dynamic lib
  34. # --------------------------------------------
  35. bisonEngineLibName = "calc1-engine"
  36. # ------------------------------------------------------------------
  37. # override default read method with a version that prompts for input
  38. # ------------------------------------------------------------------
  39. def read(self, nbytes):
  40. try:
  41. return raw_input("> ") + "\n"
  42. except EOFError:
  43. return ''
  44. # -----------------------------------------------------------
  45. # override default run method to set up our variables storage
  46. # -----------------------------------------------------------
  47. def run(self, *args, **kw):
  48. self.vars = {}
  49. BisonParser.run(self, *args, **kw)
  50. # ---------------------------------------------------------------
  51. # These methods are the python handlers for the bison targets.
  52. # (which get called by the bison code each time the corresponding
  53. # parse target is unambiguously reached)
  54. #
  55. # WARNING - don't touch the method docstrings unless you know what
  56. # you are doing - they are in bison rule syntax, and are passed
  57. # verbatim to bison to build the parser engine library.
  58. # ---------------------------------------------------------------
  59. # Declare the start target here (by name)
  60. start = "input"
  61. def on_input(self, target, option, names, values):
  62. """
  63. input :
  64. | input line
  65. """
  66. if option == 1:
  67. return values[0]
  68. def on_line(self, target, option, names, values):
  69. """
  70. line : NEWLINE
  71. | exp NEWLINE
  72. | IDENTIFIER EQUALS exp NEWLINE
  73. | HELP
  74. | error
  75. """
  76. if option == 1:
  77. print values[0]
  78. return values[0]
  79. elif option == 2:
  80. self.vars[values[0]] = values[2]
  81. return values[2]
  82. elif option == 3:
  83. self.show_help()
  84. elif option == 4:
  85. line, msg, near = self.lasterror
  86. print "Line %s: \"%s\" near %s" % (line, msg, repr(near))
  87. def on_exp(self, target, option, names, values):
  88. """
  89. exp : number | plusexp | minusexp | timesexp | divexp | modexp
  90. | negexp | powexp | parenexp | varexp | functioncall | constant
  91. """
  92. return values[0]
  93. def on_number(self, target, option, names, values):
  94. """
  95. number : NUMBER
  96. """
  97. return float(values[0])
  98. def on_plusexp(self, target, option, names, values):
  99. """
  100. plusexp : exp PLUS exp
  101. """
  102. return values[0] + values[2]
  103. def on_minusexp(self, target, option, names, values):
  104. """
  105. minusexp : exp MINUS exp
  106. """
  107. return values[0] - values[2]
  108. def on_timesexp(self, target, option, names, values):
  109. """
  110. timesexp : exp TIMES exp
  111. """
  112. return values[0] * values[2]
  113. def on_divexp(self, target, option, names, values):
  114. """
  115. divexp : exp DIVIDE exp
  116. """
  117. try:
  118. return values[0] / values[2]
  119. except:
  120. return self.error("Division by zero error")
  121. def on_modexp(self, target, option, names, values):
  122. """
  123. modexp : exp MOD exp
  124. """
  125. try:
  126. return values[0] % values[2]
  127. except:
  128. return self.error("Modulus by zero error")
  129. def on_powexp(self, target, option, names, values):
  130. """
  131. powexp : exp POW exp
  132. """
  133. return values[0] ** values[2]
  134. def on_negexp(self, target, option, names, values):
  135. """
  136. negexp : MINUS exp %prec NEG
  137. """
  138. return values[1]
  139. def on_parenexp(self, target, option, names, values):
  140. """
  141. parenexp : LPAREN exp RPAREN
  142. """
  143. return values[1]
  144. def on_varexp(self, target, option, names, values):
  145. """
  146. varexp : IDENTIFIER
  147. """
  148. if self.vars.has_key(values[0]):
  149. return self.vars[values[0]]
  150. else:
  151. return self.error("No such variable '%s'" % values[0])
  152. def on_functioncall(self, target, option, names, values):
  153. """
  154. functioncall : IDENTIFIER LPAREN exp RPAREN
  155. """
  156. func = getattr(math, values[0], None)
  157. if not callable(func):
  158. return self.error("No such function '%s'" % values[0])
  159. try:
  160. return func(values[2])
  161. except Exception, e:
  162. return self.error(e.args[0])
  163. def on_constant(self, target, option, names, values):
  164. """
  165. constant : PI
  166. | E
  167. """
  168. return getattr(math, values[0])
  169. # -----------------------------------------
  170. # Display help
  171. # -----------------------------------------
  172. def show_help(self):
  173. print "This PyBison parser implements a basic scientific calculator"
  174. print " * scientific notation now works for numbers, eg '2.3e+12'"
  175. print " * you can assign values to variables, eg 'x = 23.2'"
  176. print " * the constants 'pi' and 'e' are supported"
  177. print " * all the python 'math' module functions are available, eg 'sin(pi/6)'"
  178. print " * errors, such as division by zero, are now reported"
  179. # -----------------------------------------
  180. # raw lex script, verbatim here
  181. # -----------------------------------------
  182. lexscript = r"""
  183. %{
  184. int yylineno = 0;
  185. #include <stdio.h>
  186. #include <string.h>
  187. #include "Python.h"
  188. #define YYSTYPE void *
  189. #include "tokens.h"
  190. extern void *py_parser;
  191. extern void (*py_input)(PyObject *parser, char *buf, int *result, int max_size);
  192. #define returntoken(tok) yylval = PyString_FromString(strdup(yytext)); return (tok);
  193. #define YY_INPUT(buf,result,max_size) { (*py_input)(py_parser, buf, &result, max_size); }
  194. %}
  195. %%
  196. ([0-9]*\.?)([0-9]+)(e[-+]?[0-9]+)? { returntoken(NUMBER); }
  197. ([0-9]+)(\.?[0-9]*)(e[-+]?[0-9]+)? { returntoken(NUMBER); }
  198. "(" { returntoken(LPAREN); }
  199. ")" { returntoken(RPAREN); }
  200. "+" { returntoken(PLUS); }
  201. "-" { returntoken(MINUS); }
  202. "*" { returntoken(TIMES); }
  203. "**" { returntoken(POW); }
  204. "/" { returntoken(DIVIDE); }
  205. "%" { returntoken(MOD); }
  206. "quit" { printf("lex: got QUIT\n"); yyterminate(); returntoken(QUIT); }
  207. "=" { returntoken(EQUALS); }
  208. "e" { returntoken(E); }
  209. "pi" { returntoken(PI); }
  210. "help" { returntoken(HELP); }
  211. [a-zA-Z_][0-9a-zA-Z_]* { returntoken(IDENTIFIER); }
  212. [ \t\v\f] {}
  213. [\n] {yylineno++; returntoken(NEWLINE); }
  214. . { printf("unknown char %c ignored, yytext=0x%lx\n", yytext[0], yytext); /* ignore bad chars */}
  215. %%
  216. yywrap() { return(1); }
  217. """
  218. if __name__ == '__main__':
  219. p = Parser(keepfiles=0)
  220. print "Scientific calculator example. Type 'help' for help"
  221. p.run()