I need to parse this using pyparsing: iif(condition,value if true,value if false)
, but this kind of ternary comparisson shall have another comparisson, I mean:
`iif(iif(condition1,value1,value2)>iif(condition2,value1,value2),value3,value4)`
I found this:
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),
])
which is able to parse this:
expr.parseString("(x==1? true: (y == 10? 100 : 200) )")
but I was unable to modify this code to fit my needs. How can I achieve this?
UPDATE
Thanks to Mr. Paul I came up with this solution:
arith_expr = Forward()
iif = CaselessKeyword("iif")
open = Literal("(")
close = Literal(")")
var_name = pyparsing_common.identifier()
fn_call = Group(iif + open - Group(Optional(delimitedList(arith_expr))) + close)
arith_operand = fn_call | num
rel_comparison_operator = oneOf("< > <= >=")
eq_comparison_operator = oneOf("== !=")
plus_minus_operator = oneOf("+ -")
mult_div_operator = oneOf("* / %")
arith_expr <<= infixNotation(arith_operand,
[
# add other operators here - in descending order of precedence
# http://www.tutorialspoint.com/cprogramming/c_operators_precedence.htm
('-', 1, opAssoc.RIGHT),
(mult_div_operator, 2, opAssoc.LEFT,),
(plus_minus_operator, 2, opAssoc.LEFT,),
(rel_comparison_operator, 2, opAssoc.LEFT,),
(eq_comparison_operator, 2, opAssoc.LEFT,),
]
)
I'm using some of my previous rules. Now I'm voting to close this post.
As @sepp2k mentions in his comment, the string you are trying to parse isn't infix notation, although you may end up using as an operand in infix notation. And the arguments you pass to iif
may themselves be infix notation expressions. So infix notation will definitely be part of this parser, but it won't be the part that parses your iif
function call.
Here is how the function call would look in pyparsing:
fn_call = pp.Group(var_name + LPAREN - pp.Group(pp.Optional(pp.delimitedList(arith_expr))) + RPAREN)
The operands you use to define an arithmetic expression could themselves include a function call, so the parser's recursion will require you to use pyparsing's Forward class.
arith_expr = pp.Forward()
This will allow you to use arith_expr
in other sub-expressions (like we just did in fn_call) before we have fully defined just what arith_expr
looks like.
Cutting to the chase, here is a minimal parser to parse your iif
function:
import pyparsing as pp
# for recursive infix notations, or those with many precedence levels, it is best to enable packrat parsing
pp.ParserElement.enablePackrat()
LPAREN, RPAREN = map(pp.Suppress, "()")
arith_expr= pp.Forward()
var_name = pp.pyparsing_common.identifier()
integer = pp.pyparsing_common.integer()
fn_call = pp.Group(var_name + LPAREN - pp.Group(pp.Optional(pp.delimitedList(arith_expr))) + RPAREN)
arith_operand = fn_call | var_name | integer
rel_comparison_operator = pp.oneOf("< > <= >=")
eq_comparison_operator = pp.oneOf("== !=")
plus_minus_operator = pp.oneOf("+ -")
mult_div_operator = pp.oneOf("* / %")
arith_expr <<= pp.infixNotation(arith_operand,
[
# add other operators here - in descending order of precedence
# http://www.tutorialspoint.com/cprogramming/c_operators_precedence.htm
(mult_div_operator, 2, pp.opAssoc.LEFT,),
(plus_minus_operator, 2, pp.opAssoc.LEFT,),
(rel_comparison_operator, 2, pp.opAssoc.LEFT,),
(eq_comparison_operator, 2, pp.opAssoc.LEFT,),
]
)
Using runTests, we can try it out against a few test cases:
tests = """\
cos(60)
sqrt(1 - sin(60) * sin(60))
divmod(a, 100)
iif(iif(condition1,value1,value2)>iif(condition2,value1,value2),value3,value4)
"""
arith_expr.runTests(tests)
Prints:
cos(60)
[['cos', [60]]]
[0]:
['cos', [60]]
[0]:
cos
[1]:
[60]
sqrt(1 - sin(60) * sin(60))
[['sqrt', [[1, '-', [['sin', [60]], '*', ['sin', [60]]]]]]]
[0]:
['sqrt', [[1, '-', [['sin', [60]], '*', ['sin', [60]]]]]]
[0]:
sqrt
[1]:
[[1, '-', [['sin', [60]], '*', ['sin', [60]]]]]
[0]:
[1, '-', [['sin', [60]], '*', ['sin', [60]]]]
[0]:
1
[1]:
-
[2]:
[['sin', [60]], '*', ['sin', [60]]]
[0]:
['sin', [60]]
[0]:
sin
[1]:
[60]
[1]:
*
[2]:
['sin', [60]]
[0]:
sin
[1]:
[60]
divmod(a, 100)
[['divmod', ['a', 100]]]
[0]:
['divmod', ['a', 100]]
[0]:
divmod
[1]:
['a', 100]
iif(iif(condition1,value1,value2)>iif(condition2,value1,value2),value3,value4)
[['iif', [[['iif', ['condition1', 'value1', 'value2']], '>', ['iif', ['condition2', 'value1', 'value2']]], 'value3', 'value4']]]
[0]:
['iif', [[['iif', ['condition1', 'value1', 'value2']], '>', ['iif', ['condition2', 'value1', 'value2']]], 'value3', 'value4']]
[0]:
iif
[1]:
[[['iif', ['condition1', 'value1', 'value2']], '>', ['iif', ['condition2', 'value1', 'value2']]], 'value3', 'value4']
[0]:
[['iif', ['condition1', 'value1', 'value2']], '>', ['iif', ['condition2', 'value1', 'value2']]]
[0]:
['iif', ['condition1', 'value1', 'value2']]
[0]:
iif
[1]:
['condition1', 'value1', 'value2']
[1]:
>
[2]:
['iif', ['condition2', 'value1', 'value2']]
[0]:
iif
[1]:
['condition2', 'value1', 'value2']
[1]:
value3
[2]:
value4