summary refs log tree commit diff stats
path: root/abdl/_vm.py
diff options
context:
space:
mode:
Diffstat (limited to 'abdl/_vm.py')
-rw-r--r--abdl/_vm.py200
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():