mirror of
https://github.com/Reuh/anselme.git
synced 2025-10-27 16:49:31 +00:00
Proper checkpointing of mutable values
This commit is contained in:
parent
801df67461
commit
04c6683de8
14 changed files with 243 additions and 23 deletions
|
|
@ -49,6 +49,8 @@ $ parallel
|
||||||
parallel: {main.var}
|
parallel: {main.var}
|
||||||
|
|
||||||
~ main
|
~ main
|
||||||
|
|
||||||
|
(note: if two scripts try to modify the same value at the same time, one of them will win, but which one is undefined/a surprise)
|
||||||
```
|
```
|
||||||
|
|
||||||
The purpose of this system is both to allow several scripts to run at the same time with an easy way to avoid interferences, and to make sure the global state is always in a consistent (and not in the middle of a calculation): since scripts can be interrupted at any time, when it is interrupted, anything that was changed between the last checkpoint and the interruption will be discarded. If you're a RDBMS person, that's more-or-less equivalent to a transaction with a repeatable read isolation level (without any sort of locking or lost update protection though).
|
The purpose of this system is both to allow several scripts to run at the same time with an easy way to avoid interferences, and to make sure the global state is always in a consistent (and not in the middle of a calculation): since scripts can be interrupted at any time, when it is interrupted, anything that was changed between the last checkpoint and the interruption will be discarded. If you're a RDBMS person, that's more-or-less equivalent to a transaction with a repeatable read isolation level (without any sort of locking or lost update protection though).
|
||||||
|
|
|
||||||
|
|
@ -479,11 +479,13 @@ local vm_mt = {
|
||||||
__index = function(variables, k)
|
__index = function(variables, k)
|
||||||
local cache = getmetatable(variables).cache
|
local cache = getmetatable(variables).cache
|
||||||
if cache[k] == nil then
|
if cache[k] == nil then
|
||||||
cache[k] = copy(self.state.variables[k])
|
cache[k] = copy(self.state.variables[k], getmetatable(variables).copy_cache)
|
||||||
end
|
end
|
||||||
return cache[k]
|
return cache[k]
|
||||||
end,
|
end,
|
||||||
cache = {} -- cache of previously read values, to get repeatable reads & handle mutable types without changing global state
|
copy_cache = {}, -- table of [original table] = copied table
|
||||||
|
modified_tables = {}, -- list of modified tables (copies) that should be merged with global state on next checkpoint
|
||||||
|
cache = {} -- cache of previously read values (copies), to get repeatable reads & handle mutable types without changing global state
|
||||||
}),
|
}),
|
||||||
interpreter = {
|
interpreter = {
|
||||||
-- constant
|
-- constant
|
||||||
|
|
|
||||||
43
common.lua
43
common.lua
|
|
@ -1,6 +1,20 @@
|
||||||
|
--- replace values recursively in table t according to to_replace ([old table] = new table)
|
||||||
|
local function replace_in_table(t, to_replace, already_replaced)
|
||||||
|
already_replaced = already_replaced or {}
|
||||||
|
already_replaced[t] = true
|
||||||
|
for k, v in pairs(t) do
|
||||||
|
if to_replace[v] then
|
||||||
|
t[k] = to_replace[v]
|
||||||
|
elseif type(v) == "table" and not already_replaced[v] then
|
||||||
|
replace_in_table(v, to_replace, already_replaced)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local common
|
local common
|
||||||
common = {
|
common = {
|
||||||
--- recursively copy a table, handle cyclic references, no metatable
|
--- recursively copy a table, handle cyclic references, no metatable, don't copy keys
|
||||||
|
-- cache is table with copied tables [original table] = copied value, will use temporary table is omitted
|
||||||
copy = function(t, cache)
|
copy = function(t, cache)
|
||||||
if type(t) == "table" then
|
if type(t) == "table" then
|
||||||
cache = cache or {}
|
cache = cache or {}
|
||||||
|
|
@ -17,6 +31,33 @@ common = {
|
||||||
else
|
else
|
||||||
return t
|
return t
|
||||||
end
|
end
|
||||||
|
end,
|
||||||
|
--- given a table t from which some copy was issued, the copy cache, and a list of tables from the copied version,
|
||||||
|
-- put theses copied tables in t in place of their original values, preserving references to non-modified values
|
||||||
|
replace_with_copied_values = function(t, cache, copied_to_replace)
|
||||||
|
-- reverse copy cache
|
||||||
|
local ehcac = {}
|
||||||
|
for k, v in pairs(cache) do ehcac[v] = k end
|
||||||
|
-- build table of [original table] = replacement copied table
|
||||||
|
local to_replace = {}
|
||||||
|
for _, v in ipairs(copied_to_replace) do
|
||||||
|
local original = ehcac[v]
|
||||||
|
if original then -- table doesn't have an original value if it's a new table...
|
||||||
|
to_replace[original] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- fix references to not-modified tables in modified values
|
||||||
|
local not_modified = {}
|
||||||
|
for original, modified in pairs(cache) do
|
||||||
|
if not to_replace[original] then
|
||||||
|
not_modified[modified] = original
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for _, m in ipairs(copied_to_replace) do
|
||||||
|
replace_in_table(m, not_modified)
|
||||||
|
end
|
||||||
|
-- replace
|
||||||
|
replace_in_table(t, to_replace)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
return common
|
return common
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
local atypes, ltypes
|
local atypes, ltypes
|
||||||
local eval, run_block
|
local eval, run_block
|
||||||
local copy
|
local replace_with_copied_values
|
||||||
local common
|
local common
|
||||||
|
|
||||||
--- copy some text & process it to be suited to be sent to Lua in an event
|
--- copy some text & process it to be suited to be sent to Lua in an event
|
||||||
|
|
@ -49,17 +49,13 @@ common = {
|
||||||
global.aliases[alias] = fqm
|
global.aliases[alias] = fqm
|
||||||
state.aliases[alias] = nil
|
state.aliases[alias] = nil
|
||||||
end
|
end
|
||||||
-- variable state
|
-- merge modified mutable varables
|
||||||
-- move values modifed in-place from read cache to variables
|
local mt = getmetatable(state.variables)
|
||||||
local cache = getmetatable(state.variables).cache
|
replace_with_copied_values(global.variables, mt.copy_cache, mt.modified_tables)
|
||||||
for var, value in pairs(cache) do
|
mt.copy_cache = {}
|
||||||
if value.modified then
|
mt.modified = {}
|
||||||
value.modified = nil
|
mt.cache = {}
|
||||||
state.variables[var] = value
|
-- merge modified re-assigned variables
|
||||||
end
|
|
||||||
cache[var] = nil
|
|
||||||
end
|
|
||||||
-- merge modified variables
|
|
||||||
for var, value in pairs(state.variables) do
|
for var, value in pairs(state.variables) do
|
||||||
global.variables[var] = value
|
global.variables[var] = value
|
||||||
state.variables[var] = nil
|
state.variables[var] = nil
|
||||||
|
|
@ -109,6 +105,23 @@ common = {
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return true
|
return true
|
||||||
|
elseif a.type == "function reference" then
|
||||||
|
if #a.value ~= #b.value then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
for _, aname in ipairs(a.value) do
|
||||||
|
local found = false
|
||||||
|
for _, bname in ipairs(b.value) do
|
||||||
|
if aname == bname then
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not found then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
else
|
else
|
||||||
return a.value == b.value
|
return a.value == b.value
|
||||||
end
|
end
|
||||||
|
|
@ -394,6 +407,6 @@ local types = require((...):gsub("interpreter%.common$", "stdlib.types"))
|
||||||
atypes, ltypes = types.anselme, types.lua
|
atypes, ltypes = types.anselme, types.lua
|
||||||
eval = require((...):gsub("common$", "expression"))
|
eval = require((...):gsub("common$", "expression"))
|
||||||
run_block = require((...):gsub("common$", "interpreter")).run_block
|
run_block = require((...):gsub("common$", "interpreter")).run_block
|
||||||
copy = require((...):gsub("interpreter%.common$", "common")).copy
|
replace_with_copied_values = require((...):gsub("interpreter%.common$", "common")).replace_with_copied_values
|
||||||
|
|
||||||
return common
|
return common
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
local truthy, anselme, compare, is_of_type, identifier_pattern, format_identifier, find, get_variable
|
local truthy, anselme, compare, is_of_type, identifier_pattern, format_identifier, find, get_variable
|
||||||
|
|
||||||
|
local function mark_as_modified(v)
|
||||||
|
local modified = getmetatable(anselme.running.state.variables).modified_tables
|
||||||
|
table.insert(modified, v)
|
||||||
|
end
|
||||||
|
|
||||||
local functions
|
local functions
|
||||||
functions = {
|
functions = {
|
||||||
-- discard left
|
-- discard left
|
||||||
|
|
@ -107,23 +112,23 @@ functions = {
|
||||||
["()(l::list, i::number) := v"] = {
|
["()(l::list, i::number) := v"] = {
|
||||||
mode = "raw",
|
mode = "raw",
|
||||||
value = function(l, i, v)
|
value = function(l, i, v)
|
||||||
l.modified = true
|
|
||||||
local lv = l.type == "type" and l.value[1] or l
|
local lv = l.type == "type" and l.value[1] or l
|
||||||
local iv = i.type == "type" and i.value[1] or i
|
local iv = i.type == "type" and i.value[1] or i
|
||||||
lv.value[iv.value] = v
|
lv.value[iv.value] = v
|
||||||
|
mark_as_modified(lv.value)
|
||||||
return v
|
return v
|
||||||
end
|
end
|
||||||
},
|
},
|
||||||
["()(l::list, k::string) := v"] = {
|
["()(l::list, k::string) := v"] = {
|
||||||
mode = "raw",
|
mode = "raw",
|
||||||
value = function(l, k, v)
|
value = function(l, k, v)
|
||||||
l.modified = true
|
|
||||||
local lv = l.type == "type" and l.value[1] or l
|
local lv = l.type == "type" and l.value[1] or l
|
||||||
local kv = k.type == "type" and k.value[1] or k
|
local kv = k.type == "type" and k.value[1] or k
|
||||||
-- update index
|
-- update index
|
||||||
for _, x in ipairs(lv.value) do
|
for _, x in ipairs(lv.value) do
|
||||||
if x.type == "pair" and compare(x.value[1], kv) then
|
if x.type == "pair" and compare(x.value[1], kv) then
|
||||||
x.value[2] = v
|
x.value[2] = v
|
||||||
|
mark_as_modified(x.value)
|
||||||
return v
|
return v
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -132,6 +137,7 @@ functions = {
|
||||||
type = "pair",
|
type = "pair",
|
||||||
value = { kv, v }
|
value = { kv, v }
|
||||||
})
|
})
|
||||||
|
mark_as_modified(lv.value)
|
||||||
return v
|
return v
|
||||||
end
|
end
|
||||||
},
|
},
|
||||||
|
|
@ -192,33 +198,33 @@ functions = {
|
||||||
["insert(l::list, v)"] = {
|
["insert(l::list, v)"] = {
|
||||||
mode = "raw",
|
mode = "raw",
|
||||||
value = function(l, v)
|
value = function(l, v)
|
||||||
l.modified = true
|
|
||||||
local lv = l.type == "type" and l.value[1] or l
|
local lv = l.type == "type" and l.value[1] or l
|
||||||
table.insert(lv.value, v)
|
table.insert(lv.value, v)
|
||||||
|
mark_as_modified(lv.value)
|
||||||
return l
|
return l
|
||||||
end
|
end
|
||||||
},
|
},
|
||||||
["insert(l::list, i::number, v)"] = {
|
["insert(l::list, i::number, v)"] = {
|
||||||
mode = "raw",
|
mode = "raw",
|
||||||
value = function(l, i, v)
|
value = function(l, i, v)
|
||||||
l.modified = true
|
|
||||||
local lv = l.type == "type" and l.value[1] or l
|
local lv = l.type == "type" and l.value[1] or l
|
||||||
local iv = i.type == "type" and i.value[1] or i
|
local iv = i.type == "type" and i.value[1] or i
|
||||||
table.insert(lv.value, iv.value, v)
|
table.insert(lv.value, iv.value, v)
|
||||||
|
mark_as_modified(lv.value)
|
||||||
return l
|
return l
|
||||||
end
|
end
|
||||||
},
|
},
|
||||||
["remove(l::list)"] = {
|
["remove(l::list)"] = {
|
||||||
mode = "untyped raw",
|
mode = "untyped raw",
|
||||||
value = function(l)
|
value = function(l)
|
||||||
l.modified = true
|
mark_as_modified(l.value)
|
||||||
return table.remove(l.value)
|
return table.remove(l.value)
|
||||||
end
|
end
|
||||||
},
|
},
|
||||||
["remove(l::list, i::number)"] = {
|
["remove(l::list, i::number)"] = {
|
||||||
mode = "untyped raw",
|
mode = "untyped raw",
|
||||||
value = function(l, i)
|
value = function(l, i)
|
||||||
l.modified = true
|
mark_as_modified(l.value)
|
||||||
return table.remove(l.value, i.value)
|
return table.remove(l.value, i.value)
|
||||||
end
|
end
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ local function write_result(filebase, result)
|
||||||
o:write(ser(result))
|
o:write(ser(result))
|
||||||
o:write("\n--[[\n")
|
o:write("\n--[[\n")
|
||||||
for _, v in ipairs(result) do
|
for _, v in ipairs(result) do
|
||||||
o:write(inspect(v).."\n")
|
o:write(inspect(v):gsub("]]", "] ]").."\n") -- professional-level bandaid when ]] appear in the output
|
||||||
end
|
end
|
||||||
o:write("]]--")
|
o:write("]]--")
|
||||||
o:close()
|
o:close()
|
||||||
|
|
|
||||||
17
test/tests/merge nested mutable bis.ans
Normal file
17
test/tests/merge nested mutable bis.ans
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
:post run = "check"
|
||||||
|
|
||||||
|
:a = [1]
|
||||||
|
:b = [2]
|
||||||
|
|
||||||
|
~ a!insert(b)
|
||||||
|
|
||||||
|
§ c
|
||||||
|
|
||||||
|
~ b!insert(3)
|
||||||
|
|
||||||
|
§ d
|
||||||
|
|
||||||
|
~ b!insert(4)
|
||||||
|
|
||||||
|
$ check
|
||||||
|
\[1,\[2,3,4]]: {a}
|
||||||
21
test/tests/merge nested mutable bis.lua
Normal file
21
test/tests/merge nested mutable bis.lua
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
local _={}
|
||||||
|
_[8]={}
|
||||||
|
_[7]={}
|
||||||
|
_[6]={text="[1, [2, 3, 4]]",tags=_[8]}
|
||||||
|
_[5]={text="[1,[2,3,4]]: ",tags=_[7]}
|
||||||
|
_[4]={_[5],_[6]}
|
||||||
|
_[3]={"return"}
|
||||||
|
_[2]={"text",_[4]}
|
||||||
|
_[1]={"return"}
|
||||||
|
return {_[1],_[2],_[3]}
|
||||||
|
--[[
|
||||||
|
{ "return" }
|
||||||
|
{ "text", { {
|
||||||
|
tags = {},
|
||||||
|
text = "[1,[2,3,4] ]: "
|
||||||
|
}, {
|
||||||
|
tags = {},
|
||||||
|
text = "[1, [2, 3, 4] ]"
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
19
test/tests/merge nested mutable error bis.ans
Normal file
19
test/tests/merge nested mutable error bis.ans
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
:post run = "check"
|
||||||
|
|
||||||
|
:a = [1]
|
||||||
|
:b = [2]
|
||||||
|
|
||||||
|
~ a!insert(b)
|
||||||
|
|
||||||
|
§ c
|
||||||
|
|
||||||
|
~ b!insert(3)
|
||||||
|
|
||||||
|
§ d
|
||||||
|
|
||||||
|
~ b!insert(4)
|
||||||
|
|
||||||
|
~ error("abort")
|
||||||
|
|
||||||
|
$ check
|
||||||
|
\[1,\[2,3]]: {a}
|
||||||
21
test/tests/merge nested mutable error bis.lua
Normal file
21
test/tests/merge nested mutable error bis.lua
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
local _={}
|
||||||
|
_[8]={}
|
||||||
|
_[7]={}
|
||||||
|
_[6]={text="[1, [2, 3]]",tags=_[8]}
|
||||||
|
_[5]={text="[1,[2,3]]: ",tags=_[7]}
|
||||||
|
_[4]={_[5],_[6]}
|
||||||
|
_[3]={"return"}
|
||||||
|
_[2]={"text",_[4]}
|
||||||
|
_[1]={"error","abort; in Lua function \"error\"; at test/tests/merge nested mutable error bis.ans:16"}
|
||||||
|
return {_[1],_[2],_[3]}
|
||||||
|
--[[
|
||||||
|
{ "error", 'abort; in Lua function "error"; at test/tests/merge nested mutable error bis.ans:16' }
|
||||||
|
{ "text", { {
|
||||||
|
tags = {},
|
||||||
|
text = "[1,[2,3] ]: "
|
||||||
|
}, {
|
||||||
|
tags = {},
|
||||||
|
text = "[1, [2, 3] ]"
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
19
test/tests/merge nested mutable error.ans
Normal file
19
test/tests/merge nested mutable error.ans
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
:post run = "check"
|
||||||
|
|
||||||
|
:a = [1]
|
||||||
|
:b = [2]
|
||||||
|
|
||||||
|
~ a!insert(b)
|
||||||
|
|
||||||
|
§ c
|
||||||
|
|
||||||
|
~ b!insert(3)
|
||||||
|
|
||||||
|
§ d
|
||||||
|
|
||||||
|
~ a!insert(4)
|
||||||
|
|
||||||
|
~ error("abort")
|
||||||
|
|
||||||
|
$ check
|
||||||
|
\[1,\[2,3]]: {a}
|
||||||
21
test/tests/merge nested mutable error.lua
Normal file
21
test/tests/merge nested mutable error.lua
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
local _={}
|
||||||
|
_[8]={}
|
||||||
|
_[7]={}
|
||||||
|
_[6]={text="[1, [2, 3]]",tags=_[8]}
|
||||||
|
_[5]={text="[1,[2,3]]: ",tags=_[7]}
|
||||||
|
_[4]={_[5],_[6]}
|
||||||
|
_[3]={"return"}
|
||||||
|
_[2]={"text",_[4]}
|
||||||
|
_[1]={"error","abort; in Lua function \"error\"; at test/tests/merge nested mutable error.ans:16"}
|
||||||
|
return {_[1],_[2],_[3]}
|
||||||
|
--[[
|
||||||
|
{ "error", 'abort; in Lua function "error"; at test/tests/merge nested mutable error.ans:16' }
|
||||||
|
{ "text", { {
|
||||||
|
tags = {},
|
||||||
|
text = "[1,[2,3] ]: "
|
||||||
|
}, {
|
||||||
|
tags = {},
|
||||||
|
text = "[1, [2, 3] ]"
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
17
test/tests/merge nested mutable.ans
Normal file
17
test/tests/merge nested mutable.ans
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
:post run = "check"
|
||||||
|
|
||||||
|
:a = [1]
|
||||||
|
:b = [2]
|
||||||
|
|
||||||
|
~ a!insert(b)
|
||||||
|
|
||||||
|
§ c
|
||||||
|
|
||||||
|
~ b!insert(3)
|
||||||
|
|
||||||
|
§ d
|
||||||
|
|
||||||
|
~ a!insert(4)
|
||||||
|
|
||||||
|
$ check
|
||||||
|
\[1,\[2,3],4]: {a}
|
||||||
21
test/tests/merge nested mutable.lua
Normal file
21
test/tests/merge nested mutable.lua
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
local _={}
|
||||||
|
_[8]={}
|
||||||
|
_[7]={}
|
||||||
|
_[6]={text="[1, [2, 3], 4]",tags=_[8]}
|
||||||
|
_[5]={text="[1,[2,3],4]: ",tags=_[7]}
|
||||||
|
_[4]={_[5],_[6]}
|
||||||
|
_[3]={"return"}
|
||||||
|
_[2]={"text",_[4]}
|
||||||
|
_[1]={"return"}
|
||||||
|
return {_[1],_[2],_[3]}
|
||||||
|
--[[
|
||||||
|
{ "return" }
|
||||||
|
{ "text", { {
|
||||||
|
tags = {},
|
||||||
|
text = "[1,[2,3],4]: "
|
||||||
|
}, {
|
||||||
|
tags = {},
|
||||||
|
text = "[1, [2, 3], 4]"
|
||||||
|
} } }
|
||||||
|
{ "return" }
|
||||||
|
]]--
|
||||||
Loading…
Add table
Add a link
Reference in a new issue