# 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)