badger0053
badger0053

Reputation: 1209

pyparsing using own class

I am trying to follow this tutorial and am having a hard time converting the grammar used in the tutorial to pyparsing grammar. The gist of the blog is creating an expression language to parse and execute dictionary comparisons.

properties = {
    "name": "David Bowie",
    "years_active2": 47
}

expression to evaluate:

properties["name"] == "David Bowie"

The grammar they used is:

 expr:   literal                      { return $1 } 
  | "properties" "[" literal "]" { return PropertyLookup($3) }
  | expr "[" expr "]"            { return Index($1, $3) }
  | expr "and" expr              { return And($1, $3) }
  | expr "==" expr               { return Equals($1, $3) }
  | expr ">" expr                { return GreaterThan($1, $3) }
;

literal:   QUOTED_STRING  { return Literal($1) }
     | DECIMAL_NUMBER { return Literal($1) }
;

so far I have:

string_literal = pp.Word(pp.alphas, pp.alphanums)
numeric_literal = pp.Word(pp.nums)
literal = string_literal | numeric_literal
properties = "properties" + "[" + literal + "]"  

PropertyLookup(), Index(), And(), Equals(), and GreaterThan() are custom classes created for building an expression.

How can I change my 4th line of properties to act the same way their second line does? I'm mostly confused about how to pass the literal into a custom class such as PropertyLookup() which is

class PropertyLookup(object):
    def evaluate(self, props):
        return props[self.key]

Any help is appreciated!

Upvotes: 1

Views: 707

Answers (1)

PaulMcG
PaulMcG

Reputation: 63747

To have pyparsing construct your classes for you, attach them as parse actions to the expressions. Here's a small example of how to do this:

import pyparsing as pp

class Node(object):
    def __init__(self, tokens):
        self._tokens = tokens
    def __repr__(self):
        return "{}: ({})".format(self.__class__.__name__, self._tokens.asList())

class AlphaNode(Node): pass
class NumericNode(Node): pass
class BinExprNode(Node): pass
class OperatorNode(Node): pass

alpha_expr = pp.Word(pp.alphas)
numeric_expr = pp.Word(pp.nums)
operand = alpha_expr | numeric_expr
operator = pp.oneOf("+ - * /")
bin_expr = operand + pp.OneOrMore(operator + operand)

# by setting the node classes as each expression's parse action, 
# the node instances will be constructed at parse time and returned 
# as pyparsing's parse results
alpha_expr.addParseAction(AlphaNode)
numeric_expr.addParseAction(NumericNode)
operator.addParseAction(OperatorNode)
bin_expr.addParseAction(BinExprNode)

result = bin_expr.parseString("a + 27 * X")
print(repr(result[0]))

prints

BinExprNode: ([AlphaNode: (['a']), OperatorNode: (['+']), NumericNode: (['27']), OperatorNode: (['*']), AlphaNode: (['X'])])

You will also have to clear up some left-recursion in your grammar - you won't be able to implement an expr that begins with expr, this will just recurse on itself until you hit the recursion limit. Start by defining your base operand expression as:

expr:   literal                 
  | "properties" ("[" expr "]")...
;

Use pyparsing's OneOrMore class for the repetition of the indexing into "properties". Then use the methods in the pyparsing examples such as SimpleBool.py or evalArith.py to build up infix notation expressions and evaluators.

Upvotes: 3

Related Questions