Module:Repr

require('strict')
local libraryUtil = require("libraryUtil")
local checkType = libraryUtil.checkType
local checkTypeForNamedArg = libraryUtil.checkTypeForNamedArg

local defaultOptions = {
	pretty = false,
	tabs = true,
	semicolons = false,
	spaces = 4,
	sortKeys = true,
	depth = 0,
}

-- 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

local luaKeywords = {
	["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,
}

--[[
-- Whether the given value is a valid Lua identifier (i.e. whether it can be
-- used as a variable name.)
--]]
local function isLuaIdentifier(str)
	return type(str) == "string"
		-- Must start with a-z, A-Z or underscore, and can only contain
		-- a-z, A-Z, 0-9 and underscore
		and str:find("^[%a_][%a%d_]*$") ~= nil
		-- Cannot be a Lua keyword
		and not luaKeywords[str]
end

--[[
-- Render a string representation.
--]]
local function renderString(s)
	return (("%q"):format(s):gsub("\\\n", "\\n"))
end

--[[
-- Render a number representation.
--]]
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

--[[
-- Whether a table has a __tostring metamethod.
--]]
local function hasTostringMetamethod(t)
	return getmetatable(t) and type(getmetatable(t).__tostring) == "function"
end

--[[
-- Pretty print a sequence of string representations.
-- This can be made to represent different constructs depending on the values
-- of prefix, suffix, and separator. The amount of whitespace is controlled by
-- the depth and indent parameters.
--]]
local function prettyPrintItemsAtDepth(items, prefix, suffix, separator, indent, depth)
	local whitespaceAtCurrentDepth = "\n" .. indent:rep(depth)
	local whitespaceAtNextDepth = whitespaceAtCurrentDepth .. indent
	local ret = {prefix, whitespaceAtNextDepth}
	local first = items[1]
	if first ~= nil then
		table.insert(ret, first)
	end
	for i = 2, #items do
		table.insert(ret, separator)
		table.insert(ret, whitespaceAtNextDepth)
		table.insert(ret, items[i])
	end
	table.insert(ret, whitespaceAtCurrentDepth)
	table.insert(ret, suffix)
	return table.concat(ret)
end

--[[
-- Render a sequence of string representations.
-- This can be made to represent different constructs depending on the values of
-- prefix, suffix and separator.
--]]
local function renderItems(items, prefix, suffix, separator)
	return prefix .. table.concat(items, separator .. " ") .. suffix
end

--[[
-- Render a regular table (a non-cyclic table with no __tostring metamethod).
-- This can be a sequence table, a key-value table, or a mix of the two.
--]]
local function renderNormalTable(t, context, depth)
	local items = {}

	-- Render the items in the sequence part
	local seen = {}
	for i, value in ipairs(t) do
		table.insert(items, reprRecursive(t[i], context, depth + 1))
		seen[i] = true
	end
	
	-- Render the items in the key-value part	
	local keyOrder = {}
	local keyValueStrings = {}
	for k, v in pairs(t) do
		if not seen[k] then
			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
	end
	if context.sortKeys then
		table.sort(keyOrder)
	end
	for _, kStr in ipairs(keyOrder) do
		table.insert(items, string.format("%s = %s", kStr, keyValueStrings[kStr]))
	end
	
	-- Render the table structure
	local prefix = "{"
	local suffix = "}"
	if context.pretty then
		return prettyPrintItemsAtDepth(
			items,
			prefix,
			suffix,
			context.separator,
			context.indent,
			depth
		)
	else
		return renderItems(items, prefix, suffix, context.separator)
	end
end

--[[
-- Render the given table.
-- As well as rendering regular tables, this function also renders cyclic tables
-- and tables with a __tostring metamethod.
--]]
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 = renderNormalTable(t, context, depth)
	context.shown[t] = false
	return result
end

--[[
-- Recursively render a string representation of the given value.
--]]
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

--[[
-- Normalize a table of options passed by the user.
-- Any values not specified will be assigned default values.
--]]
local function normalizeOptions(options)
	options = options or {}
	local ret = {}
	for option, defaultValue in pairs(defaultOptions) do
		local value = options[option]
		if value ~= nil then
			if type(value) == type(defaultValue) then
				ret[option] = value
			else
				error(
					string.format(
						'Invalid type for option "%s" (expected %s, received %s)',
						option,
						type(defaultValue),
						type(value)
					),
					3
				)
			end
		else
			ret[option] = defaultValue
		end
	end
	return ret
end

--[[
-- Get the indent from the options table.
--]]
local function getIndent(options)
	if options.tabs then
		return "\t"
	else
		return string.rep(" ", options.spaces)
	end
end

--[[
-- Render a string representation of the given value.
--]]
local function repr(value, options)
	checkType("repr", 2, options, "table", true)
	
	options = normalizeOptions(options)
	local context = {}

	context.pretty = options.pretty
	if context.pretty then
		context.indent = getIndent(options)
	else
		context.indent = ""
	end
	
	if options.semicolons then
		context.separator = ";"
	else
		context.separator = ","
	end
	
	context.sortKeys = options.sortKeys
	context.shown = {}
	local depth = options.depth
	
	return reprRecursive(value, context, depth)
end

--[[
-- Render a string representation of the given function invocation.
--]]
local function invocationRepr(keywordArgs)
	checkType("invocationRepr", 1, keywordArgs, "table")
	checkTypeForNamedArg("invocationRepr", "funcName", keywordArgs.funcName, "string")
	checkTypeForNamedArg("invocationRepr", "args", keywordArgs.args, "table", true)
	checkTypeForNamedArg("invocationRepr", "options", keywordArgs.options, "table", true)
	
	local options = normalizeOptions(keywordArgs.options)
	local depth = options.depth

	options.depth = depth + 1
	local items = {}
	if keywordArgs.args then
		for _, arg in ipairs(keywordArgs.args) do
			table.insert(items, repr(arg, options))
		end
	end

	local prefix = "("
	local suffix = ")"
	local separator = ","
	local renderedArgs
	if options.pretty then
		renderedArgs = prettyPrintItemsAtDepth(
			items,
			prefix,
			suffix,
			separator,
			getIndent(options),
			depth
		)
	else
		renderedArgs = renderItems(items, prefix, suffix, separator)
	end
	return keywordArgs.funcName .. renderedArgs
end

return {
	_isLuaIdentifier = isLuaIdentifier,
	repr = repr,
	invocationRepr = invocationRepr,
}

Content Disclaimer

Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.

  1. The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
  2. There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
  3. It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
  4. Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
  5. Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.