Help improve program for parsing simple rules

Paul McGuire ptmcg at austin.rr.com
Fri Apr 17 13:15:28 EDT 2009


On Apr 17, 10:43 am, John Machin <sjmac... at lexicon.net> wrote:
>
> I don't see how it can handle the chained relop in the last two
> testcases e. g. '0.00 LE A LE 4.00' -- unless relops are chained by
> default in your parser.
>

John -

First of all, to respect precedence of operations, higher level
precedences are parsed and grouped first.  If you left off the parse
actions and just printed out the parse tree created by the example
(using asList()), for "A + B * C" you would get ['A', '+', [ 'B', '*',
'C' ]].  If you expand that test case to "A + B * C + D", you would
get ['A', '+', [ 'B', '*', 'C' ], '+', 'D' ].  This is counter to the
conventional infix parser that would create [['A', '+', [ 'B', '*',
'C' ]], '+', 'D' ], in which binary operators typicaly return
'operand' 'operator' 'operand' triples, and either operand might be a
nested parse tree.

As it happens, when using pyparsing's operatorPrecedence helper, *all*
binary operators at the same precedence level are actually parsed in a
single chain.

This is why you see this logic in EvalAddOp.eval:

    def eval(self):
        sum = self.value[0].eval()
        for op,val in operatorOperands(self.value[1:]):
            if op == '+':
                sum += val.eval()
            if op == '-':
                sum -= val.eval()
        return sum

operatorOperands is a little generator that returns operator-operand
pairs, beginning at the second (that is, the "1th") token in the
list.  You can't just do the simple evaluation of "operand1 operator
operand2", you have to build up the sum by first evaluating operand1,
and then iterating over the operator-operand pairs in the rest of the
list.  Same thing for the muliplication operators.

For the comparison operators, things are a little more involved.
"operand1 operator1 operand2 operator2 operand3" (as in "0.00 LE A LE
4.00") has to evaluate as

op1 operator1 op2 AND op2 operator2 op3

So EvalComparisonOp's eval method looks like:

    def eval(self):
        val1 = self.value[0].eval()
        ret = True
        for op,val in operatorOperands(self.value[1:]):
            fn = EvalComparisonOp.opMap[op]
            val2 = val.eval()
            ret = ret and fn(val1,val2)
            val1 = val2
        return ret

The first term is evaluated and stored in val1.  Then each
comparisonop-operand pair is extracted, the operand is eval()'ed and
stored in val2, and the comparison method that is mapped to the
comparisonop is called using val1 and val2.  Then, to move on to do
the next comparison, val2 is stored into val1, and the we iterate to
the next comparison-operand pair.  In fact, not only does this handle
"0.00 LE A LE 4.00", but it could also evaluate "0.00 LE A LE 4.00 LE
E > D".  (I see that I should actually do some short-circuiting here -
if ret is false after calling fn(val1,val2), I should just break out
at that point.  I'll have that fixed in the online version shortly.)

-- Paul




More information about the Python-list mailing list