project/build/default_html5/builtins/scripts/edn.luac
2025-08-25 01:59:20 +01:00

255 lines
No EOL
5.8 KiB
Text
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Ÿ.
<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"