Mô đun:Params

---                                        ---
	---     LOCAL ENVIRONMENT                  ---
	---    ________________________________    ---
	---                                        ---
	-- Abstract utilities --
	----------------------------
-- Helper function for `string.gsub()` (for managing zero-padded numbers)
local function zero_padded (str)
	return ('%03d%s'):format(#str, str)
end
-- Helper function for `table.sort()` (for natural sorting)
local function natural_sort (var1, var2)
	return tostring(var1):gsub('%d+', zero_padded) = idx then
			if key > tmp then cachekey - len = val end
			tblkey = nil
		end
	end
	for key, val in pairs(cache) do tblkey = val end
end
-- Make a reduced copy of a table (shifting in both directions if necessary)
local function copy_table_reduced (tbl, idx, len)
	local ret = {}
	local tmp = idx + len - 1
	if idx > 0 then
		for key, val in pairs(tbl) do
			if type(key) ~= 'number' or key  tmp then retkey - len = val end
		end
	elseif tmp > 0 then
		local nshift = 1 - idx
		for key, val in pairs(tbl) do
			if type(key) ~= 'number' then retkey = val
			elseif key > tmp then retkey - tmp = val
			elseif key  tmp then
				retkey = val
			elseif key  0 then
		for key, val in pairs(tbl) do
			if type(key) ~= 'number' or key  0 then
		local nshift = idx - 1
		for key, val in pairs(tbl) do
			if type(key) ~= 'number' then retkey = val
			elseif key > 0 then retkey + tmp = val
			elseif key  tmp then
				retkey = val
			else retkey - len = val end
		end
	end
	return ret
end
--
-- Move a key from a table to another, but only if under a different name and
-- always parsing numeric strings as numbers
local function steal_if_renamed (val, src, skey, dest, dkey)
	local realkey = tonumber(dkey) or dkey:match'^%s*(.-)%s*$'
	if skey ~= realkey then
		destrealkey = val
		srcskey = nil
	end
end
	-- Public strings --
	------------------------
-- Special match keywords (functions and modifiers MUST avoid these names)
local mkeywords = {
	'or' = 0,
	pattern = 1,
	plain = 2,
	strict = 3
}
-- Sort functions (functions and modifiers MUST avoid these names)
local sortfunctions = {
	--alphabetically = false, -- Simply uncommenting enables the option
	naturally = natural_sort
}
-- Callback styles for the `mapping_*` and `renaming_*` class of modifiers
-- (functions and modifiers MUST avoid these names)
--
Meanings of the columns:
col1 = Loop type (0-3)
col2 = Number of module arguments that the style requires (1-3)
col3 = Minimum number of sequential parameters passed to the callback
col4 = Name of the callback parameter where to place each parameter name
col5 = Name of the callback parameter where to place each parameter value
col6 = Argument in the modifier's invocation that will override `col4`
col7 = Argument in the modifier's invocation that will override `col5`
A value of `-1` indicates that no meaningful value is stored (i.e. `nil`)
--
local mapping_styles = {
	names_and_values = { 3, 2, 2, 1, 2, -1, -1 },
	values_and_names = { 3, 2, 2, 2, 1, -1, -1 },
	values_only = { 1, 2, 1, -1, 1, -1, -1 },
	names_only = { 2, 2, 1, 1, -1, -1, -1 },
	names_and_values_as = { 3, 4, 0, -1, -1, 2, 3 },
	names_only_as = { 2, 3, 0, -1, -1, 2, -1 },
	values_only_as = { 1, 3, 0, -1, -1, -1, 2 },
	blindly = { 0, 2, 0, -1, -1, -1, -1 }
}
-- Memory slots (functions and modifiers MUST avoid these names)
local memoryslots = {
	i = 'itersep',
	l = 'lastsep',
	p = 'pairsep',
	h = 'header',
	f = 'footer',
	n = 'ifngiven'
}
-- Possible trimming modes for the `parsing` modifier
local trim_parse_opts = {
	trim_none = { false, false },
	trim_positional = { false, true },
	trim_named = { true, false },
	trim_all = { true, true }
}
-- Possible string modes for the iteration separator in the `parsing` and
-- `reinterpreting` modifiers
local isep_parse_opts = {
	splitter_pattern = false,
	splitter_string = true
}
-- Possible string modes for the key-value separator in the `parsing` and
-- `reinterpreting` modifiers
local psep_parse_opts = {
	setter_pattern = false,
	setter_string = true
}
-- Functions and modifiers MUST avoid these names too: `let`
	-- Module's private environment --
	--------------------------------------
-- Hard-coded name of the module (to avoid going through `frame:getTitle()`)
local modulename = 'Module:Params'
-- The functions listed here declare that they don't need the `frame.args`
-- metatable to be copied into a regular table; if they are modifiers they also
-- guarantee that they will make their own (modified) copy available
local refpipe = {
	call_for_each_group = true,
	coins = true,
	count = true,
	for_each = true,
	list = true,
	list_values = true,
	value_of = true
}
-- The functions listed here declare that they don't need the
-- `frame:getParent().args` metatable to be copied into a regular table; if 
-- they are modifiers they also guarantee that they will make their own
-- (modified) copy available
local refparams = {
	call_for_each_group = true,
	combining_by_calling = true,
	concat_and_call = true,
	concat_and_invoke = true,
	concat_and_magic = true,
	count = true,
	--inserting = true,
	grouping_by_calling = true,
	value_of = true,
	with_name_matching = true
}
-- Maximum number of numeric parameters that can be filled, if missing (we
-- chose an arbitrary number for this constant; you can discuss about its
-- optimal value at Module talk:Params)
local maxfill = 1024
-- The private table of functions
local library = {}
-- Functions and modifiers that can only be invoked in first position
local static_iface = {}
-- Create a new context
local function context_new (frame)
	local ctx = {}
	ctx.frame = frame
	ctx.oparams = frame.args
	ctx.firstposonly = static_iface
	ctx.iterfunc = pairs
	ctx.sorttype = 0
	ctx.n_parents = 0
	ctx.n_children = 0
	ctx.n_available = maxfill
	return ctx
end
-- Move to the next action within the user-given list
local function context_iterate (ctx, n_forward)
	local nextfn
	if ctx.pipen_forward ~= nil then
		nextfn = ctx.pipen_forward:match'^%s*(.*%S)'
	end
	if nextfn == nil then error(modulename ..
		': You must specify a function to call', 0) end
	if librarynextfn == nil then
		if ctx.firstposonlynextfn == nil then error(modulename ..
			': The function ‘' .. nextfn .. '’ does not exist', 0)
		else error(modulename .. ': The ‘' .. nextfn ..
			'’ directive can only appear in first position', 0)
		end
	end
	remove_numeric_keys(ctx.pipe, 1, n_forward)
	return librarynextfn
end
-- Main loop
local function main_loop (ctx, start_with)
	local fn = start_with
	repeat fn = fn(ctx) until not fn
	if ctx.n_parents > 0 then error(modulename ..
		': One or more ‘merging_substack’ directives are missing', 0) end
	if ctx.n_children > 0 then error(modulename ..
		', For some of the snapshots either the ‘flushing’ directive is missing or a group has not been properly closed with ‘merging_substack’', 0) end
end
-- Add a new stack of parameters to `ctx.children`
local function push_cloned_stack (ctx, tbl)
	local newparams = {}
	local currsnap = ctx.n_children + 1
	if ctx.children == nil then ctx.children = { newparams }
	else ctx.childrencurrsnap = newparams end
	for key, val in pairs(tbl) do newparamskey = val end
	ctx.n_children = currsnap
end
-- Parse optional user arguments of type `...|let|...number of additional
-- parameters|parameter 1|parameter 2|...`
local function load_child_opts (src, start_from, append_after)
	local names
	local tmp
	local tbl = {}
	local pin = start_from
	if srcpin ~= nil and srcpin:match'^%s*let%s*$' then
		names = {}
		repeat
			tmp = srcpin + 1 or 
			namestonumber(tmp) or tmp:match'^%s*(.-)%s*$' or  =
				srcpin + 2
			pin = pin + 3
		until srcpin == nil or not srcpin:match'^%s*let%s*$'
	end
	tmp = tonumber(srcpin)
	if tmp ~= nil then
		if tmp  -1 then
		tmp = srctmp + shf
		karg = tonumber(tmp)
		if karg == nil then karg = tmp:match'^%s*(.-)%s*$'
		else n_exist = math.max(n_exist, karg) end
	end
	tmp = style7
	if tmp > -1 then
		tmp = srctmp + shf
		varg = tonumber(tmp)
		if varg == nil then varg = tmp:match'^%s*(.-)%s*$'
		else n_exist = math.max(n_exist, varg) end
	end
	local dest, nargs = load_child_opts(src, style2 + shf, n_exist)
	tmp = style1
	if (tmp == 3 or tmp == 2) and destkarg ~= nil then
		tmp = tmp - 2 end
	if (tmp == 3 or tmp == 1) and destvarg ~= nil then
		tmp = tmp - 1 end
	return dest, nargs, tmp, karg, varg
end
-- Parse the arguments of some of the `mapping_*` and `renaming_*` class of
-- modifiers
local function load_replace_args (opts, fname)
	if opts1 == nil then error(modulename ..
		', ‘' .. fname .. '’: No pattern string was given', 0) end
	if opts2 == nil then error(modulename ..
		', ‘' .. fname .. '’: No replacement string was given', 0) end
	local ptn = opts1
	local repl = opts2
	local argc = 3
	local nmax = tonumber(opts3)
	if nmax ~= nil or (opts3 or ):match'^%s*$' ~= nil then argc = 4 end
	local flg = optsargc
	if flg ~= nil then flg = mkeywordsflg:match'^%s*(.-)%s*$' end
	if flg == 0 then flg = nil elseif flg ~= nil then argc = argc + 1 end
	return ptn, repl, nmax, flg == 3, argc, (nmax ~= nil and nmax  0 and mkeywordskeyw > 0
			) then break
			else
				state = mkeywordskeyw
				if state > 1 then ptnsnptns2 = true end
				if state == 3 then ptnsnptns3 = true end
			end
		end
		cnt = cnt + 1
	end
	if state == 0 then error(modulename .. ', ‘' .. fname ..
		'’: No pattern was given', 0) end
	return ptns, nptns, cnt
end
-- Load the optional arguments of the `parsing` and `reinterpreting` modifiers
local function load_parse_opts (opts, start_from)
	local argc = start_from
	local tmp
	local optslots = { true, true, true }
	local noptslots = 3
	local trimn = true
	local trimu = false
	local iplain = true
	local pplain = true
	local isp = '|'
	local psp = '='
	repeat
		noptslots = noptslots - 1
		tmp = optsargc
		if tmp == nil then break end
		tmp = tmp:match'^%s*(.-)%s*$'
		if optslots1 ~= nil and trim_parse_optstmp ~= nil then
			tmp = trim_parse_optstmp
			trimn = tmp1
			trimu = tmp2
			optslots1 = nil
		elseif optslots2 ~= nil and isep_parse_optstmp ~= nil then
			argc = argc + 1
			iplain = isep_parse_optstmp
			isp = optsargc
			optslots2 = nil
		elseif optslots3 ~= nil and psep_parse_optstmp ~= nil then
			argc = argc + 1
			pplain = psep_parse_optstmp
			psp = optsargc
			optslots3 = nil
		else break end
		argc = argc + 1
	until noptslots  0 then
				retvalkey + nmax = val
			else retvalkey = val end
		end
	end
	for key, val in pairs(ctx.pipe) do retvalkey = val end
	return retval
end
-- Flush the parameters by calling a custom function for each value (after this
-- function has been invoked `ctx.params` will be no longer usable)
local function flush_params (ctx, fn)
	local tbl = ctx.params
	if ctx.subset == 1 then
		for key, val in ipairs(tbl) do fn(key, val) end
		return
	end
	if ctx.subset == -1 then
		for key, val in ipairs(tbl) do tblkey = nil end
	end
	if ctx.sorttype > 0 then
		local nums = {}
		local words = {}
		local nn = 0
		local nw = 0
		for key, val in pairs(tbl) do
			if type(key) == 'number' then
				nn = nn + 1
				numsnn = key
			else
				nw = nw + 1
				wordsnw = key
			end
		end
		table.sort(nums)
		table.sort(words, natural_sort)
		if ctx.sorttype == 2 then
			for idx = 1, nw do fn(wordsidx, tblwordsidx) end
			for idx = 1, nn do fn(numsidx, tblnumsidx) end
			return
		end
		for idx = 1, nn do fn(numsidx, tblnumsidx) end
		for idx = 1, nw do fn(wordsidx, tblwordsidx) end
		return
	end
	if ctx.subset ~= -1 then
		for key, val in ipairs(tbl) do
			fn(key, val)
			tblkey = nil
		end
	end
	for key, val in pairs(tbl) do fn(key, val) end
end
	-- Modifiers --
	-----------------------------
-- Syntax:  #invoke:params|sequential|pipe to
library.sequential = function (ctx)
	if ctx.subset == -1 then error(modulename ..
		': The two directives ‘non-sequential’ and ‘sequential’ are in contradiction with each other', 0) end
	if ctx.sorttype > 0 then error(modulename ..
		': The ‘all_sorted’ and ‘reassorted’ directives are redundant when followed by ‘sequential’', 0) end
	ctx.iterfunc = ipairs
	ctx.subset = 1
	return context_iterate(ctx, 1)
end
-- Syntax:  #invoke:params|non-sequential|pipe to
library'non-sequential' = function (ctx)
	if ctx.subset == 1 then error(modulename ..
		': The two directives ‘sequential’ and ‘non-sequential’ are in contradiction with each other', 0) end
	ctx.iterfunc = pairs
	ctx.subset = -1
	return context_iterate(ctx, 1)
end
-- Syntax:  #invoke:params|all_sorted|pipe to
library.all_sorted = function (ctx)
	if ctx.subset == 1 then error(modulename ..
		': The ‘all_sorted’ directive is redundant after ‘sequential’', 0) end
	if ctx.sorttype == 2 then error(modulename ..
		': The two directives ‘reassorted’ and ‘sequential’ are in contradiction with each other', 0) end
	ctx.sorttype = 1
	return context_iterate(ctx, 1)
end
-- Syntax:  #invoke:params|reassorted|pipe to
library.reassorted = function (ctx)
	if ctx.subset == 1 then error(modulename ..
		': The ‘reassorted’ directive is redundant after ‘sequential’', 0) end
	if ctx.sorttype == 1 then error(modulename ..
		': The two directives ‘sequential’ and ‘reassorted’ are in contradiction with each other', 0) end
	ctx.sorttype = 2
	return context_iterate(ctx, 1)
end
-- Syntax:  #invoke:params|setting|directives|...|pipe to
library.setting = function (ctx)
	local opts = ctx.pipe
	local cmd = opts1
	if cmd ~= nil then
		cmd = cmd:gsub('%s+', ):gsub('/+', '/'):match'^/*(.*^/)'
	end
	if cmd == nil then error(modulename ..
		', ‘setting’: No directive was given', 0) end
	local sep = string.byte('/')
	local argc = 2
	local dest = {}
	local vname
	local chr
	for idx = 1, #cmd do
		chr = cmd:byte(idx)
		if chr == sep then
			for key, val in ipairs(dest) do
				ctxval = optsargc
				destkey = nil
			end
			argc = argc + 1
		else
			vname = memoryslotsstring.char(chr)
			if vname == nil then error(modulename ..
				', ‘setting’: Unknown slot ‘' ..
				string.char(chr) .. '’', 0) end
			table.insert(dest, vname)
		end
	end
	for key, val in ipairs(dest) do ctxval = optsargc end
	return context_iterate(ctx, argc + 1)
end
-- Syntax:  #invoke:params|squeezing|pipe to
library.squeezing = function (ctx)
	local tbl = ctx.params
	local store = {}
	local indices = {}
	local newlen = 0
	for key, val in pairs(tbl) do
		if type(key) == 'number' then
			newlen = newlen + 1
			indicesnewlen = key
			storekey = val
			tblkey = nil
		end
	end
	table.sort(indices)
	for idx = 1, newlen do tblidx = storeindicesidx end
	return context_iterate(ctx, 1)
end
-- Syntax:  #invoke:params|filling_the_gaps|pipe to
library.filling_the_gaps = function (ctx)
	local tbl = ctx.params
	local nmin = 1
	local nmax = nil
	local nnums = -1
	local tmp = {}
	for key, val in pairs(tbl) do
		if type(key) == 'number' then
			if nmax == nil then
				if key  nmax then nmax = key
			elseif key  nnums then
		ctx.n_available = ctx.n_available + nmin + nnums - nmax
		if ctx.n_available  0 then
		local cache = {}
		if tot >= len then
			for key in ipairs(tbl) do tblkey = nil end
			tot = len
		else
			for idx = len - rcut + 1, len, 1 do tblidx = nil end
			for idx = 1, lcut, 1 do tblidx = nil end
		end
		for key, val in pairs(tbl) do
			if type(key) == 'number' and key > 0 then
				if key > len then cachekey - tot = val
				else cachekey - lcut = val end
				tblkey = nil
			end
		end
		for key, val in pairs(cache) do tblkey = val end
	end
	return context_iterate(ctx, 3)
end
-- Syntax:  #invoke:params|cropping|left crop|right crop|pipe to
library.cropping = function (ctx)
	local lcut = tonumber(ctx.pipe1)
	if lcut == nil then error(modulename ..
		', ‘cropping’: Left crop must be a number', 0) end
	local rcut = tonumber(ctx.pipe2)
	if rcut == nil then error(modulename ..
		', ‘cropping’: Right crop must be a number', 0) end
	local tbl = ctx.params
	local nmin
	local nmax
	for key in pairs(tbl) do
		if type(key) == 'number' then
			if nmin == nil then
				nmin = key
				nmax = key
			elseif key > nmax then nmax = key
			elseif key  -1 then
			for key in pairs(tbl) do
				if type(key) == 'number' then tblkey = nil end
			end
		elseif lcut + rcut > 0 then
			for idx = nmax - rcut + 1, nmax do tblidx = nil end
			for idx = nmin, nmin + lcut - 1 do tblidx = nil end
			local lshift = nmin + lcut - 1
			if lshift > 0 then
				for idx = lshift + 1, nmax, 1 do
					tblidx - lshift = tblidx
					tblidx = nil
				end
			end
		end
	end
	return context_iterate(ctx, 3)
end
-- Syntax:  #invoke:params|purging|start offset|length|pipe to
library.purging = function (ctx)
	local idx = tonumber(ctx.pipe1)
	if idx == nil then error(modulename ..
		', ‘purging’: Start offset must be a number', 0) end
	local len = tonumber(ctx.pipe2)
	if len == nil then error(modulename ..
		', ‘purging’: Length must be a number', 0) end
	local tbl = ctx.params
	if len  len then return context_iterate(ctx, 3) end
		len = len - idx + 1
	end
	ctx.params = copy_table_reduced(tbl, idx, len)
	return context_iterate(ctx, 3)
end
-- Syntax:  #invoke:params|backpurging|start offset|length|pipe to
library.backpurging = function (ctx)
	local last = tonumber(ctx.pipe1)
	if last == nil then error(modulename ..
		', ‘backpurging’: Start offset must be a number', 0) end
	local len = tonumber(ctx.pipe2)
	if len == nil then error(modulename ..
		', ‘backpurging’: Length must be a number', 0) end
	local idx
	local tbl = ctx.params
	if len > 0 then
		idx = last - len + 1
	else
		for key in pairs(tbl) do
			if type(key) == 'number' and (idx == nil or
				key  nmax then nmax = key end
		end
	end
	for key, val in pairs(numerics) do tblnmax - key + 1 = val end
	return context_iterate(ctx, 1)
end
-- Syntax:  #invoke:params|pivoting|pipe to
--
library.pivoting = function (ctx)
	local tbl = ctx.params
	local shift = #tbl + 1
	if shift  nmax then nmax = key
			elseif key  0 then
		ctx.params = ctx.childrencurrsnap
		ctx.childrencurrsnap = nil
		ctx.n_children = currsnap - 1
	else
		local newparams = {}
		for key, val in pairs(tbl) do newparamskey = val end
		ctx.params = newparams
	end
	return context_iterate(ctx, 1)
end
-- Syntax:  #invoke:params|pulling|parameter name|pipe to
library.pulling = function (ctx)
	local opts = ctx.pipe
	if opts1 == nil then error(modulename ..
		', ‘pulling’: No parameter to pull was provided', 0) end
	local parent
	local tmp = ctx.n_parents
	if tmp  len or knum  0)
	) then
		ctx.text = (ctx.header or ) .. val .. (ctx.footer or )
		return false
	end
	ctx.text = ctx.ifngiven or 
	return false
