简体   繁体   中英

Parsing nested ternary expressions

The continuation of my PyParsing quest is parsing nested ternary expressions (eg (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

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'], {})], {})], {})

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM