Skip to content

Commit 6bb643d

Browse files
authored
Merge pull request #53 from jonathanhogg/enh_pure_expression_tree
Convert language into a pure expression tree
2 parents f2f78f4 + 94e3562 commit 6bb643d

File tree

13 files changed

+469
-432
lines changed

13 files changed

+469
-432
lines changed

docs/language.md

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -692,11 +692,11 @@ an insufficient number of matching arguments, otherwise any parameters lacking
692692
matching arguments will be bound to `null`. The result of evaluating all
693693
body expressions will be returned as a single vector to the caller.
694694

695-
Functions may only be declared at the top-level of a program and so cannot be
696-
nested within another function definition. Functions may refer to names defined
697-
above the function definition at the top level. These will be captured at
698-
definition time. Thus rebinding a name later in the same scope will be ignored.
699-
For example:
695+
Functions may be declared anywhere in a program including within another
696+
function definition. Functions may refer to names defined outside of the
697+
function definition at the top level. The values of these names will be
698+
captured at definition time and so rebinding a name later in the same scope
699+
will be ignored. For example:
700700

701701
```flitter
702702
let x=10
@@ -732,13 +732,41 @@ the value `1` and `z` taking the value `3`. Named arguments should be given
732732
*after* any positional arguments and should not repeat positional arguments.
733733

734734
Functions that have all literal (or unspecified) default parameter values, and
735-
that do not reference any non-local names within the body, will be in-lined by
735+
that do not reference any non-local names within the body, will be inlined by
736736
the simplifier at each call site. The simplifier is then able to bind the
737737
parameters to the argument expressions and continue simplifying the body on
738738
that basis. Therefore, it is often more efficient to pass dynamic values (such
739739
as `beat`) into the function as parameters than allow them to be captured from
740740
the environment.
741741

742+
## Anonymous functions
743+
744+
**Flitter** also allows anonymous functions to be defined and used as values
745+
with the syntax:
746+
747+
```flitter
748+
func (parameter《=default》《, parameter…》) body
749+
```
750+
751+
In the grammar rules, anonymous functions sit above inline `if`/`else`
752+
expressions, but below inline `for` and `where` expressions. Therefore, the
753+
following creates a vector of 10 anonymous functions, each of which multiplies
754+
by a different value, not a single function that does 10 multiplications in
755+
a loop:
756+
757+
```flitter
758+
let f = func(x) x*y for y in ..10
759+
```
760+
761+
Note that as function calls are allowed to accept a vector of functions, it is
762+
valid to call `f`. So `f(3)` will still evaluate to `0;3;6;9;12;15;18;21;24;27`.
763+
764+
It is wise to use parentheses to make the precedence explicit.
765+
766+
As with regular functions, any captured names are bound at the point of
767+
definition. An anonymous function cannot be recursive as it has no function
768+
name to use within the body.
769+
742770
## Template function calls
743771

744772
The special `@` operator provides syntactic sugar for calling a function using
@@ -813,8 +841,11 @@ between these different uses.
813841

814842
## Pragmas
815843

816-
There are three supported pragmas that may be placed at the top-level of a
817-
source file:
844+
One or more pragmas may be placed at the top of a source file before any other
845+
expressions. Pragmas take a name and a value, which must be a literal number
846+
or string.
847+
848+
There are three currently supported pragmas:
818849

819850
```flitter
820851
%pragma tempo 110
@@ -830,8 +861,8 @@ option).
830861
## Imports
831862

832863
Top-level definitions (`let`s and `func`s) may be imported from one **Flitter**
833-
program file into the top level of another. This allows common definitions to
834-
be collected into *modules* that can be used elsewhere. For example:
864+
program file into another. This allows common definitions to be collected into
865+
*modules* that can be used elsewhere. For example:
835866

