From 12b5dbc00bcadb1feb11cf7f0583de646a61aacf Mon Sep 17 00:00:00 2001 From: Philippe Proulx Date: Thu, 5 Oct 2023 16:57:57 -0400 Subject: [PATCH] Add `!else` support for conditional block 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 --- README.adoc | 34 ++++++++----- normand/normand.py | 63 ++++++++++++++++++------- pyproject.toml | 2 +- tests/fail-cond-blk-else-missing-end.nt | 11 +++++ tests/fail-macro-def-in-cond.nt | 2 +- tests/pass-cond-blk-else.nt | 13 +++++ tests/pass-readme-learn-cond-1.nt | 14 ++++-- 7 files changed, 105 insertions(+), 34 deletions(-) create mode 100644 tests/fail-cond-blk-else-missing-end.nt create mode 100644 tests/pass-cond-blk-else.nt diff --git a/README.adoc b/README.adoc index 3b8ec75..0e5c506 100644 --- a/README.adoc +++ b/README.adoc @@ -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 ---- ==== diff --git a/normand/normand.py b/normand/normand.py index 90ca0d8..b712150 100644 --- a/normand/normand.py +++ b/normand/normand.py @@ -30,7 +30,7 @@ # Upstream repository: . __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 diff --git a/pyproject.toml b/pyproject.toml index 9567508..02ffe6d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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 '] diff --git a/tests/fail-cond-blk-else-missing-end.nt b/tests/fail-cond-blk-else-missing-end.nt new file mode 100644 index 0000000..4f391a4 --- /dev/null +++ b/tests/fail-cond-blk-else-missing-end.nt @@ -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` diff --git a/tests/fail-macro-def-in-cond.nt b/tests/fail-macro-def-in-cond.nt index deedf4c..9cd3d00 100644 --- a/tests/fail-macro-def-in-cond.nt +++ b/tests/fail-macro-def-in-cond.nt @@ -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 index 0000000..72a2b53 --- /dev/null +++ b/tests/pass-cond-blk-else.nt @@ -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 diff --git a/tests/pass-readme-learn-cond-1.nt b/tests/pass-readme-learn-cond-1.nt index 44c1ee1..48ff39c 100644 --- a/tests/pass-readme-learn-cond-1.nt +++ b/tests/pass-readme-learn-cond-1.nt @@ -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 -- 2.34.1