Add conditional block support v0.9.0
authorPhilippe Proulx <eeppeliteloop@gmail.com>
Mon, 2 Oct 2023 04:28:01 +0000 (00:28 -0400)
committerPhilippe Proulx <eeppeliteloop@gmail.com>
Mon, 2 Oct 2023 15:38:29 +0000 (11:38 -0400)
This patch adds the `!if` directive to support conditional blocks.

A conditional block is functionally equivalent to a repetition block
when casting the expression to `bool`:

    !if {meow + mix > 32}
      # ...
    !end

    !repeat {meow + mix > 32}
      # ...
    !end

In other words, a conditional block is a repetition of its contained
items zero or one time.

The expression of a fixed-length number, an LEB128 integer, a variable
assignment, and a repetition may now evaluate to a `bool` value: Normand
converts it to `int` (0 or 1) automatically.

Change-Id: I3b686a3196d1ea8daa5741e78320c031b40abc00
Signed-off-by: Philippe Proulx <eeppeliteloop@gmail.com>
14 files changed:
README.adoc
normand/normand.py
pyproject.toml
tests/fail-rep-post-inval-count.nt
tests/pass-cond-blk-expr-label-inner.nt [new file with mode: 0644]
tests/pass-cond-blk-expr-label.nt [new file with mode: 0644]
tests/pass-cond-blk-expr-var-1.nt [new file with mode: 0644]
tests/pass-cond-blk-expr-var-2.nt [new file with mode: 0644]
tests/pass-cond-blk-icitte.nt [new file with mode: 0644]
tests/pass-cond-blk-label.nt [new file with mode: 0644]
tests/pass-cond-blk-var.nt [new file with mode: 0644]
tests/pass-readme-intro-cond.nt [new file with mode: 0644]
tests/pass-readme-learn-cond-1.nt [new file with mode: 0644]
tests/pass-readme-learn-cond-2.nt [new file with mode: 0644]

index f0852d140d8067831406377f96d932fa15fa8381..fe99d829db78cf3c2ae475977afc069c50b93356 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.8, meaning both the Normand
+WARNING: This version of Normand is 0.9, meaning both the Normand
 language and the module/CLI interface aren't stable.
 
 ifdef::env-github[]
@@ -172,6 +172,29 @@ aa bb cc b7 70 dd ee ff e3 07
 The encoded integer is the evaluation of a valid {py3} expression which
 may include label and variable names.
 
+Conditional::
++
+Input:
++
+----
+aa bb cc
+
+(
+  "foo"
+
+  !if {ICITTE > 10}
+    "bar"
+  !end
+) * 4
+----
++
+Output:
++
+----
+aa bb cc 66 6f 6f 66 6f  6f 66 6f 6f 62 61 72 66  ┆ •••foofoofoobarf
+6f 6f 62 61 72                                    ┆ oobar
+----
+
 Repetition::
 +
 Input:
@@ -304,8 +327,11 @@ current state:
 |
 The current offset has an effect on the value of <<label,labels>> and of
 the special `ICITTE` name in <<fixed-length-number,fixed-length
-number>>, <<leb-128-integer,LEB128 integer>>, and
-<<variable-assignment,variable assignment>> expression evaluation.
+number>>, <<leb-128-integer,LEB128 integer>>,
+<<variable-assignment,variable assignment>>,
+<<conditional-block,conditional block>>, <<repetition-block,repetition
+block>>, and <<post-item-repetition,post-item repetition>> expression
+evaluation.
 
 Each generated byte increments the current offset.
 
@@ -333,7 +359,7 @@ the current byte order.
 |One or more `--label` options.
 
 |<<variable-assignment,Variables>>
-|Mapping of variable names to integral values.
+|Mapping of variable names to integral or floating point number values.
 |`init_variables` parameter of the `parse()` function.
 |One or more `--var` options.
 |===
@@ -369,6 +395,8 @@ This is similar to an assembly label.
 
 * A <<group,group>>, that is, a scoped sequence of items.
 
+* A <<conditional-block,conditional block>>.
+
 * A <<repetition-block,repetition block>>.
 
 Moreover, you can repeat many items above a constant or variable number
@@ -385,6 +413,8 @@ A Normand comment may exist:
   number or expression.
 * Between the ``!repeat``/``!r`` prefix and the following constant
   integer, name, or expression of a repetition block.
