From 9a38dfa34f4371a0edd9cd4c5ce965c85962b5d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Reuh=20Fildadut?= Date: Wed, 3 Jan 2024 19:45:36 +0100 Subject: [PATCH] Persist LuaFunctions & add tests --- anselme/ast/LuaFunction.lua | 8 ------- anselme/ast/abstract/Node.lua | 16 +++++++++++++ anselme/lib/binser.lua | 32 +++++++++++++++++++++++-- anselme/state/State.lua | 14 ++++------- test/results/serialize function.ans | 7 ++++++ test/results/serialize lua function.ans | 7 ++++++ test/results/serialize.ans | 11 +++++++++ test/run.lua | 2 ++ test/tests/serialize function.ans | 10 ++++++++ test/tests/serialize lua function.ans | 7 ++++++ test/tests/serialize.ans | 13 ++++++++++ 11 files changed, 108 insertions(+), 19 deletions(-) create mode 100644 test/results/serialize function.ans create mode 100644 test/results/serialize lua function.ans create mode 100644 test/results/serialize.ans create mode 100644 test/tests/serialize function.ans create mode 100644 test/tests/serialize lua function.ans create mode 100644 test/tests/serialize.ans diff --git a/anselme/ast/LuaFunction.lua b/anselme/ast/LuaFunction.lua index a2c432e..9d87f7c 100644 --- a/anselme/ast/LuaFunction.lua +++ b/anselme/ast/LuaFunction.lua @@ -75,14 +75,6 @@ LuaFunction = ast.abstract.Runtime(Overloadable) { to_lua = function(self, state) return self.func end, - - -- TODO: binser does not serialize lua function upvalues! - _serialize = function(self) - error("LuaFunction can not be serialized") - end, - _deserialize = function(self) - error("LuaFunction can not be serialized") - end } package.loaded[...] = LuaFunction diff --git a/anselme/ast/abstract/Node.lua b/anselme/ast/abstract/Node.lua index 870a304..f4204da 100644 --- a/anselme/ast/abstract/Node.lua +++ b/anselme/ast/abstract/Node.lua @@ -306,6 +306,22 @@ Node = class { binser.register(self, self.type) end end, + -- return a serialized representation of the node + -- can redefine _serialize and _deserialize to customize the serialization, see binser docs + serialize = function(self, state) + package.loaded["anselme.serializer_state"] = state + local r = binser.serialize(self) + package.loaded["anselme.serializer_state"] = nil + return r + end, + -- return the deserialized Node + -- class method + deserialize = function(self, state, str, index) + package.loaded["anselme.serializer_state"] = state + local r = binser.deserializeN(str, 1, index) + package.loaded["anselme.serializer_state"] = nil + return r + end, __tostring = function(self) return self:format() end, diff --git a/anselme/lib/binser.lua b/anselme/lib/binser.lua index a427398..70c8b8b 100644 --- a/anselme/lib/binser.lua +++ b/anselme/lib/binser.lua @@ -1,3 +1,5 @@ +-- TODO: upstream + -- binser.lua --[[ @@ -39,6 +41,8 @@ local floor = math.floor local frexp = math.frexp local unpack = unpack or table.unpack local huge = math.huge +local getupvalue = debug.getupvalue +local setupvalue = debug.setupvalue -- Lua 5.3 frexp polyfill -- From https://github.com/excessive/cpml/blob/master/modules/utils.lua @@ -220,6 +224,7 @@ local function newbinser() -- RESOURCE = 211 -- INT64 = 212 -- TABLE WITH META = 213 + -- FUNCTION WITH UPVALUES = 214 local mts = {} local ids = {} @@ -342,9 +347,19 @@ local function newbinser() visited[x] = visited[NEXT] visited[NEXT] = visited[NEXT] + 1 local str = dump(x) - accum[#accum + 1] = "\210" + accum[#accum + 1] = "\214" accum[#accum + 1] = number_to_str(#str) accum[#accum + 1] = str + local upvalues = {} + local i = 1 + repeat + local name, value = getupvalue(x, i) + if name and name ~= "_ENV" then + upvalues[name] = value + end + i = i + 1 + until not name + types.table(upvalues, visited, accum) end end @@ -433,13 +448,26 @@ local function newbinser() local ret = deserializers[name](unpack(args)) visited[#visited + 1] = ret return ret, nextindex - elseif t == 210 then + elseif t == 210 or t == 214 then local length, dataindex = number_from_str(str, index + 1) local nextindex = dataindex + length if not (length >= 0) then error("Bad string length") end if #str < nextindex - 1 then error("Expected more bytes of string") end local ret = loadstring(sub(str, dataindex, nextindex - 1)) visited[#visited + 1] = ret + if t == 214 then + local upvalues + upvalues, nextindex = deserialize_value(str, nextindex, visited) + if type(upvalues) ~= "table" then error("Expected function upvalues") end + local i = 1 + repeat + local name = getupvalue(ret, i) + if name and upvalues[name] ~= nil then + setupvalue(ret, i, upvalues[name]) + end + i = i + 1 + until not name + end return ret, nextindex elseif t == 211 then local resname, nextindex = deserialize_value(str, index + 1, visited) diff --git a/anselme/state/State.lua b/anselme/state/State.lua index 18b670b..5c4af20 100644 --- a/anselme/state/State.lua +++ b/anselme/state/State.lua @@ -12,7 +12,7 @@ local parser = require("anselme.parser") local binser = require("anselme.lib.binser") local assert0 = require("anselme.common").assert0 local anselme -local Identifier, Return +local Identifier, Return, Node local State State = class { @@ -114,19 +114,15 @@ State = class { -- This can be loaded back later using `:load`. save = function(self) local struct = persistent_manager:get_struct(self) - package.loaded["anselme.serializer_state"] = self - local r = binser.serialize(anselme.versions.save, struct) - package.loaded["anselme.serializer_state"] = nil - return r + return binser.serialize(anselme.versions.save) .. struct:serialize(self) end, --- Load a string generated by `:save`. -- -- Variables that already exist will be overwritten with the loaded data. load = function(self, save) - package.loaded["anselme.serializer_state"] = self - local version, struct = binser.deserializeN(save, 2) - package.loaded["anselme.serializer_state"] = nil + local version, nextindex = binser.deserializeN(save, 1) if version ~= anselme.versions.save then print("Loading a save file generated by a different Anselme version, things may break!") end + local struct = Node:deserialize(self, save, nextindex) for key, val in struct:iter() do persistent_manager:set(self, key, val) end @@ -246,6 +242,6 @@ State = class { package.loaded[...] = State anselme = require("anselme") local ast = require("anselme.ast") -Identifier, Return = ast.Identifier, ast.Return +Identifier, Return, Node = ast.Identifier, ast.Return, ast.abstract.Node return State diff --git a/test/results/serialize function.ans b/test/results/serialize function.ans new file mode 100644 index 0000000..4f6e7b7 --- /dev/null +++ b/test/results/serialize function.ans @@ -0,0 +1,7 @@ +--# run #-- +--- text --- +| {}"" {}"bar1" {}"" | +--- return --- +() +--# saved #-- +{} \ No newline at end of file diff --git a/test/results/serialize lua function.ans b/test/results/serialize lua function.ans new file mode 100644 index 0000000..0fc682c --- /dev/null +++ b/test/results/serialize lua function.ans @@ -0,0 +1,7 @@ +--# run #-- +--- text --- +| {}"" {}"2" {}"" | +--- return --- +() +--# saved #-- +{} \ No newline at end of file diff --git a/test/results/serialize.ans b/test/results/serialize.ans new file mode 100644 index 0000000..426c21e --- /dev/null +++ b/test/results/serialize.ans @@ -0,0 +1,11 @@ +--# run #-- +--- text --- +| {}"" {}"43" {}"" | +--- text --- +| {}"" {}"[2, \"\", 5]" {}"" | +--- text --- +| {}"" {}"*{\"f\":\"b\", 1:2, 2:\"\", 3:\"ok\"}" {}"" | +--- return --- +() +--# saved #-- +{} \ No newline at end of file diff --git a/test/run.lua b/test/run.lua index 05ef85a..764014a 100644 --- a/test/run.lua +++ b/test/run.lua @@ -88,6 +88,8 @@ local function run(path, interactive) run_loop(parallel_state, write_output, interactive) write_output("--# main script #--") end) + state:define("serialize", "(value)", function(state, value) return ast.String:new(value:serialize(state)) end, true) + state:define("deserialize", "(str::string)", function(state, str) return ast.abstract.Node:deserialize(state, str.string) end, true) local run_state = state:branch() diff --git a/test/tests/serialize function.ans b/test/tests/serialize function.ans new file mode 100644 index 0000000..b009d40 --- /dev/null +++ b/test/tests/serialize function.ans @@ -0,0 +1,10 @@ +:f = () +_ + :$_+_(a::string, b) + "{a}{b}" + + :a=1 + f = $(x) + x+a + +|{(f!serialize!deserialize)("bar")} diff --git a/test/tests/serialize lua function.ans b/test/tests/serialize lua function.ans new file mode 100644 index 0000000..94fa1ec --- /dev/null +++ b/test/tests/serialize lua function.ans @@ -0,0 +1,7 @@ +:f = () +_ + :a=1 + f = $() + 1+a + +|{(f!serialize!deserialize)!} diff --git a/test/tests/serialize.ans b/test/tests/serialize.ans new file mode 100644 index 0000000..fce8c7b --- /dev/null +++ b/test/tests/serialize.ans @@ -0,0 +1,13 @@ +:x = 43 + +|{x!serialize!deserialize} + +:l = [2, "", 5] + +|{l!serialize!deserialize} + +:t = *{2, "", 5} +t(3) = "ok" +t("f") = "b" + +|{t!serialize!deserialize}