mirror of
https://github.com/Reuh/anselme.git
synced 2025-10-27 16:49:31 +00:00
Changed a few things
- Bumped to 0.15.0 - Add boot script - Change variable definition syntax, using a = to distinguish more cleary between identifier and value - Variables initial values are evaluated on first use instead of at parsing time - Error on variable redefinition. Means you should make sure to load saves after your scripts. - Improve string parsing, support for escape codes - Remove support for number literals with empty decimal part (42. for 42.0) as there's no distinction in Anselme and it conflicts with .function call suffix - Changed priority of : pair operator - Add type type, and type annotations to variables and function parameters - Change Lua function system to use regular Anselme functions - Defining a function from Lua is now way simpler and require providing a full Anselme function signature - Change Anselme function system - Dynamic dispatch, based on arity, type annotation and parameter names. Will select the most specific function at runtime. - Define way to overload most operators - Allow custom type to text formatters - Allow assignment to custom functions - Index operator ( renamed to () - Functions with parameters each have their own private namespace (scoping ersatz) - Internal: "custom"-mode operators now have their own expression AST type instead of cluttering the function system - Remove static type checker as it is barely useful with new function system. May or may not rewrite one in the future. - Improve error messages here and there - Internal: cleaning
This commit is contained in:
parent
4b139019c9
commit
64bc85741a
86 changed files with 2096 additions and 1012 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
*.sublime-workspace
|
||||||
|
*.sublime-project
|
||||||
5
LICENSE
Normal file
5
LICENSE
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
Copyright 2019-2021 Étienne "Reuh" Fildadut
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
138
README.md
138
README.md
|
|
@ -3,7 +3,7 @@ Anselme
|
||||||
|
|
||||||
The overengineered dialog scripting system in pure Lua.
|
The overengineered dialog scripting system in pure Lua.
|
||||||
|
|
||||||
**Has been rewritten recently, doc and language are still WIP**
|
**Documentation and language are still WIP and will change.**
|
||||||
|
|
||||||
Purpose
|
Purpose
|
||||||
-------
|
-------
|
||||||
|
|
@ -94,7 +94,7 @@ Right after reaching a checkpoint line, Anselme will merge the local state with
|
||||||
|
|
||||||
```
|
```
|
||||||
$ main
|
$ main
|
||||||
:5 var
|
:var = 5
|
||||||
|
|
||||||
~ var := 2
|
~ var := 2
|
||||||
|
|
||||||
|
|
@ -164,11 +164,11 @@ There's different types of lines, depending on their first character(s) (after i
|
||||||
> Last choice
|
> Last choice
|
||||||
```
|
```
|
||||||
|
|
||||||
* `$`: 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.
|
* `$`: 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).
|
||||||
|
|
||||||
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 `=`). 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 annotation (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")
|
||||||
|
|
@ -176,6 +176,9 @@ $ f(a, b: alias for b, c="default for c", d: alias for d = "default for d")
|
||||||
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)
|
||||||
|
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.
|
||||||
|
|
@ -184,7 +187,6 @@ Functions can also have a variable number of arguments. By adding `...` after th
|
||||||
$ f(a, b...)
|
$ f(a, b...)
|
||||||
{b}
|
{b}
|
||||||
|
|
||||||
|
|
||||||
(will print [1])
|
(will print [1])
|
||||||
~ f("discarded", 1)
|
~ f("discarded", 1)
|
||||||
|
|
||||||
|
|
@ -195,7 +197,7 @@ $ f(a, b...)
|
||||||
~ f("discarded")
|
~ f("discarded")
|
||||||
```
|
```
|
||||||
|
|
||||||
Functions with the same name can be defined, as long as they have a different number of argument. Functions will be selected based on the number of arguments given:
|
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 annotation:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ f(a, b)
|
$ f(a, b)
|
||||||
|
|
@ -204,11 +206,40 @@ $ f(a, b)
|
||||||
$ f(x)
|
$ f(x)
|
||||||
b
|
b
|
||||||
|
|
||||||
|
$ f(x::string)
|
||||||
|
c
|
||||||
|
|
||||||
(will print a)
|
(will print a)
|
||||||
~ f(1,2)
|
~ f(1,2)
|
||||||
|
|
||||||
(will print b)
|
(will print b)
|
||||||
~ f(1)
|
~ f(1)
|
||||||
|
|
||||||
|
(will print c)
|
||||||
|
~ f("hello")
|
||||||
|
```
|
||||||
|
|
||||||
|
Every operator, except assignement operators, `|`, `&` and `,` can also be use as a function name in order to overload the operator:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ /(a::string, b::string)
|
||||||
|
@"{a}/{b}"
|
||||||
|
```
|
||||||
|
|
||||||
|
After the parameter list, you may also write `:=` followed by an identifier, and eventually an alias. This defines an assignement function, which will be called when assigning a value to the function:
|
||||||
|
|
||||||
|
```
|
||||||
|
:x = "value"
|
||||||
|
$ f()
|
||||||
|
@x
|
||||||
|
$ f() := v
|
||||||
|
@x := v
|
||||||
|
|
||||||
|
value = {f}
|
||||||
|
|
||||||
|
~ f() := "other"
|
||||||
|
|
||||||
|
other = {f}
|
||||||
```
|
```
|
||||||
|
|
||||||
Functions can return a value using a [return line](#lines-that-can-t-have-children).
|
Functions can return a value using a [return line](#lines-that-can-t-have-children).
|
||||||
|
|
@ -251,10 +282,11 @@ Checkpoints always have the following variable defined in its namespace by defau
|
||||||
|
|
||||||
#### Lines that can't have children:
|
#### Lines that can't have children:
|
||||||
|
|
||||||
* `:`: variable declaration. Followed by an [expression](#expressions) and an [identifier](#identifiers), then eventually an [alias](#aliases). Defines a variable with a default value and this identifier in the current [namespace]("identifiers"). Once defined, the type of a variable can not change.
|
* `:`: 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.
|
||||||
|
|
||||||
```
|
```
|
||||||
:42 foo
|
:foo = 42
|
||||||
|
:bar : alias = 12
|
||||||
```
|
```
|
||||||
|
|
||||||
* `@`: return line. Can be followed by an [expression](#expressions); otherwise nil expression is assumed. Exit the current function and returns the expression's value.
|
* `@`: return line. Can be followed by an [expression](#expressions); otherwise nil expression is assumed. Exit the current function and returns the expression's value.
|
||||||
|
|
@ -366,11 +398,13 @@ $ loop
|
||||||
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.
|
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.
|
||||||
|
|
||||||
```
|
```
|
||||||
:5 a
|
:a = 5
|
||||||
|
|
||||||
Value of a: {a}
|
Value of a: {a}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The expression is automatically wrapped in a call to `format(expr)`. You can overload `format` to change its behaviour for custom types.
|
||||||
|
|
||||||
### Event buffer
|
### Event buffer
|
||||||
|
|
||||||
Since Lua mainly run into a signle thread, Anselme need to give back control to the game at some point. This is done with flush event lines (empty lines), where the intrepreter yield its coroutine and returns a buch of data to your game (called the event buffer). It's called an event buffer because, well, it's a buffer, and events are what we call whatever Anselme sends back to your game.
|
Since Lua mainly run into a signle thread, Anselme need to give back control to the game at some point. This is done with flush event lines (empty lines), where the intrepreter yield its coroutine and returns a buch of data to your game (called the event buffer). It's called an event buffer because, well, it's a buffer, and events are what we call whatever Anselme sends back to your game.
|
||||||
|
|
@ -416,9 +450,9 @@ Every event have a type (`text`, `choice`, `return` or `error` by default, custo
|
||||||
|
|
||||||
### Identifiers
|
### Identifiers
|
||||||
|
|
||||||
Valid identifiers must be at least 1 caracters long and can contain anything except the caracters `%/*+-()!&|=$§?><:{}[],\`. They can contain spaces.
|
Valid identifiers must be at least 1 caracters long and can contain anything except the caracters ``~`^+-=<>/[]*{}|\_!?,;:()"@&$#%`` (that is, every special caracter on a US keyboard except '). They can contain spaces. They can not start with a number.
|
||||||
|
|
||||||
When defining an identifier (using a function, checkpoint or variable delcaration line), it will be defined into the current namespace (defined by the parent function/checkpoint). When evaluating an expression, Anselme will look for variables into the current line's namespace, then go up a level if it isn't found, and so on.
|
When defining an identifier (using a function, checkpoint or variable delcaration line), it will be defined into the current namespace (defined by the parent function/checkpoint). When evaluating an expression, Anselme will look for variables into the current line's namespace, then go up a level if it isn't found, and so on. Note that the namespace of functions with arguments are not accessible from outside the function.
|
||||||
|
|
||||||
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:
|
||||||
|
|
||||||
|
|
@ -427,17 +461,17 @@ $ 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}
|
||||||
|
|
||||||
Var2 = not found: {var2}
|
Var2 = not found: {var2}
|
||||||
Var2 = 42: {fn2.var2}
|
Var2 = 42: {fn2.var2}
|
||||||
|
|
||||||
:var1 1
|
:var1 = 1
|
||||||
|
|
||||||
Var2 = 42: {fn1.fn2.var2}
|
Var2 = 42: {fn1.fn2.var2}
|
||||||
|
|
||||||
:var1 2
|
:var1 = 2
|
||||||
|
|
||||||
Var1 in the current namespace = 1: {var1}
|
Var1 in the current namespace = 1: {var1}
|
||||||
Var1 in the fn1 namespace = 2: {fn1.var1}
|
Var1 in the fn1 namespace = 2: {fn1.var1}
|
||||||
|
|
@ -451,7 +485,7 @@ Var1 in the fn1 namespace = 2: {fn1.var1}
|
||||||
When defining identifiers (in variables, functions or checkpoint definitions), they can be followed by a colon and another identifier. This identifier can be used as a new way to access the identifier (i.e., an alias).
|
When defining identifiers (in variables, functions or checkpoint definitions), they can be followed by a colon and another identifier. This identifier can be used as a new way to access the identifier (i.e., an alias).
|
||||||
|
|
||||||
```
|
```
|
||||||
:42 name: alias
|
:name: alias = 42
|
||||||
|
|
||||||
{name} is the same as {alias}
|
{name} is the same as {alias}
|
||||||
```
|
```
|
||||||
|
|
@ -466,12 +500,12 @@ Anselme's solution is to keep the original name in the translated script file, b
|
||||||
|
|
||||||
```
|
```
|
||||||
(in the original, english script)
|
(in the original, english script)
|
||||||
:"John Pizzapone" player name
|
:player name = "John Pizzapone"
|
||||||
|
|
||||||
Hi {player name}!
|
Hi {player name}!
|
||||||
|
|
||||||
(in a translated, french script)
|
(in a translated, french script)
|
||||||
:"John Pizzapone" player name : nom du joueur
|
:player name : nom du joueur = "John Pizzapone"
|
||||||
|
|
||||||
Salut {nom du joueur} !
|
Salut {nom du joueur} !
|
||||||
```
|
```
|
||||||
|
|
@ -490,14 +524,16 @@ Default types are:
|
||||||
|
|
||||||
* `nil`: nil. Can be defined using empty parantheses `()`.
|
* `nil`: nil. Can be defined using empty parantheses `()`.
|
||||||
|
|
||||||
* `number`: a number. Can be defined similarly to Lua number literals.
|
* `number`: a number (double). Can be defined using the forms `42`, `.42`, `42.42`.
|
||||||
|
|
||||||
* `string`: a string. Can be defined between double quotes `"string"`. Support [text interpolation](#text-interpolation).
|
* `string`: a string. Can be defined between double quotes `"string"`. Support [text interpolation](#text-interpolation). Support the escape codes `\\` for `\`, `\"` for `"`, `\n` for a newline and `\t` for a tabulation.
|
||||||
|
|
||||||
* `list`: a list of values. Types can be mixed. Can be defined between square brackets and use comma as a separator '[1,2,3,4]'.
|
* `list`: a list of values. Types can be mixed. Can be defined between square brackets and use comma as a separator '[1,2,3,4]'.
|
||||||
|
|
||||||
* `pair`: a couple of values. Types can be mixed. Can be defined using colon `"key":5`. Pairs named by a string that is also a valid identifier can be created using the `key=5` shorthand syntax.
|
* `pair`: a couple of values. Types can be mixed. Can be defined using colon `"key":5`. Pairs named by a string that is also a valid identifier can be created using the `key=5` shorthand syntax.
|
||||||
|
|
||||||
|
* `type`: a couple of values. Types can be mixed. Can be defined using colon `expr::type`. The second value is used in type checks, this is intended to be use to give a custom type to a value.
|
||||||
|
|
||||||
How conversions are handled from Anselme to Lua:
|
How conversions are handled from Anselme to Lua:
|
||||||
|
|
||||||
* `nil` -> `nil`
|
* `nil` -> `nil`
|
||||||
|
|
@ -628,6 +664,36 @@ $ f(a, b...)
|
||||||
[2,3,4,5]
|
[2,3,4,5]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
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 annotations. The function with the most specific arguments will be selected. If several functions match, an error is thrown.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ fn(x::number, y)
|
||||||
|
a
|
||||||
|
|
||||||
|
$ fn(x::number)
|
||||||
|
b
|
||||||
|
|
||||||
|
$ fn(a::string)
|
||||||
|
c
|
||||||
|
|
||||||
|
$ fn(x::number, y::number)
|
||||||
|
c
|
||||||
|
|
||||||
|
a = {fn(5, "s")}
|
||||||
|
|
||||||
|
b = {fn("s")}
|
||||||
|
|
||||||
|
c = {fn(5)}
|
||||||
|
|
||||||
|
d = {fn(5, 2)}
|
||||||
|
|
||||||
|
$ g(x)
|
||||||
|
|
||||||
|
$ g(x, a="t")
|
||||||
|
|
||||||
|
error, can't select unique function: {g(5)}
|
||||||
|
```
|
||||||
|
|
||||||
#### Checkpoint calls
|
#### Checkpoint calls
|
||||||
|
|
||||||
Most of the time, you should'nt need to call checkpoints yourself - they will be automatically be set as the active checkpoint when the interperter reach their line, and they will be automatically called when resuming its parent function.
|
Most of the time, you should'nt need to call checkpoints yourself - they will be automatically be set as the active checkpoint when the interperter reach their line, and they will be automatically called when resuming its parent function.
|
||||||
|
|
@ -677,6 +743,24 @@ Please also be aware that when resuming from a checkpoint, Anselme will try to r
|
||||||
* if the checkpoint is in a choice block, it will assume this choice was selected (but will not re-evaluate any of the choices from the same choice group)
|
* if the checkpoint is in a choice block, it will assume this choice was selected (but will not re-evaluate any of the choices from the same choice group)
|
||||||
* will try to re-add every tag from parent lines; this require Anselme to re-evaluate every tag line and decorator that's a parent of the checkpoint in the function. Be careful if your tag expressions have side-effects.
|
* will try to re-add every tag from parent lines; this require Anselme to re-evaluate every tag line and decorator that's a parent of the checkpoint in the function. Be careful if your tag expressions have side-effects.
|
||||||
|
|
||||||
|
##### Operator priority
|
||||||
|
|
||||||
|
From lowest to highest priority:
|
||||||
|
|
||||||
|
```
|
||||||
|
;
|
||||||
|
:= += -= //= /= *= %= ^=
|
||||||
|
,
|
||||||
|
| &
|
||||||
|
!= == >= <= < >
|
||||||
|
+ -
|
||||||
|
* // / %
|
||||||
|
:: :
|
||||||
|
unary -, unary !
|
||||||
|
^
|
||||||
|
.
|
||||||
|
```
|
||||||
|
|
||||||
#### Operators
|
#### Operators
|
||||||
|
|
||||||
Built-in operators:
|
Built-in operators:
|
||||||
|
|
@ -731,7 +815,9 @@ This only works on strings:
|
||||||
|
|
||||||
`a : b`: evaluate a and b, returns a new pair with a as key and b as value.
|
`a : b`: evaluate a and b, returns a new pair with a as key and b as value.
|
||||||
|
|
||||||
`a(b)`: evaluate b (number), returns the value with this index in a (list). Use 1-based indexing. If b is a string, will search the first pair in the list with this string as its name.
|
`a :: b`: evaluate a and b, returns a new typed value with a as value and b as type.
|
||||||
|
|
||||||
|
`a(b)`: evaluate b (number), returns the value with this index in a (list). Use 1-based indexing. If b is a string, will search the first pair in the list with this string as its name. Operator is named `()`.
|
||||||
|
|
||||||
#### Built-in functions
|
#### Built-in functions
|
||||||
|
|
||||||
|
|
@ -761,8 +847,20 @@ This only works on strings:
|
||||||
|
|
||||||
##### Various
|
##### Various
|
||||||
|
|
||||||
|
`format(v)`: function called when formatting a value in a text interpolation
|
||||||
|
|
||||||
`rand([m[, n]])`: when called whitout arguments, returns a random float in [0,1). Otherwise, returns a random number in [m,n]; m=1 if not given.
|
`rand([m[, n]])`: when called whitout arguments, returns a random float in [0,1). Otherwise, returns a random number in [m,n]; m=1 if not given.
|
||||||
|
|
||||||
|
`error(str)`: throw an error with the specified message
|
||||||
|
|
||||||
|
`raw(v)`: return v, stripped of its custom types
|
||||||
|
|
||||||
|
`type(v)`: return v's type
|
||||||
|
|
||||||
|
#### Built-in variables
|
||||||
|
|
||||||
|
TODO see stdlib/bootscript.lua
|
||||||
|
|
||||||
API reference
|
API reference
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
|
|
||||||
78
anselme.lua
78
anselme.lua
|
|
@ -4,12 +4,14 @@ local anselme = {
|
||||||
-- major.minor.fix
|
-- major.minor.fix
|
||||||
-- saves files are incompatible between major versions
|
-- saves files are incompatible between major versions
|
||||||
-- scripts files may break between minor versions
|
-- scripts files may break between minor versions
|
||||||
version = "0.14.0",
|
version = "0.15.0",
|
||||||
--- currently running interpreter
|
--- currently running interpreter
|
||||||
running = nil
|
running = nil
|
||||||
}
|
}
|
||||||
package.loaded[...] = anselme
|
package.loaded[...] = anselme
|
||||||
|
|
||||||
|
i = require("inspect") -- luacheck: ignore
|
||||||
|
|
||||||
-- load libs
|
-- load libs
|
||||||
local preparse = require((...):gsub("anselme$", "parser.preparser"))
|
local preparse = require((...):gsub("anselme$", "parser.preparser"))
|
||||||
local postparse = require((...):gsub("anselme$", "parser.postparser"))
|
local postparse = require((...):gsub("anselme$", "parser.postparser"))
|
||||||
|
|
@ -17,8 +19,10 @@ local expression = require((...):gsub("anselme$", "parser.expression"))
|
||||||
local eval = require((...):gsub("anselme$", "interpreter.expression"))
|
local eval = require((...):gsub("anselme$", "interpreter.expression"))
|
||||||
local run_line = require((...):gsub("anselme$", "interpreter.interpreter")).run_line
|
local run_line = require((...):gsub("anselme$", "interpreter.interpreter")).run_line
|
||||||
local to_lua = require((...):gsub("anselme$", "interpreter.common")).to_lua
|
local to_lua = require((...):gsub("anselme$", "interpreter.common")).to_lua
|
||||||
|
local identifier_pattern = require((...):gsub("anselme$", "parser.common")).identifier_pattern
|
||||||
local merge_state = require((...):gsub("anselme$", "interpreter.common")).merge_state
|
local merge_state = require((...):gsub("anselme$", "interpreter.common")).merge_state
|
||||||
local stdfuncs = require((...):gsub("anselme$", "stdlib.functions"))
|
local stdfuncs = require((...):gsub("anselme$", "stdlib.functions"))
|
||||||
|
local bootscript = require((...):gsub("anselme$", "stdlib.bootscript"))
|
||||||
|
|
||||||
-- wrappers for love.filesystem / luafilesystem
|
-- wrappers for love.filesystem / luafilesystem
|
||||||
local function list_directory(path)
|
local function list_directory(path)
|
||||||
|
|
@ -151,7 +155,7 @@ local interpreter_methods = {
|
||||||
local co = coroutine.create(function()
|
local co = coroutine.create(function()
|
||||||
local r, e = eval(self.state, expr)
|
local r, e = eval(self.state, expr)
|
||||||
if not r then return "error", e end
|
if not r then return "error", e end
|
||||||
return "return", to_lua(r)
|
return "return", r
|
||||||
end)
|
end)
|
||||||
local previous = anselme.running
|
local previous = anselme.running
|
||||||
anselme.running = self
|
anselme.running = self
|
||||||
|
|
@ -162,7 +166,7 @@ local interpreter_methods = {
|
||||||
elseif event ~= "return" then
|
elseif event ~= "return" then
|
||||||
return nil, ("evaluated expression generated an %q event"):format(event)
|
return nil, ("evaluated expression generated an %q event"):format(event)
|
||||||
else
|
else
|
||||||
return data
|
return to_lua(data)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
@ -170,6 +174,9 @@ interpreter_methods.__index = interpreter_methods
|
||||||
|
|
||||||
--- vm methods
|
--- vm methods
|
||||||
local vm_mt = {
|
local vm_mt = {
|
||||||
|
-- anselme state
|
||||||
|
state = nil,
|
||||||
|
|
||||||
--- wrapper for loading a whole set of scripts
|
--- wrapper for loading a whole set of scripts
|
||||||
-- should be preferred to other loading functions if possible
|
-- should be preferred to other loading functions if possible
|
||||||
-- will load in path, in order:
|
-- will load in path, in order:
|
||||||
|
|
@ -270,39 +277,30 @@ local vm_mt = {
|
||||||
end,
|
end,
|
||||||
|
|
||||||
--- define functions from Lua
|
--- define functions from Lua
|
||||||
-- name: full name of the function
|
-- signature: full signature of the function
|
||||||
-- fn: function (Lua function or table, see examples in stdlib/functions.lua)
|
-- fn: function (Lua function or table, see examples in stdlib/functions.lua)
|
||||||
-- return self
|
-- return self
|
||||||
loadfunction = function(self, name, fn)
|
loadfunction = function(self, signature, fn)
|
||||||
if type(name) == "table" then
|
if type(signature) == "table" then
|
||||||
for k, v in pairs(name) do
|
for k, v in pairs(signature) do
|
||||||
if type(v) == "table" then
|
local s, e = self:loadfunction(k, v)
|
||||||
for _, variant in ipairs(v) do
|
if not s then return nil, e end
|
||||||
self:loadfunction(k, variant)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
self:loadfunction(k, v)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
if not self.state.functions[name] then
|
if type(fn) == "function" then fn = { value = fn } end
|
||||||
self.state.functions[name] = {}
|
self.state.link_next_function_definition_to_lua_function = fn
|
||||||
end
|
local s, e = self:loadstring("$"..signature, "", "lua")
|
||||||
if type(fn) == "function" then
|
if not s then return nil, e end
|
||||||
local info = debug.getinfo(fn)
|
assert(self.state.link_next_function_definition_to_lua_function == nil, "unexpected error while defining lua function")
|
||||||
table.insert(self.state.functions[name], {
|
return self
|
||||||
arity = info.isvararg and {info.nparams, math.huge} or info.nparams,
|
|
||||||
value = fn
|
|
||||||
})
|
|
||||||
else
|
|
||||||
table.insert(self.state.functions[name], fn)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
return self
|
return self
|
||||||
end,
|
end,
|
||||||
|
|
||||||
--- save/load script state
|
--- save/load script state
|
||||||
-- only saves variables full names and values, so make sure to not change important variables, checkpoints and functions names between a save and a load
|
-- only saves variables full names and values, so make sure to not change important variables, checkpoints and functions names between a save and a load
|
||||||
|
-- only save variables with usable identifiers, so will skip functions with arguments, operators, etc.
|
||||||
|
-- loading should be after loading scripts (otherwise you will "variable already defined" errors)
|
||||||
load = function(self, data)
|
load = function(self, data)
|
||||||
local saveMajor, currentMajor = data.anselme_version:match("^[^%.]*"), anselme.version:match("^[^%.]*")
|
local saveMajor, currentMajor = data.anselme_version:match("^[^%.]*"), anselme.version:match("^[^%.]*")
|
||||||
assert(saveMajor == currentMajor, ("trying to load data from an incompatible version of Anselme; save was done using %s but current version is %s"):format(data.anselme_version, anselme.version))
|
assert(saveMajor == currentMajor, ("trying to load data from an incompatible version of Anselme; save was done using %s but current version is %s"):format(data.anselme_version, anselme.version))
|
||||||
|
|
@ -314,7 +312,7 @@ local vm_mt = {
|
||||||
save = function(self)
|
save = function(self)
|
||||||
local vars = {}
|
local vars = {}
|
||||||
for k, v in pairs(self.state.variables) do
|
for k, v in pairs(self.state.variables) do
|
||||||
if v.type ~= "undefined argument" then
|
if v.type ~= "undefined argument" and v.type ~= "pending definition" and k:match("^"..identifier_pattern.."$") then
|
||||||
vars[k] = v
|
vars[k] = v
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -343,7 +341,7 @@ local vm_mt = {
|
||||||
local interpreter
|
local interpreter
|
||||||
interpreter = {
|
interpreter = {
|
||||||
state = {
|
state = {
|
||||||
builtin_aliases = self.builtin_aliases,
|
builtin_aliases = self.state.builtin_aliases,
|
||||||
aliases = self.state.aliases,
|
aliases = self.state.aliases,
|
||||||
functions = self.state.functions,
|
functions = self.state.functions,
|
||||||
variables = setmetatable({}, { __index = self.state.variables }),
|
variables = setmetatable({}, { __index = self.state.variables }),
|
||||||
|
|
@ -365,7 +363,7 @@ local vm_mt = {
|
||||||
interrupt = nil,
|
interrupt = nil,
|
||||||
-- tag stack
|
-- tag stack
|
||||||
tags = tags or {},
|
tags = tags or {},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
vm = self
|
vm = self
|
||||||
}
|
}
|
||||||
|
|
@ -397,29 +395,29 @@ return setmetatable(anselme, {
|
||||||
-- ["🏁"] = "reached"
|
-- ["🏁"] = "reached"
|
||||||
},
|
},
|
||||||
aliases = {
|
aliases = {
|
||||||
-- ["bonjour.salutation"] = "hello.greeting",
|
-- ["bonjour.salutation"] = "hello.greeting", ...
|
||||||
},
|
},
|
||||||
functions = {
|
functions = {
|
||||||
-- [":="] = {
|
-- ["script.fn"] = {
|
||||||
-- {
|
-- {
|
||||||
-- arity = {3,42}, type = { [1] = "variable" }, check = function, rewrite = function, mode = "custom",
|
-- function or checkpoint table
|
||||||
-- value = function(state, exp)
|
-- }, ...
|
||||||
-- end -- or checkpoint, function, line
|
-- }, ...
|
||||||
-- }
|
|
||||||
-- },
|
|
||||||
},
|
},
|
||||||
variables = {
|
variables = {
|
||||||
-- foo = {
|
-- foo = {
|
||||||
-- type = "number",
|
-- type = "number",
|
||||||
-- value = 42
|
-- value = 42
|
||||||
-- },
|
-- }, ...
|
||||||
},
|
},
|
||||||
queued_lines = {
|
queued_lines = {
|
||||||
-- { line = line, namespace = "foo" },
|
-- { line = line, namespace = "foo" }, ...
|
||||||
}
|
},
|
||||||
|
link_next_function_definition_to_lua_function = nil -- temporarly set to tell the preparser to link a anselme function definition with a lua function
|
||||||
}
|
}
|
||||||
local vm = setmetatable({ state = state }, vm_mt)
|
local vm = setmetatable({ state = state }, vm_mt)
|
||||||
vm:loadfunction(stdfuncs)
|
assert(vm:loadstring(bootscript, "", "boot script"))
|
||||||
|
assert(vm:loadfunction(stdfuncs))
|
||||||
return vm
|
return vm
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
|
|
||||||
14
ideas.txt
14
ideas.txt
|
|
@ -1,14 +0,0 @@
|
||||||
TODO: improve type checking.
|
|
||||||
Right now, there is some basic type checking done at parsing - but since Lua and Anselme functions may not always define the type of
|
|
||||||
their parameters and return value, a lot of checks are skipped ("undefined argument" type).
|
|
||||||
Probably won't be able to remove them completely (since lists can have mixed types, etc.), but would be good to limit them.
|
|
||||||
Ideally, we'd avoid runtime type checking.
|
|
||||||
|
|
||||||
TODO: test reacheability of script paths
|
|
||||||
|
|
||||||
Symbols still available:
|
|
||||||
`
|
|
||||||
'
|
|
||||||
_
|
|
||||||
¤ £ €
|
|
||||||
?
|
|
||||||
|
|
@ -10,6 +10,23 @@ common = {
|
||||||
global_vars[var] = value
|
global_vars[var] = value
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
-- returns a variable's value, evaluating a pending expression if neccessary
|
||||||
|
-- if you're sure the variable has already been evaluated, use state.variables[fqm] directly
|
||||||
|
-- return var
|
||||||
|
-- return nil, err
|
||||||
|
get_variable = function(state, fqm)
|
||||||
|
local var = state.variables[fqm]
|
||||||
|
if var.type == "pending definition" then
|
||||||
|
local v, e = eval(state, var.value.expression)
|
||||||
|
if not v then
|
||||||
|
return nil, ("%s; while evaluating default value for variable %q defined at %s"):format(e, fqm, var.value.source)
|
||||||
|
end
|
||||||
|
state.variables[fqm] = v
|
||||||
|
return v
|
||||||
|
else
|
||||||
|
return var
|
||||||
|
end
|
||||||
|
end,
|
||||||
-- check truthyness of an anselme value
|
-- check truthyness of an anselme value
|
||||||
truthy = function(val)
|
truthy = function(val)
|
||||||
if val.type == "number" then
|
if val.type == "number" then
|
||||||
|
|
@ -20,6 +37,29 @@ common = {
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
-- compare two anselme value for equality
|
||||||
|
compare = function(a, b)
|
||||||
|
if a.type ~= b.type then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if a.type == "pair" or a.type == "type" then
|
||||||
|
return common.compare(a.value[1], b.value[1]) and common.compare(a.value[2], b.value[2])
|
||||||
|
elseif a.type == "list" then
|
||||||
|
if #a.value ~= #b.value then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
for i, v in ipairs(a.value) do
|
||||||
|
if not common.compare(v, b.value[i]) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return a.value == b.value
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
-- format a anselme value to something printable
|
||||||
|
-- does not call custom format() functions, only built-in ones, so it should not be able to fail
|
||||||
-- str: if success
|
-- str: if success
|
||||||
-- * nil, err: if error
|
-- * nil, err: if error
|
||||||
format = function(val)
|
format = function(val)
|
||||||
|
|
@ -63,6 +103,37 @@ common = {
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return s
|
return s
|
||||||
|
end,
|
||||||
|
-- specificity(number): if var is of type type
|
||||||
|
-- false: if not
|
||||||
|
is_of_type = function(var, type)
|
||||||
|
local depth = 1
|
||||||
|
-- var has a custom type
|
||||||
|
if var.type == "type" then
|
||||||
|
local var_type = var.value[2]
|
||||||
|
while true do
|
||||||
|
if common.compare(var_type, type) then -- same type
|
||||||
|
return depth
|
||||||
|
elseif var_type.type == "type" then -- compare parent type
|
||||||
|
depth = depth + 1
|
||||||
|
var_type = var_type.value[2]
|
||||||
|
else -- no parent, fall back on base type
|
||||||
|
depth = depth + 1
|
||||||
|
var = var.value[1]
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- var has a base type
|
||||||
|
return type.type == "string" and type.value == var.type and depth
|
||||||
|
end,
|
||||||
|
-- return a pretty printable type value for var
|
||||||
|
pretty_type = function(var)
|
||||||
|
if var.type == "type" then
|
||||||
|
return common.format(var.value[2])
|
||||||
|
else
|
||||||
|
return var.type
|
||||||
|
end
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
local expression
|
local expression
|
||||||
local to_lua, from_lua, eval_text
|
local to_lua, from_lua, eval_text, is_of_type, truthy, format, pretty_type, get_variable
|
||||||
|
|
||||||
local run
|
local run
|
||||||
|
|
||||||
|
|
@ -52,9 +52,6 @@ local function eval(state, exp)
|
||||||
value = {}
|
value = {}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
-- variable
|
|
||||||
elseif exp.type == "variable" then
|
|
||||||
return state.variables[exp.name]
|
|
||||||
-- list
|
-- list
|
||||||
elseif exp.type == "list" then
|
elseif exp.type == "list" then
|
||||||
local l = {}
|
local l = {}
|
||||||
|
|
@ -72,107 +69,319 @@ local function eval(state, exp)
|
||||||
type = "list",
|
type = "list",
|
||||||
value = l
|
value = l
|
||||||
}
|
}
|
||||||
|
-- assignment
|
||||||
|
elseif exp.type == ":=" then
|
||||||
|
if exp.left.type == "variable" then
|
||||||
|
local name = exp.left.name
|
||||||
|
local val, vale = eval(state, exp.right)
|
||||||
|
if not val then return val, vale end
|
||||||
|
state.variables[name] = val
|
||||||
|
return val
|
||||||
|
else
|
||||||
|
return nil, ("don't know how to perform assignment on %s expression"):format(exp.left.type)
|
||||||
|
end
|
||||||
|
-- lazy boolean operators
|
||||||
|
elseif exp.type == "&" then
|
||||||
|
local left, lefte = eval(state, exp.left)
|
||||||
|
if not left then return left, lefte end
|
||||||
|
if truthy(left) then
|
||||||
|
local right, righte = eval(state, exp.right)
|
||||||
|
if not right then return right, righte end
|
||||||
|
if truthy(right) then
|
||||||
|
return {
|
||||||
|
type = "number",
|
||||||
|
value = 1
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return {
|
||||||
|
type = "number",
|
||||||
|
value = 0
|
||||||
|
}
|
||||||
|
elseif exp.type == "|" then
|
||||||
|
local left, lefte = eval(state, exp.left)
|
||||||
|
if not left then return left, lefte end
|
||||||
|
if truthy(left) then
|
||||||
|
return {
|
||||||
|
type = "number",
|
||||||
|
value = 1
|
||||||
|
}
|
||||||
|
end
|
||||||
|
local right, righte = eval(state, exp.right)
|
||||||
|
if not right then return right, righte end
|
||||||
|
return {
|
||||||
|
type = "number",
|
||||||
|
value = truthy(right) and 1 or 0
|
||||||
|
}
|
||||||
|
-- variable
|
||||||
|
elseif exp.type == "variable" then
|
||||||
|
return get_variable(state, exp.name)
|
||||||
-- function
|
-- function
|
||||||
elseif exp.type == "function" then
|
elseif exp.type == "function" then
|
||||||
local fn = exp.variant
|
-- eval args: list_brackets
|
||||||
-- custom lua functions
|
local args = {}
|
||||||
if fn.mode == "custom" then
|
if exp.argument then
|
||||||
return fn.value(state, exp)
|
local arg, arge = eval(state, exp.argument)
|
||||||
else
|
if not arg then return arg, arge end
|
||||||
-- eval args: list_brackets
|
args = arg.value
|
||||||
local args = {}
|
end
|
||||||
if exp.argument then
|
-- map named arguments
|
||||||
local arg, arge = eval(state, exp.argument)
|
local named_args = {}
|
||||||
if not arg then return arg, arge end
|
for i, arg in ipairs(args) do
|
||||||
args = arg.value
|
if arg.type == "pair" and arg.value[1].type == "string" then
|
||||||
|
named_args[arg.value[1].value] = { i, arg.value[2] }
|
||||||
end
|
end
|
||||||
-- anselme function
|
end
|
||||||
if type(fn.value) == "table" then
|
-- eval assignment arg
|
||||||
-- checkpoint
|
local assignment
|
||||||
if fn.value.type == "checkpoint" then
|
if exp.assignment then
|
||||||
local r, e = run(state, fn.value.child, not exp.explicit_call)
|
local arge
|
||||||
if not r then return r, e end
|
assignment, arge = eval(state, exp.assignment)
|
||||||
return r
|
if not assignment then return assignment, arge end
|
||||||
-- function
|
end
|
||||||
elseif fn.value.type == "function" then
|
-- try to select a function
|
||||||
-- map named arguments
|
local tried_function_error_messages = {}
|
||||||
for _, arg in ipairs(args) do
|
local selected_variant = { depths = { assignment = nil }, variant = nil }
|
||||||
if arg.type == "pair" and arg.value[1].type == "string" then
|
for _, fn in ipairs(exp.variants) do
|
||||||
args[arg.value[1].value] = arg.value[2]
|
-- checkpoint: no args, nothing to select on
|
||||||
end
|
if fn.type == "checkpoint" then
|
||||||
end
|
if not selected_variant.variant then
|
||||||
|
selected_variant.depths = {}
|
||||||
|
selected_variant.variant = fn
|
||||||
|
else
|
||||||
|
return nil, ("checkpoint call %q is ambigous; may be at least either:\n\t%s\n\t%s"):format(exp.called_name, fn.pretty_signature, selected_variant.variant.pretty_signature)
|
||||||
|
end
|
||||||
|
-- function
|
||||||
|
elseif fn.type == "function" then
|
||||||
|
if not fn.assignment or exp.assignment then
|
||||||
|
local ok = true
|
||||||
-- get and set args
|
-- get and set args
|
||||||
for j, param in ipairs(fn.value.params) do
|
local used_args = {}
|
||||||
|
local depths = { assignment = nil }
|
||||||
|
for j, param in ipairs(fn.params) do
|
||||||
local val
|
local val
|
||||||
-- named
|
-- named
|
||||||
if param.alias and args[param.alias] then
|
if param.alias and named_args[param.alias] then
|
||||||
val = args[param.alias]
|
val = named_args[param.alias][2]
|
||||||
elseif args[param.name] then
|
used_args[named_args[param.alias][1]] = true
|
||||||
val = args[param.name]
|
elseif named_args[param.name] then
|
||||||
|
val = named_args[param.name][2]
|
||||||
|
used_args[named_args[param.name][1]] = true
|
||||||
-- vararg
|
-- vararg
|
||||||
elseif param.vararg then
|
elseif param.vararg then
|
||||||
val = { type = "list", value = {} }
|
val = { type = "list", value = {} }
|
||||||
for k=j, #args do
|
for k=j, #args do
|
||||||
table.insert(val.value, args[k])
|
table.insert(val.value, args[k])
|
||||||
|
used_args[k] = true
|
||||||
end
|
end
|
||||||
-- positional
|
-- positional
|
||||||
elseif args[j] and args[j].type ~= "pair" then
|
elseif args[j] and args[j].type ~= "pair" then
|
||||||
val = args[j]
|
val = args[j]
|
||||||
-- default
|
used_args[j] = true
|
||||||
elseif param.default then
|
|
||||||
local v, e = eval(state, param.default)
|
|
||||||
if not v then return v, e end
|
|
||||||
val = v
|
|
||||||
end
|
end
|
||||||
if val then
|
if val then
|
||||||
|
-- check type annotation
|
||||||
|
if param.type_annotation then
|
||||||
|
local v, e = eval(state, param.type_annotation)
|
||||||
|
if not v then return v, e end
|
||||||
|
local depth = is_of_type(val, v)
|
||||||
|
if not depth then
|
||||||
|
ok = false
|
||||||
|
table.insert(tried_function_error_messages, ("%s: argument %s is not of expected type %s"):format(fn.pretty_signature, param.name, format(v) or v))
|
||||||
|
break
|
||||||
|
end
|
||||||
|
depths[j] = depth
|
||||||
|
else
|
||||||
|
depths[j] = math.huge
|
||||||
|
end
|
||||||
|
-- set
|
||||||
state.variables[param.full_name] = val
|
state.variables[param.full_name] = val
|
||||||
|
-- default: evaluate once function is selected
|
||||||
|
-- there's no need to type check because the type annotation is already the default value's type, because of syntax
|
||||||
|
elseif param.default then
|
||||||
|
state.variables[param.full_name] = { type = "pending definition", value = { expression = param.default, source = fn.source } }
|
||||||
else
|
else
|
||||||
return nil, ("missing mandatory argument %q in function %q call"):format(param.name, fn.value.name)
|
ok = false
|
||||||
|
table.insert(tried_function_error_messages, ("%s: missing mandatory argument %q in function %q call"):format(fn.pretty_signature, param.name, fn.name))
|
||||||
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
-- eval function
|
-- check for unused arguments
|
||||||
local r, e
|
if ok then
|
||||||
if exp.explicit_call or state.variables[fn.value.namespace.."🔖"].value == "" then
|
for i, arg in ipairs(args) do
|
||||||
r, e = run(state, fn.value.child)
|
if not used_args[i] then
|
||||||
-- resume at last checkpoint
|
ok = false
|
||||||
else
|
if arg.type == "pair" and arg.value[1].type == "string" then
|
||||||
local expr, err = expression(state.variables[fn.value.namespace.."🔖"].value, state, "")
|
table.insert(tried_function_error_messages, ("%s: unexpected %s argument"):format(fn.pretty_signature, arg.value[1].value))
|
||||||
if not expr then return expr, err end
|
else
|
||||||
r, e = eval(state, expr)
|
table.insert(tried_function_error_messages, ("%s: unexpected argument in position %s"):format(fn.pretty_signature, i))
|
||||||
|
end
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- assignment arg
|
||||||
|
if ok and exp.assignment then
|
||||||
|
-- check type annotation
|
||||||
|
local param = fn.assignment
|
||||||
|
if param.type_annotation then
|
||||||
|
local v, e = eval(state, param.type_annotation)
|
||||||
|
if not v then return v, e end
|
||||||
|
local depth = is_of_type(assignment, v)
|
||||||
|
if not depth then
|
||||||
|
ok = false
|
||||||
|
table.insert(tried_function_error_messages, ("%s: argument %s is not of expected type %s"):format(fn.pretty_signature, param.name, format(v) or v))
|
||||||
|
else
|
||||||
|
depths.assignment = depth
|
||||||
|
end
|
||||||
|
else
|
||||||
|
depths.assignment = math.huge
|
||||||
|
end
|
||||||
|
-- set
|
||||||
|
state.variables[param.full_name] = assignment
|
||||||
|
end
|
||||||
|
if ok then
|
||||||
|
if not selected_variant.variant then
|
||||||
|
selected_variant.depths = depths
|
||||||
|
selected_variant.variant = fn
|
||||||
|
else
|
||||||
|
-- check specificity order
|
||||||
|
local lower
|
||||||
|
for j, d in ipairs(depths) do
|
||||||
|
local current_depth = selected_variant.depths[j] or math.huge -- not every arg may be set on every variant (varargs)
|
||||||
|
if d < current_depth then -- stricly lower, i.e. more specific function
|
||||||
|
lower = true
|
||||||
|
break
|
||||||
|
elseif d > current_depth then -- stricly greater, i.e. less specific function
|
||||||
|
lower = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if lower == nil and exp.assignment then -- use assignment if still ambigous
|
||||||
|
local current_depth = selected_variant.depths.assignment
|
||||||
|
if depths.assignment < current_depth then -- stricly lower, i.e. more specific function
|
||||||
|
lower = true
|
||||||
|
elseif depths.assignment > current_depth then -- stricly greater, i.e. less specific function
|
||||||
|
lower = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if lower then
|
||||||
|
selected_variant.depths = depths
|
||||||
|
selected_variant.variant = fn
|
||||||
|
elseif lower == nil then -- equal, ambigous dispatch
|
||||||
|
return nil, ("function call %q is ambigous; may be at least either:\n\t%s\n\t%s"):format(exp.called_name, fn.pretty_signature, selected_variant.variant.pretty_signature)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
if not r then return r, e end
|
|
||||||
state.variables[fn.value.namespace.."👁️"] = {
|
|
||||||
type = "number",
|
|
||||||
value = state.variables[fn.value.namespace.."👁️"].value + 1
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
else
|
|
||||||
return nil, ("unknown function type %q"):format(fn.value.type)
|
|
||||||
end
|
end
|
||||||
-- lua functions
|
|
||||||
-- TODO: handle named and default arguments
|
|
||||||
else
|
else
|
||||||
if fn.mode == "raw" then
|
return nil, ("unknown function type %q"):format(fn.type)
|
||||||
return fn.value(unpack(args))
|
|
||||||
else
|
|
||||||
local l_lua = {}
|
|
||||||
for _, v in ipairs(args) do
|
|
||||||
table.insert(l_lua, to_lua(v))
|
|
||||||
end
|
|
||||||
local r, e
|
|
||||||
if _VERSION == "Lua 5.1" and not jit then -- PUC Lua 5.1 doesn't allow yield from a pcall
|
|
||||||
r, e = true, fn.value(unpack(l_lua))
|
|
||||||
else
|
|
||||||
r, e = pcall(fn.value, unpack(l_lua)) -- pcall to produce a more informative error message (instead of full coroutine crash)
|
|
||||||
end
|
|
||||||
if r then
|
|
||||||
return from_lua(e)
|
|
||||||
else
|
|
||||||
return nil, ("%s; in Lua function %q"):format(e, exp.name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
-- function successfully selected
|
||||||
|
if selected_variant.variant then
|
||||||
|
local fn = selected_variant.variant
|
||||||
|
if fn.type == "checkpoint" then
|
||||||
|
local r, e = run(state, fn.child, not exp.explicit_call)
|
||||||
|
if not r then return r, e end
|
||||||
|
return r
|
||||||
|
elseif fn.type == "function" then
|
||||||
|
local ret
|
||||||
|
-- get function vars
|
||||||
|
local checkpoint, checkpointe = get_variable(state, fn.namespace.."🔖")
|
||||||
|
if not checkpoint then return nil, checkpointe end
|
||||||
|
local seen, seene = get_variable(state, fn.namespace.."👁️")
|
||||||
|
if not seen then return nil, seene end
|
||||||
|
-- execute lua functions
|
||||||
|
-- I guess we could technically skip getting & updating the seen and checkpoints vars since they can't be used from Anselme
|
||||||
|
-- but it's also kinda fun to known how many time a function was ran
|
||||||
|
if fn.lua_function then
|
||||||
|
local lua_fn = fn.lua_function
|
||||||
|
-- get args
|
||||||
|
local final_args = {}
|
||||||
|
for j, param in ipairs(fn.params) do
|
||||||
|
local v, e = get_variable(state, param.full_name)
|
||||||
|
if not v then return v, e end
|
||||||
|
final_args[j] = v
|
||||||
|
end
|
||||||
|
if fn.assignment then
|
||||||
|
local v, e = get_variable(state, fn.assignment.full_name)
|
||||||
|
if not v then return v, e end
|
||||||
|
final_args[#final_args+1] = v
|
||||||
|
end
|
||||||
|
-- execute function
|
||||||
|
-- raw mode: pass raw anselme values to the Lua function
|
||||||
|
if lua_fn.mode == "raw" then
|
||||||
|
ret = lua_fn.value(unpack(final_args))
|
||||||
|
-- untyped raw mode: same as raw, but strips custom types from the arguments
|
||||||
|
elseif lua_fn.mode == "untyped raw" then
|
||||||
|
-- extract value from custom types
|
||||||
|
for i, arg in ipairs(final_args) do
|
||||||
|
if arg.type == "type" then
|
||||||
|
final_args[i] = arg.value[1]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
ret = lua_fn.value(unpack(final_args))
|
||||||
|
-- normal mode: convert args to Lua and convert back Lua value to Anselme
|
||||||
|
elseif lua_fn.mode == nil then
|
||||||
|
local l_lua = {}
|
||||||
|
for _, v in ipairs(final_args) do
|
||||||
|
table.insert(l_lua, to_lua(v))
|
||||||
|
end
|
||||||
|
local r, e
|
||||||
|
if _VERSION == "Lua 5.1" and not jit then -- PUC Lua 5.1 doesn't allow yield from a pcall
|
||||||
|
r, e = true, lua_fn.value(unpack(l_lua))
|
||||||
|
else
|
||||||
|
r, e = pcall(lua_fn.value, unpack(l_lua)) -- pcall to produce a more informative error message (instead of full coroutine crash)
|
||||||
|
end
|
||||||
|
if r then
|
||||||
|
ret = from_lua(e)
|
||||||
|
else
|
||||||
|
return nil, ("%s; in Lua function %q"):format(e, exp.called_name)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return nil, ("unknown Lua function mode %q"):format(lua_fn.mode)
|
||||||
|
end
|
||||||
|
-- execute anselme functions
|
||||||
|
else
|
||||||
|
local e
|
||||||
|
-- eval function from start
|
||||||
|
if exp.explicit_call or checkpoint.value == "" then
|
||||||
|
ret, e = run(state, fn.child)
|
||||||
|
-- resume at last checkpoint
|
||||||
|
else
|
||||||
|
local expr, err = expression(checkpoint.value, state, "")
|
||||||
|
if not expr then return expr, err end
|
||||||
|
ret, e = eval(state, expr)
|
||||||
|
end
|
||||||
|
if not ret then return ret, e end
|
||||||
|
end
|
||||||
|
-- update function vars
|
||||||
|
state.variables[fn.namespace.."👁️"] = {
|
||||||
|
type = "number",
|
||||||
|
value = seen.value + 1
|
||||||
|
}
|
||||||
|
-- return value
|
||||||
|
if not ret then return nil, ("function %q didn't return a value"):format(exp.called_name) end
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- no matching function found
|
||||||
|
local args_txt = {}
|
||||||
|
for _, arg in ipairs(args) do
|
||||||
|
local s = ""
|
||||||
|
if arg.type == "pair" and arg.value[1].type == "string" then
|
||||||
|
s = s .. ("%s="):format(arg.value[1].value)
|
||||||
|
arg = arg.value[2]
|
||||||
|
end
|
||||||
|
s = s .. pretty_type(arg)
|
||||||
|
table.insert(args_txt, s)
|
||||||
|
end
|
||||||
|
local called_name = ("%s(%s)"):format(exp.called_name, table.concat(args_txt, ", "))
|
||||||
|
if assignment then
|
||||||
|
called_name = called_name .. " := " .. pretty_type(assignment)
|
||||||
|
end
|
||||||
|
return nil, ("no compatible function found for call to %s; potential candidates were:\n\t%s"):format(called_name, table.concat(tried_function_error_messages, "\n\t"))
|
||||||
else
|
else
|
||||||
return nil, ("unknown expression %q"):format(tostring(exp.type))
|
return nil, ("unknown expression %q"):format(tostring(exp.type))
|
||||||
end
|
end
|
||||||
|
|
@ -182,6 +391,6 @@ package.loaded[...] = eval
|
||||||
run = require((...):gsub("expression$", "interpreter")).run
|
run = require((...):gsub("expression$", "interpreter")).run
|
||||||
expression = require((...):gsub("interpreter%.expression$", "parser.expression"))
|
expression = require((...):gsub("interpreter%.expression$", "parser.expression"))
|
||||||
local common = require((...):gsub("expression$", "common"))
|
local common = require((...):gsub("expression$", "common"))
|
||||||
to_lua, from_lua, eval_text = common.to_lua, common.from_lua, common.eval_text
|
to_lua, from_lua, eval_text, is_of_type, truthy, format, pretty_type, get_variable = common.to_lua, common.from_lua, common.eval_text, common.is_of_type, common.truthy, common.format, common.pretty_type, common.get_variable
|
||||||
|
|
||||||
return eval
|
return eval
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
local eval
|
local eval
|
||||||
local truthy, merge_state, to_lua, eval_text, escape
|
local truthy, merge_state, to_lua, eval_text, escape, get_variable
|
||||||
|
|
||||||
local tags = {
|
local tags = {
|
||||||
--- push new tags on top of the stack, from Anselme values
|
--- push new tags on top of the stack, from Anselme values
|
||||||
|
|
@ -135,9 +135,11 @@ local function run_line(state, line)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
elseif line.type == "checkpoint" then
|
elseif line.type == "checkpoint" then
|
||||||
|
local reached, reachede = get_variable(state, line.namespace.."🏁")
|
||||||
|
if not reached then return nil, reachede end
|
||||||
state.variables[line.namespace.."🏁"] = {
|
state.variables[line.namespace.."🏁"] = {
|
||||||
type = "number",
|
type = "number",
|
||||||
value = state.variables[line.namespace.."🏁"].value + 1
|
value = reached.value + 1
|
||||||
}
|
}
|
||||||
state.variables[line.parent_function.namespace.."🔖"] = {
|
state.variables[line.parent_function.namespace.."🔖"] = {
|
||||||
type = "string",
|
type = "string",
|
||||||
|
|
@ -179,17 +181,23 @@ run_block = function(state, block, resume_from_there, i, j)
|
||||||
-- (and we want this to be done after executing the checkpoint block anyway)
|
-- (and we want this to be done after executing the checkpoint block anyway)
|
||||||
if block.parent_line and block.parent_line.type == "checkpoint" then
|
if block.parent_line and block.parent_line.type == "checkpoint" then
|
||||||
local parent_line = block.parent_line
|
local parent_line = block.parent_line
|
||||||
|
local reached, reachede = get_variable(state, parent_line.namespace.."🏁")
|
||||||
|
if not reached then return nil, reachede end
|
||||||
|
local seen, seene = get_variable(state, parent_line.namespace.."👁️")
|
||||||
|
if not seen then return nil, seene end
|
||||||
|
local checkpoint, checkpointe = get_variable(state, parent_line.parent_function.namespace.."🔖")
|
||||||
|
if not checkpoint then return nil, checkpointe end
|
||||||
state.variables[parent_line.namespace.."👁️"] = {
|
state.variables[parent_line.namespace.."👁️"] = {
|
||||||
type = "number",
|
type = "number",
|
||||||
value = state.variables[parent_line.namespace.."👁️"].value + 1
|
value = seen.value + 1
|
||||||
}
|
}
|
||||||
state.variables[parent_line.namespace.."🏁"] = {
|
state.variables[parent_line.namespace.."🏁"] = {
|
||||||
type = "number",
|
type = "number",
|
||||||
value = state.variables[parent_line.namespace.."🏁"].value + 1
|
value = reached.value + 1
|
||||||
}
|
}
|
||||||
-- don't update checkpoint if an already more precise checkpoint is set
|
-- don't update checkpoint if an already more precise checkpoint is set
|
||||||
-- (since we will go up the whole checkpoint hierarchy when resuming from a nested checkpoint)
|
-- (since we will go up the whole checkpoint hierarchy when resuming from a nested checkpoint)
|
||||||
local current_checkpoint = state.variables[parent_line.parent_function.namespace.."🔖"].value
|
local current_checkpoint = checkpoint.value
|
||||||
if not current_checkpoint:match("^"..escape(parent_line.name)) then
|
if not current_checkpoint:match("^"..escape(parent_line.name)) then
|
||||||
state.variables[parent_line.parent_function.namespace.."🔖"] = {
|
state.variables[parent_line.parent_function.namespace.."🔖"] = {
|
||||||
type = "string",
|
type = "string",
|
||||||
|
|
@ -272,7 +280,7 @@ local interpreter = {
|
||||||
package.loaded[...] = interpreter
|
package.loaded[...] = interpreter
|
||||||
eval = require((...):gsub("interpreter$", "expression"))
|
eval = require((...):gsub("interpreter$", "expression"))
|
||||||
local common = require((...):gsub("interpreter$", "common"))
|
local common = require((...):gsub("interpreter$", "common"))
|
||||||
truthy, merge_state, to_lua, eval_text = common.truthy, common.merge_state, common.to_lua, common.eval_text
|
truthy, merge_state, to_lua, eval_text, get_variable = common.truthy, common.merge_state, common.to_lua, common.eval_text, common.get_variable
|
||||||
escape = require((...):gsub("interpreter%.interpreter$", "parser.common")).escape
|
escape = require((...):gsub("interpreter%.interpreter$", "parser.common")).escape
|
||||||
|
|
||||||
return interpreter
|
return interpreter
|
||||||
|
|
|
||||||
61
notes.txt
Normal file
61
notes.txt
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
# Symbol selection
|
||||||
|
|
||||||
|
Anselme favor symbols over keywords, as it make translation easier.
|
||||||
|
|
||||||
|
We prefer to use symbols available on a standard US keyboard as it often is the lowest common denominator.
|
||||||
|
|
||||||
|
As we want to be able to write identifiers with little restriction, we try to only use symbols which are unlikely to appear naturally in a name.
|
||||||
|
|
||||||
|
Considering Anselme is aimed to people with a light programming introduction, are safe to use for syntax purposes:
|
||||||
|
|
||||||
|
* Diacritics (should be safe when used on their own): ~`^
|
||||||
|
* Usual mathematical symbols (should be safe to use): +-=<>/
|
||||||
|
* Unusual punctuation / main use is already programming (should be safe to use): []*{}|\_
|
||||||
|
* Usual punctuation used to separate parts of a sentence (should be safe to use): !?.,;:()
|
||||||
|
* Signs (could be used in a name, but also common programming symbols): @&$#%
|
||||||
|
* Usual punctuation (could be used in a name): '"
|
||||||
|
|
||||||
|
In the end, we decided to reserve all of those except '.
|
||||||
|
|
||||||
|
Using other unicode symbols may be also alright, but there also should be a way to only use these symbols.
|
||||||
|
|
||||||
|
TODO: add alias to §
|
||||||
|
|
||||||
|
Reserved symbols that are still not used in expressions: ~`\_?@$#
|
||||||
|
|
||||||
|
Reserved symbols that are still not used as a line type: `^+-=</[]*{}|\_!?.,;()"&%
|
||||||
|
|
||||||
|
# Code Q&A
|
||||||
|
|
||||||
|
* What does "fqm" means?
|
||||||
|
It means "fully qualified matriname", which is the same as a fully qualified name, but considers the hierarchy to be mostly mother-daugher based.
|
||||||
|
It has nothing to do with the fact I'm inept at writing acronyms and realized I wrote it wrong after using it for a whole year.
|
||||||
|
* What's a "variant"?
|
||||||
|
One of the different forms of a same function with a given fqm. No idea why I chose "variant".
|
||||||
|
* Why emojis?
|
||||||
|
They're kinda language independent I guess. I have no idea.
|
||||||
|
* Why?
|
||||||
|
I still have no idea.
|
||||||
|
|
||||||
|
# Other
|
||||||
|
|
||||||
|
TODO: test reacheability of script paths
|
||||||
|
|
||||||
|
TODO: redisign the checkpoint system to work better when used with parallel scripts (as they will rewrite each other's variables)
|
||||||
|
|
||||||
|
TODO: redisign a static type checking system
|
||||||
|
If we want to go full gradual typing, it would help to:
|
||||||
|
* add type anotation+type check to variables (:a::number=5) and functions return ($ f()::number)
|
||||||
|
* enforce some restrictions on type (assume they're constant?)
|
||||||
|
* make some tuple/list distinction (homogenous/heterogenous types) as right now index operations are a type roulette. Or type annotate lists using some parametric type.
|
||||||
|
Advantages:
|
||||||
|
* can be used for better static variant selection; if everything is type annotated, selection could be restricted to a single function
|
||||||
|
Disadvantages:
|
||||||
|
* idk if it's worth the trouble
|
||||||
|
|
||||||
|
TODO: allow to define aliases after the initial definition
|
||||||
|
~ alias(number, "nombre")
|
||||||
|
|
||||||
|
TODO: ensure that most stuff in the state stays consistent after an error was thrown
|
||||||
|
|
||||||
|
TODO: way to migrate save files
|
||||||
|
|
@ -18,9 +18,33 @@ local replace_aliases = function(aliases, namespace, name)
|
||||||
return table.concat(name_list, ".")
|
return table.concat(name_list, ".")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local disallowed_set = ("~`^+-=<>/[]*{}|\\_!?,;:()\"@&$#%"):gsub("[^%w]", "%%%1")
|
||||||
|
|
||||||
common = {
|
common = {
|
||||||
--- valid identifier pattern
|
--- valid identifier pattern
|
||||||
identifier_pattern = "[^%%%/%*%+%-%(%)%!%&%|%=%$%§%?%>%<%:%{%}%[%]%,%\"]+",
|
identifier_pattern = "%s*[^0-9%s"..disallowed_set.."][^"..disallowed_set.."]*",
|
||||||
|
-- names allowed for a function that aren't valide identifiers, mainly for overloading operators
|
||||||
|
special_functions_names = {
|
||||||
|
-- operators not included here:
|
||||||
|
-- * assignment operators (:=, +=, -=, //=, /=, *=, %=, ^=): handled with its own syntax (function assignment)
|
||||||
|
-- * list operator (,): is used when calling every functions, sounds like more trouble than it's worth
|
||||||
|
-- * | and & oprators: are lazy and don't behave like regular functions
|
||||||
|
-- * . operator: don't behave like regular functions either
|
||||||
|
";",
|
||||||
|
"!=", "==", ">=", "<=", "<", ">",
|
||||||
|
"+", "-",
|
||||||
|
"*", "//", "/", "%",
|
||||||
|
"!",
|
||||||
|
"^", "::", ":", "()"
|
||||||
|
},
|
||||||
|
-- escapement code and their value in strings
|
||||||
|
-- I don't think there's a point in supporting form feed, carriage return, and other printer and terminal related codes
|
||||||
|
string_escapes = {
|
||||||
|
["\\\\"] = "\\",
|
||||||
|
["\\\""] = "\"",
|
||||||
|
["\\n"] = "\n",
|
||||||
|
["\\t"] = "\t"
|
||||||
|
},
|
||||||
--- escape a string to be used as an exact match pattern
|
--- escape a string to be used as an exact match pattern
|
||||||
escape = function(str)
|
escape = function(str)
|
||||||
if not escapeCache[str] then
|
if not escapeCache[str] then
|
||||||
|
|
@ -42,6 +66,8 @@ common = {
|
||||||
end,
|
end,
|
||||||
--- find a variable/function in a list, going up through the namespace hierarchy
|
--- find a variable/function in a list, going up through the namespace hierarchy
|
||||||
-- will apply aliases
|
-- will apply aliases
|
||||||
|
-- returns value, fqm in case of success
|
||||||
|
-- returns nil, err in case of error
|
||||||
find = function(aliases, list, namespace, name)
|
find = function(aliases, list, namespace, name)
|
||||||
local ns = common.split(namespace)
|
local ns = common.split(namespace)
|
||||||
for i=#ns, 1, -1 do
|
for i=#ns, 1, -1 do
|
||||||
|
|
@ -58,6 +84,25 @@ common = {
|
||||||
end
|
end
|
||||||
return nil, ("can't find %q in namespace %s"):format(name, namespace)
|
return nil, ("can't find %q in namespace %s"):format(name, namespace)
|
||||||
end,
|
end,
|
||||||
|
--- same as find, but return a list of every encoutered possibility
|
||||||
|
-- returns a list of fqm
|
||||||
|
find_all = function(aliases, list, namespace, name)
|
||||||
|
local l = {}
|
||||||
|
local ns = common.split(namespace)
|
||||||
|
for i=#ns, 1, -1 do
|
||||||
|
local current_namespace = table.concat(ns, ".", 1, i)
|
||||||
|
local fqm = ("%s.%s"):format(current_namespace, replace_aliases(aliases, current_namespace, name))
|
||||||
|
if list[fqm] then
|
||||||
|
table.insert(l, fqm)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- root namespace
|
||||||
|
name = replace_aliases(aliases, "", name)
|
||||||
|
if list[name] then
|
||||||
|
table.insert(l, name)
|
||||||
|
end
|
||||||
|
return l
|
||||||
|
end,
|
||||||
--- transform an identifier into a clean version (trim each part)
|
--- transform an identifier into a clean version (trim each part)
|
||||||
format_identifier = function(identifier)
|
format_identifier = function(identifier)
|
||||||
local r = identifier:gsub("[^%.]+", function(str)
|
local r = identifier:gsub("[^%.]+", function(str)
|
||||||
|
|
@ -76,6 +121,7 @@ common = {
|
||||||
end
|
end
|
||||||
return t
|
return t
|
||||||
end,
|
end,
|
||||||
|
-- parse interpolated expressions in a text
|
||||||
-- * list of strings and expressions
|
-- * list of strings and expressions
|
||||||
-- * nil, err: in case of error
|
-- * nil, err: in case of error
|
||||||
parse_text = function(text, state, namespace)
|
parse_text = function(text, state, namespace)
|
||||||
|
|
@ -89,7 +135,11 @@ common = {
|
||||||
local exp, rem = expression(e:gsub("^{", ""), state, namespace)
|
local exp, rem = expression(e:gsub("^{", ""), state, namespace)
|
||||||
if not exp then return nil, rem end
|
if not exp then return nil, rem end
|
||||||
if not rem:match("^%s*}") then return nil, ("expected closing } at end of expression before %q"):format(rem) end
|
if not rem:match("^%s*}") then return nil, ("expected closing } at end of expression before %q"):format(rem) end
|
||||||
table.insert(l, exp)
|
-- wrap in format() call
|
||||||
|
local variant, err = common.find_function_variant(state, namespace, "format", exp, true)
|
||||||
|
if not variant then return variant, err end
|
||||||
|
-- add to text
|
||||||
|
table.insert(l, variant)
|
||||||
text = rem:match("^%s*}(.*)$")
|
text = rem:match("^%s*}(.*)$")
|
||||||
else
|
else
|
||||||
break
|
break
|
||||||
|
|
@ -97,82 +147,106 @@ common = {
|
||||||
end
|
end
|
||||||
return l
|
return l
|
||||||
end,
|
end,
|
||||||
-- find compatible function variant
|
-- find compatible function variants from a fully qualified name
|
||||||
-- * variant: if success
|
-- this functions does not guarantee that functions are fully compatible with the given arguments and only performs a pre-selection without the ones which definitely aren't
|
||||||
|
-- * list of variants: if success
|
||||||
-- * nil, err: if error
|
-- * nil, err: if error
|
||||||
find_function_variant = function(fqm, state, arg, explicit_call)
|
find_function_variant_from_fqm = function(fqm, state, arg)
|
||||||
local err = ("function %q variant not found"):format(fqm)
|
local err = ("compatible function %q variant not found"):format(fqm)
|
||||||
local func = state.functions[fqm] or {}
|
local func = state.functions[fqm] or {}
|
||||||
local args = arg and common.flatten_list(arg) or {}
|
local args = arg and common.flatten_list(arg) or {}
|
||||||
|
local variants = {}
|
||||||
for _, variant in ipairs(func) do
|
for _, variant in ipairs(func) do
|
||||||
local ok = true
|
local ok = true
|
||||||
local return_type = variant.return_type
|
|
||||||
-- arity check
|
-- arity check
|
||||||
-- note: because named args can't be predicted in advance (pairs need to be evaluated), this arity check isn't enough to guarantee a compatible function
|
-- note: because named args can't be predicted in advance (pairs need to be evaluated), this arity check isn't enough to guarantee a compatible arity
|
||||||
-- (e.g., if there's 3 required args but only provide 3 optional arg in a call, will pass)
|
-- (e.g., if there's 3 required args but only provide 3 optional arg in a call, will pass)
|
||||||
if variant.arity then
|
local min, max = variant.arity[1], variant.arity[2]
|
||||||
local min, max
|
if #args < min or #args > max then
|
||||||
if type(variant.arity) == "table" then
|
if min == max then
|
||||||
min, max = variant.arity[1], variant.arity[2]
|
err = ("function %q expected %s arguments but received %s"):format(fqm, min, #args)
|
||||||
else
|
else
|
||||||
min, max = variant.arity, variant.arity
|
err = ("function %q expected between %s and %s arguments but received %s"):format(fqm, min, max, #args)
|
||||||
end
|
|
||||||
if #args < min or #args > max then
|
|
||||||
if min == max then
|
|
||||||
err = ("function %q expected %s arguments but received %s"):format(fqm, min, #args)
|
|
||||||
else
|
|
||||||
err = ("function %q expected between %s and %s arguments but received %s"):format(fqm, min, max, #args)
|
|
||||||
end
|
|
||||||
ok = false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- custom check
|
|
||||||
if ok and variant.check then
|
|
||||||
local s, e = variant.check(state, args)
|
|
||||||
if not s then
|
|
||||||
err = e or ("function %q variant failed to check arguments"):format(fqm)
|
|
||||||
ok = false
|
|
||||||
end
|
|
||||||
return_type = s == true and return_type or s
|
|
||||||
end
|
|
||||||
-- type check
|
|
||||||
if ok and variant.types then
|
|
||||||
for j, t in pairs(variant.types) do
|
|
||||||
if args[j] and args[j].return_type and args[j].return_type ~= t then
|
|
||||||
err = ("function %q expected a %s as argument %s but received a %s"):format(fqm, t, j, args[j].return_type)
|
|
||||||
ok = false
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
ok = false
|
||||||
end
|
end
|
||||||
-- done
|
-- done
|
||||||
if ok then
|
if ok then
|
||||||
if variant.rewrite then
|
table.insert(variants, variant)
|
||||||
local r, e = variant.rewrite(fqm, state, arg, explicit_call)
|
end
|
||||||
if not r then
|
end
|
||||||
err = e
|
if #variants > 0 then
|
||||||
ok = false
|
return variants
|
||||||
end
|
else
|
||||||
if ok then
|
return nil, err
|
||||||
return r
|
end
|
||||||
end
|
end,
|
||||||
else
|
--- same as find_function_variant_from_fqm, but will search every function from the current namespace and up using find
|
||||||
return {
|
-- returns directly a function expression in case of success
|
||||||
type = "function",
|
-- return nil, err otherwise
|
||||||
return_type = return_type,
|
find_function_variant = function(state, namespace, name, arg, explicit_call)
|
||||||
name = fqm,
|
local variants = {}
|
||||||
explicit_call = explicit_call,
|
local err = ("compatible function %q variant not found"):format(name)
|
||||||
variant = variant,
|
local l = common.find_all(state.aliases, state.functions, namespace, name)
|
||||||
argument = { -- wrap everything in a list literal to simply later things (otherwise may be nil, single value, list constructor)
|
for _, ffqm in ipairs(l) do
|
||||||
type = "list_brackets",
|
local found
|
||||||
return_type = "list",
|
found, err = common.find_function_variant_from_fqm(ffqm, state, arg)
|
||||||
expression = arg
|
if found then
|
||||||
}
|
for _, v in ipairs(found) do
|
||||||
}
|
table.insert(variants, v)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return nil, err
|
if #variants > 0 then
|
||||||
end
|
return {
|
||||||
|
type = "function",
|
||||||
|
called_name = name,
|
||||||
|
explicit_call = explicit_call,
|
||||||
|
variants = variants,
|
||||||
|
argument = { -- wrap everything in a list literal to simplify later things (otherwise may be nil, single value, list constructor)
|
||||||
|
type = "list_brackets",
|
||||||
|
expression = arg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return nil, err -- returns last error
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
-- returns the function's signature text
|
||||||
|
signature = function(fn)
|
||||||
|
if fn.signature then return fn.signature end
|
||||||
|
local signature
|
||||||
|
local function make_param_signature(p)
|
||||||
|
local sig = p.name
|
||||||
|
if p.vararg then
|
||||||
|
sig = sig .. "..."
|
||||||
|
end
|
||||||
|
if p.alias then
|
||||||
|
sig = sig .. ":" .. p.alias
|
||||||
|
end
|
||||||
|
if p.type_annotation then
|
||||||
|
sig = sig .. "::" .. p.type_annotation
|
||||||
|
end
|
||||||
|
if p.default then
|
||||||
|
sig = sig .. "=" .. p.default
|
||||||
|
end
|
||||||
|
return sig
|
||||||
|
end
|
||||||
|
local arg_sig = {}
|
||||||
|
for j, p in ipairs(fn.params) do
|
||||||
|
arg_sig[j] = make_param_signature(p)
|
||||||
|
end
|
||||||
|
if fn.assignment then
|
||||||
|
signature = ("%s(%s) := %s"):format(fn.name, table.concat(arg_sig, ", "), make_param_signature(fn.assignment))
|
||||||
|
else
|
||||||
|
signature = ("%s(%s)"):format(fn.name, table.concat(arg_sig, ", "))
|
||||||
|
end
|
||||||
|
return signature
|
||||||
|
end,
|
||||||
|
-- same as signature, format the signature for displaying to the user and add some debug information
|
||||||
|
pretty_signature = function(fn)
|
||||||
|
return ("%s (at %s)"):format(common.signature(fn), fn.source)
|
||||||
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
package.loaded[...] = common
|
package.loaded[...] = common
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
local identifier_pattern, format_identifier, find, escape, find_function_variant, parse_text
|
local identifier_pattern, format_identifier, find, escape, find_function_variant, parse_text, string_escapes
|
||||||
|
|
||||||
--- binop priority
|
--- binop priority
|
||||||
local binops_prio = {
|
local binops_prio = {
|
||||||
|
|
@ -9,9 +9,10 @@ local binops_prio = {
|
||||||
[5] = { "!=", "==", ">=", "<=", "<", ">" },
|
[5] = { "!=", "==", ">=", "<=", "<", ">" },
|
||||||
[6] = { "+", "-" },
|
[6] = { "+", "-" },
|
||||||
[7] = { "*", "//", "/", "%" },
|
[7] = { "*", "//", "/", "%" },
|
||||||
[8] = {}, -- unary operators
|
[8] = { "::", ":" },
|
||||||
[9] = { "^", ":" },
|
[9] = {}, -- unary operators
|
||||||
[10] = { "." }
|
[10] = { "^" },
|
||||||
|
[11] = { "." }
|
||||||
}
|
}
|
||||||
-- unop priority
|
-- unop priority
|
||||||
local unops_prio = {
|
local unops_prio = {
|
||||||
|
|
@ -22,8 +23,10 @@ local unops_prio = {
|
||||||
[5] = {},
|
[5] = {},
|
||||||
[6] = {},
|
[6] = {},
|
||||||
[7] = {},
|
[7] = {},
|
||||||
[8] = { "-", "!" },
|
[8] = {},
|
||||||
[9] = {}
|
[9] = { "-", "!" },
|
||||||
|
[10] = {},
|
||||||
|
[11] = {},
|
||||||
}
|
}
|
||||||
|
|
||||||
--- parse an expression
|
--- parse an expression
|
||||||
|
|
@ -34,29 +37,47 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
|
||||||
currentPriority = currentPriority or 0
|
currentPriority = currentPriority or 0
|
||||||
if not operatingOn then
|
if not operatingOn then
|
||||||
-- number
|
-- number
|
||||||
if s:match("^%d+%.%d*") or s:match("^%d*%.%d+") or s:match("^%d+") then
|
if s:match("^%d*%.%d+") or s:match("^%d+") then
|
||||||
local d, r = s:match("^(%d*%.%d*)(.*)$")
|
local d, r = s:match("^(%d*%.%d+)(.*)$")
|
||||||
if not d then
|
if not d then
|
||||||
d, r = s:match("^(%d+)(.*)$")
|
d, r = s:match("^(%d+)(.*)$")
|
||||||
end
|
end
|
||||||
return expression(r, state, namespace, currentPriority, {
|
return expression(r, state, namespace, currentPriority, {
|
||||||
type = "number",
|
type = "number",
|
||||||
return_type = "number",
|
|
||||||
value = tonumber(d)
|
value = tonumber(d)
|
||||||
})
|
})
|
||||||
-- string
|
-- string
|
||||||
elseif s:match("^%\"[^\"]*%\"") then
|
elseif s:match("^%\"") then
|
||||||
local d, r = s:match("^%\"([^\"]*)%\"(.*)$")
|
local d, r
|
||||||
while d:match("\\$") and not d:match("\\\\$") do
|
-- find end of string
|
||||||
local nd, nr = r:match("([^\"]*)%\"(.*)$")
|
local i = 2
|
||||||
if not nd then return nil, ("unfinished string near %q"):format(r) end
|
while true do
|
||||||
d, r = d:sub(1, -2) .. "\"" .. nd, nr
|
local skip
|
||||||
|
skip = s:match("^[^%\\\"]-%b{}()", i) -- skip interpolated expressions
|
||||||
|
if skip then i = skip end
|
||||||
|
skip = s:match("^[^%\\\"]-\\.()", i) -- skip escape codes (need to skip every escape code in order to correctly parse \\": the " is not escaped)
|
||||||
|
if skip then i = skip end
|
||||||
|
if not skip then -- nothing skipped
|
||||||
|
local end_pos = s:match("^[^%\"]-\"()", i) -- search final double quote
|
||||||
|
if end_pos then
|
||||||
|
d, r = s:sub(2, end_pos-2), s:sub(end_pos)
|
||||||
|
break
|
||||||
|
else
|
||||||
|
return nil, ("expected \" to finish string near %q"):format(s:sub(i))
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
local l, e = parse_text(tostring(d), state, namespace)
|
-- parse interpolated expressions
|
||||||
|
local l, e = parse_text(d, state, namespace)
|
||||||
if not l then return l, e end
|
if not l then return l, e end
|
||||||
|
-- escape the string parts
|
||||||
|
for j, ls in ipairs(l) do
|
||||||
|
if type(ls) == "string" then
|
||||||
|
l[j] = ls:gsub("\\.", string_escapes)
|
||||||
|
end
|
||||||
|
end
|
||||||
return expression(r, state, namespace, currentPriority, {
|
return expression(r, state, namespace, currentPriority, {
|
||||||
type = "string",
|
type = "string",
|
||||||
return_type = "string",
|
|
||||||
value = l
|
value = l
|
||||||
})
|
})
|
||||||
-- paranthesis
|
-- paranthesis
|
||||||
|
|
@ -70,11 +91,10 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
|
||||||
if not exp then return nil, "invalid expression inside parentheses: "..r_paren end
|
if not exp then return nil, "invalid expression inside parentheses: "..r_paren end
|
||||||
if r_paren:match("[^%s]") then return nil, ("unexpected %q at end of parenthesis expression"):format(r_paren) end
|
if r_paren:match("[^%s]") then return nil, ("unexpected %q at end of parenthesis expression"):format(r_paren) end
|
||||||
else
|
else
|
||||||
exp = { type = "nil", return_type = "nil", value = nil }
|
exp = { type = "nil", value = nil }
|
||||||
end
|
end
|
||||||
return expression(r, state, namespace, currentPriority, {
|
return expression(r, state, namespace, currentPriority, {
|
||||||
type = "parentheses",
|
type = "parentheses",
|
||||||
return_type = exp.return_type,
|
|
||||||
expression = exp
|
expression = exp
|
||||||
})
|
})
|
||||||
-- list parenthesis
|
-- list parenthesis
|
||||||
|
|
@ -90,7 +110,6 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
|
||||||
end
|
end
|
||||||
return expression(r, state, namespace, currentPriority, {
|
return expression(r, state, namespace, currentPriority, {
|
||||||
type = "list_brackets",
|
type = "list_brackets",
|
||||||
return_type = "list",
|
|
||||||
expression = exp
|
expression = exp
|
||||||
})
|
})
|
||||||
-- identifier
|
-- identifier
|
||||||
|
|
@ -104,16 +123,14 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
|
||||||
if not val then return val, r end
|
if not val then return val, r end
|
||||||
local args = {
|
local args = {
|
||||||
type = "list",
|
type = "list",
|
||||||
return_type = "list",
|
|
||||||
left = {
|
left = {
|
||||||
type = "string",
|
type = "string",
|
||||||
return_type = "string",
|
|
||||||
value = { name }
|
value = { name }
|
||||||
},
|
},
|
||||||
right = val
|
right = val
|
||||||
}
|
}
|
||||||
-- find compatible variant
|
-- find compatible variant
|
||||||
local variant, err = find_function_variant(":", state, args, true)
|
local variant, err = find_function_variant(state, namespace, ":", args, true)
|
||||||
if not variant then return variant, err end
|
if not variant then return variant, err end
|
||||||
return expression(r, state, namespace, currentPriority, variant)
|
return expression(r, state, namespace, currentPriority, variant)
|
||||||
end
|
end
|
||||||
|
|
@ -122,7 +139,6 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
|
||||||
if var then
|
if var then
|
||||||
return expression(r, state, namespace, currentPriority, {
|
return expression(r, state, namespace, currentPriority, {
|
||||||
type = "variable",
|
type = "variable",
|
||||||
return_type = var.type ~= "undefined argument" and var.type or nil,
|
|
||||||
name = vfqm
|
name = vfqm
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
@ -133,34 +149,29 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
|
||||||
if svar then
|
if svar then
|
||||||
return expression(suffix..r, state, namespace, currentPriority, {
|
return expression(suffix..r, state, namespace, currentPriority, {
|
||||||
type = "variable",
|
type = "variable",
|
||||||
return_type = svar.type ~= "undefined argument" and svar.type or nil,
|
|
||||||
name = svfqm
|
name = svfqm
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
-- functions
|
-- function call
|
||||||
local funcs, ffqm = find(state.aliases, state.functions, namespace, name)
|
local args, explicit_call
|
||||||
if funcs then
|
if r:match("^%b()") then
|
||||||
local args, explicit_call
|
explicit_call = true
|
||||||
if r:match("^%b()") then
|
local content, rem = r:match("^(%b())(.*)$")
|
||||||
explicit_call = true
|
content = content:gsub("^%(", ""):gsub("%)$", "")
|
||||||
local content, rem = r:match("^(%b())(.*)$")
|
r = rem
|
||||||
content = content:gsub("^%(", ""):gsub("%)$", "")
|
-- get arguments
|
||||||
r = rem
|
if content:match("[^%s]") then
|
||||||
-- get arguments
|
local err
|
||||||
if content:match("[^%s]") then
|
args, err = expression(content, state, namespace)
|
||||||
local err
|
if not args then return args, err end
|
||||||
args, err = expression(content, state, namespace)
|
if err:match("[^%s]") then return nil, ("unexpected %q at end of argument list"):format(err) end
|
||||||
if not args then return args, err end
|
|
||||||
if err:match("[^%s]") then return nil, ("unexpected %q at end of argument list"):format(err) end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
-- find compatible variant
|
|
||||||
local variant, err = find_function_variant(ffqm, state, args, explicit_call)
|
|
||||||
if not variant then return variant, err end
|
|
||||||
return expression(r, state, namespace, currentPriority, variant)
|
|
||||||
end
|
end
|
||||||
return nil, ("unknown identifier %q"):format(name)
|
-- find compatible variant
|
||||||
|
local variant, err = find_function_variant(state, namespace, name, args, explicit_call)
|
||||||
|
if not variant then return variant, err end
|
||||||
|
return expression(r, state, namespace, currentPriority, variant)
|
||||||
end
|
end
|
||||||
-- unops
|
-- unops
|
||||||
for prio, oplist in ipairs(unops_prio) do
|
for prio, oplist in ipairs(unops_prio) do
|
||||||
|
|
@ -170,7 +181,7 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
|
||||||
local right, r = expression(s:match("^"..escaped.."(.*)$"), state, namespace, prio)
|
local right, r = expression(s:match("^"..escaped.."(.*)$"), state, namespace, prio)
|
||||||
if not right then return nil, ("invalid expression after unop %q: %s"):format(op, r) end
|
if not right then return nil, ("invalid expression after unop %q: %s"):format(op, r) end
|
||||||
-- find variant
|
-- find variant
|
||||||
local variant, err = find_function_variant(op, state, right, true)
|
local variant, err = find_function_variant(state, namespace, op, right, true)
|
||||||
if not variant then return variant, err end
|
if not variant then return variant, err end
|
||||||
return expression(r, state, namespace, currentPriority, variant)
|
return expression(r, state, namespace, currentPriority, variant)
|
||||||
end
|
end
|
||||||
|
|
@ -189,38 +200,35 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
|
||||||
if op == "." and sright:match("^"..identifier_pattern) then
|
if op == "." and sright:match("^"..identifier_pattern) then
|
||||||
local name, r = sright:match("^("..identifier_pattern..")(.-)$")
|
local name, r = sright:match("^("..identifier_pattern..")(.-)$")
|
||||||
name = format_identifier(name)
|
name = format_identifier(name)
|
||||||
local funcs, ffqm = find(state.aliases, state.functions, namespace, name)
|
local args, explicit_call
|
||||||
if funcs then
|
if r:match("^%b()") then
|
||||||
local args, explicit_call
|
explicit_call = true
|
||||||
if r:match("^%b()") then
|
local content, rem = r:match("^(%b())(.*)$")
|
||||||
explicit_call = true
|
content = content:gsub("^%(", ""):gsub("%)$", "")
|
||||||
local content, rem = r:match("^(%b())(.*)$")
|
r = rem
|
||||||
content = content:gsub("^%(", ""):gsub("%)$", "")
|
-- get arguments
|
||||||
r = rem
|
if content:match("[^%s]") then
|
||||||
-- get arguments
|
local err
|
||||||
if content:match("[^%s]") then
|
args, err = expression(content, state, namespace)
|
||||||
local err
|
if not args then return args, err end
|
||||||
args, err = expression(content, state, namespace)
|
if err:match("[^%s]") then return nil, ("unexpected %q at end of argument list"):format(err) end
|
||||||
if not args then return args, err end
|
|
||||||
if err:match("[^%s]") then return nil, ("unexpected %q at end of argument list"):format(err) end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
-- add first argument
|
|
||||||
if not args then
|
|
||||||
args = operatingOn
|
|
||||||
else
|
|
||||||
args = {
|
|
||||||
type = "list",
|
|
||||||
return_type = "list",
|
|
||||||
left = operatingOn,
|
|
||||||
right = args
|
|
||||||
}
|
|
||||||
end
|
|
||||||
-- find compatible variant
|
|
||||||
local variant, err = find_function_variant(ffqm, state, args, explicit_call)
|
|
||||||
if not variant then return variant, err end
|
|
||||||
return expression(r, state, namespace, currentPriority, variant)
|
|
||||||
end
|
end
|
||||||
|
-- add first argument
|
||||||
|
if not args then
|
||||||
|
args = operatingOn
|
||||||
|
else
|
||||||
|
args = {
|
||||||
|
type = "list",
|
||||||
|
left = operatingOn,
|
||||||
|
right = args
|
||||||
|
}
|
||||||
|
end
|
||||||
|
-- find compatible variant
|
||||||
|
local variant, err = find_function_variant(state, namespace, name, args, explicit_call)
|
||||||
|
if not variant then return variant, err end
|
||||||
|
return expression(r, state, namespace, currentPriority, variant)
|
||||||
|
-- other binops
|
||||||
else
|
else
|
||||||
local right, r = expression(sright, state, namespace, prio)
|
local right, r = expression(sright, state, namespace, prio)
|
||||||
if not right then return nil, ("invalid expression after binop %q: %s"):format(op, r) end
|
if not right then return nil, ("invalid expression after binop %q: %s"):format(op, r) end
|
||||||
|
|
@ -228,7 +236,48 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
|
||||||
if op == "," then
|
if op == "," then
|
||||||
return expression(r, state, namespace, currentPriority, {
|
return expression(r, state, namespace, currentPriority, {
|
||||||
type = "list",
|
type = "list",
|
||||||
return_type = "list",
|
left = operatingOn,
|
||||||
|
right = right
|
||||||
|
})
|
||||||
|
-- special binops
|
||||||
|
elseif op == ":=" or op == "+=" or op == "-=" or op == "//=" or op == "/=" or op == "*=" or op == "%=" or op == "^=" then
|
||||||
|
-- rewrite assignment + arithmetic operators into a normal assignment
|
||||||
|
if op ~= ":=" then
|
||||||
|
local args = {
|
||||||
|
type = "list",
|
||||||
|
left = operatingOn,
|
||||||
|
right = right
|
||||||
|
}
|
||||||
|
local variant, err = find_function_variant(state, namespace, op:match("^(.*)%=$"), args, true)
|
||||||
|
if not variant then return variant, err end
|
||||||
|
right = variant
|
||||||
|
end
|
||||||
|
-- assign to a function
|
||||||
|
if operatingOn.type == "function" then
|
||||||
|
-- remove non-assignment functions
|
||||||
|
for i=#operatingOn.variants, 1, -1 do
|
||||||
|
if not operatingOn.variants[i].assignment then
|
||||||
|
table.remove(operatingOn.variants, i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #operatingOn.variants == 0 then
|
||||||
|
return nil, ("trying to perform assignment on function %s with no compatible assignment variant"):format(operatingOn.called_name)
|
||||||
|
end
|
||||||
|
-- rewrite function to perform assignment
|
||||||
|
operatingOn.assignment = right
|
||||||
|
return expression(r, state, namespace, currentPriority, operatingOn)
|
||||||
|
elseif operatingOn.type ~= "variable" then
|
||||||
|
return nil, ("trying to perform assignment on a %s expression"):format(operatingOn.type)
|
||||||
|
end
|
||||||
|
-- assign to a variable
|
||||||
|
return expression(r, state, namespace, currentPriority, {
|
||||||
|
type = ":=",
|
||||||
|
left = operatingOn,
|
||||||
|
right = right
|
||||||
|
})
|
||||||
|
elseif op == "&" or op == "|" then
|
||||||
|
return expression(r, state, namespace, currentPriority, {
|
||||||
|
type = op,
|
||||||
left = operatingOn,
|
left = operatingOn,
|
||||||
right = right
|
right = right
|
||||||
})
|
})
|
||||||
|
|
@ -237,12 +286,11 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
|
||||||
-- find variant
|
-- find variant
|
||||||
local args = {
|
local args = {
|
||||||
type = "list",
|
type = "list",
|
||||||
return_type = "list",
|
|
||||||
left = operatingOn,
|
left = operatingOn,
|
||||||
-- wrap in parentheses to avoid appending to argument list if right is a list
|
-- wrap in parentheses to avoid appending to argument list if right is a list
|
||||||
right = { type = "parentheses", return_type = right.return_type, expression = right }
|
right = { type = "parentheses", expression = right }
|
||||||
}
|
}
|
||||||
local variant, err = find_function_variant(op, state, args, true)
|
local variant, err = find_function_variant(state, namespace, op, args, true)
|
||||||
if not variant then return variant, err end
|
if not variant then return variant, err end
|
||||||
return expression(r, state, namespace, currentPriority, variant)
|
return expression(r, state, namespace, currentPriority, variant)
|
||||||
end
|
end
|
||||||
|
|
@ -259,7 +307,7 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
|
||||||
if not right then return right, r_paren end
|
if not right then return right, r_paren end
|
||||||
if r_paren:match("[^%s]") then return nil, ("unexpected %q at end of index expression"):format(r_paren) end
|
if r_paren:match("[^%s]") then return nil, ("unexpected %q at end of index expression"):format(r_paren) end
|
||||||
local args = { type = "list", left = operatingOn, right = right }
|
local args = { type = "list", left = operatingOn, right = right }
|
||||||
local variant, err = find_function_variant("(", state, args, true)
|
local variant, err = find_function_variant(state, namespace, "()", args, true)
|
||||||
if not variant then return variant, err end
|
if not variant then return variant, err end
|
||||||
return expression(r, state, namespace, currentPriority, variant)
|
return expression(r, state, namespace, currentPriority, variant)
|
||||||
end
|
end
|
||||||
|
|
@ -270,6 +318,6 @@ end
|
||||||
|
|
||||||
package.loaded[...] = expression
|
package.loaded[...] = expression
|
||||||
local common = require((...):gsub("expression$", "common"))
|
local common = require((...):gsub("expression$", "common"))
|
||||||
identifier_pattern, format_identifier, find, escape, find_function_variant, parse_text = common.identifier_pattern, common.format_identifier, common.find, common.escape, common.find_function_variant, common.parse_text
|
identifier_pattern, format_identifier, find, escape, find_function_variant, parse_text, string_escapes = common.identifier_pattern, common.format_identifier, common.find, common.escape, common.find_function_variant, common.parse_text, common.string_escapes
|
||||||
|
|
||||||
return expression
|
return expression
|
||||||
|
|
|
||||||
|
|
@ -4,22 +4,44 @@ local parse_text
|
||||||
-- * true: if success
|
-- * true: if success
|
||||||
-- * nil, error: in case of error
|
-- * nil, error: in case of error
|
||||||
local function parse(state)
|
local function parse(state)
|
||||||
|
-- expression parsing
|
||||||
for _, l in ipairs(state.queued_lines) do
|
for _, l in ipairs(state.queued_lines) do
|
||||||
local line, namespace = l.line, l.namespace
|
local line, namespace = l.line, l.namespace
|
||||||
-- default arguments
|
-- default arguments and type annotation
|
||||||
if line.type == "function" then
|
if line.type == "function" then
|
||||||
for i, param in ipairs(line.params) do
|
for _, param in ipairs(line.params) do
|
||||||
|
-- get type annotation
|
||||||
|
if param.type_annotation then
|
||||||
|
local type_exp, rem = expression(param.type_annotation, state, namespace)
|
||||||
|
if not type_exp then return nil, ("in type annotation, %s; at %s"):format(rem, line.source) end
|
||||||
|
if rem:match("[^%s]") then
|
||||||
|
return nil, ("unexpected characters after parameter %q: %q; at %s"):format(param.full_name, rem, line.source)
|
||||||
|
end
|
||||||
|
param.type_annotation = type_exp
|
||||||
|
end
|
||||||
|
-- get default value
|
||||||
if param.default then
|
if param.default then
|
||||||
local exp, rem = expression(param.default, state, namespace)
|
local default_exp, rem = expression(param.default, state, namespace)
|
||||||
if not exp then return nil, ("%s; at %s"):format(rem, line.source) end
|
if not default_exp then return nil, ("in default value, %s; at %s"):format(rem, line.source) end
|
||||||
if rem:match("[^%s]") then return nil, ("expected end of expression before %q; at %s"):format(rem, line.source) end
|
if rem:match("[^%s]") then
|
||||||
param.default = exp
|
return nil, ("unexpected characters after parameter %q: %q; at %s"):format(param.full_name, rem, line.source)
|
||||||
-- complete type information
|
end
|
||||||
if exp.return_type then
|
param.default = default_exp
|
||||||
line.variant.types[i] = exp.return_type
|
-- extract type annotation from default value
|
||||||
|
if default_exp.type == "function" and default_exp.called_name == "::" then
|
||||||
|
param.type_annotation = default_exp.argument.expression.right
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
-- assignment argument
|
||||||
|
if line.assignment and line.assignment.type_annotation then
|
||||||
|
local type_exp, rem = expression(line.assignment.type_annotation, state, namespace)
|
||||||
|
if not type_exp then return nil, ("in type annotation, %s; at %s"):format(rem, line.source) end
|
||||||
|
if rem:match("[^%s]") then
|
||||||
|
return nil, ("unexpected characters after parameter %q: %q; at %s"):format(line.assignment.full_name, rem, line.source)
|
||||||
|
end
|
||||||
|
line.assignment.type_annotation = type_exp
|
||||||
|
end
|
||||||
end
|
end
|
||||||
-- expressions
|
-- expressions
|
||||||
if line.expression then
|
if line.expression then
|
||||||
|
|
@ -27,17 +49,9 @@ local function parse(state)
|
||||||
if not exp then return nil, ("%s; at %s"):format(rem, line.source) end
|
if not exp then return nil, ("%s; at %s"):format(rem, line.source) end
|
||||||
if rem:match("[^%s]") then return nil, ("expected end of expression before %q; at %s"):format(rem, line.source) end
|
if rem:match("[^%s]") then return nil, ("expected end of expression before %q; at %s"):format(rem, line.source) end
|
||||||
line.expression = exp
|
line.expression = exp
|
||||||
-- function return type information
|
-- variable pending definition: expression will be evaluated when variable is needed
|
||||||
if line.type == "return" then
|
if line.type == "definition" then
|
||||||
local variant = line.parent_function.variant
|
state.variables[line.fqm].value.expression = line.expression
|
||||||
local return_type = line.expression.return_type
|
|
||||||
if return_type then
|
|
||||||
if not variant.return_type then
|
|
||||||
variant.return_type = return_type
|
|
||||||
elseif variant.return_type ~= return_type then
|
|
||||||
return nil, ("trying to return a %s in a function that returns a %s; at %s"):format(return_type, variant.return_type, line.source)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
-- text
|
-- text
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
local expression
|
local format_identifier, identifier_pattern, escape, special_functions_names, pretty_signature, signature
|
||||||
local format_identifier, identifier_pattern
|
|
||||||
local eval
|
|
||||||
|
|
||||||
-- try to define an alias using rem, the text that follows the identifier
|
-- try to define an alias using rem, the text that follows the identifier
|
||||||
-- returns true, new_rem, alias_name in case of success
|
-- returns true, new_rem, alias_name in case of success
|
||||||
|
|
@ -8,7 +6,7 @@ local eval
|
||||||
-- returns nil, err in case of alias and error
|
-- returns nil, err in case of alias and error
|
||||||
local function maybe_alias(rem, fqm, namespace, line, state)
|
local function maybe_alias(rem, fqm, namespace, line, state)
|
||||||
local alias
|
local alias
|
||||||
if rem:match("^%:") then
|
if rem:match("^%:[^%:%=]") then
|
||||||
local param_content = rem:sub(2)
|
local param_content = rem:sub(2)
|
||||||
alias, rem = param_content:match("^("..identifier_pattern..")(.-)$")
|
alias, rem = param_content:match("^("..identifier_pattern..")(.-)$")
|
||||||
if not alias then return nil, ("expected an identifier in alias, but got %q; at %s"):format(param_content, line.source) end
|
if not alias then return nil, ("expected an identifier in alias, but got %q; at %s"):format(param_content, line.source) end
|
||||||
|
|
@ -51,41 +49,100 @@ local function parse_line(line, state, namespace)
|
||||||
elseif 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("^%$") or l:match("^§") then -- § is a 2-bytes caracter, DO NOT USE LUA PATTERN OPERATORS as they operate on single bytes
|
||||||
r.type = l:match("^%$") and "function" or "checkpoint"
|
r.type = l:match("^%$") and "function" or "checkpoint"
|
||||||
r.child = true
|
r.child = true
|
||||||
|
-- store parent function and run checkpoint when line is read
|
||||||
|
if r.type == "checkpoint" then
|
||||||
|
r.parent_function = true
|
||||||
|
end
|
||||||
|
-- don't keep function node in block AST
|
||||||
|
if r.type == "function" then
|
||||||
|
r.remove_from_block_ast = true
|
||||||
|
-- lua function
|
||||||
|
if state.link_next_function_definition_to_lua_function then
|
||||||
|
r.lua_function = state.link_next_function_definition_to_lua_function
|
||||||
|
state.link_next_function_definition_to_lua_function = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
-- get identifier
|
-- get identifier
|
||||||
local lc = l:match("^%$(.*)$") or l:match("^§(.*)$")
|
local lc = l:match("^%$(.-)$") or l:match("^§(.-)$")
|
||||||
local identifier, rem = lc:match("^("..identifier_pattern..")(.-)$")
|
local identifier, rem = lc:match("^("..identifier_pattern..")(.-)$")
|
||||||
if not identifier then return nil, ("no valid identifier in checkpoint/function definition line %q; at %s"):format(lc, line.source) end
|
if not identifier then
|
||||||
|
for _, name in ipairs(special_functions_names) do
|
||||||
|
identifier, rem = lc:match("^(%s*"..escape(name).."%s*)(.-)$")
|
||||||
|
if identifier then break end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not identifier then
|
||||||
|
return nil, ("no valid identifier in checkpoint/function definition line %q; at %s"):format(lc, line.source)
|
||||||
|
end
|
||||||
-- format identifier
|
-- format identifier
|
||||||
local fqm = ("%s%s"):format(namespace, format_identifier(identifier))
|
local fqm = ("%s%s"):format(namespace, format_identifier(identifier))
|
||||||
|
local func_namespace = fqm .. "."
|
||||||
-- get alias
|
-- get alias
|
||||||
local ok_alias
|
local ok_alias
|
||||||
ok_alias, rem = maybe_alias(rem, fqm, namespace, line, state)
|
ok_alias, rem = maybe_alias(rem, fqm, namespace, line, state)
|
||||||
if not ok_alias then return ok_alias, rem end
|
if not ok_alias then return ok_alias, rem end
|
||||||
|
-- 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, 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
|
-- get params
|
||||||
r.params = {}
|
r.params = {}
|
||||||
if r.type == "function" and rem:match("^%b()$") then
|
if r.type == "function" and rem:match("^%b()") then
|
||||||
local content = rem:gsub("^%(", ""):gsub("%)$", "")
|
local content
|
||||||
|
content, rem = rem:match("^(%b())%s*(.*)$")
|
||||||
|
content = content:gsub("^%(", ""):gsub("%)$", "")
|
||||||
for param in content:gmatch("[^%,]+") do
|
for param in content:gmatch("[^%,]+") do
|
||||||
-- get identifier
|
-- get identifier
|
||||||
local param_identifier, param_rem = param:match("^("..identifier_pattern..")(.-)$")
|
local param_identifier, param_rem = param:match("^("..identifier_pattern..")(.-)$")
|
||||||
if not 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
|
||||||
param_identifier = format_identifier(param_identifier)
|
param_identifier = format_identifier(param_identifier)
|
||||||
-- format identifier
|
-- format identifier
|
||||||
local param_fqm = ("%s.%s"):format(fqm, param_identifier)
|
local param_fqm = ("%s%s"):format(func_namespace, param_identifier)
|
||||||
-- get alias
|
-- get alias
|
||||||
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, fqm..".", 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 default value
|
-- get potential type annotation and default value
|
||||||
local default
|
local type_annotation, default
|
||||||
if param_rem:match("^=") then
|
if param_rem:match("^::") then
|
||||||
|
type_annotation = param_rem:match("^::(.*)$")
|
||||||
|
elseif param_rem:match("^=") then
|
||||||
default = param_rem:match("^=(.*)$")
|
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, default = default })
|
table.insert(r.params, { name = param_identifier, alias = param_alias, full_name = param_fqm, type_annotation = type_annotation, default = default, vararg = nil })
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
-- get assignment param
|
||||||
|
if r.type == "function" and rem:match("^%:%=") then
|
||||||
|
local param = rem:match("^%:%=(.*)$")
|
||||||
|
-- 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 annotation
|
||||||
|
local type_annotation
|
||||||
|
if param_rem:match("^::") then
|
||||||
|
type_annotation = 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
|
||||||
|
r.assignment = { name = param_identifier, alias = param_alias, full_name = param_fqm, type_annotation = type_annotation, default = nil, vararg = nil }
|
||||||
elseif rem:match("[^%s]") then
|
elseif rem:match("[^%s]") then
|
||||||
return nil, ("expected end-of-line at end of checkpoint/function definition line, but got %q; at %s"):format(rem, line.source)
|
return nil, ("expected end-of-line at end of checkpoint/function definition line, but got %q; at %s"):format(rem, line.source)
|
||||||
end
|
end
|
||||||
|
|
@ -96,152 +153,100 @@ local function parse_line(line, state, namespace)
|
||||||
minarity = minarity - 1
|
minarity = minarity - 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if maxarity > 0 and r.params[maxarity].full_name:match("%.%.%.$") then -- varargs
|
-- varargs
|
||||||
|
if maxarity > 0 and r.params[maxarity].full_name:match("%.%.%.$") then
|
||||||
r.params[maxarity].name = r.params[maxarity].name:match("^(.*)%.%.%.$")
|
r.params[maxarity].name = r.params[maxarity].name:match("^(.*)%.%.%.$")
|
||||||
r.params[maxarity].full_name = r.params[maxarity].full_name:match("^(.*)%.%.%.$")
|
r.params[maxarity].full_name = r.params[maxarity].full_name:match("^(.*)%.%.%.$")
|
||||||
r.params[maxarity].vararg = true
|
r.params[maxarity].vararg = true
|
||||||
minarity = minarity - 1
|
minarity = minarity - 1
|
||||||
maxarity = math.huge
|
maxarity = math.huge
|
||||||
end
|
end
|
||||||
-- store parent function and run checkpoint when line is read
|
r.arity = { minarity, maxarity }
|
||||||
if r.type == "checkpoint" then
|
r.signature = signature(r)
|
||||||
r.parent_function = true
|
r.pretty_signature = pretty_signature(r)
|
||||||
end
|
-- define variables
|
||||||
-- don't keep function node in block AST
|
if not line.children then line.children = {} end
|
||||||
if r.type == "function" then
|
-- define 👁️ variable
|
||||||
r.remove_from_block_ast = true
|
local seen_alias = state.builtin_aliases["👁️"]
|
||||||
end
|
if seen_alias then
|
||||||
-- define function and variables
|
table.insert(line.children, 1, { content = (":👁️:%s=0"):format(seen_alias), source = line.source })
|
||||||
r.namespace = fqm.."."
|
|
||||||
r.name = fqm
|
|
||||||
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.variant = {
|
|
||||||
arity = { minarity, maxarity },
|
|
||||||
types = {},
|
|
||||||
value = r
|
|
||||||
}
|
|
||||||
-- new function (no overloading yet)
|
|
||||||
if not state.functions[fqm] then
|
|
||||||
state.functions[fqm] = { r.variant }
|
|
||||||
-- define 👁️ variable
|
|
||||||
if not state.variables[fqm..".👁️"] then
|
|
||||||
state.variables[fqm..".👁️"] = {
|
|
||||||
type = "number",
|
|
||||||
value = 0
|
|
||||||
}
|
|
||||||
end
|
|
||||||
-- define alias for 👁️
|
|
||||||
local seen_alias = state.builtin_aliases["👁️"]
|
|
||||||
if seen_alias then
|
|
||||||
local alias = ("%s.%s"):format(fqm, seen_alias)
|
|
||||||
if state.aliases[alias] ~= nil and state.aliases[alias] then
|
|
||||||
return nil, ("trying to define alias %q for variable %q, but already exist and refer to different variable %q; at %s"):format(alias, fqm..".👁️", state.aliases[alias], line.source)
|
|
||||||
end
|
|
||||||
state.aliases[alias] = fqm..".👁️"
|
|
||||||
end
|
|
||||||
if r.type == "function" then
|
|
||||||
-- define 🔖 variable
|
|
||||||
if not state.variables[fqm..".🔖"] then
|
|
||||||
state.variables[fqm..".🔖"] = {
|
|
||||||
type = "string",
|
|
||||||
value = ""
|
|
||||||
}
|
|
||||||
end
|
|
||||||
-- define alias for 🔖
|
|
||||||
local checkpoint_alias = state.builtin_aliases["🔖"]
|
|
||||||
if checkpoint_alias then
|
|
||||||
local alias = ("%s.%s"):format(fqm, checkpoint_alias)
|
|
||||||
if state.aliases[alias] ~= nil and state.aliases[alias] then
|
|
||||||
return nil, ("trying to define alias %q for variable %q, but already exist and refer to different variable %q; at %s"):format(alias, fqm..".🔖", state.aliases[alias], line.source)
|
|
||||||
end
|
|
||||||
state.aliases[alias] = fqm..".🔖"
|
|
||||||
end
|
|
||||||
elseif r.type == "checkpoint" then
|
|
||||||
-- define 🏁 variable
|
|
||||||
if not state.variables[fqm..".🏁"] then
|
|
||||||
state.variables[fqm..".🏁"] = {
|
|
||||||
type = "number",
|
|
||||||
value = 0
|
|
||||||
}
|
|
||||||
end
|
|
||||||
-- define alias for 🏁
|
|
||||||
local reached_alias = state.builtin_aliases["🏁"]
|
|
||||||
if reached_alias then
|
|
||||||
local alias = ("%s.%s"):format(fqm, reached_alias)
|
|
||||||
if state.aliases[alias] ~= nil and state.aliases[alias] then
|
|
||||||
return nil, ("trying to define alias %q for variable %q, but already exist and refer to different variable %q; at %s"):format(alias, fqm..".🏁", state.aliases[alias], line.source)
|
|
||||||
end
|
|
||||||
state.aliases[alias] = fqm..".🏁"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- overloading
|
|
||||||
else
|
else
|
||||||
-- check for arity conflict
|
table.insert(line.children, 1, { content = ":👁️=0", source = line.source })
|
||||||
for _, variant in ipairs(state.functions[fqm]) do
|
|
||||||
local vmin, vmax = 0, math.huge
|
|
||||||
if type(variant.arity) == "table" then
|
|
||||||
vmin, vmax = variant.arity[1], variant.arity[2]
|
|
||||||
elseif variant.arity then
|
|
||||||
vmin, vmax = variant.arity, variant.arity
|
|
||||||
end
|
|
||||||
local min, max = 0, math.huge
|
|
||||||
if type(r.variant.arity) == "table" then
|
|
||||||
min, max = r.variant.arity[1], r.variant.arity[2]
|
|
||||||
elseif r.variant.arity then
|
|
||||||
min, max = variant.arity, r.variant.arity
|
|
||||||
end
|
|
||||||
if min == vmin and max == vmax then
|
|
||||||
return nil, ("trying to define %s %s with arity [%s;%s], but another function with the same name and arity exist; at %s"):format(r.type, fqm, min, max, line.source)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- add
|
|
||||||
table.insert(state.functions[fqm], r.variant)
|
|
||||||
end
|
end
|
||||||
-- define args and set type check information
|
if r.type == "function" then
|
||||||
for i, param in ipairs(r.params) do
|
-- define 🔖 variable
|
||||||
|
local checkpoint_alias = state.builtin_aliases["🔖"]
|
||||||
|
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
|
||||||
|
elseif r.type == "checkpoint" then
|
||||||
|
-- define 🏁 variable
|
||||||
|
local reached_alias = 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
|
||||||
|
end
|
||||||
|
-- define args
|
||||||
|
for _, param in ipairs(r.params) do
|
||||||
if not state.variables[param.full_name] then
|
if not state.variables[param.full_name] then
|
||||||
state.variables[param.full_name] = {
|
state.variables[param.full_name] = {
|
||||||
type = "undefined argument",
|
type = "undefined argument",
|
||||||
value = { r.variant, i }
|
value = nil
|
||||||
}
|
}
|
||||||
elseif state.variables[param.full_name].type ~= "undefined argument" then
|
else
|
||||||
r.variant.types[i] = state.variables[param.full_name].type
|
return nil, ("trying to define parameter %q, but a variable with the same name exists; at %s"):format(param.full_name, line.source)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
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
|
||||||
|
-- check for signature conflict with functions with the same fqm
|
||||||
|
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
|
||||||
|
-- add
|
||||||
|
table.insert(state.functions[fqm], r)
|
||||||
|
end
|
||||||
-- definition
|
-- definition
|
||||||
elseif l:match("^:") then
|
elseif l:match("^:") then
|
||||||
r.type = "definition"
|
r.type = "definition"
|
||||||
r.remove_from_block_ast = true
|
r.remove_from_block_ast = true
|
||||||
-- get expression
|
|
||||||
local exp, rem = expression(l:match("^:(.*)$"), state, namespace) -- expression parsing is done directly to get type information
|
|
||||||
if not exp then return nil, ("%s; at %s"):format(rem, line.source) end
|
|
||||||
-- get identifier
|
-- get identifier
|
||||||
local identifier
|
local identifier, rem = l:match("^:("..identifier_pattern..")(.-)$")
|
||||||
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
|
||||||
if not identifier then return nil, ("no valid identifier after expression in definition line %q; at %s"):format(rem, line.source) end
|
|
||||||
-- format identifier
|
-- format identifier
|
||||||
local fqm = ("%s%s"):format(namespace, format_identifier(identifier))
|
local fqm = ("%s%s"):format(namespace, format_identifier(identifier))
|
||||||
-- get alias
|
-- get alias
|
||||||
local ok_alias
|
local ok_alias
|
||||||
ok_alias, rem = maybe_alias(rem, fqm, namespace, line, state)
|
ok_alias, rem = maybe_alias(rem, fqm, namespace, line, state)
|
||||||
if not ok_alias then return ok_alias, rem end
|
if not ok_alias then return ok_alias, rem end
|
||||||
if rem:match("[^%s]") then
|
-- get expression
|
||||||
return nil, ("expected end-of-line after identifier in definition line, but got %q; at %s"):format(rem, line.source)
|
local exp = rem:match("^=(.*)$")
|
||||||
end
|
if not exp then return nil, ("expected \"= expression\" after %q in definition line; at %s"):format(rem, line.source) end
|
||||||
-- define identifier
|
-- define identifier
|
||||||
if state.functions[fqm] then return nil, ("trying to define variable %s, but a function with the same name exists; at %s"):format(fqm, line.source) end
|
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 not state.variables[fqm] or state.variables[fqm].type == "undefined argument" then
|
if state.variables[fqm] then return nil, ("trying to define variable %q but it is already defined; at %s"):format(fqm, line.source) end
|
||||||
local v, e = eval(state, exp)
|
r.fqm = fqm
|
||||||
if not v then return v, e end
|
r.expression = exp
|
||||||
-- update function typecheck information
|
state.variables[fqm] = { type = "pending definition", value = { expression = nil, source = r.source } }
|
||||||
if state.variables[fqm] and state.variables[fqm].type == "undefined argument" then
|
|
||||||
local und = state.variables[fqm].value
|
|
||||||
und[1].types[und[2]] = v.type
|
|
||||||
end
|
|
||||||
state.variables[fqm] = v
|
|
||||||
elseif state.variables[fqm].type ~= exp.type then
|
|
||||||
return nil, ("trying to define variable %s of type %s but it is already defined with type %s; at %s"):format(fqm, exp.type, state.variables[fqm].type, line.source)
|
|
||||||
end
|
|
||||||
-- tag
|
-- tag
|
||||||
elseif l:match("^%#") then
|
elseif l:match("^%#") then
|
||||||
r.type = "tag"
|
r.type = "tag"
|
||||||
|
|
@ -424,9 +429,7 @@ local function parse(state, s, name, source)
|
||||||
end
|
end
|
||||||
|
|
||||||
package.loaded[...] = parse
|
package.loaded[...] = parse
|
||||||
expression = require((...):gsub("preparser$", "expression"))
|
|
||||||
local common = require((...):gsub("preparser$", "common"))
|
local common = require((...):gsub("preparser$", "common"))
|
||||||
format_identifier, identifier_pattern = common.format_identifier, common.identifier_pattern
|
format_identifier, identifier_pattern, escape, special_functions_names, pretty_signature, signature = common.format_identifier, common.identifier_pattern, common.escape, common.special_functions_names, common.pretty_signature, common.signature
|
||||||
eval = require((...):gsub("parser%.preparser$", "interpreter.expression"))
|
|
||||||
|
|
||||||
return parse
|
return parse
|
||||||
|
|
|
||||||
12
stdlib/bootscript.lua
Normal file
12
stdlib/bootscript.lua
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
return [[
|
||||||
|
(Built-in type definition)
|
||||||
|
:nil="nil"
|
||||||
|
:number="number"
|
||||||
|
:string="string"
|
||||||
|
:list="list"
|
||||||
|
:pair="pair"
|
||||||
|
|
||||||
|
(Default formatter: doesn't do anything and let built-in formatters do the job)
|
||||||
|
$ format(v)
|
||||||
|
@v
|
||||||
|
]]
|
||||||
|
|
@ -1,421 +1,222 @@
|
||||||
local truthy, eval, find_function_variant, anselme
|
local truthy, anselme, compare, is_of_type
|
||||||
|
|
||||||
local function rewrite_assignement(fqm, state, arg, explicit_call)
|
|
||||||
local op, e = find_function_variant(fqm:match("^(.*)%=$"), state, arg, true)
|
|
||||||
if not op then return op, e end
|
|
||||||
local ass, err = find_function_variant(":=", state, { type = "list", left = arg.left, right = op }, explicit_call)
|
|
||||||
if not ass then return ass, err end
|
|
||||||
return ass
|
|
||||||
end
|
|
||||||
|
|
||||||
local function compare(a, b)
|
|
||||||
if a.type ~= b.type then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
if a.type == "pair" then
|
|
||||||
return compare(a.value[1], b.value[1]) and compare(a.value[2], b.value[2])
|
|
||||||
elseif a.type == "list" then
|
|
||||||
if #a.value ~= #b.value then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
for i, v in ipairs(a.value) do
|
|
||||||
if not compare(v, b.value[i]) then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
return a.value == b.value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local numeric_index
|
|
||||||
local string_index
|
|
||||||
|
|
||||||
local functions
|
local functions
|
||||||
functions = {
|
functions = {
|
||||||
-- discard left
|
-- discard left
|
||||||
[";"] = {
|
[";(a, b)"] = {
|
||||||
{
|
mode = "raw",
|
||||||
arity = 2, mode = "raw",
|
value = function(a, b) return b end
|
||||||
value = function(a, b) return b end
|
|
||||||
}
|
|
||||||
},
|
|
||||||
-- assignement
|
|
||||||
[":="] = {
|
|
||||||
-- assign to numeric index
|
|
||||||
{
|
|
||||||
arity = 2, mode = "custom",
|
|
||||||
check = function(state, args)
|
|
||||||
local left = args[1]
|
|
||||||
return left.type == "function" and left.variant == numeric_index and left.argument.expression.left.type == "variable"
|
|
||||||
end,
|
|
||||||
value = function(state, exp)
|
|
||||||
local arg = exp.argument.expression
|
|
||||||
local name = arg.left.argument.expression.left.name
|
|
||||||
local index, indexe = eval(state, arg.left.argument.expression.right)
|
|
||||||
if not index then return index, indexe end
|
|
||||||
local right, righte = eval(state, arg.right)
|
|
||||||
if not right then return right, righte end
|
|
||||||
state.variables[name].value[index.value] = right
|
|
||||||
return right
|
|
||||||
end
|
|
||||||
},
|
|
||||||
-- assign to string index
|
|
||||||
{
|
|
||||||
arity = 2, mode = "custom",
|
|
||||||
check = function(state, args)
|
|
||||||
local left = args[1]
|
|
||||||
return left.type == "function" and left.variant == string_index and left.argument.expression.left.type == "variable"
|
|
||||||
end,
|
|
||||||
value = function(state, exp)
|
|
||||||
local arg = exp.argument.expression
|
|
||||||
local name = arg.left.argument.expression.left.name
|
|
||||||
local index, indexe = eval(state, arg.left.argument.expression.right)
|
|
||||||
if not index then return index, indexe end
|
|
||||||
local right, righte = eval(state, arg.right)
|
|
||||||
if not right then return right, righte end
|
|
||||||
-- update index
|
|
||||||
local list = state.variables[name].value
|
|
||||||
for _,v in ipairs(list) do
|
|
||||||
if v.type == "pair" and compare(v.value[1], index) then
|
|
||||||
v.value[2] = right
|
|
||||||
return right
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- new index
|
|
||||||
table.insert(list, {
|
|
||||||
type = "pair",
|
|
||||||
value = { index, right }
|
|
||||||
})
|
|
||||||
return right
|
|
||||||
end
|
|
||||||
},
|
|
||||||
-- assign to direct variable
|
|
||||||
{
|
|
||||||
arity = 2, mode = "custom",
|
|
||||||
check = function(state, args)
|
|
||||||
local left, right = args[1], args[2]
|
|
||||||
if left.type ~= "variable" then
|
|
||||||
return nil, ("assignement expected a variable as a left argument but received a %s"):format(left.type)
|
|
||||||
end
|
|
||||||
if left.return_type and right.return_type and left.return_type ~= right.return_type then
|
|
||||||
return nil, ("trying to assign a %s value to a %s variable"):format(right.return_type, left.return_type)
|
|
||||||
end
|
|
||||||
return right.return_type or true
|
|
||||||
end,
|
|
||||||
value = function(state, exp)
|
|
||||||
local arg = exp.argument.expression
|
|
||||||
local name = arg.left.name
|
|
||||||
local right, righte = eval(state, arg.right)
|
|
||||||
if not right then return right, righte end
|
|
||||||
state.variables[name] = right
|
|
||||||
return right
|
|
||||||
end
|
|
||||||
}
|
|
||||||
},
|
|
||||||
["+="] = {
|
|
||||||
{ rewrite = rewrite_assignement }
|
|
||||||
},
|
|
||||||
["-="] = {
|
|
||||||
{ rewrite = rewrite_assignement }
|
|
||||||
},
|
|
||||||
["*="] = {
|
|
||||||
{ rewrite = rewrite_assignement }
|
|
||||||
},
|
|
||||||
["/="] = {
|
|
||||||
{ rewrite = rewrite_assignement }
|
|
||||||
},
|
|
||||||
["//="] = {
|
|
||||||
{ rewrite = rewrite_assignement }
|
|
||||||
},
|
|
||||||
["%="] = {
|
|
||||||
{ rewrite = rewrite_assignement }
|
|
||||||
},
|
|
||||||
["^="] = {
|
|
||||||
{ rewrite = rewrite_assignement }
|
|
||||||
},
|
},
|
||||||
-- comparaison
|
-- comparaison
|
||||||
["=="] = {
|
["==(a, b)"] = {
|
||||||
{
|
mode = "raw",
|
||||||
arity = 2, return_type = "number", mode = "raw",
|
value = function(a, b)
|
||||||
value = function(a, b)
|
return {
|
||||||
return {
|
type = "number",
|
||||||
type = "number",
|
value = compare(a, b) and 1 or 0
|
||||||
value = compare(a, b) and 1 or 0
|
}
|
||||||
}
|
end
|
||||||
end
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
["!="] = {
|
["!=(a, b)"] = {
|
||||||
{
|
mode = "raw",
|
||||||
arity = 2, return_type = "number", mode = "raw",
|
value = function(a, b)
|
||||||
value = function(a, b)
|
return {
|
||||||
return {
|
type = "number",
|
||||||
type = "number",
|
value = compare(a, b) and 0 or 1
|
||||||
value = compare(a, b) and 0 or 1
|
}
|
||||||
}
|
end
|
||||||
end
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[">"] = {
|
|
||||||
{
|
|
||||||
arity = 2, types = { "number", "number" }, return_type = "number",
|
|
||||||
value = function(a, b) return a > b end
|
|
||||||
}
|
|
||||||
},
|
|
||||||
["<"] = {
|
|
||||||
{
|
|
||||||
arity = 2, types = { "number", "number" }, return_type = "number",
|
|
||||||
value = function(a, b) return a < b end
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[">="] = {
|
|
||||||
{
|
|
||||||
arity = 2, types = { "number", "number" }, return_type = "number",
|
|
||||||
value = function(a, b) return a >= b end
|
|
||||||
}
|
|
||||||
},
|
|
||||||
["<="] = {
|
|
||||||
{
|
|
||||||
arity = 2, types = { "number", "number" }, return_type = "number",
|
|
||||||
value = function(a, b) return a <= b end
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
[">(a::number, b::number)"] = function(a, b) return a > b end,
|
||||||
|
["<(a::number, b::number)"] = function(a, b) return a < b end,
|
||||||
|
[">=(a::number, b::number)"] = function(a, b) return a >= b end,
|
||||||
|
["<=(a::number, b::number)"] = function(a, b) return a <= b end,
|
||||||
-- arithmetic
|
-- arithmetic
|
||||||
["+"] = {
|
["+(a::number, b::number)"] = function(a, b) return a + b end,
|
||||||
{
|
["+(a::string, b::string)"] = function(a, b) return a .. b end,
|
||||||
arity = 2,
|
["-(a::number, b::number)"] = function(a, b) return a - b end,
|
||||||
value = function(a, b)
|
["-(a::number)"] = function(a) return -a end,
|
||||||
if type(a) == "string" then
|
["*(a::number, b::number)"] = function(a, b) return a * b end,
|
||||||
return a .. b
|
["/(a::number, b::number)"] = function(a, b) return a / b end,
|
||||||
else
|
["//(a::number, b::number)"] = function(a, b) return math.floor(a / b) end,
|
||||||
return a + b
|
["^(a::number, b::number)"] = function(a, b) return a ^ b end,
|
||||||
end
|
|
||||||
end
|
|
||||||
}
|
|
||||||
},
|
|
||||||
["-"] = {
|
|
||||||
{
|
|
||||||
arity = 2, types = { "number", "number" }, return_type = "number",
|
|
||||||
value = function(a, b) return a - b end
|
|
||||||
},
|
|
||||||
{
|
|
||||||
arity = 1, types = { "number" }, return_type = "number",
|
|
||||||
value = function(a) return -a end
|
|
||||||
}
|
|
||||||
},
|
|
||||||
["*"] = {
|
|
||||||
{
|
|
||||||
arity = 2, types = { "number", "number" }, return_type = "number",
|
|
||||||
value = function(a, b) return a * b end
|
|
||||||
}
|
|
||||||
},
|
|
||||||
["/"] = {
|
|
||||||
{
|
|
||||||
arity = 2, types = { "number", "number" }, return_type = "number",
|
|
||||||
value = function(a, b) return a / b end
|
|
||||||
}
|
|
||||||
},
|
|
||||||
["//"] = {
|
|
||||||
{
|
|
||||||
arity = 2, types = { "number", "number" }, return_type = "number",
|
|
||||||
value = function(a, b) return math.floor(a / b) end
|
|
||||||
}
|
|
||||||
},
|
|
||||||
["^"] = {
|
|
||||||
{
|
|
||||||
arity = 2, types = { "number", "number" }, return_type = "number",
|
|
||||||
value = function(a, b) return a ^ b end
|
|
||||||
}
|
|
||||||
},
|
|
||||||
-- boolean
|
-- boolean
|
||||||
["!"] = {
|
["!(a)"] = {
|
||||||
{
|
mode = "raw",
|
||||||
arity = 1, return_type = "number", mode = "raw",
|
value = function(a)
|
||||||
value = function(a)
|
return {
|
||||||
return {
|
type = "number",
|
||||||
type = "number",
|
value = truthy(a) and 0 or 1
|
||||||
value = truthy(a) and 0 or 1
|
}
|
||||||
}
|
end
|
||||||
end
|
|
||||||
}
|
|
||||||
},
|
|
||||||
["&"] = {
|
|
||||||
{
|
|
||||||
arity = 2, return_type = "number", mode = "custom",
|
|
||||||
value = function(state, exp)
|
|
||||||
local arg = exp.argument.expression
|
|
||||||
local left, lefte = eval(state, arg.left)
|
|
||||||
if not left then return left, lefte end
|
|
||||||
if truthy(left) then
|
|
||||||
local right, righte = eval(state, arg.right)
|
|
||||||
if not right then return right, righte end
|
|
||||||
if truthy(right) then
|
|
||||||
return {
|
|
||||||
type = "number",
|
|
||||||
value = 1
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return {
|
|
||||||
type = "number",
|
|
||||||
value = 0
|
|
||||||
}
|
|
||||||
end
|
|
||||||
}
|
|
||||||
},
|
|
||||||
["|"] = {
|
|
||||||
{
|
|
||||||
arity = 2, return_type = "number", mode = "custom",
|
|
||||||
value = function(state, exp)
|
|
||||||
local arg = exp.argument.expression
|
|
||||||
local left, lefte = eval(state, arg.left)
|
|
||||||
if not left then return left, lefte end
|
|
||||||
if truthy(left) then
|
|
||||||
return {
|
|
||||||
type = "number",
|
|
||||||
value = 1
|
|
||||||
}
|
|
||||||
end
|
|
||||||
local right, righte = eval(state, arg.right)
|
|
||||||
if not right then return right, righte end
|
|
||||||
return {
|
|
||||||
type = "number",
|
|
||||||
value = truthy(right) and 1 or 0
|
|
||||||
}
|
|
||||||
end
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
-- pair
|
-- pair
|
||||||
[":"] = {
|
[":(a, b)"] = {
|
||||||
{
|
mode = "raw",
|
||||||
arity = 2, return_type = "pair", mode = "raw",
|
value = function(a, b)
|
||||||
value = function(a, b)
|
return {
|
||||||
return {
|
type = "pair",
|
||||||
type = "pair",
|
value = { a, b }
|
||||||
value = { a, b }
|
}
|
||||||
}
|
end
|
||||||
end
|
},
|
||||||
}
|
-- type
|
||||||
|
["::(a, b)"] = {
|
||||||
|
mode = "raw",
|
||||||
|
value = function(a, b)
|
||||||
|
return {
|
||||||
|
type = "type",
|
||||||
|
value = { a, b }
|
||||||
|
}
|
||||||
|
end
|
||||||
},
|
},
|
||||||
-- index
|
-- index
|
||||||
["("] = {
|
["()(l::list, i::number)"] = {
|
||||||
{
|
mode = "untyped raw",
|
||||||
arity = 2, types = { "list", "number" }, mode = "raw",
|
value = function(l, i)
|
||||||
value = function(a, b)
|
return l.value[i.value] or { type = "nil", value = nil }
|
||||||
return a.value[b.value] or { type = "nil", value = nil }
|
end
|
||||||
end
|
},
|
||||||
},
|
["()(l::list, i::string)"] = {
|
||||||
{
|
mode = "untyped raw",
|
||||||
arity = 2, types = { "list", "string" }, mode = "raw",
|
value = function(l, i)
|
||||||
value = function(a, b)
|
for _, v in ipairs(l.value) do
|
||||||
for _,v in ipairs(a.value) do
|
if v.type == "pair" and compare(v.value[1], i) then
|
||||||
if v.type == "pair" and compare(v.value[1], b) then
|
return v.value[2]
|
||||||
return v.value[2]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
return { type = "nil", value = nil }
|
|
||||||
end
|
end
|
||||||
}
|
return { type = "nil", value = nil }
|
||||||
|
end
|
||||||
|
},
|
||||||
|
-- index assignment
|
||||||
|
["()(l::list, i::number) := v"] = {
|
||||||
|
mode = "raw",
|
||||||
|
value = function(l, i, v)
|
||||||
|
local lv = l.type == "type" and l.value[1] or l
|
||||||
|
local iv = i.type == "type" and i.value[1] or i
|
||||||
|
lv.value[iv.value] = v
|
||||||
|
return v
|
||||||
|
end
|
||||||
|
},
|
||||||
|
["()(l::list, k::string) := v"] = {
|
||||||
|
mode = "raw",
|
||||||
|
value = function(l, k, v)
|
||||||
|
local lv = l.type == "type" and l.value[1] or l
|
||||||
|
local kv = k.type == "type" and k.value[1] or k
|
||||||
|
-- update index
|
||||||
|
for _, x in ipairs(lv.value) do
|
||||||
|
if x.type == "pair" and compare(x.value[1], kv) then
|
||||||
|
x.value[2] = v
|
||||||
|
return v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- new index
|
||||||
|
table.insert(lv.value, {
|
||||||
|
type = "pair",
|
||||||
|
value = { kv, v }
|
||||||
|
})
|
||||||
|
return v
|
||||||
|
end
|
||||||
},
|
},
|
||||||
-- pair methods
|
-- pair methods
|
||||||
name = {
|
["name(p::pair)"] = {
|
||||||
{
|
mode = "untyped raw",
|
||||||
arity = 1, types = { "pair" }, mode = "raw",
|
value = function(a)
|
||||||
value = function(a)
|
return a.value[1]
|
||||||
return a.value[1]
|
end
|
||||||
end
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
value = {
|
["value(p::pair)"] = {
|
||||||
{
|
mode = "untyped raw",
|
||||||
arity = 1, types = { "pair" }, mode = "raw",
|
value = function(a)
|
||||||
value = function(a)
|
return a.value[2]
|
||||||
return a.value[2]
|
end
|
||||||
end
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
-- list methods
|
-- list methods
|
||||||
len = {
|
["len(l::list)"] = {
|
||||||
{
|
mode = "untyped raw", -- raw to count pairs in the list
|
||||||
arity = 1, types = { "list" }, return_type = "number", mode = "raw", -- raw to count pairs in the list
|
value = function(a)
|
||||||
value = function(a)
|
return {
|
||||||
return {
|
type = "number",
|
||||||
type = "number",
|
value = #a.value
|
||||||
value = #a.value
|
}
|
||||||
}
|
end
|
||||||
end
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
insert = {
|
["insert(l::list, v)"] = {
|
||||||
{
|
mode = "raw",
|
||||||
arity = 2, types = { "list" }, return_type = "list", mode = "raw",
|
value = function(l, v)
|
||||||
value = function(a, v)
|
local lv = l.type == "type" and l.value[1] or l
|
||||||
table.insert(a.value, v)
|
table.insert(lv.value, v)
|
||||||
return a
|
end
|
||||||
end
|
|
||||||
},
|
|
||||||
{
|
|
||||||
arity = 3, types = { "list", "number" }, return_type = "list", mode = "raw",
|
|
||||||
value = function(a, k, v)
|
|
||||||
table.insert(a.value, k.value, v)
|
|
||||||
return a
|
|
||||||
end
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
remove = {
|
["insert(l::list, i::number, v)"] = {
|
||||||
{
|
mode = "raw",
|
||||||
arity = 1, types = { "list" }, return_type = "list", mode = "raw",
|
value = function(l, i, v)
|
||||||
value = function(a)
|
local lv = l.type == "type" and l.value[1] or l
|
||||||
table.remove(a.value)
|
local iv = i.type == "type" and i.value[1] or i
|
||||||
return a
|
table.insert(lv.value, iv.value, v)
|
||||||
end
|
end
|
||||||
},
|
|
||||||
{
|
|
||||||
arity = 2, types = { "list", "number" }, return_type = "list", mode = "raw",
|
|
||||||
value = function(a, k)
|
|
||||||
table.remove(a.value, k.value)
|
|
||||||
return a
|
|
||||||
end
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
find = {
|
["remove(l::list)"] = {
|
||||||
{
|
mode = "untyped raw",
|
||||||
arity = 2, types = { "list" }, return_type = "number", mode = "raw",
|
value = function(l)
|
||||||
value = function(a, v)
|
return table.remove(l.value)
|
||||||
for i, x in ipairs(v.value) do
|
end
|
||||||
if compare(v, x) then
|
},
|
||||||
return i
|
["remove(l::list, i::number)"] = {
|
||||||
end
|
mode = "untyped raw",
|
||||||
|
value = function(l, i)
|
||||||
|
return table.remove(l.value, i.value)
|
||||||
|
end
|
||||||
|
},
|
||||||
|
["find(l::list, v)"] = {
|
||||||
|
mode = "raw",
|
||||||
|
value = function(l, v)
|
||||||
|
local lv = l.type == "type" and l.value[1] or l
|
||||||
|
for i, x in ipairs(lv.value) do
|
||||||
|
if compare(x, v) then
|
||||||
|
return i
|
||||||
end
|
end
|
||||||
return 0
|
|
||||||
end
|
end
|
||||||
},
|
return { type = "number", value = 0 }
|
||||||
|
end
|
||||||
},
|
},
|
||||||
-- other methods
|
-- other methods
|
||||||
rand = {
|
["error(m::string)"] = function(m) error(m, 0) end,
|
||||||
{
|
["rand"] = function() return math.random() end,
|
||||||
arity = 0, return_type = "number",
|
["rand(a::number)"] = function(a) return math.random(a) end,
|
||||||
value = function()
|
["rand(a::number, b::number)"] = function(a, b) return math.random(a, b) end,
|
||||||
return math.random()
|
["raw(v)"] = {
|
||||||
|
mode = "raw",
|
||||||
|
value = function(v)
|
||||||
|
if v.type == "type" then
|
||||||
|
return v.value[1]
|
||||||
|
else
|
||||||
|
return v
|
||||||
end
|
end
|
||||||
},
|
end
|
||||||
{
|
|
||||||
arity = 1, types = { "number" }, return_type = "number",
|
|
||||||
value = function(a)
|
|
||||||
return math.random(a)
|
|
||||||
end
|
|
||||||
},
|
|
||||||
{
|
|
||||||
arity = 2, types = { "number", "number" }, return_type = "number",
|
|
||||||
value = function(a, b)
|
|
||||||
return math.random(a, b)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
cycle = function(...)
|
["type(v)"] = {
|
||||||
local l = {...}
|
mode = "raw",
|
||||||
|
value = function(v)
|
||||||
|
if v.type == "type" then
|
||||||
|
return v.value[2]
|
||||||
|
else
|
||||||
|
return {
|
||||||
|
type = "string",
|
||||||
|
value = v.type
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
},
|
||||||
|
["is of type(v, t)"] = {
|
||||||
|
mode = "raw",
|
||||||
|
value = function(v, t)
|
||||||
|
return {
|
||||||
|
type = "number",
|
||||||
|
value = is_of_type(v, t) or 0
|
||||||
|
}
|
||||||
|
end
|
||||||
|
},
|
||||||
|
["cycle(l...)"] = function(l)
|
||||||
local f, fseen = l[1], assert(anselme.running:eval(l[1]..".👁️", anselme.running:current_namespace()))
|
local f, fseen = l[1], assert(anselme.running:eval(l[1]..".👁️", anselme.running:current_namespace()))
|
||||||
for j=2, #l do
|
for j=2, #l do
|
||||||
local seen = assert(anselme.running:eval(l[j]..".👁️", anselme.running:current_namespace()))
|
local seen = assert(anselme.running:eval(l[j]..".👁️", anselme.running:current_namespace()))
|
||||||
|
|
@ -426,12 +227,10 @@ functions = {
|
||||||
end
|
end
|
||||||
return anselme.running:run(f, anselme.running:current_namespace())
|
return anselme.running:run(f, anselme.running:current_namespace())
|
||||||
end,
|
end,
|
||||||
random = function(...)
|
["random(l...)"] = function(l)
|
||||||
local l = {...}
|
|
||||||
return anselme.running:run(l[math.random(1, #l)], anselme.running:current_namespace())
|
return anselme.running:run(l[math.random(1, #l)], anselme.running:current_namespace())
|
||||||
end,
|
end,
|
||||||
next = function(...)
|
["next(l...)"] = function(l)
|
||||||
local l = {...}
|
|
||||||
local f = l[#l]
|
local f = l[#l]
|
||||||
for j=1, #l-1 do
|
for j=1, #l-1 do
|
||||||
local seen = assert(anselme.running:eval(l[j]..".👁️", anselme.running:current_namespace()))
|
local seen = assert(anselme.running:eval(l[j]..".👁️", anselme.running:current_namespace()))
|
||||||
|
|
@ -444,13 +243,7 @@ functions = {
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
numeric_index = functions["("][1]
|
|
||||||
string_index = functions["("][2]
|
|
||||||
|
|
||||||
package.loaded[...] = functions
|
package.loaded[...] = functions
|
||||||
truthy = require((...):gsub("stdlib%.functions$", "interpreter.common")).truthy
|
local common = require((...):gsub("stdlib%.functions$", "interpreter.common"))
|
||||||
eval = require((...):gsub("stdlib%.functions$", "interpreter.expression"))
|
truthy, compare, is_of_type = common.truthy, common.compare, common.is_of_type
|
||||||
find_function_variant = require((...):gsub("stdlib%.functions$", "parser.common")).find_function_variant
|
|
||||||
anselme = require((...):gsub("stdlib%.functions$", "anselme"))
|
anselme = require((...):gsub("stdlib%.functions$", "anselme"))
|
||||||
|
|
||||||
return functions
|
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,20 @@ types.anselme = {
|
||||||
if not v and ve then return v, ve end
|
if not v and ve then return v, ve end
|
||||||
return { [k] = v }
|
return { [k] = v }
|
||||||
end
|
end
|
||||||
|
},
|
||||||
|
type = {
|
||||||
|
format = function(val)
|
||||||
|
local k, ke = format(val[1])
|
||||||
|
if not k then return k, ke end
|
||||||
|
local v, ve = format(val[2])
|
||||||
|
if not v then return v, ve end
|
||||||
|
return ("%s::%s"):format(k, v)
|
||||||
|
end,
|
||||||
|
to_lua = function(val)
|
||||||
|
local k, ke = to_lua(val[1])
|
||||||
|
if not k and ke then return k, ke end
|
||||||
|
return k
|
||||||
|
end
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
67
test/run.lua
67
test/run.lua
|
|
@ -88,6 +88,8 @@ if args.script then
|
||||||
elseif t == "choice" then
|
elseif t == "choice" then
|
||||||
print(format_text(d, "\n> "))
|
print(format_text(d, "\n> "))
|
||||||
istate:choose(io.read())
|
istate:choose(io.read())
|
||||||
|
elseif t == "error" then
|
||||||
|
print(t, d)
|
||||||
else
|
else
|
||||||
print(t, inspect(d))
|
print(t, inspect(d))
|
||||||
end
|
end
|
||||||
|
|
@ -110,51 +112,38 @@ else
|
||||||
vm:setaliases("seen", "checkpoint", "reached")
|
vm:setaliases("seen", "checkpoint", "reached")
|
||||||
vm:loadfunction {
|
vm:loadfunction {
|
||||||
-- custom event test
|
-- custom event test
|
||||||
["wait"] = {
|
["wait(time::number)"] = {
|
||||||
{
|
value = function(duration)
|
||||||
arity = 1, types = { "number" },
|
coroutine.yield("wait", duration)
|
||||||
value = function(duration)
|
end
|
||||||
coroutine.yield("wait", duration)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
-- run another function in parallel
|
-- run another function in parallel
|
||||||
["run"] = {
|
["run(name::string)"] = {
|
||||||
{
|
value = function(str)
|
||||||
arity = 1, types = { "string" },
|
local istate, e = anselme.running.vm:run(str, anselme.running:current_namespace())
|
||||||
value = function(str)
|
if not istate then coroutine.yield("error", e) end
|
||||||
local istate, e = anselme.running.vm:run(str, anselme.running:current_namespace())
|
local event, data = istate:step()
|
||||||
if not istate then coroutine.yield("error", e) end
|
coroutine.yield(event, data)
|
||||||
local event, data = istate:step()
|
end
|
||||||
coroutine.yield(event, data)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
-- manual choice
|
-- manual choice
|
||||||
choose = {
|
["choose(choice::number)"] = {
|
||||||
{
|
value = function(c)
|
||||||
arity = 1, types = { "number" },
|
anselme.running:choose(c)
|
||||||
value = function(c)
|
end
|
||||||
anselme.running:choose(c)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
-- manual interrupt
|
-- manual interrupt
|
||||||
interrupt = {
|
["interrupt(name::string)"] = {
|
||||||
{
|
value = function(str)
|
||||||
arity = 1, types = { "string" },
|
anselme.running:interrupt(str)
|
||||||
value = function(str)
|
coroutine.yield("wait", 0)
|
||||||
anselme.running:interrupt(str)
|
end
|
||||||
coroutine.yield("wait", 0)
|
},
|
||||||
end
|
["interrupt()"] = {
|
||||||
},
|
value = function()
|
||||||
{
|
anselme.running:interrupt()
|
||||||
arity = 0,
|
coroutine.yield("wait", 0)
|
||||||
value = function()
|
end
|
||||||
anselme.running:interrupt()
|
|
||||||
coroutine.yield("wait", 0)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
local state, err = vm:loadfile(file, namespace)
|
local state, err = vm:loadfile(file, namespace)
|
||||||
|
|
|
||||||
11
test/tests/binary operator overload.ans
Normal file
11
test/tests/binary operator overload.ans
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
$ -(a, b)
|
||||||
|
@"generic minus"
|
||||||
|
|
||||||
|
$ -(a::string, b::string)
|
||||||
|
@a + " minus " + b
|
||||||
|
|
||||||
|
{2-5}
|
||||||
|
|
||||||
|
{"heh"-"lol"}
|
||||||
|
|
||||||
|
{[]-[]}
|
||||||
30
test/tests/binary operator overload.lua
Normal file
30
test/tests/binary operator overload.lua
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
local _={}
|
||||||
|
_[13]={}
|
||||||
|
_[12]={}
|
||||||
|
_[11]={}
|
||||||
|
_[10]={data="generic minus",tags=_[13]}
|
||||||
|
_[9]={data="heh minus lol",tags=_[12]}
|
||||||
|
_[8]={data="-3",tags=_[11]}
|
||||||
|
_[7]={_[10]}
|
||||||
|
_[6]={_[9]}
|
||||||
|
_[5]={_[8]}
|
||||||
|
_[4]={"return"}
|
||||||
|
_[3]={"text",_[7]}
|
||||||
|
_[2]={"text",_[6]}
|
||||||
|
_[1]={"text",_[5]}
|
||||||
|
return {_[1],_[2],_[3],_[4]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "-3",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "heh minus lol",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "generic minus",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
|
|
@ -4,14 +4,14 @@ _[25]={k="v"}
|
||||||
_[24]={42,k="v"}
|
_[24]={42,k="v"}
|
||||||
_[23]={}
|
_[23]={}
|
||||||
_[22]={42}
|
_[22]={42}
|
||||||
_[21]={tags=_[26],data="f"}
|
_[21]={data="f",tags=_[26]}
|
||||||
_[20]={tags=_[25],data="e"}
|
_[20]={data="e",tags=_[25]}
|
||||||
_[19]={tags=_[24],data="b"}
|
_[19]={data="b",tags=_[24]}
|
||||||
_[18]={tags=_[25],data="d"}
|
_[18]={data="d",tags=_[25]}
|
||||||
_[17]={tags=_[24],data="a"}
|
_[17]={data="a",tags=_[24]}
|
||||||
_[16]={tags=_[22],data="b"}
|
_[16]={data="b",tags=_[22]}
|
||||||
_[15]={tags=_[23],data="c"}
|
_[15]={data="c",tags=_[23]}
|
||||||
_[14]={tags=_[22],data="a"}
|
_[14]={data="a",tags=_[22]}
|
||||||
_[13]={_[21]}
|
_[13]={_[21]}
|
||||||
_[12]={_[20]}
|
_[12]={_[20]}
|
||||||
_[11]={_[19]}
|
_[11]={_[19]}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
$ bar
|
$ bar
|
||||||
:5 var
|
:var=5
|
||||||
|
|
||||||
~ var := 2
|
~ var := 2
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
:5 a
|
:a = 5
|
||||||
|
|
||||||
~ a == 2
|
~ a == 2
|
||||||
ko
|
ko
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
:5 a
|
:a = 5
|
||||||
|
|
||||||
~ a == 5
|
~ a == 5
|
||||||
ok
|
ok
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
:5 a
|
:a = 5
|
||||||
|
|
||||||
~ a == 2
|
~ a == 2
|
||||||
ko
|
ko
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
:5 a
|
:a = 5
|
||||||
|
|
||||||
~ a == 2
|
~ a == 2
|
||||||
ko
|
ko
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
:5 a
|
:a = 5
|
||||||
|
|
||||||
~ a == 2
|
~ a == 2
|
||||||
ko
|
ko
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
:5 a
|
:a = 5
|
||||||
|
|
||||||
~ a == 5
|
~ a == 5
|
||||||
ok
|
ok
|
||||||
|
|
|
||||||
11
test/tests/custom text formatting.ans
Normal file
11
test/tests/custom text formatting.ans
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
:person = "personne"
|
||||||
|
|
||||||
|
$ Person(name, age)
|
||||||
|
@[name=name, age=age]::person
|
||||||
|
|
||||||
|
:abject = Person("Darmanin", 38)
|
||||||
|
|
||||||
|
{abject}
|
||||||
|
|
||||||
|
$ format(p::person)
|
||||||
|
@"Name: {p("name")}\nAge: {p("age")}"
|
||||||
14
test/tests/custom text formatting.lua
Normal file
14
test/tests/custom text formatting.lua
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
local _={}
|
||||||
|
_[5]={}
|
||||||
|
_[4]={data="Name: Darmanin\nAge: 38",tags=_[5]}
|
||||||
|
_[3]={_[4]}
|
||||||
|
_[2]={"return"}
|
||||||
|
_[1]={"text",_[3]}
|
||||||
|
return {_[1],_[2]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "Name: Darmanin\nAge: 38",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
$ a
|
$ a
|
||||||
|
|
||||||
:2 a
|
:a = 2
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
local _={}
|
local _={}
|
||||||
_[1]={"error","trying to define variable define override function.a, but a function with the same name exists; at test/tests/define override function.ans:3"}
|
_[1]={"error","trying to define variable \"define override function.a\", but a function with the same name exists; at test/tests/define override function.ans:3"}
|
||||||
return {_[1]}
|
return {_[1]}
|
||||||
--[[
|
--[[
|
||||||
{ "error", "trying to define variable define override function.a, but a function with the same name exists; at test/tests/define override function.ans:3" }
|
{ "error", 'trying to define variable "define override function.a", but a function with the same name exists; at test/tests/define override function.ans:3' }
|
||||||
]]--
|
]]--
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
:2 a
|
:a = 2
|
||||||
|
|
||||||
$ a
|
$ a
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
:5 a
|
:a = 5
|
||||||
|
|
||||||
:2 a
|
:a = 2
|
||||||
|
|
||||||
a: {a}
|
a: {a}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,6 @@
|
||||||
local _={}
|
local _={}
|
||||||
_[5]={}
|
_[1]={"error","trying to define variable \"define override.a\" but it is already defined; at test/tests/define override.ans:3"}
|
||||||
_[4]={data="a: 5",tags=_[5]}
|
return {_[1]}
|
||||||
_[3]={_[4]}
|
|
||||||
_[2]={"return"}
|
|
||||||
_[1]={"text",_[3]}
|
|
||||||
return {_[1],_[2]}
|
|
||||||
--[[
|
--[[
|
||||||
{ "text", { {
|
{ "error", 'trying to define variable "define override.a" but it is already defined; at test/tests/define override.ans:3' }
|
||||||
data = "a: 5",
|
|
||||||
tags = {}
|
|
||||||
} } }
|
|
||||||
{ "return" }
|
|
||||||
]]--
|
]]--
|
||||||
|
|
@ -1 +1 @@
|
||||||
:5 a
|
:a = 5
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
:(1:2) a
|
:a = [1:2]
|
||||||
|
|
||||||
0 = {a == (5:2)}
|
0 = {a == [5:2]}
|
||||||
|
|
||||||
0 = {a == (1:3)}
|
0 = {a == [1:3]}
|
||||||
|
|
||||||
1 = {a == (1:2)}
|
1 = {a == [1:2]}
|
||||||
|
|
||||||
:[1,2,3] b
|
:b = [1,2,3]
|
||||||
|
|
||||||
0 = {b == a}
|
0 = {b == a}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
local _={}
|
|
||||||
_[1]={"error","trying to define function function arity conflict.f with arity [2;2], but another function with the same name and arity exist; at test/tests/function arity conflict.ans:5"}
|
|
||||||
return {_[1]}
|
|
||||||
--[[
|
|
||||||
{ "error", "trying to define function function arity conflict.f with arity [2;2], but another function with the same name and arity exist; at test/tests/function arity conflict.ans:5" }
|
|
||||||
]]--
|
|
||||||
22
test/tests/function assignement.ans
Normal file
22
test/tests/function assignement.ans
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
:a = 5
|
||||||
|
|
||||||
|
$ f(p)
|
||||||
|
@a
|
||||||
|
|
||||||
|
$ f(p) := v
|
||||||
|
v={v}
|
||||||
|
~ a := v
|
||||||
|
|
||||||
|
$ f(p) := v::string
|
||||||
|
v2={v}
|
||||||
|
~ a := p
|
||||||
|
|
||||||
|
{f(a)}
|
||||||
|
|
||||||
|
~ f(3) := 50
|
||||||
|
|
||||||
|
{f(a)}
|
||||||
|
|
||||||
|
~ f(3) := "ok"
|
||||||
|
|
||||||
|
{f(a)}
|
||||||
46
test/tests/function assignement.lua
Normal file
46
test/tests/function assignement.lua
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
local _={}
|
||||||
|
_[21]={}
|
||||||
|
_[20]={}
|
||||||
|
_[19]={}
|
||||||
|
_[18]={}
|
||||||
|
_[17]={}
|
||||||
|
_[16]={data="3",tags=_[21]}
|
||||||
|
_[15]={data="v2=ok",tags=_[20]}
|
||||||
|
_[14]={data="50",tags=_[19]}
|
||||||
|
_[13]={data="v=50",tags=_[18]}
|
||||||
|
_[12]={data="5",tags=_[17]}
|
||||||
|
_[11]={_[16]}
|
||||||
|
_[10]={_[15]}
|
||||||
|
_[9]={_[14]}
|
||||||
|
_[8]={_[13]}
|
||||||
|
_[7]={_[12]}
|
||||||
|
_[6]={"return"}
|
||||||
|
_[5]={"text",_[11]}
|
||||||
|
_[4]={"text",_[10]}
|
||||||
|
_[3]={"text",_[9]}
|
||||||
|
_[2]={"text",_[8]}
|
||||||
|
_[1]={"text",_[7]}
|
||||||
|
return {_[1],_[2],_[3],_[4],_[5],_[6]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "5",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "v=50",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "50",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "v2=ok",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "3",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
|
|
@ -2,4 +2,4 @@ $ f(a, b)
|
||||||
|
|
||||||
$ f(x)
|
$ f(x)
|
||||||
|
|
||||||
$ f(u, v)
|
$ f(a, b)
|
||||||
6
test/tests/function conflict.lua
Normal file
6
test/tests/function conflict.lua
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
local _={}
|
||||||
|
_[1]={"error","trying to define function function conflict.f(a, b) (at test/tests/function conflict.ans:5), but another function with same signature function conflict.f(a, b) (at test/tests/function conflict.ans:1) exists; at test/tests/function conflict.ans:5"}
|
||||||
|
return {_[1]}
|
||||||
|
--[[
|
||||||
|
{ "error", "trying to define function function conflict.f(a, b) (at test/tests/function conflict.ans:5), but another function with same signature function conflict.f(a, b) (at test/tests/function conflict.ans:1) exists; at test/tests/function conflict.ans:5" }
|
||||||
|
]]--
|
||||||
17
test/tests/function custom type dispatch error.ans
Normal file
17
test/tests/function custom type dispatch error.ans
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
:name="name"::string
|
||||||
|
:french name="french"::name
|
||||||
|
:esperanto name="esperanto"::name
|
||||||
|
|
||||||
|
$ a(name::string)
|
||||||
|
{name} is english or generic
|
||||||
|
|
||||||
|
$ a(name:nom::french name)
|
||||||
|
{nom} is french
|
||||||
|
|
||||||
|
~ a("bob"::name)
|
||||||
|
|
||||||
|
~ a("pierre"::french name)
|
||||||
|
|
||||||
|
~ a("idk"::esperanto name)
|
||||||
|
|
||||||
|
~ a(5)
|
||||||
30
test/tests/function custom type dispatch error.lua
Normal file
30
test/tests/function custom type dispatch error.lua
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
local _={}
|
||||||
|
_[13]={}
|
||||||
|
_[12]={}
|
||||||
|
_[11]={}
|
||||||
|
_[10]={data="idk::esperanto::name::string is english or generic",tags=_[13]}
|
||||||
|
_[9]={data="pierre::french::name::string is french",tags=_[12]}
|
||||||
|
_[8]={data="bob::name::string is english or generic",tags=_[11]}
|
||||||
|
_[7]={_[10]}
|
||||||
|
_[6]={_[9]}
|
||||||
|
_[5]={_[8]}
|
||||||
|
_[4]={"error","no compatible function found for call to a(number); potential candidates were:\n\9function custom type dispatch error.a(name::string) (at test/tests/function custom type dispatch error.ans:5): argument name is not of expected type string\n\9function custom type dispatch error.a(name:nom::french name) (at test/tests/function custom type dispatch error.ans:8): argument name is not of expected type french::name::string; at test/tests/function custom type dispatch error.ans:17"}
|
||||||
|
_[3]={"text",_[7]}
|
||||||
|
_[2]={"text",_[6]}
|
||||||
|
_[1]={"text",_[5]}
|
||||||
|
return {_[1],_[2],_[3],_[4]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "bob::name::string is english or generic",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "pierre::french::name::string is french",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "idk::esperanto::name::string is english or generic",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "error", "no compatible function found for call to a(number); potential candidates were:\n\tfunction custom type dispatch error.a(name::string) (at test/tests/function custom type dispatch error.ans:5): argument name is not of expected type string\n\tfunction custom type dispatch error.a(name:nom::french name) (at test/tests/function custom type dispatch error.ans:8): argument name is not of expected type french::name::string; at test/tests/function custom type dispatch error.ans:17" }
|
||||||
|
]]--
|
||||||
15
test/tests/function custom type dispatch.ans
Normal file
15
test/tests/function custom type dispatch.ans
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
:name="name"::string
|
||||||
|
:french name="french"::name
|
||||||
|
:esperanto name="esperanto"::name
|
||||||
|
|
||||||
|
$ a(name)
|
||||||
|
{name} is english or generic
|
||||||
|
|
||||||
|
$ a(name:nom::french name)
|
||||||
|
{nom} is french
|
||||||
|
|
||||||
|
~ a("bob"::name)
|
||||||
|
|
||||||
|
~ a("pierre"::french name)
|
||||||
|
|
||||||
|
~ a("idk"::esperanto name)
|
||||||
30
test/tests/function custom type dispatch.lua
Normal file
30
test/tests/function custom type dispatch.lua
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
local _={}
|
||||||
|
_[13]={}
|
||||||
|
_[12]={}
|
||||||
|
_[11]={}
|
||||||
|
_[10]={data="idk::esperanto::name::string is english or generic",tags=_[13]}
|
||||||
|
_[9]={data="pierre::french::name::string is french",tags=_[12]}
|
||||||
|
_[8]={data="bob::name::string is english or generic",tags=_[11]}
|
||||||
|
_[7]={_[10]}
|
||||||
|
_[6]={_[9]}
|
||||||
|
_[5]={_[8]}
|
||||||
|
_[4]={"return"}
|
||||||
|
_[3]={"text",_[7]}
|
||||||
|
_[2]={"text",_[6]}
|
||||||
|
_[1]={"text",_[5]}
|
||||||
|
return {_[1],_[2],_[3],_[4]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "bob::name::string is english or generic",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "pierre::french::name::string is french",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "idk::esperanto::name::string is english or generic",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
local _={}
|
local _={}
|
||||||
_[5]={}
|
_[5]={}
|
||||||
_[4]={tags=_[5],data="a.\240\159\145\129\239\184\143: 0"}
|
_[4]={data="a.\240\159\145\129\239\184\143: 0",tags=_[5]}
|
||||||
_[3]={_[4]}
|
_[3]={_[4]}
|
||||||
_[2]={"return"}
|
_[2]={"return"}
|
||||||
_[1]={"text",_[3]}
|
_[1]={"text",_[3]}
|
||||||
|
|
|
||||||
9
test/tests/function name dispatch.ans
Normal file
9
test/tests/function name dispatch.ans
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
$ fn(x)
|
||||||
|
x
|
||||||
|
|
||||||
|
$ fn(a)
|
||||||
|
a
|
||||||
|
|
||||||
|
~ fn(a=5)
|
||||||
|
|
||||||
|
~ fn(x=5)
|
||||||
22
test/tests/function name dispatch.lua
Normal file
22
test/tests/function name dispatch.lua
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
local _={}
|
||||||
|
_[9]={}
|
||||||
|
_[8]={}
|
||||||
|
_[7]={data="x",tags=_[9]}
|
||||||
|
_[6]={data="a",tags=_[8]}
|
||||||
|
_[5]={_[7]}
|
||||||
|
_[4]={_[6]}
|
||||||
|
_[3]={"return"}
|
||||||
|
_[2]={"text",_[5]}
|
||||||
|
_[1]={"text",_[4]}
|
||||||
|
return {_[1],_[2],_[3]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "a",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "x",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
5
test/tests/function no conflict.ans
Normal file
5
test/tests/function no conflict.ans
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
$ f(a, b)
|
||||||
|
|
||||||
|
$ f(x)
|
||||||
|
|
||||||
|
$ f(u, v)
|
||||||
6
test/tests/function no conflict.lua
Normal file
6
test/tests/function no conflict.lua
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
local _={}
|
||||||
|
_[1]={"return"}
|
||||||
|
return {_[1]}
|
||||||
|
--[[
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
$ a
|
$ a
|
||||||
:5 b
|
:b = 5
|
||||||
|
|
||||||
a: {b}
|
a: {b}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
local _={}
|
local _={}
|
||||||
_[1]={"error","unknown identifier \"b\"; at test/tests/function scope wrong.ans:4"}
|
_[1]={"error","compatible function \"b\" variant not found; at test/tests/function scope wrong.ans:4"}
|
||||||
return {_[1]}
|
return {_[1]}
|
||||||
--[[
|
--[[
|
||||||
{ "error", 'unknown identifier "b"; at test/tests/function scope wrong.ans:4' }
|
{ "error", 'compatible function "b" variant not found; at test/tests/function scope wrong.ans:4' }
|
||||||
]]--
|
]]--
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
$ a
|
$ a
|
||||||
:5 b
|
:b = 5
|
||||||
|
|
||||||
a: {a.b}
|
a: {a.b}
|
||||||
|
|
|
||||||
12
test/tests/function selection.ans
Normal file
12
test/tests/function selection.ans
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
$ a(x::number)
|
||||||
|
@x + 2
|
||||||
|
|
||||||
|
$ x
|
||||||
|
$ a(x::string)
|
||||||
|
@x + "heh"
|
||||||
|
|
||||||
|
{a("plop")}
|
||||||
|
|
||||||
|
{a(2)}
|
||||||
|
|
||||||
|
~ x
|
||||||
22
test/tests/function selection.lua
Normal file
22
test/tests/function selection.lua
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
local _={}
|
||||||
|
_[9]={}
|
||||||
|
_[8]={}
|
||||||
|
_[7]={data="4",tags=_[9]}
|
||||||
|
_[6]={data="plopheh",tags=_[8]}
|
||||||
|
_[5]={_[7]}
|
||||||
|
_[4]={_[6]}
|
||||||
|
_[3]={"return"}
|
||||||
|
_[2]={"text",_[5]}
|
||||||
|
_[1]={"text",_[4]}
|
||||||
|
return {_[1],_[2],_[3]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "plopheh",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "4",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
10
test/tests/function separate variable from variants.ans
Normal file
10
test/tests/function separate variable from variants.ans
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
$ f
|
||||||
|
:a = 2
|
||||||
|
|
||||||
|
$ f(x)
|
||||||
|
:a = 5
|
||||||
|
|
||||||
|
$ f(b)
|
||||||
|
:a = 10
|
||||||
|
|
||||||
|
{f.a} = 2
|
||||||
14
test/tests/function separate variable from variants.lua
Normal file
14
test/tests/function separate variable from variants.lua
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
local _={}
|
||||||
|
_[5]={}
|
||||||
|
_[4]={data="2 = 2",tags=_[5]}
|
||||||
|
_[3]={_[4]}
|
||||||
|
_[2]={"return"}
|
||||||
|
_[1]={"text",_[3]}
|
||||||
|
return {_[1],_[2]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "2 = 2",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
7
test/tests/function type dispatch ambigous.ans
Normal file
7
test/tests/function type dispatch ambigous.ans
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
$ fn(x::number)
|
||||||
|
x
|
||||||
|
|
||||||
|
$ fn(a::number)
|
||||||
|
a
|
||||||
|
|
||||||
|
~ fn(5)
|
||||||
6
test/tests/function type dispatch ambigous.lua
Normal file
6
test/tests/function type dispatch ambigous.lua
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
local _={}
|
||||||
|
_[1]={"error","function call \"fn\" is ambigous; may be at least either:\n\9function type dispatch ambigous.fn(a::number) (at test/tests/function type dispatch ambigous.ans:4)\n\9function type dispatch ambigous.fn(x::number) (at test/tests/function type dispatch ambigous.ans:1); at test/tests/function type dispatch ambigous.ans:7"}
|
||||||
|
return {_[1]}
|
||||||
|
--[[
|
||||||
|
{ "error", 'function call "fn" is ambigous; may be at least either:\n\tfunction type dispatch ambigous.fn(a::number) (at test/tests/function type dispatch ambigous.ans:4)\n\tfunction type dispatch ambigous.fn(x::number) (at test/tests/function type dispatch ambigous.ans:1); at test/tests/function type dispatch ambigous.ans:7' }
|
||||||
|
]]--
|
||||||
26
test/tests/function type dispatch with default.ans
Normal file
26
test/tests/function type dispatch with default.ans
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
$ fn(x::number)
|
||||||
|
x
|
||||||
|
|
||||||
|
$ fn(a="o"::string)
|
||||||
|
a
|
||||||
|
|
||||||
|
~ fn("s")
|
||||||
|
|
||||||
|
~ fn(5)
|
||||||
|
|
||||||
|
~ fn()
|
||||||
|
|
||||||
|
$ g(n="s", a=5::number)
|
||||||
|
@"gn"
|
||||||
|
|
||||||
|
$ g(n="s", a="lol"::string)
|
||||||
|
@"gs"
|
||||||
|
|
||||||
|
{g(n="k", a="l")}
|
||||||
|
{g(n="k", a=1)}
|
||||||
|
{g(n="k", "l")}
|
||||||
|
{g(n="k", 1)}
|
||||||
|
{g("k", "l")}
|
||||||
|
{g("k", 1)}
|
||||||
|
{g("k", a="l")}
|
||||||
|
{g("k", a=1)}
|
||||||
73
test/tests/function type dispatch with default.lua
Normal file
73
test/tests/function type dispatch with default.lua
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
local _={}
|
||||||
|
_[31]={}
|
||||||
|
_[30]={}
|
||||||
|
_[29]={}
|
||||||
|
_[28]={}
|
||||||
|
_[27]={}
|
||||||
|
_[26]={}
|
||||||
|
_[25]={}
|
||||||
|
_[24]={}
|
||||||
|
_[23]={}
|
||||||
|
_[22]={}
|
||||||
|
_[21]={}
|
||||||
|
_[20]={data="gn",tags=_[31]}
|
||||||
|
_[19]={data="gs",tags=_[30]}
|
||||||
|
_[18]={data="gn",tags=_[29]}
|
||||||
|
_[17]={data="gs",tags=_[28]}
|
||||||
|
_[16]={data="gn",tags=_[27]}
|
||||||
|
_[15]={data="gs",tags=_[26]}
|
||||||
|
_[14]={data="gn",tags=_[25]}
|
||||||
|
_[13]={data="gs",tags=_[24]}
|
||||||
|
_[12]={data="a",tags=_[23]}
|
||||||
|
_[11]={data="x",tags=_[22]}
|
||||||
|
_[10]={data="a",tags=_[21]}
|
||||||
|
_[9]={_[13],_[14],_[15],_[16],_[17],_[18],_[19],_[20]}
|
||||||
|
_[8]={_[12]}
|
||||||
|
_[7]={_[11]}
|
||||||
|
_[6]={_[10]}
|
||||||
|
_[5]={"return"}
|
||||||
|
_[4]={"text",_[9]}
|
||||||
|
_[3]={"text",_[8]}
|
||||||
|
_[2]={"text",_[7]}
|
||||||
|
_[1]={"text",_[6]}
|
||||||
|
return {_[1],_[2],_[3],_[4],_[5]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "a",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "x",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "a",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "gs",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "gn",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "gs",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "gn",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "gs",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "gn",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "gs",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "gn",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
9
test/tests/function type dispatch.ans
Normal file
9
test/tests/function type dispatch.ans
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
$ fn(x::number)
|
||||||
|
x
|
||||||
|
|
||||||
|
$ fn(a::string)
|
||||||
|
a
|
||||||
|
|
||||||
|
~ fn("s")
|
||||||
|
|
||||||
|
~ fn(5)
|
||||||
22
test/tests/function type dispatch.lua
Normal file
22
test/tests/function type dispatch.lua
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
local _={}
|
||||||
|
_[9]={}
|
||||||
|
_[8]={}
|
||||||
|
_[7]={data="x",tags=_[9]}
|
||||||
|
_[6]={data="a",tags=_[8]}
|
||||||
|
_[5]={_[7]}
|
||||||
|
_[4]={_[6]}
|
||||||
|
_[3]={"return"}
|
||||||
|
_[2]={"text",_[5]}
|
||||||
|
_[1]={"text",_[4]}
|
||||||
|
return {_[1],_[2],_[3]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "a",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "x",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
|
|
@ -3,7 +3,7 @@ $ oh
|
||||||
in interrupt: {bar.var}
|
in interrupt: {bar.var}
|
||||||
no
|
no
|
||||||
$ bar
|
$ bar
|
||||||
:5 var
|
:var = 5
|
||||||
|
|
||||||
~ var := 2
|
~ var := 2
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ $ leave
|
||||||
$ oh
|
$ oh
|
||||||
no
|
no
|
||||||
$ bar
|
$ bar
|
||||||
:5 var
|
:var = 5
|
||||||
|
|
||||||
~ var := 2
|
~ var := 2
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
$ bar
|
$ bar
|
||||||
:5 var
|
:var = 5
|
||||||
|
|
||||||
~ var := 2
|
~ var := 2
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
$ bar
|
$ bar
|
||||||
:5 var
|
:var = 5
|
||||||
|
|
||||||
~ var := 2
|
~ var := 2
|
||||||
|
|
||||||
|
|
|
||||||
23
test/tests/lazy boolean operators.ans
Normal file
23
test/tests/lazy boolean operators.ans
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
$ a
|
||||||
|
a
|
||||||
|
@1
|
||||||
|
|
||||||
|
$ b
|
||||||
|
b
|
||||||
|
@0
|
||||||
|
|
||||||
|
{a & b} = a b 0
|
||||||
|
|
||||||
|
{b & a} = b 0
|
||||||
|
|
||||||
|
{a & a} = a a 1
|
||||||
|
|
||||||
|
{b & b} = b 0
|
||||||
|
|
||||||
|
{a | b} = a 1
|
||||||
|
|
||||||
|
{b | a} = b a 1
|
||||||
|
|
||||||
|
{a | a} = a 1
|
||||||
|
|
||||||
|
{b | b} = b b 0
|
||||||
130
test/tests/lazy boolean operators.lua
Normal file
130
test/tests/lazy boolean operators.lua
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
local _={}
|
||||||
|
_[57]={}
|
||||||
|
_[56]={}
|
||||||
|
_[55]={}
|
||||||
|
_[54]={}
|
||||||
|
_[53]={}
|
||||||
|
_[52]={}
|
||||||
|
_[51]={}
|
||||||
|
_[50]={}
|
||||||
|
_[49]={}
|
||||||
|
_[48]={}
|
||||||
|
_[47]={}
|
||||||
|
_[46]={}
|
||||||
|
_[45]={}
|
||||||
|
_[44]={}
|
||||||
|
_[43]={}
|
||||||
|
_[42]={}
|
||||||
|
_[41]={}
|
||||||
|
_[40]={}
|
||||||
|
_[39]={}
|
||||||
|
_[38]={}
|
||||||
|
_[37]={data="0 = b b 0",tags=_[57]}
|
||||||
|
_[36]={data="b",tags=_[56]}
|
||||||
|
_[35]={data="b",tags=_[55]}
|
||||||
|
_[34]={data="1 = a 1",tags=_[54]}
|
||||||
|
_[33]={data="a",tags=_[53]}
|
||||||
|
_[32]={data="1 = b a 1",tags=_[52]}
|
||||||
|
_[31]={data="a",tags=_[51]}
|
||||||
|
_[30]={data="b",tags=_[50]}
|
||||||
|
_[29]={data="1 = a 1",tags=_[49]}
|
||||||
|
_[28]={data="a",tags=_[48]}
|
||||||
|
_[27]={data="0 = b 0",tags=_[47]}
|
||||||
|
_[26]={data="b",tags=_[46]}
|
||||||
|
_[25]={data="1 = a a 1",tags=_[45]}
|
||||||
|
_[24]={data="a",tags=_[44]}
|
||||||
|
_[23]={data="a",tags=_[43]}
|
||||||
|
_[22]={data="0 = b 0",tags=_[42]}
|
||||||
|
_[21]={data="b",tags=_[41]}
|
||||||
|
_[20]={data="0 = a b 0",tags=_[40]}
|
||||||
|
_[19]={data="b",tags=_[39]}
|
||||||
|
_[18]={data="a",tags=_[38]}
|
||||||
|
_[17]={_[35],_[36],_[37]}
|
||||||
|
_[16]={_[33],_[34]}
|
||||||
|
_[15]={_[30],_[31],_[32]}
|
||||||
|
_[14]={_[28],_[29]}
|
||||||
|
_[13]={_[26],_[27]}
|
||||||
|
_[12]={_[23],_[24],_[25]}
|
||||||
|
_[11]={_[21],_[22]}
|
||||||
|
_[10]={_[18],_[19],_[20]}
|
||||||
|
_[9]={"return"}
|
||||||
|
_[8]={"text",_[17]}
|
||||||
|
_[7]={"text",_[16]}
|
||||||
|
_[6]={"text",_[15]}
|
||||||
|
_[5]={"text",_[14]}
|
||||||
|
_[4]={"text",_[13]}
|
||||||
|
_[3]={"text",_[12]}
|
||||||
|
_[2]={"text",_[11]}
|
||||||
|
_[1]={"text",_[10]}
|
||||||
|
return {_[1],_[2],_[3],_[4],_[5],_[6],_[7],_[8],_[9]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "a",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "b",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "0 = a b 0",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "b",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "0 = b 0",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "a",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "a",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "1 = a a 1",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "b",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "0 = b 0",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "a",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "1 = a 1",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "b",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "a",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "1 = b a 1",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "a",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "1 = a 1",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "b",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "b",
|
||||||
|
tags = {}
|
||||||
|
}, {
|
||||||
|
data = "0 = b b 0",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
:[1,2] x
|
:x = [1,2]
|
||||||
|
|
||||||
{x}
|
{x}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
local _={}
|
local _={}
|
||||||
_[5]={}
|
_[5]={}
|
||||||
_[4]={tags=_[5],data="abc = abc = abc"}
|
_[4]={data="abc = abc = abc",tags=_[5]}
|
||||||
_[3]={_[4]}
|
_[3]={_[4]}
|
||||||
_[2]={"return"}
|
_[2]={"return"}
|
||||||
_[1]={"text",_[3]}
|
_[1]={"text",_[3]}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
$ f(l...)
|
$ f(l...)
|
||||||
~ l.len
|
~ l.len
|
||||||
:0 a
|
:a = 0
|
||||||
~ a := l(1)
|
~ a := l(1)
|
||||||
~ l.remove(1)
|
~ l.remove(1)
|
||||||
@a + f(l=l)
|
@a + f(l=l)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
local _={}
|
local _={}
|
||||||
_[5]={}
|
_[5]={}
|
||||||
_[4]={tags=_[5],data="15"}
|
_[4]={data="15",tags=_[5]}
|
||||||
_[3]={_[4]}
|
_[3]={_[4]}
|
||||||
_[2]={"return"}
|
_[2]={"return"}
|
||||||
_[1]={"text",_[3]}
|
_[1]={"text",_[3]}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
local _={}
|
local _={}
|
||||||
_[5]={}
|
_[5]={}
|
||||||
_[4]={tags=_[5],data="abc = abc = abc"}
|
_[4]={data="abc = abc = abc",tags=_[5]}
|
||||||
_[3]={_[4]}
|
_[3]={_[4]}
|
||||||
_[2]={"return"}
|
_[2]={"return"}
|
||||||
_[1]={"text",_[3]}
|
_[1]={"text",_[3]}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
local _={}
|
local _={}
|
||||||
_[5]={}
|
_[5]={}
|
||||||
_[4]={tags=_[5],data="ok = ok"}
|
_[4]={data="ok = ok",tags=_[5]}
|
||||||
_[3]={_[4]}
|
_[3]={_[4]}
|
||||||
_[2]={"return"}
|
_[2]={"return"}
|
||||||
_[1]={"text",_[3]}
|
_[1]={"text",_[3]}
|
||||||
|
|
|
||||||
|
|
@ -17,24 +17,24 @@ _[49]={}
|
||||||
_[48]={}
|
_[48]={}
|
||||||
_[47]={}
|
_[47]={}
|
||||||
_[46]={}
|
_[46]={}
|
||||||
_[45]={tags=_[63],data="c"}
|
_[45]={data="c",tags=_[63]}
|
||||||
_[44]={tags=_[62],data="a"}
|
_[44]={data="a",tags=_[62]}
|
||||||
_[43]={tags=_[61],data="Force p checkpoint:"}
|
_[43]={data="Force p checkpoint:",tags=_[61]}
|
||||||
_[42]={tags=_[60],data="d"}
|
_[42]={data="d",tags=_[60]}
|
||||||
_[41]={tags=_[59],data="c"}
|
_[41]={data="c",tags=_[59]}
|
||||||
_[40]={tags=_[58],data="b"}
|
_[40]={data="b",tags=_[58]}
|
||||||
_[39]={tags=_[57],data="From q checkpoint again:"}
|
_[39]={data="From q checkpoint again:",tags=_[57]}
|
||||||
_[38]={tags=_[56],data="d"}
|
_[38]={data="d",tags=_[56]}
|
||||||
_[37]={tags=_[55],data="c"}
|
_[37]={data="c",tags=_[55]}
|
||||||
_[36]={tags=_[54],data="b"}
|
_[36]={data="b",tags=_[54]}
|
||||||
_[35]={tags=_[53],data="From q checkpoint:"}
|
_[35]={data="From q checkpoint:",tags=_[53]}
|
||||||
_[34]={tags=_[52],data="d"}
|
_[34]={data="d",tags=_[52]}
|
||||||
_[33]={tags=_[51],data="c"}
|
_[33]={data="c",tags=_[51]}
|
||||||
_[32]={tags=_[50],data="a"}
|
_[32]={data="a",tags=_[50]}
|
||||||
_[31]={tags=_[49],data="From p checkpoint:"}
|
_[31]={data="From p checkpoint:",tags=_[49]}
|
||||||
_[30]={tags=_[48],data="d"}
|
_[30]={data="d",tags=_[48]}
|
||||||
_[29]={tags=_[47],data="x"}
|
_[29]={data="x",tags=_[47]}
|
||||||
_[28]={tags=_[46],data="From start:"}
|
_[28]={data="From start:",tags=_[46]}
|
||||||
_[27]={_[45]}
|
_[27]={_[45]}
|
||||||
_[26]={_[43],_[44]}
|
_[26]={_[43],_[44]}
|
||||||
_[25]={_[42]}
|
_[25]={_[42]}
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,18 @@ local _={}
|
||||||
_[32]={}
|
_[32]={}
|
||||||
_[31]={a="a"}
|
_[31]={a="a"}
|
||||||
_[30]={a="a",b="b"}
|
_[30]={a="a",b="b"}
|
||||||
_[29]={a="a",c="c",b="b"}
|
_[29]={a="a",b="b",c="c"}
|
||||||
_[28]={}
|
_[28]={}
|
||||||
_[27]={a="a",b="b"}
|
_[27]={a="a",b="b"}
|
||||||
_[26]={a="a"}
|
_[26]={a="a"}
|
||||||
_[25]={tags=_[32],data="e"}
|
_[25]={data="e",tags=_[32]}
|
||||||
_[24]={tags=_[31],data="d"}
|
_[24]={data="d",tags=_[31]}
|
||||||
_[23]={tags=_[30],data="c"}
|
_[23]={data="c",tags=_[30]}
|
||||||
_[22]={tags=_[29],data="b"}
|
_[22]={data="b",tags=_[29]}
|
||||||
_[21]={tags=_[28],data="e"}
|
_[21]={data="e",tags=_[28]}
|
||||||
_[20]={tags=_[26],data="d"}
|
_[20]={data="d",tags=_[26]}
|
||||||
_[19]={tags=_[27],data="c"}
|
_[19]={data="c",tags=_[27]}
|
||||||
_[18]={tags=_[26],data="a"}
|
_[18]={data="a",tags=_[26]}
|
||||||
_[17]={_[25]}
|
_[17]={_[25]}
|
||||||
_[16]={_[24]}
|
_[16]={_[24]}
|
||||||
_[15]={_[23]}
|
_[15]={_[23]}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
local _={}
|
local _={}
|
||||||
_[121]={}
|
|
||||||
_[120]={}
|
|
||||||
_[119]={}
|
|
||||||
_[118]={}
|
_[118]={}
|
||||||
_[117]={}
|
_[117]={}
|
||||||
_[116]={}
|
_[116]={}
|
||||||
|
|
@ -34,15 +31,15 @@ _[90]={}
|
||||||
_[89]={}
|
_[89]={}
|
||||||
_[88]={}
|
_[88]={}
|
||||||
_[87]={}
|
_[87]={}
|
||||||
_[86]={data="c",tags=_[121]}
|
_[86]={data="c",tags=_[118]}
|
||||||
_[85]={data="b",tags=_[120]}
|
_[85]={data="b",tags=_[117]}
|
||||||
_[84]={data="a",tags=_[119]}
|
_[84]={data="a",tags=_[116]}
|
||||||
_[83]={data="-> aa",tags=_[118]}
|
_[83]={data="-> aa",tags=_[115]}
|
||||||
_[82]={data="ab",tags=_[117]}
|
_[82]={data="ab",tags=_[114]}
|
||||||
_[81]={data="aa",tags=_[116]}
|
_[81]={data="aa",tags=_[113]}
|
||||||
_[80]={data="-> aa",tags=_[115]}
|
_[80]={data="-> aa",tags=_[112]}
|
||||||
_[79]={data="ab",tags=_[114]}
|
_[79]={data="ab",tags=_[112]}
|
||||||
_[78]={data="aa",tags=_[113]}
|
_[78]={data="aa",tags=_[112]}
|
||||||
_[77]={data="-> a",tags=_[112]}
|
_[77]={data="-> a",tags=_[112]}
|
||||||
_[76]={data="c",tags=_[111]}
|
_[76]={data="c",tags=_[111]}
|
||||||
_[75]={data="b",tags=_[110]}
|
_[75]={data="b",tags=_[110]}
|
||||||
|
|
@ -222,10 +219,10 @@ return {_[1],_[2],_[3],_[4],_[5],_[6],_[7],_[8],_[9],_[10],_[11],_[12],_[13],_[1
|
||||||
} } }
|
} } }
|
||||||
{ "choice", { {
|
{ "choice", { {
|
||||||
data = "aa",
|
data = "aa",
|
||||||
tags = {}
|
tags = <1>{}
|
||||||
}, {
|
}, {
|
||||||
data = "ab",
|
data = "ab",
|
||||||
tags = {}
|
tags = <table 1>
|
||||||
} } }
|
} } }
|
||||||
{ "text", { {
|
{ "text", { {
|
||||||
data = "-> aa",
|
data = "-> aa",
|
||||||
|
|
|
||||||
7
test/tests/string escaping.ans
Normal file
7
test/tests/string escaping.ans
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
expression {"{"a"}"}
|
||||||
|
|
||||||
|
quote {"\""}
|
||||||
|
|
||||||
|
other codes {"\n"} {"\\"} {"\t"}
|
||||||
|
|
||||||
|
{"escaping expressions {"a"+"bc"} and stuff \\ and quotes \""}
|
||||||
38
test/tests/string escaping.lua
Normal file
38
test/tests/string escaping.lua
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
local _={}
|
||||||
|
_[17]={}
|
||||||
|
_[16]={}
|
||||||
|
_[15]={}
|
||||||
|
_[14]={}
|
||||||
|
_[13]={data="escaping expressions abc and stuff \\ and quotes \"",tags=_[17]}
|
||||||
|
_[12]={data="other codes \n \\ \9",tags=_[16]}
|
||||||
|
_[11]={data="quote \"",tags=_[15]}
|
||||||
|
_[10]={data="expression a",tags=_[14]}
|
||||||
|
_[9]={_[13]}
|
||||||
|
_[8]={_[12]}
|
||||||
|
_[7]={_[11]}
|
||||||
|
_[6]={_[10]}
|
||||||
|
_[5]={"return"}
|
||||||
|
_[4]={"text",_[9]}
|
||||||
|
_[3]={"text",_[8]}
|
||||||
|
_[2]={"text",_[7]}
|
||||||
|
_[1]={"text",_[6]}
|
||||||
|
return {_[1],_[2],_[3],_[4],_[5]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "expression a",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = 'quote "',
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "other codes \n \\ \t",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = 'escaping expressions abc and stuff \\ and quotes "',
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
local _={}
|
local _={}
|
||||||
|
_[7]={1}
|
||||||
_[6]={1}
|
_[6]={1}
|
||||||
_[5]={data="bar",tags=_[6]}
|
_[5]={data="bar",tags=_[7]}
|
||||||
_[4]={data="foo",tags=_[6]}
|
_[4]={data="foo",tags=_[6]}
|
||||||
_[3]={_[4],_[5]}
|
_[3]={_[4],_[5]}
|
||||||
_[2]={"return"}
|
_[2]={"return"}
|
||||||
|
|
@ -9,10 +10,10 @@ return {_[1],_[2]}
|
||||||
--[[
|
--[[
|
||||||
{ "text", { {
|
{ "text", { {
|
||||||
data = "foo",
|
data = "foo",
|
||||||
tags = <1>{ 1 }
|
tags = { 1 }
|
||||||
}, {
|
}, {
|
||||||
data = "bar",
|
data = "bar",
|
||||||
tags = <table 1>
|
tags = { 1 }
|
||||||
} } }
|
} } }
|
||||||
{ "return" }
|
{ "return" }
|
||||||
]]--
|
]]--
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
local _={}
|
local _={}
|
||||||
|
_[7]={1}
|
||||||
_[6]={1}
|
_[6]={1}
|
||||||
_[5]={data="bar",tags=_[6]}
|
_[5]={data="bar",tags=_[7]}
|
||||||
_[4]={data="foo",tags=_[6]}
|
_[4]={data="foo",tags=_[6]}
|
||||||
_[3]={_[4],_[5]}
|
_[3]={_[4],_[5]}
|
||||||
_[2]={"return"}
|
_[2]={"return"}
|
||||||
|
|
@ -9,10 +10,10 @@ return {_[1],_[2]}
|
||||||
--[[
|
--[[
|
||||||
{ "text", { {
|
{ "text", { {
|
||||||
data = "foo",
|
data = "foo",
|
||||||
tags = <1>{ 1 }
|
tags = { 1 }
|
||||||
}, {
|
}, {
|
||||||
data = "bar",
|
data = "bar",
|
||||||
tags = <table 1>
|
tags = { 1 }
|
||||||
} } }
|
} } }
|
||||||
{ "return" }
|
{ "return" }
|
||||||
]]--
|
]]--
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
:5 a
|
:a = 5
|
||||||
|
|
||||||
a: {a}
|
a: {a}
|
||||||
|
|
|
||||||
11
test/tests/unary operator overload.ans
Normal file
11
test/tests/unary operator overload.ans
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
$ -(f)
|
||||||
|
@"generic minus"
|
||||||
|
|
||||||
|
$ -(f::string)
|
||||||
|
@"minus "+f
|
||||||
|
|
||||||
|
{-5}
|
||||||
|
|
||||||
|
{-"lol"}
|
||||||
|
|
||||||
|
{-[]}
|
||||||
30
test/tests/unary operator overload.lua
Normal file
30
test/tests/unary operator overload.lua
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
local _={}
|
||||||
|
_[13]={}
|
||||||
|
_[12]={}
|
||||||
|
_[11]={}
|
||||||
|
_[10]={data="generic minus",tags=_[13]}
|
||||||
|
_[9]={data="minus lol",tags=_[12]}
|
||||||
|
_[8]={data="-5",tags=_[11]}
|
||||||
|
_[7]={_[10]}
|
||||||
|
_[6]={_[9]}
|
||||||
|
_[5]={_[8]}
|
||||||
|
_[4]={"return"}
|
||||||
|
_[3]={"text",_[7]}
|
||||||
|
_[2]={"text",_[6]}
|
||||||
|
_[1]={"text",_[5]}
|
||||||
|
return {_[1],_[2],_[3],_[4]}
|
||||||
|
--[[
|
||||||
|
{ "text", { {
|
||||||
|
data = "-5",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "minus lol",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "text", { {
|
||||||
|
data = "generic minus",
|
||||||
|
tags = {}
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
:42 a : b
|
:a : b = 42
|
||||||
|
|
||||||
{a} = {b}
|
{a} = {b}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue