diff options
Diffstat (limited to 'abdl/_vm.py')
-rw-r--r-- | abdl/_vm.py | 200 |
1 files changed, 138 insertions, 62 deletions
diff --git a/abdl/_vm.py b/abdl/_vm.py index 41f28eb..0ec1018 100644 --- a/abdl/_vm.py +++ b/abdl/_vm.py @@ -14,28 +14,61 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>. +"""[Internal] The VM Interpreter. + +This module holds the VM instructions and the interpreter loop. +""" + import collections.abc import re from abdl import predicates from abdl import exceptions class PatternElement: + """A parsed pattern element (token) and VM instruction.""" + def on_not_in_key(self, frame, path, defs): - raise NotImplementedError + """Called while the key matching step of the pattern isn't done. + + Returns whether the key matching step is ready. + """ + raise RuntimeError(self) def on_in_key(self, frame, path, defs): - raise NotImplementedError + """Called while the key matching step of the pattern is done. + + Returns whether the key matching step is ready. + """ + raise RuntimeError(self) def collect_params(self, res: list): - pass + """Appends parameter names used in this pattern to ``res``. + """ + + @classmethod + def action(cls, toks): + """Parse action, for pyparsing. + + Returns: + PatternElement: Parsed token. + """ + return [cls(toks)] class Arrow(PatternElement): + """The 'arrow' token.""" + def on_not_in_key(self, frame, path, defs): assert not path[-1].empty - path.append(Holder(key=None, value=None, name=None, parent=path[-1].value, empty=True)) + path.append(Holder(value=None, parent=path[-1].value, empty=True)) return False + @classmethod + def action(cls, toks): + return [cls()] + class StringKey(PatternElement): + """The 'literal' token.""" + def __init__(self, toks): self.key = toks[0] self.skippable = toks[1] == '?' @@ -44,11 +77,11 @@ class StringKey(PatternElement): return self.on_not_in_key(frame, path, defs) def on_not_in_key(self, frame, path, defs): - path[-1].iterator = self.extract(path[-1].parent) + path[-1].iterator = self._extract(path[-1].parent) path[-1].empty = False return True - def extract(self, obj): + def _extract(self, obj): try: yield (self.key, obj[self.key]) except (TypeError, IndexError, KeyError): @@ -56,6 +89,8 @@ class StringKey(PatternElement): raise exceptions.ValidationError class RegexKey(PatternElement): + """The 'regex' token.""" + def __init__(self, toks): self.key = toks[0] self.compiled = re.compile(self.key) @@ -65,18 +100,18 @@ class RegexKey(PatternElement): return self.on_not_in_key(frame, path, defs) def on_not_in_key(self, frame, path, defs): - filtered_iterator = self.filter(path[-1].iterator) + filtered_iterator = self._filter(path[-1].iterator) del path[-1].iterator path[-1].iterator = filtered_iterator del filtered_iterator path[-1].empty = False return True - def filter(self, iter_): - for el in iter_: + def _filter(self, iter_): + for elem in iter_: try: - if self.compiled.search(el[0]): - yield el + if self.compiled.search(elem[0]): + yield elem elif not self.skippable: raise exceptions.ValidationError except TypeError: @@ -84,6 +119,8 @@ class RegexKey(PatternElement): raise exceptions.ValidationError class KeySubtree(PatternElement): + """The 'keymatch' token.""" + def __init__(self, toks): self.key = toks[0] self.skippable = toks[1] == '?' @@ -93,45 +130,49 @@ class KeySubtree(PatternElement): def on_not_in_key(self, frame, path, defs): path[-1].subtree = True - filtered_iterator = self.filter(path[-1].iterator, defs, name=path[-1].name) + filtered_iterator = self._filter(path[-1].iterator, defs, name=path[-1].name) del path[-1].iterator path[-1].iterator = filtered_iterator del filtered_iterator path[-1].empty = False return True - def filter(self, iter_, defs, name): - for x in iter_: - for y in match_helper(self.key, defs, x[0]): + def _filter(self, iter_, defs, name): + for pair in iter_: + for matches in match_helper(self.key, defs, pair[0]): if name: # FIXME this "name" thing is a bit suboptimal - y.setdefault(name, x) - yield (y, x[1]) + matches.setdefault(name, pair) + yield (matches, pair[1]) def collect_params(self, res: list): for sub in self.key: sub.collect_params(res) class ValueSubtree(PatternElement): + """The 'subvalue' token.""" + def __init__(self, toks): self.key = toks[0] self.skippable = toks[1] == '?' def on_not_in_key(self, frame, path, defs): assert not path[-1].empty - path.append(Holder(key=None, value=None, name=None, parent=path[-1].value, empty=False, subtree=True)) - path[-1].iterator = self.filter(path[-1].parent, defs) + path.append(Holder(value=None, parent=path[-1].value, empty=False, subtree=True)) + path[-1].iterator = self._filter(path[-1].parent, defs) return True - def filter(self, parent, defs): - for x in match_helper(self.key, defs, parent): - yield (x, parent) + def _filter(self, parent, defs): + for pair in match_helper(self.key, defs, parent): + yield (pair, parent) def collect_params(self, res: list): for sub in self.key: sub.collect_params(res) class Ident(PatternElement): + """The 'identifier' token.""" + def __init__(self, toks): self.key = toks[0] @@ -141,6 +182,8 @@ class Ident(PatternElement): return True class Param(PatternElement): + """The 'parameter' token.""" + def __init__(self, toks): assert isinstance(toks[1], Ident) self.skippable = toks[0] == '?' @@ -150,11 +193,11 @@ class Param(PatternElement): return self.on_not_in_key(frame, path, defs) def on_not_in_key(self, frame, path, defs): - path[-1].iterator = self.extract(path[-1].parent, defs[self.key]) + path[-1].iterator = self._extract(path[-1].parent, defs[self.key]) path[-1].empty = False return True - def extract(self, obj, key): + def _extract(self, obj, key): try: yield (key, obj[key]) except (TypeError, IndexError, KeyError): @@ -164,24 +207,24 @@ class Param(PatternElement): def collect_params(self, res: list): res.append(self.key) - def get_value(self, defs): - return defs[self.key] - class ApplyPredicate(PatternElement): + """The 'predicate' token.""" + def __init__(self, toks): assert isinstance(toks[1], Ident) self.skippable = toks[0] == '?' self.key = toks[1].key def on_in_key(self, frame, path, defs): - filtered_iterator = self.filter(path[-1].iterator, defs) + filtered_iterator = self._filter(path[-1].iterator, defs) del path[-1].iterator path[-1].iterator = filtered_iterator del filtered_iterator path[-1].empty = False return True - def check(self, defs, obj): + def _check(self, defs, obj): + # pylint: disable=protected-access if predicates._to_predicate(defs[self.key]).accept(obj): return True if self.skippable: @@ -190,19 +233,21 @@ class ApplyPredicate(PatternElement): def on_not_in_key(self, frame, path, defs): assert len(path) == 1 - if not self.check(defs, path[-1].value): + if not self._check(defs, path[-1].value): path.clear() return False - def filter(self, iter_, defs): - for el in iter_: - if self.check(defs, el[1]): - yield el + def _filter(self, iter_, defs): + for elem in iter_: + if self._check(defs, elem[1]): + yield elem def collect_params(self, res: list): res.append(self.key) class End(PatternElement): + """Pseudo-token, used to advance iteration.""" + def on_in_key(self, frame, path, defs): try: path[-1].next() @@ -212,25 +257,33 @@ class End(PatternElement): while frame.prev() and not isinstance(frame.current_op, End): pass if not frame.prev(): - # FIXME? path.clear() - return True # FIXME? + return True + + @classmethod + def action(cls, toks): + return [cls()] def _pairs(obj): if isinstance(obj, collections.abc.Mapping): return iter(obj.items()) - elif isinstance(obj, collections.abc.Sequence): + if isinstance(obj, collections.abc.Sequence): return iter(enumerate(obj, 0)) - elif isinstance(obj, collections.abc.Set): + if isinstance(obj, collections.abc.Set): return iter(((e, e) for e in obj)) - else: - # maybe there's more stuff I can implement later - raise TypeError + # maybe there's more stuff I can implement later + raise TypeError class Holder: - def __init__(self, key, value, name, parent=None, iterator=None, empty=False, subtree=False): - self.name = name - self.key = key + """Stores a single match and associated metadata. + + A single match is generally a key-value pair, but may be a collection of + named pairs in the case of subtree matches. + """ + + def __init__(self, value, parent=None, iterator=None, empty=False, subtree=False): + self.name = None + self.match = None self.value = value self.empty = empty self._iterator = iterator @@ -239,6 +292,7 @@ class Holder: @property def iterator(self): + """Returns the iterator for this match.""" if self._iterator is None: self._iterator = _pairs(self.parent) return self._iterator @@ -253,46 +307,68 @@ class Holder: self._iterator = None def next(self): - self.key, self.value = next(self.iterator) + """Updates the stored match.""" + self.match, self.value = next(self.iterator) -class Frame: +class _Frame: def __init__(self, ops): self.ops = ops - self.pc = -1 + self.iar = -1 def next(self): - pc = self.pc + 1 - if pc >= len(self.ops): + """Advances the instruction address register. + + Returns: + ``True`` if successful, ``False``otherwise. + """ + + iar = self.iar + 1 + if iar >= len(self.ops): return False - self.pc = pc + self.iar = iar return True @property def current_op(self): - return self.ops[self.pc] + """Returns the current instruction.""" + return self.ops[self.iar] def prev(self): - pc = self.pc - 1 - if pc < 0: + """Rewinds the instruction address register. + + Returns: + ``True`` if successful, ``False``otherwise. + """ + + iar = self.iar - 1 + if iar < 0: return False - self.pc = pc + self.iar = iar return True def match_helper(ops, defs, tree): - frame = Frame(ops) + """The interpreter loop itself. + + The opcode/token dispatch logic is implemented through ``PatternElement``. + + Yields: + dict: Matches. + """ + + frame = _Frame(ops) - path = [Holder(key=None, value=tree, parent=None, iterator=iter(()), name=None)] + path = [Holder(value=tree, parent=None, iterator=iter(()))] in_key = False while path: if not frame.next(): assert not path[-1].empty res = {} - for h in path: - if h.subtree: - for name, kv in h.key.items(): - res[name] = kv - elif h.name is not None: - res[h.name] = (h.key, h.value) + for holder in path: + if holder.subtree: + for name, pair in holder.match.items(): + res[name] = pair + elif holder.name is not None: + res[holder.name] = (holder.match, holder.value) yield res assert len(path) == 1 or isinstance(frame.current_op, End) if not frame.prev(): |