2

I am using pyparsing to write a simple calculator. after reading fourFn example (and not completely understanding it!) i decided to modify it a little to serve my needs.

here is my code(most of it is taken from here):

def summer(p1, p2):
    return p1 + p2

class NumericStringParser(object):
    '''
    Most of this code comes from the fourFn.py pyparsing example

    '''

    def pushFirst(self, strg, loc, toks):
        self.exprStack.append(toks[0])

    def pushUMinus(self, strg, loc, toks):
        if toks and toks[0] == '-':
            self.exprStack.append('unary -')

    def __init__(self):
        """
        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 ]*
        """
        point = Literal(".")
        e = CaselessLiteral("E")

        fnumber = Combine(Word("+-" + nums, nums) +
                          Optional(point + Optional(Word(nums))) +
                          Optional(e + Word("+-" + nums, nums)))
        ident = Word(alphas, alphas + nums + "_$")
        plus = Literal("+")
        minus = Literal("-")
        mult = Literal("*")
        div = Literal("/")
        lpar = Literal("(").suppress()
        rpar = Literal(")").suppress()
        comma = Literal(',').suppress()
        addop = plus | minus
        multop = mult | div
        expop = Literal("^")
        pi = CaselessLiteral("PI")
        expr = Forward()
        atom = ((Optional(oneOf("- +")) +
                 (
                 pi | e | fnumber | ident + lpar + expr + rpar | ident + lpar + expr + comma + expr + rpar).setParseAction(
                     self.pushFirst))
                | Optional(oneOf("- +")) + Group(lpar + expr + rpar)
                ).setParseAction(self.pushUMinus)

        # 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 + ZeroOrMore((expop + factor).setParseAction(self.pushFirst))
        term = factor + ZeroOrMore((multop + factor).setParseAction(self.pushFirst))
        expr << term + ZeroOrMore((addop + term).setParseAction(self.pushFirst))
        # addop_term = ( addop + term ).setParseAction( self.pushFirst )
        # general_term = term + ZeroOrMore( addop_term ) | OneOrMore( addop_term)
        # expr <<  general_term
        self.bnf = expr
        # map operator symbols to corresponding arithmetic operations
        epsilon = 1e-12
        self.opn = {"+": operator.add,
                    "-": operator.sub,
                    "*": operator.mul,
                    "/": operator.truediv,
                    "^": operator.pow}
        self.fn = {"sin": math.sin,
                   "cos": math.cos,
                   "tan": math.tan,
                   "abs": abs,
                   "trunc": lambda a: int(a),
                   "round": round,
                   "sgn": lambda a: abs(a) > epsilon and cmp(a, 0) or 0}
        self.fn2 = {"sum": summer}

    def evaluateStack(self, s):
        op = s.pop()
        if op == 'unary -':
            res = self.evaluateStack(s)
            return -res
        if op in "+-*/^":
            op2 = self.evaluateStack(s)
            op1 = self.evaluateStack(s)
            res = self.opn[op](op1, op2)
            return res
        elif op == "PI":
            return math.pi  # 3.1415926535
        elif op == "E":
            return math.e  # 2.718281828
        elif op in self.fn:
            return self.fn[op](self.evaluateStack(s))
        elif op in self.fn2:
            op2 = self.evaluateStack(s)
            op1 = self.evaluateStack(s)
            return self.fn2[op](op1, op2)
        elif op[0].isalpha():
            return 0
        else:
            return float(op)

    def eval(self, num_string, parseAll=True):
        self.exprStack = []
        results = self.bnf.parseString(num_string, parseAll)

        val = self.evaluateStack(self.exprStack[:])
        return val


n = NumericStringParser()
print(n.eval('(4.0*sum(3,3))^1'))

as you can see, i just added ident + lpar + expr + comma + expr + rpar to atom (which i guess it is a unit that finally evaluates to a number) and added fn2 to evaluateStack(self, s).

if i use print(n.eval('sum(3,7.0)')) everything is fine and the result is 10. but when i use print(n.eval('1*sum(3,7.0)')) the result is 30. showing that first argument of sum is accounted for twice.

Can you help me to understand what is missing? or at least help me understand the problem?

Edit: when i print stack (s) on the top of evaluateStack(self, s), stack contains first argument twice:

n.eval('1.0*sum(3+0.0,7)') --> ['1.0', '3', '0.0', '+', '3', '0.0', '+', '7', 'sum', '*']

Community
  • 1
  • 1
samad montazeri
  • 1,203
  • 16
  • 28

0 Answers0