836867
```flitter
837868
import SIZE;thing from 'common.fl'

src/flitter/engine/control.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ async def run(self):
186186
level = 'SUCCESS' if current_program is None else 'INFO'
187187
logger.log(level, "Loaded page {}: {}", self.current_page, self.current_path)
188188
run_program = current_program = program
189+
self.handle_pragmas(program.pragmas, frame_time)
189190
errors = set()
190191
logs = set()
191192
self.state_generation0 ^= self.state_generation1
@@ -243,7 +244,6 @@ async def run(self):
243244
run_program.run(context, record_stats=self.vm_stats)
244245
else:
245246
context = Context()
246-
self.handle_pragmas(context.pragmas, frame_time)
247247

248248
new_errors = context.errors.difference(errors) if errors is not None else context.errors
249249
errors = context.errors

src/flitter/language/grammar.lark

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
%import common.WS_INLINE
33
%ignore WS_INLINE
44
%ignore /\\$\n/m
5-
%declare _INDENT _DEDENT
5+
%declare _INDENT _DEDENT _EOF
66

77
_NL: /((--[^\r\n]*)?\r?\n[\t ]*)+/
88

@@ -16,35 +16,36 @@ _HASHBANG : /#!.*\r?\n/
1616
_LPAREN : "("
1717
_RPAREN : ")"
1818

19-
top : _HASHBANG? _NL? top_expressions
19+
top : _HASHBANG? _NL? pragmas _NL? sequence
2020

21-
top_expressions : top_expression* -> tuple
21+
pragmas : pragma* -> tuple
2222

23-
?top_expression : expression
24-
| "let" multiline_bindings -> let
25-
| "%pragma" NAME node _NL -> pragma
26-
| "import" name_list "from" composition _NL -> file_import
27-
| "func" NAME _LPAREN parameters _RPAREN _NL _INDENT sequence _DEDENT -> function
23+
pragma : "%pragma" NAME literal _NL -> binding
2824

29-
sequence : expressions [let_sequence]
25+
sequence : expressions
3026

31-
let_sequence : "let" multiline_bindings sequence
27+
expressions : expression* let_expression? -> tuple
3228

33-
expressions : expression* -> tuple
29+
let_expression : "let" multiline_bindings sequence -> let
30+
| function sequence -> let_function
31+
| "import" name_list "from" composition _NL sequence -> let_import
3432

3533
?expression : node _NL
3634
| node _NL _INDENT sequence _DEDENT -> append
3735
| "@" atom [attribute_bindings] _NL [_INDENT sequence _DEDENT] -> template_call
3836
| "for" name_list "in" range _NL _INDENT sequence _DEDENT -> loop
39-
| "if" tests ["else" _NL _INDENT sequence _DEDENT] -> if_else
37+
| "if" conditions ["else" _NL _INDENT sequence _DEDENT] -> if_else
38+
| _EOF -> export
39+
40+
function : "func" NAME _LPAREN parameters _RPAREN _NL _INDENT sequence _DEDENT
4041

4142
parameters : (parameter ("," parameter)*)? -> tuple
4243

4344
parameter : NAME ["=" node] -> binding
4445

45-
tests : test ("elif" test)* -> tuple
46+
conditions : condition ("elif" condition)* -> tuple
4647

47-
test : composition _NL _INDENT sequence _DEDENT
48+
condition : composition _NL _INDENT sequence _DEDENT
4849

4950
?node : composition
5051
| node TAG -> tag
@@ -68,6 +69,7 @@ compositions : comprehension (";" comprehension)+ -> tuple
6869
?comprehension : conditional
6970
| comprehension "for" name_list "in" conditional -> inline_loop
7071
| comprehension "where" inline_bindings -> inline_let
72+
| "func" _LPAREN parameters _RPAREN conditional -> anonymous_function
7173

7274
inline_bindings: binding+ -> tuple
7375

src/flitter/language/parser.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from lark import Lark, Transformer
1010
from lark.exceptions import UnexpectedInput
1111
from lark.indenter import Indenter
12+
from lark.lexer import Token
1213
from lark.visitors import v_args
1314

1415
from .. import model
@@ -26,6 +27,10 @@ class FlitterIndenter(Indenter):
2627
DEDENT_type = '_DEDENT'
2728
tab_len = 8
2829

30+
def _process(self, stream):
31+
yield from super()._process(stream)
32+
yield Token('_EOF', '')
33+
2934

3035
class ParseError(Exception):
3136
def __init__(self, msg, line, column, context):
@@ -68,14 +73,6 @@ def inline_if_else(self, then, condition, else_):
6873
def inline_loop(self, body, names, source):
6974
return tree.For(names, source, body)
7075

71-
def sequence(self, expressions, let_sequence=None):
72-
if let_sequence is not None:
73-
expressions = expressions + (let_sequence,)
74-
return tree.Sequence(expressions)
75-
76-
def let_sequence(self, bindings, expressions):
77-
return tree.InlineLet(expressions, bindings)
78-
7976
def call(self, function, args):
8077
args = list(args)
8178
bindings = []
@@ -91,24 +88,31 @@ def template_call(self, function, bindings, sequence):
9188
return tree.Call(function, (sequence,), bindings)
9289
return tree.Call(function, (tree.Literal(model.null),), bindings or None)
9390

91+
def let_function(self, function, sequence):
92+
return tree.Let((tree.PolyBinding((function.name,), function),), sequence)
93+
94+
def anonymous_function(self, parameters, body):
95+
return tree.Function('<anon>', parameters, body)
96+
9497
tuple = v_args(inline=False)(tuple)
9598

9699
add = tree.Add
97100
append = tree.Append
98101
attributes = tree.Attributes
99102
binding = tree.Binding
100103
bool = tree.Literal
104+
condition = tree.IfCondition
101105
divide = tree.Divide
106+
export = tree.Export
102107
eq = tree.EqualTo
103-
file_import = tree.Import
104-
floor_divide = tree.FloorDivide
105108
function = tree.Function
109+
floor_divide = tree.FloorDivide
106110
ge = tree.GreaterThanOrEqualTo
107111
gt = tree.GreaterThan
108112
if_else = tree.IfElse
109-
inline_let = tree.InlineLet
110113
le = tree.LessThanOrEqualTo
111114
let = tree.Let
115+
let_import = tree.Import
112116
literal = tree.Literal
113117
logical_and = tree.And
114118
logical_not = tree.Not
@@ -125,11 +129,10 @@ def template_call(self, function, bindings, sequence):
125129
poly_binding = tree.PolyBinding
126130
pos = tree.Positive
127131
power = tree.Power
128-
pragma = tree.Pragma
132+
sequence = tree.Sequence
129133
slice = tree.Slice
130134
subtract = tree.Subtract
131135
tag = tree.Tag
132-
test = tree.IfCondition
133136
top = tree.Top
134137

135138

0 commit comments

Comments
 (0)