summaryrefslogtreecommitdiff
path: root/src/examples/eval_arith.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/examples/eval_arith.py')
-rw-r--r--src/examples/eval_arith.py227
1 files changed, 227 insertions, 0 deletions
diff --git a/src/examples/eval_arith.py b/src/examples/eval_arith.py
new file mode 100644
index 0000000..4d7683c
--- /dev/null
+++ b/src/examples/eval_arith.py
@@ -0,0 +1,227 @@
+# eval_arith.py
+#
+# Copyright 2009, 2011 Paul McGuire
+#
+# Expansion on the pyparsing example simpleArith.py, to include evaluation
+# of the parsed tokens.
+#
+# Added support for exponentiation, using right-to-left evaluation of
+# operands
+#
+from pyparsing import Word, nums, alphas, Combine, oneOf, \
+ opAssoc, operatorPrecedence, Literal
+
+class EvalConstant(object):
+ "Class to evaluate a parsed constant or variable"
+ vars_ = {}
+ def __init__(self, tokens):
+ self.value = tokens[0]
+ def eval(self):
+ if self.value in EvalConstant.vars_:
+ return EvalConstant.vars_[self.value]
+ else:
+ return float(self.value)
+
+class EvalSignOp(object):
+ "Class to evaluate expressions with a leading + or - sign"
+ def __init__(self, tokens):
+ self.sign, self.value = tokens[0]
+ def eval(self):
+ mult = {'+':1, '-':-1}[self.sign]
+ return mult * self.value.eval()
+
+def operatorOperands(tokenlist):
+ "generator to extract operators and operands in pairs"
+ it = iter(tokenlist)
+ while 1:
+ try:
+ yield (it.next(), it.next())
+ except StopIteration:
+ break
+
+class EvalPowerOp(object):
+ "Class to evaluate multiplication and division expressions"
+ def __init__(self, tokens):
+ self.value = tokens[0]
+ def eval(self):
+ res = self.value[-1].eval()
+ for val in self.value[-3::-2]:
+ res = val.eval()**res
+ return res
+
+class EvalMultOp(object):
+ "Class to evaluate multiplication and division expressions"
+ def __init__(self, tokens):
+ self.value = tokens[0]
+ def eval(self):
+ prod = self.value[0].eval()
+ for op,val in operatorOperands(self.value[1:]):
+ if op == '*':
+ prod *= val.eval()
+ if op == '/':
+ prod /= val.eval()
+ return prod
+
+class EvalAddOp(object):
+ "Class to evaluate addition and subtraction expressions"
+ def __init__(self, tokens):
+ self.value = tokens[0]
+ def eval(self):
+ sum = self.value[0].eval()
+ for op,val in operatorOperands(self.value[1:]):
+ if op == '+':
+ sum += val.eval()
+ if op == '-':
+ sum -= val.eval()
+ return sum
+
+class EvalComparisonOp(object):
+ "Class to evaluate comparison expressions"
+ opMap = {
+ "<" : lambda a,b : a < b,
+ "<=" : lambda a,b : a <= b,
+ ">" : lambda a,b : a > b,
+ ">=" : lambda a,b : a >= b,
+ "!=" : lambda a,b : a != b,
+ "=" : lambda a,b : a == b,
+ "LT" : lambda a,b : a < b,
+ "LE" : lambda a,b : a <= b,
+ "GT" : lambda a,b : a > b,
+ "GE" : lambda a,b : a >= b,
+ "NE" : lambda a,b : a != b,
+ "EQ" : lambda a,b : a == b,
+ "<>" : lambda a,b : a != b,
+ }
+ def __init__(self, tokens):
+ self.value = tokens[0]
+ def eval(self):
+ val1 = self.value[0].eval()
+ for op,val in operatorOperands(self.value[1:]):
+ fn = EvalComparisonOp.opMap[op]
+ val2 = val.eval()
+ if not fn(val1,val2):
+ break
+ val1 = val2
+ else:
+ return True
+ return False
+
+
+# define the parser
+integer = Word(nums)
+real = Combine(Word(nums) + "." + Word(nums))
+variable = Word(alphas,exact=1)
+operand = real | integer | variable
+
+signop = oneOf('+ -')
+multop = oneOf('* /')
+plusop = oneOf('+ -')
+expop = Literal('**')
+
+# use parse actions to attach EvalXXX constructors to sub-expressions
+operand.setParseAction(EvalConstant)
+arith_expr = operatorPrecedence(operand,
+ [
+ (signop, 1, opAssoc.RIGHT, EvalSignOp),
+ (expop, 2, opAssoc.LEFT, EvalPowerOp),
+ (multop, 2, opAssoc.LEFT, EvalMultOp),
+ (plusop, 2, opAssoc.LEFT, EvalAddOp),
+ ])
+
+comparisonop = oneOf("< <= > >= != = <> LT GT LE GE EQ NE")
+comp_expr = operatorPrecedence(arith_expr,
+ [
+ (comparisonop, 2, opAssoc.LEFT, EvalComparisonOp),
+ ])
+
+def main():
+ # sample expressions posted on comp.lang.python, asking for advice
+ # in safely evaluating them
+ rules=[
+ '( A - B ) = 0',
+ '(A + B + C + D + E + F + G + H + I) = J',
+ '(A + B + C + D + E + F + G + H) = I',
+ '(A + B + C + D + E + F) = G',
+ '(A + B + C + D + E) = (F + G + H + I + J)',
+ '(A + B + C + D + E) = (F + G + H + I)',
+ '(A + B + C + D + E) = F',
+ '(A + B + C + D) = (E + F + G + H)',
+ '(A + B + C) = (D + E + F)',
+ '(A + B) = (C + D + E + F)',
+ '(A + B) = (C + D)',
+ '(A + B) = (C - D + E - F - G + H + I + J)',
+ '(A + B) = C',
+ '(A + B) = 0',
+ '(A+B+C+D+E) = (F+G+H+I+J)',
+ '(A+B+C+D) = (E+F+G+H)',
+ '(A+B+C+D)=(E+F+G+H)',
+ '(A+B+C)=(D+E+F)',
+ '(A+B)=(C+D)',
+ '(A+B)=C',
+ '(A-B)=C',
+ '(A/(B+C))',
+ '(B/(C+D))',
+ '(G + H) = I',
+ '-0.99 LE ((A+B+C)-(D+E+F+G)) LE 0.99',
+ '-0.99 LE (A-(B+C)) LE 0.99',
+ '-1000.00 LE A LE 0.00',
+ '-5000.00 LE A LE 0.00',
+ 'A < B',
+ 'A < 7000',
+ 'A = -(B)',
+ 'A = C',
+ 'A = 0',
+ 'A GT 0',
+ 'A GT 0.00',
+ 'A GT 7.00',
+ 'A LE B',
+ 'A LT -1000.00',
+ 'A LT -5000',
+ 'A LT 0',
+ 'A=(B+C+D)',
+ 'A=B',
+ 'I = (G + H)',
+ '0.00 LE A LE 4.00',
+ '4.00 LT A LE 7.00',
+ '0.00 LE A LE 4.00 LE E > D',
+ '2**2**(A+3)',
+ ]
+ vars_={'A': 0, 'B': 1.1, 'C': 2.2, 'D': 3.3, 'E': 4.4, 'F': 5.5, 'G':
+ 6.6, 'H':7.7, 'I':8.8, 'J':9.9}
+
+ # define tests from given rules
+ tests = []
+ for t in rules:
+ t_orig = t
+ t = t.replace("=","==")
+ t = t.replace("EQ","==")
+ t = t.replace("LE","<=")
+ t = t.replace("GT",">")
+ t = t.replace("LT","<")
+ t = t.replace("GE",">=")
+ t = t.replace("LE","<=")
+ t = t.replace("NE","!=")
+ t = t.replace("<>","!=")
+ tests.append( (t_orig,eval(t,vars_)) )
+
+ # copy vars_ to EvalConstant lookup dict
+ EvalConstant.vars_ = vars_
+ failed = 0
+ for test,expected in tests:
+ ret = comp_expr.parseString(test)[0]
+ parsedvalue = ret.eval()
+ print test, expected, parsedvalue,
+ if parsedvalue != expected:
+ print "<<< FAIL"
+ failed += 1
+ else:
+ print
+
+ print
+ if failed:
+ print failed, "tests FAILED"
+ else:
+ print "all tests PASSED"
+
+if __name__=='__main__':
+ main()