Add `!else` support for conditional block v0.14.0
authorPhilippe Proulx <eeppeliteloop@gmail.com>
Thu, 5 Oct 2023 20:57:57 +0000 (16:57 -0400)
committerPhilippe Proulx <eeppeliteloop@gmail.com>
Fri, 6 Oct 2023 18:01:52 +0000 (14:01 -0400)
This patch adds the support of `!else` within an `!if` context:

    !if something
      11 22 33
    !else
      44 55 66
    !end

This is just equivalent to:

    !if something
      11 22 33
    !end

    !if {not something}
      44 55 66
    !end

but more readable/natural.

Change-Id: I0efa68a65c485d7dbf8124cd51fc5ed3c1ac9f46
Signed-off-by: Philippe Proulx <eeppeliteloop@gmail.com>
README.adoc
normand/normand.py
pyproject.toml
tests/fail-cond-blk-else-missing-end.nt [new file with mode: 0644]
tests/fail-macro-def-in-cond.nt
tests/pass-cond-blk-else.nt [new file with mode: 0644]
tests/pass-readme-learn-cond-1.nt

index 3b8ec75a882fa2739dff2ddc164dd2e5b3fd755a..0e5c506989c5154d443e660ae326515da7f09b17 100644 (file)
@@ -29,7 +29,7 @@ _**Normand**_ is a text-to-binary processor with its own language.
 This package offers both a portable {py3} module and a command-line
 tool.
 
-WARNING: This version of Normand is 0.13, meaning both the Normand
+WARNING: This version of Normand is 0.14, meaning both the Normand
 language and the module/CLI interface aren't stable.
 
 ifdef::env-github[]
@@ -184,6 +184,8 @@ aa bb cc
 
   !if {ICITTE > 10}
     "bar"
+  !else
+    "fight"
   !end
 ) * 4
 ----
@@ -191,8 +193,8 @@ aa bb cc
 Output:
 +
 ----
-aa bb cc 66 6f 6f 66 6f  6f 66 6f 6f 62 61 72 66  ┆ •••foofoofoobarf
-6f 6f 62 61 72                                    ┆ oobar
+aa bb cc 66 6f 6f 66 69  67 68 74 66 6f 6f 66 69  ┆ •••foofightfoofi
+67 68 74 66 6f 6f 62 61  72 66 6f 6f 62 61 72     ┆ ghtfoobarfoobar
 ----
 
 Repetition::
@@ -1283,8 +1285,9 @@ Output:
 
 === Conditional block
 
-A _conditional block_ represents either the bytes of one or more items
-if some expression is true, or no bytes at all if it's false.
+A _conditional block_ represents either the bytes of zero or more items
+if some expression is true, or the bytes of zero or more other items if
+it's false.
 
 A conditional block is:
 
@@ -1315,7 +1318,12 @@ items).
 For the name `__NAME__`, this is equivalent to the
 `pass:[{]__NAME__pass:[}]` form above.
 
-. Zero or more items.
+. Zero or more items to be handled when the condition is true.
+
+. **Optional**:
+
+.. The `!else` opening.
+.. Zero or more items to be handled when the condition is false.
 
 . The `!end` closing.
 
@@ -1331,10 +1339,12 @@ Input:
 
   !if {ICITTE > 25}
     "mix"
-
-    !if {at < rep_count} 20 !end
+  !else
+    "zoom"
   !end
 
+  !if {at < rep_count} 20 !end
+
   {at = at + 1}
 !end
 ----
@@ -1342,10 +1352,12 @@ Input:
 Output:
 
 ----
