Reputation: 1209
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
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