mirror of
https://github.com/luanti-org/luanti.git
synced 2025-06-27 16:36:03 +00:00
Revamp dump
This commit is contained in:
parent
98b2edeb11
commit
9ad23e4384
3 changed files with 240 additions and 55 deletions
|
@ -108,65 +108,133 @@ function dump2(o, name, dumped)
|
||||||
return string.format("%s = {}\n%s", name, table.concat(t))
|
return string.format("%s = {}\n%s", name, table.concat(t))
|
||||||
end
|
end
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
-- This dumps values in a one-statement format.
|
-- This dumps values in a human-readable expression format.
|
||||||
|
-- If possible, the resulting string should evaluate to an equivalent value if loaded and executed.
|
||||||
-- For example, {test = {"Testing..."}} becomes:
|
-- For example, {test = {"Testing..."}} becomes:
|
||||||
-- [[{
|
-- [[{
|
||||||
-- test = {
|
-- test = {
|
||||||
-- "Testing..."
|
-- "Testing..."
|
||||||
-- }
|
-- }
|
||||||
-- }]]
|
-- }]]
|
||||||
-- This supports tables as keys, but not circular references.
|
function dump(value, indent)
|
||||||
-- It performs poorly with multiple references as it writes out the full
|
|
||||||
-- table each time.
|
|
||||||
-- The indent field specifies a indentation string, it defaults to a tab.
|
|
||||||
-- Use the empty string to disable indentation.
|
|
||||||
-- The dumped and level arguments are internal-only.
|
|
||||||
|
|
||||||
function dump(o, indent, nested, level)
|
|
||||||
local t = type(o)
|
|
||||||
if not level and t == "userdata" then
|
|
||||||
-- when userdata (e.g. player) is passed directly, print its metatable:
|
|
||||||
return "userdata metatable: " .. dump(getmetatable(o))
|
|
||||||
end
|
|
||||||
if t ~= "table" then
|
|
||||||
return basic_dump(o)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Contains table -> true/nil of currently nested tables
|
|
||||||
nested = nested or {}
|
|
||||||
if nested[o] then
|
|
||||||
return "<circular reference>"
|
|
||||||
end
|
|
||||||
nested[o] = true
|
|
||||||
indent = indent or "\t"
|
indent = indent or "\t"
|
||||||
level = level or 1
|
local newline = indent == "" and "" or "\n"
|
||||||
|
|
||||||
local ret = {}
|
local rope = {}
|
||||||
local dumped_indexes = {}
|
local function write(str)
|
||||||
for i, v in ipairs(o) do
|
table.insert(rope, str)
|
||||||
ret[#ret + 1] = dump(v, indent, nested, level + 1)
|
|
||||||
dumped_indexes[i] = true
|
|
||||||
end
|
end
|
||||||
for k, v in pairs(o) do
|
|
||||||
if not dumped_indexes[k] then
|
local n_refs = {}
|
||||||
if type(k) ~= "string" or not is_valid_identifier(k) then
|
local function count_refs(val)
|
||||||
k = "["..dump(k, indent, nested, level + 1).."]"
|
if type(val) ~= "table" then
|
||||||
|
return
|
||||||
end
|
end
|
||||||
v = dump(v, indent, nested, level + 1)
|
local tbl = val
|
||||||
ret[#ret + 1] = k.." = "..v
|
if n_refs[tbl] then
|
||||||
|
n_refs[tbl] = n_refs[tbl] + 1
|
||||||
|
return
|
||||||
|
end
|
||||||
|
n_refs[tbl] = 1
|
||||||
|
for k, v in pairs(tbl) do
|
||||||
|
count_refs(k)
|
||||||
|
count_refs(v)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
nested[o] = nil
|
count_refs(value)
|
||||||
if indent ~= "" then
|
|
||||||
local indent_str = "\n"..string.rep(indent, level)
|
local refs = {}
|
||||||
local end_indent_str = "\n"..string.rep(indent, level - 1)
|
local cur_ref = 1
|
||||||
return string.format("{%s%s%s}",
|
local function write_value(val, level)
|
||||||
indent_str,
|
if type(val) ~= "table" then
|
||||||
table.concat(ret, ","..indent_str),
|
write(basic_dump(val))
|
||||||
end_indent_str)
|
return
|
||||||
end
|
end
|
||||||
return "{"..table.concat(ret, ", ").."}"
|
|
||||||
|
local tbl = val
|
||||||
|
if refs[tbl] then
|
||||||
|
write(refs[tbl])
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if n_refs[val] > 1 then
|
||||||
|
refs[val] = ("getref(%d)"):format(cur_ref)
|
||||||
|
write(("setref(%d)"):format(cur_ref))
|
||||||
|
cur_ref = cur_ref + 1
|
||||||
|
end
|
||||||
|
write("{")
|
||||||
|
if next(tbl) == nil then
|
||||||
|
write("}")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
write(newline)
|
||||||
|
|
||||||
|
local function write_entry(k, v)
|
||||||
|
write(indent:rep(level))
|
||||||
|
write("[")
|
||||||
|
write_value(k, level + 1)
|
||||||
|
write("] = ")
|
||||||
|
write_value(v, level + 1)
|
||||||
|
write(",")
|
||||||
|
write(newline)
|
||||||
|
end
|
||||||
|
|
||||||
|
local keys = {string = {}, number = {}}
|
||||||
|
for k in pairs(tbl) do
|
||||||
|
local t = type(k)
|
||||||
|
if keys[t] then
|
||||||
|
table.insert(keys[t], k)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Write string-keyed entries
|
||||||
|
table.sort(keys.string)
|
||||||
|
for _, k in ipairs(keys.string) do
|
||||||
|
local v = val[k]
|
||||||
|
if is_valid_identifier(k) then
|
||||||
|
write(indent:rep(level))
|
||||||
|
write(k)
|
||||||
|
write(" = ")
|
||||||
|
write_value(v, level + 1)
|
||||||
|
write(",")
|
||||||
|
write(newline)
|
||||||
|
else
|
||||||
|
write_entry(k, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Write number-keyed entries
|
||||||
|
local len = 0
|
||||||
|
for i in ipairs(tbl) do
|
||||||
|
len = i
|
||||||
|
end
|
||||||
|
if #keys.number == len then -- table is a list
|
||||||
|
for _, v in ipairs(tbl) do
|
||||||
|
write(indent:rep(level))
|
||||||
|
write_value(v, level + 1)
|
||||||
|
write(",")
|
||||||
|
write(newline)
|
||||||
|
end
|
||||||
|
else -- table harbors arbitrary number keys
|
||||||
|
table.sort(keys.number)
|
||||||
|
for _, k in ipairs(keys.number) do
|
||||||
|
write_entry(k, tbl[k])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Write all remaining entries
|
||||||
|
for k, v in pairs(val) do
|
||||||
|
if not keys[type(k)] then
|
||||||
|
write_entry(k, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
write(indent:rep(level - 1))
|
||||||
|
write("}")
|
||||||
|
end
|
||||||
|
write_value(value, 1)
|
||||||
|
return table.concat(rope)
|
||||||
end
|
end
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
|
@ -232,8 +232,122 @@ describe("math", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe("dump", function()
|
describe("dump", function()
|
||||||
it("avoids misleading rounding of floating point numbers", function()
|
local function test_expression(expr)
|
||||||
assert.equal("0.3", dump(0.3))
|
local chunk = assert(loadstring("return " .. expr))
|
||||||
|
local refs = {}
|
||||||
|
setfenv(chunk, {
|
||||||
|
setref = function(id)
|
||||||
|
refs[id] = {}
|
||||||
|
return function(fields)
|
||||||
|
for k, v in pairs(fields) do
|
||||||
|
refs[id][k] = v
|
||||||
|
end
|
||||||
|
return refs[id]
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
getref = function(id)
|
||||||
|
return assert(refs[id])
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
assert.equal(expr, dump(chunk()))
|
||||||
|
end
|
||||||
|
|
||||||
|
it("nil", function()
|
||||||
|
test_expression("nil")
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("booleans", function()
|
||||||
|
test_expression("false")
|
||||||
|
test_expression("true")
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("numbers", function()
|
||||||
|
it("formats integers nicely", function()
|
||||||
|
test_expression("42")
|
||||||
|
end)
|
||||||
|
it("avoids misleading rounding", function()
|
||||||
|
test_expression("0.3")
|
||||||
assert.equal("0.30000000000000004", dump(0.1 + 0.2))
|
assert.equal("0.30000000000000004", dump(0.1 + 0.2))
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it("strings", function()
|
||||||
|
test_expression('"hello world"')
|
||||||
|
test_expression([["hello \"world\""]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("tables", function()
|
||||||
|
it("empty", function()
|
||||||
|
test_expression("{}")
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("lists", function()
|
||||||
|
test_expression([[
|
||||||
|
{
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
"foo",
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
}]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("number keys", function()
|
||||||
|
test_expression([[
|
||||||
|
{
|
||||||
|
[0.5] = false,
|
||||||
|
[1.5] = true,
|
||||||
|
[2.5] = "foo",
|
||||||
|
}]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("dicts", function()
|
||||||
|
test_expression([[{
|
||||||
|
a = 1,
|
||||||
|
b = 2,
|
||||||
|
c = 3,
|
||||||
|
}]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("mixed", function()
|
||||||
|
test_expression([[{
|
||||||
|
a = 1,
|
||||||
|
b = 2,
|
||||||
|
c = 3,
|
||||||
|
["d e"] = true,
|
||||||
|
"foo",
|
||||||
|
"bar",
|
||||||
|
}]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("nested", function()
|
||||||
|
test_expression([[{
|
||||||
|
a = {
|
||||||
|
1,
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
b = "foo",
|
||||||
|
c = {
|
||||||
|
[0.5] = 0.1,
|
||||||
|
[1.5] = 0.2,
|
||||||
|
},
|
||||||
|
}]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("circular references", function()
|
||||||
|
test_expression([[setref(1){
|
||||||
|
child = {
|
||||||
|
parent = getref(1),
|
||||||
|
},
|
||||||
|
other_child = {
|
||||||
|
parent = getref(1),
|
||||||
|
},
|
||||||
|
}]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("supports variable indent", function()
|
||||||
|
assert.equal('{1,2,3,{foo = "bar",},}', dump({1, 2, 3, {foo = "bar"}}, ""))
|
||||||
|
assert.equal('{\n "x",\n "y",\n}', dump({"x", "y"}, " "))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
|
@ -4162,9 +4162,11 @@ Helper functions
|
||||||
* `obj`: arbitrary variable
|
* `obj`: arbitrary variable
|
||||||
* `name`: string, default: `"_"`
|
* `name`: string, default: `"_"`
|
||||||
* `dumped`: table, default: `{}`
|
* `dumped`: table, default: `{}`
|
||||||
* `dump(obj, dumped)`: returns a string which makes `obj` human-readable
|
* `dump(value, indent)`: returns a string which makes `value` human-readable
|
||||||
* `obj`: arbitrary variable
|
* `value`: arbitrary value
|
||||||
* `dumped`: table, default: `{}`
|
* Circular references are supported. Every table is dumped only once.
|
||||||
|
* `indent`: string to use for indentation, default: `"\t"`
|
||||||
|
* `""` disables indentation & line breaks (compact output)
|
||||||
* `math.hypot(x, y)`
|
* `math.hypot(x, y)`
|
||||||
* Get the hypotenuse of a triangle with legs x and y.
|
* Get the hypotenuse of a triangle with legs x and y.
|
||||||
Useful for distance calculation.
|
Useful for distance calculation.
|
||||||
|
@ -7611,9 +7613,10 @@ Misc.
|
||||||
* Example: `write_json({10, {a = false}})`,
|
* Example: `write_json({10, {a = false}})`,
|
||||||
returns `'[10, {"a": false}]'`
|
returns `'[10, {"a": false}]'`
|
||||||
* `core.serialize(table)`: returns a string
|
* `core.serialize(table)`: returns a string
|
||||||
* Convert a table containing tables, strings, numbers, booleans and `nil`s
|
* Convert a value into string form readable by `core.deserialize`.
|
||||||
into string form readable by `core.deserialize`
|
* Supports tables, strings, numbers, booleans and `nil`.
|
||||||
* Support for dumping function bytecode is **deprecated**.
|
* Support for dumping function bytecode is **deprecated**.
|
||||||
|
* Note: To obtain a human-readable representation of a value, use `dump` instead.
|
||||||
* Example: `serialize({foo="bar"})`, returns `'return { ["foo"] = "bar" }'`
|
* Example: `serialize({foo="bar"})`, returns `'return { ["foo"] = "bar" }'`
|
||||||
* `core.deserialize(string[, safe])`: returns a table
|
* `core.deserialize(string[, safe])`: returns a table
|
||||||
* Convert a string returned by `core.serialize` into a table
|
* Convert a string returned by `core.serialize` into a table
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue