1
0
Fork 0
mirror of https://github.com/Reuh/anselme.git synced 2025-10-27 08:39:30 +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:
Étienne Fildadut 2021-06-03 15:29:25 +02:00
parent 4b139019c9
commit 64bc85741a
86 changed files with 2096 additions and 1012 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*.sublime-workspace
*.sublime-project

5
LICENSE Normal file
View 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
View file

@ -3,7 +3,7 @@ Anselme
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
-------
@ -94,7 +94,7 @@ Right after reaching a checkpoint line, Anselme will merge the local state with
```
$ main
:5 var
:var = 5
~ var := 2
@ -164,11 +164,11 @@ There's different types of lines, depending on their first character(s) (after i
> 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.
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")
@ -176,6 +176,9 @@ $ f(a, b: alias for b, c="default for c", d: alias for d = "default for d")
second argument: {b}
third argument: {c}
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.
@ -184,7 +187,6 @@ Functions can also have a variable number of arguments. By adding `...` after th
$ f(a, b...)
{b}
(will print [1])
~ f("discarded", 1)
@ -195,7 +197,7 @@ $ f(a, b...)
~ 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)
@ -204,11 +206,40 @@ $ f(a, b)
$ f(x)
b
$ f(x::string)
c
(will print a)
~ f(1,2)
(will print b)
~ 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).
@ -251,10 +282,11 @@ Checkpoints always have the following variable defined in its namespace by defau
#### 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.
@ -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.
```
:5 a
:a = 5
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
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
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:
@ -427,17 +461,17 @@ $ fn1
(everything here is in the fn1 namespace)
$ fn2
(fn1.fn2 namespace)
:var2 42
:var2 = 42
Var2 = 42: {var2}
Var2 = not found: {var2}
Var2 = 42: {fn2.var2}
:var1 1
:var1 = 1
Var2 = 42: {fn1.fn2.var2}
:var1 2
:var1 = 2
Var1 in the current namespace = 1: {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).
```
:42 name: alias
:name: alias = 42
{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)
:"John Pizzapone" player name
:player name = "John Pizzapone"
Hi {player name}!
(in a translated, french script)
:"John Pizzapone" player name : nom du joueur
:player name : nom du joueur = "John Pizzapone"
Salut {nom du joueur} !
```
@ -490,14 +524,16 @@ Default types are:
* `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]'.
* `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:
* `nil` -> `nil`
@ -628,6 +664,36 @@ $ f(a, b...)
[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
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)
* 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
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 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
@ -761,8 +847,20 @@ This only works on strings:
##### 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.
`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
-------------

View file

@ -4,12 +4,14 @@ local anselme = {
-- major.minor.fix
-- saves files are incompatible between major versions
-- scripts files may break between minor versions
version = "0.14.0",
version = "0.15.0",
--- currently running interpreter
running = nil
}
package.loaded[...] = anselme
i = require("inspect") -- luacheck: ignore
-- load libs
local preparse = require((...):gsub("anselme$", "parser.preparser"))
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 run_line = require((...):gsub("anselme$", "interpreter.interpreter")).run_line
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 stdfuncs = require((...):gsub("anselme$", "stdlib.functions"))
local bootscript = require((...):gsub("anselme$", "stdlib.bootscript"))
-- wrappers for love.filesystem / luafilesystem
local function list_directory(path)
@ -151,7 +155,7 @@ local interpreter_methods = {
local co = coroutine.create(function()
local r, e = eval(self.state, expr)
if not r then return "error", e end
return "return", to_lua(r)
return "return", r
end)
local previous = anselme.running
anselme.running = self
@ -162,7 +166,7 @@ local interpreter_methods = {
elseif event ~= "return" then
return nil, ("evaluated expression generated an %q event"):format(event)
else
return data
return to_lua(data)
end
end,
}
@ -170,6 +174,9 @@ interpreter_methods.__index = interpreter_methods
--- vm methods
local vm_mt = {
-- anselme state
state = nil,
--- wrapper for loading a whole set of scripts
-- should be preferred to other loading functions if possible
-- will load in path, in order:
@ -270,39 +277,30 @@ local vm_mt = {
end,
--- 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)
-- return self
loadfunction = function(self, name, fn)
if type(name) == "table" then
for k, v in pairs(name) do
if type(v) == "table" then
for _, variant in ipairs(v) do
self:loadfunction(k, variant)
loadfunction = function(self, signature, fn)
if type(signature) == "table" then
for k, v in pairs(signature) do
local s, e = self:loadfunction(k, v)
if not s then return nil, e end
end
else
self:loadfunction(k, v)
end
end
else
if not self.state.functions[name] then
self.state.functions[name] = {}
end
if type(fn) == "function" then
local info = debug.getinfo(fn)
table.insert(self.state.functions[name], {
arity = info.isvararg and {info.nparams, math.huge} or info.nparams,
value = fn
})
else
table.insert(self.state.functions[name], fn)
end
if type(fn) == "function" then fn = { value = fn } end
self.state.link_next_function_definition_to_lua_function = fn
local s, e = self:loadstring("$"..signature, "", "lua")
if not s then return nil, e end
assert(self.state.link_next_function_definition_to_lua_function == nil, "unexpected error while defining lua function")
return self
end
return self
end,
--- 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 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)
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))
@ -314,7 +312,7 @@ local vm_mt = {
save = function(self)
local vars = {}
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
end
end
@ -343,7 +341,7 @@ local vm_mt = {
local interpreter
interpreter = {
state = {
builtin_aliases = self.builtin_aliases,
builtin_aliases = self.state.builtin_aliases,
aliases = self.state.aliases,
functions = self.state.functions,
variables = setmetatable({}, { __index = self.state.variables }),
@ -365,7 +363,7 @@ local vm_mt = {
interrupt = nil,
-- tag stack
tags = tags or {},
}
},
},
vm = self
}
@ -397,29 +395,29 @@ return setmetatable(anselme, {
-- ["🏁"] = "reached"
},
aliases = {
-- ["bonjour.salutation"] = "hello.greeting",
-- ["bonjour.salutation"] = "hello.greeting", ...
},
functions = {
-- [":="] = {
-- ["script.fn"] = {
-- {
-- arity = {3,42}, type = { [1] = "variable" }, check = function, rewrite = function, mode = "custom",
-- value = function(state, exp)
-- end -- or checkpoint, function, line
-- }
-- },
-- function or checkpoint table
-- }, ...
-- }, ...
},
variables = {
-- foo = {
-- type = "number",
-- value = 42
-- },
-- }, ...
},
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)
vm:loadfunction(stdfuncs)
assert(vm:loadstring(bootscript, "", "boot script"))
assert(vm:loadfunction(stdfuncs))
return vm
end
})

View file

@ -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:
`
'
_
¤ £ €
?

