Jump to content

Module:Repr

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Mr. Stradivarius (talk | contribs) at 13:26, 22 February 2021 (split the reprRecursive function into functions by type). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.

require('Module:No globals')

local defaultSettings = {
	pretty = false;
	tabs = true;
	semicolons = false;
	spaces = 4;
	sortKeys = true;
}

-- Define the reprRecursive variable here so that we can call the reprRecursive
-- function from renderSequence and renderKeyValueTable without getting
-- "Tried to read nil global reprRecursive" errors.
local reprRecursive

-- lua keywords
local keywords = {
	["and"]      = true,
	["break"]    = true,
	["do"]       = true,
	["else"]     = true,
	["elseif"]   = true,
	["end"]      = true,
	["false"]    = true,
	["for"]      = true,
	["function"] = true,
	["if"]       = true,
	["in"]       = true,
	["local"]    = true,
	["nil"]      = true,
	["not"]      = true,
	["or"]       = true,
	["repeat"]   = true,
	["return"]   = true,
	["then"]     = true,
	["true"]     = true,
	["until"]    = true,
	["while"]    = true,
}
 
local function isLuaIdentifier(str)
	return type(str) == "string"
		-- must be nonempty
		and str:len() > 0
		-- can only contain a-z, A-Z, 0-9 and underscore
		and not str:find("[^%d%a_]")
		-- cannot begin with digit
		and not tonumber(str:sub(1, 1))
		-- cannot be keyword
		and not keywords[str]
end

local function renderString(s)
	return (("%q"):format(s):gsub("\\\n", "\\n"))
end

local function renderNumber(n)
	if n == math.huge then
		return "math.huge"
	elseif n == -math.huge then
		return "-math.huge"
	else
		return tostring(n)
	end
end

local function hasTostringMetamethod(t)
	return getmetatable(t) and type(getmetatable(t).__tostring) == "function"
end


local function isPositiveInteger(value)
	return type(value) == "number"
		and value >= 1
		and value < math.huge
		and value == math.floor(value)
end

local function isSequence(t)
	for k in pairs(t) do
		if not isPositiveInteger(k) then
			return false
		end
	end
	return true
end

local function renderSequence(t, reprSettings, indent, depth, shown)
	local tabs = indent:rep(depth)
	local str = "{" .. (reprSettings.pretty and ("\n" .. indent .. tabs) or "")
	for i = 1, #t do
		if i ~= 1 then
			str = str .. (reprSettings.semicolons and ";" or ",") .. (reprSettings.pretty and ("\n" .. indent .. tabs) or " ")
		end
		depth = depth + 1
		str = str .. reprRecursive(t[i], reprSettings, indent, depth, shown)
		depth = depth - 1
	end
	if reprSettings.pretty then
		str = str .. "\n" .. tabs
	end
	str = str .. "}"
	return str
end

local function renderKeyValueTable(t, reprSettings, indent, depth, shown)
	local tabs = indent:rep(depth)
	local str = "{" .. (reprSettings.pretty and ("\n" .. indent .. tabs) or "")
	local keyOrder = {}
	local keyValueStrings = {}
	for k, v in pairs(t) do
		depth = depth + 1
		local kStr = isLuaIdentifier(k) and k or ("[" .. reprRecursive(k, reprSettings, indent, depth, shown) .. "]")
		local vStr = reprRecursive(v, reprSettings, indent, depth, shown)
		table.insert(keyOrder, kStr)
		keyValueStrings[kStr] = vStr
		depth = depth - 1
	end
	if reprSettings.sortKeys then table.sort(keyOrder) end
	local first = true
	for _, kStr in pairs(keyOrder) do
		if not first then
			str = str .. (reprSettings.semicolons and ";" or ",") .. (reprSettings.pretty and ("\n" .. indent .. tabs) or " ")
		end
		str = str .. ("%s = %s"):format(kStr, keyValueStrings[kStr])
		first = false
	end
	if reprSettings.pretty then
		str = str .. "\n" .. tabs
	end
	str = str .. "}"
	return str
end

local function renderTable(t, reprSettings, indent, depth, shown)
	if hasTostringMetamethod(t) then
		return tostring(t)
	elseif shown[t] then
		return "{CYCLIC}"
	end
	shown[t] = true
	local result
	if isSequence(t) then
		result = renderSequence(t, reprSettings, indent, depth, shown)
	else
		result = renderKeyValueTable(t, reprSettings, indent, depth, shown)
	end
	shown[t] = false
	return result
end
 
function reprRecursive(value, reprSettings, indent, depth, shown)
	if value == nil then
		return "nil"
	end
	local valueType = type(value)
	if valueType == "boolean" then
		return tostring(value)
	elseif valueType == "number" then
		return renderNumber(value)
	elseif valueType == "string" then
		return renderString(value)
	elseif valueType == "table" then
		return renderTable(value, reprSettings, indent, depth, shown)
	else
		return "<" .. valueType .. ">"
	end
end

local function repr(value, reprSettings)
	reprSettings = reprSettings or {}
	for setting, settingValue in pairs(defaultSettings) do
		if reprSettings[setting] == nil then
			reprSettings[setting] = defaultSettings[setting]
		end
	end
	
	local indent = (" "):rep(reprSettings.spaces or defaultSettings.spaces)
	if reprSettings.tabs then
		indent = "\t"
	end

	local depth = 0	
	local shown = {} 

	return reprRecursive(value, reprSettings, indent, depth, shown)
end
 
return {
	isLuaIdentifier = isLuaIdentifier,
	repr = repr,
}