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

Add variable constraints, rename type annotation checks to constraints, rename custom type to annotation

This commit is contained in:
Étienne Fildadut 2022-09-08 20:43:36 +09:00
parent 92a496e584
commit 3e658e4780
16 changed files with 237 additions and 118 deletions

View file

@ -139,7 +139,7 @@ $ fn
The function body is not executed when the line is reached; it must be explicitely called in an expression. See [expressions](#function-calls) to see the different ways of calling a function.
A parameter list can be optionally given after the identifier. Parameter names are identifiers, with eventually an alias (after a `:`) and a default value (after a `=`), and then a type annotation (after a `::`). It is enclosed with paranthesis and contain a comma-separated list of identifiers:
A parameter list can be optionally given after the identifier. Parameter names are identifiers, with eventually an alias (after a `:`) and a default value (after a `=`), and then a type constraint (after a `::`). It is enclosed with paranthesis and contain a comma-separated list of identifiers:
```
$ f(a, b: alias for b, c="default for c", d: alias for d = "default for d")
@ -195,7 +195,7 @@ $ g()
This is basically the behaviour you'd expect from functions in most other programming languages, and what you would use in Anselme any time you don't care about storing the function variables or want the exact same initial function variables each time you call the function (e.g. recursion). Scoped variables are not kept in save files, and are not affected by checkpointing.
Functions with the same name can be defined, as long as they have a different arguments. Functions will be selected based on the number of arguments given, their name and their type annotation:
Functions with the same name can be defined, as long as they have a different arguments. Functions will be selected based on the number of arguments given, their name and their type constraint:
```
$ f(a, b)
@ -289,7 +289,7 @@ Is 3: {object.a}
Is 1: {class.a}
```
Note that the new object returned by the class is also automatically given a type that is a reference to the class. This can be used to define methods/function that operate only on objects based on this specific class.
Note that the new object returned by the class is also automatically given an annotation that is a reference to the class. This can be used to define methods/function that operate only on objects based on this specific class.
```
% class
@ -620,7 +620,7 @@ Default types are:
* `pair`: a couple of values. Types can be mixed. Can be defined using equal sign `"key"=5`. Pairs named by a string that is also a valid identifier can be created using the `key=5` shorthand syntax; `key` will not be interpreted as the variable `key` but the string `"key"` (if `key` is a variable and you want to force the use of its value as a key instead of the string `"key"`, you can wrap it in parentheses).
* `type`: a couple of values. Types can be mixed. Can be defined using colon `expr::type`. The second value is used in type checks, this is intended to be use to give a custom type to a value.
* `annotated`: a couple of values. Types can be mixed. Can be defined using colon `expr::type`. The second value is used in type constraints, this is intended to be use to give a custom type to a value.
* `function reference`: reference to one or more function(s) with a given name. Can be defined using `&function name`, which will create a reference to every function with this name accessible from the current namespace. Can be called as if it was the original function using `func ref!` and `func ref(args)`.
@ -797,7 +797,7 @@ $ f(a, b...)
[2,3,4,5]
```
Anselme use dynamic dispatch, meaning the correct function is selected at runtime. The correct function is selected based on number of arguments, argument names, and argument type annotations. The function with the most specific arguments will be selected. If several functions match, an error is thrown.
Anselme use dynamic dispatch, meaning the correct function is selected at runtime. The correct function is selected based on number of arguments, argument names, and argument type constraint. The function with the most specific arguments will be selected. If several functions match, an error is thrown.
```
$ fn(x::number, y)
@ -827,6 +827,8 @@ $ g(x, a="t")
error, can't select unique function: {g(5)}
```
Note that types constraints are expected to be constant and are evaluated only once. Default values, however, are evaluated each time the function is called (and the user didn't explicitely give an argument that would replace this default).
#### 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.
@ -980,7 +982,7 @@ This only works on strings:
`a = b`: evaluate a and b, returns a new pair with a as key and b as value. If a is an identifier, will interpret it as a string (and not a variable; you can wrap a in parentheses if you want to use the value associated with variable a instead).
`a :: b`: evaluate a and b, returns a new typed value with a as value and b as type.
`a :: b`: evaluate a and b, returns a new annotated value with a as value and b as the annotation. This annotation will be checked in type constraints.
`a # b`: evaluates b, then evaluates a whith b added to the active tags. Returns a.
@ -1030,10 +1032,14 @@ This only works on strings:
`error(str)`: throw an error with the specified message
`raw(v)`: return v, stripped of its custom types
`annotation(v::annotated)`: returns v's annotation
`unannotated(v)`: return v, eventual annotations removed
`type(v)`: return v's type
`is a(v, type or annotation)`: check if v is of a certain type or annotation
#### Built-in variables
Variables for default types (each is associated to a string of the internal variable type name): `nil`, `number`, `string`, `list`, `pair`, `function reference`, `variable reference`.

View file

@ -667,6 +667,7 @@ local vm_mt = {
builtin_aliases = self.state.builtin_aliases,
aliases = setmetatable({}, { __index = self.state.aliases }),
functions = self.state.functions, -- no need for a cache as we can't define or modify any function from the interpreter for now
variable_constraints = self.state.variable_constraints, -- no cache as constraints are expected to be constant
variables = setmetatable({}, {
__index = function(variables, k)
local cache = getmetatable(variables).cache
@ -759,6 +760,9 @@ return setmetatable(anselme, {
-- }, ...
-- }, ...
},
variable_constraints = {
-- foo = { constraint }, ...
},
variables = {
-- foo = {
-- type = "number",

View file

@ -96,6 +96,27 @@ common = {
common.scope:set_last_scope(state, fn)
end
end,
--- checks if the value is compatible with the variable's (eventual) constraint
-- returns depth, or math.huge if no constraint
-- returns nil, err
check_constraint = function(state, fqm, val)
local constraint = state.variable_constraints[fqm]
if constraint then
if not constraint.value then
local v, e = eval(state, constraint.pending)
if not v then
return nil, ("%s; while evaluating constraint for variable %q"):format(e, fqm)
end
constraint.value = v
end
local depth = common.is_of_type(val, constraint.value)
if not depth then
return nil, ("constraint check failed")
end
return depth
end
return math.huge
end,
--- returns a variable's value, evaluating a pending expression if neccessary
-- if you're sure the variable has already been evaluated, use state.variables[fqm] directly
-- return var
@ -107,15 +128,25 @@ common = {
if not v then
return nil, ("%s; while evaluating default value for variable %q defined at %s"):format(e, fqm, var.value.source)
end
state.variables[fqm] = v
local s, err = common.set_variable(state, fqm, v)
if not s then return nil, err end
return v
else
return var
end
end,
--- set the value of a variable
-- returns true
-- returns nil, err
set_variable = function(state, name, val)
if val.type ~= "pending definition" then
local s, e = common.check_constraint(state, name, val)
if not s then
return nil, ("%s; while assigning value to variable %q"):format(e, name)
end
end
state.variables[name] = val
return true
end,
--- handle scoped function
scope = {
@ -207,7 +238,7 @@ common = {
if a.type ~= b.type then
return false
end
if a.type == "pair" or a.type == "type" then
if a.type == "pair" or a.type == "annotated" then
return common.compare(a.value[1], b.value[1]) and common.compare(a.value[2], b.value[2])
elseif a.type == "list" then
if #a.value ~= #b.value then
@ -300,18 +331,23 @@ common = {
end
return true
end,
--- check if an anselme value is of a certain type
--- check if an anselme value is of a certain type or annotation
-- specificity(number): if var is of type type. lower is more specific
-- false: if not
is_of_type = function(var, type)
local depth = 1
-- var has a custom type
if var.type == "type" then
-- var has a custom annotation
if var.type == "annotated" then
-- special case: if we just want to see if a value is annotated
if type.type == "string" and type.value == "annotated" then
return depth
end
-- check annotation
local var_type = var.value[2]
while true do
if common.compare(var_type, type) then -- same type
return depth
elseif var_type.type == "type" then -- compare parent type
elseif var_type.type == "annotated" then -- compare parent type
depth = depth + 1
var_type = var_type.value[2]
else -- no parent, fall back on base type
@ -326,7 +362,7 @@ common = {
end,
-- return a pretty printable type value for var
pretty_type = function(var)
if var.type == "type" then
if var.type == "annotated" then
return common.format(var.value[2])
else
return var.type

View file

@ -1,5 +1,5 @@
local expression
local to_lua, from_lua, eval_text, is_of_type, truthy, format, pretty_type, get_variable, tags, eval_text_callback, events, flatten_list, set_variable, scope
local to_lua, from_lua, eval_text, truthy, format, pretty_type, get_variable, tags, eval_text_callback, events, flatten_list, set_variable, scope, check_constraint
local run
@ -71,7 +71,8 @@ local function eval(state, exp)
local name = exp.left.name
local val, vale = eval(state, exp.right)
if not val then return nil, vale end
set_variable(state, name, val)
local s, e = set_variable(state, name, val)
if not s then return nil, e end
return val
else
return nil, ("don't know how to perform assignment on %s expression"):format(exp.left.type)
@ -240,24 +241,19 @@ local function eval(state, exp)
used_args[j] = true
end
if val then
-- check type annotation
if param.type_annotation then
local v, e = eval(state, param.type_annotation)
if not v then return nil, e end
local depth = is_of_type(val, v)
if not depth then
ok = false
table.insert(tried_function_error_messages, ("%s: argument %s is not of expected type %s"):format(fn.pretty_signature, param.name, format(v) or v))
break
end
depths[j] = depth
else
depths[j] = math.huge
-- check type constraint
local depth, err = check_constraint(state, param.full_name, val)
if not depth then
ok = false
local v = state.variable_constraints[param.full_name].value
table.insert(tried_function_error_messages, ("%s: argument %s is not of expected type %s"):format(fn.pretty_signature, param.name, format(v) or v))
break
end
depths[j] = depth
-- set
variant_args[param.full_name] = val
-- default: evaluate once function is selected
-- there's no need to type check because the type annotation is already the default value's type, because of syntax
-- there's no need to type check because the type constraint is already the default value's type, because of syntax
elseif param.default then
variant_args[param.full_name] = { type = "pending definition", value = { expression = param.default, source = fn.source } }
else
@ -282,21 +278,15 @@ local function eval(state, exp)
end
-- assignment arg
if ok and exp.assignment then
-- check type annotation
-- check type constraint
local param = fn.assignment
if param.type_annotation then
local v, e = eval(state, param.type_annotation)
if not v then return nil, e end
local depth = is_of_type(assignment, v)
if not depth then
ok = false
table.insert(tried_function_error_messages, ("%s: argument %s is not of expected type %s"):format(fn.pretty_signature, param.name, format(v) or v))
else
depths.assignment = depth
end
else
depths.assignment = math.huge
local depth, err = check_constraint(state, param.full_name, assignment)
if not depth then
ok = false
local v = state.variable_constraints[param.full_name].value
table.insert(tried_function_error_messages, ("%s: argument %s is not of expected type %s"):format(fn.pretty_signature, param.name, format(v) or v))
end
depths.assignment = depth
-- set
variant_args[param.full_name] = assignment
end
@ -360,7 +350,8 @@ local function eval(state, exp)
end
-- set arguments
for name, val in pairs(selected_variant.args_to_set) do
set_variable(state, name, val)
local s, e = set_variable(state, name, val)
if not s then return nil, e end
end
-- get function vars
local checkpoint, checkpointe = get_variable(state, fn.namespace.."🔖")
@ -393,11 +384,11 @@ local function eval(state, exp)
else
return nil, ("%s; in Lua function %q"):format(e or "raw function returned nil and no error message", exp.called_name)
end
-- untyped raw mode: same as raw, but strips custom types from the arguments
elseif lua_fn.mode == "untyped raw" then
-- unannotated raw mode: same as raw, but strips custom annotations from the arguments
elseif lua_fn.mode == "unannotated raw" then
-- extract value from custom types
for i, arg in ipairs(final_args) do
if arg.type == "type" then
if arg.type == "annotated" then
final_args[i] = arg.value[1]
end
end
@ -405,7 +396,7 @@ local function eval(state, exp)
if r then
ret = r
else
return nil, ("%s; in Lua function %q"):format(e or "untyped raw function returned nil and no error message", exp.called_name)
return nil, ("%s; in Lua function %q"):format(e or "unannotated raw function returned nil and no error message", exp.called_name)
end
-- normal mode: convert args to Lua and convert back Lua value to Anselme
elseif lua_fn.mode == nil then
@ -444,14 +435,15 @@ local function eval(state, exp)
if not ret then return nil, e end
end
-- update function vars
set_variable(state, fn.namespace.."👁️", {
local s, e = set_variable(state, fn.namespace.."👁️", {
type = "number",
value = seen.value + 1
})
if not s then return nil, e end
-- for classes: build resulting object
if fn.subtype == "class" then
local object = {
type = "type",
type = "annotated",
value = {
{
type = "object",
@ -519,6 +511,6 @@ run = require((...):gsub("expression$", "interpreter")).run
expression = require((...):gsub("interpreter%.expression$", "parser.expression"))
flatten_list = require((...):gsub("interpreter%.expression$", "parser.common")).flatten_list
local common = require((...):gsub("expression$", "common"))
to_lua, from_lua, eval_text, is_of_type, truthy, format, pretty_type, get_variable, tags, eval_text_callback, events, set_variable, scope = common.to_lua, common.from_lua, common.eval_text, common.is_of_type, common.truthy, common.format, common.pretty_type, common.get_variable, common.tags, common.eval_text_callback, common.events, common.set_variable, common.scope
to_lua, from_lua, eval_text, truthy, format, pretty_type, get_variable, tags, eval_text_callback, events, set_variable, scope, check_constraint = common.to_lua, common.from_lua, common.eval_text, common.truthy, common.format, common.pretty_type, common.get_variable, common.tags, common.eval_text_callback, common.events, common.set_variable, common.scope, common.check_constraint
return eval

View file

@ -114,14 +114,16 @@ run_line = function(state, line)
elseif line.type == "function" and line.subtype == "checkpoint" then
local reached, reachede = get_variable(state, line.namespace.."🏁")
if not reached then return nil, reachede end
set_variable(state, line.namespace.."🏁", {
local s, e = set_variable(state, line.namespace.."🏁", {
type = "number",
value = reached.value + 1
})
set_variable(state, line.parent_function.namespace.."🔖", {
if not s then return nil, e end
s, e = set_variable(state, line.parent_function.namespace.."🔖", {
type = "function reference",
value = { line.name }
})
if not s then return nil, e end
merge_state(state)
else
return nil, ("unknown line type %q; at %s"):format(line.type, line.source)
@ -160,21 +162,24 @@ run_block = function(state, block, resume_from_there, i, j)
if not seen then return nil, seene end
local checkpoint, checkpointe = get_variable(state, parent_line.parent_function.namespace.."🔖")
if not checkpoint then return nil, checkpointe end
set_variable(state, parent_line.namespace.."👁️", {
local s, e = set_variable(state, parent_line.namespace.."👁️", {
type = "number",
value = seen.value + 1
})
set_variable(state, parent_line.namespace.."🏁", {
if not s then return nil, e end
s, e = set_variable(state, parent_line.namespace.."🏁", {
type = "number",
value = reached.value + 1
})
if not s then return nil, e end
-- don't update checkpoint if an already more precise checkpoint is set
-- (since we will go up the whole checkpoint hierarchy when resuming from a nested checkpoint)
if checkpoint.type == "nil" or not checkpoint.value[1]:match("^"..escape(parent_line.name)) then
set_variable(state, parent_line.parent_function.namespace.."🔖", {
s, e = set_variable(state, parent_line.parent_function.namespace.."🔖", {
type = "function reference",
value = { parent_line.name }
})
if not s then return nil, e end
end
merge_state(state)
end

View file

@ -307,8 +307,8 @@ common = {
if p.alias then
sig = sig .. ":" .. p.alias
end
if p.type_annotation then
sig = sig .. "::" .. p.type_annotation
if p.type_constraint then
sig = sig .. "::" .. p.type_constraint
end
if p.default then
sig = sig .. "=" .. p.default

View file

@ -8,17 +8,17 @@ local function parse(state)
for i=#state.queued_lines, 1, -1 do
local l = state.queued_lines[i]
local line, namespace = l.line, l.namespace
-- default arguments and type annotation
-- default arguments and type constraints
if line.type == "function" then
for _, param in ipairs(line.params) do
-- get type annotation
if param.type_annotation then
local type_exp, rem = expression(param.type_annotation, state, namespace)
if not type_exp then return nil, ("in type annotation, %s; at %s"):format(rem, line.source) end
-- get type constraints
if param.type_constraint then
local type_exp, rem = expression(param.type_constraint, state, namespace)
if not type_exp then return nil, ("in type constraint, %s; at %s"):format(rem, line.source) end
if rem:match("[^%s]") then
return nil, ("unexpected characters after parameter %q: %q; at %s"):format(param.full_name, rem, line.source)
end
param.type_annotation = type_exp
state.variable_constraints[param.full_name] = { pending = type_exp }
end
-- get default value
if param.default then
@ -28,20 +28,20 @@ local function parse(state)
return nil, ("unexpected characters after parameter %q: %q; at %s"):format(param.full_name, rem, line.source)
end
param.default = default_exp
-- extract type annotation from default value
-- extract type constraint from default value
if default_exp.type == "function call" and default_exp.called_name == "_::_" then
param.type_annotation = default_exp.argument.expression.right
state.variable_constraints[param.full_name] = { pending = default_exp.argument.expression.right }
end
end
end
-- assignment argument
if line.assignment and line.assignment.type_annotation then
local type_exp, rem = expression(line.assignment.type_annotation, state, namespace)
if not type_exp then return nil, ("in type annotation, %s; at %s"):format(rem, line.source) end
if line.assignment and line.assignment.type_constraint then
local type_exp, rem = expression(line.assignment.type_constraint, state, namespace)
if not type_exp then return nil, ("in type constraint, %s; at %s"):format(rem, line.source) end
if rem:match("[^%s]") then
return nil, ("unexpected characters after parameter %q: %q; at %s"):format(line.assignment.full_name, rem, line.source)
end
line.assignment.type_annotation = type_exp
state.variable_constraints[line.assignment.full_name] = { pending = type_exp }
end
-- get list of scoped variables
-- (note includes every variables in the namespace of subnamespace, so subfunctions are scoped alongside this function)
@ -63,6 +63,15 @@ local function parse(state)
-- variable pending definition: expression will be evaluated when variable is needed
if line.type == "definition" then
state.variables[line.fqm].value.expression = line.expression
-- parse constraints
if line.constraint then
local type_exp, rem2 = expression(line.constraint, state, namespace)
if not type_exp then return nil, ("in type constraint, %s; at %s"):format(rem2, line.source) end
if rem2:match("[^%s]") then
return nil, ("unexpected characters after variable %q: %q; at %s"):format(line.fqm, rem2, line.source)
end
state.variable_constraints[line.fqm] = { pending = type_exp }
end
end
end
-- text (text & choice lines)

View file

@ -134,17 +134,17 @@ local function parse_line(line, state, namespace, parent_function)
local ok_param_alias, param_alias
ok_param_alias, param_rem, param_alias = maybe_alias(param_rem, param_fqm, func_namespace, line, state)
if not ok_param_alias then return ok_param_alias, param_rem end
-- get potential type annotation and default value
local type_annotation, default
-- get potential type constraints and default value
local type_constraint, default
if param_rem:match("^::") then
type_annotation = param_rem:match("^::(.*)$")
type_constraint = param_rem:match("^::(.*)$")
elseif param_rem:match("^=") then
default = param_rem:match("^=(.*)$")
elseif param_rem:match("[^%s]") then
return nil, ("unexpected characters after parameter %q: %q; at %s"):format(param_fqm, param_rem, line.source)
end
-- add parameter
table.insert(r.params, { name = param_identifier, alias = param_alias, full_name = param_fqm, type_annotation = type_annotation, default = default, vararg = nil })
table.insert(r.params, { name = param_identifier, alias = param_alias, full_name = param_fqm, type_constraint = type_constraint, default = default, vararg = nil })
end
end
-- get assignment param
@ -160,15 +160,15 @@ local function parse_line(line, state, namespace, parent_function)
local ok_param_alias, param_alias
ok_param_alias, param_rem, param_alias = maybe_alias(param_rem, param_fqm, func_namespace, line, state)
if not ok_param_alias then return ok_param_alias, param_rem end
-- get potential type annotation
local type_annotation
-- get potential type constraint
local type_constraint
if param_rem:match("^::") then
type_annotation = param_rem:match("^::(.*)$")
type_constraint = param_rem:match("^::(.*)$")
elseif param_rem:match("[^%s]") then
return nil, ("unexpected characters after parameter %q: %q; at %s"):format(param_fqm, param_rem, line.source)
end
-- add parameter
r.assignment = { name = param_identifier, alias = param_alias, full_name = param_fqm, type_annotation = type_annotation, default = nil, vararg = nil }
r.assignment = { name = param_identifier, alias = param_alias, full_name = param_fqm, type_constraint = type_constraint, default = nil, vararg = nil }
elseif rem:match("[^%s]") then
return nil, ("expected end-of-line at end of function definition line, but got %q; at %s"):format(rem, line.source)
end
@ -300,6 +300,10 @@ local function parse_line(line, state, namespace, parent_function)
local ok_alias
ok_alias, rem = maybe_alias(rem, fqm, namespace, line, state)
if not ok_alias then return ok_alias, rem end
-- type constraint
if rem:match("^::(.-)=") then
r.constraint, rem = rem:match("^::%s*(.-)%s*(=.*)$")
end
-- get expression
local exp = rem:match("^=(.*)$")
if not exp then return nil, ("expected \"= expression\" after %q in definition line; at %s"):format(rem, line.source) end
@ -509,6 +513,7 @@ local function parse(state, s, name, source)
local state_proxy = {
inject = {},
aliases = setmetatable({}, { __index = state.aliases }),
variable_constraints = setmetatable({}, { __index = state.variable_constraints }),
variables = setmetatable({}, { __index = state.aliases }),
functions = setmetatable({}, {
__index = function(self, key)
@ -541,6 +546,9 @@ local function parse(state, s, name, source)
for k,v in pairs(state_proxy.aliases) do
state.aliases[k] = v
end
for k,v in pairs(state_proxy.variable_constraints) do
state.variable_constraints[k] = v
end
for k,v in pairs(state_proxy.variables) do
state.variables[k] = v
end

View file

@ -9,6 +9,7 @@ return [[
:function reference="function reference"
:variable reference="variable reference"
:object="object"
:annotated="annotated"
:pi=3.1415926535898
]]

View file

@ -64,19 +64,19 @@ lua_functions = {
}
end
},
-- type
-- annotate
["_::_(a, b)"] = {
mode = "raw",
value = function(a, b)
return {
type = "type",
type = "annotated",
value = { a, b }
}
end
},
-- namespace
["_._(r::function reference, name::string)"] = {
mode = "untyped raw",
mode = "unannotated raw",
value = function(r, n)
local state = anselme.running.state
local rval = r.value
@ -100,7 +100,7 @@ lua_functions = {
end
},
["_._(r::function reference, name::string) := v"] = {
mode = "untyped raw",
mode = "unannotated raw",
value = function(r, n, v)
local state = anselme.running.state
local rval = r.value
@ -108,7 +108,8 @@ lua_functions = {
for _, ffqm in ipairs(rval) do
local var, vfqm = find(state.aliases, state.interpreter.global_state.variables, "", ffqm.."."..name)
if var then
set_variable(state, vfqm, v)
local s, e = set_variable(state, vfqm, v)
if not s then return nil, e end
return v
end
end
@ -116,7 +117,7 @@ lua_functions = {
end
},
["_._(r::object, name::string)"] = {
mode = "untyped raw",
mode = "unannotated raw",
value = function(r, n)
local state = anselme.running.state
local obj = r.value
@ -139,7 +140,7 @@ lua_functions = {
end
},
["_._(r::object, name::string) := v"] = {
mode = "untyped raw",
mode = "unannotated raw",
value = function(r, n, v)
local state = anselme.running.state
local obj = r.value
@ -163,13 +164,13 @@ lua_functions = {
},
-- index
["()(l::list, i::number)"] = {
mode = "untyped raw",
mode = "unannotated raw",
value = function(l, i)
return l.value[i.value] or { type = "nil", value = nil }
end
},
["()(l::list, i::string)"] = {
mode = "untyped raw",
mode = "unannotated raw",
value = function(l, i)
for _, v in ipairs(l.value) do
if v.type == "pair" and compare(v.value[1], i) then
@ -183,8 +184,8 @@ lua_functions = {
["()(l::list, i::number) := v"] = {
mode = "raw",
value = function(l, i, v)
local lv = l.type == "type" and l.value[1] or l
local iv = i.type == "type" and i.value[1] or i
local lv = l.type == "annotated" and l.value[1] or l
local iv = i.type == "annotated" and i.value[1] or i
lv.value[iv.value] = v
mark_as_modified(anselme.running.state, lv.value)
return v
@ -193,8 +194,8 @@ lua_functions = {
["()(l::list, k::string) := v"] = {
mode = "raw",
value = function(l, k, v)
local lv = l.type == "type" and l.value[1] or l
local kv = k.type == "type" and k.value[1] or k
local lv = l.type == "annotated" and l.value[1] or l
local kv = k.type == "annotated" and k.value[1] or k
-- update index
for _, x in ipairs(lv.value) do
if x.type == "pair" and compare(x.value[1], kv) then
@ -219,7 +220,7 @@ lua_functions = {
-- bypassed, this case is manually handled in the expression interpreter
},
["_!(fn::variable reference)"] = {
mode = "untyped raw",
mode = "unannotated raw",
value = function(v)
return get_variable(anselme.running.state, v.value)
end
@ -233,7 +234,7 @@ lua_functions = {
},
-- alias
["alias(ref::function reference, alias::string)"] = {
mode = "untyped raw",
mode = "unannotated raw",
value = function(ref, alias)
-- check identifiers
alias = alias.value
@ -252,7 +253,7 @@ lua_functions = {
end
},
["alias(ref::variable reference, alias::string)"] = {
mode = "untyped raw",
mode = "unannotated raw",
value = function(ref, alias)
-- check identifiers
alias = alias.value
@ -270,20 +271,20 @@ lua_functions = {
},
-- pair methods
["name(p::pair)"] = {
mode = "untyped raw",
mode = "unannotated raw",
value = function(a)
return a.value[1]
end
},
["value(p::pair)"] = {
mode = "untyped raw",
mode = "unannotated raw",
value = function(a)
return a.value[2]
end
},
-- list methods
["len(l::list)"] = {
mode = "untyped raw", -- raw to count pairs in the list
mode = "unannotated raw", -- raw to count pairs in the list
value = function(a)
return {
type = "number",
@ -294,7 +295,7 @@ lua_functions = {
["insert(l::list, v)"] = {
mode = "raw",
value = function(l, v)
local lv = l.type == "type" and l.value[1] or l
local lv = l.type == "annotated" and l.value[1] or l
table.insert(lv.value, v)
mark_as_modified(anselme.running.state, lv.value)
return l
@ -303,22 +304,22 @@ lua_functions = {
["insert(l::list, i::number, v)"] = {
mode = "raw",
value = function(l, i, v)
local lv = l.type == "type" and l.value[1] or l
local iv = i.type == "type" and i.value[1] or i
local lv = l.type == "annotated" and l.value[1] or l
local iv = i.type == "annotated" and i.value[1] or i
table.insert(lv.value, iv.value, v)
mark_as_modified(anselme.running.state, lv.value)
return l
end
},
["remove(l::list)"] = {
mode = "untyped raw",
mode = "unannotated raw",
value = function(l)
mark_as_modified(anselme.running.state, l.value)
return table.remove(l.value)
end
},
["remove(l::list, i::number)"] = {
mode = "untyped raw",
mode = "unannotated raw",
value = function(l, i)
mark_as_modified(anselme.running.state, l.value)
return table.remove(l.value, i.value)
@ -327,7 +328,7 @@ lua_functions = {
["find(l::list, v)"] = {
mode = "raw",
value = function(l, v)
local lv = l.type == "type" and l.value[1] or l
local lv = l.type == "annotated" and l.value[1] or l
for i, x in ipairs(lv.value) do
if compare(x, v) then
return i
@ -345,10 +346,10 @@ lua_functions = {
["rand()"] = function() return math.random() end,
["rand(a::number)"] = function(a) return math.random(a) end,
["rand(a::number, b::number)"] = function(a, b) return math.random(a, b) end,
["raw(v)"] = {
["unannotated(v)"] = {
mode = "raw",
value = function(v)
if v.type == "type" then
if v.type == "annotated" then
return v.value[1]
else
return v
@ -356,19 +357,21 @@ lua_functions = {
end
},
["type(v)"] = {
mode = "raw",
mode = "unannotated raw",
value = function(v)
if v.type == "type" then
return v.value[2]
else
return {
type = "string",
value = v.type
}
end
return {
type = "string",
value = v.type
}
end
},
["is of type(v, t)"] = {
["annotation(v::annotated)"] = {
mode = "raw",
value = function(v)
return v.value[2]
end
},
["is a(v, t)"] = {
mode = "raw",
value = function(v, t)
return {

View file

@ -7,6 +7,7 @@ return [[
~ &pair!alias("paire")
~ &function reference!alias("réference de fonction")
~ &variable reference!alias("réference de variable")
~ &annotated!alias("annoté")
(Built-in functions)
(~ &alias!alias("alias")
@ -19,8 +20,8 @@ return [[
~ &error!alias("erreur")
~ &rand!alias("aléa")
~ &raw!alias("brut")
(~ &type!alias("type")
~ &is of type!alias("est de type")
~ &is a!alias("est un")
~ &unannotated!alias("non annoté")
~ &cycle!alias("cycler")
~ &random!alias("aléatoire")
~ &next!alias("séquence")

View file

@ -136,7 +136,7 @@ types.anselme = {
return { [k] = v }
end
},
type = {
annotated = {
format = function(val)
local k, ke = format(val[1])
if not k then return k, ke end

View file

@ -0,0 +1,11 @@
:weigh::"kg" = 5::"kg"
{weigh}
~ weigh := 12::"kg"
{weigh}
~ weigh := 32
{weigh}

View file

@ -0,0 +1,22 @@
local _={}
_[9]={}
_[8]={}
_[7]={text="12::kg",tags=_[9]}
_[6]={text="5::kg",tags=_[8]}
_[5]={_[7]}
_[4]={_[6]}
_[3]={"error","constraint check failed; while assigning value to variable \"constrained variable assignement.weigh\"; at test/tests/constrained variable assignement.ans:9"}
_[2]={"text",_[5]}
_[1]={"text",_[4]}
return {_[1],_[2],_[3]}
--[[
{ "text", { {
tags = {},
text = "5::kg"
} } }
{ "text", { {
tags = {},
text = "12::kg"
} } }
{ "error", 'constraint check failed; while assigning value to variable "constrained variable assignement.weigh"; at test/tests/constrained variable assignement.ans:9' }
]]--

View file

@ -0,0 +1,7 @@
:weigh::"kg" = 5::"kg"
{weigh}
:not weigh::"kg" = 12
{not weigh}

View file

@ -0,0 +1,14 @@
local _={}
_[5]={}
_[4]={text="5::kg",tags=_[5]}
_[3]={_[4]}
_[2]={"error","constraint check failed; while assigning value to variable \"constrained variable definition.not weigh\"; at test/tests/constrained variable definition.ans:7"}
_[1]={"text",_[3]}
return {_[1],_[2]}
--[[
{ "text", { {
tags = {},
text = "5::kg"
} } }
{ "error", 'constraint check failed; while assigning value to variable "constrained variable definition.not weigh"; at test/tests/constrained variable definition.ans:7' }
]]--