fourFn.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. # fourFn.py
  2. #
  3. # Demonstration of the pyparsing module, implementing a simple 4-function expression parser,
  4. # with support for scientific notation, and symbols for e and pi.
  5. # Extended to add exponentiation and simple built-in functions.
  6. # Extended test cases, simplified pushFirst method.
  7. # Removed unnecessary expr.suppress() call (thanks Nathaniel Peterson!), and added Group
  8. # Changed fnumber to use a Regex, which is now the preferred method
  9. #
  10. # Copyright 2003-2009 by Paul McGuire
  11. #
  12. from pyparsing import Literal,Word,Group,\
  13. ZeroOrMore,Forward,alphas,alphanums,Regex,ParseException,\
  14. CaselessKeyword, Suppress
  15. import math
  16. import operator
  17. exprStack = []
  18. def pushFirst( strg, loc, toks ):
  19. exprStack.append( toks[0] )
  20. def pushUMinus( strg, loc, toks ):
  21. for t in toks:
  22. if t == '-':
  23. exprStack.append( 'unary -' )
  24. #~ exprStack.append( '-1' )
  25. #~ exprStack.append( '*' )
  26. else:
  27. break
  28. bnf = None
  29. def BNF():
  30. """
  31. expop :: '^'
  32. multop :: '*' | '/'
  33. addop :: '+' | '-'
  34. integer :: ['+' | '-'] '0'..'9'+
  35. atom :: PI | E | real | fn '(' expr ')' | '(' expr ')'
  36. factor :: atom [ expop factor ]*
  37. term :: factor [ multop factor ]*
  38. expr :: term [ addop term ]*
  39. """
  40. global bnf
  41. if not bnf:
  42. point = Literal( "." )
  43. # use CaselessKeyword for e and pi, to avoid accidentally matching
  44. # functions that start with 'e' or 'pi' (such as 'exp'); Keyword
  45. # and CaselessKeyword only match whole words
  46. e = CaselessKeyword( "E" )
  47. pi = CaselessKeyword( "PI" )
  48. #~ fnumber = Combine( Word( "+-"+nums, nums ) +
  49. #~ Optional( point + Optional( Word( nums ) ) ) +
  50. #~ Optional( e + Word( "+-"+nums, nums ) ) )
  51. fnumber = Regex(r"[+-]?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?")
  52. ident = Word(alphas, alphanums+"_$")
  53. plus, minus, mult, div = map(Literal, "+-*/")
  54. lpar, rpar = map(Suppress, "()")
  55. addop = plus | minus
  56. multop = mult | div
  57. expop = Literal( "^" )
  58. expr = Forward()
  59. atom = ((0,None)*minus + ( pi | e | fnumber | ident + lpar + expr + rpar | ident ).setParseAction( pushFirst ) |
  60. Group( lpar + expr + rpar )).setParseAction(pushUMinus)
  61. # by defining exponentiation as "atom [ ^ factor ]..." instead of "atom [ ^ atom ]...", we get right-to-left exponents, instead of left-to-righ
  62. # that is, 2^3^2 = 2^(3^2), not (2^3)^2.
  63. factor = Forward()
  64. factor << atom + ZeroOrMore( ( expop + factor ).setParseAction( pushFirst ) )
  65. term = factor + ZeroOrMore( ( multop + factor ).setParseAction( pushFirst ) )
  66. expr << term + ZeroOrMore( ( addop + term ).setParseAction( pushFirst ) )
  67. bnf = expr
  68. return bnf
  69. # map operator symbols to corresponding arithmetic operations
  70. epsilon = 1e-12
  71. opn = { "+" : operator.add,
  72. "-" : operator.sub,
  73. "*" : operator.mul,
  74. "/" : operator.truediv,
  75. "^" : operator.pow }
  76. fn = { "sin" : math.sin,
  77. "cos" : math.cos,
  78. "tan" : math.tan,
  79. "exp" : math.exp,
  80. "abs" : abs,
  81. "trunc" : lambda a: int(a),
  82. "round" : round,
  83. "sgn" : lambda a: (a > epsilon) - (a < -epsilon) }
  84. def evaluateStack( s ):
  85. op = s.pop()
  86. if op == 'unary -':
  87. return -evaluateStack( s )
  88. if op in "+-*/^":
  89. op2 = evaluateStack( s )
  90. op1 = evaluateStack( s )
  91. return opn[op]( op1, op2 )
  92. elif op == "PI":
  93. return math.pi # 3.1415926535
  94. elif op == "E":
  95. return math.e # 2.718281828
  96. elif op in fn:
  97. return fn[op]( evaluateStack( s ) )
  98. elif op[0].isalpha():
  99. raise Exception("invalid identifier '%s'" % op)
  100. else:
  101. return float( op )
  102. if __name__ == "__main__":
  103. def test( s, expVal ):
  104. global exprStack
  105. exprStack[:] = []
  106. try:
  107. results = BNF().parseString( s, parseAll=True )
  108. val = evaluateStack( exprStack[:] )
  109. except ParseException as pe:
  110. print(s, "failed parse:", str(pe))
  111. except Exception as e:
  112. print(s, "failed eval:", str(e))
  113. else:
  114. if val == expVal:
  115. print(s, "=", val, results, "=>", exprStack)
  116. else:
  117. print(s+"!!!", val, "!=", expVal, results, "=>", exprStack)
  118. test( "9", 9 )
  119. test( "-9", -9 )
  120. test( "--9", 9 )
  121. test( "-E", -math.e )
  122. test( "9 + 3 + 6", 9 + 3 + 6 )
  123. test( "9 + 3 / 11", 9 + 3.0 / 11 )
  124. test( "(9 + 3)", (9 + 3) )
  125. test( "(9+3) / 11", (9+3.0) / 11 )
  126. test( "9 - 12 - 6", 9 - 12 - 6 )
  127. test( "9 - (12 - 6)", 9 - (12 - 6) )
  128. test( "2*3.14159", 2*3.14159 )
  129. test( "3.1415926535*3.1415926535 / 10", 3.1415926535*3.1415926535 / 10 )
  130. test( "PI * PI / 10", math.pi * math.pi / 10 )
  131. test( "PI*PI/10", math.pi*math.pi/10 )
  132. test( "PI^2", math.pi**2 )
  133. test( "round(PI^2)", round(math.pi**2) )
  134. test( "6.02E23 * 8.048", 6.02E23 * 8.048 )
  135. test( "e / 3", math.e / 3 )
  136. test( "sin(PI/2)", math.sin(math.pi/2) )
  137. test( "trunc(E)", int(math.e) )
  138. test( "trunc(-E)", int(-math.e) )
  139. test( "round(E)", round(math.e) )
  140. test( "round(-E)", round(-math.e) )
  141. test( "E^PI", math.e**math.pi )
  142. test( "exp(0)", 1 )
  143. test( "exp(1)", math.e )
  144. test( "2^3^2", 2**3**2 )
  145. test( "2^3+2", 2**3+2 )
  146. test( "2^3+5", 2**3+5 )
  147. test( "2^9", 2**9 )
  148. test( "sgn(-2)", -1 )
  149. test( "sgn(0)", 0 )
  150. test( "foo(0.1)", None )
  151. test( "sgn(0.1)", 1 )
  152. """
  153. Test output:
  154. >pythonw -u fourFn.py
  155. 9 = 9.0 ['9'] => ['9']
  156. 9 + 3 + 6 = 18.0 ['9', '+', '3', '+', '6'] => ['9', '3', '+', '6', '+']
  157. 9 + 3 / 11 = 9.27272727273 ['9', '+', '3', '/', '11'] => ['9', '3', '11', '/', '+']
  158. (9 + 3) = 12.0 [] => ['9', '3', '+']
  159. (9+3) / 11 = 1.09090909091 ['/', '11'] => ['9', '3', '+', '11', '/']
  160. 9 - 12 - 6 = -9.0 ['9', '-', '12', '-', '6'] => ['9', '12', '-', '6', '-']
  161. 9 - (12 - 6) = 3.0 ['9', '-'] => ['9', '12', '6', '-', '-']
  162. 2*3.14159 = 6.28318 ['2', '*', '3.14159'] => ['2', '3.14159', '*']
  163. 3.1415926535*3.1415926535 / 10 = 0.986960440053 ['3.1415926535', '*', '3.1415926535', '/', '10'] => ['3.1415926535', '3.1415926535', '*', '10', '/']
  164. PI * PI / 10 = 0.986960440109 ['PI', '*', 'PI', '/', '10'] => ['PI', 'PI', '*', '10', '/']
  165. PI*PI/10 = 0.986960440109 ['PI', '*', 'PI', '/', '10'] => ['PI', 'PI', '*', '10', '/']
  166. PI^2 = 9.86960440109 ['PI', '^', '2'] => ['PI', '2', '^']
  167. 6.02E23 * 8.048 = 4.844896e+024 ['6.02E23', '*', '8.048'] => ['6.02E23', '8.048', '*']
  168. e / 3 = 0.90609394282 ['E', '/', '3'] => ['E', '3', '/']
  169. sin(PI/2) = 1.0 ['sin', 'PI', '/', '2'] => ['PI', '2', '/', 'sin']
  170. trunc(E) = 2 ['trunc', 'E'] => ['E', 'trunc']
  171. E^PI = 23.1406926328 ['E', '^', 'PI'] => ['E', 'PI', '^']
  172. 2^3^2 = 512.0 ['2', '^', '3', '^', '2'] => ['2', '3', '2', '^', '^']
  173. 2^3+2 = 10.0 ['2', '^', '3', '+', '2'] => ['2', '3', '^', '2', '+']
  174. 2^9 = 512.0 ['2', '^', '9'] => ['2', '9', '^']
  175. sgn(-2) = -1 ['sgn', '-2'] => ['-2', 'sgn']
  176. sgn(0) = 0 ['sgn', '0'] => ['0', 'sgn']
  177. sgn(0.1) = 1 ['sgn', '0.1'] => ['0.1', 'sgn']
  178. >Exit code: 0
  179. """