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 02:07, 23 February 2021 (only increment depth when calling reprRecursive). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.

require('Module:No globals')

local defaultOptions = {
	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, context, depth)
	local str = "{" .. context.makeTableStartWhitespace(depth)
	local itemSeparator = context.separator .. context.makeTableItemWhitespace(depth)
	for i = 1, #t do
		if i ~= 1 then
			str = str .. itemSeparator
		end
		str = str .. reprRecursive(t[i], context, depth + 1)
	end
	str = str .. context.makeTableEndWhitespace(depth) .. "}"
	return str
end

local function renderKeyValueTable(t, context, depth)
	local str = "{" .. context.makeTableStartWhitespace(depth)
	local keyOrder = {}
	local keyValueStrings = {}
	for k, v in pairs(t) do
		local kStr = isLuaIdentifier(k) and k or ("[" .. reprRecursive(k, context, depth + 1) .. "]")
		local vStr = reprRecursive(v, context, depth + 1)
		table.insert(keyOrder, kStr)
		keyValueStrings[kStr] = vStr
	end
	if context.sortKeys then
		table.sort(keyOrder)
	end
	local first = true
	local itemSeparator = context.separator .. context.makeTableItemWhitespace(depth)
	for _, kStr in pairs(keyOrder) do
		if not first then
			str = str .. itemSeparator
		end
		str = str .. ("%s = %s"):format(kStr, keyValueStrings[kStr])
		first = false
	end
	str = str .. context.makeTableEndWhitespace(depth) .. "}"
	return str
end

local function renderTable(t, context, depth)
	if hasTostringMetamethod(t) then
		return tostring(t)
	elseif context.shown[t] then
		return "{CYCLIC}"
	end
	context.shown[t] = true
	local result
	if isSequence(t) then
		result = renderSequence(t, context, depth)
	else
		result = renderKeyValueTable(t, context, depth)
	end
	context.shown[t] = false
	return result
end
 
function reprRecursive(value, context, depth)
	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, context, depth)
	else
		return "<" .. valueType .. ">"
	end
end

local function returnEmptyString()
	return ""
end

local function returnSpace()
	return " "
end

local function repr(value, options)
	options = options or {}
	
	local function getOption(option)
		local value = options[option]
		if value ~= nil then
			return value
		else
			return defaultOptions[option]
		end
	end
	
	local context = {}

	local indent
	if getOption("tabs") then
		indent = "\t"
	else
		indent = (" "):rep(getOption("spaces"))
	end
	
	local function makeWhitespaceForCurrentDepth(depth)
		return "\n" .. indent:rep(depth)
	end
	
	local function makeWhitespaceForNextDepth(depth)
		return "\n" .. indent:rep(depth + 1)
	end
	
	if getOption("pretty") then
		context.makeTableStartWhitespace = makeWhitespaceForNextDepth
		context.makeTableItemWhitespace = makeWhitespaceForNextDepth
		context.makeTableEndWhitespace = makeWhitespaceForCurrentDepth
	else
		context.makeTableStartWhitespace = returnEmptyString
		context.makeTableItemWhitespace = returnSpace
		context.makeTableEndWhitespace = returnEmptyString
	end
	
	if getOption("semicolons") then
		context.separator = ";"
	else
		context.separator = ","
	end
	
	context.sortKeys = getOption("sortKeys")
	context.shown = {}
	local depth = 0	
	
	return reprRecursive(value, context, depth)
end
 
return {
	isLuaIdentifier = isLuaIdentifier,
	repr = repr,
}