summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorSoniEx2 <endermoneymod@gmail.com>2020-11-06 21:03:04 -0300
committerSoniEx2 <endermoneymod@gmail.com>2020-11-06 21:05:25 -0300
commit436939628ff1c5bbc37d5c91c4a7c68d83f12f49 (patch)
tree6930fb0cb46774c79845956a98aee7760504a208
parentfa55f672dfe9e20cadfef5b4ac8bb14a691277f8 (diff)
Add optional subvalues
-rw-r--r--abdl/__init__.py10
-rw-r--r--abdl/_vm.py7
-rw-r--r--setup.py2
-rw-r--r--testing/test_abdl.py7
4 files changed, 21 insertions, 5 deletions
diff --git a/abdl/__init__.py b/abdl/__init__.py
index efcd3df..7208e44 100644
--- a/abdl/__init__.py
+++ b/abdl/__init__.py
@@ -14,7 +14,7 @@
 # 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/>.
 
-"""A Boneless Datastructure Language, version 2.1.
+"""A Boneless Datastructure Language, version 2.2.
 
 ABDL expressions are regex-like constructs for matching and validating object
 structures. They can be used with JSON and similar formats, and even
@@ -76,7 +76,11 @@ Language Reference:
         the enclosed ABDL expression to the value (or index) being processed.
         A subvalue enables the ability to match multiple values on the same
         object, and accepts a value if and only the enclosed expression
-        matches the value.
+        matches the value. A subvalue can be made optional by the presence of
+        a ``?`` after the subvalue - in case of no match, it will just omit
+        the relevant keys in the result. Optional subvalues are unrelated to
+        non-validating syntax elements (see below), they just use the same
+        syntax.
 
         Some syntax elements can be validating or non-validating. Validating
         syntax elements will raise a :py:exc:`abdl.exceptions.ValidationError`
@@ -100,7 +104,7 @@ Language Reference:
 
             arrow ::= '->'
             keymatch ::= '[' {predicate} abdlexpression ']'
-            subvalue ::= '(' {predicate} abdlexpression ')'
+            subvalue ::= '(' {predicate} abdlexpression ')' ['?']
 
         For a description of the terminals "parameter", "literal", "regex" and
         "predicate", see "Syntax Elements of ABDL Expressions" above.
diff --git a/abdl/_vm.py b/abdl/_vm.py
index 2f76586..c6cd7be 100644
--- a/abdl/_vm.py
+++ b/abdl/_vm.py
@@ -162,7 +162,7 @@ class ValueSubtree(PatternElement):
 
     def __init__(self, toks):
         self.key = toks[0]
-        self.skippable = toks[1] == '?'
+        self.optional = toks[1] == '?'
 
     def on_not_in_key(self, frame, path, defs):
         assert not path[-1].empty
@@ -171,8 +171,13 @@ class ValueSubtree(PatternElement):
         return True
 
     def _filter(self, parent, defs):
+        has_results = False
         for pair in match_helper(self.key, defs, parent):
+            has_results = True
             yield (pair, parent)
+        # support for optional subtrees
+        if self.optional and not has_results:
+            yield ({}, parent)
 
     def collect_params(self, res: list):
         for sub in self.key:
diff --git a/setup.py b/setup.py
index 163b9a3..8a4e81a 100644
--- a/setup.py
+++ b/setup.py
@@ -1,3 +1,3 @@
 import setuptools
 
-setuptools.setup(name="gan0f74bd87a23b515b45da7e6f5d9cc82380443dab", version="2.1.4", packages=["abdl"], install_requires=["pyparsing >= 2.4.2"])
+setuptools.setup(name="gan0f74bd87a23b515b45da7e6f5d9cc82380443dab", version="2.2.0", packages=["abdl"], install_requires=["pyparsing >= 2.4.2"])
diff --git a/testing/test_abdl.py b/testing/test_abdl.py
index d28658a..79a0edf 100644
--- a/testing/test_abdl.py
+++ b/testing/test_abdl.py
@@ -262,6 +262,13 @@ def test_empty(foo, pat):
         yield {}
     assert all(LogAndCompare(pat.match(foo), deep(foo)))
 
+def test_optional_value_subtree():
+    pat = abdl.compile("(->foo'foo'?)?(->bar'bar')")
+    matcher = pat.match({'foo': 1, 'bar': 2})
+    assert list(matcher) == [{'foo': ('foo', 1), 'bar': ('bar', 2)}]
+    matcher = pat.match({'bar': 2})
+    assert list(matcher) == [{'bar': ('bar', 2)}]
+
 # FIXME
 #@hypothesis.given(objtree, st.text())
 #def test_exhaustive(foo, pat):