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

Add default and named arguments, rename equality operator to ==, shortcut for string pairs

This commit is contained in:
Étienne Fildadut 2021-04-25 18:40:45 +02:00
parent 17751c5c59
commit 151c70ed26
28 changed files with 396 additions and 146 deletions

View file

@ -98,16 +98,16 @@ $ main
~ var := 2 ~ var := 2
before: {var}=2, because the value has been changed in the current execution context before: {var}==2, because the value has been changed in the current execution context
(But if we run the script "parallel" in parallel at this point, it will still think var=5) (But if we run the script "parallel" in parallel at this point, it will still think var==5)
§ foo § foo
But the variable will be merged with the global state on a checkpoint But the variable will be merged with the global state on a checkpoint
after: {var}=2, still, as expected after: {var}==2, still, as expected
(And if we run the script "parallel" in parallel at this point, it will now think var=2) (And if we run the script "parallel" in parallel at this point, it will now think var==2)
$ parallel $ parallel
parallel: {main.var} parallel: {main.var}
@ -150,7 +150,7 @@ There's different types of lines, depending on their first character(s) (after i
~ 0 ~ 0
This is never run. This is never run.
~~ 1 = 0 ~~ 1 == 0
This neither. This neither.
~~ ~~
This is. This is.
@ -168,13 +168,14 @@ There's different types of lines, depending on their first character(s) (after i
The function body is not executed when the line is reached; it must be explicitely called in an expression. See [expressions](#function-calls) to see the different ways of calling a function. The function body is not executed when the line is reached; it must be explicitely called in an expression. See [expressions](#function-calls) to see the different ways of calling a function.
A parameter list can be optionally given after the identifier. Parameter names are identifiers, with eventually an alias. 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 `=`). It is enclosed with paranthesis and contain a comma-separated list of identifiers:
``` ```
$ f(a, b, c) $ f(a, b: alias for b, c="default for c", d: alias for d = "default for d")
first argument: {a} first argument: {a}
second argument: {b} second argument: {b}
third argument: {c} third argument: {c}
fourth argument: {d}
``` ```
Functions can also have a variable number of arguments. By adding `...` after the last argument identifier, it will be considered a variable length argument ("vararg"), and will contain a list of every extraneous argument. Functions can also have a variable number of arguments. By adding `...` after the last argument identifier, it will be considered a variable length argument ("vararg"), and will contain a list of every extraneous argument.
@ -274,7 +275,7 @@ $ f
@2 @2
(f will return 2 since the choice is run after the @2 line) (f will return 2 since the choice is run after the @2 line)
~ f = 2 ~ f == 2
Yes. Yes.
@ -442,7 +443,7 @@ Var1 in the current namespace = 1: {var1}
Var1 in the fn1 namespace = 2: {fn1.var1} Var1 in the fn1 namespace = 2: {fn1.var1}
(Weird, but valid, and also the reason I'm not talking of scoping:) (Weird, but valid, and also the reason I'm not talking of scoping:)
~ fn1.var1 = 3 ~ fn1.var1 == 3
``` ```
#### Aliases #### Aliases
@ -495,7 +496,7 @@ Default types are:
* `list`: a list of values. Types can be mixed. Can be defined between square brackets and use comma as a separator '[1,2,3,4]'. * `list`: a list of values. Types can be mixed. Can be defined between square brackets and use comma as a separator '[1,2,3,4]'.
* `pair`: a couple of values. Types can be mixed. Can be defined using colon `"key":5`. * `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.
How conversions are handled from Anselme to Lua: How conversions are handled from Anselme to Lua:
@ -507,7 +508,7 @@ How conversions are handled from Anselme to Lua:
* `list` -> `table`. Pair elements in the list will be assigned as a key-value pair in the Lua list and its index skipped in the sequential part, e.g. `[1,2,"key":"value",3]` -> `{1,2,3,key="value"}`. * `list` -> `table`. Pair elements in the list will be assigned as a key-value pair in the Lua list and its index skipped in the sequential part, e.g. `[1,2,"key":"value",3]` -> `{1,2,3,key="value"}`.
* `pair` -> `table`, with a signle key-value pair. * `pair` -> `table`, with a single key-value pair.
How conservions are handled from Lua to Anselme: How conservions are handled from Lua to Anselme:
@ -584,6 +585,49 @@ $ f
this is text: {f} this is text: {f}
``` ```
Functions can also have default arguments. Defaults values can be any expression and are re-evaluated each time the function is called:
```
$ f(a, b=1)
@a+b
{f(1)} = 2
$ g(a, b=a)
@a+b
{g(1)} = 2
{g(2)} = 4
```
Arguments can also be passed by naming them instead of their position. These syntaxes can be mixed:
```
$ f(a, b, c)
@a + b + c
{f(1,2,3)} = {f(c=3,b=2,a=1)} = {f(1,2,c=3)}
```
Anselme actually treat argument list are regular lists; named arguments are actually pairs.
This means that pairs can't be passed directly as arguments to a function (as they will be considered named arguments). If you want to use pairs, always wrap them in a list.
Functions can have a variable number of arguments. Additional arguments are added in a list:
```
$ f(a, b...)
{a}
{b}
{f(1, 2, 3, 4, 5)}
(Will print:)
1
[2,3,4,5]
```
#### Checkpoint calls #### Checkpoint calls
Most of the time, you should'nt need to call checkpoints yourself - they will be automatically be set as the active checkpoint when the interperter reach their line, and they will be automatically called when resuming its parent function. Most of the time, you should'nt need to call checkpoints yourself - they will be automatically be set as the active checkpoint when the interperter reach their line, and they will be automatically called when resuming its parent function.
@ -647,7 +691,7 @@ Built-in operators:
##### Comparaison ##### Comparaison
`a = b`: returns `1` if a and b have the same value (will recursively compare list and pairs), `0` otherwise `a == b`: returns `1` if a and b have the same value (will recursively compare list and pairs), `0` otherwise
`a != b`: returns `1` if a and b do not have the same value, `0` otherwise `a != b`: returns `1` if a and b do not have the same value, `0` otherwise
@ -685,7 +729,7 @@ This only works on strings:
`a : b`: evaluate a and b, returns a new pair with a as key and b as value. `a : b`: evaluate a and b, returns a new pair with a as key and b as value.
`a(b)`: evaluate b (number), returns the value with this index in a (list). Use 1-based indexing. `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.
#### Built-in functions #### Built-in functions

