Thread: [Pyparsing] operatorPrecedence: Problems with Power and Unary Minus
Brought to you by:
ptmcg
From: Eike W. <eik...@gm...> - 2008-09-06 20:11:53
|
The parser generated by the function "operatorPrecedence" can not correctly parse the power operator in conjunction with the unary minus operator. The Python reference says: "The power operator binds more tightly than unary operators on its left; it binds less tightly than unary operators on its right." [http://docs.python.org/ref/power.html] I think this can't be done with operatorPrecedence. Is there a more elegant (shorter) way to get the correct syntax than what I did in this example program here? http://pastebin.com/f7eb6be8a Maybe a parser that gets this right could be added to operatorPrecedence. The syntax could look like this for example: expression = operatorPrecedence( u_expr, [OpPower(power='**', sign=oneOf('+ -')), (oneOf('* /'), 2, opAssoc.LEFT), (oneOf('+ -'), 2, opAssoc.LEFT), ]) I think an example like the little program I've posted here, should be included in the examples, so that newbies get mathematical expressions right. For me mathematical expressions are by far the most tricky part in writing the parser for my toy language. Kind regards, Eike |
From: Paul M. <pt...@au...> - 2008-09-07 14:32:02
|
I worked on this problem a bit last night. The opAssoc.RIGHT parameter is there to address this problem, and in fact, this expression: a**b**c Does get correctly evaluated as (a**(b**c)). But things get muddled when unary signs are added, and I think this points up a bug in operatorPrecedence. As your pastebin code comments state: #Power and unary operations are intertwined to get correct operator precedence: # -a**-b == -(a ** (-b)) Which should be supported using this form of operatorPrecedence: u_expr = Word(nums) | Word(alphas) expression = operatorPrecedence( u_expr, [('**', 2, opAssoc.RIGHT), (oneOf('+ -'), 1, opAssoc.RIGHT), (oneOf('* /'), 2, opAssoc.LEFT), (oneOf('+ -'), 2, opAssoc.LEFT), ]) For the moment, this expression correctly parses "a**b**c" as "(a**(b**c))", but can only parse "-a**-b" if it is given as "-a**(-b)". Ideally, your request should be handled by the operatorPrecedence API as it exists today - I don't think any more specialized interface should be needed. But I'll see if I make any progress with finding this problem in the next day or so. -- Paul |
From: Paul M. <pt...@au...> - 2008-09-07 15:15:47
|
Eike - Well, this came together a bit faster than I'd thought. The solution begins with an understanding of operatorPrecedence. Here is the expression that is published in the online example simpleArith.py: integer = Word(nums).setParseAction(lambda t:int(t[0])) variable = Word(alphas,exact=1) operand = integer | variable expop = Literal('**') signop = oneOf('+ -') multop = oneOf('* /') plusop = oneOf('+ -') factop = Literal('!') expr = operatorPrecedence( operand, [(factop, 1, opAssoc.LEFT), (expop, 2, opAssoc.RIGHT), (signop, 1, opAssoc.RIGHT), (multop, 2, opAssoc.LEFT), (plusop, 2, opAssoc.LEFT),] ) As I mentioned earlier, this will only recognize "-a**-b" if given as "-a**(-b)". The reason for this is that operator precedence cannot look "down" the precedence chain unless the next term is enclosed in parentheses. My first attempt at fixing this was to change the definition of operand to include an optional leading sign, and this parsed successfully. But then it dawned on me that this same thing can be accomplished by inserting another signop operator *above* expop, as in: expr = operatorPrecedence( operand, [(factop, 1, opAssoc.LEFT), (signop, 1, opAssoc.RIGHT), (expop, 2, opAssoc.RIGHT), (signop, 1, opAssoc.RIGHT), (multop, 2, opAssoc.LEFT), (plusop, 2, opAssoc.LEFT),] ) I think this is the correct solution, rather than mucking about with operatorPrecedence, or requiring strange embellishments to one's operand expression. I don't think this is a general issue with unary operators or right-associated operations, I think this is just a special case borne out of the definition of exponentiation with respect to leading sign operators. I'll fix simpleArith.py to correctly include the extra unary sign operator precedence level, and also include some examples. I'll also include simpleArith2.py, which actually evaluates the parsed expression. -- Paul |