-6d 65 6f 77 20 6d 65 6f  77 20 6d 65 6f 77 20 6d  ┆ meow meow meow m
-65 6f 77 20 6d 65 6f 77  20 6d 65 6f 77 20 6d 69  ┆ eow meow meow mi
+6d 65 6f 77 20 7a 6f 6f  6d 20 6d 65 6f 77 20 7a  ┆ meow zoom meow z
+6f 6f 6d 20 6d 65 6f 77  20 7a 6f 6f 6d 20 6d 65  ┆ oom meow zoom me
+6f 77 20 6d 69 78 20 6d  65 6f 77 20 6d 69 78 20  ┆ ow mix meow mix
+6d 65 6f 77 20 6d 69 78  20 6d 65 6f 77 20 6d 69  ┆ meow mix meow mi
 78 20 6d 65 6f 77 20 6d  69 78 20 6d 65 6f 77 20  ┆ x meow mix meow
-6d 69 78 20 6d 65 6f 77  20 6d 69 78              ┆ mix meow mix
+6d 69 78                                          ┆ mix
 ----
 ====
 
index 90ca0d8ea1f9664e23524b5c85fcd595fce7ff42..b712150a82d829e7e38530f98fc2126756c90e91 100644 (file)
@@ -30,7 +30,7 @@
 # Upstream repository: <https://github.com/efficios/normand>.
 
 __author__ = "Philippe Proulx"
