diff options
Diffstat (limited to 'examples/eval_arith.py')
-rw-r--r-- | examples/eval_arith.py | 252 |
1 files changed, 146 insertions, 106 deletions
diff --git a/examples/eval_arith.py b/examples/eval_arith.py index 8f3e996..bfd0ce0 100644 --- a/examples/eval_arith.py +++ b/examples/eval_arith.py @@ -8,28 +8,43 @@ # Added support for exponentiation, using right-to-left evaluation of # operands # -from pyparsing import Word, nums, alphas, Combine, oneOf, \ - opAssoc, infixNotation, Literal +from pyparsing import ( + Word, + nums, + alphas, + Combine, + oneOf, + opAssoc, + infixNotation, + Literal, +) + class EvalConstant: "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: "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] + mult = {"+": 1, "-": -1}[self.sign] return mult * self.value.eval() + def operatorOperands(tokenlist): "generator to extract operators and operands in pairs" it = iter(tokenlist) @@ -39,67 +54,79 @@ def operatorOperands(tokenlist): except StopIteration: break + class EvalPowerOp: "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 + res = val.eval() ** res return res + class EvalMultOp: "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 == '*': + for op, val in operatorOperands(self.value[1:]): + if op == "*": prod *= val.eval() - if op == '/': + if op == "/": prod /= val.eval() return prod + class EvalAddOp: "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 == '+': + for op, val in operatorOperands(self.value[1:]): + if op == "+": sum += val.eval() - if op == '-': + if op == "-": sum -= val.eval() return sum + class EvalComparisonOp: "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, - } + "<": 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:]): + for op, val in operatorOperands(self.value[1:]): fn = EvalComparisonOp.opMap[op] val2 = val.eval() - if not fn(val1,val2): + if not fn(val1, val2): break val1 = val2 else: @@ -110,104 +137,116 @@ class EvalComparisonOp: # define the parser integer = Word(nums) real = Combine(Word(nums) + "." + Word(nums)) -variable = Word(alphas,exact=1) +variable = Word(alphas, exact=1) operand = real | integer | variable -signop = oneOf('+ -') -multop = oneOf('* /') -plusop = oneOf('+ -') -expop = Literal('**') +signop = oneOf("+ -") +multop = oneOf("* /") +plusop = oneOf("+ -") +expop = Literal("**") # use parse actions to attach EvalXXX constructors to sub-expressions operand.setParseAction(EvalConstant) -arith_expr = infixNotation(operand, +arith_expr = infixNotation( + operand, [ - (signop, 1, opAssoc.RIGHT, EvalSignOp), - (expop, 2, opAssoc.LEFT, EvalPowerOp), - (multop, 2, opAssoc.LEFT, EvalMultOp), - (plusop, 2, opAssoc.LEFT, EvalAddOp), - ]) + (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 = infixNotation(arith_expr, - [ - (comparisonop, 2, opAssoc.LEFT, EvalComparisonOp), - ]) +comp_expr = infixNotation( + 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} + 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_)) ) + 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: + for test, expected in tests: ret = comp_expr.parseString(test)[0] parsedvalue = ret.eval() print(test, expected, parsedvalue) @@ -215,9 +254,9 @@ def main(): print("<<< FAIL") failed += 1 else: - print('') + print("") - print('') + print("") if failed: print(failed, "tests FAILED") return 1 @@ -225,5 +264,6 @@ def main(): print("all tests PASSED") return 0 -if __name__=='__main__': + +if __name__ == "__main__": exit(main()) |