calc.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. #!/usr/bin/env python
  2. """
  3. A simple pybison parser program implementing a calculator
  4. """
  5. from __future__ import division
  6. from sympy import Symbol
  7. import os.path
  8. PYBISON_BUILD = os.path.realpath('build/external/pybison')
  9. EXTERNAL_MODS = os.path.realpath('external')
  10. import sys
  11. sys.path.insert(0, PYBISON_BUILD)
  12. sys.path.insert(1, EXTERNAL_MODS)
  13. from pybison import BisonParser
  14. class Parser(BisonParser):
  15. """
  16. Implements the calculator parser. Grammar rules are defined in the method
  17. docstrings. Scanner rules are in the 'lexscript' attribute.
  18. """
  19. # Output directory of generated pybison files, including a trailing slash.
  20. buildDirectory = PYBISON_BUILD + '/'
  21. # ----------------------------------------------------------------
  22. # lexer tokens - these must match those in your lex script (below)
  23. # ----------------------------------------------------------------
  24. tokens = ['NUMBER', 'IDENTIFIER',
  25. 'PLUS', 'MINUS', 'TIMES', 'DIVIDE', 'POW',
  26. 'LPAREN', 'RPAREN',
  27. 'NEWLINE', 'QUIT']
  28. # ------------------------------
  29. # precedences
  30. # ------------------------------
  31. precedences = (
  32. ('left', ('MINUS', 'PLUS')),
  33. ('left', ('TIMES', 'DIVIDE')),
  34. ('left', ('NEG', )),
  35. ('right', ('POW', )),
  36. )
  37. interactive = 0
  38. def __init__(self, **kwargs):
  39. BisonParser.__init__(self, **kwargs)
  40. self.interactive = kwargs.get('interactive', 0)
  41. self.timeout = kwargs.get('timeout', 0)
  42. # ------------------------------------------------------------------
  43. # override default read method with a version that prompts for input
  44. # ------------------------------------------------------------------
  45. def read(self, nbytes):
  46. try:
  47. return raw_input('>>> ') + "\n"
  48. except EOFError:
  49. return ''
  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. # Interactive mode is enabled if the term rewriting system is used
  68. # as a shell. In that case, it is useful that the shell prints the
  69. # output of the evaluation.
  70. if self.interactive:
  71. print values[1]
  72. return values[1]
  73. def on_line(self, target, option, names, values):
  74. """
  75. line : NEWLINE
  76. | exp NEWLINE
  77. """
  78. if option in [1, 2]:
  79. if self.verbose:
  80. print 'on_line: exp =', values[0]
  81. return values[0]
  82. def on_exp(self, target, option, names, values):
  83. """
  84. exp : NUMBER
  85. | IDENTIFIER
  86. | exp PLUS exp
  87. | exp MINUS exp
  88. | exp TIMES exp
  89. | exp DIVIDE exp
  90. | MINUS exp %prec NEG
  91. | exp POW exp
  92. | LPAREN exp RPAREN
  93. | symbolic
  94. """
  95. if self.verbose:
  96. print 'on_exp: got %s %s %s %s' % (target, option, names, values)
  97. # rule: NUMBER
  98. if option == 0:
  99. # TODO: A bit hacky, this achieves long integers and floats.
  100. #return float(values[0]) if '.' in values[0] else long(values[0])
  101. #return float(values[0])
  102. return float(values[0]) if '.' in values[0] else int(values[0])
  103. # rule: IDENTIFIER
  104. if option == 1:
  105. return Symbol(values[0])
  106. # rule: LPAREN exp RPAREN
  107. if option == 8:
  108. return values[1]
  109. # rule: symbolic
  110. if option == 9:
  111. return values[1]
  112. try:
  113. # rule: exp PLUS expo
  114. if option == 2:
  115. return values[0] + values[2]
  116. # rule: exp MINUS expo
  117. if option == 3:
  118. return values[0] - values[2]
  119. # rule: exp TIMES expo
  120. if option == 4:
  121. return values[0] * values[2]
  122. # rule: exp DIVIDE expo
  123. if option == 5:
  124. return values[0] / values[2]
  125. # rule: NEG expo
  126. if option == 6:
  127. return - values[1]
  128. # rule: exp POW expo
  129. if option == 7:
  130. return values[0] ** values[2]
  131. except OverflowError:
  132. print >>sys.stderr, 'error: Overflow occured in "%s" %s %s %s' \
  133. % (target, option, names, values)
  134. def on_symbolic(self, target, option, names, values):
  135. """
  136. symbolic : NUMBER IDENTIFIER
  137. | IDENTIFIER NUMBER
  138. | IDENTIFIER IDENTIFIER
  139. """
  140. # TODO: this class method requires verification.
  141. # rule: NUMBER IDENTIFIER
  142. if option == 0:
  143. # 4x -> 4*x
  144. return values[0] * Symbol(values[1])
  145. # rule: IDENTIFIER NUMBER
  146. if option == 1:
  147. # x4 -> x^4
  148. return Symbol(values[0]) ** values[1]
  149. # rule: IDENTIFIER IDENTIFIER
  150. if option == 2:
  151. # a b -> a * b
  152. return Symbol(values[0]) * Symbol(values[1])
  153. # -----------------------------------------
  154. # raw lex script, verbatim here
  155. # -----------------------------------------
  156. lexscript = r"""
  157. %{
  158. //int yylineno = 0;
  159. #include <stdio.h>
  160. #include <string.h>
  161. #include "Python.h"
  162. #define YYSTYPE void *
  163. #include "tokens.h"
  164. extern void *py_parser;
  165. extern void (*py_input)(PyObject *parser, char *buf, int *result,
  166. int max_size);
  167. #define returntoken(tok) \
  168. yylval = PyString_FromString(strdup(yytext)); return (tok);
  169. #define YY_INPUT(buf,result,max_size) { \
  170. (*py_input)(py_parser, buf, &result, max_size); \
  171. }
  172. %}
  173. %%
  174. [0-9]+ { returntoken(NUMBER); }
  175. [a-zA-Z][a-zA-Z0-9]* { returntoken(IDENTIFIER); }
  176. "(" { returntoken(LPAREN); }
  177. ")" { returntoken(RPAREN); }
  178. "+" { returntoken(PLUS); }
  179. "-" { returntoken(MINUS); }
  180. "*" { returntoken(TIMES); }
  181. "^" { returntoken(POW); }
  182. "/" { returntoken(DIVIDE); }
  183. "quit" { printf("lex: got QUIT\n"); yyterminate(); returntoken(QUIT); }
  184. [ \t\v\f] {}
  185. [\n] {yylineno++; returntoken(NEWLINE); }
  186. . { printf("unknown char %c ignored, yytext=0x%lx\n",
  187. yytext[0], yytext); /* ignore bad chars */}
  188. %%
  189. yywrap() { return(1); }
  190. """
  191. if __name__ == '__main__':
  192. p = Parser(verbose=0, keepfiles=1, interactive=1)
  193. p.run(debug=0)
  194. # Clear the line, when the shell exits.
  195. print