1
0
Fork 0
mirror of https://github.com/Reuh/anselme.git synced 2025-10-27 16:49:31 +00:00

Preprend every definition line type with a colon, remove function decorator, add immediately ran definition line

This commit is contained in:
Étienne Fildadut 2022-09-14 15:28:58 +09:00
parent 7b756ad092
commit d1f49d1894
117 changed files with 686 additions and 658 deletions

View file

@ -26,7 +26,7 @@ When executing a piece of Anselme code, your scripts will not directly modify th
Right after reaching a checkpoint line, Anselme will merge the local state with the global one, i.e., make every change accessible to other scripts, and get checkpointed changes from other scripts. Right after reaching a checkpoint line, Anselme will merge the local state with the global one, i.e., make every change accessible to other scripts, and get checkpointed changes from other scripts.
``` ```
$ main :$ main
:var = 5 :var = 5
~ var := 2 ~ var := 2
@ -35,14 +35,14 @@ $ main
(But if we run the script "parallel" in parallel at this point, it will still think var==5) (But if we run the script "parallel" in parallel at this point, it will still think var==5)
§ foo :! foo
But the variable will be merged with the global state on a checkpoint But the variable will be merged with the global state on a checkpoint
after: {var}==2, still, as expected after: {var}==2, still, as expected
(And if we run the script "parallel" in parallel at this point, it will now think var==2) (And if we run the script "parallel" in parallel at this point, it will now think var==2)
$ parallel :$ parallel
parallel: {main.var} parallel: {main.var}
~ main ~ main
@ -62,8 +62,6 @@ State merging also happens after a checkpoint has been manually called or resume
There's different types of lines, depending on their first character(s) (after indentation). There's different types of lines, depending on their first character(s) (after indentation).
#### Lines that can have children:
* `(`: comment line. Everything following this is ignored. Doesn't even check for children indentation and syntax correctness, so have fun. * `(`: comment line. Everything following this is ignored. Doesn't even check for children indentation and syntax correctness, so have fun.
``` ```
@ -74,6 +72,60 @@ There's different types of lines, depending on their first character(s) (after i
here here
``` ```
#### Text lines:
Lines that can append data to the event buffer and emit text or choice events.
* `>`: write a choice into the [event buffer](#event-buffer). Followed by arbitrary text. Support [text interpolation](#text-interpolation); if a text event is created during the text interpolation, it is added to the choice text content instead of the global event buffer. Support [escape codes](#escape-codes). Empty choices are discarded.
```
:$ f
Third choice
> First choice
> Second choice
> Last choice
> {f}
```
If an unescaped `~`, `~?` or `#` appears in the line, the associated operator is applied to the line (see [operators](#operators)), using the previous text as the left argument and everything that follows as the right argument expression.
```
(Conditionnaly executing a line)
:$ fn
> show this choice only once ~ 👁️
(Tagging a single line)
> tagged # 42
not tagged
```
* regular text, i.e. any line that doesn't start with a special line type caracter: write some text into the [event buffer](#event-buffer). Support [text interpolation](#text-interpolation). Support [escape codes](#escape-codes). Don't accept children lines.
```
Hello,
this is some text.
And this is more text, in a different event.
```
If an unescaped `~`, `~?` or `#` appears in the line, the associated operator is applied to the line (see [operators](#operators)), using the previous text as the left argument and everything that follows as the right argument expression.
```
(Conditionnaly executing a line)
:$ fn
run this line only once ~ 👁️
(Tagging a single line)
tagged # 42
```
* empty line: flush the event buffer, i.e., if there are any pending lines of text or choices, send them to your game. See [Event buffer](#event-buffer). This line always keep the same identation as the last non-empty line, so you don't need to put invisible whitespace on an empty-looking line. Is also automatically added at the end of a file. Don't accept children lines.
#### Expression lines:
Lines that evaluate an [expression](#expressions) and do something with the result.
* `~`: condition line. Can be followed by an [expression](#expressions); otherwise the expression `1` is assumed. If the expression evaluates to [true](#truethness), run its children. Without children, this line is typically use to simply run an expression. * `~`: condition line. Can be followed by an [expression](#expressions); otherwise the expression `1` is assumed. If the expression evaluates to [true](#truethness), run its children. Without children, this line is typically use to simply run an expression.
* `~~`: else-condition. Same as a condition line, but is only run if the last condition or else-condition line (in the same indentation block) was false (regardless of line distance). * `~~`: else-condition. Same as a condition line, but is only run if the last condition or else-condition line (in the same indentation block) was false (regardless of line distance).
@ -111,51 +163,111 @@ There's different types of lines, depending on their first character(s) (after i
This is run. This is run.
``` ```
* `>`: write a choice into the [event buffer](#event-buffer). Followed by arbitrary text. Support [text interpolation](#text-interpolation); if a text event is created during the text interpolation, it is added to the choice text content instead of the global event buffer. Support [escape codes](#escape-codes). Empty choices are discarded. * `#`: tag line. Can be followed by an [expression](#expressions); otherwise nil expression is assumed. The results of the [expression](#expressions) will be wrapped in a map and added to the tags send along with any `choice` or `text` event sent from its children. Can be nested.
``` ```
$ f # color="red"
Third choice Text tagged with a red color
> First choice # "blink"
> Second choice Tagged with a red color and blink.
> Last choice
> {f}
``` ```
If an unescaped `~`, `~?` or `#` appears in the line, the associated operator is applied to the line (see [operators](#operators)), using the previous text as the left argument and everything that follows as the right argument expression. * `@`: return line. Can be followed by an [expression](#expressions); otherwise nil expression is assumed. Exit the current function and returns the expression's value.
``` ```
(Conditionnaly executing a line) :$ hey
$ fn @5
> show this choice only once ~ 👁️
(Tagging a single line) {hey} = 5
> tagged # 42
not tagged
``` ```
* `$`: function line. Followed by an [identifier](#identifiers), then eventually an [alias](#aliases), and eventually a parameter list. Define a function using its children as function body. Also define a new namespace for its children (using the function name if it has no arguments, or a unique name otherwise). If this line has children, they will be ran _after_ evaluating the returned expression but _before_ exiting the current function. If the children return a value, it is used instead.
```
(Returns 0 and print 5)
:$ fn
:i=0
@i
~ i:=5
{i}
(Returns 3)
:$ g
@0
@3
```
Please note that Anselme will discard returns values sent from within a choice block. Returns inside choice block still have the expected behaviour of stopping the execution of the choice block.
This is the case because choice blocks are not ran right as they are read, but only at the next event flush (i.e. empty line). This means that if there is no flush in the function itself, the choice will be ran *after* the function has already been executed and returning a value at this point makes no sense:
```
:$ f
> a
@1
@2
(f will return 2 since the choice is run after the @2 line)
~ f == 2
Yes.
(Choice block is actually ran right before the "Yes" line, when the choice event is flushed.)
```
#### Definition lines:
Definition lines are used to define variables, constants, functions, checkpoints, and objects. Definition lines always start with `:`.
For every definition line type, it is possible to make it so it is immediately ran after definition by inserting a `~` after the initial `:`:
```
:~ var = &fn
:~$ loop
This text is run immediately.
> Loop
@loop
> Exit
```
is equivalent to
```
:var = &fn
~ var
:$ loop
This text is run immediately.
> Loop
@loop
> Exit
~ loop
```
* `:$`: function definition line. Followed by an [identifier](#identifiers), then eventually an [alias](#aliases), and eventually a parameter list. Define a function using its children as function body. Also define a new namespace for its children (using the function name if it has no arguments, or a unique name otherwise).
The function body is not executed when the line is reached; it must be explicitely called in an expression. See [expressions](#function-calls) to see the different ways of calling a function. The function body is not executed when the line is reached; it must be explicitely called in an expression. See [expressions](#function-calls) to see the different ways of calling a function.
A parameter list can be optionally given after the identifier. Parameter names are identifiers, with eventually an alias (after a `:`) and a default value (after a `=`), and then a type constraint (after a `::`). It is enclosed with paranthesis and contain a comma-separated list of identifiers: A parameter list can be optionally given after the identifier. Parameter names are identifiers, with eventually an alias (after a `:`) and a default value (after a `=`), and then a type constraint (after a `::`). It is enclosed with paranthesis and contain a comma-separated list of identifiers:
``` ```
$ f(a, b: alias for b, c="default for c", d: alias for d = "default for d") :$ f(a, b: alias for b, c="default for c", d: alias for d = "default for d")
first argument: {a} first argument: {a}
second argument: {b} second argument: {b}
third argument: {c} third argument: {c}
fourth argument: {d} fourth argument: {d}
$ f(a::string, b: alias for b::string, c::alias="default for c"::string) :$ f(a::string, b: alias for b::string, c::alias="default for c"::string)
same same
``` ```
Functions can also have a variable number of arguments. By adding `...` after the last argument identifier, it will be considered a variable length argument ("vararg"), and will contain a list of every extraneous argument. Functions can also have a variable number of arguments. By adding `...` after the last argument identifier, it will be considered a variable length argument ("vararg"), and will contain a list of every extraneous argument.
``` ```
$ f(a, b...) :$ f(a, b...)
{b} {b}
(will print [1]) (will print [1])
@ -172,7 +284,7 @@ When a parameter list is given (or just empty parentheses `()`), the function is
``` ```
(Non-scoped function: usual behaviour, variables are accessible from everywhere and always.) (Non-scoped function: usual behaviour, variables are accessible from everywhere and always.)
$ f :$ f
:a = 1 :a = 1
~ a += 1 ~ a += 1
@ -182,7 +294,7 @@ $ f
{f.a} is 2 {f.a} is 2
(Scoped function: can't access g.a from outside the function) (Scoped function: can't access g.a from outside the function)
$ g() :$ g()
:a = 1 :a = 1
{a} {a}
~ a += 1 ~ a += 1
@ -198,13 +310,13 @@ This is basically the behaviour you'd expect from functions in most other progra
Functions with the same name can be defined, as long as they have a different arguments. Functions will be selected based on the number of arguments given, their name and their type constraint: Functions with the same name can be defined, as long as they have a different arguments. Functions will be selected based on the number of arguments given, their name and their type constraint:
``` ```
$ f(a, b) :$ f(a, b)
a a
$ f(x) :$ f(x)
b b
$ f(x::string) :$ f(x::string)
c c
(will print a) (will print a)
@ -223,7 +335,7 @@ Every operator, except assignement operators, `|`, `&`, `,`, `~?`, `~` and `#` c
(binary operator names: _op_) (binary operator names: _op_)
(prefix unary operator: op_) (prefix unary operator: op_)
(suffix unary operator: _op) (suffix unary operator: _op)
$ _/_(a::string, b::string) :$ _/_(a::string, b::string)
@"{a}/{b}" @"{a}/{b}"
``` ```
@ -231,9 +343,9 @@ After the parameter list, you may also write `:=` followed by an identifier, and
``` ```
:x = "value" :x = "value"
$ f() :$ f()
@x @x
$ f() := v :$ f() := v
@x := v @x := v
value = {f} value = {f}
@ -250,7 +362,7 @@ Functions always have the following variables defined in its namespace by defaul
`👁️`: number, number of times the function was executed before `👁️`: number, number of times the function was executed before
`🔖`: function reference, last reached checkpoint. `nil` if no checkpoint reached. `🔖`: function reference, last reached checkpoint. `nil` if no checkpoint reached.
* `§`: checkpoint. Followed by an [identifier](#identifiers), then eventually an [alias](#aliases). Define a checkpoint. Also define a new namespace for its children. * `:!`: checkpoint definition. Followed by an [identifier](#identifiers), then eventually an [alias](#aliases). Define a checkpoint. Also define a new namespace for its children.
Checkpoints share most of their behavior with functions, with several exceptions. Like functions, the body is not executed when the line is reached; it must either be explicitely called in an expression or executed when resuming the parent function (see checkpoint behaviour below). Can be called in an expression. See [expressions](#checkpoint-calls) to see the different ways of calling a checkpoint manually. Checkpoints share most of their behavior with functions, with several exceptions. Like functions, the body is not executed when the line is reached; it must either be explicitely called in an expression or executed when resuming the parent function (see checkpoint behaviour below). Can be called in an expression. See [expressions](#checkpoint-calls) to see the different ways of calling a checkpoint manually.
@ -259,9 +371,9 @@ The local interpreter state will be merged with the global state when the line i
When executing the parent function after this checkpoint has been reached (using the paranthesis-less function call syntax), the function will resume from this checkpoint, and the checkpoint's children will be run. This is meant to be used as a way to restart the conversation from this point after it was interrupted, providing necessary context. When executing the parent function after this checkpoint has been reached (using the paranthesis-less function call syntax), the function will resume from this checkpoint, and the checkpoint's children will be run. This is meant to be used as a way to restart the conversation from this point after it was interrupted, providing necessary context.
``` ```
$ inane dialog :$ inane dialog
Hello George. Nice weather we're having today? Hello George. Nice weather we're having today?
§ interrupted :! interrupted
What was I saying? Ah yes, the weather... What was I saying? Ah yes, the weather...
(further dialog here) (further dialog here)
``` ```
@ -271,14 +383,14 @@ Checkpoints always have the following variable defined in its namespace by defau
`👁️`: number, number of times the checkpoint was executed before `👁️`: number, number of times the checkpoint was executed before
`🏁`: number, number of times the checkpoint was reached before (includes times where it was resumed from and executed) `🏁`: number, number of times the checkpoint was reached before (includes times where it was resumed from and executed)
* `%`: class. Followed by an [identifier](#identifiers), then eventually an [alias](#aliases). Define a class. Also define a new namespace for its children. * `:%`: class definition. Followed by an [identifier](#identifiers), then eventually an [alias](#aliases). Define a class. Also define a new namespace for its children.
Classes share most of their behavior with functions, with a few exceptions. Classes can not take arguments or be scoped; and when called, if the function does not return a value or returns `()` (nil), it will returns a new object instead based on this class. The object can be used to access variables ("attributes") defined in the class, but if one of these attributes is modified on the object it will not change the value in the base class but only in the object. Classes share most of their behavior with functions, with a few exceptions. Classes can not take arguments or be scoped; and when called, if the function does not return a value or returns `()` (nil), it will returns a new object instead based on this class. The object can be used to access variables ("attributes") defined in the class, but if one of these attributes is modified on the object it will not change the value in the base class but only in the object.
Objects can therefore be used to create independant data structures that can contain any variable defined in the base class, inspired by object-oriented programming. Objects can therefore be used to create independant data structures that can contain any variable defined in the base class, inspired by object-oriented programming.
``` ```
% class :% class
:a = 1 :a = 1
:object = class :object = class
@ -292,10 +404,10 @@ Is 1: {class.a}
Note that the new object returned by the class is also automatically given an annotation that is a reference to the class. This can be used to define methods/function that operate only on objects based on this specific class. Note that the new object returned by the class is also automatically given an annotation that is a reference to the class. This can be used to define methods/function that operate only on objects based on this specific class.
``` ```
% class :% class
:a = 1 :a = 1
$ show(object::&class) :$ show(object::&class)
a = {object.a} a = {object.a}
:object = class :object = class
@ -303,136 +415,19 @@ $ show(object::&class)
~ object!show ~ object!show
``` ```
* `#`: tag line. Can be followed by an [expression](#expressions); otherwise nil expression is assumed. The results of the [expression](#expressions) will be wrapped in a map and added to the tags send along with any `choice` or `text` event sent from its children. Can be nested. * `:`: variable declaration. Followed by an [identifier](#identifiers) (with eventually an [alias](#aliases)), a `=` and an [expression](#expressions). Defines a variable with a default value and this identifier in the current [namespace]("identifiers"). The expression is not evaluated instantly, but the first time the variable is used. Don't accept children lines.
```
# color="red"
Text tagged with a red color
# "blink"
Tagged with a red color and blink.
```
* `@`: return line. Can be followed by an [expression](#expressions); otherwise nil expression is assumed. Exit the current function and returns the expression's value.
```
$ hey
@5
{hey} = 5
```
If this line has children, they will be ran _after_ evaluating the returned expression but _before_ exiting the current function. If the children return a value, it is used instead.
```
(Returns 0 and print 5)
$ fn
:i=0
@i
~ i:=5
{i}
(Returns 3)
$ g
@0
@3
```
Please note that Anselme will discard returns values sent from within a choice block. Returns inside choice block still have the expected behaviour of stopping the execution of the choice block.
This is the case because choice blocks are not ran right as they are read, but only at the next event flush (i.e. empty line). This means that if there is no flush in the function itself, the choice will be ran *after* the function has already been executed and returning a value at this point makes no sense:
```
$ f
> a
@1
@2
(f will return 2 since the choice is run after the @2 line)
~ f == 2
Yes.
(Choice block is actually ran right before the "Yes" line, when the choice event is flushed.)
```
#### Lines that can't have children:
* `:`: variable declaration. Followed by an [identifier](#identifiers) (with eventually an [alias](#aliases)), a `=` and an [expression](#expressions). Defines a variable with a default value and this identifier in the current [namespace]("identifiers"). The expression is not evaluated instantly, but the first time the variable is used.
``` ```
:foo = 42 :foo = 42
:bar : alias = 12 :bar : alias = 12
``` ```
You can also use two colons instead of one to define a constant: * `::`: constant declaration. Work the same way as a variable declaration, but the variable can't be reassigned after their declaration and first evaluation, and their value is marked as constant (i.e. can not be modified even it is of a mutable type). Constants are not stored in save files and should therefore always contain the result of the expression written in the script file, even if the script has been updated.
``` ```
::foo = 42 ::foo = 42
``` ```
After their declaration and first evaluation, constants cannot be reassigned and their value is marked as constant (i.e. can not be modified even it is of a mutable type). Constants are not stored in save files and should therefore always contain the result of the expression written in the script file, even if the script has been updated.
* empty line: flush the event buffer, i.e., if there are any pending lines of text or choices, send them to your game. See [Event buffer](#event-buffer). This line always keep the same identation as the last non-empty line, so you don't need to put invisible whitespace on an empty-looking line. Is also automatically added at the end of a file.
* regular text: write some text into the [event buffer](#event-buffer). Support [text interpolation](#text-interpolation). Support [escape codes](#escape-codes).
```
Hello,
this is some text.
And this is more text, in a different event.
```
If an unescaped `~`, `~?` or `#` appears in the line, the associated operator is applied to the line (see [operators](#operators)), using the previous text as the left argument and everything that follows as the right argument expression.
```
(Conditionnaly executing a line)
$ fn
run this line only once ~ 👁️
(Tagging a single line)
tagged # 42
```
### Line decorators
Every line can also be followed with decorators, which are appended at the end of the line and affect its behaviour. Decorators are just syntaxic sugar to make some common operations simpler to write.
* `$`: function decorator. Same as a function line, behaving as if this line was it sole child, but also run the function. Function can not take arguments.
```
text $ f
```
is equivalent to:
```
~ f
$ f
text
```
This is typically used for immediatletly running functions when defining them, for example for a looping choice :
```
~$ loop
> Loop
@loop
> Exit
```
is equivalent to:
```
$ loop
> Loop
@loop
> Exit
~ loop
```
### Text interpolation ### Text interpolation
Text and choice lines allow for arbitrary text. Expression can be evaluated and inserted into the text as the line is executed by enclosing the [expression](#expressions) into brackets. The expressions are evaluated in the same order as the reading direction. Text and choice lines allow for arbitrary text. Expression can be evaluated and inserted into the text as the line is executed by enclosing the [expression](#expressions) into brackets. The expressions are evaluated in the same order as the reading direction.
@ -449,7 +444,7 @@ Text interpolated in choices have the special property of capturing text events
Value of a: {a} Value of a: {a}
(in text and choices, text events created from an interpolated expression are included in the final text) (in text and choices, text events created from an interpolated expression are included in the final text)
$ f :$ f
wor wor
(the returned value comes after) (the returned value comes after)
@"ld." @"ld."
@ -510,7 +505,7 @@ Beyond technical reasons, the event buffer serves as a way to group together sev
In practise, this is mostly useful to provide some choice or text from another function: In practise, this is mostly useful to provide some choice or text from another function:
``` ```
$ reusable choice :$ reusable choice
> Reusable choice > Reusable choice
> Choice A > Choice A
@ -538,7 +533,7 @@ Some text # tag
``` ```
(There is a space between the text and the tag expression; but there is a space as well after the text interpolation in the last line. The two spaces are converted into a single space (the space will belong to the first text element, i.e. the "text " element).) (There is a space between the text and the tag expression; but there is a space as well after the text interpolation in the last line. The two spaces are converted into a single space (the space will belong to the first text element, i.e. the "text " element).)
$ f :$ f
text # tag text # tag
Some {text} here. Some {text} here.
@ -555,9 +550,9 @@ When defining an identifier (using a function, checkpoint or variable delcaratio
In practise, this means you have to use the "genealogy" of the variable to refer to it from a line not in the same namespace: In practise, this means you have to use the "genealogy" of the variable to refer to it from a line not in the same namespace:
``` ```
$ fn1 :$ fn1
(everything here is in the fn1 namespace) (everything here is in the fn1 namespace)
$ fn2 :$ fn2
(fn1.fn2 namespace) (fn1.fn2 namespace)
:var2 = 42 :var2 = 42
Var2 = 42: {var2} Var2 = 42: {var2}
@ -749,14 +744,14 @@ When the identifier is preceeded by another expression directly (without any ope
The simplest way to call a function is simply to use its name. If the function has no arguments, parantheses are optional, or can be replaced with a `!`: The simplest way to call a function is simply to use its name. If the function has no arguments, parantheses are optional, or can be replaced with a `!`:
``` ```
$ f :$ f
called called
~ f ~ f
(equivalent to) (equivalent to)
~ f! ~ f!
$ f(a) :$ f(a)
called with {a} called with {a}
~ f("an argument") ~ f("an argument")
@ -765,9 +760,9 @@ $ f(a)
Please note, however, that if the function contains checkpoints, these two syntaxes behave differently. Without parantheses, the function will resume from the last reached checkpoint; with parantheses, the function always restart from its beginning: Please note, however, that if the function contains checkpoints, these two syntaxes behave differently. Without parantheses, the function will resume from the last reached checkpoint; with parantheses, the function always restart from its beginning:
``` ```
$ f :$ f
a a
§ checkpoint :! checkpoint
b b
c c
@ -785,12 +780,12 @@ Force no checkpoint, will write "a" and "c":
Functions with arguments can also be called with a "method-like" syntax using the `!` operator (though Anselme has no concept of classes and methods): Functions with arguments can also be called with a "method-like" syntax using the `!` operator (though Anselme has no concept of classes and methods):
``` ```
$ f(a) :$ f(a)
called with {a} called with {a}
"an argument"!f "an argument"!f
$ f(a, b) :$ f(a, b)
called with {a} and {b} called with {a} and {b}
"an argument"!f("another argument") "an argument"!f("another argument")
@ -799,7 +794,7 @@ $ f(a, b)
If the function has a return value, any of these calls will of course return the value. If the function has a return value, any of these calls will of course return the value.
``` ```
$ f :$ f
@"text" @"text"
this is text: {f} this is text: {f}
@ -808,12 +803,12 @@ this is text: {f}
Functions can also have default arguments. Defaults values can be any expression and are re-evaluated each time the function is called: Functions can also have default arguments. Defaults values can be any expression and are re-evaluated each time the function is called:
``` ```
$ f(a, b=1) :$ f(a, b=1)
@a+b @a+b
{f(1)} = 2 {f(1)} = 2
$ g(a, b=a) :$ g(a, b=a)
@a+b @a+b
{g(1)} = 2 {g(1)} = 2
@ -823,7 +818,7 @@ $ g(a, b=a)
Arguments can also be passed by naming them instead of their position. These syntaxes can be mixed: Arguments can also be passed by naming them instead of their position. These syntaxes can be mixed:
``` ```
$ f(a, b, c) :$ f(a, b, c)
@a + b + c @a + b + c
{f(1,2,3)} = {f(c=3,b=2,a=1)} = {f(1,2,c=3)} {f(1,2,3)} = {f(c=3,b=2,a=1)} = {f(1,2,c=3)}
@ -836,7 +831,7 @@ This means that pairs can't be passed directly as arguments to a function (as th
Functions can have a variable number of arguments. Additional arguments are added in a list: Functions can have a variable number of arguments. Additional arguments are added in a list:
``` ```
$ f(a, b...) :$ f(a, b...)
{a} {a}
{b} {b}
@ -851,16 +846,16 @@ $ f(a, b...)
Anselme use dynamic dispatch, meaning the correct function is selected at runtime. The correct function is selected based on number of arguments, argument names, and argument type constraint. The function with the most specific arguments will be selected. If several functions match, an error is thrown. Anselme use dynamic dispatch, meaning the correct function is selected at runtime. The correct function is selected based on number of arguments, argument names, and argument type constraint. The function with the most specific arguments will be selected. If several functions match, an error is thrown.
``` ```
$ fn(x::number, y) :$ fn(x::number, y)
a a
$ fn(x::number) :$ fn(x::number)
b b
$ fn(a::string) :$ fn(a::string)
c c
$ fn(x::number, y::number) :$ fn(x::number, y::number)
c c
a = {fn(5, "s")} a = {fn(5, "s")}
@ -871,9 +866,9 @@ c = {fn(5)}
d = {fn(5, 2)} d = {fn(5, 2)}
$ g(x) :$ g(x)
$ g(x, a="t") :$ g(x, a="t")
error, can't select unique function: {g(5)} error, can't select unique function: {g(5)}
``` ```
@ -887,9 +882,9 @@ Most of the time, you should'nt need to call checkpoints yourself - they will be
But in the cases when you want to manually set the current checkpoint, you can call it with a similar syntax to paranthesis-less function calls: But in the cases when you want to manually set the current checkpoint, you can call it with a similar syntax to paranthesis-less function calls:
``` ```
$ f :$ f
a a
§ checkpoint :! checkpoint
b b
c c
@ -907,9 +902,9 @@ Function can always be restarted from the begining using parantheses:
You can also only execute the checkpoints' children code only by using a parantheses-syntax: You can also only execute the checkpoints' children code only by using a parantheses-syntax:
``` ```
$ f :$ f
a a
§ checkpoint :! checkpoint
b b
c c

View file

@ -548,7 +548,7 @@ local vm_mt = {
else else
if type(fn) == "function" then fn = { value = fn } end if type(fn) == "function" then fn = { value = fn } end
self.state.link_next_function_definition_to_lua_function = fn self.state.link_next_function_definition_to_lua_function = fn
local s, e = self:loadstring("$"..signature, "", "lua") local s, e = self:loadstring(":$"..signature, "", "lua")
if not s then return nil, e end if not s then return nil, e end
assert(self.state.link_next_function_definition_to_lua_function == nil, "unexpected error while defining lua function") assert(self.state.link_next_function_definition_to_lua_function == nil, "unexpected error while defining lua function")
return self return self

View file

@ -62,8 +62,6 @@ TODO: type system is not super nice to use.
TODO: some sensible way to capture text event in string litterals (string interpolation/subtexts)? -> meh, this means we can capture choice events, and choice events contain their code block, and we don't want to store code blocks in the save file (as code can be updated/translated/whatever) TODO: some sensible way to capture text event in string litterals (string interpolation/subtexts)? -> meh, this means we can capture choice events, and choice events contain their code block, and we don't want to store code blocks in the save file (as code can be updated/translated/whatever)
ignoring choice events, we might be able to use subtexts in string litterals; using the same code for text lines and text litterals? we would lose tags... ignoring choice events, we might be able to use subtexts in string litterals; using the same code for text lines and text litterals? we would lose tags...
TODO: the function decorator feels a bit glued-on to the current syntax
TODO: simplify language, it is much too complicated. Less line types? (var def, func, checkpoint, tag). Rewrite some ad hoc syntax using the expression system? TODO: simplify language, it is much too complicated. Less line types? (var def, func, checkpoint, tag). Rewrite some ad hoc syntax using the expression system?
TODO: fn/checkpoint/tag: maybe consider them a regular func call that takes children as arg; can keep compatibility using $/§ as shortcut for the actual call. TODO: fn/checkpoint/tag: maybe consider them a regular func call that takes children as arg; can keep compatibility using $/§ as shortcut for the actual call.

View file

@ -62,15 +62,15 @@ local function parse(state)
line.expression = exp line.expression = exp
-- variable pending definition: expression will be evaluated when variable is needed -- variable pending definition: expression will be evaluated when variable is needed
if line.type == "definition" then if line.type == "definition" then
state.variables[line.fqm].value.expression = line.expression state.variables[line.name].value.expression = line.expression
-- parse constraints -- parse constraints
if line.constraint then if line.constraint then
local type_exp, rem2 = expression(line.constraint, state, namespace) local type_exp, rem2 = expression(line.constraint, state, namespace)
if not type_exp then return nil, ("in type constraint, %s; at %s"):format(rem2, line.source) end if not type_exp then return nil, ("in type constraint, %s; at %s"):format(rem2, line.source) end
if rem2:match("[^%s]") then if rem2:match("[^%s]") then
return nil, ("unexpected characters after variable %q: %q; at %s"):format(line.fqm, rem2, line.source) return nil, ("unexpected characters after variable %q: %q; at %s"):format(line.name, rem2, line.source)
end end
state.variable_constraints[line.fqm] = { pending = type_exp } state.variable_constraints[line.name] = { pending = type_exp }
end end
end end
end end

View file

@ -53,77 +53,112 @@ local function parse_line(line, state, namespace, parent_function)
r.type = "choice" r.type = "choice"
r.child = true r.child = true
r.text = l:match("^>%s*(.-)$") r.text = l:match("^>%s*(.-)$")
-- function & checkpoint -- definition
elseif l:match("^%$") or l:match("") or l:match("^%%") then -- § is a 2-bytes caracter, DO NOT USE LUA PATTERN OPERATORS as they operate on single bytes elseif l:match("^:") then
r.type = "function" local lr = l:match("^:(.*)$")
r.child = true -- immediately run variable
-- subtype options local run_immediately = false
local allow_params = true if lr:match("^~") then
local allow_assign = true lr = lr:match("^~(.*)$")
local keep_in_ast = false run_immediately = true
if l:match("^%$") then
r.subtype = "function"
r.resume_boundary = true
elseif l:match("^%%") then
r.subtype = "class"
r.resume_boundary = true
allow_params = false
allow_assign = false
elseif l:match("") then
r.subtype = "checkpoint"
allow_params = false
allow_assign = false
keep_in_ast = true
r.parent_function = parent_function -- store parent function and run checkpoint when line is read
else
error("unknown function line type")
end end
-- don't keep function node in block AST -- function & checkpoint
if not keep_in_ast then if lr:match("^%$") or lr:match("^%!") or lr:match("^%%") then -- § is a 2-bytes caracter, DO NOT USE LUA PATTERN OPERATORS as they operate on single bytes
r.remove_from_block_ast = true r.type = "function"
end r.child = true
-- lua function -- subtype options
if r.subtype == "function" and state.global_state.link_next_function_definition_to_lua_function then local allow_params = true
r.lua_function = state.global_state.link_next_function_definition_to_lua_function local allow_assign = true
state.global_state.link_next_function_definition_to_lua_function = nil local keep_in_ast = false
end if lr:match("^%$") then
-- get identifier r.subtype = "function"
local lc = l:match("^[%$%%](.-)$") or l:match("^§(.-)$") r.resume_boundary = true
local identifier, rem = lc:match("^("..identifier_pattern..")(.-)$") elseif lr:match("^%%") then
if not identifier then r.subtype = "class"
for _, name in ipairs(special_functions_names) do r.resume_boundary = true
identifier, rem = lc:match("^(%s*"..escape(name).."%s*)(.-)$") allow_params = false
if identifier then break end allow_assign = false
elseif lr:match("^%!") then
r.subtype = "checkpoint"
allow_params = false
allow_assign = false
keep_in_ast = true
r.parent_function = parent_function -- store parent function and run checkpoint when line is read
else
error("unknown function line type")
end end
end -- don't keep function node in block AST
if not identifier then if not keep_in_ast then
return nil, ("no valid identifier in function definition line %q; at %s"):format(lc, line.source) r.remove_from_block_ast = true
end end
-- format identifier -- lua function
local fqm = ("%s%s"):format(namespace, format_identifier(identifier)) if r.subtype == "function" and state.global_state.link_next_function_definition_to_lua_function then
local func_namespace = fqm .. "." r.lua_function = state.global_state.link_next_function_definition_to_lua_function
-- get alias state.global_state.link_next_function_definition_to_lua_function = nil
local ok_alias end
ok_alias, rem = maybe_alias(rem, fqm, namespace, line, state) -- get identifier
if not ok_alias then return ok_alias, rem end local lc = lr:match("^[%$%%%!](.-)$")
-- anything else are argument, isolate function it its own namespace local identifier, rem = lc:match("^("..identifier_pattern..")(.-)$")
-- (to not mix its args and variables with the main variant) if not identifier then
if rem:match("[^%s]") then for _, name in ipairs(special_functions_names) do
func_namespace = ("%s(%s)."):format(fqm, tostring(r)) identifier, rem = lc:match("^(%s*"..escape(name).."%s*)(.-)$")
r.private_namespace = true if identifier then break end
end end
-- define function end
if state.variables[fqm] then return nil, ("trying to define %s %s, but a variable with the same name exists; at %s"):format(r.type, fqm, line.source) end if not identifier then
r.namespace = func_namespace return nil, ("no valid identifier in function definition line %q; at %s"):format(lc, line.source)
r.name = fqm end
-- get params -- format identifier
r.params = {} local fqm = ("%s%s"):format(namespace, format_identifier(identifier))
if allow_params and rem:match("^%b()") then local func_namespace = fqm .. "."
r.scoped = true -- get alias
local content local ok_alias
content, rem = rem:match("^(%b())%s*(.*)$") ok_alias, rem = maybe_alias(rem, fqm, namespace, line, state)
content = content:gsub("^%(", ""):gsub("%)$", "") if not ok_alias then return ok_alias, rem end
for param in content:gmatch("[^%,]+") do -- anything else are argument, isolate function it its own namespace
-- (to not mix its args and variables with the main variant)
if rem:match("[^%s]") then
func_namespace = ("%s(%s)."):format(fqm, tostring(r))
r.private_namespace = true
end
-- define function
if state.variables[fqm] then return nil, ("trying to define %s %s, but a variable with the same name exists; at %s"):format(r.type, fqm, line.source) end
r.namespace = func_namespace
r.name = fqm
-- get params
r.params = {}
if allow_params and rem:match("^%b()") then
r.scoped = true
local content
content, rem = rem:match("^(%b())%s*(.*)$")
content = content:gsub("^%(", ""):gsub("%)$", "")
for param in content:gmatch("[^%,]+") do
-- get identifier
local param_identifier, param_rem = param:match("^("..identifier_pattern..")(.-)$")
if not param_identifier then return nil, ("no valid identifier in function parameter %q; at %s"):format(param, line.source) end
param_identifier = format_identifier(param_identifier)
-- format identifier
local param_fqm = ("%s%s"):format(func_namespace, param_identifier)
-- get alias
local ok_param_alias, param_alias
ok_param_alias, param_rem, param_alias = maybe_alias(param_rem, param_fqm, func_namespace, line, state)
if not ok_param_alias then return ok_param_alias, param_rem end
-- get potential type constraints and default value
local type_constraint, default
if param_rem:match("^::") then
type_constraint = param_rem:match("^::(.*)$")
elseif param_rem:match("^=") then
default = param_rem:match("^=(.*)$")
elseif param_rem:match("[^%s]") then
return nil, ("unexpected characters after parameter %q: %q; at %s"):format(param_fqm, param_rem, line.source)
end
-- add parameter
table.insert(r.params, { name = param_identifier, alias = param_alias, full_name = param_fqm, type_constraint = type_constraint, default = default, vararg = nil })
end
end
-- get assignment param
if allow_assign and rem:match("^%:%=") then
local param = rem:match("^%:%=(.*)$")
-- get identifier -- get identifier
local param_identifier, param_rem = param:match("^("..identifier_pattern..")(.-)$") local param_identifier, param_rem = param:match("^("..identifier_pattern..")(.-)$")
if not param_identifier then return nil, ("no valid identifier in function parameter %q; at %s"):format(param, line.source) end if not param_identifier then return nil, ("no valid identifier in function parameter %q; at %s"):format(param, line.source) end
@ -134,199 +169,178 @@ local function parse_line(line, state, namespace, parent_function)
local ok_param_alias, param_alias local ok_param_alias, param_alias
ok_param_alias, param_rem, param_alias = maybe_alias(param_rem, param_fqm, func_namespace, line, state) ok_param_alias, param_rem, param_alias = maybe_alias(param_rem, param_fqm, func_namespace, line, state)
if not ok_param_alias then return ok_param_alias, param_rem end if not ok_param_alias then return ok_param_alias, param_rem end
-- get potential type constraints and default value -- get potential type constraint
local type_constraint, default local type_constraint
if param_rem:match("^::") then if param_rem:match("^::") then
type_constraint = param_rem:match("^::(.*)$") type_constraint = param_rem:match("^::(.*)$")
elseif param_rem:match("^=") then
default = param_rem:match("^=(.*)$")
elseif param_rem:match("[^%s]") then elseif param_rem:match("[^%s]") then
return nil, ("unexpected characters after parameter %q: %q; at %s"):format(param_fqm, param_rem, line.source) return nil, ("unexpected characters after parameter %q: %q; at %s"):format(param_fqm, param_rem, line.source)
end end
-- add parameter -- add parameter
table.insert(r.params, { name = param_identifier, alias = param_alias, full_name = param_fqm, type_constraint = type_constraint, default = default, vararg = nil }) r.assignment = { name = param_identifier, alias = param_alias, full_name = param_fqm, type_constraint = type_constraint, default = nil, vararg = nil }
elseif rem:match("[^%s]") then
return nil, ("expected end-of-line at end of function definition line, but got %q; at %s"):format(rem, line.source)
end end
end -- calculate arity
-- get assignment param local minarity, maxarity = #r.params, #r.params
if allow_assign and rem:match("^%:%=") then for _, param in ipairs(r.params) do -- params with default values
local param = rem:match("^%:%=(.*)$") if param.default then
-- get identifier minarity = minarity - 1
local param_identifier, param_rem = param:match("^("..identifier_pattern..")(.-)$") end
if not param_identifier then return nil, ("no valid identifier in function parameter %q; at %s"):format(param, line.source) end
param_identifier = format_identifier(param_identifier)
-- format identifier
local param_fqm = ("%s%s"):format(func_namespace, param_identifier)
-- get alias
local ok_param_alias, param_alias
ok_param_alias, param_rem, param_alias = maybe_alias(param_rem, param_fqm, func_namespace, line, state)
if not ok_param_alias then return ok_param_alias, param_rem end
-- get potential type constraint
local type_constraint
if param_rem:match("^::") then
type_constraint = param_rem:match("^::(.*)$")
elseif param_rem:match("[^%s]") then
return nil, ("unexpected characters after parameter %q: %q; at %s"):format(param_fqm, param_rem, line.source)
end end
-- add parameter -- varargs
r.assignment = { name = param_identifier, alias = param_alias, full_name = param_fqm, type_constraint = type_constraint, default = nil, vararg = nil } if maxarity > 0 and r.params[maxarity].full_name:match("%.%.%.$") then
elseif rem:match("[^%s]") then r.params[maxarity].name = r.params[maxarity].name:match("^(.*)%.%.%.$")
return nil, ("expected end-of-line at end of function definition line, but got %q; at %s"):format(rem, line.source) r.params[maxarity].full_name = r.params[maxarity].full_name:match("^(.*)%.%.%.$")
end r.params[maxarity].vararg = true
-- calculate arity
local minarity, maxarity = #r.params, #r.params
for _, param in ipairs(r.params) do -- params with default values
if param.default then
minarity = minarity - 1 minarity = minarity - 1
maxarity = math.huge
end end
end r.arity = { minarity, maxarity }
-- varargs r.signature = signature(r)
if maxarity > 0 and r.params[maxarity].full_name:match("%.%.%.$") then r.pretty_signature = pretty_signature(r)
r.params[maxarity].name = r.params[maxarity].name:match("^(.*)%.%.%.$") -- check for signature conflict with functions with the same fqm
r.params[maxarity].full_name = r.params[maxarity].full_name:match("^(.*)%.%.%.$") if state.functions[fqm] then
r.params[maxarity].vararg = true for _, variant in ipairs(state.functions[fqm]) do
minarity = minarity - 1 if r.signature == variant.signature then
maxarity = math.huge return nil, ("trying to define %s %s, but another function with same signature %s exists; at %s"):format(r.type, r.pretty_signature, variant.pretty_signature, line.source)
end end
r.arity = { minarity, maxarity }
r.signature = signature(r)
r.pretty_signature = pretty_signature(r)
-- check for signature conflict with functions with the same fqm
if state.functions[fqm] then
for _, variant in ipairs(state.functions[fqm]) do
if r.signature == variant.signature then
return nil, ("trying to define %s %s, but another function with same signature %s exists; at %s"):format(r.type, r.pretty_signature, variant.pretty_signature, line.source)
end end
end end
end -- define variables
-- define variables if not line.children then line.children = {} end
if not line.children then line.children = {} end -- define 👁️ variable
-- define 👁️ variable local seen_alias = state.global_state.builtin_aliases["👁️"]
local seen_alias = state.global_state.builtin_aliases["👁️"] if seen_alias then
if seen_alias then table.insert(line.children, 1, { content = (":👁️:%s=0"):format(seen_alias), source = line.source })
table.insert(line.children, 1, { content = (":👁️:%s=0"):format(seen_alias), source = line.source })
else
table.insert(line.children, 1, { content = ":👁️=0", source = line.source })
end
if r.subtype ~= "checkpoint" then
-- define 🔖 variable
local checkpoint_alias = state.global_state.builtin_aliases["🔖"]
if checkpoint_alias then
table.insert(line.children, 1, { content = (":🔖:%s=()"):format(checkpoint_alias), source = line.source })
else else
table.insert(line.children, 1, { content = ":🔖=()", source = line.source }) table.insert(line.children, 1, { content = ":👁️=0", source = line.source })
end end
-- custom code injection if r.subtype ~= "checkpoint" then
if r.scoped then -- define 🔖 variable
if state.inject.scoped_function_start then local checkpoint_alias = state.global_state.builtin_aliases["🔖"]
for i, ll in ipairs(state.inject.scoped_function_start) do if checkpoint_alias then
table.insert(line.children, 1, { content = (":🔖:%s=()"):format(checkpoint_alias), source = line.source })
else
table.insert(line.children, 1, { content = ":🔖=()", source = line.source })
end
-- custom code injection
if r.scoped then
if state.inject.scoped_function_start then
for i, ll in ipairs(state.inject.scoped_function_start) do
table.insert(line.children, 1+i, copy(ll))
end
end
if state.inject.scoped_function_end then
for _, ll in ipairs(state.inject.scoped_function_end) do
table.insert(line.children, copy(ll))
end
end
else
if state.inject.function_start then
for i, ll in ipairs(state.inject.function_start) do
table.insert(line.children, 1+i, copy(ll))
end
end
if state.inject.function_end then
for _, ll in ipairs(state.inject.function_end) do
table.insert(line.children, copy(ll))
end
end
end
elseif r.subtype == "checkpoint" then
-- define 🏁 variable
local reached_alias = state.global_state.builtin_aliases["🏁"]
if reached_alias then
table.insert(line.children, 1, { content = (":🏁:%s=0"):format(reached_alias), source = line.source })
else
table.insert(line.children, 1, { content = ":🏁=0", source = line.source })
end
-- custom code injection
if state.inject.checkpoint_start then
for i, ll in ipairs(state.inject.checkpoint_start) do
table.insert(line.children, 1+i, copy(ll)) table.insert(line.children, 1+i, copy(ll))
end end
end end
if state.inject.scoped_function_end then if state.inject.checkpoint_end then
for _, ll in ipairs(state.inject.scoped_function_end) do for _, ll in ipairs(state.inject.checkpoint_end) do
table.insert(line.children, copy(ll))
end
end
else
if state.inject.function_start then
for i, ll in ipairs(state.inject.function_start) do
table.insert(line.children, 1+i, copy(ll))
end
end
if state.inject.function_end then
for _, ll in ipairs(state.inject.function_end) do
table.insert(line.children, copy(ll)) table.insert(line.children, copy(ll))
end end
end end
end end
elseif r.subtype == "checkpoint" then -- define args
-- define 🏁 variable for _, param in ipairs(r.params) do
local reached_alias = state.global_state.builtin_aliases["🏁"] if not state.variables[param.full_name] then
if reached_alias then state.variables[param.full_name] = {
table.insert(line.children, 1, { content = (":🏁:%s=0"):format(reached_alias), source = line.source }) type = "undefined argument",
else value = nil
table.insert(line.children, 1, { content = ":🏁=0", source = line.source }) }
end else
-- custom code injection return nil, ("trying to define parameter %q, but a variable with the same name exists; at %s"):format(param.full_name, line.source)
if state.inject.checkpoint_start then
for i, ll in ipairs(state.inject.checkpoint_start) do
table.insert(line.children, 1+i, copy(ll))
end end
end end
if state.inject.checkpoint_end then if r.assignment then
for _, ll in ipairs(state.inject.checkpoint_end) do if not state.variables[r.assignment.full_name] then
table.insert(line.children, copy(ll)) state.variables[r.assignment.full_name] = {
type = "undefined argument",
value = nil
}
else
return nil, ("trying to define parameter %q, but a variable with the same name exists; at %s"):format(r.assignment.full_name, line.source)
end end
end end
end -- define new function, no other variant yet
-- define args if not state.functions[fqm] then
for _, param in ipairs(r.params) do state.functions[fqm] = { r }
if not state.variables[param.full_name] then -- overloading
state.variables[param.full_name] = {
type = "undefined argument",
value = nil
}
else else
return nil, ("trying to define parameter %q, but a variable with the same name exists; at %s"):format(param.full_name, line.source) table.insert(state.functions[fqm], r)
end end
end -- variable and constants
if r.assignment then
if not state.variables[r.assignment.full_name] then
state.variables[r.assignment.full_name] = {
type = "undefined argument",
value = nil
}
else
return nil, ("trying to define parameter %q, but a variable with the same name exists; at %s"):format(r.assignment.full_name, line.source)
end
end
-- define new function, no other variant yet
if not state.functions[fqm] then
state.functions[fqm] = { r }
-- overloading
else else
table.insert(state.functions[fqm], r) r.type = "definition"
end r.remove_from_block_ast = true
-- definition local rem = lr
elseif l:match("^:") then -- check if constant
r.type = "definition" if rem:match("^:") then
r.remove_from_block_ast = true rem = rem:match("^:(.*)$")
local rem = l:match("^:(.*)$") r.constant = true
-- check if constant
if rem:match("^:") then
rem = rem:match("^:(.*)$")
r.constant = true
end
-- get identifier
local identifier
identifier, rem = rem:match("^("..identifier_pattern..")(.-)$")
if not identifier then return nil, ("no valid identifier at start of definition line %q; at %s"):format(l, line.source) end
-- format identifier
local fqm = ("%s%s"):format(namespace, format_identifier(identifier))
-- get alias
local ok_alias
ok_alias, rem = maybe_alias(rem, fqm, namespace, line, state)
if not ok_alias then return ok_alias, rem end
-- type constraint
if rem:match("^::(.-)=") then
r.constraint, rem = rem:match("^::%s*(.-)%s*(=.*)$")
end
-- get expression
local exp = rem:match("^=(.*)$")
if not exp then return nil, ("expected \"= expression\" after %q in definition line; at %s"):format(rem, line.source) end
-- define identifier
if state.functions[fqm] then return nil, ("trying to define variable %q, but a function with the same name exists; at %s"):format(fqm, line.source) end
if state.variables[fqm] then
if state.variables[fqm].type == "pending definition" then
return nil, ("trying to define variable %q but it is already defined at %s; at %s"):format(fqm, state.variables[fqm].value.source, line.source)
else
return nil, ("trying to define variable %q but it is already defined; at %s"):format(fqm, line.source)
end end
-- get identifier
local identifier
identifier, rem = rem:match("^("..identifier_pattern..")(.-)$")
if not identifier then return nil, ("no valid identifier at start of definition line %q; at %s"):format(l, line.source) end
-- format identifier
local fqm = ("%s%s"):format(namespace, format_identifier(identifier))
-- get alias
local ok_alias
ok_alias, rem = maybe_alias(rem, fqm, namespace, line, state)
if not ok_alias then return ok_alias, rem end
-- type constraint
if rem:match("^::(.-)=") then
r.constraint, rem = rem:match("^::%s*(.-)%s*(=.*)$")
end
-- get expression
local exp = rem:match("^=(.*)$")
if not exp then return nil, ("expected \"= expression\" after %q in definition line; at %s"):format(rem, line.source) end
-- define identifier
if state.functions[fqm] then return nil, ("trying to define variable %q, but a function with the same name exists; at %s"):format(fqm, line.source) end
if state.variables[fqm] then
if state.variables[fqm].type == "pending definition" then
return nil, ("trying to define variable %q but it is already defined at %s; at %s"):format(fqm, state.variables[fqm].value.source, line.source)
else
return nil, ("trying to define variable %q but it is already defined; at %s"):format(fqm, line.source)
end
end
r.name = fqm
r.expression = exp
state.variables[fqm] = { type = "pending definition", value = { expression = nil, source = r.source } }
if r.constant then state.variable_constants[fqm] = true end
end
-- add expression line after to perform the immediate execution
if run_immediately then
line.line_after = { content = "~ "..r.name, source = line.source }
end end
r.fqm = fqm
r.expression = exp
state.variables[fqm] = { type = "pending definition", value = { expression = nil, source = r.source } }
if r.constant then state.variable_constants[fqm] = true end
-- tag -- tag
elseif l:match("^%#") then elseif l:match("^%#") then
r.type = "tag" r.type = "tag"
@ -376,7 +390,7 @@ end
-- * nil, err: in case of error -- * nil, err: in case of error
local function parse_block(indented, state, namespace, parent_function) local function parse_block(indented, state, namespace, parent_function)
local block = { type = "block" } local block = { type = "block" }
for _, l in ipairs(indented) do for i, l in ipairs(indented) do
-- parsable line -- parsable line
local ast, err = parse_line(l, state, namespace, parent_function) local ast, err = parse_line(l, state, namespace, parent_function)
if err then return nil, err end if err then return nil, err end
@ -403,6 +417,11 @@ local function parse_block(indented, state, namespace, parent_function)
ast.child = r ast.child = r
end end
end end
-- insert line after
if l.line_after then
table.insert(indented, i+1, l.line_after)
end
end end
return block return block
end end
@ -416,17 +435,7 @@ local function transform_indented(indented)
if l.content:match("^%(") then if l.content:match("^%(") then
table.remove(indented, i) table.remove(indented, i)
else else
-- function decorator i = i + 1
if l.content:match("^.-[^\\]%$"..identifier_pattern.."$") then
local name
l.content, name = l.content:match("^(.-[^\\])%$("..identifier_pattern..")$")
indented[i] = { content = "~"..name, source = l.source }
table.insert(indented, i+1, { content = "$"..name, source = l.source, children = { l } })
i = i + 1 -- $ line should not contain any decorator anymore
else
i = i + 1 -- only increment when no decorator, as there may be several decorators per line
end
-- indented block -- indented block
if l.children then if l.children then
transform_indented(l.children) transform_indented(l.children)
@ -497,7 +506,7 @@ parse_indented = function(s, fnname, source)
return nil, ("invalid function name %q"):format(fnname) return nil, ("invalid function name %q"):format(fnname)
end end
indented = { indented = {
{ content = "$ "..fnname, source = ("%s:%s"):format(source, 0), children = indented }, { content = ":$ "..fnname, source = ("%s:%s"):format(source, 0), children = indented },
} }
end end
-- transform ast -- transform ast

View file

@ -422,21 +422,21 @@ lua_functions = {
} }
local anselme_functions = [[ local anselme_functions = [[
$ random(l...) :$ random(l...)
~ l(rand(1, l!len))! ~ l(rand(1, l!len))!
$ next(l...) :$ next(l...)
:j = 0 :j = 0
~? j += 1; j < len(l) & l(j).👁 != 0 ~? j += 1; j < len(l) & l(j).👁 != 0
~ l(j)! ~ l(j)!
$ cycle(l...) :$ cycle(l...)
:f = l(1) :f = l(1)
:j = 1 :j = 1
~? j += 1; j <= len(l) & !((f := l(j); 1) ~ l(j).👁 < f.👁) ~? j += 1; j <= len(l) & !((f := l(j); 1) ~ l(j).👁 < f.👁)
~ f! ~ f!
$ concat(l::list, separator=""::string) :$ concat(l::list, separator=""::string)
:r = "" :r = ""
:j = 0 :j = 0
~? j += 1; j <= len(l) ~? j += 1; j <= len(l)

View file

@ -1,4 +1,4 @@
$ f(str: foo) :$ f(str: foo)
@str + foo @str + foo
{f("bi")} = {f(foo="bi")} {f("bi")} = {f(foo="bi")}

View file

@ -1,7 +1,7 @@
$ _-_(a, b) :$ _-_(a, b)
@"generic minus" @"generic minus"
$ _-_(a::string, b::string) :$ _-_(a::string, b::string)
@a + " minus " + b @a + " minus " + b
{2-5} {2-5}

View file

@ -1,9 +1,9 @@
$ f :$ f
x x
§ p :! p
a a
§ q :! q
b b
c c

View file

@ -8,13 +8,13 @@
1,2,3: {l} 1,2,3: {l}
§ a :! a
~ l!insert(4) ~ l!insert(4)
1,2,3,4: {l} 1,2,3,4: {l}
§ b :! b
~ l!insert(5) ~ l!insert(5)
@ -22,5 +22,5 @@
~ error("cancel merge") ~ error("cancel merge")
$ after error :$ after error
1,2,3,4: {l} 1,2,3,4: {l}

View file

@ -8,13 +8,13 @@
2: {l} 2: {l}
§ a :! a
~ l := 3 ~ l := 3
3: {l} 3: {l}
§ b :! b
~ l := 4 ~ l := 4
@ -22,5 +22,5 @@
~ error("cancel merge") ~ error("cancel merge")
$ after error :$ after error
3: {l} 3: {l}

View file

@ -1,4 +1,4 @@
§ p :! p
seen! seen!
Seen: {p.seen} Seen: {p.seen}

View file

@ -1,4 +1,4 @@
$ f :$ f
> neol > neol
nah nah

View file

@ -4,7 +4,7 @@
> No > No
ko ko
$ jump button :$ jump button
A # 1 A # 1
> Suprise choice! > Suprise choice!
@"JOIN" @"JOIN"

View file

@ -5,7 +5,7 @@
> No > No
ko ko
$ jump button :$ jump button
A # 1 A # 1
@"SPLIT" @"SPLIT"

View file

@ -4,7 +4,7 @@
ko ko
~ choose(1) ~ choose(1)
$ jump button :$ jump button
A # 1 A # 1
> Other > Other
@ -13,6 +13,6 @@ $ jump button
ok ok
~ choose(2) ~ choose(2)
$ move axis :$ move axis
left # 1 left # 1
@" joystick" @" joystick"

View file

@ -1,4 +1,4 @@
$ f :$ f
# 42 # 42
> a > a
b b

View file

@ -1,4 +1,4 @@
$ bar :$ bar
:var=5 :var=5
~ var := 2 ~ var := 2
@ -7,14 +7,14 @@ $ bar
~ run("parallel") ~ run("parallel")
§ foo :! foo
checkpoint checkpoint
after: {var} after: {var}
~ run("parallel") ~ run("parallel")
$ parallel :$ parallel
parallel: {bar.var} parallel: {bar.var}
~ bar ~ bar

View file

@ -1,4 +1,4 @@
$ f :$ f
b b
a {f ~ 5} c a {f ~ 5} c

View file

@ -1,4 +1,4 @@
% obj :% obj
::a = 12 ::a = 12
:x = obj() :x = obj()

View file

@ -1,4 +1,4 @@
% obj :% obj
:a = 12 :a = 12
::x = obj() ::x = obj()

View file

@ -1,11 +1,11 @@
:person = "personne" :person = "personne"
$ Person(name, age) :$ Person(name, age)
@{name=name, age=age}::person @{name=name, age=age}::person
:abject = Person("Darmanin", 38) :abject = Person("Darmanin", 38)
{abject} {abject}
$ {}(p::person) :$ {}(p::person)
@"Name: {p("name")}\nAge: {p("age")}" @"Name: {p("name")}\nAge: {p("age")}"

View file

@ -1,3 +1,3 @@
$ a :$ a
:a = 2 :a = 2

View file

@ -1,3 +1,3 @@
:a = 2 :a = 2
$ a :$ a

View file

@ -1,9 +1,9 @@
$ f : test :$ f : test
@"ok" @"ok"
{f} = {test} {f} = {test}
$ g : bis(a) :$ g : bis(a)
@a @a
{g("ye")} = {bis("ye")} {g("ye")} = {bis("ye")}

View file

@ -1,4 +1,4 @@
$ f(a, l...) :$ f(a, l...)
{a} {a}
{l} {l}

View file

@ -1,4 +1,4 @@
$ f(a) :$ f(a)
{a} {a}
~ f("ok") ~ f("ok")

View file

@ -1,4 +1,4 @@
$ f(a, b) :$ f(a, b)
{a}{b} {a}{b}
~ f("ok") ~ f("ok")

View file

@ -1,4 +1,4 @@
$ f(a, b, l...) :$ f(a, b, l...)
{a}{b} {a}{b}
{l} {l}

View file

@ -1,4 +1,4 @@
$ f(a, b, l...) :$ f(a, b, l...)
{a}{b} {a}{b}
{l} {l}

View file

@ -1,4 +1,4 @@
$ f(a, b) :$ f(a, b)
{a}{b} {a}{b}
~ f("o", "k") ~ f("o", "k")

View file

@ -1,13 +1,13 @@
:a = 5 :a = 5
$ f(p) :$ f(p)
@a @a
$ f(p) := v :$ f(p) := v
v={v} v={v}
~ a := v ~ a := v
$ f(p) := v::string :$ f(p) := v::string
v2={v} v2={v}
~ a := p ~ a := p

View file

@ -1,5 +1,5 @@
$ f(a, b) :$ f(a, b)
$ f(x) :$ f(x)
$ f(a, b) :$ f(a, b)

View file

@ -2,10 +2,10 @@
:french name="french"::name :french name="french"::name
:esperanto name="esperanto"::name :esperanto name="esperanto"::name
$ a(name::string) :$ a(name::string)
{name} is english or generic {name} is english or generic
$ a(name:nom::french name) :$ a(name:nom::french name)
{nom} is french {nom} is french
~ a("bob"::name) ~ a("bob"::name)

View file

@ -2,10 +2,10 @@
:french name="french"::name :french name="french"::name
:esperanto name="esperanto"::name :esperanto name="esperanto"::name
$ a(name) :$ a(name)
{name} is english or generic {name} is english or generic
$ a(name:nom::french name) :$ a(name:nom::french name)
{nom} is french {nom} is french
~ a("bob"::name) ~ a("bob"::name)

View file

@ -1,9 +1,9 @@
$ f :$ f
$ a :$ a
a a
$ b :$ b
b b
$ c :$ c
c c
~ cycle(&a,&b,&c) ~ cycle(&a,&b,&c)

View file

@ -1,3 +0,0 @@
a.👁️: {a.👁️} $ a
~ a()

View file

@ -1,9 +0,0 @@
$ f
ko
a.👁️: {a.👁️} $ a
ok
~ f.a
In function:
~ f

View file

@ -1 +0,0 @@
a.👁️: {a.👁️} $ a

View file

@ -1,7 +1,7 @@
$ fn(x) :$ fn(x)
x x
$ fn(a) :$ fn(a)
a a
~ fn(a=5) ~ fn(a=5)

View file

@ -1,9 +1,9 @@
$ f :$ f
$ a :$ a
a a
$ b :$ b
b b
$ c :$ c
c c
~ next(&a, &b, &c) ~ next(&a, &b, &c)

View file

@ -1,5 +1,5 @@
$ f(a, b) :$ f(a, b)
$ f(x) :$ f(x)
$ f(u, v) :$ f(u, v)

View file

@ -1,9 +1,9 @@
$ f :$ f
$ a :$ a
a a
$ b :$ b
b b
$ c :$ c
c c
~ random(&a,&b,&c) ~ random(&a,&b,&c)

View file

@ -1,6 +1,6 @@
$ fn :$ fn
1 1
§ c :! c
2 2
:c = &fn :c = &fn

View file

@ -1,7 +1,7 @@
$ f(x) :$ f(x)
@x+2 @x+2
$ g(x) :$ g(x)
@x+3 @x+3
:a = &f :a = &f

View file

@ -1,7 +1,7 @@
$ fn :$ fn
@1 @1
$ add(x) :$ add(x)
@x+2 @x+2
:c = &fn :c = &fn

View file

@ -1,5 +1,5 @@
$ f :$ f
$ a :$ a
@12 @12
:x = &f :x = &f

View file

@ -1,4 +1,4 @@
$ f :$ f
:a = 12 :a = 12
:x = &f :x = &f

View file

@ -1,5 +1,5 @@
$ hey :$ hey
§ foo :! foo
@2 @2
@3 @3
@5 @5

View file

@ -1,4 +1,4 @@
$ hey :$ hey
@5 @5
a a
@2 @2

View file

@ -1,5 +1,5 @@
$ hey :$ hey
§ foo :! foo
@2 @2
@5 @5

View file

@ -1,4 +1,4 @@
$ hey :$ hey
@5 @5
{hey} {hey}

View file

@ -1,4 +1,4 @@
$ a :$ a
:b = 5 :b = 5
a: {b} a: {b}

View file

@ -1,4 +1,4 @@
$ a :$ a
:b = 5 :b = 5
a: {a.b} a: {a.b}

View file

@ -1,6 +1,6 @@
:n = 0 :n = 0
$ f(c=1) :$ f(c=1)
:a = [] :a = []
start: {a} start: {a}
@ -20,7 +20,7 @@ new list each time:
~ f ~ f
$ g(c=1, a=[]) :$ g(c=1, a=[])
start: {a} start: {a}
~ a!insert(c) ~ a!insert(c)

View file

@ -1,6 +1,6 @@
:n = 0 :n = 0
$ f(c=1) :$ f(c=1)
:a = 1 :a = 1
start: {a} start: {a}

View file

@ -1,11 +1,11 @@
$ f() :$ f()
:a = 1 :a = 1
{a} {a}
~ a := a + 1 ~ a := a + 1
$ g :$ g
:a = 1 :a = 1
{a} {a}

View file

@ -1,8 +1,8 @@
$ a(x::number) :$ a(x::number)
@x + 2 @x + 2
$ x :$ x
$ a(x::string) :$ a(x::string)
@x + "heh" @x + "heh"
{a("plop")} {a("plop")}

View file

@ -1,10 +1,10 @@
$ f :$ f
:a = 2 :a = 2
$ f(x) :$ f(x)
:a = 5 :a = 5
$ f(b) :$ f(b)
:a = 10 :a = 10
{f.a} = 2 {f.a} = 2

View file

@ -1,7 +1,7 @@
$ fn(x::number) :$ fn(x::number)
x x
$ fn(a::number) :$ fn(a::number)
a a
~ fn(5) ~ fn(5)

View file

@ -1,7 +1,7 @@
$ fn(x::number) :$ fn(x::number)
x x
$ fn(a="o"::string) :$ fn(a="o"::string)
a a
~ fn("s") ~ fn("s")
@ -10,10 +10,10 @@ $ fn(a="o"::string)
~ fn() ~ fn()
$ g(n="s", a=5::number) :$ g(n="s", a=5::number)
@"gn" @"gn"
$ g(n="s", a="lol"::string) :$ g(n="s", a="lol"::string)
@"gs" @"gs"
{g(n="k", a="l")} {g(n="k", a="l")}

View file

@ -1,7 +1,7 @@
$ fn(x::number) :$ fn(x::number)
x x
$ fn(a::string) :$ fn(a::string)
a a
~ fn("s") ~ fn("s")

View file

@ -1,4 +1,4 @@
$ f(a) :$ f(a)
{a} {a}
~ "ok"!f ~ "ok"!f

View file

@ -1,4 +1,4 @@
$ f(a, b) :$ f(a, b)
{a}{b} {a}{b}
~ "o"!f("k") ~ "o"!f("k")

View file

@ -1,4 +1,4 @@
$ f(l...) :$ f(l...)
{l} {l}
~ f() ~ f()

View file

@ -1,4 +1,4 @@
$ f(l...) :$ f(l...)
{l} {l}
~ f("o", "k") ~ f("o", "k")

View file

@ -1,4 +1,4 @@
$ f :$ f
ok ok
~ f ~ f

View file

@ -0,0 +1,4 @@
:~$ a
a.👁️: {a.👁️}
~ a()

View file

@ -0,0 +1,10 @@
:$ f
ko
:~$ a
a.👁️: {a.👁️}
ok
~ f.a
In function:
~ f

View file

@ -0,0 +1,2 @@
:~$ a
a.👁️: {a.👁️}

View file

@ -0,0 +1,4 @@
:$ a
a.👁️: {a.👁️}
:~ b = &a

View file

@ -0,0 +1,19 @@
local _={}
_[7]={}
_[6]={}
_[5]={tags=_[7],text="0"}
_[4]={tags=_[6],text="a.\240\159\145\129\239\184\143: "}
_[3]={_[4],_[5]}
_[2]={"return"}
_[1]={"text",_[3]}
return {_[1],_[2]}
--[[
{ "text", { {
tags = {},
text = "a.👁️: "
}, {
tags = {},
text = "0"
} } }
{ "return" }
]]--

View file

@ -1,14 +1,14 @@
Function with argument: Function with argument:
$ f(x) :$ f(x)
{&f} {&f}
Function without argument: Function without argument:
$ a :$ a
lol lol
$ b :$ b
hihi hihi
a: {a} a: {a}
@ -39,8 +39,8 @@ ref.b(): {ref.b()}
Objects: Objects:
% A :% A
$ b :$ b
KK KK
@1 @1

View file

@ -1,8 +1,8 @@
$ oh :$ oh
§ leave :! leave
in interrupt: {bar.var} in interrupt: {bar.var}
no no
$ bar :$ bar
:var = 5 :var = 5
~ var := 2 ~ var := 2
@ -11,7 +11,7 @@ $ oh
~ interrupt("leave") ~ interrupt("leave")
§ foo :! foo
checkpoint checkpoint
after: {var} after: {var}

View file

@ -1,9 +1,9 @@
$ leave :$ leave
in interrupt: {oh.bar.var} in interrupt: {oh.bar.var}
$ oh :$ oh
no no
$ bar :$ bar
:var = 5 :var = 5
~ var := 2 ~ var := 2
@ -12,7 +12,7 @@ $ oh
~ interrupt("leave") ~ interrupt("leave")
§ foo :! foo
checkpoint checkpoint
after: {var} after: {var}

View file

@ -1,16 +1,16 @@
$ bar :$ bar
:var = 5 :var = 5
~ var := 2 ~ var := 2
$ leave :$ leave
in interrupt: {var} in interrupt: {var}
before: {var} before: {var}
~ interrupt("leave") ~ interrupt("leave")
§ foo :! foo
checkpoint checkpoint
after: {var} after: {var}

View file

@ -1,16 +1,16 @@
$ bar :$ bar
:var = 5 :var = 5
~ var := 2 ~ var := 2
$ leave :$ leave
in interrupt: {var} in interrupt: {var}
before: {var} before: {var}
~ interrupt() ~ interrupt()
§ foo :! foo
checkpoint checkpoint
after: {var} after: {var}

View file

@ -1,8 +1,8 @@
$ a :$ a
a a
@1 @1
$ b :$ b
b b
@0 @0

View file

@ -10,17 +10,17 @@ x={x}
a(x)={a(x)} a(x)={a(x)}
§ ch a :! ch a
a(x)={a(x)} a(x)={a(x)}
§ ch b :! ch b
~ x(2) := 3 ~ x(2) := 3
a(x)={a(x)} a(x)={a(x)}
§ ch c :! ch c
a(x)={a(x)} a(x)={a(x)}

View file

@ -5,13 +5,13 @@
~ a!insert(b) ~ a!insert(b)
§ c :! c
~ b!insert(3) ~ b!insert(3)
§ d :! d
~ b!insert(4) ~ b!insert(4)
$ check :$ check
\[1,\[2,3,4]]: {a} \[1,\[2,3,4]]: {a}

View file

@ -5,15 +5,15 @@
~ a!insert(b) ~ a!insert(b)
§ c :! c
~ b!insert(3) ~ b!insert(3)
§ d :! d
~ b!insert(4) ~ b!insert(4)
~ error("abort") ~ error("abort")
$ check :$ check
\[1,\[2,3]]: {a} \[1,\[2,3]]: {a}

View file

@ -5,15 +5,15 @@
~ a!insert(b) ~ a!insert(b)
§ c :! c
~ b!insert(3) ~ b!insert(3)
§ d :! d
~ a!insert(4) ~ a!insert(4)
~ error("abort") ~ error("abort")
$ check :$ check
\[1,\[2,3]]: {a} \[1,\[2,3]]: {a}

View file

@ -5,13 +5,13 @@
~ a!insert(b) ~ a!insert(b)
§ c :! c
~ b!insert(3) ~ b!insert(3)
§ d :! d
~ a!insert(4) ~ a!insert(4)
$ check :$ check
\[1,\[2,3],4]: {a} \[1,\[2,3],4]: {a}

View file

@ -1,4 +1,4 @@
$ f(a, b, c) :$ f(a, b, c)
@a + b + c @a + b + c
{f("a", "b", "c")} = {f(a="a", b="b", c="c")} = {f(c="c", a="a", b="b")} {f("a", "b", "c")} = {f(a="a", b="b", c="c")} = {f(c="c", a="a", b="b")}

View file

@ -1,4 +1,4 @@
$ f(l...) :$ f(l...)
~ l!len ~ l!len
:a = 0 :a = 0
~ a := l(1) ~ a := l(1)

View file

@ -1,4 +1,4 @@
% class :% class
:a:b = "foo" :a:b = "foo"
:c = "bar" :c = "bar"

View file

@ -1,8 +1,8 @@
% class :% class
:a:b = "foo" :a:b = "foo"
:c = "bar" :c = "bar"
$ new(o::&class, x) :$ new(o::&class, x)
~ o.c := x ~ o.c := x
@o @o

View file

@ -1,4 +1,4 @@
% class :% class
:a:b = "foo" :a:b = "foo"
:c = "bar" :c = "bar"

View file

@ -1,4 +1,4 @@
% class :% class
:a:b = "foo" :a:b = "foo"
:c = "bar" :c = "bar"

View file

@ -1,4 +1,4 @@
$ f(a, b, c="c") :$ f(a, b, c="c")
@a + b + c @a + b + c
{f("a", "b")} = {f("a", "b", "c")} = {f(b="b", a="a")} {f("a", "b")} = {f("a", "b", "c")} = {f(b="b", a="a")}

View file

@ -1,4 +1,4 @@
§ f : test :! f : test
@"ok" @"ok"
{f} = {test} {f} = {test}

View file

@ -1,6 +1,6 @@
$ f :$ f
x x
§ p :! p
a a
b b

View file

@ -1,6 +1,6 @@
$ f :$ f
x x
§ p :! p
a a
b b

View file

@ -1,6 +1,6 @@
$ f :$ f
x x
§ p :! p
a a
b b

View file

@ -1,3 +1,3 @@
§ p :! p
a a
b b

View file

@ -1,9 +1,9 @@
$ f :$ f
x x
§ p :! p
a a
§ q :! q
b b
c c

View file

@ -1,9 +1,9 @@
$ f :$ f
# "a"="a" # "a"="a"
a a
~ 1 # "x"="x" ~ 1 # "x"="x"
# "b"="b" # "b"="b"
§ p :! p
b # "c"="c" b # "c"="c"
c c

Some files were not shown because too many files have changed in this diff Show more