pyparsing infix notation with non-grouping parentheses

248 Views Asked by At

I'm working on a billing application where users can enter arbitrary mathematical expressions. For example, a line item might be defined as (a + b) * c.

pyparsing handles the order of operations well enough in this case:

import pyparsing as pp

operand = pp.Word(pp.alphanums)
boolean_operator = pp.one_of(['||', '&&'])
comparison_operator = pp.one_of(['<', '<=', '>', '>=', '==', '!='])

infix_pattern = pp.infix_notation(
    operand,
    [
        ('^', 2, pp.OpAssoc.LEFT),
        ('*', 2, pp.OpAssoc.LEFT),
        ('/', 2, pp.OpAssoc.LEFT),
        ('+', 2, pp.OpAssoc.LEFT),
        ('-', 2, pp.OpAssoc.LEFT),
        (comparison_operator, 2, pp.OpAssoc.LEFT),
        (boolean_operator, 2, pp.OpAssoc.LEFT),
    ]
)

print(infix_pattern.parse_string('(a + b) * c'))
# [[['a', '+', 'b'], '*', 'c']]

Users can also enter a handful of well-known functions, including the calling parentheses and arguments. For example, if a line item is defined as a(b) == c(d), pyparsing gets confused:

print(infix_pattern.parse_string('a(b) == c(d)'))
# ['a']

What I would like to see in this case is ['a(b)', '==', 'c(d)']. What do I need to do differently in order to keep the first example working as-is and get the desired behavior from the second example?

1

There are 1 best solutions below

1
Big McLargeHuge On

Okay I think I got it:

import pyparsing as pp

operand = pp.Word(pp.alphanums) + pp.Opt(pp.Literal('(') + ... + pp.Literal(')'))
boolean_operator = pp.one_of(['||', '&&'])
comparison_operator = pp.one_of(['<', '<=', '>', '>=', '==', '!='])

infix_pattern = pp.infix_notation(
    operand,
    [
        ('^', 2, pp.OpAssoc.LEFT),
        (pp.one_of(['*', '/']), 2, pp.OpAssoc.LEFT),
        (pp.one_of(['+', '-']), 2, pp.OpAssoc.LEFT),
        (comparison_operator, 2, pp.OpAssoc.LEFT),
        (boolean_operator, 2, pp.OpAssoc.LEFT),
    ]
)

print(infix_pattern.parse_string('(a + b) * c'))
# [[['a', '+', 'b'], '*', 'c']]

print(infix_pattern.parse_string('a(b) == c(d)'))
# [['a', '(', 'b', ')', '==', 'c', '(', 'd', ')']]

I could combine the a(b) and c(d) with pp.Combine or something but this is fine.