Search code examples
pythonpyparsing

Implementing property access operator in pyparsing


I'm trying to implement a DSL for writing custom condition expressions, which include keywords for certain objects and properties on those. E.g.:

user.foo.bar or user.baz

user is a keyword which is substituted with the current user object. I've also implemented or, and and not operators just fine based on the existing sample.

I have implemented property access like this:

from pyparsing import infixNotation, opAssoc, Keyword, Word, alphas

class User:
    pass

class PropertyAccess:
    def __init__(self, t):
        self.value = reduce(lambda o, p: getattr(o, p, None), t[0][0::2])

user = Keyword('user')
user.setParseAction(User)
identifier = Word(alphas + '_')
property_access_expr = infixNotation(user | identifier, [
    ('.', 2, opAssoc.LEFT, PropertyAccess)
])

While this works, it also allows expressions like foo.bar, which are nonsensical. A property access must start with one of the supported keywords like user, otherwise they should be invalid. How can I reimplement the grammar to restrict that?


Solution

  • You're making the task needlessly difficult by using infixNotation. infixNotation is useful for defining multiple different infix operators with different priorities (i.e. order of operations). But property access has only a single operator (.), and is easily implemented with OneOrMore:

    property_access_expr = user + OneOrMore('.' + identifier)
    property_access_expr.setParseAction(PropertyAccess)
    

    Changing the grammar like this requires a minor modification of the PropertyAccess class. Previously the input was a nested list, but now it is a flat list. Consequently, we have to change t[0][0::2] to t[0::2].

    class PropertyAccess:
        def __init__(self, t):
            self.value = reduce(lambda o, p: getattr(o, p, None), t[0::2])
    

    Now the grammar correctly parses only attribute access that starts with user.:

    >>> property_access_expr.parseString('user.foo.bar')
    ([<__main__.PropertyAccess object at 0x0000000002DD4048>], {})
    >>> property_access_expr.parseString('foo.bar')
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    pyparsing.ParseException: Expected "user" (at char 0), (line:1, col:1)