end
-- Syntax:  #invoke:params|list
library.list = function (ctx)
	-- NOTE: `ctx.pipe` might be the original metatable!
	local kvs = ctx.pairsep or 
	local pps = ctx.itersep or 
	local ret = {}
	local nss = 0
	flush_params(
		ctx,
		function (key, val)
			retnss + 1 = pps
			retnss + 2 = key
			retnss + 3 = kvs
			retnss + 4 = val
			nss = nss + 4
		end
	)
	if nss > 0 then
		if nss > 4 and ctx.lastsep ~= nil then
			retnss - 3 = ctx.lastsep
		end
		ret1 = ctx.header or 
		if ctx.footer ~= nil then retnss + 1 = ctx.footer end
		ctx.text = table.concat(ret)
		return false
	end
	ctx.text = ctx.ifngiven or 
	return false
end
-- Syntax:  #invoke:params|list_values
library.list_values = function (ctx)
	-- NOTE: `ctx.pipe` might be the original metatable!
	-- NOTE: `library.coins()` and `library.unique_coins()` rely on us
	local pps = ctx.itersep or 
	local ret = {}
	local nss = 0
	flush_params(
		ctx,
		function (key, val)
			retnss + 1 = pps
			retnss + 2 = val
			nss = nss + 2
		end
	)
	if nss > 0 then
		if nss > 2 and ctx.lastsep ~= nil then
			retnss - 1 = ctx.lastsep
		end
		ret1 = ctx.header or 
		if ctx.footer ~= nil then retnss + 1 = ctx.footer end
		ctx.text = table.concat(ret)
		return false
	end
	ctx.text = ctx.ifngiven or 
	return false
