mirror of
https://github.com/Reuh/anselme.git
synced 2025-10-27 16:49:31 +00:00
[doc] standard library documentation first draft
This commit is contained in:
parent
41dede808e
commit
0195913dcc
29 changed files with 2045 additions and 67 deletions
|
|
@ -1,3 +1,6 @@
|
|||
---# Variable assignment
|
||||
-- @titlelevel 3
|
||||
|
||||
local ast = require("anselme.ast")
|
||||
local Nil, Boolean, LuaCall, ParameterTuple, FunctionParameter, Identifier, Overloadable, Overload, Call, Quote = ast.Nil, ast.Boolean, ast.LuaCall, ast.ParameterTuple, ast.FunctionParameter, ast.Identifier, ast.abstract.Overloadable, ast.Overload, ast.Call, ast.Quote
|
||||
|
||||
|
|
@ -46,6 +49,9 @@ return {
|
|||
|
||||
{
|
||||
--- Assign `value` to the variable `identifier`.
|
||||
-- ```
|
||||
-- var = 42
|
||||
-- ```
|
||||
-- @title identifier = value
|
||||
"_=_", "(quote::is quoted(\"identifier\"), value)",
|
||||
function(state, quote, value)
|
||||
|
|
@ -55,6 +61,9 @@ return {
|
|||
},
|
||||
{
|
||||
--- Define the variable using the symbol `symbol` with the initial value `value`.
|
||||
-- ```
|
||||
-- :var = 42
|
||||
-- ```
|
||||
-- @title symbol::is symbol = value
|
||||
"_=_", "(quote::is quoted(\"symbol\"), value)",
|
||||
function(state, quote, value)
|
||||
|
|
@ -69,6 +78,10 @@ return {
|
|||
},
|
||||
{
|
||||
--- For each `variable` element of the variable tuple and associated `value` element of the value tuple, call `variable = value`.
|
||||
-- ```
|
||||
-- (:a, :b) = (24, 42)
|
||||
-- (a, b) = (b, a)
|
||||
-- ```
|
||||
-- @title variable tuple::is tuple = value tuple::is tuple
|
||||
"_=_", "(quote::is quoted(\"tuple\"), value::is tuple)",
|
||||
function(state, quote, tuple)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,15 @@
|
|||
--- # Attached block
|
||||
--
|
||||
-- The attached block can usually be accessed using the `_` variable. However, `_` is only defined in the scope of the line the block is attached to.
|
||||
-- These functions are intended to be used to retrieve an attached block where `_` can not be used directly.
|
||||
--
|
||||
-- ```
|
||||
-- // `if` use `attached block!` in order to obtain the attached block without needing to pass `_` as an argument
|
||||
-- if(true)
|
||||
-- print("hello")
|
||||
-- ```
|
||||
-- @titlelevel 3
|
||||
|
||||
local ast = require("anselme.ast")
|
||||
local Function, ParameterTuple, Identifier = ast.Function, ast.ParameterTuple, ast.Identifier
|
||||
|
||||
|
|
@ -7,6 +19,30 @@ local block_identifier = Identifier:new("_")
|
|||
|
||||
return {
|
||||
{
|
||||
--- Return the attached block (as a function).
|
||||
--
|
||||
-- `level` indicates the position on the call stack where the attached block should be searched. 0 is where `attached block` was called, 1 is where the function calling `attached block` was called, 2 is where the function calling the function that called `attached block` is called, etc.
|
||||
--
|
||||
-- ```
|
||||
-- // level is 1, `attached block` is called from `call attached block`: the attached block will be searched from where `call attached block` was called
|
||||
-- :$call attached block()
|
||||
-- :fn = attached block!
|
||||
-- fn!
|
||||
-- call attached block!
|
||||
-- print("hello")
|
||||
-- ```
|
||||
--
|
||||
-- ```
|
||||
-- // level is 0: the attached block is searched where `attached block` was called, i.e. the current scope
|
||||
-- :block = attached block(level=0)
|
||||
-- print("hello")
|
||||
-- block! // hello
|
||||
-- // which is the same as
|
||||
-- :block = $_
|
||||
-- print("hello")
|
||||
-- ```
|
||||
--
|
||||
-- if `keep return` is true, if the attached block function returns a return value when called, it will be returned as is (instead of unwrapping only the value associated with the return), and will therefore propagate the return to the current block.
|
||||
"attached block", "(level::is number=1, keep return=false)",
|
||||
function(state, level, keep_return)
|
||||
-- level 2: env of the function that called the function that called attached block
|
||||
|
|
@ -25,6 +61,7 @@ return {
|
|||
end
|
||||
},
|
||||
{
|
||||
--- Same as the above function, but returns `default` if there is no attached block instead of throwing an error.
|
||||
"attached block", "(level::is number=1, keep return=false, default)",
|
||||
function(state, level, keep_return, default)
|
||||
-- level 2: env of the function that called the function that called attached block
|
||||
|
|
|
|||
|
|
@ -1,27 +1,40 @@
|
|||
---
|
||||
-- @titlelevel 3
|
||||
|
||||
local ast = require("anselme.ast")
|
||||
local Nil, String = ast.Nil, ast.String
|
||||
|
||||
return {
|
||||
{
|
||||
"print", "(a)",
|
||||
--- Print a human-readable string representation (using `format(val)`) of `val` to the console.
|
||||
"print", "(val)",
|
||||
function(state, a)
|
||||
print(a:format_custom(state))
|
||||
return Nil:new()
|
||||
end
|
||||
},
|
||||
{
|
||||
"hash", "(a)",
|
||||
--- Returns a human-readable string representation of `val`.
|
||||
--
|
||||
-- This function is called by string and text interpolations to convert the value returned by the interpolation to a string.
|
||||
--
|
||||
-- This generic version uses the internal Anselme formatter for all other values, which tries to generate a representation close to valid Anselme code.
|
||||
"format", "(val)",
|
||||
function(state, val)
|
||||
return String:new(val:format(state))
|
||||
end
|
||||
},
|
||||
{
|
||||
--- Returns a hash of `val`.
|
||||
--
|
||||
-- A hash is a string that uniquely represents the value. Two equal hashes mean the values are equal.
|
||||
"hash", "(val)",
|
||||
function(state, a)
|
||||
return String:new(a:hash())
|
||||
end
|
||||
},
|
||||
{
|
||||
--- Throw an error.
|
||||
"error", "(message=\"error\")",
|
||||
function(state, message)
|
||||
if message.type == "string" then message = message.string end
|
||||
|
|
|
|||
|
|
@ -1,11 +1,20 @@
|
|||
--- # Boolean
|
||||
-- @titlelevel 3
|
||||
|
||||
local ast = require("anselme.ast")
|
||||
local Boolean, ArgumentTuple = ast.Boolean, ast.ArgumentTuple
|
||||
|
||||
return {
|
||||
--- A boolean true value.
|
||||
{ "true", Boolean:new(true) },
|
||||
--- A boolean false value.
|
||||
{ "false", Boolean:new(false) },
|
||||
|
||||
{
|
||||
--- Equality operator.
|
||||
-- Returns true if `a` and `b` are equal, false otherwise.
|
||||
--
|
||||
-- Mutable values are compared by reference, immutable values are compared by value.
|
||||
"_==_", "(a, b)",
|
||||
function(state, a, b)
|
||||
if a.mutable ~= b.mutable then return Boolean:new(false)
|
||||
|
|
@ -15,6 +24,8 @@ return {
|
|||
end
|
||||
},
|
||||
{
|
||||
--- Inequality operator.
|
||||
-- Retrusn false if `a` and `b` are equal, true otherwise.
|
||||
"_!=_", "(a, b)",
|
||||
function(state, a, b)
|
||||
if a.mutable ~= b.mutable then return Boolean:new(true)
|
||||
|
|
@ -24,12 +35,16 @@ return {
|
|||
end
|
||||
},
|
||||
{
|
||||
--- Boolean not operator.
|
||||
-- Returns false if `a` is true, false otherwise.
|
||||
"!_", "(a)",
|
||||
function(state, a)
|
||||
return Boolean:new(not a:truthy())
|
||||
end
|
||||
},
|
||||
{
|
||||
--- Boolean lazy and operator.
|
||||
-- If `left` is truthy, evaluate `right` and returns it. Otherwise, returns left.
|
||||
"_&_", "(left, right)",
|
||||
function(state, left, right)
|
||||
if left:truthy() then
|
||||
|
|
@ -40,6 +55,8 @@ return {
|
|||
end
|
||||
},
|
||||
{
|
||||
--- Boolean lazy or operator.
|
||||
-- If `left` is truthy, returns it. Otherwise, evaluate `right` and returns it.
|
||||
"_|_", "(left, right)",
|
||||
function(state, left, right)
|
||||
if left:truthy() then
|
||||
|
|
|
|||
|
|
@ -1,3 +1,18 @@
|
|||
--- # Control flow
|
||||
--
|
||||
-- ```
|
||||
-- if(5 > 3)
|
||||
-- print("called")
|
||||
--
|
||||
-- if(3 > 5)
|
||||
-- print("not called")
|
||||
-- else if(1 > 5)
|
||||
-- print("not called")
|
||||
-- else!
|
||||
-- print("called")
|
||||
-- ```
|
||||
-- @titlelevel 3
|
||||
|
||||
local ast = require("anselme.ast")
|
||||
local ArgumentTuple, Nil, Boolean, Identifier, Return = ast.ArgumentTuple, ast.Nil, ast.Boolean, ast.Identifier, ast.Return
|
||||
|
||||
|
|
@ -18,6 +33,10 @@ end
|
|||
|
||||
return {
|
||||
{
|
||||
--- Call `expression` if `condition` is true.
|
||||
-- Returns the result of the call to `expression`, or nil if the condition was false.
|
||||
--
|
||||
-- If we are currently resuming to an anchor contained in `expression`, `expression` is called regardless of the condition result.
|
||||
"if", "(condition, expression=attached block(keep return=true))", function(state, condition, expression)
|
||||
ensure_if_variable(state)
|
||||
if condition:truthy() or expression:contains_current_resume_target(state) then
|
||||
|
|
@ -30,7 +49,11 @@ return {
|
|||
end
|
||||
},
|
||||
{
|
||||
"if", "(condition, true, false)", function(state, condition, if_true, if_false)
|
||||
--- Call `if true` if `condition` is true, `if false` otherwise.
|
||||
-- Return the result of the call.
|
||||
--
|
||||
-- If we are currently resuming to an anchor contained in `if true` or `if false`, `if true` or `if false` (respectively) is called regardless of the condition result.
|
||||
"if", "(condition, if true, if false)", function(state, condition, if_true, if_false)
|
||||
ensure_if_variable(state)
|
||||
if condition:truthy() or if_true:contains_current_resume_target(state) then
|
||||
set_if_variable(state, true)
|
||||
|
|
@ -42,6 +65,10 @@ return {
|
|||
end
|
||||
},
|
||||
{
|
||||
--- Call `expression` if `condition` is true and the last if, else if or else's condition was false, or the last while loop was never entered.
|
||||
-- Returns the result of the call to `expression`, or nil if not called.
|
||||
--
|
||||
-- If we are currently resuming to an anchor contained in `expression`, `expression` is called regardless of the condition result.
|
||||
"else if", "(condition, expression=attached block(keep return=true))",
|
||||
function(state, condition, expression)
|
||||
ensure_if_variable(state)
|
||||
|
|
@ -55,6 +82,10 @@ return {
|
|||
end
|
||||
},
|
||||
{
|
||||
--- Call `expression` if the last if, else if or else's condition was false, or the last while loop was never entered.
|
||||
-- Returns the result of the call to `expression`, or nil if not called.
|
||||
--
|
||||
-- If we are currently resuming to an anchor contained in `expression`, `expression` is called regardless of the condition result.
|
||||
"else", "(expression=attached block(keep return=true))",
|
||||
function(state, expression)
|
||||
ensure_if_variable(state)
|
||||
|
|
@ -68,6 +99,49 @@ return {
|
|||
},
|
||||
|
||||
{
|
||||
--- Call `condition`, if it returns a true value, call `expression`, and repeat until `condition` returns a false value.
|
||||
--
|
||||
-- Returns the value returned by the the last loop.
|
||||
-- If `condition` returns a false value on its first call, returns nil.
|
||||
--
|
||||
-- If a `continue` happens in the loop, the current iteration is stopped and skipped.
|
||||
-- If a `break` happens in the loop, the whole loop is stopped.
|
||||
--
|
||||
-- ```
|
||||
-- :i = 1
|
||||
-- while(i <= 5)
|
||||
-- print(i)
|
||||
-- i += 1
|
||||
-- // 1, 2, 3, 4, 5
|
||||
-- ```
|
||||
--
|
||||
-- ```
|
||||
-- :i = 1
|
||||
-- while(i <= 5)
|
||||
-- if(i == 3, break)
|
||||
-- print(i)
|
||||
-- i += 1
|
||||
-- // 1, 2
|
||||
-- ```
|
||||
--
|
||||
-- ```
|
||||
-- :i = 1
|
||||
-- while(i <= 5)
|
||||
-- if(i == 3, continue)
|
||||
-- print(i)
|
||||
-- i += 1
|
||||
-- // 1, 2, 4, 5
|
||||
-- ```
|
||||
--
|
||||
-- ```
|
||||
-- :i = 10
|
||||
-- while(i <= 5)
|
||||
-- print(i)
|
||||
-- i += 1
|
||||
-- else!
|
||||
-- print("the while loop was never entered")
|
||||
-- // the while loop was never entered
|
||||
-- ```
|
||||
"while", "(condition, expression=attached block(keep return=true))",
|
||||
function(state, condition, expression)
|
||||
ensure_if_variable(state)
|
||||
|
|
@ -96,6 +170,8 @@ return {
|
|||
end
|
||||
},
|
||||
{
|
||||
--- Returns a `break` return value, eventually with an associated value.
|
||||
-- This can be used to exit a loop.
|
||||
"break", "(value=())",
|
||||
function(state, val)
|
||||
if Return:is(val) then val = val.expression end
|
||||
|
|
@ -103,6 +179,8 @@ return {
|
|||
end
|
||||
},
|
||||
{
|
||||
--- Returns a `continue` return value, eventually with an associated value.
|
||||
-- This can be used to skip the current loop iteration.
|
||||
"continue", "(value=())",
|
||||
function(state, val)
|
||||
if Return:is(val) then val = val.expression end
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
--- # Environment
|
||||
-- @titlelevel 3
|
||||
|
||||
local ast = require("anselme.ast")
|
||||
local Nil, Boolean, Call, Quote = ast.Nil, ast.Boolean, ast.Call, ast.Quote
|
||||
local assert0 = require("anselme.common").assert0
|
||||
|
|
@ -5,7 +8,10 @@ local parser = require("anselme.parser")
|
|||
|
||||
return {
|
||||
{
|
||||
"defined", "(c::is environment, s::is string, search parent::is boolean=false)",
|
||||
--- Returns true if the variable named `var` is defined in in the environment `env`, false otherwise.
|
||||
--
|
||||
-- If `search parent` is true, this will also search in parent scopes of the environment `env`.
|
||||
"defined", "(env::is environment, var::is string, search parent::is boolean=false)",
|
||||
function(state, env, s, l)
|
||||
if l:truthy() then
|
||||
return Boolean:new(env:defined(state, s:to_identifier()))
|
||||
|
|
@ -16,6 +22,7 @@ return {
|
|||
},
|
||||
|
||||
{
|
||||
--- Gets the variable named `s` defined in the environment `c`.
|
||||
"_._", "(c::is environment, s::is string)",
|
||||
function(state, env, s)
|
||||
local identifier = s:to_identifier()
|
||||
|
|
@ -24,6 +31,7 @@ return {
|
|||
end
|
||||
},
|
||||
{
|
||||
--- Sets the variable named `s` defined in the environment `c` to `v`.
|
||||
"_._", "(c::is environment, s::is string) = v",
|
||||
function(state, env, s, v)
|
||||
local identifier = s:to_identifier()
|
||||
|
|
@ -33,6 +41,7 @@ return {
|
|||
end
|
||||
},
|
||||
{
|
||||
--- Define a new variable `s` in the environment `c` with the value `v`.
|
||||
"_._", "(c::is environment, s::is symbol) = v",
|
||||
function(state, env, s, v)
|
||||
state.scope:push(env)
|
||||
|
|
@ -43,6 +52,14 @@ return {
|
|||
},
|
||||
|
||||
{
|
||||
--- Get all the variables indicated in the tuple `symbol typle` from the environment `env`,
|
||||
-- and define them in the current scope.
|
||||
-- ```
|
||||
-- import(env, [:a, :b])
|
||||
-- // is the same as
|
||||
-- :a = env.a
|
||||
-- :b = env.b
|
||||
-- ```
|
||||
"import", "(env::is environment, symbol tuple::is tuple)",
|
||||
function(state, env, l)
|
||||
for _, sym in l:iter(state) do
|
||||
|
|
@ -52,6 +69,13 @@ return {
|
|||
end
|
||||
},
|
||||
{
|
||||
--- Get the variable `symbol` from the environment `env`,
|
||||
-- and define it in the current scope.
|
||||
-- ```
|
||||
-- import(env, :a)
|
||||
-- // is the same as
|
||||
-- :a = env.a
|
||||
-- ```
|
||||
"import", "(env::is environment, symbol::is symbol)",
|
||||
function(state, env, sym)
|
||||
Call:from_operator("_=_", Quote:new(sym), env:get(state, sym:to_identifier())):eval(state)
|
||||
|
|
@ -60,6 +84,8 @@ return {
|
|||
},
|
||||
|
||||
{
|
||||
--- Load an Anselme script from a file and run it.
|
||||
-- Returns the environment containing the exported variables from the file.
|
||||
"load", "(path::is string)",
|
||||
function(state, path)
|
||||
-- read file
|
||||
|
|
|
|||
|
|
@ -1,3 +1,43 @@
|
|||
--- ## For loops
|
||||
-- @titlelevel 3
|
||||
|
||||
--- Iterates over the elements of `var`: for each element, set the variable `symbol` in the function `block`'s environment and call it.
|
||||
--
|
||||
-- In order to get the elements of `var`, this calls `iter(var)` to obtain an iterator over var.
|
||||
-- An iterator is a function that, each time it is called, returns the next value given to the for loop. When the iterator returns nil, the loop ends.
|
||||
--
|
||||
-- ```
|
||||
-- :l = [1,2,3]
|
||||
-- // prints 1, 2, and 3
|
||||
-- for(:x, l)
|
||||
-- print(x)
|
||||
-- ```
|
||||
-- @title for (symbol::is symbol, var, block=attached block(keep return=true))
|
||||
|
||||
--- ## Ranges
|
||||
|
||||
--- Returns true if `val` is a range, false otherwise.
|
||||
-- @title is range (val)
|
||||
-- @defer value checking
|
||||
|
||||
--- Returns a new range, going from 1 to `stop` with a step of 1.
|
||||
-- @title range (stop::is number)
|
||||
|
||||
--- Returns a new range, going from `start` to `stop` with a step of `step`.
|
||||
-- @title range (start::is number, stop::is number, step::is number=1)
|
||||
|
||||
--- Returns an iterator that iterates over the range.
|
||||
-- For a range going from `start` to `stop` with a step of `step`, this means this will iterate over all the numbers `x` such that x = start + n⋅step with n ∈ N and x ≤ stop, starting from n = 0.
|
||||
-- @title iter (t::is range)
|
||||
|
||||
--- Returns an iterator that iterates over the elements of the sequence (a list or tuple).
|
||||
-- @title iter (t::is sequence)
|
||||
-- @defer structures
|
||||
|
||||
--- Returns an iterator that iterates over the keys of the table.
|
||||
-- @title iter (t::is table)
|
||||
-- @defer structures
|
||||
|
||||
return [[
|
||||
/* For loop */
|
||||
:@$for(symbol::is symbol, var, block=attached block(keep return=true))
|
||||
|
|
@ -13,9 +53,9 @@ return [[
|
|||
|
||||
/* Range iterables */
|
||||
:@is range = is("range")
|
||||
:@$range(stop)
|
||||
:@$range(stop::is number)
|
||||
[1, stop, 1]!type("range")
|
||||
:@$range(start, stop, step=1)
|
||||
:@$range(start::is number, stop::is number, step::is number=1)
|
||||
[start, stop, step]!type("range")
|
||||
:@$iter(range::is range)
|
||||
:v = range!value
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
--- # Function
|
||||
-- @titlelevel 3
|
||||
|
||||
local ast = require("anselme.ast")
|
||||
local Nil, Boolean, Call, Quote, Return, Overload, Overloadable = ast.Nil, ast.Boolean, ast.Call, ast.Quote, ast.Return, ast.Overload, ast.abstract.Overloadable
|
||||
local assert0 = require("anselme.common").assert0
|
||||
|
||||
return {
|
||||
{
|
||||
"defined", "(c::is function, s::is string, search parent::is boolean=false)",
|
||||
--- Returns true if the variable named `var` is defined in in the function `fn`'s scope, false otherwise.
|
||||
--
|
||||
-- If `search parent` is true, this will also search in parent scopes of the function scope.
|
||||
"defined", "(fn::is function, var::is string, search parent::is boolean=false)",
|
||||
function(state, c, s, l)
|
||||
if l:truthy() then
|
||||
return Boolean:new(c.scope:defined(state, s:to_identifier()))
|
||||
|
|
@ -15,6 +21,7 @@ return {
|
|||
},
|
||||
|
||||
{
|
||||
--- Creates and returns a new overload containing all the callables in sequence `l`.
|
||||
"overload", "(l::is sequence)",
|
||||
function(state, l)
|
||||
local r = Overload:new()
|
||||
|
|
@ -27,6 +34,7 @@ return {
|
|||
},
|
||||
|
||||
{
|
||||
--- Returns a copy of the function that keeps return values intact when returned, instead of only returning the associated value.
|
||||
"keep return", "(f::is function)",
|
||||
function(state, f)
|
||||
return f:without_return_boundary()
|
||||
|
|
@ -34,12 +42,16 @@ return {
|
|||
},
|
||||
|
||||
{
|
||||
--- Call `func` with the arguments in `args`, and returns the result.
|
||||
-- If pairs with a string name appear in `args`, they are interpreted as named arguments.
|
||||
"call", "(func, args::is tuple)",
|
||||
function(state, fn, args)
|
||||
return fn:call(state, args:to_argument_tuple())
|
||||
end
|
||||
},
|
||||
{
|
||||
--- Call `func` with the arguments in `args` and assignment argument `v`, and returns the result.
|
||||
-- If pairs with a string name appear in `args`, they are interpreted as named arguments.
|
||||
"call", "(func, args::is tuple) = v",
|
||||
function(state, fn, args, v)
|
||||
local argumenttuple = args:to_argument_tuple()
|
||||
|
|
@ -48,12 +60,16 @@ return {
|
|||
end
|
||||
},
|
||||
{
|
||||
--- Returns true if `func` can be called with arguments `args`.
|
||||
-- If pairs with a string name appear in `args`, they are interpreted as named arguments.
|
||||
"can dispatch", "(func, args::is tuple)",
|
||||
function(state, fn, args)
|
||||
return Boolean:new(not not fn:dispatch(state, args:to_argument_tuple()))
|
||||
end,
|
||||
},
|
||||
{
|
||||
--- Returns true if `func` can be called with arguments `args` and assignment argument `v`.
|
||||
-- If pairs with a string name appear in `args`, they are interpreted as named arguments.
|
||||
"can dispatch", "(func, args::is tuple) = v",
|
||||
function(state, fn, args, v)
|
||||
local argumenttuple = args:to_argument_tuple()
|
||||
|
|
@ -63,7 +79,8 @@ return {
|
|||
},
|
||||
|
||||
{
|
||||
"_._", "(c::is function, s::is string)",
|
||||
--- Returns the value of the variable `var` defined in the function `fn`'s scope.
|
||||
"_._", "(fn::is function, var::is string)",
|
||||
function(state, c, s)
|
||||
local identifier = s:to_identifier()
|
||||
assert0(c.scope:defined(state, identifier), ("no variable %q defined in closure"):format(s.string))
|
||||
|
|
@ -71,7 +88,8 @@ return {
|
|||
end
|
||||
},
|
||||
{
|
||||
"_._", "(c::is function, s::is string) = v",
|
||||
--- Sets the value of the variable `var` defined in the function `fn`'s scope to `v`.
|
||||
"_._", "(fn::is function, var::is string) = v",
|
||||
function(state, c, s, v)
|
||||
local identifier = s:to_identifier()
|
||||
assert0(c.scope:defined(state, identifier), ("no variable %q defined in closure"):format(s.string))
|
||||
|
|
@ -80,7 +98,8 @@ return {
|
|||
end
|
||||
},
|
||||
{
|
||||
"_._", "(c::is function, s::is symbol) = v",
|
||||
--- Define a variable `var` in the function `fn`'s scope with the value `v`.
|
||||
"_._", "(fn::is function, var::is symbol) = v",
|
||||
function(state, c, s, v)
|
||||
state.scope:push(c.scope)
|
||||
local r = Call:from_operator("_=_", Quote:new(s), v):eval(state)
|
||||
|
|
@ -90,6 +109,8 @@ return {
|
|||
},
|
||||
|
||||
{
|
||||
--- Returns a return value with an associated value `value`.
|
||||
-- This can be used to exit a function.
|
||||
"return", "(value=())",
|
||||
function(state, val)
|
||||
if Return:is(val) then val = val.expression end
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
-- TODO: doc in other language
|
||||
|
||||
return [[
|
||||
:@bloc attaché = stdlib.attached block
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,23 @@
|
|||
--- # Arithmetic and math functions
|
||||
--
|
||||
-- Comparaison operators are designed to be chained:
|
||||
-- ```
|
||||
-- 1 < 2 < 3
|
||||
-- // is parsed as
|
||||
-- (1 < 2) < 3
|
||||
-- // (1 < 2) returns 2, 2 < 3 returns 3 which is true
|
||||
-- ```
|
||||
-- @titlelevel 3
|
||||
|
||||
local ast = require("anselme.ast")
|
||||
local Boolean, Number = ast.Boolean, ast.Number
|
||||
|
||||
return {
|
||||
--- Pi.
|
||||
{ "pi", Number:new(math.pi) },
|
||||
|
||||
{
|
||||
--- Returns `b` if `a` < `b`, false otherwise.
|
||||
"_<_", "(a::is number, b::is number)",
|
||||
function(state, a, b)
|
||||
if a.number < b.number then return b
|
||||
|
|
@ -12,8 +25,10 @@ return {
|
|||
end
|
||||
end
|
||||
},
|
||||
--- Returns false.
|
||||
{ "_<_", "(a::is false, b::is number)", function(state, a, b) return Boolean:new(false) end },
|
||||
{
|
||||
--- Returns `b` if `a` <= `b`, false otherwise.
|
||||
"_<=_", "(a::is number, b::is number)",
|
||||
function(state, a, b)
|
||||
if a.number <= b.number then return b
|
||||
|
|
@ -21,8 +36,10 @@ return {
|
|||
end
|
||||
end
|
||||
},
|
||||
--- Returns false.
|
||||
{ "_<=_", "(a::is false, b::is number)", function(state, a, b) return Boolean:new(false) end },
|
||||
{
|
||||
--- Returns `b` if `a` > `b`, false otherwise.
|
||||
"_>_", "(a::is number, b::is number)",
|
||||
function(state, a, b)
|
||||
if a.number > b.number then return b
|
||||
|
|
@ -30,8 +47,10 @@ return {
|
|||
end
|
||||
end
|
||||
},
|
||||
--- Returns false.
|
||||
{ "_>_", "(a::is false, b::is number)", function(state, a, b) return Boolean:new(false) end },
|
||||
{
|
||||
--- Returns `b` if `a` >= `b`, false otherwise.
|
||||
"_>=_", "(a::is number, b::is number)",
|
||||
function(state, a, b)
|
||||
if a.number >= b.number then return b
|
||||
|
|
@ -39,12 +58,18 @@ return {
|
|||
end
|
||||
end
|
||||
},
|
||||
--- Returns false.
|
||||
{ "_>=_", "(a::is false, b::is number)", function(state, a, b) return Boolean:new(false) end },
|
||||
--- Returns `a` + `b`.
|
||||
{ "_+_", "(a::is number, b::is number)", function(state, a, b) return Number:new(a.number + b.number) end },
|
||||
--- Returns `a` - `b`.
|
||||
{ "_-_", "(a::is number, b::is number)", function(state, a, b) return Number:new(a.number - b.number) end },
|
||||
--- Returns `a` * `b`.
|
||||
{ "_*_", "(a::is number, b::is number)", function(state, a, b) return Number:new(a.number * b.number) end },
|
||||
--- Returns `a` / `b`.
|
||||
{ "_/_", "(a::is number, b::is number)", function(state, a, b) return Number:new(a.number / b.number) end },
|
||||
{
|
||||
--- Returns the integer division of `a` by `b`.
|
||||
"div", "(a::is number, b::is number)", function(state, a, b)
|
||||
local r = a.number / b.number
|
||||
if r < 0 then
|
||||
|
|
@ -54,18 +79,29 @@ return {
|
|||
end
|
||||
end
|
||||
},
|
||||
--- Returns the modulo of `a` by `b`.
|
||||
{ "_%_", "(a::is number, b::is number)", function(state, a, b) return Number:new(a.number % b.number) end },
|
||||
--- Returns `a` to the power of `b`.
|
||||
{ "_^_", "(a::is number, b::is number)", function(state, a, b) return Number:new(a.number ^ b.number) end },
|
||||
--- Returns the negative of `a`.
|
||||
{ "-_", "(a::is number)", function(state, a) return Number:new(-a.number) end },
|
||||
--- Returns `a`.
|
||||
{ "+_", "(a::is number)", function(state, a) return a end },
|
||||
|
||||
--- Returns a random integer number with uniform distribution in [`min`, `max`].
|
||||
{ "rand", "(min::is number, max::is number)", function(state, min, max) return Number:new(math.random(min.number, max.number)) end },
|
||||
--- Returns a random integer number with uniform distribution in [1, `max`].
|
||||
{ "rand", "(max::is number)", function(state, max) return Number:new(math.random(max.number)) end },
|
||||
--- Returns a random float number with uniform distribution in [0,1).
|
||||
{ "rand", "()", function(state) return Number:new(math.random()) end },
|
||||
|
||||
--- Returns the largest integral value less than or equal to `x`.
|
||||
{ "floor", "(x::is number)", function(state, x) return Number:new(math.floor(x.number)) end },
|
||||
--- Returns the smallest integral value greater than or equal to `x`.
|
||||
{ "ceil", "(x::is number)", function(state, x) return Number:new(math.ceil(x.number)) end },
|
||||
{
|
||||
--- Returns `x` rounded to the nearest integer.
|
||||
-- If `increment` > 1, rounds to the nearest float with `increment` decimals.
|
||||
"round", "(x::is number, increment=1)",
|
||||
function(state, x, increment)
|
||||
local n = x.number / increment.number
|
||||
|
|
@ -77,21 +113,40 @@ return {
|
|||
end
|
||||
},
|
||||
|
||||
--- Returns the square root of `x`.
|
||||
{ "sqrt", "(x::is number)", function(state, x) return Number:new(math.sqrt(x.number)) end },
|
||||
|
||||
--- Returns the absolute value of `x`.
|
||||
{ "abs", "(x::is number)", function(state, x) return Number:new(math.abs(x.number)) end },
|
||||
|
||||
--- Returns the exponential of `x`.
|
||||
{ "exp", "(x::is number)", function(state, x) return Number:new(math.exp(x.number)) end },
|
||||
--- Returns the natural logarithm of `x`.
|
||||
{ "log", "(x::is number)", function(state, x) return Number:new(math.log(x.number)) end },
|
||||
--- Returns the logarithm in base `base` of `x`.
|
||||
{ "log", "(x::is number, base::is number)", function(state, x, base) return Number:new(math.log(x.number, base.number)) end },
|
||||
|
||||
--- Convert `x` from radian to degrees.
|
||||
{ "deg", "(x::is number)", function(state, x) return Number:new(math.deg(x.number)) end },
|
||||
--- Convert `x` from degrees to radians.
|
||||
{ "rad", "(x::is number)", function(state, x) return Number:new(math.rad(x.number)) end },
|
||||
|
||||
--- ## Trigonometric functions
|
||||
--
|
||||
-- All triginometric functions take and return angles in radians.
|
||||
|
||||
--- Returns the cosinus of `x`.
|
||||
{ "cos", "(x::is number)", function(state, x) return Number:new(math.cos(x.number)) end },
|
||||
--- Returns the sinus of `x`.
|
||||
{ "sin", "(x::is number)", function(state, x) return Number:new(math.sin(x.number)) end },
|
||||
--- Returns the tagent of `x`.
|
||||
{ "tan", "(x::is number)", function(state, x) return Number:new(math.tan(x.number)) end },
|
||||
--- Returns the arc cosinus of `x`.
|
||||
{ "acos", "(x::is number)", function(state, x) return Number:new(math.acos(x.number)) end },
|
||||
--- Returns the arc sinus of `x`.
|
||||
{ "asin", "(x::is number)", function(state, x) return Number:new(math.asin(x.number)) end },
|
||||
--- Returns the arc tangent of `x`.
|
||||
{ "atan", "(x::is number)", function(state, x) return Number:new(math.atan(x.number)) end },
|
||||
--- Returns the arc tangent of `x` / `y`, taking the signs of both arguments into account to find the correct quandrant (see [atan2](https://en.wikipedia.org/wiki/Atan2)).
|
||||
{ "atan", "(y::is number, x::is number)", function(state, y, x) return Number:new((math.atan2 or math.atan)(y.number, x.number)) end },
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,29 @@
|
|||
--- # Pairs
|
||||
-- @titlelevel 3
|
||||
|
||||
local ast = require("anselme.ast")
|
||||
local Pair = ast.Pair
|
||||
|
||||
return {
|
||||
--- Returns a new pair with name `name` and value `value`.
|
||||
--
|
||||
-- Note that if the left expression is an identifier, it is parsed as a string.
|
||||
-- ```
|
||||
-- name: 42
|
||||
-- // is the same as
|
||||
-- "name": 42
|
||||
-- ```
|
||||
{ "_:_", "(name, value)", function(state, a, b) return Pair:new(a,b) end },
|
||||
|
||||
{
|
||||
--- Returns the pair `pair`'s name.
|
||||
"name", "(pair::is pair)",
|
||||
function(state, pair)
|
||||
return pair.name
|
||||
end
|
||||
},
|
||||
{
|
||||
--- Returns the pair `pair`'s value.
|
||||
"value", "(pair::is pair)",
|
||||
function(state, pair)
|
||||
return pair.value
|
||||
|
|
|
|||
|
|
@ -1,3 +1,17 @@
|
|||
--- # Persistence helpers
|
||||
--
|
||||
-- Theses functions store and retrieve data from persistent storage.
|
||||
-- Persistent storage is a key-value store intended to be saved and loaded alongside the host game's save files.
|
||||
-- See the [relatied Lua API methods](api.md#saving_and_loading_persistent_variables) for how to retrieve and load the persistent data.
|
||||
--
|
||||
-- A persistent value can be accessed like a regular variable using aliases and the warp operator:
|
||||
-- ```
|
||||
-- :&var => persist("name", "Hero") // persistent value with key "name" and default value "Hero"
|
||||
-- print(var) // gets persistent value "name": "Hero"
|
||||
-- var = "Link" // sets persistent value "name" to "Link"
|
||||
-- ```
|
||||
-- @titlelevel 3
|
||||
|
||||
local ast = require("anselme.ast")
|
||||
local Nil = ast.Nil
|
||||
|
||||
|
|
@ -5,12 +19,15 @@ local persistent_manager = require("anselme.state.persistent_manager")
|
|||
|
||||
return {
|
||||
{
|
||||
--- Returns the value associated with the key `key` in persistent storage.
|
||||
-- If the key is not defined, returns `default`.
|
||||
"persist", "(key, default)",
|
||||
function(state, key, default)
|
||||
return persistent_manager:get(state, key, default)
|
||||
end
|
||||
},
|
||||
{
|
||||
--- Sets the value associated with the key `key` in persistent storage to `value`.
|
||||
"persist", "(key, default) = value",
|
||||
function(state, key, default, value)
|
||||
persistent_manager:set(state, key, value)
|
||||
|
|
@ -18,12 +35,15 @@ return {
|
|||
end
|
||||
},
|
||||
{
|
||||
--- Returns the value associated with the key `key` in persistent storage.
|
||||
-- If the key is not defined, raise an error.
|
||||
"persist", "(key)",
|
||||
function(state, key)
|
||||
return persistent_manager:get(state, key)
|
||||
end
|
||||
},
|
||||
{
|
||||
--- Sets the value associated with the key `key` in persistent storage to `value`.
|
||||
"persist", "(key) = value",
|
||||
function(state, key, value)
|
||||
persistent_manager:set(state, key, value)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,30 @@
|
|||
--- ## Resuming functions
|
||||
--
|
||||
-- Instead of starting from the beginning of the function expression each time, functions can be started from any anchor anchor literal present in the function expression using the functions resuming functions described below.
|
||||
--
|
||||
-- ```
|
||||
-- :$f
|
||||
-- print(1)
|
||||
-- #anchor
|
||||
-- print(2)
|
||||
-- f!from(#anchor) // 2
|
||||
-- f! // 1, 2
|
||||
-- ```
|
||||
--
|
||||
-- To execute a function from an anchor, or _resuming_ a function, Anselme, when evaluating a block, simply skip any line that does not contain the anchor literal (either in the line itself or its attached block) until we reach the anchor.
|
||||
--
|
||||
-- ```
|
||||
-- :$f
|
||||
-- print("not run")
|
||||
-- (print("run"), _)
|
||||
-- print("not run")
|
||||
-- #anchor
|
||||
-- print("run")
|
||||
-- print("run")
|
||||
-- f!from(#anchor)
|
||||
-- ```
|
||||
-- @titlelevel 3
|
||||
|
||||
local ast = require("anselme.ast")
|
||||
local ArgumentTuple, Boolean, Nil = ast.ArgumentTuple, ast.Boolean, ast.Nil
|
||||
|
||||
|
|
@ -7,18 +34,23 @@ local calling_environment_manager = require("anselme.state.calling_environment_m
|
|||
|
||||
return {
|
||||
{
|
||||
--- Call the function `function` with no arguments, starting from the anchor `anchor`.
|
||||
"from", "(function::is function, anchor::is anchor)",
|
||||
function(state, func, anchor)
|
||||
return func:resume(state, anchor)
|
||||
end
|
||||
},
|
||||
{
|
||||
"from", "(function::is function, anchor::is nil)",
|
||||
--- Call the function `function` with no arguments, starting from the beginning.
|
||||
"from", "(function::is function, anchor::is nil=())",
|
||||
function(state, func)
|
||||
return func:call(state, ArgumentTuple:new())
|
||||
end
|
||||
},
|
||||
{
|
||||
--- Returns true if we are currently resuming the function call (i.e. the function started from a anchor instead of its beginning).
|
||||
--
|
||||
-- `level` indicates the position on the call stack where the resuming status should be checked. 0 is where `resuming` was called, 1 is where the function calling `resuming` was called, 2 is where the function calling the function that called `resuming` is called, etc.
|
||||
"resuming", "(level::is number=0)",
|
||||
function(state, level)
|
||||
local env = calling_environment_manager:get_level(state, level:to_lua(state))
|
||||
|
|
@ -29,12 +61,16 @@ return {
|
|||
end
|
||||
},
|
||||
{
|
||||
--- Returns the current resuming target (an anchor).
|
||||
"resume target", "()",
|
||||
function(state)
|
||||
return resume_manager:get(state)
|
||||
end
|
||||
},
|
||||
{
|
||||
--- Merge all variables defined or changed in the branch back into the parent branch.
|
||||
--
|
||||
-- If `complete flush` is true, all waiting events will be flushed until no events remain before merging the state.
|
||||
"merge branch", "(complete flush=true)",
|
||||
function(state, complete_flush)
|
||||
if complete_flush:truthy() then
|
||||
|
|
|
|||
|
|
@ -1,3 +1,84 @@
|
|||
--- # Scripts
|
||||
--
|
||||
-- Scripts extends on functions to provide tracking and persistence features useful for game dialogs:
|
||||
--
|
||||
-- * checkpoints allow scripts to be restarted from specific points when they are interrupted or restarted;
|
||||
-- * tracking of reached status of anchors to be able to know what has already been shown to the player;
|
||||
-- * helper functions to call scripts in common patterns.
|
||||
--
|
||||
-- ```
|
||||
-- :hello = "hello"!script
|
||||
-- | Hello...
|
||||
-- #midway!checkpoint
|
||||
-- | Let's resume. Hello...
|
||||
-- | ...world!
|
||||
-- hello! // Hello..., ...world!
|
||||
-- hello! // Let's resume. Hello..., ...world!
|
||||
-- print(hello.reached(#midway)) // 1
|
||||
-- print(hello.run) // 2
|
||||
-- print(hello.current checkpoint) // #midway
|
||||
-- ```
|
||||
-- @titlelevel 3
|
||||
|
||||
--- Creates and returns a new script.
|
||||
--
|
||||
-- `name` is the script identifier (typically a string), which is used as a prefix for the persistent storage keys of the script variables. This means that for the script variables to be stored and retrieved properly from a game save, the script name must stays the same and be unique for each script.
|
||||
--
|
||||
-- `fn` is the function that will be run when the script is called.
|
||||
--
|
||||
-- Some variables are defined into the script/`fn` scope. They are all stored from persistent storage, using the script name as part of their persistent key:
|
||||
--
|
||||
-- * `current checkpoint` is the currently set checkpoint (an anchor);
|
||||
-- * `reached` is a table of *{ #anchor = number, ... } which associates to an anchor the number of times it was reached (see `check` and `checkpoint`);
|
||||
-- * `run` is the number of times the script was successfully called.
|
||||
--
|
||||
-- As well as functions defined in the script scope:
|
||||
--
|
||||
-- * `check (anchor::is anchor)` increment by 1 the number of times `anchor` was reached in `reached`;
|
||||
-- * `checkpoint (anchor::is anchor, on resume=attached block(default=()))` sets the current checkpoint to `anchor`, increment by 1 the number of times `anchor` was reached in `reached`, and merge the current branch state into the parent branch. If we are currently resuming to `anchor`, instead this only calls `on resume!` and keep resuming the script from the anchor.
|
||||
-- @title script (name, fn=attached block!)
|
||||
|
||||
--- Returns true if `x` is a script, false otherwise.
|
||||
-- @title is script (x)
|
||||
-- @defer value checking
|
||||
|
||||
--- Run the script `s`.
|
||||
--
|
||||
-- If a checkpoint is set, resume the script from this checkpoint.
|
||||
-- Otherwise, run the script from the beginning.
|
||||
-- `s.run` is incremented by 1 after it is run.
|
||||
-- @title s::is script !
|
||||
|
||||
--- Returns the value of the variable `k` defined in the scripts `s`'s scope.
|
||||
-- @title s::is script . k::is string
|
||||
|
||||
--- Sets the value of the variable `k` defined in the scripts `s`'s scope to `val`.
|
||||
-- @title s::is script . k::is string = val
|
||||
|
||||
--- Define the variable `k` in the scripts `s`'s scope with the value `val`.
|
||||
-- @title s::is script . k::is symbol = val
|
||||
|
||||
--- Resume the script `s` from anchor `a`, setting it as the current checkpoint.
|
||||
-- @title from (s::is script, a::is anchor)
|
||||
|
||||
--- Run the script `s` from its beginning, discarding any current checkpoint.
|
||||
-- @title from (s::is script, anchor::is nil=())
|
||||
|
||||
--- Run the first script in the the tuple `l` with a `run` variable strictly lower than the first element, or the first element if it has the lowest `run`.
|
||||
--
|
||||
-- This means that, if the scripts are only called through `cycle`, the scripts in the tuple `l` will be called in a cycle:
|
||||
-- when `cycle` is first called the 1st script is called, then the 2nd, ..., then the last, and then looping back to the 1st.
|
||||
-- @title cycle (l::is tuple)
|
||||
|
||||
--- Run the first script in the tuple `l` with a `run` that is 0, or the last element if there is no such script.
|
||||
--
|
||||
-- This means that, if the scripts are only called through `next`, the scripts in the tuple `l` will be called in order:
|
||||
-- when `next` is first called the 1st script is called, then the 2nd, ..., then the last, and then will keep calling the last element.
|
||||
-- @title next (l::is tuple)
|
||||
|
||||
--- Run a random script from the typle `l`.
|
||||
-- @title random (l::is tuple)
|
||||
|
||||
return [[
|
||||
:@ script = $(name, fn=attached block!)
|
||||
fn.:¤t checkpoint => "{name}.checkpoint"!persist(false)
|
||||
|
|
@ -7,7 +88,7 @@ return [[
|
|||
:resume target = ()
|
||||
|
||||
fn.:check = $(anchor::is anchor)
|
||||
fn.reached(anchor) = fn.reached(anchor, 0) + 1
|
||||
fn.reached(anchor, 0) += 1
|
||||
fn.:checkpoint = $(anchor::is anchor, on resume=attached block(default=()))
|
||||
:resuming = resuming(1) /* calling function is resuming */
|
||||
if(on resume)
|
||||
|
|
@ -15,12 +96,12 @@ return [[
|
|||
if(resume target == anchor | resuming)
|
||||
on resume!
|
||||
else!
|
||||
fn.reached(anchor) = fn.reached(anchor, 0) + 1
|
||||
fn.reached(anchor, 0) += 1
|
||||
merge branch!
|
||||
else!
|
||||
fn.current checkpoint = anchor
|
||||
if(resume target != anchor)
|
||||
fn.reached(anchor) = fn.reached(anchor, 0) + 1
|
||||
fn.reached(anchor, 0) += 1
|
||||
merge branch!
|
||||
|
||||
:f = $
|
||||
|
|
@ -49,7 +130,7 @@ return [[
|
|||
:@$ from(s::is script, a::is anchor)
|
||||
s.current checkpoint = a
|
||||
return(s!)
|
||||
:@$ from(s::is script)
|
||||
:@$ from(s::is script, anchor::is nil=())
|
||||
s.current checkpoint = ()
|
||||
return(s!)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,23 @@
|
|||
--- # Strings
|
||||
-- @titlelevel 3
|
||||
|
||||
local utf8 = utf8 or require("lua-utf8")
|
||||
local ast = require("anselme.ast")
|
||||
local String, Number = ast.String, ast.Number
|
||||
|
||||
return {
|
||||
--- Concatenate two strings and return the result as a new string.
|
||||
{ "_+_", "(a::is string, b::is string)", function(state, a, b) return String:new(a.string .. b.string) end },
|
||||
{
|
||||
--- Returns the length of the string `s`.
|
||||
"len", "(s::is string)",
|
||||
function(state, s)
|
||||
return Number:new(utf8.len(s.string))
|
||||
end
|
||||
},
|
||||
{
|
||||
--- Return the same string.
|
||||
-- See [format](#format-val) for details on the format function.
|
||||
"format", "(val::is string)",
|
||||
function(state, val)
|
||||
return val
|
||||
|
|
|
|||
|
|
@ -1,27 +1,53 @@
|
|||
--- # Structures
|
||||
--
|
||||
-- Anselme offers:
|
||||
-- * indexed structures: tuple (immutable) and list (mutable)
|
||||
-- * dictionary structures: struct (immutable) and table (mutable)
|
||||
--
|
||||
-- ```
|
||||
-- :tuple = ["a","b",42]
|
||||
-- tuple(2) // "b"
|
||||
--
|
||||
-- :list = *["a","b",42]
|
||||
-- list(2) = "c"
|
||||
--
|
||||
-- :struct = { a: 42, 2: "b" }
|
||||
-- struct("a") // 42
|
||||
--
|
||||
-- :table = *{ a: 42, 2: "b" }
|
||||
-- table(2) = "c"
|
||||
-- ```
|
||||
--
|
||||
-- @titlelevel 3
|
||||
|
||||
local ast = require("anselme.ast")
|
||||
local Nil, List, Table, Number, LuaCall, ParameterTuple, Boolean = ast.Nil, ast.List, ast.Table, ast.Number, ast.LuaCall, ast.ParameterTuple, ast.Boolean
|
||||
|
||||
return {
|
||||
-- tuple
|
||||
{
|
||||
--- Create a list from the tuple.
|
||||
"*_", "(t::is tuple)",
|
||||
function(state, tuple)
|
||||
return List:new(state, tuple)
|
||||
end
|
||||
},
|
||||
{
|
||||
--- Returns the `i`-th element of the tuple.
|
||||
"_!", "(l::is tuple, i::is number)",
|
||||
function(state, l, i)
|
||||
return l:get(i.number)
|
||||
end
|
||||
},
|
||||
{
|
||||
--- Returns the length of the tuple.
|
||||
"len", "(l::is tuple)",
|
||||
function(state, l)
|
||||
return Number:new(l:len())
|
||||
end
|
||||
},
|
||||
{
|
||||
--- Returns the index of the `value` element in the tuple. If `value` is not in the tuple, returns nil.
|
||||
"find", "(l::is tuple, value)",
|
||||
function(state, l, v)
|
||||
local i = l:find(v)
|
||||
|
|
@ -35,12 +61,14 @@ return {
|
|||
|
||||
-- list
|
||||
{
|
||||
--- Returns the `i`-th element of the list.
|
||||
"_!", "(l::is list, i::is number)",
|
||||
function(state, l, i)
|
||||
return l:get(state, i.number)
|
||||
end
|
||||
},
|
||||
{
|
||||
--- Set the `i`-th element of the list to `value`.
|
||||
"_!", "(l::is list, i::is number) = value",
|
||||
function(state, l, i, v)
|
||||
l:set(state, i.number, v)
|
||||
|
|
@ -48,12 +76,14 @@ return {
|
|||
end
|
||||
},
|
||||
{
|
||||
--- Returns the length of the list.
|
||||
"len", "(l::is list)",
|
||||
function(state, l)
|
||||
return Number:new(l:len(state))
|
||||
end
|
||||
},
|
||||
{
|
||||
--- Returns the index of the `value` element in the list. If `value` is not in the list, returns nil.
|
||||
"find", "(l::is list, value)",
|
||||
function(state, l, v)
|
||||
local i = l:find(state, v)
|
||||
|
|
@ -65,6 +95,7 @@ return {
|
|||
end
|
||||
},
|
||||
{
|
||||
--- Insert a new value `value` at the end of the list.
|
||||
"insert", "(l::is list, value)",
|
||||
function(state, l, v)
|
||||
l:insert(state, v)
|
||||
|
|
@ -72,13 +103,15 @@ return {
|
|||
end
|
||||
},
|
||||
{
|
||||
"insert", "(l::is list, position::is number, value)",
|
||||
--- Insert a new value `value` at the `i`-th position in list, shifting the `i`, `i`+1, etc. elements by one.
|
||||
"insert", "(l::is list, i::is number, value)",
|
||||
function(state, l, position, v)
|
||||
l:insert(state, position.number, v)
|
||||
return Nil:new()
|
||||
end
|
||||
},
|
||||
{
|
||||
--- Remove the last element of the list.
|
||||
"remove", "(l::is list)",
|
||||
function(state, l)
|
||||
l:remove(state)
|
||||
|
|
@ -86,13 +119,15 @@ return {
|
|||
end
|
||||
},
|
||||
{
|
||||
"remove", "(l::is list, position::is number)",
|
||||
--- Remove the `i`-th element of the list, shifting the `i`, `i`+1, etc. elements by minus one.
|
||||
"remove", "(l::is list, i::is number)",
|
||||
function(state, l, position)
|
||||
l:remove(state, position.number)
|
||||
return Nil:new()
|
||||
end
|
||||
},
|
||||
{
|
||||
--- Returns a tuple with the same content as this list.
|
||||
"to tuple", "(l::is list)",
|
||||
function(state, l)
|
||||
return l:to_tuple(state)
|
||||
|
|
@ -101,18 +136,22 @@ return {
|
|||
|
||||
-- struct
|
||||
{
|
||||
--- Create a table from the struct.
|
||||
"*_", "(s::is struct)",
|
||||
function(state, struct)
|
||||
return Table:new(state, struct)
|
||||
end
|
||||
},
|
||||
{
|
||||
--- Returns the value associated with `key` in the struct.
|
||||
"_!", "(s::is struct, key)",
|
||||
function(state, s, k)
|
||||
return s:get(k)
|
||||
end
|
||||
},
|
||||
{
|
||||
--- Returns the value associated with `key` in the struct.
|
||||
-- If the `key` is not present in the struct, returns `default` instead.
|
||||
"_!", "(s::is struct, key, default)",
|
||||
function(state, s, k, default)
|
||||
if s:has(k) then
|
||||
|
|
@ -123,12 +162,14 @@ return {
|
|||
end
|
||||
},
|
||||
{
|
||||
--- Returns true if the struct contains the key `key`, false otherwise.
|
||||
"has", "(s::is struct, key)",
|
||||
function(state, s, k)
|
||||
return Boolean:new(s:has(k))
|
||||
end
|
||||
},
|
||||
{
|
||||
--- Returns an iterator over the keys of the struct.
|
||||
"iter", "(s::is struct)",
|
||||
function(state, struct)
|
||||
local iter = struct:iter()
|
||||
|
|
@ -142,12 +183,15 @@ return {
|
|||
|
||||
-- table
|
||||
{
|
||||
--- Returns the value associated with `key` in the table.
|
||||
"_!", "(t::is table, key)",
|
||||
function(state, t, key)
|
||||
return t:get(state, key)
|
||||
end
|
||||
},
|
||||
{
|
||||
--- Returns the value associated with `key` in the table.
|
||||
-- If the `key` is not present in the table, returns `default` instead.
|
||||
"_!", "(t::is table, key, default)",
|
||||
function(state, t, key, default)
|
||||
if t:has(state, key) then
|
||||
|
|
@ -158,6 +202,8 @@ return {
|
|||
end
|
||||
},
|
||||
{
|
||||
--- Sets the value associated with `key` in the table to `value`, creating it if not present
|
||||
-- If `value` is nil, deletes the entry in the table.
|
||||
"_!", "(t::is table, key) = value",
|
||||
function(state, t, key, value)
|
||||
t:set(state, key, value)
|
||||
|
|
@ -165,12 +211,23 @@ return {
|
|||
end
|
||||
},
|
||||
{
|
||||
--- Sets the value associated with `key` in the table to `value`, creating it if not present
|
||||
-- If `value` is nil, deletes the entry in the table.
|
||||
"_!", "(t::is table, key, default) = value",
|
||||
function(state, t, key, default, value)
|
||||
t:set(state, key, value)
|
||||
return Nil:new()
|
||||
end
|
||||
},
|
||||
{
|
||||
--- Returns true if the table contains the key `key`, false otherwise.
|
||||
"has", "(t::is table, key)",
|
||||
function(state, t, k)
|
||||
return Boolean:new(t:has(state, k))
|
||||
end
|
||||
},
|
||||
{
|
||||
--- Returns a struct with the same content as this table.
|
||||
"to struct", "(t::is table)",
|
||||
function(state, t)
|
||||
return t:to_struct(state)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
--- # Symbols
|
||||
-- @titlelevel 3
|
||||
|
||||
return {
|
||||
{
|
||||
--- Return a string of the symbol name.
|
||||
"to string", "(symbol::is symbol)",
|
||||
function(state, sym)
|
||||
return sym:to_string()
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
--- # Tagging
|
||||
-- @titlelevel 3
|
||||
|
||||
local ast = require("anselme.ast")
|
||||
local Tuple, Table, Struct, ArgumentTuple, Nil = ast.Tuple, ast.Table, ast.Struct, ast.ArgumentTuple, ast.Nil
|
||||
|
||||
|
|
@ -5,6 +8,15 @@ local tag_manager = require("anselme.state.tag_manager")
|
|||
|
||||
return {
|
||||
{
|
||||
--- Add the tags from `tags` to the tag stack while calling `expression`.
|
||||
--
|
||||
-- `tags` can be:
|
||||
--
|
||||
-- * a tuple of tags
|
||||
-- * a struct of tags
|
||||
-- * a table of tags
|
||||
-- * nil, for no new tags
|
||||
-- * any other value, for a single tag
|
||||
"_#_", "(tags, expression)",
|
||||
function(state, tags, expression)
|
||||
local tags_struct
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
--- # Text
|
||||
-- @titlelevel 3
|
||||
|
||||
local ast = require("anselme.ast")
|
||||
local Nil, Choice, PartialScope, ArgumentTuple, Identifier, Text = ast.Nil, ast.Choice, ast.PartialScope, ast.ArgumentTuple, ast.Identifier, ast.Text
|
||||
|
||||
|
|
@ -9,6 +12,7 @@ local resume_manager = require("anselme.state.resume_manager")
|
|||
return {
|
||||
-- text
|
||||
{
|
||||
--- Concatenate two texts, returning a new text value.
|
||||
"_+_", "(a::is text, b::is text)",
|
||||
function(state, a, b)
|
||||
local r = Text:new()
|
||||
|
|
@ -22,6 +26,7 @@ return {
|
|||
end
|
||||
},
|
||||
{
|
||||
--- Write a text event in the event buffer using this text.
|
||||
"_!", "(txt::is text)",
|
||||
function(state, text)
|
||||
event_manager:write(state, text)
|
||||
|
|
@ -29,6 +34,7 @@ return {
|
|||
end
|
||||
},
|
||||
{
|
||||
--- Create and return a new text from `text`, with the tags from `tags` added.
|
||||
"tag", "(txt::is text, tags::is struct)",
|
||||
function(state, text, tags)
|
||||
return text:with_tags(tags)
|
||||
|
|
@ -37,6 +43,17 @@ return {
|
|||
|
||||
-- choice
|
||||
{
|
||||
--- Write a choice event to the event buffer using this text and `fn` as the function to call if the choice is selected.
|
||||
--
|
||||
-- The same function is also defined in the `*_` operator:
|
||||
-- ```
|
||||
-- *| Choice
|
||||
-- 42
|
||||
-- // is the same as
|
||||
-- write choice(| Choice |, $42)
|
||||
-- ```
|
||||
--
|
||||
-- If we are currently resuming to an anchor contained in `fn`, `fn` is directly called and the current choice event buffer will be discarded on flush, simulating the choice event buffer being sent to the host game and this choice being selected.
|
||||
"write choice", "(text::is text, fn=attached block(keep return=true, default=($()())))",
|
||||
function(state, text, func)
|
||||
if func:contains_current_resume_target(state) then
|
||||
|
|
@ -52,6 +69,8 @@ return {
|
|||
|
||||
-- translation
|
||||
{
|
||||
--- Add a translation so `original` is replaced with `translated`.
|
||||
-- @title original -> translated
|
||||
"_->_", "(original::is(\"quote\"), translated::is(\"quote\"))",
|
||||
function(state, original, translated)
|
||||
local exp = PartialScope:preserve(state, translated.expression, Identifier:new("_"))
|
||||
|
|
|
|||
|
|
@ -1,8 +1,14 @@
|
|||
--- # Typed values
|
||||
-- @titlelevel 3
|
||||
|
||||
local ast = require("anselme.ast")
|
||||
local ArgumentTuple, String, Typed, Boolean = ast.ArgumentTuple, ast.String, ast.Typed, ast.Boolean
|
||||
|
||||
return {
|
||||
{
|
||||
--- Call `check(value)` and error if it returns a false value.
|
||||
-- This can be used to ensure a value checking function is verified on a value.
|
||||
-- @defer value checking
|
||||
"_::_", "(value, check)",
|
||||
function(state, value, check)
|
||||
local r = check:call(state, ArgumentTuple:new(value))
|
||||
|
|
@ -15,12 +21,17 @@ return {
|
|||
},
|
||||
|
||||
{
|
||||
--- Returns true if `value` is a typed value, false otherwise.
|
||||
"is typed", "(value)",
|
||||
function(state, v)
|
||||
return Boolean:new(v.type == "typed")
|
||||
end,
|
||||
},
|
||||
{
|
||||
--- Returns the type of `value`.
|
||||
--
|
||||
-- If `value` is a typed value, returns its associated type.
|
||||
-- Otherwise, returns a string of its type (`"string"`, `"number"`, etc.).
|
||||
"type", "(value)",
|
||||
function(state, v)
|
||||
if v.type == "typed" then
|
||||
|
|
@ -31,12 +42,17 @@ return {
|
|||
end
|
||||
},
|
||||
{
|
||||
--- Returns a new typed value with value `value` and type `type`.
|
||||
"type", "(value, type)",
|
||||
function(state, v, t)
|
||||
return Typed:new(v, t)
|
||||
end
|
||||
},
|
||||
{
|
||||
--- Returns the value of `value`.
|
||||
--
|
||||
-- If `value` is a typed value, returns its associated value.
|
||||
-- Otherwise, returns a `value` directly.
|
||||
"value", "(value)",
|
||||
function(state, v)
|
||||
if v.type == "typed" then
|
||||
|
|
|
|||
|
|
@ -1,6 +1,71 @@
|
|||
---# Value checking
|
||||
--
|
||||
-- See the [language reference](language.md#value_checking) for information on how value checking functions works.
|
||||
-- @titlelevel 3
|
||||
|
||||
--- Returns a function `$(x)` that returns true if `x` is of type `t`, false otherwise.
|
||||
-- @title is (t)
|
||||
|
||||
--- Returns a function `$(y)` that returns true if `x` is equal to `y`, false otherwise.
|
||||
-- @title is equal (x)
|
||||
|
||||
--- Returns a true if `x` is a nil, false otherwise.
|
||||
-- @title is nil (x)
|
||||
|
||||
--- Returns a true if `x` is a number, false otherwise.
|
||||
-- @title is number (x)
|
||||
|
||||
--- Returns a true if `x` is a string, false otherwise.
|
||||
-- @title is string (x)
|
||||
|
||||
--- Returns a true if `x` is a boolean, false otherwise.
|
||||
-- @title is boolean (x)
|
||||
|
||||
--- Returns a true if `x` is false, false otherwise.
|
||||
-- @title is false (x)
|
||||
|
||||
--- Returns a true if `x` is true, false otherwise.
|
||||
-- @title is true (x)
|
||||
|
||||
--- Returns a true if `x` is a symbol, false otherwise.
|
||||
-- @title is symbol (x)
|
||||
|
||||
--- Returns a true if `x` is an anchor, false otherwise.
|
||||
-- @title is anchor (x)
|
||||
|
||||
--- Returns a true if `x` is a pair, false otherwise.
|
||||
-- @title is pair (x)
|
||||
|
||||
--- Returns a true if `x` is a text, false otherwise.
|
||||
-- @title is text (x)
|
||||
|
||||
--- Returns a true if `x` is a sequence (a tuple or a list), false otherwise.
|
||||
-- @title is sequence (x)
|
||||
|
||||
--- Returns a true if `x` is a list, false otherwise.
|
||||
-- @title is list (x)
|
||||
|
||||
--- Returns a true if `x` is a map (a struct or a table), false otherwise.
|
||||
-- @title is map (x)
|
||||
|
||||
--- Returns a true if `x` is a struct, false otherwise.
|
||||
-- @title is struct (x)
|
||||
|
||||
--- Returns a true if `x` is a table, false otherwise.
|
||||
-- @title is table (x)
|
||||
|
||||
--- Returns a true if `x` is an environment, false otherwise.
|
||||
-- @title is environment (x)
|
||||
|
||||
--- Returns a true if `x` is a function, false otherwise.
|
||||
-- @title is function (x)
|
||||
|
||||
--- Returns a true if `x` is an overload, false otherwise.
|
||||
-- @title is overload (x)
|
||||
|
||||
return [[
|
||||
:@$is(t) $(x) x!type == t
|
||||
:@$equal(x) $(y) x == y
|
||||
:@$is equal(x) $(y) x == y
|
||||
|
||||
:@is nil = is("nil")
|
||||
:@is number = is("number")
|
||||
|
|
|
|||
|
|
@ -1,8 +1,16 @@
|
|||
--- # Wrap operator
|
||||
-- @titlelevel 3
|
||||
|
||||
local ast = require("anselme.ast")
|
||||
local Call, Function, ParameterTuple, FunctionParameter, Identifier, Overload = ast.Call, ast.Function, ast.ParameterTuple, ast.FunctionParameter, ast.Identifier, ast.Overload
|
||||
|
||||
return {
|
||||
{
|
||||
--- Returns a new function or overload with `expression` as the function expression.
|
||||
--
|
||||
-- If `expression` is a function call or an identifier, this returns instead an overload of two functions,
|
||||
-- defined like `$<expression>` and `$() = v; <expression> = v`, where `<expression>` is replaced by `expression`.
|
||||
-- @title > expression
|
||||
">_", "(q::is(\"quote\"))",
|
||||
function(state, q)
|
||||
local exp = q.expression
|
||||
|
|
|
|||
|
|
@ -296,4 +296,4 @@ Otherwise, each Node has its own module file defined in the [ast/](../ast) direc
|
|||
|
||||
|
||||
---
|
||||
_file generated at 2024-05-28T16:12:56Z_
|
||||
_file generated at 2024-06-01T11:51:03Z_
|
||||
|
|
@ -65,23 +65,14 @@ local function extract_block_title(line)
|
|||
return title
|
||||
end
|
||||
|
||||
local valid_tags = { title = true, defer = true }
|
||||
local valid_tags = { title = true, defer = true, titlelevel = true }
|
||||
local function process(content)
|
||||
local deferred = {}
|
||||
local titlelevel
|
||||
|
||||
-- process lua files
|
||||
local out = content:gsub("{{(.-)}}", function(lua_file)
|
||||
-- deferred doc comments
|
||||
if lua_file:match("^:") then
|
||||
local defer = lua_file:match("^:(.-)$")
|
||||
if deferred[defer] then
|
||||
local output = table.concat(deferred[defer], "\n")
|
||||
deferred[defer] = nil
|
||||
return output
|
||||
else
|
||||
return ""
|
||||
end
|
||||
-- lua file
|
||||
else
|
||||
if not lua_file:match("^:") then
|
||||
local f = assert(io.open(lua_file, "r"))
|
||||
local c = f:read("a")
|
||||
f:close()
|
||||
|
|
@ -108,15 +99,18 @@ local function process(content)
|
|||
end
|
||||
-- end doc comment
|
||||
else
|
||||
local detected_indent = 0
|
||||
if line:match("[^%s]") then
|
||||
local indent, code = line:match("^(%s*)(.-)$")
|
||||
if not comment_block.indent then comment_block.indent = utf8.len(indent) end
|
||||
detected_indent = utf8.len(indent)
|
||||
if not comment_block.title then comment_block.title = extract_block_title(code) end
|
||||
table.insert(comment_block, ("\n_defined at line %s of [%s](%s):_ `%s`"):format(line_no, lua_file, source_link_prefix..lua_file, code))
|
||||
end
|
||||
if comment_block.titlelevel then titlelevel = comment_block.titlelevel end
|
||||
if comment_block.title then
|
||||
local level = titlelevel or base_header_level+detected_indent
|
||||
table.insert(comment_block, 1, ("%s %s\n"):format(
|
||||
("#"):rep(base_header_level+(comment_block.indent or 0)),
|
||||
("#"):rep(level),
|
||||
comment_block.title
|
||||
))
|
||||
end
|
||||
|
|
@ -136,13 +130,23 @@ local function process(content)
|
|||
|
||||
return table.concat(output, "\n")
|
||||
end
|
||||
end) .. ("\n---\n_file generated at %s_"):format(os.date("!%Y-%m-%dT%H:%M:%SZ"))
|
||||
end)
|
||||
|
||||
-- process deferred doc comments
|
||||
out = out:gsub("{{:(.-)}}", function(defer)
|
||||
if deferred[defer] then
|
||||
local output = table.concat(deferred[defer], "\n")
|
||||
deferred[defer] = nil
|
||||
return output
|
||||
else
|
||||
return ""
|
||||
end
|
||||
end)
|
||||
for k in pairs(deferred) do
|
||||
print("[warning] unused defer "..tostring(k))
|
||||
end
|
||||
|
||||
return out
|
||||
return out .. ("\n---\n_file generated at %s_"):format(os.date("!%Y-%m-%dT%H:%M:%SZ"))
|
||||
end
|
||||
|
||||
local function generate_file(input, output)
|
||||
|
|
|
|||
|
|
@ -243,6 +243,20 @@ There are three ways to associate an argument to a function parameter:
|
|||
* positional arguments: the i-th argument in the argument list is associated with the i-th parameter in the function definition parameter list;
|
||||
* the assignment argument is always associated with the assignment parameter.
|
||||
|
||||
If the function only takes a single tuple or struct as an argument, the parentheses can be omitted.
|
||||
|
||||
```
|
||||
:$fn(x)
|
||||
|
||||
fn[1,2,3]
|
||||
// is the same as
|
||||
fn([1,2,3])
|
||||
|
||||
fn{1:2,3}
|
||||
// is the same as
|
||||
fn({1:2,3})
|
||||
```
|
||||
|
||||
##### Dynamic dispatch
|
||||
|
||||
Anselme uses [dynamic dispatch](https://en.wikipedia.org/wiki/Dynamic_dispatch), meaning it determine which function should be called at run-time. The dispatch is performed using all of the function parameters.
|
||||
|
|
@ -776,6 +790,12 @@ For these forms, the parameters can optionally be wrapped in parentheses in case
|
|||
:$(a::is number).b
|
||||
```
|
||||
|
||||
### Environments
|
||||
|
||||
Environments consist of a scope, and can be used to get, set, and define variable in a scope that isn't the current one.
|
||||
|
||||
An environment can, for example, be obtained using `load(path)`, which returns the exported scope of the file `path`.
|
||||
|
||||
### Overloads
|
||||
|
||||
Overloads consist of a list of arbitrary values. Each value should be [callable](#calling_callables).
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,17 +1,45 @@
|
|||
TODO
|
||||
This document describes the functions defined by default by Anselme. These are accessible from any Anselme script.
|
||||
|
||||
# Variable assignment
|
||||
This document is generated automatically from the source files in [anselme/stdlib](../anselme/stdlib).
|
||||
|
||||
TODO intro
|
||||
{{anselme/stdlib/base.lua}}
|
||||
|
||||
{{anselme/stdlib/assignment.lua}}
|
||||
|
||||
# Value checking
|
||||
|
||||
TODO
|
||||
|
||||
{{anselme/stdlib/value check.lua}}
|
||||
{{:value checking}}
|
||||
|
||||
# Control flow
|
||||
{{anselme/stdlib/boolean.lua}}
|
||||
|
||||
TODO
|
||||
{{anselme/stdlib/conditionals.lua}}
|
||||
{{anselme/stdlib/for.lua}}
|
||||
|
||||
{{anselme/stdlib/number.lua}}
|
||||
|
||||
{{anselme/stdlib/string.lua}}
|
||||
|
||||
{{anselme/stdlib/text.lua}}
|
||||
|
||||
{{anselme/stdlib/symbol.lua}}
|
||||
|
||||
{{anselme/stdlib/pair.lua}}
|
||||
|
||||
{{anselme/stdlib/structures.lua}}
|
||||
{{:structures}}
|
||||
|
||||
{{anselme/stdlib/function.lua}}
|
||||
{{anselme/stdlib/resume.lua}}
|
||||
|
||||
{{anselme/stdlib/script.lua}}
|
||||
|
||||
{{anselme/stdlib/environment.lua}}
|
||||
|
||||
{{anselme/stdlib/typed.lua}}
|
||||
|
||||
{{anselme/stdlib/persist.lua}}
|
||||
|
||||
{{anselme/stdlib/attached block.lua}}
|
||||
|
||||
{{anselme/stdlib/tag.lua}}
|
||||
|
||||
{{anselme/stdlib/wrap.lua}}
|
||||
|
|
|
|||
1
ideas.md
1
ideas.md
|
|
@ -6,7 +6,6 @@ Loosely ordered by willingness to implement.
|
|||
|
||||
Documentation:
|
||||
* tutorial
|
||||
* standard library
|
||||
|
||||
# Can be done later
|
||||
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@
|
|||
expected 3 arguments, received 2
|
||||
• $(s::is script, k::is string) (from stdlib/script.ans:41:1):
|
||||
value check failure for parameter s
|
||||
• $(c::is function, s::is symbol) = v (from stdlib/for.ans:2:1):
|
||||
• $(fn::is function, var::is symbol) = v (from stdlib/for.ans:2:1):
|
||||
expected 3 arguments, received 2
|
||||
• $(c::is function, s::is string) = v (from stdlib/for.ans:2:1):
|
||||
• $(fn::is function, var::is string) = v (from stdlib/for.ans:2:1):
|
||||
expected 3 arguments, received 2
|
||||
• $(c::is function, s::is string) (from stdlib/for.ans:2:1):
|
||||
value check failure for parameter c
|
||||
• $(fn::is function, var::is string) (from stdlib/for.ans:2:1):
|
||||
value check failure for parameter fn
|
||||
• $(c::is environment, s::is symbol) = v (from stdlib/for.ans:2:1):
|
||||
expected 3 arguments, received 2
|
||||
• $(c::is environment, s::is string) = v (from stdlib/for.ans:2:1):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue