မော်ဂျူး:parameters: တည်းဖြတ်မှု မူကွဲများ

ဝစ်ရှင်နရီ မှ
Content deleted Content added
No edit summary
No edit summary
စာတွဲ: နောက်ပြန်ပြင်ခဲ့ပြီး
စာကြောင်း ၁ - စာကြောင်း ၁ -
local m_str_utils = require("Module:string utilities")

local require_when_needed = require("Module:utilities/require when needed")

local dump = mw.dumpObject
local floor = math.floor
local gsplit = mw.text.gsplit
local gsub = string.gsub
local huge = math.huge
local insert = table.insert
local list_to_set = require("Module:table").listToSet
local list_to_text = mw.text.listToText
local match = string.match
local max = math.max
local pairs = pairs
local pattern_escape = m_str_utils.pattern_escape
local remove_holes = require_when_needed("Module:parameters/remove holes")
local scribunto_param_key = m_str_utils.scribunto_param_key
local sort = table.sort
local trim = mw.text.trim
local type = type
local yesno = require_when_needed("Module:yesno")

local export = {}
local export = {}


local function track(page, calling_module, calling_function, param_name)
local function track(page)
local track = require("Module:debug/track")
require("Module:debug/track")("parameters/" .. page)
end
local tracking_page = "parameters/" .. page

-- Cascades down in specificity, as each level is a prerequisite for the next.
local function save_pattern(name, list_name, patterns)
track(tracking_page)
name = type(name) == "string" and gsub(name, "\1", "") or name
if calling_module then
if match(list_name, "\1") then
track(tracking_page .. "/" .. calling_module)
patterns["^" .. gsub(pattern_escape(list_name), "\1", "([1-9]%%d*)") .. "$"] = name
if calling_function then
else
track(tracking_page .. "/" .. calling_module .. "/" .. calling_function)
patterns["^" .. pattern_escape(list_name) .. "([1-9]%d*)$"] = name
if param_name then
end
track(tracking_page .. "/" .. calling_module .. "/" .. calling_function .. "/" .. param_name)
end
end

local function concat_list(list, conjunction, dump_vals)
if dump_vals then
for i = 1, #list do
list[i] = dump(list[i])
end
end
end
end
return list_to_text(list, nil, conjunction)
return true
end
end