end
-- Syntax:  #invoke:params|coins|first coin = value 1|second coin = value
--            2|...|last coin = value N
library.coins = function (ctx)
	-- NOTE: `ctx.pipe` might be the original metatable!
	local opts = ctx.pipe
	local tbl = ctx.params
	for key, val in pairs(tbl) do tblkey = optstonumber(val) or val end
	return library.list_values(ctx)
end
-- Syntax:  #invoke:params|unique_coins|first coin = value 1|second coin =
--            value 2|...|last coin = value N
library.unique_coins = function (ctx)
	local opts = ctx.pipe
	local tbl = ctx.params
	local tmp
	for key, val in pairs(tbl) do
		tmp = tonumber(val) or val
		tblkey = optstmp
		optstmp = nil
	end
	return library.list_values(ctx)
end
-- Syntax:  #invoke:params|for_each|wikitext
library.for_each = function (ctx)
	-- NOTE: `ctx.pipe` might be the original metatable!
	local txt = ctx.pipe1 or 
	local pps = ctx.itersep or 
	local ret = {}
	local nss = 0
	flush_params(
		ctx,
		function (key, val)
			retnss + 1 = pps
			retnss + 2 = txt:gsub('%$#', key):gsub('%$@', val)
			nss = nss + 2
		end
	)
	if nss > 0 then
		if nss > 2 and ctx.lastsep ~= nil then
			retnss - 1 = ctx.lastsep
		end
		ret1 = ctx.header or 
		if ctx.footer ~= nil then retnss + 1 = ctx.footer end
		ctx.text = table.concat(ret)
		return false
	end
	ctx.text = ctx.ifngiven or 
	return false
