summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul McGuire <ptmcg@austin.rr.com>2019-08-03 16:08:29 -0500
committerPaul McGuire <ptmcg@austin.rr.com>2019-08-03 16:08:29 -0500
commit36aa91c4f603ddb15054b0ff9f5daf5044b1dd31 (patch)
treea5dde20d0aa5c2c431855f4bc901255e7e5da691
parentb9c11d7f84c9886ba037d6b3f7e2f6ec27f5ed45 (diff)
downloadpyparsing-git-36aa91c4f603ddb15054b0ff9f5daf5044b1dd31.tar.gz
Update fourFn.py to handle functions that take multiple args, and nested functions
-rw-r--r--examples/SimpleCalc.py239
-rw-r--r--examples/fourFn.py424
-rw-r--r--unitTests.py69
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):