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