end
-- Syntax:  #invoke:params|call_for_each|template name|append 1|append 2
--            
...|append n|named param 1=value 1|...|named param
--            n=value n|...
library.call_for_each = function (ctx)
	local opts = ctx.pipe
	local tname
	if opts1 ~= nil then tname = opts1:match'^%s*(.*%S)' end
	if tname == nil then error(modulename ..
		', ‘call_for_each’: No template name was provided', 0) end
	local model = { title = tname, args = opts }
	local ccs = ctx.itersep or 
	local ret = {}
	local nss = 0
	table.insert(opts, 1, true)
	flush_params(
		ctx,
		function (key, val)
			opts1 = key
			opts2 = val
			retnss + 1 = ccs
			retnss + 2 = ctx.frame:expandTemplate(model)
			nss = nss + 2
		end
	)
	if nss > 0 then
		if nss > 2 and ctx.lastsep ~= nil then
			retnss - 1 = ctx.lastsep
		end
		ret1 = ctx.header or 
		if ctx.footer ~= nil then retnss + 1 = ctx.footer end
		ctx.text = table.concat(ret)
		return false
	end
	ctx.text = ctx.ifngiven or 
	return false
end
-- Syntax:  #invoke:params|invoke_for_each|module name|module function|append
--            1|append 2|...|append n|named param 1=value 1|...
--            
named param n=value n|...
library.invoke_for_each = function (ctx)
	local opts = ctx.pipe
	local mname
	local fname
	if opts1 ~= nil then mname = opts1:match'^%s*(.*%S)' end
	if mname == nil then error(modulename ..
		', ‘invoke_for_each’: No module name was provided', 0) end
	if opts2 ~= nil then fname = opts2:match'^%s*(.*%S)' end
	if fname == nil then error(modulename ..
		', ‘invoke_for_each’: No function name was provided', 0) end
	local model = { title = 'Module:' .. mname, args = opts }
	local mfunc = require(model.title)fname
	local ccs = ctx.itersep or 
	local ret = {}
	local nss = 0
	flush_params(
		ctx,
		function (key, val)
			opts1 = key
			opts2 = val
			retnss + 1 = ccs
			retnss + 2 = mfunc(ctx.frame:newChild(model))
			nss = nss + 2
		end
	)
	if nss > 0 then
		if nss > 2 and ctx.lastsep ~= nil then
			retnss - 1 = ctx.lastsep
		end
		ret1 = ctx.header or 
		if ctx.footer ~= nil then retnss + 1 = ctx.footer end
		ctx.text = table.concat(ret)
		return false
	end
	ctx.text = ctx.ifngiven or 
	return false
