Advertisement
ptmcg

Engineering Calculator supporting suffixed numeric literals

Mar 19th, 2011
1,001
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 7.33 KB | None | 0 0
  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. # Adapted to accept engineering suffixes, such as 1M -> 1e6
  11. #
  12. # Copyright 2003-2009 by Paul McGuire
  13. #
  14. from pyparsing import Literal,CaselessLiteral,Word,Group,Optional,\
  15.     ZeroOrMore,Forward,nums,alphas,Regex
  16. import math
  17. import operator
  18.  
  19. exprStack = []
  20.  
  21. def pushFirst( strg, loc, toks ):
  22.     exprStack.append( toks[0] )
  23. def pushUMinus( strg, loc, toks ):
  24.     if toks and toks[0]=='-':
  25.         exprStack.append( 'unary -' )
  26.        
  27. suffixes = dict(zip("YZEPTGMkhDdcmunpfazy",
  28.                      map(lambda x:10**x,
  29.                          (24,21,18,15,12,9,6,3,2,1,-1,-2,-3,
  30.                           -6,-9,-12,-15,-18,-21,-24))))
  31. bnf = None
  32. def BNF():
  33.     """
  34.    expop   :: '^'
  35.    multop  :: '*' | '/'
  36.    addop   :: '+' | '-'
  37.    integer :: ['+' | '-'] '0'..'9'+
  38.    atom    :: PI | E | real | fn '(' expr ')' | '(' expr ')'
  39.    factor  :: atom [ expop factor ]*
  40.    term    :: factor [ multop factor ]*
  41.    expr    :: term [ addop term ]*
  42.    """
  43.     global bnf
  44.     if not bnf:
  45.         point = Literal( "." )
  46.         e     = CaselessLiteral( "E" )
  47.         #~ fnumber = Combine( Word( "+-"+nums, nums ) +
  48.                            #~ Optional( point + Optional( Word( nums ) ) ) +
  49.                            #~ Optional( e + Word( "+-"+nums, nums ) ) )
  50.         fnumber = Regex(r"(?P<floatval>[+-]?\d+(:?\.\d*)?(:?[eE][+-]?\d+)?)(?P<suffix>[%s])?" %
  51.                         ''.join(suffixes.keys()))
  52.         def floatify(t):
  53.             ret = float(t.floatval)
  54.             if t.suffix:
  55.                 ret *= suffixes[t.suffix]
  56.             return ret
  57.         fnumber.setParseAction(floatify)
  58.         #~ fnumber = Regex(r"[+-]?\d+(:?\.\d*)?(:?[eE][+-]?\d+)?")
  59.         ident = Word(alphas, alphas+nums+"_$")
  60.      
  61.         plus  = Literal( "+" )
  62.         minus = Literal( "-" )
  63.         mult  = Literal( "*" )
  64.         div   = Literal( "/" )
  65.         lpar  = Literal( "(" ).suppress()
  66.         rpar  = Literal( ")" ).suppress()
  67.         addop  = plus | minus
  68.         multop = mult | div
  69.         expop = Literal( "^" )
  70.         pi    = CaselessLiteral( "PI" )
  71.        
  72.         expr = Forward()
  73.         atom = (Optional("-") + ( pi | e | fnumber | ident + lpar + expr + rpar ).setParseAction( pushFirst ) |
  74.                 Group( lpar + expr + rpar )).setParseAction(pushUMinus)
  75.        
  76.         # by defining exponentiation as "atom [ ^ factor ]..." instead of "atom [ ^ atom ]...", we get right-to-left exponents, instead of left-to-righ
  77.         # that is, 2^3^2 = 2^(3^2), not (2^3)^2.
  78.         factor = Forward()
  79.         factor << atom + ZeroOrMore( ( expop + factor ).setParseAction( pushFirst ) )
  80.        
  81.         term = factor + ZeroOrMore( ( multop + factor ).setParseAction( pushFirst ) )
  82.         expr << term + ZeroOrMore( ( addop + term ).setParseAction( pushFirst ) )
  83.         bnf = expr
  84.     return bnf
  85.  
  86. # map operator symbols to corresponding arithmetic operations
  87. opn = { "+" : operator.add,
  88.         "-" : operator.sub,
  89.         "*" : operator.mul,
  90.         "/" : operator.truediv,
  91.         "^" : operator.pow }
  92. fn  = { "sin" : math.sin,
  93.         "cos" : math.cos,
  94.         "tan" : math.tan,
  95.         "abs" : abs,
  96.         "trunc" : lambda a: int(a),
  97.         "round" : round,
  98.         "sgn" : lambda a: (a>0) - (a<0)
  99.         }
  100. def evaluateStack( s ):
  101.     op = s.pop()
  102.     if op == 'unary -':
  103.         return -evaluateStack( s )
  104.     elif isinstance(op, float):
  105.         return op
  106.     if op in "+-*/^":
  107.         op2 = evaluateStack( s )
  108.         op1 = evaluateStack( s )
  109.         return opn[op]( op1, op2 )
  110.     elif op == "PI":
  111.         return math.pi # 3.1415926535
  112.     elif op == "E":
  113.         return math.e  # 2.718281828
  114.     elif op in fn:
  115.         return fn[op]( evaluateStack( s ) )
  116.     elif op[0].isalpha():
  117.         return 0
  118.     else:
  119.         return float( op )
  120.  
  121. if __name__ == "__main__":
  122.    
  123.     def test( s, expVal ):
  124.         global exprStack
  125.         eps = 1e-12
  126.         exprStack = []
  127.         results = BNF().parseString( s )
  128.         val = evaluateStack( exprStack[:] )
  129.         if abs(val-expVal) < eps:
  130.             print s, "=", val, results, "=>", exprStack
  131.         else:
  132.             print s+"!!!", val, "!=", expVal, results, "=>", exprStack
  133.     test( "9", 9 )
  134.     test( "-9", -9 )
  135.     test( "--9", 9 )
  136.     test( "-E", -math.e )
  137.     test( "9 + 3 + 6", 9 + 3 + 6 )
  138.     test( "9 + 3 / 11", 9 + 3.0 / 11 )
  139.     test( "(9 + 3)", (9 + 3) )
  140.     test( "(9+3) / 11", (9+3.0) / 11 )
  141.     test( "9 - 12 - 6", 9 - 12 - 6 )
  142.     test( "9 - (12 - 6)", 9 - (12 - 6) )
  143.     test( "2*3.14159", 2*3.14159 )
  144.     test( "3.1415926535*3.1415926535 / 10", 3.1415926535*3.1415926535 / 10 )
  145.     test( "PI * PI / 10", math.pi * math.pi / 10 )
  146.     test( "PI*PI/10", math.pi*math.pi/10 )
  147.     test( "PI^2", math.pi**2 )
  148.     test( "round(PI^2)", round(math.pi**2) )
  149.     test( "6.02E23 * 8.048", 6.02E23 * 8.048 )
  150.     test( "e / 3", math.e / 3 )
  151.     test( "sin(PI/2)", math.sin(math.pi/2) )
  152.     test( "trunc(E)", int(math.e) )
  153.     test( "trunc(-E)", int(-math.e) )
  154.     test( "round(E)", round(math.e) )
  155.     test( "round(-E)", round(-math.e) )
  156.     test( "E^PI", math.e**math.pi )
  157.     test( "2^3^2", 2**3**2 )
  158.     test( "2^3+2", 2**3+2 )
  159.     test( "2^3+5", 2**3+5 )
  160.     test( "2^9", 2**9 )
  161.     test( "sgn(-2)", -1 )
  162.     test( "sgn(0)", 0 )
  163.     test( "sgn(0.1)", 1 )
  164.     test("1y", 1e-24)
  165.     test("1Y", 1e24)
  166.     test("1Y*1y", 1.0)
  167.  
  168.  
  169. """
  170. Test output:
  171. >pythonw -u fourFn.py
  172. 9 = 9.0 ['9'] => ['9']
  173. 9 + 3 + 6 = 18.0 ['9', '+', '3', '+', '6'] => ['9', '3', '+', '6', '+']
  174. 9 + 3 / 11 = 9.27272727273 ['9', '+', '3', '/', '11'] => ['9', '3', '11', '/', '+']
  175. (9 + 3) = 12.0 [] => ['9', '3', '+']
  176. (9+3) / 11 = 1.09090909091 ['/', '11'] => ['9', '3', '+', '11', '/']
  177. 9 - 12 - 6 = -9.0 ['9', '-', '12', '-', '6'] => ['9', '12', '-', '6', '-']
  178. 9 - (12 - 6) = 3.0 ['9', '-'] => ['9', '12', '6', '-', '-']
  179. 2*3.14159 = 6.28318 ['2', '*', '3.14159'] => ['2', '3.14159', '*']
  180. 3.1415926535*3.1415926535 / 10 = 0.986960440053 ['3.1415926535', '*', '3.1415926535', '/', '10'] => ['3.1415926535', '3.1415926535', '*', '10', '/']
  181. PI * PI / 10 = 0.986960440109 ['PI', '*', 'PI', '/', '10'] => ['PI', 'PI', '*', '10', '/']
  182. PI*PI/10 = 0.986960440109 ['PI', '*', 'PI', '/', '10'] => ['PI', 'PI', '*', '10', '/']
  183. PI^2 = 9.86960440109 ['PI', '^', '2'] => ['PI', '2', '^']
  184. 6.02E23 * 8.048 = 4.844896e+024 ['6.02E23', '*', '8.048'] => ['6.02E23', '8.048', '*']
  185. e / 3 = 0.90609394282 ['E', '/', '3'] => ['E', '3', '/']
  186. sin(PI/2) = 1.0 ['sin', 'PI', '/', '2'] => ['PI', '2', '/', 'sin']
  187. trunc(E) = 2 ['trunc', 'E'] => ['E', 'trunc']
  188. E^PI = 23.1406926328 ['E', '^', 'PI'] => ['E', 'PI', '^']
  189. 2^3^2 = 512.0 ['2', '^', '3', '^', '2'] => ['2', '3', '2', '^', '^']
  190. 2^3+2 = 10.0 ['2', '^', '3', '+', '2'] => ['2', '3', '^', '2', '+']
  191. 2^9 = 512.0 ['2', '^', '9'] => ['2', '9', '^']
  192. sgn(-2) = -1 ['sgn', '-2'] => ['-2', 'sgn']
  193. sgn(0) = 0 ['sgn', '0'] => ['0', 'sgn']
  194. sgn(0.1) = 1 ['sgn', '0.1'] => ['0.1', 'sgn']
  195. >Exit code: 0
  196. """
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement