JB2
JB2

Reputation: 1607

Parsing nested ternary expressions

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

Answers (2)

PaulMcG
PaulMcG

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

xnx
xnx

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

Related Questions