View file

@ -10,6 +10,23 @@ common = {
global_vars[var] = value
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
truthy = function(val)
if val.type == "number" then
@ -20,6 +37,29 @@ common = {
return true
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
-- * nil, err: if error
format = function(val)
@ -63,6 +103,37 @@ common = {
end
end
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
}

View file

@ -1,5 +1,5 @@
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
@ -52,9 +52,6 @@ local function eval(state, exp)
value = {}
}
end
-- variable
elseif exp.type == "variable" then
return state.variables[exp.name]
-- list
elseif exp.type == "list" then
local l = {}
@ -72,13 +69,55 @@ local function eval(state, exp)
type = "list",
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
elseif exp.type == "function" then
local fn = exp.variant
-- custom lua functions
if fn.mode == "custom" then
return fn.value(state, exp)
else
-- eval args: list_brackets
local args = {}
if exp.argument then
@ -86,93 +125,263 @@ local function eval(state, exp)
if not arg then return arg, arge end
args = arg.value
end
-- anselme function
if type(fn.value) == "table" then
-- checkpoint
if fn.value.type == "checkpoint" then
local r, e = run(state, fn.value.child, not exp.explicit_call)
if not r then return r, e end
return r
-- function
elseif fn.value.type == "function" then
-- map named arguments
for _, arg in ipairs(args) do
local named_args = {}
for i, arg in ipairs(args) do
if arg.type == "pair" and arg.value[1].type == "string" then
args[arg.value[1].value] = arg.value[2]
named_args[arg.value[1].value] = { i, arg.value[2] }
end
end
-- eval assignment arg
local assignment
if exp.assignment then
local arge
assignment, arge = eval(state, exp.assignment)
if not assignment then return assignment, arge end
end
-- try to select a function
local tried_function_error_messages = {}
local selected_variant = { depths = { assignment = nil }, variant = nil }
for _, fn in ipairs(exp.variants) do
-- checkpoint: no args, nothing to select on
if fn.type == "checkpoint" then
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
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
-- named
if param.alias and args[param.alias] then
val = args[param.alias]
elseif args[param.name] then
val = args[param.name]
if param.alias and named_args[param.alias] then
val = named_args[param.alias][2]
used_args[named_args[param.alias][1]] = true
elseif named_args[param.name] then
val = named_args[param.name][2]
used_args[named_args[param.name][1]] = true
-- vararg
elseif param.vararg then
val = { type = "list", value = {} }
for k=j, #args do
table.insert(val.value, args[k])
used_args[k] = true
end
-- positional
elseif args[j] and args[j].type ~= "pair" then
val = args[j]
-- default
elseif param.default then
local v, e = eval(state, param.default)
if not v then return v, e end
val = v
used_args[j] = true
end
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
-- 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
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
-- eval function
local r, e
if exp.explicit_call or state.variables[fn.value.namespace.."🔖"].value == "" then
r, e = run(state, fn.value.child)
-- resume at last checkpoint
-- check for unused arguments
if ok then
for i, arg in ipairs(args) do
if not used_args[i] then
ok = false
if arg.type == "pair" and arg.value[1].type == "string" then
table.insert(tried_function_error_messages, ("%s: unexpected %s argument"):format(fn.pretty_signature, arg.value[1].value))
else
local expr, err = expression(state.variables[fn.value.namespace.."🔖"].value, state, "")
if not expr then return expr, err end
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
else
return nil, ("unknown function type %q"):format(fn.type)
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
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)
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
-- lua functions
-- TODO: handle named and default arguments
else
if fn.mode == "raw" then
return fn.value(unpack(args))
else
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(args) do
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, fn.value(unpack(l_lua))
r, e = true, lua_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)
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
return from_lua(e)
ret = from_lua(e)
else
return nil, ("%s; in Lua function %q"):format(e, exp.name)
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
return nil, ("unknown expression %q"):format(tostring(exp.type))
end
@ -182,6 +391,6 @@ package.loaded[...] = eval
run = require((...):gsub("expression$", "interpreter")).run
expression = require((...):gsub("interpreter%.expression$", "parser.expression"))
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

View file

