summary refs log tree commit diff stats
path: root/abdl
diff options
context:
space:
mode:
Diffstat (limited to 'abdl')
-rw-r--r--abdl/__init__.py10
-rw-r--r--abdl/_vm.py7
2 files changed, 13 insertions, 4 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: