From: Philippe Proulx Date: Mon, 2 Oct 2023 04:28:01 +0000 (-0400) Subject: Add conditional block support X-Git-Tag: v0.9.0 X-Git-Url: http://drtracing.org/?a=commitdiff_plain;h=27d52a19c5248aff0194e22c86e66e526aa9e4f1;p=normand.git Add conditional block support 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 --- diff --git a/README.adoc b/README.adoc index f0852d1..fe99d82 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.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 <> and of the special `ICITTE` name in <>, <>, and -<> expression evaluation. +number>>, <>, +<>, +<>, <>, and <> expression +evaluation. Each generated byte increments the current offset. @@ -333,7 +359,7 @@ the current byte order. |One or more `--label` options. |<> -|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 <>, that is, a scoped sequence of items. +* A <>. + * A <>. 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 <> (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 - <>, <>, and - <> 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 - <>, <>, and - <> 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 <> (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 <> defined before{nbsp}__**L**__ + which isn't within a nested group. +* The name of any <> 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 <> (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: + +---- + +u16le"meow mix!" + + +!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 <> defined before{nbsp}__**L**__. +* The name of any <> defined before{nbsp}__**L**__ + which isn't within a nested group. * The name of any <> 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 <>. -** A <>. -** A <>. -** A <>. -** A <>. -** A <>. +** A <>. +** A <>. +** A <>. +** An <>. +** A <>. . 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 <> defined before{nbsp}__**L**__ and - which isn't part of its repeated item. +* The name of any <> defined before{nbsp}__**L**__ + which isn't within a nested group and + which isn't part of the repeated item. * The name of any <> known at{nbsp}__**L**__, which isn't part of its repeated item, and which doesn't, directly or indirectly, refer to a label defined diff --git a/normand/normand.py b/normand/normand.py index 6b1e3d2..b2f3069 100644 --- a/normand/normand.py +++ b/normand/normand.py @@ -30,7 +30,7 @@ # Upstream repository: . __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, diff --git a/pyproject.toml b/pyproject.toml index c439ac7..2d8f3c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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 '] diff --git a/tests/fail-rep-post-inval-count.nt b/tests/fail-rep-post-inval-count.nt index 13eddc6..16a83dc 100644 --- a/tests/fail-rep-post-inval-count.nt +++ b/tests/fail-rep-post-inval-count.nt @@ -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 index 0000000..a9dbbd6 --- /dev/null +++ b/tests/pass-cond-blk-expr-label-inner.nt @@ -0,0 +1,23 @@ +11 +!repeat 2 + aa bb + + 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 index 0000000..1bb8d02 --- /dev/null +++ b/tests/pass-cond-blk-expr-label.nt @@ -0,0 +1,11 @@ +aa bb + +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 index 0000000..bff4336 --- /dev/null +++ b/tests/pass-cond-blk-expr-var-1.nt @@ -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 index 0000000..c7845d5 --- /dev/null +++ b/tests/pass-cond-blk-expr-var-2.nt @@ -0,0 +1,17 @@ +aa 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 + +--- +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 index 0000000..7fd0939 --- /dev/null +++ b/tests/pass-cond-blk-icitte.nt @@ -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 index 0000000..5d85efd --- /dev/null +++ b/tests/pass-cond-blk-label.nt @@ -0,0 +1,3 @@ +aa ff 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 index 0000000..fe7d599 --- /dev/null +++ b/tests/pass-cond-blk-var.nt @@ -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 index 0000000..e71fdbf --- /dev/null +++ b/tests/pass-readme-intro-cond.nt @@ -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 index 0000000..44c1ee1 --- /dev/null +++ b/tests/pass-readme-learn-cond-1.nt @@ -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 index 0000000..d922223 --- /dev/null +++ b/tests/pass-readme-learn-cond-2.nt @@ -0,0 +1,10 @@ + +u16le"meow mix!" + + +!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