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.py298
1 files changed, 298 insertions, 0 deletions
diff --git a/abdl/_vm.py b/abdl/_vm.py
new file mode 100644
index 0000000..1de2e15
--- /dev/null
+++ b/abdl/_vm.py
@@ -0,0 +1,298 @@
+# This file is part of A Boneless Datastructure Language
+# Copyright (C) 2020  Soni L.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# 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/>.
+
+import collections.abc
+import re
+from abdl import predicates
+from abdl import exceptions
+
+class PatternElement:
+    def on_not_in_key(self, frame, path, defs):
+        raise NotImplementedError
+
+    def on_in_key(self, frame, path, defs):
+        raise NotImplementedError
+
+    def collect_params(self, res: list):
+        pass
+
+class Arrow(PatternElement):
+    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))
+        return False
+
+class StringKey(PatternElement):
+    def __init__(self, toks):
+        self.key = toks[0]
+        self.skippable = toks[1] == '?'
+
+    def on_in_key(self, frame, path, defs):
+        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].empty = False
+        return True
+
+    def extract(self, obj):
+        try:
+            yield (self.key, obj[self.key])
+        except (TypeError, IndexError, KeyError):
+            if not self.skippable:
+                raise exceptions.ValidationError
+
+class RegexKey(PatternElement):
+    def __init__(self, toks):
+        self.key = toks[0]
+        self.compiled = re.compile(self.key)
+        self.skippable = toks[1] == '?'
+
+    def on_in_key(self, frame, path, defs):
+        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)
+        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_:
+            try:
+                if self.compiled.search(el[0]):
+                    yield el
+                elif not self.skippable:
+                    raise exceptions.ValidationError
+            except TypeError:
+                if not self.skippable:
+                    raise exceptions.ValidationError
+
+class KeySubtree(PatternElement):
+    def __init__(self, toks):
+        self.key = toks[0]
+        self.skippable = toks[1] == '?'
+
+    def on_not_in_key(self, frame, path, defs):
+        path[-1].subtree = True
+        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 filter(self, iter_, defs):
+        for x in iter_:
+            for y in match_helper(self.key, defs, x[0]):
+                yield (y, x[1])
+
+    def collect_params(self, res: list):
+        for sub in self.key:
+            sub.collect_params(res)
+
+class ValueSubtree(PatternElement):
+    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)
+        return True
+
+    def filter(self, parent, defs):
+        for x in match_helper(self.key, defs, parent):
+            yield (x, parent)
+
+    def collect_params(self, res: list):
+        for sub in self.key:
+            sub.collect_params(res)
+
+class Ident(PatternElement):
+    def __init__(self, toks):
+        self.key = toks[0]
+
+    def on_not_in_key(self, frame, path, defs):
+        path[-1].name = self.key
+        path[-1].empty = False
+        return True
+
+class Param(PatternElement):
+    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):
+        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].empty = False
+        return True
+
+    def extract(self, obj, key):
+        try:
+            yield (key, obj[key])
+        except (TypeError, IndexError, KeyError):
+            if not self.skippable:
+                raise exceptions.ValidationError
+
+    def collect_params(self, res: list):
+        res.append(self.key)
+
+    def get_value(self, defs):
+        return defs[self.key]
+
+class ApplyPredicate(PatternElement):
+    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)
+        del path[-1].iterator
+        path[-1].iterator = filtered_iterator
+        del filtered_iterator
+        path[-1].empty = False
+        return True
+
+    def check(self, defs, obj):
+        if predicates._to_predicate(defs[self.key]).accept(obj):
+            return True
+        if self.skippable:
+            return False
+        raise exceptions.ValidationError
+
+    def on_not_in_key(self, frame, path, defs):
+        assert len(path) == 1
+        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 collect_params(self, res: list):
+        res.append(self.key)
+
+class End(PatternElement):
+    def on_in_key(self, frame, path, defs):
+        try:
+            path[-1].next()
+            return False
+        except StopIteration:
+            path.pop()
+            while frame.prev() and not isinstance(frame.current_op, End):
+                pass
+            if not frame.prev():
+                # FIXME?
+                path.clear()
+        return True # FIXME?
+
+def _pairs(obj):
+    if isinstance(obj, collections.abc.Mapping):
+        return iter(obj.items())
+    elif isinstance(obj, collections.abc.Sequence):
+        return iter(enumerate(obj, 0))
+    elif 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
+
+class Holder:
+    def __init__(self, key, value, name, parent=None, iterator=None, empty=False, subtree=False):
+        self.name = name
+        self.key = key
+        self.value = value
+        self.empty = empty
+        self._iterator = iterator
+        self.parent = parent
+        self.subtree = subtree
+
+    @property
+    def iterator(self):
+        if self._iterator is None:
+            self._iterator = _pairs(self.parent)
+        return self._iterator
+
+    @iterator.setter
+    def iterator(self, value):
+        assert self._iterator is None
+        self._iterator = value
+
+    @iterator.deleter
+    def iterator(self):
+        self._iterator = None
+
+    def next(self):
+        self.key, self.value = next(self.iterator)
+
+class Frame:
+    def __init__(self, ops):
+        self.ops = ops
+        self.pc = -1
+
+    def next(self):
+        pc = self.pc + 1
+        if pc >= len(self.ops):
+            return False
+        self.pc = pc
+        return True
+
+    @property
+    def current_op(self):
+        return self.ops[self.pc]
+
+    def prev(self):
+        pc = self.pc - 1
+        if pc < 0:
+            return False
+        self.pc = pc
+        return True
+
+def match_helper(ops, defs, tree):
+    frame = Frame(ops)
+
+    path = [Holder(key=None, value=tree, parent=None, iterator=iter(()), name=None)]
+    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)
+            yield res
+            assert len(path) == 1 or isinstance(frame.current_op, End)
+            frame.prev()
+            in_key = True
+        else:
+            if in_key:
+                in_key = frame.current_op.on_in_key(frame, path, defs)
+            else:
+                in_key = frame.current_op.on_not_in_key(frame, path, defs)