local function check_set(val, name, param)
function export.process(args, params, return_unknown, calling_module, calling_function)
if not param.set[val] then
local args_new = {}
local list = {}
for k in pairs(param.set) do
if not calling_module then
insert(list, dump(k))
track("no calling module")
end
end
sort(list)
if not calling_function then
-- If the parameter is not required then put "or empty" at the end of the list, to avoid implying the parameter is actually required.
track("no calling function", calling_module)
if not param.required then
insert(list, "empty")
end
error("Parameter " .. dump(name) .. " must be " .. (#param.set > 1 and "either " or "") .. concat_list(list, " or ") .. "; the value " .. dump(val) .. " is not valid.")
end
end
end

local get_val = setmetatable({
["boolean"] = function(val)
-- Set makes no sense with booleans, so don't bother checking for it.
return yesno(val, true)
end,
["family"] = function(val, name, param)
if param.set then
check_set(val, name, param)
end
return require("Module:families")[param.method == "name" and "getByCanonicalName" or "getByCode"](val) or
error("Parameter " .. dump(name) .. " should be a valid family " .. (param.method == "name" and "name" or "code") .. "; the value " .. dump(val) .. " is not valid. See [[WT:LOF]].")
end,
["language"] = function(val, name, param)
if param.set then
check_set(val, name, param)
end
local lang = require("Module:languages")[param.method == "name" and "getByCanonicalName" or "getByCode"](val, nil, param.etym_lang, param.family)
if lang then
return lang
end
local list = {"language"}
local links = {"[[WT:LOL]]"}
if param.etym_lang then
insert(list, "etymology language")
insert(links, "[[WT:LOL/E]]")
end
if param.family then
insert(list, "family")
insert(links, "[[WT:LOF]]")
end
error("Parameter " .. dump(name) .. " should be a valid " .. concat_list(list, " or ") .. " " .. (param.method == "name" and "name" or "code") .. "; the value " .. dump(val) .. " is not valid. See " .. concat_list(links, " and ") .. ".")
end,
["number"] = function(val, name, param)
if type(val) == "number" then
return val
end
-- Avoid converting inputs like "nan" or "inf".
val = tonumber(val:match("^[+%-]?%d+%.?%d*")) or
error("Parameter " .. dump(name) .. " should be a valid number; the value " .. dump(val) .. " is not valid.")
if param.set then
check_set(val, name, param)
end
return val
end,
["script"] = function(val, name, param)
if param.set then
check_set(val, name, param)
end
return require("Module:scripts")[param.method == "name" and "getByCanonicalName" or "getByCode"](val) or
error("Parameter " .. dump(name) .. " should be a valid script " .. (param.method == "name" and "name" or "code") .. "; the value " .. dump(val) .. " is not valid. See [[WT:LOS]].")
end,
["string"] = function(val, name, param)
if param.set then
check_set(val, name, param)
end
return val
end,
["wikimedia language"] = function(val, name, param)
if param.set then
check_set(val, name, param)
end
return require("Module:wikimedia languages").getByCode(val) or
error("Parameter " .. dump(name) .. " should be a valid wikimedia language code; the value " .. dump(val) .. " is not valid.")
end,
}, {
__call = function(self, val, name, param)
local func, sublist = self[param.type or "string"], param.sublist
if not func then
error(dump(param.type) .. " is not a recognized parameter type.")
elseif sublist then
local ret_val = {}
for v in gsplit(val, sublist == true and "%s*,%s*" or sublist) do
insert(ret_val, func(v, name, param))
end
return ret_val
else
return func(val, name, param)
end
end
})

function export.process(args, params, return_unknown)
-- Process parameters for specific properties
-- Process parameters for specific properties
local args_new = {}
local required = {}
local required = {}
local seen = {}
local patterns = {}
local patterns = {}
local names_with_equal_sign = {}
local names_with_equal_sign = {}
local list_from_index = nil
local list_from_index
for name, param in pairs(params) do
for name, param in pairs(params) do
-- Populate required table, and make sure aliases aren't set to required.
if param.required then
if param.required then
if param.alias_of then
if param.alias_of then
error("`params` table error: parameter " .. dump(name) .. " is an alias of " .. dump(param.alias_of) .. ", but is also set as a required parameter. Only " .. dump(name) .. " should be set as required.")
track("required alias", calling_module, calling_function, name)
end
end
required[name] = true
required[name] = true
end
end
-- Convert param.set from a list into a set.
if name == 1 and param.no_lang_code then
-- `seen` prevents double-conversion if multiple parameter keys share the same param table.
if not params["notlangcode"] then
local set = param.set
error("The parameter \"notlangcode\" must be enabled for this template.", 2)
if set and not seen[param] then
elseif not args["notlangcode"] and require("Module:languages").getByCode(args[name]) then
param.set = list_to_set(set)
error("The parameter \"" .. name .. "\" should not be a language code.", 2)
seen[param] = true
end
local alias = param.alias_of
if alias then
-- Check that the alias_of is set to a valid parameter.
if not params[alias] then
error("`params` table error: parameter " .. dump(name) .. " is an alias of an invalid parameter.")
end
-- Check that all the parameters in params are in the form Scribunto normalizes input argument keys into (e.g. 1 not "1", "foo" not " foo "). Otherwise, this function won't be able to normalize the input arguments in the expected way.
local normalized = scribunto_param_key(alias)
if alias ~= normalized then
error("`params` table error: parameter " .. dump(alias) .. " (a " .. type(alias) .. ") given in the alias_of field of parameter " .. dump(name) .. " is not a normalized Scribunto parameter. Should be " .. dump(normalized) .. " (a " .. type(normalized) .. ").")
-- Aliases can't be lists unless the canonical parameter is also a list.
elseif param.list and not params[alias].list then
error("`params` table error: the list parameter " .. dump(name) .. " is set as an alias of " .. dump(alias) .. ", which is not a list parameter.")
-- Aliases can't be aliases of other aliases.
elseif params[alias].alias_of then
error("`params` table error: alias_of cannot be set to another alias: parameter " .. dump(name) .. " is set as an alias of " .. dump(alias) .. ", which is in turn an alias of " .. dump(params[alias].alias_of) .. ". Set alias_of for " .. dump(name) .. " to " .. dump(params[alias].alias_of) .. ".")
end
end
end
local normalized = scribunto_param_key(name)
if name ~= normalized then
error("`params` table error: parameter " .. dump(name) .. " (a " .. type(name) .. ") is not a normalized Scribunto parameter. Should be " .. dump(normalized) .. " (a " .. type(normalized) .. ").")
end
end
if param.list then
if param.list then
if not param.alias_of then
-- A helper function to escape magic characters in a string
local key = name
-- Magic characters: ^$()%.[]*+-?
local plain = require("Module:string/pattern_escape")
if type(name) == "string" then
key = gsub(name, "\1", "")

end
local key = name
-- _list is used as a temporary flag.
if type(name) == "string" then
key = string.gsub(name, "=", "")
args_new[key] = {maxindex = 0, _list = true}
end
if param.default ~= nil then
args_new[key] = {param.default, maxindex = 1}
else
args_new[key] = {maxindex = 0}
end
end
စာကြောင်း ၇၀ - စာကြောင်း ၂၁၃ -
-- where the first item is a numbered parameter and the
-- where the first item is a numbered parameter and the
-- subsequent ones are named, such as 1, pl2, pl3.
-- subsequent ones are named, such as 1, pl2, pl3.
if string.find(param.list, "=") then
save_pattern(name, param.list, patterns)
elseif type(name) == "number" then
patterns["^" .. string.gsub(plain(param.list), "=", "(%%d+)") .. "$"] = name
if list_from_index then
else
error("`params` table error: only one numeric parameter can be a list, unless the list property is a string.")
patterns["^" .. plain(param.list) .. "(%d+)$"] = name
end
end
elseif type(name) == "number" then
-- If the name is a number, then all indexed parameters from
-- If the name is a number, then all indexed parameters from
-- this number onwards go in the list.
-- this number onwards go in the list.
list_from_index = name
list_from_index = name
else
else
if string.find(name, "=") then
save_pattern(name, name, patterns)
patterns["^" .. string.gsub(plain(name), "=", "(%%d+)") .. "$"] = string.gsub(name, "=", "")
else
patterns["^" .. plain(name) .. "(%d+)$"] = name
end
end
end
if string.find(name, "=") then
if match(name, "\1") then
insert(names_with_equal_sign, name)
-- DO NOT SIDE-EFFECT A TABLE WHILE ITERATING OVER IT.
-- Some elements may be skipped or processed twice if you do.
-- Instead, track the changes we want to make to `params`, and
-- do them after the iteration over `params` is done.
table.insert(names_with_equal_sign, name)
end
end
elseif param.default ~= nil then
args_new[name] = param.default
end
end
end
end

--Process required changes to `params`.
--Process required changes to `params`.
for i = 1, #names_with_equal_sign do
if #names_with_equal_sign > 0 then
local name = names_with_equal_sign[i]
local m_params_data = calling_module and mw.loadData("Module:parameters/data")[calling_module]
params[gsub(name, "\1", "")] = params[name]
-- If there is a ready-made version in the data module, use that.
params[name] = nil
if m_params_data and m_params_data[calling_function .. "_no_equals"] then
params = m_params_data[calling_function .. "_no_equals"]
-- Otherwise, shallow copy the params table and substitute the keys.
else
params = require("Module:table").shallowcopy(params)
for _, name in ipairs(names_with_equal_sign) do
track("name with equals", calling_module, calling_function, name)
params[string.gsub(name, "=", "")] = params[name]
params[name] = nil
end
end
end
end

-- Process the arguments
-- Process the arguments
local args_unknown = {}
local args_unknown = {}
စာကြောင်း ၁၂၁ - စာကြောင်း ၂၄၃ -
for name, val in pairs(args) do
for name, val in pairs(args) do
local index = nil
local orig_name, raw_type, index, normalized = name, type(name)
if type(name) == "number" then
if raw_type == "number" then
if list_from_index ~= nil and name >= list_from_index then
if list_from_index ~= nil and name >= list_from_index then
index = name - list_from_index + 1
index = name - list_from_index + 1
စာကြောင်း ၁၃၁ - စာကြောင်း ၂၅၃ -
-- Does this argument name match a pattern?
-- Does this argument name match a pattern?
for pattern, pname in pairs(patterns) do
for pattern, pname in pairs(patterns) do
index = mw.ustring.match(name, pattern)
index = match(name, pattern)
-- It matches, so store the parameter name and the
-- It matches, so store the parameter name and the
-- numeric index extracted from the argument name.
-- numeric index extracted from the argument name.
စာကြောင်း ၁၄၅ - စာကြောင်း ၂၆၆ -
local param = params[name]
local param = params[name]
if param and param.require_index then
-- If a parameter without the trailing index was found, and
-- require_index is set on the param, set the param to nil to treat it
-- Disallow require_index for numeric parameter names, as this doesn't make sense.
if raw_type == "number" then
-- as if it isn't recognized.
error("`params` table error: cannot set require_index for numeric parameter " .. dump(name) .. ".")
if not index and param and param.require_index then
-- If a parameter without the trailing index was found, and
param = nil
-- require_index is set on the param, set the param to nil to treat it
-- as if it isn't recognized.
elseif not index then
param = nil
end
end
end
-- If no index was found, use 1 as the default index.
-- This makes list parameters like g, g2, g3 put g at index 1.
-- If `separate_no_index` is set, then use 0 as the default instead.
index = index or (param and param.separate_no_index and 0) or 1
-- If the argument is not in the list of parameters, trigger an error.
-- If the argument is not in the list of parameters, trigger an error.
စာကြောင်း ၁၆၃ - စာကြောင်း ၂၈၄ -
args_unknown[name] = val
args_unknown[name] = val
else
else
error("The parameter \"" .. name .. "\" is not used by this template.", 2)
error("Parameter " .. dump(name) .. " is not used by this template.", 2)
end
end
else
else
-- Check that separate_no_index is not being used with a numeric parameter.
if param.separate_no_index then
if raw_type == "number" then
error("`params` table error: cannot set separate_no_index for numeric parameter " .. dump(name) .. ".")
elseif type(param.alias_of) == "number" then
error("`params` table error: cannot set separate_no_index for parameter " .. dump(name) .. ", as it is an alias of numeric parameter " .. dump(param.alias_of) .. ".")
end
end
-- If no index was found, use 1 as the default index.
-- This makes list parameters like g, g2, g3 put g at index 1.
-- If `separate_no_index` is set, then use 0 as the default instead.
if param.list then
index = index or param.separate_no_index and 0 or 1
end
-- Normalize to the canonical parameter name. If it's a list, but the alias is not, then determine the index.
local raw_name = param.alias_of
if param.alias_of then
raw_type = type(raw_name)
if raw_type == "number" then
if params[raw_name].list then
index = index or param.separate_no_index and 0 or 1
normalized = raw_name + index - 1
else
normalized = raw_name
end
name = raw_name
else
name = gsub(raw_name, "\1", "")
if params[name].list then
index = index or param.separate_no_index and 0 or 1
end
if not index or index == 0 then
normalized = name
elseif name == raw_name then
normalized = name .. index
else
normalized = gsub(raw_name, "\1", index)
end
end
else
normalized = orig_name
end
-- Remove leading and trailing whitespace unless allow_whitespace is true.
-- Remove leading and trailing whitespace unless allow_whitespace is true.
if not param.allow_whitespace then
if not param.allow_whitespace then
val = mw.text.trim(val)
val = trim(val)
end
end
စာကြောင်း ၁၇၅ - စာကြောင်း ၃၄၁ -
val = nil
val = nil
-- Track empty parameters, unless (1) allow_empty is set or (2) they're numbered parameters where a higher numbered parameter is also in use (e.g. track {{l|en|term|}}, but not {{l|en||term}}).
-- Track empty parameters, unless (1) allow_empty is set or (2) they're numbered parameters where a higher numbered parameter is also in use (e.g. track {{l|en|term|}}, but not {{l|en||term}}).
if type(name) == "number" and not max_index then
if raw_type == "number" and not max_index then
-- Find the highest numbered parameter that's in use/an empty string, as we don't want parameters like 500= to mean we can't track any empty parameters with a lower index than 500.
-- Find the highest numbered parameter that's in use/an empty string, as we don't want parameters like 500= to mean we can't track any empty parameters with a lower index than 500.
local max_contiguous_index = 0
local n = 0
while args[max_contiguous_index + 1] do
while args[n + 1] do
max_contiguous_index = max_contiguous_index + 1
n = n + 1
end
end
max_index = 0
if max_contiguous_index > 0 then
for name, val in pairs(args) do
for n = n, 1, -1 do
if args[n] ~= "" then
if type(name) == "number" and name > 0 and name <= max_contiguous_index and ((not max_index) or name > max_index) and val ~= "" then
max_index = name
max_index = n
end
break
end
end
end
end
max_index = max_index or 0
end
end
if type(name) ~= "number" or name > max_index then
if raw_type ~= "number" or name > max_index then
-- Disable this for now as it causes slowdowns on large pages like [[a]].
track("empty parameter", calling_module, calling_function, name)
-- track("empty parameter")
end
end
end
-- Convert to proper type if necessary.
if param.type == "boolean" then
val = not (not val or val == "" or val == "0" or val == "no" or val == "n" or val == "false")
elseif param.type == "number" then
val = tonumber(val)
elseif param.type then
track("unrecognized type", calling_module, calling_function, name)
track("unrecognized type/" .. tostring(param.type), calling_module, calling_function, name)
end
end
-- Can't use "if val" alone, because val may be a boolean false.
-- Can't use "if val" alone, because val may be a boolean false.
if val ~= nil then
if val ~= nil then
-- Convert to proper type if necessary.
val = get_val(val, orig_name, params[raw_name] or param)
-- Mark it as no longer required, as it is present.
-- Mark it as no longer required, as it is present.
required[param.alias_of or name] = nil
required[name] = nil
-- Store the argument value.
-- Store the argument value.
if param.list then
if index then
-- If the parameter is an alias of another, store it as the original,
-- If the parameter is duplicated, throw an error.
if args_new[name][index] ~= nil then
-- but avoid overwriting it; the original takes precedence.
error("Parameter " .. dump(normalized) .. " has been entered more than once. This is probably because a list parameter has been entered without an index and with index 1 at the same time, or because a parameter alias has been used.")
if not param.alias_of then
args_new[name][index] = val
end
args_new[name][index] = val
-- Store the highest index we find.
-- Store the highest index we find.
args_new[name].maxindex = math.max(index, args_new[name].maxindex)
args_new[name].maxindex = max(index, args_new[name].maxindex)
if args_new[name][0] then
if args_new[name][0] ~= nil then
args_new[name].default = args_new[name][0]
args_new[name].default = args_new[name][0]
args_new[name][0] = nil
if args_new[name].maxindex == 0 then
args_new[name].maxindex = 1
end
end
elseif args[param.alias_of] == nil then
args_new[name][0] = nil
if params[param.alias_of] and params[param.alias_of].list then
end
args_new[param.alias_of][index] = val
if params[name].list then
-- Don't store index 0, as it's a proxy for the default.
if index > 0 then
args_new[name][index] = val
-- Store the highest index we find.
-- Store the highest index we find.
args_new[param.alias_of].maxindex = math.max(index, args_new[param.alias_of].maxindex)
args_new[name].maxindex = max(index, args_new[name].maxindex)
else
args_new[param.alias_of] = val
end
end
else
args_new[name] = val
end
end
else
else
-- If the parameter is an alias of another, store it as the original,
-- If the parameter is duplicated, throw an error.
if args_new[name] ~= nil then
-- but avoid overwriting it; the original takes precedence.
error("Parameter " .. dump(normalized) .. " has been entered more than once. This is probably because a parameter alias has been used.")
end
if not param.alias_of then
if not param.alias_of then
args_new[name] = val
args_new[name] = val
else
elseif args[param.alias_of] == nil then
if params[param.alias_of] and params[param.alias_of].list then
if params[param.alias_of].list then
args_new[param.alias_of][1] = val
args_new[param.alias_of][1] = val
-- Store the highest index we find.
-- Store the highest index we find.
args_new[param.alias_of].maxindex = math.max(1, args_new[param.alias_of].maxindex)
args_new[param.alias_of].maxindex = max(1, args_new[param.alias_of].maxindex)
else
else
args_new[param.alias_of] = val
args_new[param.alias_of] = val
စာကြောင်း ၂၄၉ - စာကြောင်း ၄၁၇ -
end
end
end
end
end
end
end
-- Remove holes in any list parameters if needed.
for name, val in pairs(args_new) do
if type(val) == "table" and val._list then
if params[name].disallow_holes then
local highest = 0
for num, _ in pairs(val) do
if type(num) == "number" and num > 0 and num < huge and floor(num) == num then
highest = max(highest, num)
end
end
for i = 1, highest do
if val[i] == nil then
error(("For %s=, saw hole at index %s; disallowed because `disallow_holes` specified"):format(name, i))
end
end
-- Some code depends on only numeric params being present when no holes are allowed (e.g. by checking for the
-- presence of arguments using next()), so remove `maxindex`.
val.maxindex = nil
elseif not params[name].allow_holes then
args_new[name] = remove_holes(val)
end
end
end
-- Handle defaults.
for name, param in pairs(params) do
if param.default ~= nil then
local arg_new = args_new[name]
if type(arg_new) == "table" and arg_new._list then
if arg_new[1] == nil then
arg_new[1] = get_val(param.default, name, param)
end
if arg_new.maxindex == 0 then
arg_new.maxindex = 1
end
arg_new._list = nil
elseif arg_new == nil then
args_new[name] = get_val(param.default, name, param)
end
end
end
end
စာကြောင်း ၂၅၇ - စာကြောင်း ၄၆၇ -
if mw.title.getCurrentTitle().namespace ~= 10 then
if mw.title.getCurrentTitle().namespace ~= 10 then
local list = {}
local list = {}
for name, param in pairs(required) do
for name in pairs(required) do
table.insert(list, name)
insert(list, dump(name))
end
end
if #list > 0 then
local n = #list
if n > 0 then
error('The parameters "' .. mw.text.listToText(list, '", "', '" and "') .. '" are required.', 2)
error("Parameter" .. (
n == 1 and (" " .. list[1] .. " is") or
("s " .. concat_list(list, " and ", true) .. " are")
) .. " required.", 2)
end
end
end
end
-- Remove holes in any list parameters if needed.
-- Remove the temporary _list flag.
for name, val in pairs(args_new) do
for _, arg_new in pairs(args_new) do
if type(val) == "table" and not params[name].allow_holes then
if type(arg_new) == "table" then
arg_new._list = nil
args_new[name] = require("Module:parameters/remove_holes")(val)
end
end
end
end

၀၇:၅၂၊ ၁၀ မေ ၂၀၂၄ ရက်နေ့က မူ

Documentation for this module may be created at မော်ဂျူး:parameters/doc

local m_str_utils = require("Module:string utilities")

local require_when_needed = require("Module:utilities/require when needed")

local dump = mw.dumpObject
local floor = math.floor
local gsplit = mw.text.gsplit
local gsub = string.gsub
local huge = math.huge
local insert = table.insert
local list_to_set = require("Module:table").listToSet
local list_to_text = mw.text.listToText
local match = string.match
local max = math.max
local pairs = pairs
local pattern_escape = m_str_utils.pattern_escape
local remove_holes = require_when_needed("Module:parameters/remove holes")
local scribunto_param_key = m_str_utils.scribunto_param_key
local sort = table.sort
local trim = mw.text.trim
local type = type
local yesno = require_when_needed("Module:yesno")

local export = {}

local function track(page)
	require("Module:debug/track")("parameters/" .. page)
end

local function save_pattern(name, list_name, patterns)
	name = type(name) == "string" and gsub(name, "\1", "") or name
	if match(list_name, "\1") then
		patterns["^" .. gsub(pattern_escape(list_name), "\1", "([1-9]%%d*)") .. "$"] = name
	else
		patterns["^" .. pattern_escape(list_name) .. "([1-9]%d*)$"] = name
	end
end

local function concat_list(list, conjunction, dump_vals)
	if dump_vals then
		for i = 1, #list do
			list[i] = dump(list[i])
		end
	end
	return list_to_text(list, nil, conjunction)
end

local function check_set(val, name, param)
	if not param.set[val] then
		local list = {}
		for k in pairs(param.set) do
			insert(list, dump(k))
		end
		sort(list)
		-- If the parameter is not required then put "or empty" at the end of the list, to avoid implying the parameter is actually required.
		if not param.required then
			insert(list, "empty")
		end
		error("Parameter " .. dump(name) .. " must be " .. (#param.set > 1 and "either " or "") .. concat_list(list, " or ") .. "; the value " .. dump(val) .. " is not valid.")
	end
end

local get_val = setmetatable({
	["boolean"] = function(val)
		-- Set makes no sense with booleans, so don't bother checking for it.
		return yesno(val, true)
	end,
	
	["family"] = function(val, name, param)
		if param.set then
			check_set(val, name, param)
		end
		return require("Module:families")[param.method == "name" and "getByCanonicalName" or "getByCode"](val) or
			error("Parameter " .. dump(name) .. " should be a valid family " .. (param.method == "name" and "name" or "code") .. "; the value " .. dump(val) .. " is not valid. See [[WT:LOF]].")
	end,
	
	["language"] = function(val, name, param)
		if param.set then
			check_set(val, name, param)
		end
		local lang = require("Module:languages")[param.method == "name" and "getByCanonicalName" or "getByCode"](val, nil, param.etym_lang, param.family)
		if lang then
			return lang
		end
		local list = {"language"}
		local links = {"[[WT:LOL]]"}
		if param.etym_lang then
			insert(list, "etymology language")
			insert(links, "[[WT:LOL/E]]")
		end
		if param.family then
			insert(list, "family")
			insert(links, "[[WT:LOF]]")
		end
		error("Parameter " .. dump(name) .. " should be a valid " .. concat_list(list, " or ") .. " " .. (param.method == "name" and "name" or "code") .. "; the value " .. dump(val) .. " is not valid. See " .. concat_list(links, " and ") .. ".")
	end,
	
	["number"] = function(val, name, param)
		if type(val) == "number" then
			return val
		end
		-- Avoid converting inputs like "nan" or "inf".
		val = tonumber(val:match("^[+%-]?%d+%.?%d*")) or
			error("Parameter " .. dump(name) .. " should be a valid number; the value " .. dump(val) .. " is not valid.")
		if param.set then
			check_set(val, name, param)
		end
		return val
	end,
	
	["script"] = function(val, name, param)
		if param.set then
			check_set(val, name, param)
		end
		return require("Module:scripts")[param.method == "name" and "getByCanonicalName" or "getByCode"](val) or
			error("Parameter " .. dump(name) .. " should be a valid script " .. (param.method == "name" and "name" or "code") .. "; the value " .. dump(val) .. " is not valid. See [[WT:LOS]].")
	end,
	
	["string"] = function(val, name, param)
		if param.set then
			check_set(val, name, param)
		end
		return val
	end,
	
	["wikimedia language"] = function(val, name, param)
		if param.set then
			check_set(val, name, param)
		end
		return require("Module:wikimedia languages").getByCode(val) or
			error("Parameter " .. dump(name) .. " should be a valid wikimedia language code; the value " .. dump(val) .. " is not valid.")
	end,
}, {
	__call = function(self, val, name, param)
		local func, sublist = self[param.type or "string"], param.sublist
		if not func then
			error(dump(param.type) .. " is not a recognized parameter type.")
		elseif sublist then
			local ret_val = {}
			for v in gsplit(val, sublist == true and "%s*,%s*" or sublist) do
				insert(ret_val, func(v, name, param))
			end
			return ret_val
		else
			return func(val, name, param)
		end
	end
})

function export.process(args, params, return_unknown)
	-- Process parameters for specific properties
	local args_new = {}
	local required = {}
	local seen = {}
	local patterns = {}
	local names_with_equal_sign = {}
	local list_from_index
	
	for name, param in pairs(params) do
		-- Populate required table, and make sure aliases aren't set to required.
		if param.required then
			if param.alias_of then
				error("`params` table error: parameter " .. dump(name) .. " is an alias of " .. dump(param.alias_of) .. ", but is also set as a required parameter. Only " .. dump(name) .. " should be set as required.")
			end
			required[name] = true
		end
		
		-- Convert param.set from a list into a set.
		-- `seen` prevents double-conversion if multiple parameter keys share the same param table.
		local set = param.set
		if set and not seen[param] then
			param.set = list_to_set(set)
			seen[param] = true
		end
		
		local alias = param.alias_of
		if alias then
			-- Check that the alias_of is set to a valid parameter.
			if not params[alias] then
				error("`params` table error: parameter " .. dump(name) .. " is an alias of an invalid parameter.")
			end
			-- Check that all the parameters in params are in the form Scribunto normalizes input argument keys into (e.g. 1 not "1", "foo" not " foo "). Otherwise, this function won't be able to normalize the input arguments in the expected way.
			local normalized = scribunto_param_key(alias)
			if alias ~= normalized then
				error("`params` table error: parameter " .. dump(alias) .. " (a " .. type(alias) .. ") given in the alias_of field of parameter " .. dump(name) .. " is not a normalized Scribunto parameter. Should be " .. dump(normalized) .. " (a " .. type(normalized) .. ").")
			-- Aliases can't be lists unless the canonical parameter is also a list.
			elseif param.list and not params[alias].list then
				error("`params` table error: the list parameter " .. dump(name) .. " is set as an alias of " .. dump(alias) .. ", which is not a list parameter.")
			-- Aliases can't be aliases of other aliases.
			elseif params[alias].alias_of then
				error("`params` table error: alias_of cannot be set to another alias: parameter " .. dump(name) .. " is set as an alias of " .. dump(alias) .. ", which is in turn an alias of " .. dump(params[alias].alias_of) .. ". Set alias_of for " .. dump(name) .. " to " .. dump(params[alias].alias_of) .. ".")
			end
		end
		
		local normalized = scribunto_param_key(name)
		if name ~= normalized then
			error("`params` table error: parameter " .. dump(name) .. " (a " .. type(name) .. ") is not a normalized Scribunto parameter. Should be " .. dump(normalized) .. " (a " .. type(normalized) .. ").")
		end
		
		if param.list then
			if not param.alias_of then
				local key = name
				if type(name) == "string" then
					key = gsub(name, "\1", "")
				end
				-- _list is used as a temporary flag.
				args_new[key] = {maxindex = 0, _list = true}
			end
			
			if type(param.list) == "string" then
				-- If the list property is a string, then it represents the name
				-- to be used as the prefix for list items. This is for use with lists
				-- where the first item is a numbered parameter and the
				-- subsequent ones are named, such as 1, pl2, pl3.
				save_pattern(name, param.list, patterns)
			elseif type(name) == "number" then
				if list_from_index then
					error("`params` table error: only one numeric parameter can be a list, unless the list property is a string.")
				end
				-- If the name is a number, then all indexed parameters from
				-- this number onwards go in the list.
				list_from_index = name
			else
				save_pattern(name, name, patterns)
			end
			
			if match(name, "\1") then
				insert(names_with_equal_sign, name)
			end
		end
	end
	
	--Process required changes to `params`.
	for i = 1, #names_with_equal_sign do
		local name = names_with_equal_sign[i]
		params[gsub(name, "\1", "")] = params[name]
		params[name] = nil
	end
	
	-- Process the arguments
	local args_unknown = {}
	local max_index
	
	for name, val in pairs(args) do
		local orig_name, raw_type, index, normalized = name, type(name)
		
		if raw_type == "number" then
			if list_from_index ~= nil and name >= list_from_index then
				index = name - list_from_index + 1
				name = list_from_index
			end
		else
			-- Does this argument name match a pattern?
			for pattern, pname in pairs(patterns) do
				index = match(name, pattern)
				-- It matches, so store the parameter name and the
				-- numeric index extracted from the argument name.
				if index then
					index = tonumber(index)
					name = pname
					break
				end
			end
		end
		
		local param = params[name]
		
		if param and param.require_index then
			-- Disallow require_index for numeric parameter names, as this doesn't make sense.
			if raw_type == "number" then
				error("`params` table error: cannot set require_index for numeric parameter " .. dump(name) .. ".")
			-- If a parameter without the trailing index was found, and
			-- require_index is set on the param, set the param to nil to treat it
			-- as if it isn't recognized.
			elseif not index then
				param = nil
			end
		end
		
		-- If the argument is not in the list of parameters, trigger an error.
		-- return_unknown suppresses the error, and stores it in a separate list instead.
		if not param then
			if return_unknown then
				args_unknown[name] = val
			else
				error("Parameter " .. dump(name) .. " is not used by this template.", 2)
			end
		else
			-- Check that separate_no_index is not being used with a numeric parameter.
			if param.separate_no_index then
				if raw_type == "number" then
					error("`params` table error: cannot set separate_no_index for numeric parameter " .. dump(name) .. ".")
				elseif type(param.alias_of) == "number" then
					error("`params` table error: cannot set separate_no_index for parameter " .. dump(name) .. ", as it is an alias of numeric parameter " .. dump(param.alias_of) .. ".")
				end
			end
			
			-- If no index was found, use 1 as the default index.
			-- This makes list parameters like g, g2, g3 put g at index 1.
			-- If `separate_no_index` is set, then use 0 as the default instead.
			if param.list then
				index = index or param.separate_no_index and 0 or 1
			end
			
			-- Normalize to the canonical parameter name. If it's a list, but the alias is not, then determine the index.
			local raw_name = param.alias_of
			if param.alias_of then
				raw_type = type(raw_name)
				if raw_type == "number" then
					if params[raw_name].list then
						index = index or param.separate_no_index and 0 or 1
						normalized = raw_name + index - 1
					else
						normalized = raw_name
					end
					name = raw_name
				else
					name = gsub(raw_name, "\1", "")
					if params[name].list then
						index = index or param.separate_no_index and 0 or 1
					end
					if not index or index == 0 then
						normalized = name
					elseif name == raw_name then
						normalized = name .. index
					else
						normalized = gsub(raw_name, "\1", index)
					end
				end
			else
				normalized = orig_name
			end
			
			-- Remove leading and trailing whitespace unless allow_whitespace is true.
			if not param.allow_whitespace then
				val = trim(val)
			end
			
			-- Empty string is equivalent to nil unless allow_empty is true.
			if val == "" and not param.allow_empty then
				val = nil
				-- Track empty parameters, unless (1) allow_empty is set or (2) they're numbered parameters where a higher numbered parameter is also in use (e.g. track {{l|en|term|}}, but not {{l|en||term}}).
				if raw_type == "number" and not max_index then
					-- Find the highest numbered parameter that's in use/an empty string, as we don't want parameters like 500= to mean we can't track any empty parameters with a lower index than 500.
					local n = 0
					while args[n + 1] do
						n = n + 1
					end
					max_index = 0
					for n = n, 1, -1 do
						if args[n] ~= "" then
							max_index = n
							break
						end
					end
				end
				if raw_type ~= "number" or name > max_index then
					-- Disable this for now as it causes slowdowns on large pages like [[a]].
					-- track("empty parameter")
				end
			end
			
			-- Can't use "if val" alone, because val may be a boolean false.
			if val ~= nil then
				-- Convert to proper type if necessary.
				val = get_val(val, orig_name, params[raw_name] or param)
				
				-- Mark it as no longer required, as it is present.
				required[name] = nil
				
				-- Store the argument value.
				if index then
					-- If the parameter is duplicated, throw an error.
					if args_new[name][index] ~= nil then
						error("Parameter " .. dump(normalized) .. " has been entered more than once. This is probably because a list parameter has been entered without an index and with index 1 at the same time, or because a parameter alias has been used.")
					end
					args_new[name][index] = val
					
					-- Store the highest index we find.
					args_new[name].maxindex = max(index, args_new[name].maxindex)
					if args_new[name][0] ~= nil then
						args_new[name].default = args_new[name][0]
						if args_new[name].maxindex == 0 then
							args_new[name].maxindex = 1
						end
						args_new[name][0] = nil
						
					end
					
					if params[name].list then
						-- Don't store index 0, as it's a proxy for the default.
						if index > 0 then
							args_new[name][index] = val
							-- Store the highest index we find.
							args_new[name].maxindex = max(index, args_new[name].maxindex)
						end
					else
						args_new[name] = val
					end
				else
					-- If the parameter is duplicated, throw an error.
					if args_new[name] ~= nil then
						error("Parameter " .. dump(normalized) .. " has been entered more than once. This is probably because a parameter alias has been used.")
					end
					
					if not param.alias_of then
						args_new[name] = val
					else
						if params[param.alias_of].list then
							args_new[param.alias_of][1] = val
							
							-- Store the highest index we find.
							args_new[param.alias_of].maxindex = max(1, args_new[param.alias_of].maxindex)
						else
							args_new[param.alias_of] = val
						end
					end
				end
			end
		end
	end
	
	-- Remove holes in any list parameters if needed.
	for name, val in pairs(args_new) do
		if type(val) == "table" and val._list then
			if params[name].disallow_holes then
				local highest = 0
				for num, _ in pairs(val) do
					if type(num) == "number" and num > 0 and num < huge and floor(num) == num then
						highest = max(highest, num)
					end
				end
				for i = 1, highest do
					if val[i] == nil then
						error(("For %s=, saw hole at index %s; disallowed because `disallow_holes` specified"):format(name, i))
					end
				end
				-- Some code depends on only numeric params being present when no holes are allowed (e.g. by checking for the
				-- presence of arguments using next()), so remove `maxindex`.
				val.maxindex = nil
			elseif not params[name].allow_holes then
				args_new[name] = remove_holes(val)
			end
		end
	end
	
	-- Handle defaults.
	for name, param in pairs(params) do
		if param.default ~= nil then
			local arg_new = args_new[name]
			if type(arg_new) == "table" and arg_new._list then
				if arg_new[1] == nil then
					arg_new[1] = get_val(param.default, name, param)
				end
				if arg_new.maxindex == 0 then
					arg_new.maxindex = 1
				end
				arg_new._list = nil
			elseif arg_new == nil then
				args_new[name] = get_val(param.default, name, param)
			end
		end
	end
	
	-- The required table should now be empty.
	-- If any entry remains, trigger an error, unless we're in the template namespace.
	if mw.title.getCurrentTitle().namespace ~= 10 then
		local list = {}
		for name in pairs(required) do
			insert(list, dump(name))
		end
		local n = #list
		if n > 0 then
			error("Parameter" .. (
				n == 1 and (" " .. list[1] .. " is") or
				("s " .. concat_list(list, " and ", true) .. " are")
			) .. " required.", 2)
		end
	end
	
	-- Remove the temporary _list flag.
	for _, arg_new in pairs(args_new) do
		if type(arg_new) == "table" then
			arg_new._list = nil
		end
	end
	
	if return_unknown then
		return args_new, args_unknown
	else
		return args_new
	end
end

return export