View file

@ -1,7 +1,10 @@
-- anselme module -- anselme module
local anselme = { local anselme = {
-- version -- version
version = "0.13.1", -- major.minor.fix
-- saves files are incompatible between major versions
-- scripts files may break between minor versions
version = "0.14.0",
--- currently running interpreter --- currently running interpreter
running = nil running = nil
} }
@ -399,7 +402,7 @@ return setmetatable(anselme, {
functions = { functions = {
-- [":="] = { -- [":="] = {
-- { -- {
-- arity = {3,42}, type = { [1] = "variable" }, check = function, rewrite = function, vararg = 2, mode = "custom", -- arity = {3,42}, type = { [1] = "variable" }, check = function, rewrite = function, mode = "custom",
-- value = function(state, exp) -- value = function(state, exp)
-- end -- or checkpoint, function, line -- end -- or checkpoint, function, line
-- } -- }

View file

@ -33,12 +33,13 @@ local function eval(state, exp)
elseif exp.type == "parentheses" then elseif exp.type == "parentheses" then
return eval(state, exp.expression) return eval(state, exp.expression)
-- list parentheses -- list parentheses
elseif exp.type == "list_parentheses" then elseif exp.type == "list_brackets" then
if exp.expression then if exp.expression then
local v, e = eval(state, exp.expression) local v, e = eval(state, exp.expression)
if not v then return v, e end if not v then return v, e end
if v.type == "list" then if exp.expression.type == "list" then
return v return v
-- contained a single element, wrap in list manually
else else
return { return {
type = "list", type = "list",
@ -78,24 +79,12 @@ local function eval(state, exp)
if fn.mode == "custom" then if fn.mode == "custom" then
return fn.value(state, exp) return fn.value(state, exp)
else else
-- eval args: same as list, but only put vararg arguments in a separate list -- eval args: list_brackets
local l = {} local args = {}
if exp.argument then if exp.argument then
local vararg = fn.vararg or math.huge local arg, arge = eval(state, exp.argument)
local i, ast = 1, exp.argument if not arg then return arg, arge end
while ast.type == "list" and i < vararg do args = arg.value
local left, lefte = eval(state, ast.left)
if not left then return left, lefte end
table.insert(l, left)
ast = ast.right
i = i + 1
end
local right, righte = eval(state, ast)
if not right then return right, righte end
table.insert(l, right)
end
if fn.vararg and #l < fn.vararg then -- empty list vararg
table.insert(l, { type = "list", value = {} })
end end
-- anselme function -- anselme function
if type(fn.value) == "table" then if type(fn.value) == "table" then
@ -106,9 +95,40 @@ local function eval(state, exp)
return r return r
-- function -- function
elseif fn.value.type == "function" then elseif fn.value.type == "function" then
-- set args -- map named arguments
for _, arg in ipairs(args) do
if arg.type == "pair" and arg.value[1].type == "string" then
args[arg.value[1].value] = arg.value[2]
end
end
-- get and set args
for j, param in ipairs(fn.value.params) do for j, param in ipairs(fn.value.params) do
state.variables[param] = l[j] local val
-- named
if param.alias and args[param.alias] then
val = args[param.alias]
elseif args[param.name] then
val = args[param.name]
-- vararg
elseif param.vararg then
val = { type = "list", value = {} }
for k=j, #args do
table.insert(val.value, args[k])
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
end
if val then
state.variables[param.full_name] = val
else
return nil, ("missing mandatory argument %q in function %q call"):format(param.name, fn.value.name)
end
end end
-- eval function -- eval function
local r, e local r, e
@ -130,12 +150,13 @@ local function eval(state, exp)
return nil, ("unknown function type %q"):format(fn.value.type) return nil, ("unknown function type %q"):format(fn.value.type)
end end
-- lua functions -- lua functions
-- TODO: handle named and default arguments
else else
if fn.mode == "raw" then if fn.mode == "raw" then
return fn.value(unpack(l)) return fn.value(unpack(args))
else else
local l_lua = {} local l_lua = {}
for _, v in ipairs(l) do for _, v in ipairs(args) do
table.insert(l_lua, to_lua(v)) table.insert(l_lua, to_lua(v))
end end
local r, e local r, e

View file

@ -107,6 +107,9 @@ common = {
for _, variant in ipairs(func) do for _, variant in ipairs(func) do
local ok = true local ok = true
local return_type = variant.return_type 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
-- (e.g., if there's 3 required args but only provide 3 optional arg in a call, will pass)
if variant.arity then if variant.arity then
local min, max local min, max
if type(variant.arity) == "table" then if type(variant.arity) == "table" then
@ -123,6 +126,7 @@ common = {
ok = false ok = false
end end
end end
-- custom check
if ok and variant.check then if ok and variant.check then
local s, e = variant.check(state, args) local s, e = variant.check(state, args)
if not s then if not s then
@ -131,6 +135,7 @@ common = {
end end
return_type = s == true and return_type or s return_type = s == true and return_type or s
end end
-- type check
if ok and variant.types then if ok and variant.types then
for j, t in pairs(variant.types) do for j, t in pairs(variant.types) do
if args[j] and args[j].return_type and args[j].return_type ~= t then if args[j] and args[j].return_type and args[j].return_type ~= t then
@ -139,6 +144,7 @@ common = {
end end
end end
end end
-- done
if ok then if ok then
if variant.rewrite then if variant.rewrite then
local r, e = variant.rewrite(fqm, state, arg, explicit_call) local r, e = variant.rewrite(fqm, state, arg, explicit_call)
@ -156,7 +162,11 @@ common = {
name = fqm, name = fqm,
explicit_call = explicit_call, explicit_call = explicit_call,
variant = variant, variant = variant,
argument = arg argument = { -- wrap everything in a list literal to simply later things (otherwise may be nil, single value, list constructor)
type = "list_brackets",
return_type = "list",
expression = arg
}
} }
end end
end end

View file

@ -6,7 +6,7 @@ local binops_prio = {
[2] = { ":=", "+=", "-=", "//=", "/=", "*=", "%=", "^=" }, [2] = { ":=", "+=", "-=", "//=", "/=", "*=", "%=", "^=" },
[3] = { "," }, [3] = { "," },
[4] = { "|", "&" }, [4] = { "|", "&" },
[5] = { "!=", "=", ">=", "<=", "<", ">" }, [5] = { "!=", "==", ">=", "<=", "<", ">" },
[6] = { "+", "-" }, [6] = { "+", "-" },
[7] = { "*", "//", "/", "%" }, [7] = { "*", "//", "/", "%" },
[8] = {}, -- unary operators [8] = {}, -- unary operators
@ -89,7 +89,7 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
if r_paren:match("[^%s]") then return nil, ("unexpected %q at end of list parenthesis expression"):format(r_paren) end if r_paren:match("[^%s]") then return nil, ("unexpected %q at end of list parenthesis expression"):format(r_paren) end
end end
return expression(r, state, namespace, currentPriority, { return expression(r, state, namespace, currentPriority, {
type = "list_parentheses", type = "list_brackets",
return_type = "list", return_type = "list",
expression = exp expression = exp
}) })
@ -97,6 +97,26 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
elseif s:match("^"..identifier_pattern) then elseif s:match("^"..identifier_pattern) then
local name, r = s:match("^("..identifier_pattern..")(.-)$") local name, r = s:match("^("..identifier_pattern..")(.-)$")
name = format_identifier(name) name = format_identifier(name)
-- string:value pair shorthand using =
if r:match("^=[^=]") then
local val
val, r = expression(r:match("^=(.*)$"), state, namespace, 9)
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)
if not variant then return variant, err end
return expression(r, state, namespace, currentPriority, variant)
end
-- variables -- variables
local var, vfqm = find(state.aliases, state.variables, namespace, name) local var, vfqm = find(state.aliases, state.variables, namespace, name)
if var then if var then
@ -132,6 +152,7 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
local err local err
args, err = expression(content, state, namespace) args, err = expression(content, state, namespace)
if not args then return args, err end if not args then return args, err end
if err:match("[^%s]") then return nil, ("unexpected %q at end of argument list"):format(err) end
end end
end end
-- find compatible variant -- find compatible variant
@ -181,6 +202,7 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
local err local err
args, err = expression(content, state, namespace) args, err = expression(content, state, namespace)
if not args then return args, err end if not args then return args, err end
if err:match("[^%s]") then return nil, ("unexpected %q at end of argument list"):format(err) end
end end
end end
-- add first argument -- add first argument

View file

@ -6,6 +6,21 @@ local parse_text
local function parse(state) local function parse(state)
for _, l in ipairs(state.queued_lines) do for _, l in ipairs(state.queued_lines) do
local line, namespace = l.line, l.namespace local line, namespace = l.line, l.namespace
-- default arguments
if line.type == "function" then
for i, param in ipairs(line.params) do
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
end
end
end
end
-- expressions -- expressions
if line.expression then if line.expression then
local exp, rem = expression(line.expression, state, namespace) local exp, rem = expression(line.expression, state, namespace)

View file

@ -2,6 +2,28 @@ local expression
local format_identifier, identifier_pattern local format_identifier, identifier_pattern
local eval local eval
-- try to define an alias using rem, the text that follows the identifier
-- returns true, new_rem, alias_name in case of success
-- returns true, rem in case of no alias and no error
-- returns nil, err in case of alias and error
local function maybe_alias(rem, fqm, namespace, line, state)
local alias
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
alias = format_identifier(alias)
-- format alias
local aliasfqm = ("%s%s"):format(namespace, alias)
-- define alias
if state.aliases[aliasfqm] ~= nil and state.aliases[aliasfqm] ~= fqm then
return nil, ("trying to define alias %q for %q, but already exist and refer to %q; at %s"):format(aliasfqm, fqm, state.aliases[aliasfqm], line.source)
end
state.aliases[aliasfqm] = fqm
end
return true, rem, alias
end
-- * ast: if success -- * ast: if success
-- * nil, error: in case of error -- * nil, error: in case of error
local function parse_line(line, state, namespace) local function parse_line(line, state, namespace)
@ -9,12 +31,6 @@ local function parse_line(line, state, namespace)
local r = { local r = {
source = line.source source = line.source
} }
-- comment
if l:match("^%(") then
r.type = "comment"
r.remove_from_block_ast = true
return r
end
-- else-condition & condition -- else-condition & condition
if l:match("^~~?") then if l:match("^~~?") then
r.type = l:match("^~~") and "else-condition" or "condition" r.type = l:match("^~~") and "else-condition" or "condition"
@ -42,19 +58,9 @@ local function parse_line(line, state, namespace)
-- format identifier -- format identifier
local fqm = ("%s%s"):format(namespace, format_identifier(identifier)) local fqm = ("%s%s"):format(namespace, format_identifier(identifier))
-- get alias -- get alias
if rem:match("^%:") then local ok_alias
local content = rem:sub(2) ok_alias, rem = maybe_alias(rem, fqm, namespace, line, state)
local alias if not ok_alias then return ok_alias, rem end
alias, rem = content:match("^("..identifier_pattern..")(.-)$")
if not alias then return nil, ("expected an identifier in alias in checkpoint/function definition line, but got %q; at %s"):format(content, line.source) end
-- format alias
local aliasfqm = ("%s%s"):format(namespace, format_identifier(alias))
-- define alias
if state.aliases[aliasfqm] ~= nil and state.aliases[aliasfqm] ~= fqm then
return nil, ("trying to define alias %q for checkpoint/function %q, but already exist and refer to %q; at %s"):format(aliasfqm, fqm, state.aliases[aliasfqm], line.source)
end
state.aliases[aliasfqm] = fqm
end
-- get params -- get params
r.params = {} r.params = {}
if r.type == "function" and rem:match("^%b()$") then if r.type == "function" and rem:match("^%b()$") then
@ -63,36 +69,39 @@ local function parse_line(line, state, namespace)
-- get identifier -- get identifier
local param_identifier, param_rem = param:match("^("..identifier_pattern..")(.-)$") local param_identifier, param_rem = param:match("^("..identifier_pattern..")(.-)$")
if not identifier then return nil, ("no valid identifier in function parameter %q; at %s"):format(param, line.source) end if not 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 -- format identifier
local param_fqm = ("%s.%s"):format(fqm, format_identifier(param_identifier)) local param_fqm = ("%s.%s"):format(fqm, param_identifier)
-- get alias -- get alias
if param_rem:match("^%:") then local ok_param_alias, param_alias
local param_content = param_rem:sub(2) ok_param_alias, param_rem, param_alias = maybe_alias(param_rem, param_fqm, fqm..".", line, state)
local alias if not ok_param_alias then return ok_param_alias, param_rem end
alias, param_rem = param_content:match("^("..identifier_pattern..")(.-)$") -- get default value
if not alias then return nil, ("expected an identifier in alias in parameter, but got %q; at %s"):format(param_content, line.source) end local default
-- format alias if param_rem:match("^=") then
local aliasfqm = ("%s.%s"):format(fqm, format_identifier(alias)) default = param_rem:match("^=(.*)$")
-- define alias elseif param_rem:match("[^%s]") then
if state.aliases[aliasfqm] ~= nil and state.aliases[aliasfqm] ~= param_fqm then
return nil, ("trying to define alias %q for parameter %q, but already exist and refer to %q; at %s"):format(aliasfqm, param_fqm, state.aliases[aliasfqm], line.source)
end
state.aliases[aliasfqm] = param_fqm
end
if param_rem:match("[^%s]") then
return nil, ("unexpected characters after parameter %q: %q; at %s"):format(param_fqm, param_rem, line.source) return nil, ("unexpected characters after parameter %q: %q; at %s"):format(param_fqm, param_rem, line.source)
end end
-- add parameter -- add parameter
table.insert(r.params, param_fqm) table.insert(r.params, { name = param_identifier, alias = param_alias, full_name = param_fqm, default = default })
end end
elseif rem:match("[^%s]") then elseif rem:match("[^%s]") then
return nil, ("expected end-of-line at end of checkpoint/function definition line, but got %q; at %s"):format(rem, line.source) return nil, ("expected end-of-line at end of checkpoint/function definition line, but got %q; at %s"):format(rem, line.source)
end end
local arity, vararg = #r.params, nil -- calculate arity
if arity > 0 and r.params[arity]:match("%.%.%.$") then -- varargs local minarity, maxarity = #r.params, #r.params
r.params[arity] = r.params[arity]:match("^(.*)%.%.%.$") for _, param in ipairs(r.params) do -- params with default values
vararg = arity if param.default then
arity = { arity-1, math.huge } minarity = minarity - 1
end
end
if maxarity > 0 and r.params[maxarity].full_name:match("%.%.%.$") then -- varargs
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 end
-- store parent function and run checkpoint when line is read -- store parent function and run checkpoint when line is read
if r.type == "checkpoint" then if r.type == "checkpoint" then
@ -107,9 +116,8 @@ local function parse_line(line, state, namespace)
r.name = 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 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 = { r.variant = {
arity = arity, arity = { minarity, maxarity },
types = {}, types = {},
vararg = vararg,
value = r value = r
} }
-- new function (no overloading yet) -- new function (no overloading yet)
@ -191,13 +199,13 @@ local function parse_line(line, state, namespace)
end end
-- define args and set type check information -- define args and set type check information
for i, param in ipairs(r.params) do for i, param in ipairs(r.params) do
if not state.variables[param] then if not state.variables[param.full_name] then
state.variables[param] = { state.variables[param.full_name] = {
type = "undefined argument", type = "undefined argument",
value = { r.variant, i } value = { r.variant, i }
} }
elseif state.variables[param].type ~= "undefined argument" then elseif state.variables[param.full_name].type ~= "undefined argument" then
r.variant.types[i] = state.variables[param].type r.variant.types[i] = state.variables[param.full_name].type
end end
end end
-- definition -- definition
@ -208,25 +216,17 @@ local function parse_line(line, state, namespace)
local exp, rem = expression(l:match("^:(.*)$"), state, namespace) -- expression parsing is done directly to get type information 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 if not exp then return nil, ("%s; at %s"):format(rem, line.source) end
-- get identifier -- get identifier
local identifier, rem2 = rem:match("^("..identifier_pattern..")(.-)$") 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 if not identifier then return nil, ("no valid identifier after expression in definition line %q; at %s"):format(rem, line.source) end
-- format identifier -- format identifier
local fqm = ("%s%s"):format(namespace, format_identifier(identifier)) local fqm = ("%s%s"):format(namespace, format_identifier(identifier))
-- get alias -- get alias
if rem2:match("^%:") then local ok_alias
local content = rem2:sub(2) ok_alias, rem = maybe_alias(rem, fqm, namespace, line, state)
local alias, rem3 = content:match("^("..identifier_pattern..")(.-)$") if not ok_alias then return ok_alias, rem end
if not alias then return nil, ("expected an identifier in alias in definition line, but got %q; at %s"):format(content, line.source) end if rem:match("[^%s]") then
if rem3:match("[^%s]") then return nil, ("expected end-of-line after identifier in alias in definition line, but got %q; at %s"):format(rem3, line.source) end return nil, ("expected end-of-line after identifier in definition line, but got %q; at %s"):format(rem, line.source)
-- format alias
local aliasfqm = ("%s%s"):format(namespace, format_identifier(alias))
-- define alias
if state.aliases[aliasfqm] ~= nil and state.aliases[aliasfqm] ~= fqm then
return nil, ("trying to define alias %s for variable %s, but already exist and refer to different variable %s; at %s"):format(aliasfqm, fqm, state.aliases[aliasfqm], line.source)
end
state.aliases[aliasfqm] = fqm
elseif rem2:match("[^%s]") then
return nil, ("expected end-of-line after identifier in definition line, but got %q; at %s"):format(rem2, line.source)
end end
-- define identifier -- define identifier
if state.functions[fqm] then return nil, ("trying to define variable %s, but a function with the same name exists; at %s"):format(fqm, line.source) end if state.functions[fqm] then return nil, ("trying to define variable %s, but a function with the same name exists; at %s"):format(fqm, line.source) end
@ -297,8 +297,8 @@ local function parse_block(indented, state, namespace, parent_function)
-- queue in expression evalution -- queue in expression evalution
table.insert(state.queued_lines, { namespace = ast.namespace or namespace, line = ast }) table.insert(state.queued_lines, { namespace = ast.namespace or namespace, line = ast })
-- indented block (ignore block comments) -- indented block
if l.children and ast.type ~= "comment" then if l.children then
if not ast.child then if not ast.child then
return nil, ("line %s (%s) can't have children"):format(ast.source, ast.type) return nil, ("line %s (%s) can't have children"):format(ast.source, ast.type)
else else
@ -317,31 +317,35 @@ local function transform_indented(indented)
local i = 1 local i = 1
while i <= #indented do while i <= #indented do
local l = indented[i] local l = indented[i]
-- comment
-- condition decorator if l.content:match("^%(") then
if l.content:match("^.-%s*[^~]%~[^#~$]-$") then table.remove(indented, i)
local decorator
l.content, decorator = l.content:match("^(..-)%s*(%~[^#~$]-)$")
indented[i] = { content = decorator, source = l.source, children = { l } }
-- tag decorator
elseif l.content:match("^..-%s*%#[^#~$]-$") then
local decorator
l.content, decorator = l.content:match("^(..-)%s*(%#[^#~$]-)$")
indented[i] = { content = decorator, source = l.source, children = { l } }
-- function decorator
elseif l.content:match("^..-%s*%$[^#~$]-$") then
local name
l.content, name = l.content:match("^(..-)%s*%$([^#~$]-)$")
indented[i] = { content = "~"..name, source = l.source }
table.insert(indented, i+1, { content = "$"..name, source = l.source, children = { l } })
i = i + 1 -- $ line should not contain any decorator anymore
else else
i = i + 1 -- only increment when no decorator, as there may be several decorators per line -- condition decorator
end if l.content:match("^.-%s*[^~]%~[^#~$]-$") then
local decorator
l.content, decorator = l.content:match("^(..-)%s*(%~[^#~$]-)$")
indented[i] = { content = decorator, source = l.source, children = { l } }
-- tag decorator
elseif l.content:match("^..-%s*%#[^#~$]-$") then
local decorator
l.content, decorator = l.content:match("^(..-)%s*(%#[^#~$]-)$")
indented[i] = { content = decorator, source = l.source, children = { l } }
-- function decorator
elseif l.content:match("^..-%s*%$[^#~$]-$") then
local name
l.content, name = l.content:match("^(..-)%s*%$([^#~$]-)$")
indented[i] = { content = "~"..name, source = l.source }
table.insert(indented, i+1, { content = "$"..name, source = l.source, children = { l } })
i = i + 1 -- $ line should not contain any decorator anymore
else
i = i + 1 -- only increment when no decorator, as there may be several decorators per line
end
-- indented block -- indented block
if l.children then if l.children then
transform_indented(l.children) transform_indented(l.children)
end
end end
end end
return indented return indented

View file

@ -53,7 +53,7 @@ functions = {
return right.return_type or true return right.return_type or true
end, end,
value = function(state, exp) value = function(state, exp)
local arg = exp.argument local arg = exp.argument.expression
local name = arg.left.name local name = arg.left.name
local right, righte = eval(state, arg.right) local right, righte = eval(state, arg.right)
if not right then return right, righte end if not right then return right, righte end
@ -84,7 +84,7 @@ functions = {
{ rewrite = rewrite_assignement } { rewrite = rewrite_assignement }
}, },
-- comparaison -- comparaison
["="] = { ["=="] = {
{ {
arity = 2, return_type = "number", mode = "raw", arity = 2, return_type = "number", mode = "raw",
value = function(a, b) value = function(a, b)
@ -193,7 +193,7 @@ functions = {
{ {
arity = 2, return_type = "number", mode = "custom", arity = 2, return_type = "number", mode = "custom",
value = function(state, exp) value = function(state, exp)
local arg = exp.argument local arg = exp.argument.expression
local left, lefte = eval(state, arg.left) local left, lefte = eval(state, arg.left)
if not left then return left, lefte end if not left then return left, lefte end
if truthy(left) then if truthy(left) then
@ -217,7 +217,7 @@ functions = {
{ {
arity = 2, return_type = "number", mode = "custom", arity = 2, return_type = "number", mode = "custom",
value = function(state, exp) value = function(state, exp)
local arg = exp.argument local arg = exp.argument.expression
local left, lefte = eval(state, arg.left) local left, lefte = eval(state, arg.left)
if not left then return left, lefte end if not left then return left, lefte end
if truthy(left) then if truthy(left) then
@ -254,6 +254,17 @@ functions = {
value = function(a, b) value = function(a, b)
return a.value[b.value] or { type = "nil", value = nil } return a.value[b.value] or { type = "nil", value = nil }
end 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
return v.value[2]
end
end
return { type = "nil", value = nil }
end
} }
}, },
-- list methods -- list methods

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
:5 a :5 a
~ a = 5 ~ a == 5
ok ok
~~ ~~
ko ko

View file

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

View file

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

View file

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

View file

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

View file

@ -1,19 +1,19 @@
:(1:2) a :(1:2) a
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 :[1,2,3] b
0 = {b = a} 0 = {b == a}
0 = {b = []} 0 = {b == []}
0 = {b = [3,1,2]} 0 = {b == [3,1,2]}
0 = {b = [1,2,3,4]} 0 = {b == [1,2,3,4]}
1 = {b = [1,2,3]} 1 = {b == [1,2,3]}

View file

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

View file

@ -0,0 +1,22 @@
local _={}
_[9]={}
_[8]={}
_[7]={data="ye = ye",tags=_[9]}
_[6]={data="ok = ok",tags=_[8]}
_[5]={_[7]}
_[4]={_[6]}
_[3]={"return"}
_[2]={"text",_[5]}
_[1]={"text",_[4]}
return {_[1],_[2],_[3]}
--[[
{ "text", { {
data = "ok = ok",
tags = {}
} } }
{ "text", { {
data = "ye = ye",
tags = {}
} } }
{ "return" }
]]--

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,6 +5,6 @@ $ f
y y
@2 @2
~ f = 2 ~ f == 2
~ choose(1) ~ choose(1)
Yes. Yes.

View file

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

View file

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