diff options
author | Paul McGuire <ptmcg@austin.rr.com> | 2019-08-03 16:08:29 -0500 |
---|---|---|
committer | Paul McGuire <ptmcg@austin.rr.com> | 2019-08-03 16:08:29 -0500 |
commit | 36aa91c4f603ddb15054b0ff9f5daf5044b1dd31 (patch) | |
tree | a5dde20d0aa5c2c431855f4bc901255e7e5da691 | |
parent | b9c11d7f84c9886ba037d6b3f7e2f6ec27f5ed45 (diff) | |
download | pyparsing-git-36aa91c4f603ddb15054b0ff9f5daf5044b1dd31.tar.gz |
Update fourFn.py to handle functions that take multiple args, and nested functions
-rw-r--r-- | examples/SimpleCalc.py | 239 | ||||
-rw-r--r-- | examples/fourFn.py | 424 | ||||
-rw-r--r-- | unitTests.py | 69 |
3 files changed, 401 insertions, 331 deletions
diff --git a/examples/SimpleCalc.py b/examples/SimpleCalc.py index 15a1817..d52a20f 100644 --- a/examples/SimpleCalc.py +++ b/examples/SimpleCalc.py @@ -1,117 +1,122 @@ -# SimpleCalc.py
-#
-# Demonstration of the parsing module,
-# Sample usage
-#
-# $ python SimpleCalc.py
-# Type in the string to be parse or 'quit' to exit the program
-# > g=67.89 + 7/5
-# 69.29
-# > g
-# 69.29
-# > h=(6*g+8.8)-g
-# 355.25
-# > h + 1
-# 356.25
-# > 87.89 + 7/5
-# 89.29
-# > ans+10
-# 99.29
-# > quit
-# Good bye!
-#
-#
-
-
-
-# Uncomment the line below for readline support on interactive terminal
-# import readline
-from pyparsing import ParseException, Word, alphas, alphanums
-import math
-
-# Debugging flag can be set to either "debug_flag=True" or "debug_flag=False"
-debug_flag=False
-
-variables = {}
-
-from fourFn import BNF, exprStack, fn, opn
-def evaluateStack( s ):
- op = s.pop()
- if op == 'unary -':
- return -evaluateStack( s )
- if op in "+-*/^":
- op2 = evaluateStack( s )
- op1 = evaluateStack( s )
- return opn[op]( op1, op2 )
- elif op == "PI":
- return math.pi # 3.1415926535
- elif op == "E":
- return math.e # 2.718281828
- elif op in fn:
- return fn[op]( evaluateStack( s ) )
- elif op[0].isalpha():
- if op in variables:
- return variables[op]
- raise Exception("invalid identifier '%s'" % op)
- else:
- return float( op )
-
-arithExpr = BNF()
-ident = Word(alphas, alphanums).setName("identifier")
-assignment = ident("varname") + '=' + arithExpr
-pattern = assignment | arithExpr
-
-if __name__ == '__main__':
- # input_string
- input_string=''
-
- # Display instructions on how to quit the program
- print("Type in the string to be parsed or 'quit' to exit the program")
- input_string = input("> ")
-
- while input_string.strip().lower() != 'quit':
- if input_string.strip().lower() == 'debug':
- debug_flag=True
- input_string = input("> ")
- continue
-
- # Reset to an empty exprStack
- del exprStack[:]
-
- if input_string != '':
- # try parsing the input string
- try:
- L=pattern.parseString(input_string, parseAll=True)
- except ParseException as err:
- L=['Parse Failure', input_string, (str(err), err.line, err.column)]
-
- # show result of parsing the input string
- if debug_flag: print(input_string, "->", L)
- if len(L)==0 or L[0] != 'Parse Failure':
- if debug_flag: print("exprStack=", exprStack)
-
- # calculate result , store a copy in ans , display the result to user
- try:
- result=evaluateStack(exprStack)
- except Exception as e:
- print(str(e))
- else:
- variables['ans']=result
- print(result)
-
- # Assign result to a variable if required
- if L.varname:
- variables[L.varname] = result
- if debug_flag: print("variables=", variables)
- else:
- print('Parse Failure')
- err_str, err_line, err_col = L[-1]
- print(err_line)
- print(" "*(err_col-1) + "^")
- print(err_str)
-
- # obtain new input string
- input_string = input("> ")
-
- # if user type 'quit' then say goodbye
- print("Good bye!")
+# SimpleCalc.py +# +# Demonstration of the parsing module, +# Sample usage +# +# $ python SimpleCalc.py +# Type in the string to be parse or 'quit' to exit the program +# > g=67.89 + 7/5 +# 69.29 +# > g +# 69.29 +# > h=(6*g+8.8)-g +# 355.25 +# > h + 1 +# 356.25 +# > 87.89 + 7/5 +# 89.29 +# > ans+10 +# 99.29 +# > quit +# Good bye! +# +# + + + +# Uncomment the line below for readline support on interactive terminal +# import readline +from pyparsing import ParseException, Word, alphas, alphanums + +# Debugging flag can be set to either "debug_flag=True" or "debug_flag=False" +debug_flag=False + +variables = {} + +from fourFn import BNF, exprStack, fn, opn, evaluate_stack + +# from fourFn import BNF, exprStack, fn, opn +# def evaluateStack( s ): +# op = s.pop() +# if op == 'unary -': +# return -evaluateStack( s ) +# if op in "+-*/^": +# op2 = evaluateStack( s ) +# op1 = evaluateStack( s ) +# return opn[op]( op1, op2 ) +# elif op == "PI": +# return math.pi # 3.1415926535 +# elif op == "E": +# return math.e # 2.718281828 +# elif op in fn: +# return fn[op]( evaluateStack( s ) ) +# elif op[0].isalpha(): +# if op in variables: +# return variables[op] +# raise Exception("invalid identifier '%s'" % op) +# else: +# return float( op ) + +arithExpr = BNF() +ident = Word(alphas, alphanums).setName("identifier") +assignment = ident("varname") + '=' + arithExpr +pattern = assignment | arithExpr + +if __name__ == '__main__': + # input_string + input_string='' + + # Display instructions on how to quit the program + print("Type in the string to be parsed or 'quit' to exit the program") + input_string = input("> ") + + while input_string.strip().lower() != 'quit': + if input_string.strip().lower() == 'debug': + debug_flag=True + input_string = input("> ") + continue + + # Reset to an empty exprStack + del exprStack[:] + + if input_string != '': + # try parsing the input string + try: + L = pattern.parseString(input_string, parseAll=True) + except ParseException as err: + L = ['Parse Failure', input_string, (str(err), err.line, err.column)] + + # show result of parsing the input string + if debug_flag: print(input_string, "->", L) + if len(L)==0 or L[0] != 'Parse Failure': + if debug_flag: print("exprStack=", exprStack) + + for i, ob in enumerate(exprStack): + if isinstance(ob, str) and ob in variables: + exprStack[i] = str(variables[ob]) + + # calculate result , store a copy in ans , display the result to user + try: + result=evaluate_stack(exprStack) + except Exception as e: + print(str(e)) + else: + variables['ans']=result + print(result) + + # Assign result to a variable if required + if L.varname: + variables[L.varname] = result + if debug_flag: print("variables=", variables) + else: + print('Parse Failure') + err_str, err_line, err_col = L[-1] + print(err_line) + print(" "*(err_col-1) + "^") + print(err_str) + + # obtain new input string + input_string = input("> ") + + # if user type 'quit' then say goodbye + print("Good bye!") diff --git a/examples/fourFn.py b/examples/fourFn.py index e1393b6..5108c19 100644 --- a/examples/fourFn.py +++ b/examples/fourFn.py @@ -1,192 +1,232 @@ -# fourFn.py
-#
-# Demonstration of the pyparsing module, implementing a simple 4-function expression parser,
-# with support for scientific notation, and symbols for e and pi.
-# Extended to add exponentiation and simple built-in functions.
-# Extended test cases, simplified pushFirst method.
-# Removed unnecessary expr.suppress() call (thanks Nathaniel Peterson!), and added Group
-# Changed fnumber to use a Regex, which is now the preferred method
-#
-# Copyright 2003-2009 by Paul McGuire
-#
-from pyparsing import Literal,Word,Group,\
- ZeroOrMore,Forward,alphas,alphanums,Regex,ParseException,\
- CaselessKeyword, Suppress
-import math
-import operator
-
-exprStack = []
-
-def pushFirst( strg, loc, toks ):
- exprStack.append( toks[0] )
-def pushUMinus( strg, loc, toks ):
- for t in toks:
- if t == '-':
- exprStack.append( 'unary -' )
- #~ exprStack.append( '-1' )
- #~ exprStack.append( '*' )
- else:
- break
-
-bnf = None
-def BNF():
- """
- expop :: '^'
- multop :: '*' | '/'
- addop :: '+' | '-'
- integer :: ['+' | '-'] '0'..'9'+
- atom :: PI | E | real | fn '(' expr ')' | '(' expr ')'
- factor :: atom [ expop factor ]*
- term :: factor [ multop factor ]*
- expr :: term [ addop term ]*
- """
- global bnf
- if not bnf:
- point = Literal( "." )
- # use CaselessKeyword for e and pi, to avoid accidentally matching
- # functions that start with 'e' or 'pi' (such as 'exp'); Keyword
- # and CaselessKeyword only match whole words
- e = CaselessKeyword( "E" )
- pi = CaselessKeyword( "PI" )
- #~ fnumber = Combine( Word( "+-"+nums, nums ) +
- #~ Optional( point + Optional( Word( nums ) ) ) +
- #~ Optional( e + Word( "+-"+nums, nums ) ) )
- fnumber = Regex(r"[+-]?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?")
- ident = Word(alphas, alphanums+"_$")
-
- plus, minus, mult, div = map(Literal, "+-*/")
- lpar, rpar = map(Suppress, "()")
- addop = plus | minus
- multop = mult | div
- expop = Literal( "^" )
-
- expr = Forward()
- atom = ((0,None)*minus + ( pi | e | fnumber | ident + lpar + expr + rpar | ident ).setParseAction( pushFirst ) |
- Group( lpar + expr + rpar )).setParseAction(pushUMinus)
-
- # by defining exponentiation as "atom [ ^ factor ]..." instead of "atom [ ^ atom ]...", we get right-to-left exponents, instead of left-to-righ
- # that is, 2^3^2 = 2^(3^2), not (2^3)^2.
- factor = Forward()
- factor << atom + ZeroOrMore( ( expop + factor ).setParseAction( pushFirst ) )
-
- term = factor + ZeroOrMore( ( multop + factor ).setParseAction( pushFirst ) )
- expr << term + ZeroOrMore( ( addop + term ).setParseAction( pushFirst ) )
- bnf = expr
- return bnf
-
-# map operator symbols to corresponding arithmetic operations
-epsilon = 1e-12
-opn = { "+" : operator.add,
- "-" : operator.sub,
- "*" : operator.mul,
- "/" : operator.truediv,
- "^" : operator.pow }
-fn = { "sin" : math.sin,
- "cos" : math.cos,
- "tan" : math.tan,
- "exp" : math.exp,
- "abs" : abs,
- "trunc" : lambda a: int(a),
- "round" : round,
- "sgn" : lambda a: (a > epsilon) - (a < -epsilon) }
-def evaluateStack( s ):
- op = s.pop()
- if op == 'unary -':
- return -evaluateStack( s )
- if op in "+-*/^":
- op2 = evaluateStack( s )
- op1 = evaluateStack( s )
- return opn[op]( op1, op2 )
- elif op == "PI":
- return math.pi # 3.1415926535
- elif op == "E":
- return math.e # 2.718281828
- elif op in fn:
- return fn[op]( evaluateStack( s ) )
- elif op[0].isalpha():
- raise Exception("invalid identifier '%s'" % op)
- else:
- return float( op )
-
-if __name__ == "__main__":
-
- def test( s, expVal ):
- global exprStack
- exprStack[:] = []
- try:
- results = BNF().parseString( s, parseAll=True )
- val = evaluateStack( exprStack[:] )
- except ParseException as pe:
- print(s, "failed parse:", str(pe))
- except Exception as e:
- print(s, "failed eval:", str(e))
- else:
- if val == expVal:
- print(s, "=", val, results, "=>", exprStack)
- else:
- print(s+"!!!", val, "!=", expVal, results, "=>", exprStack)
-
- test( "9", 9 )
- test( "-9", -9 )
- test( "--9", 9 )
- test( "-E", -math.e )
- test( "9 + 3 + 6", 9 + 3 + 6 )
- test( "9 + 3 / 11", 9 + 3.0 / 11 )
- test( "(9 + 3)", (9 + 3) )
- test( "(9+3) / 11", (9+3.0) / 11 )
- test( "9 - 12 - 6", 9 - 12 - 6 )
- test( "9 - (12 - 6)", 9 - (12 - 6) )
- test( "2*3.14159", 2*3.14159 )
- test( "3.1415926535*3.1415926535 / 10", 3.1415926535*3.1415926535 / 10 )
- test( "PI * PI / 10", math.pi * math.pi / 10 )
- test( "PI*PI/10", math.pi*math.pi/10 )
- test( "PI^2", math.pi**2 )
- test( "round(PI^2)", round(math.pi**2) )
- test( "6.02E23 * 8.048", 6.02E23 * 8.048 )
- test( "e / 3", math.e / 3 )
- test( "sin(PI/2)", math.sin(math.pi/2) )
- test( "trunc(E)", int(math.e) )
- test( "trunc(-E)", int(-math.e) )
- test( "round(E)", round(math.e) )
- test( "round(-E)", round(-math.e) )
- test( "E^PI", math.e**math.pi )
- test( "exp(0)", 1 )
- test( "exp(1)", math.e )
- test( "2^3^2", 2**3**2 )
- test( "2^3+2", 2**3+2 )
- test( "2^3+5", 2**3+5 )
- test( "2^9", 2**9 )
- test( "sgn(-2)", -1 )
- test( "sgn(0)", 0 )
- test( "foo(0.1)", None )
- test( "sgn(0.1)", 1 )
-
-
-"""
-Test output:
->pythonw -u fourFn.py
-9 = 9.0 ['9'] => ['9']
-9 + 3 + 6 = 18.0 ['9', '+', '3', '+', '6'] => ['9', '3', '+', '6', '+']
-9 + 3 / 11 = 9.27272727273 ['9', '+', '3', '/', '11'] => ['9', '3', '11', '/', '+']
-(9 + 3) = 12.0 [] => ['9', '3', '+']
-(9+3) / 11 = 1.09090909091 ['/', '11'] => ['9', '3', '+', '11', '/']
-9 - 12 - 6 = -9.0 ['9', '-', '12', '-', '6'] => ['9', '12', '-', '6', '-']
-9 - (12 - 6) = 3.0 ['9', '-'] => ['9', '12', '6', '-', '-']
-2*3.14159 = 6.28318 ['2', '*', '3.14159'] => ['2', '3.14159', '*']
-3.1415926535*3.1415926535 / 10 = 0.986960440053 ['3.1415926535', '*', '3.1415926535', '/', '10'] => ['3.1415926535', '3.1415926535', '*', '10', '/']
-PI * PI / 10 = 0.986960440109 ['PI', '*', 'PI', '/', '10'] => ['PI', 'PI', '*', '10', '/']
-PI*PI/10 = 0.986960440109 ['PI', '*', 'PI', '/', '10'] => ['PI', 'PI', '*', '10', '/']
-PI^2 = 9.86960440109 ['PI', '^', '2'] => ['PI', '2', '^']
-6.02E23 * 8.048 = 4.844896e+024 ['6.02E23', '*', '8.048'] => ['6.02E23', '8.048', '*']
-e / 3 = 0.90609394282 ['E', '/', '3'] => ['E', '3', '/']
-sin(PI/2) = 1.0 ['sin', 'PI', '/', '2'] => ['PI', '2', '/', 'sin']
-trunc(E) = 2 ['trunc', 'E'] => ['E', 'trunc']
-E^PI = 23.1406926328 ['E', '^', 'PI'] => ['E', 'PI', '^']
-2^3^2 = 512.0 ['2', '^', '3', '^', '2'] => ['2', '3', '2', '^', '^']
-2^3+2 = 10.0 ['2', '^', '3', '+', '2'] => ['2', '3', '^', '2', '+']
-2^9 = 512.0 ['2', '^', '9'] => ['2', '9', '^']
-sgn(-2) = -1 ['sgn', '-2'] => ['-2', 'sgn']
-sgn(0) = 0 ['sgn', '0'] => ['0', 'sgn']
-sgn(0.1) = 1 ['sgn', '0.1'] => ['0.1', 'sgn']
->Exit code: 0
-"""
+# fourFn.py +# +# Demonstration of the pyparsing module, implementing a simple 4-function expression parser, +# with support for scientific notation, and symbols for e and pi. +# Extended to add exponentiation and simple built-in functions. +# Extended test cases, simplified pushFirst method. +# Removed unnecessary expr.suppress() call (thanks Nathaniel Peterson!), and added Group +# Changed fnumber to use a Regex, which is now the preferred method +# Reformatted to latest pypyparsing features, support multiple and variable args to functions +# +# Copyright 2003-2019 by Paul McGuire +# +from pyparsing import (Literal, Word, Group, Forward, alphas, alphanums, Regex, ParseException, + CaselessKeyword, Suppress, delimitedList) +import math +import operator + +exprStack = [] + +def push_first(toks): + exprStack.append(toks[0]) + +def push_unary_minus(toks): + for t in toks: + if t == '-': + exprStack.append('unary -') + else: + break + +bnf = None +def BNF(): + """ + expop :: '^' + multop :: '*' | '/' + addop :: '+' | '-' + integer :: ['+' | '-'] '0'..'9'+ + atom :: PI | E | real | fn '(' expr ')' | '(' expr ')' + factor :: atom [ expop factor ]* + term :: factor [ multop factor ]* + expr :: term [ addop term ]* + """ + global bnf + if not bnf: + # use CaselessKeyword for e and pi, to avoid accidentally matching + # functions that start with 'e' or 'pi' (such as 'exp'); Keyword + # and CaselessKeyword only match whole words + e = CaselessKeyword("E") + pi = CaselessKeyword("PI") + # fnumber = Combine(Word("+-"+nums, nums) + + # Optional("." + Optional(Word(nums))) + + # Optional(e + Word("+-"+nums, nums))) + fnumber = Regex(r"[+-]?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?") + ident = Word(alphas, alphanums+"_$") + + plus, minus, mult, div = map(Literal, "+-*/") + lpar, rpar = map(Suppress, "()") + addop = plus | minus + multop = mult | div + expop = Literal("^") + + expr = Forward() + expr_list = delimitedList(Group(expr)) + # add parse action that replaces the function identifier with a (name, number of args) tuple + fn_call = (ident + lpar - Group(expr_list) + rpar).setParseAction(lambda t: t.insert(0, (t.pop(0), len(t[0])))) + atom = (minus[...] + + (fn_call | pi | e | fnumber | ident).setParseAction(push_first) + | Group(lpar + expr + rpar)).setParseAction(push_unary_minus) + + # by defining exponentiation as "atom [ ^ factor ]..." instead of "atom [ ^ atom ]...", we get right-to-left + # exponents, instead of left-to-right that is, 2^3^2 = 2^(3^2), not (2^3)^2. + factor = Forward() + factor <<= atom + (expop + factor).setParseAction(push_first)[...] + term = factor + (multop + factor).setParseAction(push_first)[...] + expr <<= term + (addop + term).setParseAction(push_first)[...] + bnf = expr + return bnf + +# map operator symbols to corresponding arithmetic operations +epsilon = 1e-12 +opn = { + "+": operator.add, + "-": operator.sub, + "*": operator.mul, + "/": operator.truediv, + "^": operator.pow + } + +fn = { + "sin": math.sin, + "cos": math.cos, + "tan": math.tan, + "exp": math.exp, + "abs": abs, + "trunc": lambda a: int(a), + "round": round, + "sgn": lambda a: -1 if a < -epsilon else 1 if a > epsilon else 0 + } + +def evaluate_stack(s): + op, num_args = s.pop(), 0 + if isinstance(op, tuple): + op, num_args = op + if op == 'unary -': + return -evaluate_stack(s) + if op in "+-*/^": + # note: operands are pushed onto the stack in reverse order + op2 = evaluate_stack(s) + op1 = evaluate_stack(s) + return opn[op](op1, op2) + elif op == "PI": + return math.pi # 3.1415926535 + elif op == "E": + return math.e # 2.718281828 + elif op in fn: + # note: args are pushed onto the stack in reverse order + args = reversed([evaluate_stack(s) for _ in range(num_args)]) + return fn[op](*args) + elif op[0].isalpha(): + raise Exception("invalid identifier '%s'" % op) + else: + # try to evaluate as int first, then as float if int fails + try: + return int(op) + except ValueError: + return float(op) + + +if __name__ == "__main__": + + def test(s, expected): + exprStack[:] = [] + try: + results = BNF().parseString(s, parseAll=True) + val = evaluate_stack(exprStack[:]) + except ParseException as pe: + print(s, "failed parse:", str(pe)) + except Exception as e: + print(s, "failed eval:", str(e), exprStack) + else: + if val == expected: + print(s, "=", val, results, "=>", exprStack) + else: + print(s + "!!!", val, "!=", expected, results, "=>", exprStack) + + test("9", 9) + test("-9", -9) + test("--9", 9) + test("-E", -math.e) + test("9 + 3 + 6", 9 + 3 + 6) + test("9 + 3 / 11", 9 + 3.0 / 11) + test("(9 + 3)", (9 + 3)) + test("(9+3) / 11", (9+3.0) / 11) + test("9 - 12 - 6", 9 - 12 - 6) + test("9 - (12 - 6)", 9 - (12 - 6)) + test("2*3.14159", 2*3.14159) + test("3.1415926535*3.1415926535 / 10", 3.1415926535*3.1415926535 / 10) + test("PI * PI / 10", math.pi * math.pi / 10) + test("PI*PI/10", math.pi*math.pi/10) + test("PI^2", math.pi**2) + test("round(PI^2)", round(math.pi**2)) + test("6.02E23 * 8.048", 6.02E23 * 8.048) + test("e / 3", math.e / 3) + test("sin(PI/2)", math.sin(math.pi/2)) + test("10+sin(PI/4)^2", 10 + math.sin(math.pi/4)**2) + test("trunc(E)", int(math.e)) + test("trunc(-E)", int(-math.e)) + test("round(E)", round(math.e)) + test("round(-E)", round(-math.e)) + test("E^PI", math.e**math.pi) + test("exp(0)", 1) + test("exp(1)", math.e) + test("2^3^2", 2**3**2) + test("(2^3)^2", (2**3)**2) + test("2^3+2", 2**3+2) + test("2^3+5", 2**3+5) + test("2^9", 2**9) + test("sgn(-2)", -1) + test("sgn(0)", 0) + test("sgn(0.1)", 1) + test("foo(0.1)", None) + test("round(E, 3)", round(math.e, 3)) + test("round(PI^2, 3)", round(math.pi**2, 3)) + test("sgn(cos(PI/4))", 1) + test("sgn(cos(PI/2))", 0) + test("sgn(cos(PI*3/4))", -1) + + +""" +Test output: +>python fourFn.py +9 = 9 ['9'] => ['9'] +-9 = -9 ['-', '9'] => ['9', 'unary -'] +--9 = 9 ['-', '-', '9'] => ['9', 'unary -', 'unary -'] +-E = -2.718281828459045 ['-', 'E'] => ['E', 'unary -'] +9 + 3 + 6 = 18 ['9', '+', '3', '+', '6'] => ['9', '3', '+', '6', '+'] +9 + 3 / 11 = 9.272727272727273 ['9', '+', '3', '/', '11'] => ['9', '3', '11', '/', '+'] +(9 + 3) = 12 [['9', '+', '3']] => ['9', '3', '+'] +(9+3) / 11 = 1.0909090909090908 [['9', '+', '3'], '/', '11'] => ['9', '3', '+', '11', '/'] +9 - 12 - 6 = -9 ['9', '-', '12', '-', '6'] => ['9', '12', '-', '6', '-'] +9 - (12 - 6) = 3 ['9', '-', ['12', '-', '6']] => ['9', '12', '6', '-', '-'] +2*3.14159 = 6.28318 ['2', '*', '3.14159'] => ['2', '3.14159', '*'] +3.1415926535*3.1415926535 / 10 = 0.9869604400525172 ['3.1415926535', '*', '3.1415926535', '/', '10'] => ['3.1415926535', '3.1415926535', '*', '10', '/'] +PI * PI / 10 = 0.9869604401089358 ['PI', '*', 'PI', '/', '10'] => ['PI', 'PI', '*', '10', '/'] +PI*PI/10 = 0.9869604401089358 ['PI', '*', 'PI', '/', '10'] => ['PI', 'PI', '*', '10', '/'] +PI^2 = 9.869604401089358 ['PI', '^', '2'] => ['PI', '2', '^'] +round(PI^2) = 10 [('round', 1), [['PI', '^', '2']]] => ['PI', '2', '^', ('round', 1)] +6.02E23 * 8.048 = 4.844896e+24 ['6.02E23', '*', '8.048'] => ['6.02E23', '8.048', '*'] +e / 3 = 0.9060939428196817 ['E', '/', '3'] => ['E', '3', '/'] +sin(PI/2) = 1.0 [('sin', 1), [['PI', '/', '2']]] => ['PI', '2', '/', ('sin', 1)] +10+sin(PI/4)^2 = 10.5 ['10', '+', ('sin', 1), [['PI', '/', '4']], '^', '2'] => ['10', 'PI', '4', '/', ('sin', 1), '2', '^', '+'] +trunc(E) = 2 [('trunc', 1), [['E']]] => ['E', ('trunc', 1)] +trunc(-E) = -2 [('trunc', 1), [['-', 'E']]] => ['E', 'unary -', ('trunc', 1)] +round(E) = 3 [('round', 1), [['E']]] => ['E', ('round', 1)] +round(-E) = -3 [('round', 1), [['-', 'E']]] => ['E', 'unary -', ('round', 1)] +E^PI = 23.140692632779263 ['E', '^', 'PI'] => ['E', 'PI', '^'] +exp(0) = 1.0 [('exp', 1), [['0']]] => ['0', ('exp', 1)] +exp(1) = 2.718281828459045 [('exp', 1), [['1']]] => ['1', ('exp', 1)] +2^3^2 = 512 ['2', '^', '3', '^', '2'] => ['2', '3', '2', '^', '^'] +(2^3)^2 = 64 [['2', '^', '3'], '^', '2'] => ['2', '3', '^', '2', '^'] +2^3+2 = 10 ['2', '^', '3', '+', '2'] => ['2', '3', '^', '2', '+'] +2^3+5 = 13 ['2', '^', '3', '+', '5'] => ['2', '3', '^', '5', '+'] +2^9 = 512 ['2', '^', '9'] => ['2', '9', '^'] +sgn(-2) = -1 [('sgn', 1), [['-', '2']]] => ['2', 'unary -', ('sgn', 1)] +sgn(0) = 0 [('sgn', 1), [['0']]] => ['0', ('sgn', 1)] +sgn(0.1) = 1 [('sgn', 1), [['0.1']]] => ['0.1', ('sgn', 1)] +foo(0.1) failed eval: invalid identifier 'foo' ['0.1', ('foo', 1)] +round(E, 3) = 2.718 [('round', 2), [['E'], ['3']]] => ['E', '3', ('round', 2)] +round(PI^2, 3) = 9.87 [('round', 2), [['PI', '^', '2'], ['3']]] => ['PI', '2', '^', '3', ('round', 2)] +sgn(cos(PI/4)) = 1 [('sgn', 1), [[('cos', 1), [['PI', '/', '4']]]]] => ['PI', '4', '/', ('cos', 1), ('sgn', 1)] +sgn(cos(PI/2)) = 0 [('sgn', 1), [[('cos', 1), [['PI', '/', '2']]]]] => ['PI', '2', '/', ('cos', 1), ('sgn', 1)] +sgn(cos(PI*3/4)) = -1 [('sgn', 1), [[('cos', 1), [['PI', '*', '3', '/', '4']]]]] => ['PI', '3', '*', '4', '/', ('cos', 1), ('sgn', 1)] +""" diff --git a/unitTests.py b/unitTests.py index 17654e4..4e27776 100644 --- a/unitTests.py +++ b/unitTests.py @@ -129,35 +129,60 @@ class ParseFourFnTest(ParseTestCase): import examples.fourFn as fourFn def test(s, ans): fourFn.exprStack = [] - results = (fourFn.BNF()).parseString(s) - resultValue = fourFn.evaluateStack(fourFn.exprStack) - self.assertTrue(resultValue == ans, "failed to evaluate %s, got %f" % (s, resultValue)) - print_(s, "->", resultValue) + results = fourFn.BNF().parseString(s) + try: + resultValue = fourFn.evaluate_stack(fourFn.exprStack) + except Exception: + self.assertIsNone(ans, "exception raised for expression {0!r}".format(s)) + else: + self.assertTrue(resultValue == ans, "failed to evaluate %s, got %f" % (s, resultValue)) + print_(s, "->", resultValue) + - from math import pi, exp - e = exp(1) + import math + e = math.exp(1) test("9", 9) - test("9 + 3 + 6", 18) - test("9 + 3 / 11", 9.0+3.0/11.0) - test("(9 + 3)", 12) - test("(9+3) / 11", (9.0+3.0)/11.0) - test("9 - (12 - 6)", 3) - test("2*3.14159", 6.28318) - test("3.1415926535*3.1415926535 / 10", 3.1415926535*3.1415926535/10.0) - test("PI * PI / 10", pi*pi/10.0) - test("PI*PI/10", pi*pi/10.0) + test("-9", -9) + test("--9", 9) + test("-E", -math.e) + test("9 + 3 + 6", 9 + 3 + 6) + test("9 + 3 / 11", 9 + 3.0 / 11) + test("(9 + 3)", (9 + 3)) + test("(9+3) / 11", (9 + 3.0) / 11) + test("9 - 12 - 6", 9 - 12 - 6) + test("9 - (12 - 6)", 9 - (12 - 6)) + test("2*3.14159", 2 * 3.14159) + test("3.1415926535*3.1415926535 / 10", 3.1415926535 * 3.1415926535 / 10) + test("PI * PI / 10", math.pi * math.pi / 10) + test("PI*PI/10", math.pi * math.pi / 10) + test("PI^2", math.pi ** 2) + test("round(PI^2)", round(math.pi ** 2)) test("6.02E23 * 8.048", 6.02E23 * 8.048) - test("e / 3", e/3.0) - test("sin(PI/2)", 1.0) - test("trunc(E)", 2.0) - test("E^PI", e**pi) - test("2^3^2", 2**3**2) - test("2^3+2", 2**3+2) - test("2^9", 2**9) + test("e / 3", math.e / 3) + test("sin(PI/2)", math.sin(math.pi / 2)) + test("10+sin(PI/4)^2", 10 + math.sin(math.pi / 4) ** 2) + test("trunc(E)", int(math.e)) + test("trunc(-E)", int(-math.e)) + test("round(E)", round(math.e)) + test("round(-E)", round(-math.e)) + test("E^PI", math.e ** math.pi) + test("exp(0)", 1) + test("exp(1)", math.e) + test("2^3^2", 2 ** 3 ** 2) + test("(2^3)^2", (2 ** 3) ** 2) + test("2^3+2", 2 ** 3 + 2) + test("2^3+5", 2 ** 3 + 5) + test("2^9", 2 ** 9) test("sgn(-2)", -1) test("sgn(0)", 0) test("sgn(0.1)", 1) + test("foo(0.1)", None) + test("round(E, 3)", round(math.e, 3)) + test("round(PI^2, 3)", round(math.pi ** 2, 3)) + test("sgn(cos(PI/4))", 1) + test("sgn(cos(PI/2))", 0) + test("sgn(cos(PI*3/4))", -1) class ParseSQLTest(ParseTestCase): def runTest(self): |