summary refs log tree commit diff stats
path: root/abdl/predicates.py
diff options
context:
space:
mode:
Diffstat (limited to 'abdl/predicates.py')
-rw-r--r--abdl/predicates.py94
1 files changed, 94 insertions, 0 deletions
diff --git a/abdl/predicates.py b/abdl/predicates.py
new file mode 100644
index 0000000..4df9cdf
--- /dev/null
+++ b/abdl/predicates.py
@@ -0,0 +1,94 @@
+# 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/>.
+
+"""Classes for use with ABDL's Predicate system.
+
+See ``abdl.predicates.Predicate`` and the language reference for details.
+"""
+
+# pylint: disable=too-few-public-methods
+
+class Predicate:
+    """A predicate checks if an object is accepted in an ABDL expression.
+    """
+
+    def accept(self, obj):
+        """Checks if ``obj`` is accepted by this predicate.
+
+        Args:
+            obj: The object to be accepted.
+
+        Returns:
+            bool: True if the object is accepted, False otherwise.
+        """
+        raise NotImplementedError
+
+class IsInstance(Predicate):
+    """A ``Predicate`` that accepts objects according to ``isinstance``.
+
+    Used to implement ``:$foo`` when ``defs['foo']`` is neither a ``Predicate`` nor a
+    ``tuple``.
+
+    One generally does not need to explicitly create instances of this class, as abdl
+    accepts types wherever instances of this class are accepted.
+
+    Args:
+        ty (type): The expected type.
+    """
+
+    def __init__(self, type_):
+        self.type_ = type_
+        """The expected type."""
+
+    def accept(self, obj):
+        return isinstance(obj, self.type_)
+
+class Union(Predicate):
+    """A ``Predicate`` built from multiple predicates.
+
+    Used to implement ``:$foo`` when ``defs['foo']`` is not a ``Predicate`` but is a
+    ``tuple``. Can be constructed from any iterable. If the iterable yields different
+    results on different runs (e.g. is a generator), the behaviour is undefined.
+
+    Has semantics equivalent to ``any(p.accept(obj) for p in predicates)``, except p
+    can also be a type or a tuple.
+
+    One generally does not need to explicitly create instances of this class, as abdl
+    accepts tuples wherever instances of this class are accepted.
+
+    Args:
+        predicates: An iterable of predicates, types and tuples that yields the same
+            sequence on different iterations.
+    """
+
+    def __init__(self, predicates):
+        self.predicates = predicates
+        """The iterable of predicates, types and tuples."""
+
+    def accept(self, obj):
+        for predicate in self.predicates:
+            if _to_predicate(predicate).accept(obj):
+                return True
+        return False
+
+def _to_predicate(obj):
+    if isinstance(obj, Predicate):
+        return obj
+    if isinstance(obj, tuple):
+        return Union(obj)
+    # I don't know if anyone relies on the old behaviour of passing the thing directly to isinstance
+    # but this lets the exceptions be raised almost exactly like before
+    return IsInstance(obj)