end
-- Syntax:  #invoke:params|magic_for_each|parser function|append 1|append 2
--            
...|append n|named param 1=value 1|...|named param
--            n=value n|...
library.magic_for_each = function (ctx)
	local opts = ctx.pipe
	local magic
	if opts1 ~= nil then magic = opts1:match'^%s*(.*%S)' end
	if magic == nil then error(modulename ..
		', ‘magic_for_each’: No parser function was provided', 0) end
	local ccs = ctx.itersep or 
	local ret = {}
	local nss = 0
	table.insert(opts, 1, true)
	flush_params(
		ctx,
		function (key, val)
			opts1 = key
			opts2 = val
			retnss + 1 = ccs
			retnss + 2 = ctx.frame:callParserFunction(magic,
				opts)
			nss = nss + 2
		end
	)
	if nss > 0 then
		if nss > 2 and ctx.lastsep ~= nil then
			retnss - 1 = ctx.lastsep
		end
		ret1 = ctx.header or 
		if ctx.footer ~= nil then retnss + 1 = ctx.footer end
		ctx.text = table.concat(ret)
		return false
	end
	ctx.text = ctx.ifngiven or 
	return false
end
-- Syntax:  #invoke:params|call_for_each_value|template name|append 1|append
--            2|...|append n|named param 1=value 1|...|named param
--            n=value n|...
library.call_for_each_value = function (ctx)
	local opts = ctx.pipe
	local tname
	if opts1 ~= nil then tname = opts1:match'^%s*(.*%S)' end
	if tname == nil then error(modulename ..
		', ‘call_for_each_value’: No template name was provided', 0) end
	local model = { title = tname, args = opts }
	local ccs = ctx.itersep or 
	local ret = {}
	local nss = 0
	flush_params(
		ctx,
		function (key, val)
			opts1 = val
			retnss + 1 = ccs
			retnss + 2 = ctx.frame:expandTemplate(model)
			nss = nss + 2
		end
	)
	if nss > 0 then
		if nss > 2 and ctx.lastsep ~= nil then
			retnss - 1 = ctx.lastsep
		end
		ret1 = ctx.header or 
		if ctx.footer ~= nil then retnss + 1 = ctx.footer end
		ctx.text = table.concat(ret)
		return false
	end
	ctx.text = ctx.ifngiven or 
	return false
end
-- Syntax:  #invoke:params|invoke_for_each_value|module name|append 1|append
--            2|...|append n|named param 1=value 1|...|named param
--            n=value n|...
library.invoke_for_each_value = function (ctx)
	local opts = ctx.pipe
	local mname
	local fname
	if opts1 ~= nil then mname = opts1:match'^%s*(.*%S)' end
	if mname == nil then error(modulename ..
		', ‘invoke_for_each_value’: No module name was provided', 0) end
	if opts2 ~= nil then fname = opts2:match'^%s*(.*%S)' end
	if fname == nil then error(modulename ..
		', ‘invoke_for_each_value’: No function name was provided', 0) end
	local model = { title = 'Module:' .. mname, args = opts }
	local mfunc = require(model.title)fname
	local ccs = ctx.itersep or 
	local ret = {}
	local nss = 0
	remove_numeric_keys(opts, 1, 1)
	flush_params(
		ctx,
		function (key, val)
			opts1 = val
			retnss + 1 = ccs
			retnss + 2 = mfunc(ctx.frame:newChild(model))
			nss = nss + 2
		end
	)
	if nss > 0 then
		if nss > 2 and ctx.lastsep ~= nil then
			retnss - 1 = ctx.lastsep
		end
		ret1 = ctx.header or 
		if ctx.footer ~= nil then retnss + 1 = ctx.footer end
		ctx.text = table.concat(ret)
		return false
	end
	ctx.text = ctx.ifngiven or 
	return false
end
-- Syntax:  #invoke:params|magic_for_each_value|parser function|append 1
--            
append 2|...|append n|named param 1=value 1|...|named
--            param n=value n|...
library.magic_for_each_value = function (ctx)
	local opts = ctx.pipe
	local magic
	if opts1 ~= nil then magic = opts1:match'^%s*(.*%S)' end
	if magic == nil then error(modulename ..
		', ‘magic_for_each_value’: No parser function was provided', 0) end
	local ccs = ctx.itersep or 
	local ret = {}
	local nss = 0
	flush_params(
		ctx,
		function (key, val)
			opts1 = val
			retnss + 1 = ccs
			retnss + 2 = ctx.frame:callParserFunction(magic,
				opts)
			nss = nss + 2
		end
	)
	if nss > 0 then
		if nss > 2 and ctx.lastsep ~= nil then
			retnss - 1 = ctx.lastsep
		end
		ret1 = ctx.header or 
		if ctx.footer ~= nil then retnss + 1 = ctx.footer end
		ctx.text = table.concat(ret)
		return false
	end
	ctx.text = ctx.ifngiven or 
	return false
end
-- Syntax:  #invoke:params|call_for_each_group|template name|append 1|append
--            2|...|append n|named param 1=value 1|...|named param
--            n=value n|...
library.call_for_each_group = function (ctx)
	-- NOTE: `ctx.pipe` and `ctx.params` might be the original metatables!
	local opts = ctx.pipe
	local tmp
	if opts1 ~= nil then tmp = opts1:match'^%s*(.*%S)' end
	if tmp == nil then error(modulename ..
		', ‘call_for_each_group’: No template name was provided', 0) end
	local model = { title = tmp }
	local ccs = ctx.itersep or 
	local nss = 0
	local ret = {}
	opts = {}
	for key, val in pairs(ctx.pipe) do
		if type(key) == 'number' then optskey - 1 = val
		else optskey = val end
	end
	ctx.pipe = opts
	ctx.params = make_groups(ctx.params)
	flush_params(
		ctx,
		function (gid, group)
			for key, val in pairs(opts) do groupkey = val end
			group0 = gid
			model.args = group
			retnss + 1 = ccs
			retnss + 2 = ctx.frame:expandTemplate(model)
			nss = nss + 2
		end
	)
	if nss > 0 then
		if nss > 2 and ctx.lastsep ~= nil then
			retnss - 1 = ctx.lastsep
		end
		ret1 = ctx.header or 
		if ctx.footer ~= nil then retnss + 1 = ctx.footer end
		ctx.text = table.concat(ret)
		return false
	end
	ctx.text = ctx.ifngiven or 
	return false
