Source code for gavo.adql.bindinggrammar
"""
Helpers for making the definition of grammars of the complexity of our
ADQL grammar a bit easier.
Here, we define the AutoBindingGrammar, which is used in adql.grammar to
build a grammar with parseActions defined in adql.nodes, where the
node classes say which symbols they are responsible for.
"""
#c Copyright 2008-2023, the GAVO project <gavo@ari.uni-heidelberg.de>
#c
#c This program is free software, covered by the GNU GPL. See the
#c COPYING file in the source distribution.
from gavo.adql import nodes
[docs]class SingleBindingDict(dict):
"""A dictionary that lets you assign each key just once.
"""
def __setitem__(self, key, value):
if key in self:
raise Exception(f"Trying to rebind key: {key}")
dict.__setitem__(self, key, value)
[docs]class AutoBindingGrammar:
"""A container for pyparsing symbols that receive their parse actions
from a sequence of "binders"
The binders are passed into the constructor. Most of these will be
derived from nodes.ADQLNode; but they can also be callables with
parseActionFor set (see the nodes.symbolAction decorator). Making this more
generic would take quite a bit of extra thought. Anyway, what I'm looking at
here on whatever is in nodes:
* type: a name of a symbol that should have nodeClass.fromParseResult
as a parse action.
* bindings: as type, but a list of symbol names; when bindings is
present, type is ignored.
* parseActionFor: that's a list of symbol names the "node" (which more
likely is just some function) must be called for as a parse action.
* collapsible: if set on a node class, the parse action will be
nodes.autocollapse (that's for transparent nodes I'm eliding out
of the tree unless they're more than just a single token).
"""
# instance variables must be preset on the class level here so
# __setattr__ doesn't tick them into the symbols dict.
_actionForSym = None
_symbols = None
def __init__(self, binders):
self._actionForSym = SingleBindingDict()
self._symbols = {}
for binder in binders:
if hasattr(binder, "type"):
for binding in getattr(binder, "bindings", [binder.type]):
if binding:
self.bind_(binding, binder)
if hasattr(binder, "parseActionFor"):
for sym in binder.parseActionFor:
self._actionForSym[sym] = binder
def __setattr__(self, symName, value):
if hasattr(self, symName):
object.__setattr__(self, symName, value)
return
if symName in self._actionForSym:
value.setParseAction(self._actionForSym[symName])
self._symbols[symName] = value
def __getattr__(self, symName):
if symName not in self._symbols:
raise AttributeError(f"Symbol {symName} not yet defined.")
return self._symbols[symName]
[docs] def bind_(self, symName, nodeClass):
"""enters nodeClass as a handler for symName.
This also deals with autocollapsining single nodes.
"""
if getattr(nodeClass, "collapsible", False):
self._actionForSym[symName] = \
lambda s, pos, toks: nodes.autocollapse(nodeClass, toks)
else:
self._actionForSym[symName] = \
lambda s, pos, toks: nodeClass.fromParseResult(toks)
[docs] def getSymbols_(self):
"""returns a dict of all symbols defined.
"""
return self._symbols