-__version__ = "0.13.0"
+__version__ = "0.14.0"
 __all__ = [
     "__author__",
     "__version__",
@@ -380,20 +380,32 @@ class _Rep(_Item, _ExprMixin):
 # Conditional item.
 class _Cond(_Item, _ExprMixin):
     def __init__(
-        self, item: _Item, expr_str: str, expr: ast.Expression, text_loc: TextLocation
+        self,
+        true_item: _Item,
+        false_item: _Item,
+        expr_str: str,
+        expr: ast.Expression,
+        text_loc: TextLocation,
     ):
         super().__init__(text_loc)
         _ExprMixin.__init__(self, expr_str, expr)
-        self._item = item
+        self._true_item = true_item
+        self._false_item = false_item
 
-    # Conditional item.
+    # Item when condition is true.
     @property
-    def item(self):
-        return self._item
+    def true_item(self):
+        return self._true_item
+
+    # Item when condition is false.
+    @property
+    def false_item(self):
+        return self._false_item
 
     def __repr__(self):
-        return "_Cond({}, {}, {}, {})".format(
-            repr(self._item),
+        return "_Cond({}, {}, {}, {}, {})".format(
+            repr(self._true_item),
+            repr(self._false_item),
             repr(self._expr_str),
             repr(self._expr),
             repr(self._text_loc),
@@ -1299,6 +1311,7 @@ class _Parser:
 
     # Pattern for _try_parse_cond_block()
     _cond_block_prefix_pat = re.compile(r"!if\b")
+    _cond_block_else_pat = re.compile(r"!else\b")
 
     # Tries to parse a conditional block, returning a conditional item
     # on success.
@@ -1314,20 +1327,36 @@ class _Parser:
         self._skip_ws_and_comments()
         expr_str, expr = self._expect_const_int_name_expr(False)
 
-        # Parse items
+        # Parse "true" items
         self._skip_ws_and_comments()
-        items_text_loc = self._text_loc
-        items = self._parse_items()
+        true_items_text_loc = self._text_loc
+        true_items = self._parse_items()
+        false_items = []  # type: List[_Item]
+        false_items_text_loc = begin_text_loc
 
-        # Expect end of block
+        # `!else`?
         self._skip_ws_and_comments()
+
+        if self._try_parse_pat(self._cond_block_else_pat) is not None:
+            # Parse "false" items
+            self._skip_ws_and_comments()
+            false_items_text_loc = self._text_loc
+            false_items = self._parse_items()
+
+        # Expect end of block
         self._expect_pat(
             self._block_end_pat,
-            "Expecting an item or `!end` (end of conditional block)",
+            "Expecting an item, `!else`, or `!end` (end of conditional block)",
         )
 
         # Return item
-        return _Cond(_Group(items, items_text_loc), expr_str, expr, begin_text_loc)
+        return _Cond(
+            _Group(true_items, true_items_text_loc),
+            _Group(false_items, false_items_text_loc),
+            expr_str,
+            expr,
+            begin_text_loc,
+        )
 
     # Common left parenthesis pattern
     _left_paren_pat = re.compile(r"\(")
@@ -2078,13 +2107,15 @@ class _Gen:
             self._handle_item(item.item, state)
 
     # Handles the conditional item `item`.
-    def _handle_cond_item(self, item: _Rep, state: _GenState):
+    def _handle_cond_item(self, item: _Cond, state: _GenState):
         # Compute the conditional value
         val = _Gen._eval_item_expr(item, state)
 
         # Generate item data if needed
         if val:
-            self._handle_item(item.item, state)
+            self._handle_item(item.true_item, state)
+        else:
+            self._handle_item(item.false_item, state)
 
     # Evaluates the parameters of the macro expansion item `item`
     # considering the initial state `init_state` and returns a new state
index 9567508e382c0d8fe0300c318c9a753e9381775a..02ffe6d13d06caee2f94ebc73b9f9926011067e2 100644 (file)
@@ -23,7 +23,7 @@
 
 [tool.poetry]
 name = 'normand'
-version = '0.13.0'
+version = '0.14.0'
 description = 'Text-to-binary processor with its own language'
 license = 'MIT'
 authors = ['Philippe Proulx <eeppeliteloop@gmail.com>']
diff --git a/tests/fail-cond-blk-else-missing-end.nt b/tests/fail-cond-blk-else-missing-end.nt
new file mode 100644 (file)
index 0000000..4f391a4
--- /dev/null
@@ -0,0 +1,11 @@
+!m salut(meow)
+  !if meow
+    "true"
+  !else
+    "false"
+!end
+
+m:salut(1)
+m:salut(0)
+---
+8:3 - Unknown macro name `salut`
index deedf4c3e9cc2052d89db1a598ec1078e62e72f2..9cd3d00c9c284b598a2b793db923dc010b3f0a4f 100644 (file)
@@ -4,4 +4,4 @@
   !end
 !end
 ---
-2:3 - Expecting an item or `!end` (end of conditional block)
+2:3 - Expecting an item, `!else`, or `!end` (end of conditional block)
diff --git a/tests/pass-cond-blk-else.nt b/tests/pass-cond-blk-else.nt
new file mode 100644 (file)
index 0000000..72a2b53
--- /dev/null
@@ -0,0 +1,13 @@
+!m salut(meow)
+  !if meow
+    "true"
+  !else
+    "false"
+  !end
+!end
+
+m:salut(1)
+m:salut(0)
+---
+74 72 75 65
+66 61 6c 73 65
index 44c1ee14fa7713e3e292d3cbe8f777dea6927b87..48ff39c59901742b46520765b6c2ef13abb6052d 100644 (file)
@@ -6,14 +6,18 @@
 
   !if {ICITTE > 25}
     "mix"
-
-    !if {at < rep_count} 20 !end
+  !else
+    "zoom"
   !end
 
+  !if {at < rep_count} 20 !end
+
   {at = at + 1}
 !end
 ---
-6d 65 6f 77 20 6d 65 6f 77 20 6d 65 6f 77 20 6d
-65 6f 77 20 6d 65 6f 77 20 6d 65 6f 77 20 6d 69
+6d 65 6f 77 20 7a 6f 6f 6d 20 6d 65 6f 77 20 7a
+6f 6f 6d 20 6d 65 6f 77 20 7a 6f 6f 6d 20 6d 65
+6f 77 20 6d 69 78 20 6d 65 6f 77 20 6d 69 78 20
+6d 65 6f 77 20 6d 69 78 20 6d 65 6f 77 20 6d 69
 78 20 6d 65 6f 77 20 6d 69 78 20 6d 65 6f 77 20
-6d 69 78 20 6d 65 6f 77 20 6d 69 78
+6d 69 78
This page took 0.030055 seconds and 4 git commands to generate.