255 lines
No EOL
5.8 KiB
Text
255 lines
No EOL
5.8 KiB
Text
|
||
Ÿ.
|
||
<EFBFBD>.
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
local M = {}
|
||
|
||
local tostring = tostring
|
||
local string = string
|
||
local debug = debug
|
||
local type = type
|
||
local table = table
|
||
|
||
local escape_char_map = {
|
||
["\\" ] = "\\\\",
|
||
["\"" ] = "\\\"",
|
||
["\n" ] = "\\n",
|
||
["\r" ] = "\\r",
|
||
["\t" ] = "\\t",
|
||
}
|
||
|
||
local function str(val, val_type, metatable)
|
||
local success, error_or_result = pcall(function()
|
||
if metatable then
|
||
if val_type == "table" and metatable.__tostring then
|
||
return string.format("%s %p", val, val)
|
||
end
|
||
end
|
||
return tostring(val)
|
||
end)
|
||
if success then
|
||
return error_or_result
|
||
else
|
||
print("Debugger caught error in __tostring: " .. error_or_result)
|
||
local err_trace = debug.traceback();
|
||
if err_trace ~= nil then
|
||
print(err_trace)
|
||
end
|
||
return "???"
|
||
end
|
||
end
|
||
|
||
local function escape_char(c)
|
||
return escape_char_map[c] or string.format("\\u%04x", c:byte())
|
||
end
|
||
|
||
local function encode_nil()
|
||
return "nil"
|
||
end
|
||
|
||
local function encode_number(val)
|
||
if val ~= val then
|
||
return "##NaN"
|
||
elseif val <= -math.huge then
|
||
return "##-Inf"
|
||
elseif val >= math.huge then
|
||
return "##Inf"
|
||
else
|
||
return string.format("%.14g", val)
|
||
end
|
||
end
|
||
|
||
local strings_cache = {}
|
||
local strings_cache_count = 0
|
||
local function encode_string(val, val_type)
|
||
if strings_cache[val] then
|
||
return strings_cache[val]
|
||
end
|
||
local result = '"'..val:gsub('[%z\1-\31\\"]', escape_char)..'"'
|
||
strings_cache[val] = result
|
||
strings_cache_count = strings_cache_count + 1
|
||
return result
|
||
end
|
||
|
||
local function encode_table(val, val_type)
|
||
return string.format('#lua/ref"%p"', val)
|
||
end
|
||
|
||
local function encode_userdata(val, val_type, metatable)
|
||
return '#lua/userdata"'..str(val, val_type, metatable)..'"'
|
||
end
|
||
|
||
local function encode_function(val, val_type, metatable)
|
||
return '#lua/function"'..tostring(val)..'"'
|
||
end
|
||
|
||
local function encode_thread(val, val_type, metatable)
|
||
return '#lua/thread"'..tostring(val)..'"'
|
||
end
|
||
|
||
local function encode_script(val, val_type)
|
||
return '#lua/ref"' .. tostring(val) .. '"'
|
||
end
|
||
|
||
local function encode_node(val, val_type)
|
||
local escaped_val = tostring(val):gsub('[%z\1-\31\\"]', escape_char)
|
||
return string.format('#lua/userdata"%s %p"', escaped_val, val)
|
||
end
|
||
|
||
local type_to_primitive_encoder = {
|
||
["nil"] = encode_nil,
|
||
["boolean"] = tostring,
|
||
["number"] = encode_number,
|
||
["string"] = encode_string,
|
||
["table"] = encode_table,
|
||
["userdata"] = encode_userdata,
|
||
["function"] = encode_function,
|
||
["thread"] = encode_thread
|
||
}
|
||
|
||
local has_data_in_registry = {
|
||
[GOScriptInstance] = true,
|
||
[GuiScriptInstance] = true,
|
||
[RenderScriptInstance] = true,
|
||
}
|
||
|
||
local metatable_to_primitive_encoder = {
|
||
[GOScriptInstance] = encode_script,
|
||
[GuiScriptInstance] = encode_script,
|
||
[RenderScriptInstance] = encode_script,
|
||
[NodeProxy] = encode_node
|
||
}
|
||
|
||
local function fail_on_unexpected_type(x, val_type)
|
||
error("unexpected type '" .. val_type .. "'")
|
||
end
|
||
|
||
local function encode_as_primitive(val)
|
||
local val_type = type(val)
|
||
local mt = debug.getmetatable(val)
|
||
local encoder = (mt and metatable_to_primitive_encoder[mt.__metatable]) or type_to_primitive_encoder[val_type] or fail_on_unexpected_type
|
||
return encoder(val, val_type, mt)
|
||
end
|
||
|
||
local function encode_edn_table(ref, ref_table)
|
||
local encoded_kvs = {}
|
||
for k, v in pairs(ref_table) do
|
||
encoded_kvs[#encoded_kvs + 1] = encode_as_primitive(k)
|
||
encoded_kvs[#encoded_kvs + 1] = encode_as_primitive(v)
|
||
end
|
||
local encoded_str = encode_string(str(ref, type(ref), debug.getmetatable(ref)))
|
||
return "#lua/table{:content [" .. table.concat(encoded_kvs, " ") .. "] :string " .. encoded_str .. "}"
|
||
end
|
||
|
||
local function encode_refs(ref_to_table)
|
||
local encoded_refs = {}
|
||
for ref, ref_table in pairs(ref_to_table) do
|
||
encoded_refs[#encoded_refs + 1] = encode_as_primitive(ref)
|
||
encoded_refs[#encoded_refs + 1] = encode_edn_table(ref, ref_table)
|
||
end
|
||
return "{" .. table.concat(encoded_refs, " ") .. "}"
|
||
end
|
||
|
||
local function collect_refs(depth, val, internal_graph, edge_refs_set, registry)
|
||
local val_type = type(val)
|
||
local ref_table
|
||
if val_type == "table" then
|
||
ref_table = val
|
||
elseif val_type == "userdata" then
|
||
local mt = debug.getmetatable(val)
|
||
if mt and has_data_in_registry[mt.__metatable] then
|
||
ref_table = registry[mt.__get_instance_data_table_ref(val)]
|
||
end
|
||
end
|
||
if ref_table then
|
||
if depth > 0 then
|
||
if not internal_graph[val] then
|
||
internal_graph[val] = ref_table
|
||
local next_depth = depth - 1
|
||
for k, v in pairs(ref_table) do
|
||
collect_refs(next_depth, k, internal_graph, edge_refs_set, registry)
|
||
collect_refs(next_depth, v, internal_graph, edge_refs_set, registry)
|
||
end
|
||
end
|
||
else
|
||
edge_refs_set[val] = true
|
||
end
|
||
end
|
||
end
|
||
|
||
|
||
|
||
|
||
|
||
|
||
function M.analyze(val, params)
|
||
local depth = params.maxlevel or 16
|
||
local internal_graph = {}
|
||
local edge_refs_set = {}
|
||
collect_refs(depth, val, internal_graph, edge_refs_set, debug.getregistry())
|
||
local edge_refs = {}
|
||
for edge_ref, _ in pairs(edge_refs_set) do
|
||
edge_refs[string.format("%p", edge_ref)] = edge_ref
|
||
end
|
||
return internal_graph, edge_refs
|
||
end
|
||
|
||
|
||
|
||
|
||
|
||
function M.serialize(val, internal_graph)
|
||
local result = "#lua/structure{:value " .. encode_as_primitive(val) .. " :refs " .. encode_refs(internal_graph) .. "}"
|
||
if strings_cache_count > 5000000 then
|
||
strings_cache_count = 0
|
||
strings_cache = {}
|
||
end
|
||
return result
|
||
end
|
||
|
||
|
||
|
||
|
||
|
||
function M.encode(val, params)
|
||
local err_trace
|
||
local ok, result = xpcall(
|
||
function()
|
||
local graph = M.analyze(val, params)
|
||
return M.serialize(val, graph)
|
||
end,
|
||
function(err)
|
||
err_trace = debug.traceback()
|
||
return err
|
||
end)
|
||
if ok then
|
||
return result
|
||
else
|
||
print("Debugger: " .. result)
|
||
if err_trace then
|
||
print(err_trace)
|
||
end
|
||
return encode_nil()
|
||
end
|
||
end
|
||
|
||
return M
|
||
@builtins/scripts/edn.lua" |