mirror of
https://github.com/Reuh/anselme.git
synced 2025-10-27 16:49:31 +00:00
Add maps; remove map emulation functionality from list; function and tags now internally use maps instead of lists
This commit is contained in:
parent
bac5cdde01
commit
95462391e3
20 changed files with 699 additions and 139 deletions
|
|
@ -48,6 +48,14 @@ local function post_process_text(state, text)
|
|||
return r
|
||||
end
|
||||
|
||||
local function random_identifier()
|
||||
local r = ""
|
||||
for _=1, 16 do -- that's live 10^31 possibilities, ought to be enough for anyone
|
||||
r = r .. string.char(math.random(32, 126))
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
common = {
|
||||
--- merge interpreter state with global state
|
||||
merge_state = function(state)
|
||||
|
|
@ -129,18 +137,11 @@ common = {
|
|||
end,
|
||||
--- mark a value as constant, recursively affecting all the potentially mutable subvalues
|
||||
mark_constant = function(v)
|
||||
if v.type == "list" then
|
||||
v.constant = true
|
||||
for _, item in ipairs(v.value) do
|
||||
common.mark_constant(item)
|
||||
end
|
||||
elseif v.type == "object" then
|
||||
v.constant = true
|
||||
elseif v.type == "pair" or v.type == "annotated" then
|
||||
common.mark_constant(v.value[1])
|
||||
common.mark_constant(v.value[2])
|
||||
elseif v.type ~= "nil" and v.type ~= "number" and v.type ~= "string" and v.type ~= "function reference" and v.type ~= "variable reference" then
|
||||
error("unknown type")
|
||||
if atypes[v.type] and atypes[v.type].mark_constant then
|
||||
atypes[v.type].mark_constant(v)
|
||||
if v.hash_id then v.hash_id = nil end -- no longer need to compare by id
|
||||
else
|
||||
error(("don't know how to mark type %s as constant"):format(v.type))
|
||||
end
|
||||
end,
|
||||
--- returns a variable's value, evaluating a pending expression if neccessary
|
||||
|
|
@ -311,6 +312,8 @@ common = {
|
|||
end
|
||||
end
|
||||
return true
|
||||
elseif a.constant and a.type == "map" then
|
||||
return common.hash(a) == common.hash(b)
|
||||
elseif a.constant and a.type == "object" then
|
||||
if a.value.class ~= b.value.class then
|
||||
return false
|
||||
|
|
@ -348,6 +351,35 @@ common = {
|
|||
return nil, ("no formatter for type %q"):format(val.type)
|
||||
end
|
||||
end,
|
||||
--- compute a hash for a value.
|
||||
-- A hash is a Lua string such as, given two values, they are considered equal by Anselme if and only if their hash are considered equal by Lua.
|
||||
-- Will generate random identifiers for mutable values (equality test by reference) in order for the identifier to stay the same accross checkpoints and
|
||||
-- other potential variable copies.
|
||||
-- str: if success
|
||||
-- nil, err: if error
|
||||
hash = function(val)
|
||||
if atypes[val.type] and atypes[val.type].hash then
|
||||
if atypes[val.type].mutable and not val.constant then
|
||||
if not val.hash_id then val.hash_id = random_identifier() end
|
||||
return ("mut(%s)"):format(val.hash_id)
|
||||
else
|
||||
return atypes[val.type].hash(val.value)
|
||||
end
|
||||
else
|
||||
return nil, ("no hasher for type %q"):format(val.type)
|
||||
end
|
||||
end,
|
||||
--- recompute all the hases in a map.
|
||||
-- str: if success
|
||||
-- nil, err: if error
|
||||
update_hashes = function(map)
|
||||
for k, v in pairs(map.value) do
|
||||
local hash, e = common.hash(v[1])
|
||||
if not hash then return nil, e end
|
||||
map[k] = nil
|
||||
map[hash] = v
|
||||
end
|
||||
end,
|
||||
--- convert anselme value to lua
|
||||
-- lua value: if success (may be nil!)
|
||||
-- nil, err: if error
|
||||
|
|
@ -373,7 +405,8 @@ common = {
|
|||
-- nil, err: if error
|
||||
eval_text = function(state, text)
|
||||
local l = {}
|
||||
common.eval_text_callback(state, text, function(str) table.insert(l, str) end)
|
||||
local s, e = common.eval_text_callback(state, text, function(str) table.insert(l, str) end)
|
||||
if not s then return nil, e end
|
||||
return table.concat(l)
|
||||
end,
|
||||
--- same as eval_text, but instead of building a Lua string, call callback for every evaluated part of the text
|
||||
|
|
@ -436,15 +469,14 @@ common = {
|
|||
end,
|
||||
--- tag management
|
||||
tags = {
|
||||
--- push new tags on top of the stack, from Anselme values
|
||||
--- push new tags on top of the stack, from Anselme values. val is expected to be a map.
|
||||
push = function(self, state, val)
|
||||
local new = { type = "list", value = {} }
|
||||
local new = { type = "map", value = {} }
|
||||
-- copy
|
||||
local last = self:current(state)
|
||||
for _, v in ipairs(last.value) do table.insert(new.value, v) end
|
||||
for k, v in pairs(last.value) do new.value[k] = v end
|
||||
-- append new values
|
||||
if val.type ~= "list" then val = { type = "list", value = { val } } end
|
||||
for _, v in ipairs(val.value) do table.insert(new.value, v) end
|
||||
for k, v in pairs(val.value) do new.value[k] = v end
|
||||
-- add
|
||||
table.insert(state.interpreter.tags, new)
|
||||
end,
|
||||
|
|
@ -458,7 +490,7 @@ common = {
|
|||
end,
|
||||
--- return current lua tags table
|
||||
current = function(self, state)
|
||||
return state.interpreter.tags[#state.interpreter.tags] or { type = "list", value = {} }
|
||||
return state.interpreter.tags[#state.interpreter.tags] or { type = "map", value = {} }
|
||||
end,
|
||||
--- returns length of tags stack
|
||||
len = function(self, state)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
local expression
|
||||
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 to_lua, from_lua, eval_text, truthy, format, pretty_type, get_variable, tags, eval_text_callback, events, flatten_list, set_variable, scope, check_constraint, hash
|
||||
|
||||
local run
|
||||
|
||||
|
|
@ -52,6 +52,30 @@ local function eval(state, exp)
|
|||
value = {}
|
||||
}
|
||||
end
|
||||
-- map defined in brackets
|
||||
elseif exp.type == "map_brackets" then
|
||||
-- get constructing list
|
||||
local list, e = eval(state, { type = "list_brackets", expression = exp.expression })
|
||||
if not list then return nil, e end
|
||||
-- make map
|
||||
local map = {}
|
||||
for i, v in ipairs(list.value) do
|
||||
local key, value
|
||||
if v.type == "pair" then
|
||||
key = v.value[1]
|
||||
value = v.value[2]
|
||||
else
|
||||
key = { type = "number", value = i }
|
||||
value = v
|
||||
end
|
||||
local h, err = hash(key)
|
||||
if not h then return nil, err end
|
||||
map[h] = { key, value }
|
||||
end
|
||||
return {
|
||||
type = "map",
|
||||
value = map
|
||||
}
|
||||
-- list defined using , operator
|
||||
elseif exp.type == "list" then
|
||||
local flat = flatten_list(exp)
|
||||
|
|
@ -142,7 +166,7 @@ local function eval(state, exp)
|
|||
}
|
||||
-- tag
|
||||
elseif exp.type == "#" then
|
||||
local right, righte = eval(state, exp.right)
|
||||
local right, righte = eval(state, { type = "map_brackets", expression = exp.right })
|
||||
if not right then return nil, righte end
|
||||
tags:push(state, right)
|
||||
local left, lefte = eval(state, exp.left)
|
||||
|
|
@ -165,12 +189,24 @@ local function eval(state, exp)
|
|||
}
|
||||
-- function
|
||||
elseif exp.type == "function call" then
|
||||
-- eval args: list_brackets
|
||||
-- eval args: map_brackets
|
||||
local args = {}
|
||||
local last_contiguous_positional = 0
|
||||
if exp.argument then
|
||||
local arg, arge = eval(state, exp.argument)
|
||||
if not arg then return nil, arge end
|
||||
args = arg.value
|
||||
-- map into args table
|
||||
for _, v in pairs(arg.value) do
|
||||
if v[1].type == "string" or v[1].type == "number" then
|
||||
args[v[1].value] = v[2]
|
||||
else
|
||||
return nil, ("unexpected key of type %s in argument map; keys must be string or number"):format(v[1].type)
|
||||
end
|
||||
end
|
||||
-- get length of contiguous positional arguments (#args may not be always be equal depending on implementation...)
|
||||
for i, _ in ipairs(args) do
|
||||
last_contiguous_positional = i
|
||||
end
|
||||
end
|
||||
-- function reference: call the referenced function
|
||||
local variants = exp.variants
|
||||
|
|
@ -191,13 +227,6 @@ local function eval(state, exp)
|
|||
end
|
||||
end
|
||||
end
|
||||
-- map named arguments
|
||||
local named_args = {}
|
||||
for i, arg in ipairs(args) do
|
||||
if arg.type == "pair" and arg.value[1].type == "string" then
|
||||
named_args[arg.value[1].value] = { i, arg.value[2] }
|
||||
end
|
||||
end
|
||||
-- eval assignment arg
|
||||
local assignment
|
||||
if exp.assignment then
|
||||
|
|
@ -222,21 +251,21 @@ local function eval(state, exp)
|
|||
for j, param in ipairs(fn.params) do
|
||||
local val
|
||||
-- named
|
||||
if param.alias and named_args[param.alias] then
|
||||
val = named_args[param.alias][2]
|
||||
used_args[named_args[param.alias][1]] = true
|
||||
elseif named_args[param.name] then
|
||||
val = named_args[param.name][2]
|
||||
used_args[named_args[param.name][1]] = true
|
||||
if param.alias and args[param.alias] then
|
||||
val = args[param.alias]
|
||||
used_args[param.alias] = true
|
||||
elseif args[param.name] then
|
||||
val = args[param.name]
|
||||
used_args[param.name] = true
|
||||
-- vararg
|
||||
elseif param.vararg then
|
||||
val = { type = "list", value = {} }
|
||||
for k=j, #args do
|
||||
for k=j, last_contiguous_positional do
|
||||
table.insert(val.value, args[k])
|
||||
used_args[k] = true
|
||||
end
|
||||
-- positional
|
||||
elseif args[j] and args[j].type ~= "pair" then
|
||||
elseif args[j] then
|
||||
val = args[j]
|
||||
used_args[j] = true
|
||||
end
|
||||
|
|
@ -264,8 +293,8 @@ local function eval(state, exp)
|
|||
end
|
||||
-- check for unused arguments
|
||||
if ok then
|
||||
for i, arg in ipairs(args) do
|
||||
if not used_args[i] then
|
||||
for key, arg in pairs(args) do
|
||||
if not used_args[key] then
|
||||
ok = false
|
||||
if arg.type == "pair" and arg.value[1].type == "string" then
|
||||
table.insert(tried_function_error_messages, ("%s: unexpected %s argument"):format(fn.pretty_signature, arg.value[1].value))
|
||||
|
|
@ -473,11 +502,10 @@ local function eval(state, exp)
|
|||
end
|
||||
-- no matching function found
|
||||
local args_txt = {}
|
||||
for _, arg in ipairs(args) do
|
||||
for key, arg in pairs(args) do
|
||||
local s = ""
|
||||
if arg.type == "pair" and arg.value[1].type == "string" then
|
||||
s = s .. ("%s="):format(arg.value[1].value)
|
||||
arg = arg.value[2]
|
||||
if type(key) == "string" or (type(key) == "number" and key > last_contiguous_positional) then
|
||||
s = s .. ("%s="):format(key)
|
||||
end
|
||||
s = s .. pretty_type(arg)
|
||||
table.insert(args_txt, s)
|
||||
|
|
@ -511,6 +539,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, 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
|
||||
to_lua, from_lua, eval_text, truthy, format, pretty_type, get_variable, tags, eval_text_callback, events, set_variable, scope, check_constraint, hash = 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, common.hash
|
||||
|
||||
return eval
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue