Reputation: 1607
The continuation of my PyParsing quest is parsing nested ternary expressions (e.g. (x == 1 ? true : (y == 10 ? 100 : 200))
). As such, I constructed the following expression. Which, to me, seems intuitive. However I get no matches:
any = Word(printables)
conditional = Forward()
sub_exp = (conditional | any)
conditional = Literal('(') + sub_exp + Literal('?') + sub_exp + Literal(':') + sub_exp + Literal(')')
for exp in conditional.scanString(block_str):
print exp
Initially I thought that the problem was with printables consuming everything; I set the excludeChars to not match :?)(
, but that didn't help either. The alternative was to construct nested expressions, one each for the "( ?", "? : " and ": )" blocks. But this approach is very messy. Does anybody have any recommendations on parsing ternary expressions?
UPDATE Using the answer from below, but modified to work with scanString:
When using scanString however, it returns a lot of other matches too (basically, anything matching atom).
lpar = Literal('(').suppress()
rpar = Literal(')').suppress()
any = Combine(OneOrMore(Word(printables, excludeChars='()?:') | White(' ', max=1)))
expr = Forward()
atom = any | Group(lpar + expr + Literal('?') + expr + Literal(':') + expr + rpar)
expr << Literal('(') + atom + ZeroOrMore(expr) + Literal('?') + atom + ZeroOrMore(expr) + Literal(':') + atom + ZeroOrMore(expr) + Literal(')')
for ternary_exp in expr.scanString(block_str):
print ternary_exp
Upvotes: 5
Views: 1217
Reputation: 63762
For this kind of arithmetic expression parsing, try using pyparsing's builtin infixNotation
(formerly known as operatorPrecedence
):
from pyparsing import *
integer = Word(nums)
variable = Word(alphas, alphanums)
boolLiteral = oneOf("true false")
operand = boolLiteral | variable | integer
comparison_op = oneOf("== <= >= != < >")
QM,COLON = map(Literal,"?:")
expr = infixNotation(operand,
[
(comparison_op, 2, opAssoc.LEFT),
((QM,COLON), 3, opAssoc.LEFT),
])
print expr.parseString("(x==1? true: (y == 10? 100 : 200) )")
Prints
[[['x', '==', '1'], '?', 'true', ':', [['y', '==', '10'], '?', '100', ':', '200']]]
infixNotation
takes care of all of the recursive expressions, and resolves precedence of operations and overriding of that precedence using ()'s.
Upvotes: 1
Reputation: 25548
I think your problem is two-fold: the whitespace (which isn't handled well by your any
definition), and the recursion (which should use the <<
operator):
lpar = Literal( '(' ).suppress()
rpar = Literal( ')' ).suppress()
any = Combine(OneOrMore(Word(printables, excludeChars='()?:') | White(' ',max=1)))
expr = Forward()
atom = any | Group( lpar + expr + Literal('?') + expr + Literal(':') + expr + rpar )
expr << atom + ZeroOrMore( expr )
For example,
t2 = '(x == 1 ? true : (y == 10 ? 100 : 200))'
expr.parseString(t2)
([(['x == 1 ', '?', 'true ', ':', (['y == 10 ', '?', '100 ', ':', '200'], {})], {})], {})
Upvotes: 3