diff --git a/docs/language.md b/docs/language.md index 2a2a70ae..5fed215a 100644 --- a/docs/language.md +++ b/docs/language.md @@ -559,6 +559,25 @@ ignored. If the vector is shorter, then the the additional names will be bound to items wrapped around from the start again. If the vector is `null` then all names will be bound to `null`. +A let binding may also bind one or more names to the result of evaluating an +indented "body" sequence of expressions. For example: + +```flitter +let foo= + !foo bar=12 + !baz +``` + +A let binding of this form may only have one name (or semicolon-separated list +of names for an unpacked vector binding) followed by an `=`, a newline and then +an indented sequence of expressions. This *sequence let* may contain any +multi-line sequence expressions, the same as the body of a function or a loop. +This is particularly useful for binding nested node structures to a name. + +If a semicolon-separated name list is provided in a sequence let, then the +names will be bound to values following the unpacking logic described above, +with the vector formed from the indented body being the source vector. + Names introduced with a `let` can redefine engine-supplied values, like `beat`, and built-ins, like `sin`. diff --git a/src/flitter/language/grammar.lark b/src/flitter/language/grammar.lark index f4d83ebf..7117897f 100644 --- a/src/flitter/language/grammar.lark +++ b/src/flitter/language/grammar.lark @@ -27,6 +27,7 @@ sequence : expressions expressions : expression* let_expression? -> tuple let_expression : "let" multiline_bindings sequence -> let + | "let" name_list "=" _NL _INDENT sequence _DEDENT sequence -> sequence_let | function sequence -> let_function | "import" name_list "from" composition _NL sequence -> let_import diff --git a/src/flitter/language/parser.py b/src/flitter/language/parser.py index 34f98471..283bfe39 100644 --- a/src/flitter/language/parser.py +++ b/src/flitter/language/parser.py @@ -98,6 +98,9 @@ def template_call(self, function, bindings, sequence): def let_function(self, function, sequence): return tree.Let((tree.PolyBinding((function.name,), function),), sequence) + def sequence_let(self, names, value, sequence): + return tree.Let((tree.PolyBinding(names, value),), sequence) + def anonymous_function(self, parameters, body): return tree.Function('', parameters, body) diff --git a/tests/test_language.py b/tests/test_language.py index 69b0c2fd..871227b4 100644 --- a/tests/test_language.py +++ b/tests/test_language.py @@ -372,6 +372,27 @@ def test_contains(self): !foo x=1 y=1 z=0 """, x=Vector((4, 5, 6)), y=Vector.range(10)) + def test_sequence_let(self): + self.assertCodeOutput( + """ +let foo;bar = + !foo x=1 + !bar + !baz + !bar b=2 + +!frob + bar + foo + """, + """ +!frob + !bar b=2 + !foo x=1 + !bar + !baz + """) + class ScriptTest(utils.TestCase): """