@ -1,5 +1,5 @@
local eval
local truthy, merge_state, to_lua, eval_text, escape
local truthy, merge_state, to_lua, eval_text, escape, get_variable
local tags = {
--- push new tags on top of the stack, from Anselme values
@ -135,9 +135,11 @@ local function run_line(state, line)
end
end
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.."🏁"] = {
type = "number",
value = state.variables[line.namespace.."🏁"].value + 1
value = reached.value + 1
}
state.variables[line.parent_function.namespace.."🔖"] = {
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)
if block.parent_line and block.parent_line.type == "checkpoint" then
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.."👁️"] = {
type = "number",
value = state.variables[parent_line.namespace.."👁️"].value + 1
value = seen.value + 1
}
state.variables[parent_line.namespace.."🏁"] = {
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
-- (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
state.variables[parent_line.parent_function.namespace.."🔖"] = {
type = "string",
@ -272,7 +280,7 @@ local interpreter = {
package.loaded[...] = interpreter
eval = require((...):gsub("interpreter$", "expression"))
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
return interpreter

61
notes.txt Normal file
View 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

View file

@ -18,9 +18,33 @@ local replace_aliases = function(aliases, namespace, name)
return table.concat(name_list, ".")
end
local disallowed_set = ("~`^+-=<>/[]*{}|\\_!?,;:()\"@&$#%"):gsub("[^%w]", "%%%1")
common = {
--- 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 = function(str)
if not escapeCache[str] then
@ -42,6 +66,8 @@ common = {
end,
--- find a variable/function in a list, going up through the namespace hierarchy
-- will apply aliases
-- returns value, fqm in case of success
-- returns nil, err in case of error
find = function(aliases, list, namespace, name)
local ns = common.split(namespace)
for i=#ns, 1, -1 do
@ -58,6 +84,25 @@ common = {
end
return nil, ("can't find %q in namespace %s"):format(name, namespace)
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)
format_identifier = function(identifier)
local r = identifier:gsub("[^%.]+", function(str)
@ -76,6 +121,7 @@ common = {
end
return t
end,
-- parse interpolated expressions in a text
-- * list of strings and expressions
-- * nil, err: in case of error
parse_text = function(text, state, namespace)
@ -89,7 +135,11 @@ common = {
local exp, rem = expression(e:gsub("^{", ""), state, namespace)
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
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*}(.*)$")
else
break
@ -97,26 +147,21 @@ common = {
end
return l
end,
-- find compatible function variant
-- * variant: if success
-- find compatible function variants from a fully qualified name
-- 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
find_function_variant = function(fqm, state, arg, explicit_call)
local err = ("function %q variant not found"):format(fqm)
find_function_variant_from_fqm = function(fqm, state, arg)
local err = ("compatible function %q variant not found"):format(fqm)
local func = state.functions[fqm] or {}
local args = arg and common.flatten_list(arg) or {}
local variants = {}
for _, variant in ipairs(func) do
local ok = true
local return_type = variant.return_type
-- 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)
if variant.arity then
local min, max
if type(variant.arity) == "table" then
min, max = variant.arity[1], variant.arity[2]
else
min, max = variant.arity, variant.arity
end
local min, max = variant.arity[1], variant.arity[2]
if #args < min or #args > max then
if min == max then
err = ("function %q expected %s arguments but received %s"):format(fqm, min, #args)
@ -125,54 +170,83 @@ common = {
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
-- done
if ok then
if variant.rewrite then
local r, e = variant.rewrite(fqm, state, arg, explicit_call)
if not r then
err = e
ok = false
table.insert(variants, variant)
end
if ok then
return r
end
if #variants > 0 then
return variants
else
return nil, err
end
end,
--- same as find_function_variant_from_fqm, but will search every function from the current namespace and up using find
-- returns directly a function expression in case of success
-- return nil, err otherwise
find_function_variant = function(state, namespace, name, arg, explicit_call)
local variants = {}
local err = ("compatible function %q variant not found"):format(name)
local l = common.find_all(state.aliases, state.functions, namespace, name)
for _, ffqm in ipairs(l) do
local found
found, err = common.find_function_variant_from_fqm(ffqm, state, arg)
if found then
for _, v in ipairs(found) do
table.insert(variants, v)
end
end
end
if #variants > 0 then
return {
type = "function",
return_type = return_type,
name = fqm,
called_name = name,
explicit_call = explicit_call,
variant = variant,
argument = { -- wrap everything in a list literal to simply later things (otherwise may be nil, single value, list constructor)
variants = variants,
argument = { -- wrap everything in a list literal to simplify later things (otherwise may be nil, single value, list constructor)
type = "list_brackets",
return_type = "list",
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
return nil, err
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

View file

@ -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
local binops_prio = {
@ -9,9 +9,10 @@ local binops_prio = {
[5] = { "!=", "==", ">=", "<=", "<", ">" },
[6] = { "+", "-" },
[7] = { "*", "//", "/", "%" },
[8] = {}, -- unary operators
[9] = { "^", ":" },
[10] = { "." }
[8] = { "::", ":" },
[9] = {}, -- unary operators
[10] = { "^" },
[11] = { "." }
}
-- unop priority
local unops_prio = {
@ -22,8 +23,10 @@ local unops_prio = {
[5] = {},
[6] = {},
[7] = {},
[8] = { "-", "!" },
[9] = {}
[8] = {},
[9] = { "-", "!" },
[10] = {},
[11] = {},
}
--- parse an expression
@ -34,29 +37,47 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
currentPriority = currentPriority or 0
if not operatingOn then
-- number
if s:match("^%d+%.%d*") or s:match("^%d*%.%d+") or s:match("^%d+") then
local d, r = s:match("^(%d*%.%d*)(.*)$")
if s:match("^%d*%.%d+") or s:match("^%d+") then
local d, r = s:match("^(%d*%.%d+)(.*)$")
if not d then
d, r = s:match("^(%d+)(.*)$")
end
return expression(r, state, namespace, currentPriority, {
type = "number",
return_type = "number",
value = tonumber(d)
})
-- string
elseif s:match("^%\"[^\"]*%\"") then
local d, r = s:match("^%\"([^\"]*)%\"(.*)$")
while d:match("\\$") and not d:match("\\\\$") do
local nd, nr = r:match("([^\"]*)%\"(.*)$")
if not nd then return nil, ("unfinished string near %q"):format(r) end
d, r = d:sub(1, -2) .. "\"" .. nd, nr
elseif s:match("^%\"") then
local d, r
-- find end of string
local i = 2
while true do
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
local l, e = parse_text(tostring(d), state, namespace)
end
end
-- parse interpolated expressions
local l, e = parse_text(d, state, namespace)
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, {
type = "string",
return_type = "string",
value = l
})
-- 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 r_paren:match("[^%s]") then return nil, ("unexpected %q at end of parenthesis expression"):format(r_paren) end
else
exp = { type = "nil", return_type = "nil", value = nil }
exp = { type = "nil", value = nil }
end
return expression(r, state, namespace, currentPriority, {
type = "parentheses",
return_type = exp.return_type,
expression = exp
})
-- list parenthesis
@ -90,7 +110,6 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
end
return expression(r, state, namespace, currentPriority, {
type = "list_brackets",
return_type = "list",
expression = exp
})
-- identifier
@ -104,16 +123,14 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
if not val then return val, r end
local args = {
type = "list",
return_type = "list",
left = {
type = "string",
return_type = "string",
value = { name }
},
right = val
}
-- 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
return expression(r, state, namespace, currentPriority, variant)
end
@ -122,7 +139,6 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
if var then
return expression(r, state, namespace, currentPriority, {
type = "variable",
return_type = var.type ~= "undefined argument" and var.type or nil,
name = vfqm
})
end
@ -133,14 +149,11 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
if svar then
return expression(suffix..r, state, namespace, currentPriority, {
type = "variable",
return_type = svar.type ~= "undefined argument" and svar.type or nil,
name = svfqm
})
end
end
-- functions
local funcs, ffqm = find(state.aliases, state.functions, namespace, name)
if funcs then
-- function call
local args, explicit_call
if r:match("^%b()") then
explicit_call = true
@ -156,12 +169,10 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
end
end
-- find compatible variant
local variant, err = find_function_variant(ffqm, state, args, explicit_call)
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
return nil, ("unknown identifier %q"):format(name)
end
-- unops
for prio, oplist in ipairs(unops_prio) do
for _, op in ipairs(oplist) do
@ -170,7 +181,7 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
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
-- 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
return expression(r, state, namespace, currentPriority, variant)
end
@ -189,8 +200,6 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
if op == "." and sright:match("^"..identifier_pattern) then
local name, r = sright:match("^("..identifier_pattern..")(.-)$")
name = format_identifier(name)
local funcs, ffqm = find(state.aliases, state.functions, namespace, name)
if funcs then
local args, explicit_call
if r:match("^%b()") then
explicit_call = true
@ -211,16 +220,15 @@ local function expression(s, state, namespace, currentPriority, 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)
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
-- other binops
else
local right, r = expression(sright, state, namespace, prio)
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
return expression(r, state, namespace, currentPriority, {
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,
right = right
})
@ -237,12 +286,11 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
-- find variant
local args = {
type = "list",
return_type = "list",
left = operatingOn,
-- 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
return expression(r, state, namespace, currentPriority, variant)
end
@ -259,7 +307,7 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
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
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
return expression(r, state, namespace, currentPriority, variant)
end
@ -270,6 +318,6 @@ end
package.loaded[...] = expression
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

View file

@ -4,22 +4,44 @@ local parse_text
-- * true: if success
-- * nil, error: in case of error
local function parse(state)
-- expression parsing
for _, l in ipairs(state.queued_lines) do
local line, namespace = l.line, l.namespace
-- default arguments
-- default arguments and type annotation
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
local exp, rem = expression(param.default, state, namespace)
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
param.default = exp
-- complete type information
if exp.return_type then
line.variant.types[i] = exp.return_type
local default_exp, rem = expression(param.default, state, namespace)
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, ("unexpected characters after parameter %q: %q; at %s"):format(param.full_name, rem, line.source)
end
param.default = default_exp
-- 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
-- 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
-- expressions
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 rem:match("[^%s]") then return nil, ("expected end of expression before %q; at %s"):format(rem, line.source) end
line.expression = exp
-- function return type information
if line.type == "return" then
local variant = line.parent_function.variant
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
-- variable pending definition: expression will be evaluated when variable is needed
if line.type == "definition" then
state.variables[line.fqm].value.expression = line.expression
end
end
-- text

View file

@ -1,6 +1,4 @@
local expression
local format_identifier, identifier_pattern
local eval
local format_identifier, identifier_pattern, escape, special_functions_names, pretty_signature, signature
-- try to define an alias using rem, the text that follows the identifier
-- 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
local function maybe_alias(rem, fqm, namespace, line, state)
local alias
if rem:match("^%:") then
if rem:match("^%:[^%:%=]") then
local param_content = rem:sub(2)
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
@ -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
r.type = l:match("^%$") and "function" or "checkpoint"
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
local lc = l:match("^%$(.*)$") or l:match("^§(.*)$")
local lc = l:match("^%$(.-)$") or l:match("^§(.-)$")
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
local fqm = ("%s%s"):format(namespace, format_identifier(identifier))
local func_namespace = fqm .. "."
-- get alias
local ok_alias
ok_alias, rem = maybe_alias(rem, fqm, namespace, line, state)
if not ok_alias then return ok_alias, rem end
-- 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
r.params = {}
if r.type == "function" and rem:match("^%b()$") then
local content = rem:gsub("^%(", ""):gsub("%)$", "")
if r.type == "function" and rem:match("^%b()") then
local content
content, rem = rem:match("^(%b())%s*(.*)$")
content = content:gsub("^%(", ""):gsub("%)$", "")
for param in content:gmatch("[^%,]+") do
-- get identifier
local param_identifier, param_rem = param:match("^("..identifier_pattern..")(.-)$")
if not 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)
-- format identifier
local param_fqm = ("%s.%s"):format(fqm, param_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, 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
-- get default value
local default
if param_rem:match("^=") then
-- get potential type annotation and default value
local type_annotation, default
if param_rem:match("^::") then
type_annotation = param_rem:match("^::(.*)$")
elseif param_rem:match("^=") then
default = param_rem:match("^=(.*)$")
elseif param_rem:match("[^%s]") then
return nil, ("unexpected characters after parameter %q: %q; at %s"):format(param_fqm, param_rem, line.source)
end
-- add parameter
table.insert(r.params, { name = param_identifier, alias = param_alias, full_name = param_fqm, 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
-- 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
return nil, ("expected end-of-line at end of checkpoint/function definition line, but got %q; at %s"):format(rem, line.source)
end
@ -96,152 +153,100 @@ local function parse_line(line, state, namespace)
minarity = minarity - 1
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].full_name = r.params[maxarity].full_name:match("^(.*)%.%.%.$")
r.params[maxarity].vararg = true
minarity = minarity - 1
maxarity = math.huge
end
-- 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
end
-- define function and variables
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 }
r.arity = { minarity, maxarity }
r.signature = signature(r)
r.pretty_signature = pretty_signature(r)
-- define variables
if not line.children then line.children = {} end
-- 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..".👁️"
table.insert(line.children, 1, { content = (":👁️:%s=0"):format(seen_alias), source = line.source })
else
table.insert(line.children, 1, { content = ":👁️=0", source = line.source })
end
if r.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..".🔖"
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
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
table.insert(line.children, 1, { content = (":🏁:%s=0"):format(reached_alias), source = line.source })
else
-- check for arity conflict
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)
table.insert(line.children, 1, { content = ":🏁=0", source = line.source })
end
end
-- add
table.insert(state.functions[fqm], r.variant)
end
-- define args and set type check information
for i, param in ipairs(r.params) do
-- define args
for _, param in ipairs(r.params) do
if not state.variables[param.full_name] then
state.variables[param.full_name] = {
type = "undefined argument",
value = { r.variant, i }
value = nil
}
elseif state.variables[param.full_name].type ~= "undefined argument" then
r.variant.types[i] = state.variables[param.full_name].type
else
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
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
elseif l:match("^:") then
r.type = "definition"
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
local identifier
identifier, rem = rem:match("^("..identifier_pattern..")(.-)$")
if not identifier then return nil, ("no valid identifier after expression in definition line %q; at %s"):format(rem, line.source) end
local identifier, rem = l:match("^:("..identifier_pattern..")(.-)$")
if not identifier then return nil, ("no valid identifier at start of definition line %q; at %s"):format(l, line.source) end
-- format identifier
local fqm = ("%s%s"):format(namespace, format_identifier(identifier))
-- get alias
local ok_alias
ok_alias, rem = maybe_alias(rem, fqm, namespace, line, state)
if not ok_alias then return ok_alias, rem end
if rem:match("[^%s]") then
return nil, ("expected end-of-line after identifier in definition line, but got %q; at %s"):format(rem, line.source)
end
-- get expression
local exp = rem:match("^=(.*)$")
if not exp then return nil, ("expected \"= expression\" after %q in definition line; at %s"):format(rem, line.source) end
-- define identifier
if state.functions[fqm] then return nil, ("trying to define variable %s, 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
local v, e = eval(state, exp)
if not v then return v, e end
-- update function typecheck information
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
if state.functions[fqm] then return nil, ("trying to define variable %q, but a function with the same name exists; at %s"):format(fqm, line.source) end
if state.variables[fqm] then return nil, ("trying to define variable %q but it is already defined; at %s"):format(fqm, line.source) end
r.fqm = fqm
r.expression = exp
state.variables[fqm] = { type = "pending definition", value = { expression = nil, source = r.source } }
-- tag
elseif l:match("^%#") then
r.type = "tag"
@ -424,9 +429,7 @@ local function parse(state, s, name, source)
end
package.loaded[...] = parse
expression = require((...):gsub("preparser$", "expression"))
local common = require((...):gsub("preparser$", "common"))
format_identifier, identifier_pattern = common.format_identifier, common.identifier_pattern
eval = require((...):gsub("parser%.preparser$", "interpreter.expression"))
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
return parse

12
stdlib/bootscript.lua Normal file
View 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
]]

View file

@ -1,421 +1,222 @@
local truthy, eval, find_function_variant, anselme
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 truthy, anselme, compare, is_of_type
local functions
functions = {
-- discard left
[";"] = {
{
arity = 2, mode = "raw",
[";(a, b)"] = {
mode = "raw",
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
["=="] = {
{
arity = 2, return_type = "number", mode = "raw",
["==(a, b)"] = {
mode = "raw",
value = function(a, b)
return {
type = "number",
value = compare(a, b) and 1 or 0
}
end
}
},
["!="] = {
{
arity = 2, return_type = "number", mode = "raw",
["!=(a, b)"] = {
mode = "raw",
value = function(a, b)
return {
type = "number",
value = compare(a, b) and 0 or 1
}
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
["+"] = {
{
arity = 2,
value = function(a, b)
if type(a) == "string" then
return a .. b
else
return a + b
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
}
},
["+(a::number, b::number)"] = function(a, b) return a + b end,
["+(a::string, b::string)"] = function(a, b) return a .. b end,
["-(a::number, b::number)"] = function(a, b) return a - b end,
["-(a::number)"] = function(a) return -a 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 math.floor(a / b) end,
["^(a::number, b::number)"] = function(a, b) return a ^ b end,
-- boolean
["!"] = {
{
arity = 1, return_type = "number", mode = "raw",
["!(a)"] = {
mode = "raw",
value = function(a)
return {
type = "number",
value = truthy(a) and 0 or 1
}
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
[":"] = {
{
arity = 2, return_type = "pair", mode = "raw",
[":(a, b)"] = {
mode = "raw",
value = function(a, b)
return {
type = "pair",
value = { a, b }
}
end
}
},
-- index
["("] = {
{
arity = 2, types = { "list", "number" }, mode = "raw",
-- type
["::(a, b)"] = {
mode = "raw",
value = function(a, b)
return a.value[b.value] or { type = "nil", value = nil }
return {
type = "type",
value = { a, b }
}
end
},
{
arity = 2, types = { "list", "string" }, mode = "raw",
value = function(a, b)
for _,v in ipairs(a.value) do
if v.type == "pair" and compare(v.value[1], b) then
-- index
["()(l::list, i::number)"] = {
mode = "untyped raw",
value = function(l, i)
return l.value[i.value] or { type = "nil", value = nil }
end
},
["()(l::list, i::string)"] = {
mode = "untyped raw",
value = function(l, i)
for _, v in ipairs(l.value) do
if v.type == "pair" and compare(v.value[1], i) then
return v.value[2]
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
name = {
{
arity = 1, types = { "pair" }, mode = "raw",
["name(p::pair)"] = {
mode = "untyped raw",
value = function(a)
return a.value[1]
end
}
},
value = {
{
arity = 1, types = { "pair" }, mode = "raw",
["value(p::pair)"] = {
mode = "untyped raw",
value = function(a)
return a.value[2]
end
}
},
-- list methods
len = {
{
arity = 1, types = { "list" }, return_type = "number", mode = "raw", -- raw to count pairs in the list
["len(l::list)"] = {
mode = "untyped raw", -- raw to count pairs in the list
value = function(a)
return {
type = "number",
value = #a.value
}
end
}
},
insert = {
{
arity = 2, types = { "list" }, return_type = "list", mode = "raw",
value = function(a, v)
table.insert(a.value, v)
return a
["insert(l::list, v)"] = {
mode = "raw",
value = function(l, v)
local lv = l.type == "type" and l.value[1] or l
table.insert(lv.value, v)
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 = {
{
arity = 1, types = { "list" }, return_type = "list", mode = "raw",
value = function(a)
table.remove(a.value)
return a
["insert(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
table.insert(lv.value, iv.value, v)
end
},
{
arity = 2, types = { "list", "number" }, return_type = "list", mode = "raw",
value = function(a, k)
table.remove(a.value, k.value)
return a
["remove(l::list)"] = {
mode = "untyped raw",
value = function(l)
return table.remove(l.value)
end
}
},
find = {
{
arity = 2, types = { "list" }, return_type = "number", mode = "raw",
value = function(a, v)
for i, x in ipairs(v.value) do
if compare(v, x) then
["remove(l::list, i::number)"] = {
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
return { type = "number", value = 0 }
end
},
},
-- other methods
rand = {
{
arity = 0, return_type = "number",
value = function()
return math.random()
["error(m::string)"] = function(m) error(m, 0) end,
["rand"] = function() return math.random() end,
["rand(a::number)"] = function(a) return math.random(a) end,
["rand(a::number, b::number)"] = function(a, b) return math.random(a, b) end,
["raw(v)"] = {
mode = "raw",
value = function(v)
if v.type == "type" then
return v.value[1]
else
return v
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
["type(v)"] = {
mode = "raw",
value = function(v)
if v.type == "type" then
return v.value[2]
else
return {
type = "string",
value = v.type
}
end
end
},
cycle = function(...)
local l = {...}
["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()))
for j=2, #l do
local seen = assert(anselme.running:eval(l[j]..".👁️", anselme.running:current_namespace()))
@ -426,12 +227,10 @@ functions = {
end
return anselme.running:run(f, anselme.running:current_namespace())
end,
random = function(...)
local l = {...}
["random(l...)"] = function(l)
return anselme.running:run(l[math.random(1, #l)], anselme.running:current_namespace())
end,
next = function(...)
local l = {...}
["next(l...)"] = function(l)
local f = l[#l]
for j=1, #l-1 do
local seen = assert(anselme.running:eval(l[j]..".👁️", anselme.running:current_namespace()))
@ -444,13 +243,7 @@ functions = {
end
}
numeric_index = functions["("][1]
string_index = functions["("][2]
package.loaded[...] = functions
truthy = require((...):gsub("stdlib%.functions$", "interpreter.common")).truthy
eval = require((...):gsub("stdlib%.functions$", "interpreter.expression"))
find_function_variant = require((...):gsub("stdlib%.functions$", "parser.common")).find_function_variant
local common = require((...):gsub("stdlib%.functions$", "interpreter.common"))
truthy, compare, is_of_type = common.truthy, common.compare, common.is_of_type
anselme = require((...):gsub("stdlib%.functions$", "anselme"))
return functions

View file

@ -130,6 +130,20 @@ types.anselme = {
if not v and ve then return v, ve end
return { [k] = v }
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
}
}

View file

@ -88,6 +88,8 @@ if args.script then
elseif t == "choice" then
print(format_text(d, "\n> "))
istate:choose(io.read())
elseif t == "error" then
print(t, d)
else
print(t, inspect(d))
end
@ -110,53 +112,40 @@ else
vm:setaliases("seen", "checkpoint", "reached")
vm:loadfunction {
-- custom event test
["wait"] = {
{
arity = 1, types = { "number" },
["wait(time::number)"] = {
value = function(duration)
coroutine.yield("wait", duration)
end
}
},
-- run another function in parallel
["run"] = {
{
arity = 1, types = { "string" },
["run(name::string)"] = {
value = function(str)
local istate, e = anselme.running.vm:run(str, anselme.running:current_namespace())
if not istate then coroutine.yield("error", e) end
local event, data = istate:step()
coroutine.yield(event, data)
end
}
},
-- manual choice
choose = {
{
arity = 1, types = { "number" },
["choose(choice::number)"] = {
value = function(c)
anselme.running:choose(c)
end
}
},
-- manual interrupt
interrupt = {
{
arity = 1, types = { "string" },
["interrupt(name::string)"] = {
value = function(str)
anselme.running:interrupt(str)
coroutine.yield("wait", 0)
end
},
{
arity = 0,
["interrupt()"] = {
value = function()
anselme.running:interrupt()
coroutine.yield("wait", 0)
end
}
}
}
local state, err = vm:loadfile(file, namespace)
local result = {}

View file

@ -0,0 +1,11 @@
$ -(a, b)
@"generic minus"
$ -(a::string, b::string)
@a + " minus " + b
{2-5}
{"heh"-"lol"}
{[]-[]}

View 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" }
]]--

View file

@ -4,14 +4,14 @@ _[25]={k="v"}
_[24]={42,k="v"}
_[23]={}
_[22]={42}
_[21]={tags=_[26],data="f"}
_[20]={tags=_[25],data="e"}
_[19]={tags=_[24],data="b"}
_[18]={tags=_[25],data="d"}
_[17]={tags=_[24],data="a"}
_[16]={tags=_[22],data="b"}
_[15]={tags=_[23],data="c"}
_[14]={tags=_[22],data="a"}
_[21]={data="f",tags=_[26]}
_[20]={data="e",tags=_[25]}
_[19]={data="b",tags=_[24]}
_[18]={data="d",tags=_[25]}
_[17]={data="a",tags=_[24]}
_[16]={data="b",tags=_[22]}
_[15]={data="c",tags=_[23]}
_[14]={data="a",tags=_[22]}
_[13]={_[21]}
_[12]={_[20]}
_[11]={_[19]}

View file

@ -1,5 +1,5 @@
$ bar
:5 var
:var=5
~ var := 2

View file

@ -1,4 +1,4 @@
:5 a
:a = 5
~ a == 2
ko

View file

@ -1,4 +1,4 @@
:5 a
:a = 5
~ a == 5
ok

View file

@ -1,4 +1,4 @@
:5 a
:a = 5
~ a == 2
ko

View file

@ -1,4 +1,4 @@
:5 a
:a = 5
~ a == 2
ko

View file

@ -1,4 +1,4 @@
:5 a
:a = 5
~ a == 2
ko

View file

@ -1,4 +1,4 @@
:5 a
:a = 5
~ a == 5
ok

View 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")}"

View 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" }
]]--

View file

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

View file

@ -1,6 +1,6 @@
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]}
--[[
{ "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' }
]]--

View file

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

View file

@ -1,5 +1,5 @@
:5 a
:a = 5
:2 a
:a = 2
a: {a}

View file

@ -1,14 +1,6 @@
local _={}
_[5]={}
_[4]={data="a: 5",tags=_[5]}
_[3]={_[4]}
_[2]={"return"}
_[1]={"text",_[3]}
return {_[1],_[2]}
_[1]={"error","trying to define variable \"define override.a\" but it is already defined; at test/tests/define override.ans:3"}
return {_[1]}
--[[
{ "text", { {
data = "a: 5",
tags = {}
} } }
{ "return" }
{ "error", 'trying to define variable "define override.a" but it is already defined; at test/tests/define override.ans:3' }
]]--

View file

@ -1 +1 @@
:5 a
:a = 5

View file

@ -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}

View file

@ -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" }
]]--

View 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)}

View 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" }
]]--

View file

@ -2,4 +2,4 @@ $ f(a, b)
$ f(x)
$ f(u, v)
$ f(a, b)

View 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" }
]]--

View 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)

View 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" }
]]--

View 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)

View 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" }
]]--

View file

@ -1,6 +1,6 @@
local _={}
_[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]}
_[2]={"return"}
_[1]={"text",_[3]}

View file

@ -0,0 +1,9 @@
$ fn(x)
x
$ fn(a)
a
~ fn(a=5)
~ fn(x=5)

View 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" }
]]--

View file

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

View file

@ -0,0 +1,6 @@
local _={}
_[1]={"return"}
return {_[1]}
--[[
{ "return" }
]]--

View file

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

View file

@ -1,6 +1,6 @@
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]}
--[[
{ "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' }
]]--

View file

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

View file

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

View 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" }
]]--

View file

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

View 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" }
]]--

View file

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

View 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' }
]]--

View 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)}

View 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" }
]]--

View file

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

View 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" }
]]--

View file

@ -3,7 +3,7 @@ $ oh
in interrupt: {bar.var}
no
$ bar
:5 var
:var = 5
~ var := 2

View file

@ -4,7 +4,7 @@ $ leave
$ oh
no
$ bar
:5 var
:var = 5
~ var := 2

View file

@ -1,5 +1,5 @@
$ bar
:5 var
:var = 5
~ var := 2

View file

@ -1,5 +1,5 @@
$ bar
:5 var
:var = 5
~ var := 2

View 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

View 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" }
]]--

View file

@ -1,4 +1,4 @@
:[1,2] x
:x = [1,2]
{x}

View file

@ -1,6 +1,6 @@
local _={}
_[5]={}
_[4]={tags=_[5],data="abc = abc = abc"}
_[4]={data="abc = abc = abc",tags=_[5]}
_[3]={_[4]}
_[2]={"return"}
_[1]={"text",_[3]}

View file

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

View file

@ -1,6 +1,6 @@
local _={}
_[5]={}
_[4]={tags=_[5],data="15"}
_[4]={data="15",tags=_[5]}
_[3]={_[4]}
_[2]={"return"}
_[1]={"text",_[3]}

View file

@ -1,6 +1,6 @@
local _={}
_[5]={}
_[4]={tags=_[5],data="abc = abc = abc"}
_[4]={data="abc = abc = abc",tags=_[5]}
_[3]={_[4]}
_[2]={"return"}
_[1]={"text",_[3]}

View file

@ -1,6 +1,6 @@
local _={}
_[5]={}
_[4]={tags=_[5],data="ok = ok"}
_[4]={data="ok = ok",tags=_[5]}
_[3]={_[4]}
_[2]={"return"}
_[1]={"text",_[3]}

View file

@ -17,24 +17,24 @@ _[49]={}
_[48]={}
_[47]={}
_[46]={}
_[45]={tags=_[63],data="c"}
_[44]={tags=_[62],data="a"}
_[43]={tags=_[61],data="Force p checkpoint:"}
_[42]={tags=_[60],data="d"}
_[41]={tags=_[59],data="c"}
_[40]={tags=_[58],data="b"}
_[39]={tags=_[57],data="From q checkpoint again:"}
_[38]={tags=_[56],data="d"}
_[37]={tags=_[55],data="c"}
_[36]={tags=_[54],data="b"}
_[35]={tags=_[53],data="From q checkpoint:"}
_[34]={tags=_[52],data="d"}
_[33]={tags=_[51],data="c"}
_[32]={tags=_[50],data="a"}
_[31]={tags=_[49],data="From p checkpoint:"}
_[30]={tags=_[48],data="d"}
_[29]={tags=_[47],data="x"}
_[28]={tags=_[46],data="From start:"}
_[45]={data="c",tags=_[63]}
_[44]={data="a",tags=_[62]}
_[43]={data="Force p checkpoint:",tags=_[61]}
_[42]={data="d",tags=_[60]}
_[41]={data="c",tags=_[59]}
_[40]={data="b",tags=_[58]}
_[39]={data="From q checkpoint again:",tags=_[57]}
_[38]={data="d",tags=_[56]}
_[37]={data="c",tags=_[55]}
_[36]={data="b",tags=_[54]}
_[35]={data="From q checkpoint:",tags=_[53]}
_[34]={data="d",tags=_[52]}
_[33]={data="c",tags=_[51]}
_[32]={data="a",tags=_[50]}
_[31]={data="From p checkpoint:",tags=_[49]}
_[30]={data="d",tags=_[48]}
_[29]={data="x",tags=_[47]}
_[28]={data="From start:",tags=_[46]}
_[27]={_[45]}
_[26]={_[43],_[44]}
_[25]={_[42]}

View file

@ -2,18 +2,18 @@ local _={}
_[32]={}
_[31]={a="a"}
_[30]={a="a",b="b"}
_[29]={a="a",c="c",b="b"}
_[29]={a="a",b="b",c="c"}
_[28]={}
_[27]={a="a",b="b"}
_[26]={a="a"}
_[25]={tags=_[32],data="e"}
_[24]={tags=_[31],data="d"}
_[23]={tags=_[30],data="c"}
_[22]={tags=_[29],data="b"}
_[21]={tags=_[28],data="e"}
_[20]={tags=_[26],data="d"}
_[19]={tags=_[27],data="c"}
_[18]={tags=_[26],data="a"}
_[25]={data="e",tags=_[32]}
_[24]={data="d",tags=_[31]}
_[23]={data="c",tags=_[30]}
_[22]={data="b",tags=_[29]}
_[21]={data="e",tags=_[28]}
_[20]={data="d",tags=_[26]}
_[19]={data="c",tags=_[27]}
_[18]={data="a",tags=_[26]}
_[17]={_[25]}
_[16]={_[24]}
_[15]={_[23]}

View file

@ -1,7 +1,4 @@
local _={}
_[121]={}
_[120]={}
_[119]={}
_[118]={}
_[117]={}
_[116]={}
@ -34,15 +31,15 @@ _[90]={}
_[89]={}
_[88]={}
_[87]={}
_[86]={data="c",tags=_[121]}
_[85]={data="b",tags=_[120]}
_[84]={data="a",tags=_[119]}
_[83]={data="-> aa",tags=_[118]}
_[82]={data="ab",tags=_[117]}
_[81]={data="aa",tags=_[116]}
_[80]={data="-> aa",tags=_[115]}
_[79]={data="ab",tags=_[114]}
_[78]={data="aa",tags=_[113]}
_[86]={data="c",tags=_[118]}
_[85]={data="b",tags=_[117]}
_[84]={data="a",tags=_[116]}
_[83]={data="-> aa",tags=_[115]}
_[82]={data="ab",tags=_[114]}
_[81]={data="aa",tags=_[113]}
_[80]={data="-> aa",tags=_[112]}
_[79]={data="ab",tags=_[112]}
_[78]={data="aa",tags=_[112]}
_[77]={data="-> a",tags=_[112]}
_[76]={data="c",tags=_[111]}
_[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", { {
data = "aa",
tags = {}
tags = <1>{}
}, {
data = "ab",
tags = {}
tags = <table 1>
} } }
{ "text", { {
data = "-> aa",

View file

@ -0,0 +1,7 @@
expression {"{"a"}"}
quote {"\""}
other codes {"\n"} {"\\"} {"\t"}
{"escaping expressions {"a"+"bc"} and stuff \\ and quotes \""}

View 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" }
]]--

View file

@ -1,6 +1,7 @@
local _={}
_[7]={1}
_[6]={1}
_[5]={data="bar",tags=_[6]}
_[5]={data="bar",tags=_[7]}
_[4]={data="foo",tags=_[6]}
_[3]={_[4],_[5]}
_[2]={"return"}
@ -9,10 +10,10 @@ return {_[1],_[2]}
--[[
{ "text", { {
data = "foo",
tags = <1>{ 1 }
tags = { 1 }
}, {
data = "bar",
tags = <table 1>
tags = { 1 }
} } }
{ "return" }
]]--

View file

@ -1,6 +1,7 @@
local _={}
_[7]={1}
_[6]={1}
_[5]={data="bar",tags=_[6]}
_[5]={data="bar",tags=_[7]}
_[4]={data="foo",tags=_[6]}
_[3]={_[4],_[5]}
_[2]={"return"}
@ -9,10 +10,10 @@ return {_[1],_[2]}
--[[
{ "text", { {
data = "foo",
tags = <1>{ 1 }
tags = { 1 }
}, {
data = "bar",
tags = <table 1>
tags = { 1 }
} } }
{ "return" }
]]--

View file

@ -1,3 +1,3 @@
:5 a
:a = 5
a: {a}

View file

@ -0,0 +1,11 @@
$ -(f)
@"generic minus"
$ -(f::string)
@"minus "+f
{-5}
{-"lol"}
{-[]}

View 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" }
]]--

View file

@ -1,3 +1,3 @@
:42 a : b
:a : b = 42
{a} = {b}