292 lines
7.7 KiB
Lua
Executable File
292 lines
7.7 KiB
Lua
Executable File
--------------------------------------------------------------------------------
|
|
---------------------- ## ##### ##### ###### -----------------------
|
|
---------------------- ## ## ## ## ## ## ## -----------------------
|
|
---------------------- ## ## ## ## ## ###### -----------------------
|
|
---------------------- ## ## ## ## ## ## -----------------------
|
|
---------------------- ###### ##### ##### ## -----------------------
|
|
---------------------- -----------------------
|
|
----------------------- Lua Object-Oriented Programming ------------------------
|
|
--------------------------------------------------------------------------------
|
|
-- Project: LOOP Class Library --
|
|
-- Release: 2.3 beta --
|
|
-- Title : Serializer that Serialize Values to Lua Code --
|
|
-- Author : Renato Maia <maia@inf.puc-rio.br> --
|
|
--------------------------------------------------------------------------------
|
|
|
|
local _G = _G
|
|
local getmetatable = getmetatable
|
|
local setmetatable = setmetatable
|
|
local getfenv = getfenv
|
|
local setfenv = setfenv
|
|
local package = package
|
|
local assert = assert
|
|
local select = select
|
|
local pairs = pairs
|
|
local pcall = pcall
|
|
local ipairs = ipairs
|
|
local loadstring = loadstring
|
|
local rawget = rawget
|
|
local rawset = rawset
|
|
local require = require
|
|
local tostring = tostring
|
|
local tonumber = tonumber
|
|
local error = error
|
|
local type = type
|
|
|
|
local debug = debug
|
|
local string = require "string"
|
|
local table = require "loop.table"
|
|
local oo = require "loop.base"
|
|
|
|
module("loop.serial.Serializer", oo.class)
|
|
|
|
__mode = "k"
|
|
|
|
namespace = "serial"
|
|
|
|
------------------------------------------------------------------------------
|
|
_M.globals = _G
|
|
_M.require = require
|
|
_M.getmetatable = getmetatable
|
|
_M.setmetatable = setmetatable
|
|
_M.getfenv = getfenv
|
|
_M.setfenv = setfenv
|
|
_M.getupvalue = debug and debug.getupvalue
|
|
_M.setupvalue = debug and debug.setupvalue
|
|
_M.package = package and package.loaded
|
|
------------------------------------------------------------------------------
|
|
Environment = oo.class{ __index = _G }
|
|
|
|
function addmembers(self, pack)
|
|
if type(pack) == "table" then
|
|
for field, member in pairs(pack) do
|
|
local kind = type(member)
|
|
if
|
|
self[member] == nil and
|
|
kind ~= "boolean" and kind ~= "number" and kind ~= "string" and
|
|
type(field) == "string" and field:match("^[%a_]+[%w_]*$")
|
|
then
|
|
self[member] = self[pack].."."..field
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function __init(self, object)
|
|
self = oo.rawnew(self, object)
|
|
self.environment = self.environment or Environment()
|
|
self.environment[self.namespace] = self
|
|
if self.globals then
|
|
self[self.globals] = self.namespace..".globals"
|
|
for field, member in pairs(self.globals) do
|
|
if
|
|
self[member] == nil and
|
|
type(field) == "string" and field:match("^[%a_]+[%w_]*$") and
|
|
type(member) == "function" and not pcall(string.dump, member)
|
|
then
|
|
self[member] = self.namespace..".globals."..field
|
|
end
|
|
end
|
|
end
|
|
if self.package then
|
|
for name, pack in pairs(self.package) do
|
|
if self[pack] == nil then
|
|
self[pack] = self.namespace..'.require("'..name..'")'
|
|
self:addmembers(pack)
|
|
end
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
------------------------------------------------------------------------------
|
|
local Incomplete = oo.class()
|
|
|
|
function Incomplete:__load(contents, metatable)
|
|
table.copy(contents, self)
|
|
return setmetatable(self, metatable)
|
|
end
|
|
|
|
function value(self, id, type, ...)
|
|
local value = self[id]
|
|
if not value then
|
|
if type == "function" then
|
|
value = assert(loadstring((...)))
|
|
elseif type == "userdata" then
|
|
value = assert(self[...], "unknown userdata")()
|
|
elseif type == "table" then
|
|
local meta
|
|
value, meta = ...
|
|
if meta and self.setmetatable then
|
|
self.setmetatable(value, meta)
|
|
end
|
|
else
|
|
value = Incomplete()
|
|
end
|
|
self[id] = value
|
|
elseif type == "table" and oo.classof(value) == Incomplete then
|
|
value:__load(...)
|
|
end
|
|
return value
|
|
end
|
|
|
|
function setup(self, value, ...)
|
|
local type = type(value)
|
|
if type == "function" then
|
|
if self.setfenv then self.setfenv(value, ... or self.globals) end
|
|
local setupvalue = self.setupvalue
|
|
if setupvalue then
|
|
local up = 1
|
|
while setupvalue(value, up, select(up+1, ...) or nil) do
|
|
up = up + 1
|
|
end
|
|
end
|
|
else
|
|
local loader = getmetatable(value)
|
|
if loader then loader = loader.__load end
|
|
if loader then loader(value, ...) end
|
|
end
|
|
return value
|
|
end
|
|
|
|
function load(self, data)
|
|
local errmsg
|
|
data, errmsg = loadstring(data)
|
|
if data then setfenv(data, self.environment) end
|
|
return data, errmsg
|
|
end
|
|
------------------------------------------------------------------------------
|
|
function serialstring(self, string)
|
|
self:write(string.format("%q", string))
|
|
end
|
|
|
|
function serialtable(self, table, id)
|
|
self[table] = self.namespace..":value("..id..")"
|
|
|
|
-- serialize contents
|
|
self:write(self.namespace,":value(",id,",'table',{")
|
|
for key, val in pairs(table) do
|
|
self:write("[")
|
|
self:serialize(key)
|
|
self:write("]=")
|
|
self:serialize(val)
|
|
self:write(",")
|
|
end
|
|
self:write("}")
|
|
|
|
-- serialize metatable
|
|
if self.getmetatable then
|
|
local meta = self.getmetatable(table)
|
|
if meta then
|
|
self:write(",")
|
|
self:serialize(meta)
|
|
end
|
|
end
|
|
|
|
self:write(")")
|
|
end
|
|
|
|
function serialfunction(self, func, id)
|
|
self[func] = self.namespace..":value("..id..")"
|
|
self:write(self.namespace,":setup(")
|
|
|
|
-- serialize bytecodes
|
|
self:write(self.namespace,":value(",id,",'function','")
|
|
local bytecodes = string.dump(func)
|
|
for i = 1, #bytecodes do
|
|
self:write("\\",string.byte(bytecodes, i))
|
|
end
|
|
self:write("')")
|
|
|
|
-- serialize environment
|
|
local env
|
|
if self.getfenv then
|
|
env = self.getfenv(func)
|
|
if env == self.globals then env = nil end
|
|
end
|
|
self:write(",")
|
|
self:serialize(env)
|
|
|
|
-- serialize upvalues
|
|
if self.getupvalue then
|
|
local name, value
|
|
local up = 1
|
|
repeat
|
|
name, value = self.getupvalue(func, up)
|
|
if name then
|
|
self:write(",")
|
|
self:serialize(value)
|
|
end
|
|
up = up + 1
|
|
until not name
|
|
end
|
|
|
|
self:write(")")
|
|
end
|
|
|
|
function serialcustom(self, id, name, ...)
|
|
local state = select("#", ...) > 0
|
|
if state then
|
|
self:write(self.namespace,":setup(")
|
|
end
|
|
self:write(self.namespace,":value(",id,",'userdata','",name,"')")
|
|
if state then
|
|
self:write(",")
|
|
self:serialize(...)
|
|
self:write(")")
|
|
end
|
|
end
|
|
|
|
function serialuserdata(self, userdata, id)
|
|
local serializer = getmetatable(userdata)
|
|
if serializer then
|
|
serializer = serializer.__serialize
|
|
if serializer then
|
|
self[userdata] = self.namespace..":value("..id..")"
|
|
return self:serialcustom(id, serializer(userdata))
|
|
end
|
|
end
|
|
error("unable to serialize a userdata without custom serialization")
|
|
end
|
|
|
|
local function getidfor(value)
|
|
local meta = getmetatable(value)
|
|
local backup
|
|
if meta then
|
|
backup = rawget(meta, "__tostring")
|
|
if backup ~= nil then rawset(meta, "__tostring", nil) end
|
|
end
|
|
local id = string.match(tostring(value), "%l+: (%w+)")
|
|
if meta then
|
|
if backup ~= nil then rawset(meta, "__tostring", backup) end
|
|
end
|
|
return tonumber(id, 16) or id
|
|
end
|
|
|
|
function serialize(self, ...)
|
|
for i=1, select("#", ...) do
|
|
if i ~= 1 then self:write(",") end
|
|
local value = select(i, ...)
|
|
local type = type(value)
|
|
if type == "nil" or type == "boolean" or type == "number" then
|
|
self:write(tostring(value))
|
|
elseif type == "string" then
|
|
self:serialstring(value)
|
|
else
|
|
local id = self[value]
|
|
if id then
|
|
self:write(id)
|
|
elseif self[type] then
|
|
self[type](self, value, getidfor(value))
|
|
else
|
|
error("unable to serialize a "..type)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
_M["table"] = serialtable
|
|
_M["function"] = serialfunction
|
|
_M["userdata"] = serialuserdata
|
|
_M["thread"] = serialthread
|