calc1.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  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 sys, math, readline
  7. from bison import BisonParser, BisonNode, BisonError
  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()