+* Between the ``!if`` prefix and the following name or expression of a
+  conditional block.
 
 A comment is anything between two ``pass:[#]`` characters on the same
 line, or from ``pass:[#]`` until the end of the line. Whitespaces and
@@ -629,8 +659,10 @@ is the <<cur-offset,current offset>> (before encoding the number).
 . An encoding length in bits amongst:
 +
 --
-The expression evaluates to an `int` value::
+The expression evaluates to an `int` or `bool` value::
     `8`, `16`, `24`, `32`, `40`, `48`, `56`, and `64`.
++
+NOTE: Normand automatically converts a `bool` value to `int`.
 
 The expression evaluates to a `float` value::
     `32` and `64`.
@@ -715,7 +747,8 @@ An LEB128 integer is:
 
 . The ``pass:[{]`` prefix.
 
-. A valid {py3} expression.
+. A valid {py3} expression of which the evaluation result type
+  is `int` or `bool` (automatically converted to `int`).
 +
 For an LEB128 integer at some source location{nbsp}__**L**__, this
 expression may contain:
@@ -907,9 +940,7 @@ A label is:
 
 . The `<` prefix.
 
-. A valid {py3} name which is not `ICITTE` (see
-  <<fixed-length-number>>, <<leb128-integer>>, and
-  <<variable-assignment>> to learn more).
+. A valid {py3} name which is not `ICITTE`.
 
 . The `>` suffix.
 
@@ -922,13 +953,12 @@ A variable assignment is:
 
 . The ``pass:[{]`` prefix.
 
-. A valid {py3} name which is not `ICITTE` (see
-  <<fixed-length-number>>, <<leb128-integer>>, and
-  <<variable-assignment>> to learn more).
+. A valid {py3} name which is not `ICITTE`.
 
 . The `=` character.
 
-. A valid {py3} expression.
+. A valid {py3} expression of which the evaluation result type
+  is `int`, `float`, or `bool` (automatically converted to `int`).
 +
 For a variable assignment at some source location{nbsp}__**L**__, this
 expression may contain the name of any accessible <<label,label>> (not
@@ -1030,6 +1060,96 @@ 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 is:
+
+. The `!if` prefix.
+
+. One of:
+
+** The ``pass:[{]`` prefix, a valid {py3} expression of which the
+   evaluation result type is `int` or `bool` (automatically converted to
+   `int`), and the ``pass:[}]`` suffix.
++
+For a repetition at some source location{nbsp}__**L**__, this expression
+may contain:
++
+--
+* The name of any <<label,label>> defined before{nbsp}__**L**__
+  which isn't within a nested group.
+* The name of any <<variable-assignment,variable>> known
+  at{nbsp}__**L**__ which doesn't, directly or indirectly, refer to a
+  label defined after{nbsp}__**L**__.
+--
++
+The value of the special name `ICITTE` (`int` type) in this expression
+is the <<cur-offset,current offset>> (before handling the contained
+items).
+
+** A valid {py3} name.
++
+For the name `__NAME__`, this is equivalent to the
+`pass:[{]__NAME__pass:[}]` form above.
+
+. Zero or more items.
+
+. The `!end` suffix.
+
+====
+Input:
+
+----
+{at = 1}
+{rep_count = 9}
+
+!repeat rep_count
+  "meow "
+
+  !if {ICITTE > 25}
+    "mix"
+
+    !if {at < rep_count} 20 !end
+  !end
+
+  {at = at + 1}
+!end
+----
+
+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
+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
+----
+====
+
+====
+Input:
+
+----
+<str_beg>
+u16le"meow mix!"
+<str_end>
+
+!if {str_end - str_beg > 10}
+  " BIG"
+!end
+----
+
+Output:
+
+----
+6d 00 65 00 6f 00 77 00  20 00 6d 00 69 00 78 00  ┆ m•e•o•w• •m•i•x•
+21 00 20 42 49 47                                 ┆ !• BIG
+----
+====
+
 === Repetition block
 
 A _repetition block_ represents the bytes of one or more items repeated
@@ -1044,14 +1164,16 @@ A repetition block is:
 ** A positive integer (hexadecimal starting with `0x` or `0X` accepted)
    which is the number of times to repeat the previous item.
 
-** The ``pass:[{]`` prefix, a valid {py3} expression, and the
-   ``pass:[}]`` suffix.
+** The ``pass:[{]`` prefix, a valid {py3} expression of which the
+   evaluation result type is `int` or `bool` (automatically converted to
+   `int`), and the ``pass:[}]`` suffix.
 +
 For a repetition at some source location{nbsp}__**L**__, this expression
 may contain:
 +
 --
-* The name of any <<label,label>> defined before{nbsp}__**L**__.
+* The name of any <<label,label>> defined before{nbsp}__**L**__
+  which isn't within a nested group.
 * The name of any <<variable-assignment,variable>> known
   at{nbsp}__**L**__ which doesn't, directly or indirectly, refer to a
   label defined after{nbsp}__**L**__.
@@ -1148,36 +1270,6 @@ ff ee ff ee ff ee ff ee  ff ee ff ee ff 11 22 33  ┆ ••••••••
 ----
 ====
 
-====
-This example shows how to use a repetition block as a conditional
-section depending on some predefined variable.
-
-Input:
-
-----
-aa bb cc dd
-
-!repeat cond
-  ee ff "meow mix" 00
-!end
-
-{be} {-1993:16}
-----
-
-Output (`cond` is 0):
-
-----
-aa bb cc dd f8 37
-----
-
-Output (`cond` is 1):
-
-----
-aa bb cc dd ee ff 6d 65  6f 77 20 6d 69 78 00 f8  ┆ ••••••meow mix••
-37                                                ┆ 7
-----
-====
-
 === Post-item repetition
 
 A _post-item repetition_ represents the bytes of an item repeated a
@@ -1185,14 +1277,13 @@ given number of times.
 
 A post-item repetition is:
 
-. Any item except:
+. One of those items:
 
-** A <<current-byte-order-setting,current byte order setting>>.
-** A <<current-offset-setting,current offset setting>>.
-** A <<label,label>>.
-** A <<offset-alignment,offset alignment>>.
-** A <<variable-assignment,variable assignment>>.
-** A <<repetition-block,repetition block>>.
+** A <<byte-constant,byte constant>>.
+** A <<literal-string,literal string>>.
+** A <<fixed-length-number,fixed-length number>>.
+** An <<leb128-integer,LEB128 integer>>.
+** A <<group,group>>.
 
 . The ``pass:[*]`` character.
 
@@ -1201,15 +1292,17 @@ A post-item repetition is:
 ** A positive integer (hexadecimal starting with `0x` or `0X` accepted)
    which is the number of times to repeat the previous item.
 
-** The ``pass:[{]`` prefix, a valid {py3} expression, and the
-   ``pass:[}]`` suffix.
+** The ``pass:[{]`` prefix, a valid {py3} expression of which the
+   evaluation result type is `int` or `bool` (automatically converted to
+   `int`), and the ``pass:[}]`` suffix.
 +
 For a repetition at some source location{nbsp}__**L**__, this expression
 may contain:
 +
 --
-* The name of any <<label,label>> defined before{nbsp}__**L**__ and
-  which isn't part of its repeated item.
+* The name of any <<label,label>> defined before{nbsp}__**L**__
+  which isn't within a nested group and
+  which isn't part of the repeated item.
 * The name of any <<variable-assignment,variable>> known
   at{nbsp}__**L**__, which isn't part of its repeated item, and which
   doesn't, directly or indirectly, refer to a label defined
index 6b1e3d2cced6833847a036f0a9496499f22feef3..b2f3069f8b8e55aee7f8a608741df75fc56b4660 100644 (file)
@@ -30,7 +30,7 @@
 # Upstream repository: <https://github.com/efficios/normand>.
 
 __author__ = "Philippe Proulx"
-__version__ = "0.8.0"
+__version__ = "0.9.0"
 __all__ = [
     "ByteOrder",
     "parse",
@@ -353,8 +353,31 @@ class _Rep(_Item, _ExprMixin):
         )
 
 
+# Conditional item.
+class _Cond(_Item, _ExprMixin):
+    def __init__(
+        self, item: _Item, expr_str: str, expr: ast.Expression, text_loc: TextLocation
+    ):
+        super().__init__(text_loc)
+        _ExprMixin.__init__(self, expr_str, expr)
+        self._item = item
+
+    # Conditional item.
+    @property
+    def item(self):
+        return self._item
+
+    def __repr__(self):
+        return "_Cond({}, {}, {}, {})".format(
+            repr(self._item),
+            repr(self._expr_str),
+            repr(self._expr),
+            repr(self._text_loc),
+        )
+
+
 # Expression item type.
-_ExprItemT = Union[_FlNum, _Leb128Int, _VarAssign, _Rep]
+_ExprItemT = Union[_FlNum, _Leb128Int, _VarAssign, _Rep, _Cond]
 
 
 # A parsing error containing a message and a text location.
@@ -953,17 +976,21 @@ class _Parser:
         return _AlignOffset(val, pad_val, begin_text_loc)
 
     # Patterns for _expect_rep_mul_expr()
-    _rep_expr_prefix_pat = re.compile(r"\{")
-    _rep_expr_pat = re.compile(r"[^}p]+")
-    _rep_expr_suffix_pat = re.compile(r"\}")
-
-    # Parses the multiplier expression of a repetition (block or
-    # post-item) and returns the expression string and AST node.
-    def _expect_rep_mul_expr(self):
+    _rep_cond_expr_prefix_pat = re.compile(r"\{")
+    _rep_cond_expr_pat = re.compile(r"[^}]+")
+    _rep_cond_expr_suffix_pat = re.compile(r"\}")
+
+    # Parses the expression of a conditional block or of a repetition
+    # (block or post-item) and returns the expression string and AST
+    # node.
+    def _expect_rep_cond_expr(self, accept_int: bool):
         expr_text_loc = self._text_loc
 
         # Constant integer?
-        m = self._try_parse_pat(self._pos_const_int_pat)
+        m = None
+
+        if accept_int:
+            m = self._try_parse_pat(self._pos_const_int_pat)
 
         if m is None:
             # Name?
@@ -971,19 +998,22 @@ class _Parser:
 
             if m is None:
                 # Expression?
-                if self._try_parse_pat(self._rep_expr_prefix_pat) is None:
+                if self._try_parse_pat(self._rep_cond_expr_prefix_pat) is None:
+                    if accept_int:
+                        mid_msg = "a positive constant integer, a name, or `{`"
+                    else:
+                        mid_msg = "a name or `{`"
+
                     # At this point it's invalid
-                    self._raise_error(
-                        "Expecting a positive integral multiplier, a name, or `{`"
-                    )
+                    self._raise_error("Expecting {}".format(mid_msg))
 
                 # Expect an expression
                 expr_text_loc = self._text_loc
-                m = self._expect_pat(self._rep_expr_pat, "Expecting an expression")
+                m = self._expect_pat(self._rep_cond_expr_pat, "Expecting an expression")
                 expr_str = m.group(0)
 
                 # Expect `}`
-                self._expect_pat(self._rep_expr_suffix_pat, "Expecting `}`")
+                self._expect_pat(self._rep_cond_expr_suffix_pat, "Expecting `}`")
             else:
                 expr_str = m.group(0)
         else:
@@ -991,9 +1021,16 @@ class _Parser:
 
         return self._ast_expr_from_str(expr_str, expr_text_loc)
 
+    # Parses the multiplier expression of a repetition (block or
+    # post-item) and returns the expression string and AST node.
+    def _expect_rep_mul_expr(self):
+        return self._expect_rep_cond_expr(True)
+
+    # Common block end pattern
+    _block_end_pat = re.compile(r"!end\b\s*")
+
     # Pattern for _try_parse_rep_block()
     _rep_block_prefix_pat = re.compile(r"!r(?:epeat)?\b\s*")
-    _rep_block_end_pat = re.compile(r"!end\b\s*")
 
     # Tries to parse a repetition block, returning a repetition item on
     # success.
@@ -1017,12 +1054,44 @@ class _Parser:
         # Expect end of block
         self._skip_ws_and_comments()
         self._expect_pat(
-            self._rep_block_end_pat, "Expecting an item or `!end` (end of repetition)"
+            self._block_end_pat, "Expecting an item or `!end` (end of repetition block)"
         )
 
         # Return item
         return _Rep(_Group(items, items_text_loc), expr_str, expr, begin_text_loc)
 
+    # Pattern for _try_parse_cond_block()
+    _cond_block_prefix_pat = re.compile(r"!if\b\s*")
+
+    # Tries to parse a conditional block, returning a conditional item
+    # on success.
+    def _try_parse_cond_block(self):
+        begin_text_loc = self._text_loc
+
+        # Match prefix
+        if self._try_parse_pat(self._cond_block_prefix_pat) is None:
+            # No match
+            return
+
+        # Expect expression
+        self._skip_ws_and_comments()
+        expr_str, expr = self._expect_rep_cond_expr(False)
+
+        # Parse items
+        self._skip_ws_and_comments()
+        items_text_loc = self._text_loc
+        items = self._parse_items()
+
+        # Expect end of block
+        self._skip_ws_and_comments()
+        self._expect_pat(
+            self._block_end_pat,
+            "Expecting an item or `!end` (end of conditional block)",
+        )
+
+        # Return item
+        return _Cond(_Group(items, items_text_loc), expr_str, expr, begin_text_loc)
+
     # Tries to parse a base item (anything except a repetition),
     # returning it on success.
     def _try_parse_base_item(self):
@@ -1068,6 +1137,12 @@ class _Parser:
         if item is not None:
             return item
 
+        # Conditional block item?
+        item = self._try_parse_cond_block()
+
+        if item is not None:
+            return item
+
     # Pattern for _try_parse_rep_post()
     _rep_post_prefix_pat = re.compile(r"\*")
 
@@ -1305,18 +1380,19 @@ class _GenState:
 #
 # The steps of generation are:
 #
-# 1. Validate that each repetition and LEB128 integer expression uses
-#    only reachable names.
+# 1. Validate that each repetition, conditional, and LEB128 integer
+#    expression uses only reachable names.
 #
-# 2. Compute and keep the effective repetition count and LEB128 integer
-#    value for each repetition and LEB128 integer instance.
+# 2. Compute and keep the effective repetition count, conditional value,
+#    and LEB128 integer value for each repetition and LEB128 integer
+#    instance.
 #
 # 3. Generate bytes, updating the initial state as it goes which becomes
 #    the final state after the operation.
 #
-#    During the generation, when handling a `_Rep` or `_Leb128Int` item,
-#    we already have the effective repetition count or value of the
-#    instance.
+#    During the generation, when handling a `_Rep`, `_Cond`, or
+#    `_Leb128Int` item, we already have the effective repetition count,
+#    conditional value, or value of the instance.
 #
 #    When handling a `_Group` item, first update the current labels with
 #    all the immediate (not nested) labels, and then handle each
@@ -1371,9 +1447,9 @@ class _Gen:
         visitor.visit(expr)
         return visitor.names
 
-    # Validates that all the repetition and LEB128 integer expressions
-    # within `group` don't refer, directly or indirectly, to subsequent
-    # labels.
+    # Validates that all the repetition, conditional, and LEB128 integer
+    # expressions within `group` don't refer, directly or indirectly, to
+    # subsequent labels.
     #
     # The strategy here is to keep a set of allowed label names, per
     # group, initialized to `allowed_label_names`, and a set of allowed
@@ -1394,7 +1470,7 @@ class _Gen:
     #     it's in there): a variable which refers to an unreachable name
     #     is unreachable itself.
     #
-    # `_Rep` and `_Leb128`:
+    # `_Rep`, `_Cond`, and `_Leb128`:
     #     Make sure all the names within its expression are allowed.
     #
     # `_Group`:
@@ -1428,7 +1504,7 @@ class _Gen:
             _ExprValidator(item, allowed_label_names | allowed_variable_names).visit(
                 item.expr
             )
-        elif type(item) is _Rep:
+        elif type(item) is _Rep or type(item) is _Cond:
             # Validate the expression first
             _ExprValidator(item, allowed_label_names | allowed_variable_names).visit(
                 item.expr
@@ -1480,6 +1556,10 @@ class _Gen:
                 item,
             )
 
+        # Convert `bool` result type to `int` to normalize
+        if type(val) is bool:
+            val = int(val)
+
         # Validate result type
         expected_types = {int}  # type: Set[type]
         type_msg = "`int`"
@@ -1524,15 +1604,16 @@ class _Gen:
         align_bytes = item.val // 8
         return (offset + align_bytes - 1) // align_bytes * align_bytes
 
-    # Computes the effective value for each repetition and LEB128
-    # integer instance, filling `instance_vals` (if not `None`) and
-    # returning `instance_vals`.
+    # Computes the effective value for each repetition, conditional, and
+    # LEB128 integer instance, filling `instance_vals` (if not `None`)
+    # and returning `instance_vals`.
     #
     # At this point it must be known that, for a given variable-length
     # item, its expression only contains reachable names.
     #
-    # When handling a `_Rep` item, this function appends its effective
-    # multiplier to `instance_vals` _before_ handling its repeated item.
+    # When handling a `_Rep` or `_Cond` item, this function appends its
+    # effective multiplier/value to `instance_vals` _before_ handling
+    # its repeated/conditional item.
     #
     # When handling a `_VarAssign` item, this function only evaluates it
     # if all its names are reachable.
@@ -1600,12 +1681,22 @@ class _Gen:
                     item,
                 )
 
-            # Add to repetition instance values
+            # Add to variable-length item instance values
             instance_vals.append(val)
 
             # Process the repeated item `val` times
             for _ in range(val):
                 _Gen._compute_vl_instance_vals(item.item, state, instance_vals)
+        elif type(item) is _Cond:
+            # Evaluate the expression and keep the result
+            val = _Gen._eval_item_expr(item, state)
+
+            # Add to variable-length item instance values
+            instance_vals.append(val)
+
+            # Process the conditional item if needed
+            if val:
+                _Gen._compute_vl_instance_vals(item.item, state, instance_vals)
         elif type(item) is _Group:
             prev_labels = state.labels.copy()
 
@@ -1658,6 +1749,20 @@ class _Gen:
 
         return next_vl_instance
 
+    def _dry_handle_cond_item(
+        self, item: _Cond, state: _GenState, next_vl_instance: int
+    ):
+        # Get the value from `self._vl_instance_vals` _before_
+        # incrementing `next_vl_instance` to honor the order of
+        # _compute_vl_instance_vals().
+        val = self._vl_instance_vals[next_vl_instance]
+        next_vl_instance += 1
+
+        if val:
+            next_vl_instance = self._dry_handle_item(item.item, state, next_vl_instance)
+
+        return next_vl_instance
+
     def _dry_handle_align_offset_item(
         self, item: _AlignOffset, state: _GenState, next_vl_instance: int
     ):
@@ -1859,6 +1964,19 @@ class _Gen:
 
         return next_vl_instance
 
+    # Handles the conditional item `item`.
+    def _handle_cond_item(self, item: _Rep, state: _GenState, next_vl_instance: int):
+        # Get the precomputed conditional value
+        val = self._vl_instance_vals[next_vl_instance]
+
+        # Consumed this instance
+        next_vl_instance += 1
+
+        if val:
+            next_vl_instance = self._handle_item(item.item, state, next_vl_instance)
+
+        return next_vl_instance
+
     # Handles the offset setting item `item`.
     def _handle_set_offset_item(
         self, item: _SetOffset, state: _GenState, next_vl_instance: int
@@ -1894,6 +2012,7 @@ class _Gen:
         self._item_handlers = {
             _AlignOffset: self._handle_align_offset_item,
             _Byte: self._handle_byte_item,
+            _Cond: self._handle_cond_item,
             _FlNum: self._handle_fl_num_item,
             _Group: self._handle_group_item,
             _Label: self._handle_label_item,
@@ -1910,6 +2029,7 @@ class _Gen:
         self._dry_handle_item_funcs = {
             _AlignOffset: self._dry_handle_align_offset_item,
             _Byte: self._dry_handle_scalar_item,
+            _Cond: self._dry_handle_cond_item,
             _FlNum: self._dry_handle_scalar_item,
             _Group: self._dry_handle_group_item,
             _Label: self._update_offset_noop,
index c439ac7f2f335984c88f7f23550b278bfd39c5a2..2d8f3c3fe6e96c35187e2398c15feec8c6683a5a 100644 (file)
@@ -23,7 +23,7 @@
 
 [tool.poetry]
 name = 'normand'
-version = '0.8.0'
+version = '0.9.0'
 description = 'Text-to-binary processor with its own language'
 license = 'MIT'
 authors = ['Philippe Proulx <eeppeliteloop@gmail.com>']
index 13eddc6bc13f1ff5a89aa3e3c0ba15d039663706..16a83dc4746384c38d5145d5c9469eeecafce665 100644 (file)
@@ -1,3 +1,3 @@
 88 * "salut"
 ---
-1:6 - Expecting a positive integral multiplier, a name, or `{`
+1:6 - Expecting a positive constant integer, a name, or `{`
diff --git a/tests/pass-cond-blk-expr-label-inner.nt b/tests/pass-cond-blk-expr-label-inner.nt
new file mode 100644 (file)
index 0000000..a9dbbd6
--- /dev/null
@@ -0,0 +1,23 @@
+11
+!repeat 2
+  aa bb
+  <meow>
+  cc
+  !if meow dd !end
+  ee ff
+!end
+22
+---
+11
+
+aa bb
+cc
+dd
+ee ff
+
+aa bb
+cc
+dd
+ee ff
+
+22
diff --git a/tests/pass-cond-blk-expr-label.nt b/tests/pass-cond-blk-expr-label.nt
new file mode 100644 (file)
index 0000000..1bb8d02
--- /dev/null
@@ -0,0 +1,11 @@
+aa bb
+<meow>
+cc
+!if {meow == 2} dd !end
+!if {meow != 2} 88 !end
+ee ff
+---
+aa bb
+cc
+dd
+ee ff
diff --git a/tests/pass-cond-blk-expr-var-1.nt b/tests/pass-cond-blk-expr-var-1.nt
new file mode 100644 (file)
index 0000000..bff4336
--- /dev/null
@@ -0,0 +1,9 @@
+aa bb
+{meow = 3}
+cc !if meow dd !end
+ee ff
+---
+aa bb
+cc
+dd
+ee ff
diff --git a/tests/pass-cond-blk-expr-var-2.nt b/tests/pass-cond-blk-expr-var-2.nt
new file mode 100644 (file)
index 0000000..c7845d5
--- /dev/null
@@ -0,0 +1,17 @@
+aa <mix> bb
+{meow = 3}
+{meow = end}
+11 22
+!repeat 2
+  {meow = mix * 2}
+  cc !if {meow <= 2} dd !end
+!end
+ee ff
+!if {not meow} 88 !end
+<end>
+---
+aa bb
+11 22
+cc dd
+cc dd
+ee ff
diff --git a/tests/pass-cond-blk-icitte.nt b/tests/pass-cond-blk-icitte.nt
new file mode 100644 (file)
index 0000000..7fd0939
--- /dev/null
@@ -0,0 +1,15 @@
+!if ICITTE
+  ff
+!end
+
+aa bb cc
+
+!if ICITTE
+  dd
+!end
+
+ee ff
+---
+aa bb cc
+dd
+ee ff
diff --git a/tests/pass-cond-blk-label.nt b/tests/pass-cond-blk-label.nt
new file mode 100644 (file)
index 0000000..5d85efd
--- /dev/null
@@ -0,0 +1,3 @@
+aa ff <meow> dd !if meow bb !end cc
+---
+aa ff dd bb cc
diff --git a/tests/pass-cond-blk-var.nt b/tests/pass-cond-blk-var.nt
new file mode 100644 (file)
index 0000000..fe7d599
--- /dev/null
@@ -0,0 +1,3 @@
+aa {meow = 5} dd !if {meow < 5} bb !end cc
+---
+aa dd cc
diff --git a/tests/pass-readme-intro-cond.nt b/tests/pass-readme-intro-cond.nt
new file mode 100644 (file)
index 0000000..e71fdbf
--- /dev/null
@@ -0,0 +1,12 @@
+aa bb cc
+
+(
+  "foo"
+
+  !if {ICITTE > 10}
+    "bar"
+  !end
+) * 4
+---
+aa bb cc 66 6f 6f 66 6f 6f 66 6f 6f 62 61 72 66
+6f 6f 62 61 72
diff --git a/tests/pass-readme-learn-cond-1.nt b/tests/pass-readme-learn-cond-1.nt
new file mode 100644 (file)
index 0000000..44c1ee1
--- /dev/null
@@ -0,0 +1,19 @@
+{at = 1}
+{rep_count = 9}
+
+!repeat rep_count
+  "meow "
+
+  !if {ICITTE > 25}
+    "mix"
+
+    !if {at < rep_count} 20 !end
+  !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
+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
diff --git a/tests/pass-readme-learn-cond-2.nt b/tests/pass-readme-learn-cond-2.nt
new file mode 100644 (file)
index 0000000..d922223
--- /dev/null
@@ -0,0 +1,10 @@
+<str_beg>
+u16le"meow mix!"
+<str_end>
+
+!if {str_end - str_beg > 10}
+  " BIG"
+!end
+---
+6d 00 65 00 6f 00 77 00 20 00 6d 00 69 00 78 00
+21 00 20 42 49 47
This page took 0.035744 seconds and 4 git commands to generate.