mirror of
				https://github.com/luanti-org/luanti.git
				synced 2025-11-04 09:15:29 +01:00 
			
		
		
		
	Revamp dump
				
					
				
			This commit is contained in:
		
				
					committed by
					
						
						Lars Müller
					
				
			
			
				
	
			
			
			
						parent
						
							98b2edeb11
						
					
				
				
					commit
					9ad23e4384
				
			@@ -108,65 +108,133 @@ function dump2(o, name, dumped)
 | 
			
		||||
	return string.format("%s = {}\n%s", name, table.concat(t))
 | 
			
		||||
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:
 | 
			
		||||
-- [[{
 | 
			
		||||
-- 	test = {
 | 
			
		||||
-- 		"Testing..."
 | 
			
		||||
-- 	}
 | 
			
		||||
-- }]]
 | 
			
		||||
-- This supports tables as keys, but not circular references.
 | 
			
		||||
-- 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
 | 
			
		||||
function dump(value, indent)
 | 
			
		||||
	indent = indent or "\t"
 | 
			
		||||
	level = level or 1
 | 
			
		||||
	local newline = indent == "" and "" or "\n"
 | 
			
		||||
 | 
			
		||||
	local ret = {}
 | 
			
		||||
	local dumped_indexes = {}
 | 
			
		||||
	for i, v in ipairs(o) do
 | 
			
		||||
		ret[#ret + 1] = dump(v, indent, nested, level + 1)
 | 
			
		||||
		dumped_indexes[i] = true
 | 
			
		||||
	local rope = {}
 | 
			
		||||
	local function write(str)
 | 
			
		||||
		table.insert(rope, str)
 | 
			
		||||
	end
 | 
			
		||||
	for k, v in pairs(o) do
 | 
			
		||||
		if not dumped_indexes[k] then
 | 
			
		||||
			if type(k) ~= "string" or not is_valid_identifier(k) then
 | 
			
		||||
				k = "["..dump(k, indent, nested, level + 1).."]"
 | 
			
		||||
			end
 | 
			
		||||
			v = dump(v, indent, nested, level + 1)
 | 
			
		||||
			ret[#ret + 1] = k.." = "..v
 | 
			
		||||
 | 
			
		||||
	local n_refs = {}
 | 
			
		||||
	local function count_refs(val)
 | 
			
		||||
		if type(val) ~= "table" then
 | 
			
		||||
			return
 | 
			
		||||
		end
 | 
			
		||||
		local tbl = val
 | 
			
		||||
		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
 | 
			
		||||
	nested[o] = nil
 | 
			
		||||
	if indent ~= "" then
 | 
			
		||||
		local indent_str = "\n"..string.rep(indent, level)
 | 
			
		||||
		local end_indent_str = "\n"..string.rep(indent, level - 1)
 | 
			
		||||
		return string.format("{%s%s%s}",
 | 
			
		||||
				indent_str,
 | 
			
		||||
				table.concat(ret, ","..indent_str),
 | 
			
		||||
				end_indent_str)
 | 
			
		||||
	count_refs(value)
 | 
			
		||||
 | 
			
		||||
	local refs = {}
 | 
			
		||||
	local cur_ref = 1
 | 
			
		||||
	local function write_value(val, level)
 | 
			
		||||
		if type(val) ~= "table" then
 | 
			
		||||
			write(basic_dump(val))
 | 
			
		||||
			return
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		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
 | 
			
		||||
	return "{"..table.concat(ret, ", ").."}"
 | 
			
		||||
	write_value(value, 1)
 | 
			
		||||
	return table.concat(rope)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
--------------------------------------------------------------------------------
 | 
			
		||||
 
 | 
			
		||||
@@ -232,8 +232,122 @@ describe("math", function()
 | 
			
		||||
end)
 | 
			
		||||
 | 
			
		||||
describe("dump", function()
 | 
			
		||||
	it("avoids misleading rounding of floating point numbers", function()
 | 
			
		||||
		assert.equal("0.3", dump(0.3))
 | 
			
		||||
		assert.equal("0.30000000000000004", dump(0.1 + 0.2))
 | 
			
		||||
	local function test_expression(expr)
 | 
			
		||||
		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))
 | 
			
		||||
		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
 | 
			
		||||
    * `name`: string, default: `"_"`
 | 
			
		||||
    * `dumped`: table, default: `{}`
 | 
			
		||||
* `dump(obj, dumped)`: returns a string which makes `obj` human-readable
 | 
			
		||||
    * `obj`: arbitrary variable
 | 
			
		||||
    * `dumped`: table, default: `{}`
 | 
			
		||||
* `dump(value, indent)`: returns a string which makes `value` human-readable
 | 
			
		||||
    * `value`: arbitrary value
 | 
			
		||||
      * 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)`
 | 
			
		||||
    * Get the hypotenuse of a triangle with legs x and y.
 | 
			
		||||
      Useful for distance calculation.
 | 
			
		||||
@@ -7611,9 +7613,10 @@ Misc.
 | 
			
		||||
    * Example: `write_json({10, {a = false}})`,
 | 
			
		||||
      returns `'[10, {"a": false}]'`
 | 
			
		||||
* `core.serialize(table)`: returns a string
 | 
			
		||||
    * Convert a table containing tables, strings, numbers, booleans and `nil`s
 | 
			
		||||
      into string form readable by `core.deserialize`
 | 
			
		||||
    * Convert a value into string form readable by `core.deserialize`.
 | 
			
		||||
    * Supports tables, strings, numbers, booleans and `nil`.
 | 
			
		||||
    * 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" }'`
 | 
			
		||||
* `core.deserialize(string[, safe])`: returns a table
 | 
			
		||||
    * Convert a string returned by `core.serialize` into a table
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user