end
	---                                        ---
	---     PUBLIC ENVIRONMENT                 ---
	---    ________________________________    ---
	---                                        ---
	-- First-position-only modifiers --
	---------------------------------------
-- Syntax:  #invoke:params|new|pipe to
static_iface.new = function (frame)
	local ctx = context_new(frame:getParent())
	ctx.pipe = copy_or_ref_table(frame.args, false)
	ctx.params = {}
	main_loop(ctx, context_iterate(ctx, 1))
	return ctx.text
end
	-- First-position-only functions --
	---------------------------------------
-- Syntax:  #invoke:params|self
static_iface.self = function (frame)
	return frame:getParent():getTitle()
end
	-- Public metatable of functions --
	---------------------------------------
return setmetatable({}, {
	__index = function (_, query)
		local fname = query:match'^%s*(.*%S)'
		if fname == nil then error(modulename ..
			': You must specify a function to call', 0) end
		local func = static_ifacefname
		if func ~= nil then return func end
		func = libraryfname
		if func == nil then error(modulename ..
			': The function ‘' .. fname .. '’ does not exist', 0) end
		return function (frame)
			local ctx = context_new(frame:getParent())
			ctx.pipe = copy_or_ref_table(frame.args,
				refpipefname)
			ctx.params = copy_or_ref_table(ctx.oparams,
				refparamsfname)
			main_loop(ctx, func)
			return ctx.text
		end
	end
})