Module:Params

    From WikiPasokh

    Documentation for this module may be created at Module:Params/doc

    	---                                        ---
    	---     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) <
    		tostring(var2):gsub('%d+', zero_padded)
    end
    
    
    -- Return a copy or a reference to a table
    local function copy_or_ref_table (src, refonly)
    	if refonly then return src end
    	newtab = {}
    	for key, val in pairs(src) do newtab[key] = val end
    	return newtab
    end
    
    
    -- Remove numerical elements from a table, shifting everything to the left
    local function remove_numerical_keys (tbl, idx, len)
    	local cache = {}
    	local tmp = idx + len - 1
    	for key, val in pairs(tbl) do
    		if type(key) == 'number' and key >= idx then
    			if key > tmp then cache[key - len] = val end
    			tbl[key] = nil
    		end
    	end
    	for key, val in pairs(cache) do tbl[key] = 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 < idx then
    				ret[key] = val
    			elseif key > tmp then ret[key - len] = val end
    		end
    	elseif tmp > 0 then
    		local nshift = 1 - idx
    		for key, val in pairs(tbl) do
    			if type(key) ~= 'number' then ret[key] = val
    			elseif key > tmp then ret[key - tmp] = val
    			elseif key < idx then ret[key + nshift] = val end
    		end
    	else
    		for key, val in pairs(tbl) do
    			if type(key) ~= 'number' or key > tmp then
    				ret[key] = val
    			elseif key < idx then ret[key + len] = val end
    		end
    	end
    	return ret
    end
    
    
    -- Make an expanded copy of a table (shifting in both directions if necessary)
    --[[
    local function copy_table_expanded (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 < idx then
    				ret[key] = val
    			else ret[key + len] = val end
    		end
    	elseif tmp > 0 then
    		local nshift = idx - 1
    		for key, val in pairs(tbl) do
    			if type(key) ~= 'number' then ret[key] = val
    			elseif key > 0 then ret[key + tmp] = val
    			elseif key < 1 then ret[key + nshift] = val end
    		end
    	else
    		for key, val in pairs(tbl) do
    			if type(key) ~= 'number' or key > tmp then
    				ret[key] = val
    			else ret[key - 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 numerical 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
    		dest[realkey] = val
    		src[skey] = 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:
    
      col[1] = Loop type (0-3)
      col[2] = Number of module arguments that the style requires (1-3)
      col[3] = Minimum number of sequential parameters passed to the callback
      col[4] = Name of the callback parameter where to place each parameter name
      col[5] = Name of the callback parameter where to place each parameter value
      col[6] = Argument in the modifier's invocation that will override `col[4]`
      col[7] = Argument in the modifier's invocation that will override `col[5]`
    
    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'
    }
    
    
    -- Functions and modifiers MUST avoid these names too: `let`
    
    
    
    	--[[ Module's private environment ]]--
    	--------------------------------------
    
    
    -- 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 available their own (modified) copy
    local refpipe = {
    	count = true,
    	value_of = true,
    	list = true,
    	list_values = true,
    	for_each = true,
    	call_for_each_group = true
    }
    
    
    -- 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 available their
    -- own (modified) copy
    local refparams = {
    	--inserting = true,
    	grouping_by_calling = true,
    	count = true,
    	concat_and_call = true,
    	concat_and_invoke = true,
    	concat_and_magic = true,
    	value_of = true,
    	call_for_each_group = true
    }
    
    
    -- Maximum number of numerical 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 that can only be invoked in first position
    local static_iface = {}
    
    
    -- Create a new context
    local function context_new ()
    	local ctx = {}
    	ctx.luaname = 'Module:Params'	--[[ or `frame:getTitle()` ]]--
    	ctx.iterfunc = pairs
    	ctx.sorttype = 0
    	ctx.firstposonly = static_iface
    	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.pipe[n_forward] ~= nil then
    		nextfn = ctx.pipe[n_forward]:match'^%s*(.*%S)'
    	end
    	if nextfn == nil then error(ctx.luaname ..
    		': You must specify a function to call', 0) end
    	if library[nextfn] == nil then
    		if ctx.firstposonly[nextfn] == nil then error(ctx.luaname ..
    			': The function ‘' .. nextfn .. '’ does not exist', 0)
    		else error(ctx.luaname .. ': The ‘' .. nextfn ..
    			'’ directive can only appear in first position', 0)
    		end
    	end
    	remove_numerical_keys(ctx.pipe, 1, n_forward)
    	return library[nextfn]
    end
    
    
    -- Main loop
    local function main_loop (ctx, start_with)
    	local fn = start_with
    	repeat fn = fn(ctx) until not fn
    end
    
    
    -- Parse user arguments of type `...|[let]|[...][number of additional
    -- parameters]|[parameter 1]|[parameter 2]|[...]`
    local function parse_child_args (src, start_from, append_after)
    	local names
    	local tmp
    	local dest = {}
    	local pin = start_from
    	if src[pin] ~= nil and src[pin]:match'^%s*let%s*$' then
    		names = {}
    		repeat
    			tmp = src[pin + 1] or ''
    			names[tonumber(tmp) or tmp:match'^%s*(.-)%s*$' or ''] =
    				src[pin + 2]
    			pin = pin + 3
    		until src[pin] == nil or not src[pin]:match'^%s*let%s*$'
    	end
    	tmp = tonumber(src[pin])
    	if tmp ~= nil then
    		if tmp < 0 then tmp = -1 end
    		local shf = append_after - pin
    		for idx = pin + 1, pin + tmp do dest[idx + shf] = src[idx] end
    		pin = pin + tmp + 1
    	end
    	if names ~= nil then
    		for key, val in pairs(names) do dest[key] = val end
    	end
    	return dest, pin
    end
    
    
    -- Parse the arguments of some of the `mapping_*` and `renaming_*` class of
    -- modifiers
    local function parse_callback_args (src, n_skip, default_style)
    	local style
    	local shf
    	local tmp = src[n_skip + 1]
    	if tmp ~= nil then style = mapping_styles[tmp:match'^%s*(.-)%s*$'] end
    	if style == nil then
    		style = default_style
    		shf = n_skip - 1
    	else shf = n_skip end
    	local n_exist = style[3]
    	local karg = style[4]
    	local varg = style[5]
    	tmp = style[6]
    	if tmp > -1 then
    		tmp = src[tmp + shf]
    		karg = tonumber(tmp)
    		if karg == nil then karg = tmp:match'^%s*(.-)%s*$'
    		else n_exist = math.max(n_exist, karg) end
    	end
    	tmp = style[7]
    	if tmp > -1 then
    		tmp = src[tmp + 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 = parse_child_args(src, style[2] + shf, n_exist)
    	tmp = style[1]
    	if (tmp == 3 or tmp == 2) and dest[karg] ~= nil then
    		tmp = tmp - 2 end
    	if (tmp == 3 or tmp == 1) and dest[varg] ~= 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 parse_replace_args (opts, fname)
    	if opts[1] == nil then error(ctx.luaname ..
    		', ‘' .. fname .. '’: No pattern string was given', 0) end
    	if opts[2] == nil then error(ctx.luaname ..
    		', ‘' .. fname .. '’: No replacement string was given', 0) end
    	local ptn = opts[1]
    	local repl = opts[2]
    	local argc = 3
    	local nmax = tonumber(opts[3])
    	if nmax ~= nil or (opts[3] or ''):match'^%s*$' ~= nil then argc = 4 end
    	local flg = opts[argc]
    	if flg ~= nil then flg = mkeywords[flg: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 < 1) or
    		(flg == 3 and ptn == repl)
    end
    
    
    -- Parse the arguments of the `with_*_matching` class of modifiers
    local function parse_pattern_args (ctx, fname)
    	local state = 0
    	local cnt = 1
    	local keyw
    	local nptns = 0
    	local ptns = {}
    	for _, val in ipairs(ctx.pipe) do
    		if state == 0 then
    			nptns = nptns + 1
    			ptns[nptns] = { val, false, false }
    			state = -1
    		else
    			keyw = val:match'^%s*(.*%S)'
    			if keyw == nil or mkeywords[keyw] == nil or (
    				state > 0 and mkeywords[keyw] > 0
    			) then break
    			else
    				state = mkeywords[keyw]
    				if state > 1 then ptns[nptns][2] = true end
    				if state == 3 then ptns[nptns][3] = true end
    			end
    		end
    		cnt = cnt + 1
    	end
    	if state == 0 then error(ctx.luaname .. ', ‘' .. fname ..
    		'’: No pattern was given', 0) end
    	return ptns, cnt
    end
    
    
    -- Map parameters' values using a custom callback and a referenced table
    local value_maps = {
    	[0] = function (tbl, margs, karg, varg, fn)
    		for key in pairs(tbl) do tbl[key] = fn() end
    	end,
    	[1] = function (tbl, margs, karg, varg, fn)
    		for key, val in pairs(tbl) do
    			margs[varg] = val
    			tbl[key] = fn()
    		end
    	end,
    	[2] = function (tbl, margs, karg, varg, fn)
    		for key in pairs(tbl) do
    			margs[karg] = key
    			tbl[key] = fn()
    		end
    	end,
    	[3] = function (tbl, margs, karg, varg, fn)
    		for key, val in pairs(tbl) do
    			margs[karg] = key
    			margs[varg] = val
    			tbl[key] = fn()
    		end
    	end
    }
    
    
    -- Private table for `map_names()`
    local name_thieves_maps = {
    	[0] = function (cache, tbl, rargs, karg, varg, fn)
    		for key, val in pairs(tbl) do
    			steal_if_renamed(val, tbl, key, cache, fn())
    		end
    	end,
    	[1] = function (cache, tbl, rargs, karg, varg, fn)
    		for key, val in pairs(tbl) do
    			rargs[varg] = val
    			steal_if_renamed(val, tbl, key, cache, fn())
    		end
    	end,
    	[2] = function (cache, tbl, rargs, karg, varg, fn)
    		for key, val in pairs(tbl) do
    			rargs[karg] = key
    			steal_if_renamed(val, tbl, key, cache, fn())
    		end
    	end,
    	[3] = function (cache, tbl, rargs, karg, varg, fn)
    		for key, val in pairs(tbl) do
    			rargs[karg] = key
    			rargs[varg] = val
    			steal_if_renamed(val, tbl, key, cache, fn())
    		end
    	end
    }
    
    
    -- Map parameters' names using a custom callback and a referenced table
    local function map_names (tbl, rargs, karg, varg, looptype, fn)
    	local cache = {}
    	name_thieves_maps[looptype](cache, tbl, rargs, karg, varg, fn)
    	for key, val in pairs(cache) do tbl[key] = val end
    end
    
    
    -- Return a new table that contains `src` regrouped according to the numerical
    -- suffixes in its keys
    local function make_groups (src)
    	-- NOTE: `src` might be the original metatable!
    	local tmp
    	local prefix
    	local gid
    	local groups = {}
    	for key, val in pairs(src) do
    		-- `key` must only be a string or a number...
    		gid = tonumber(key)
    		if gid == nil then
    			prefix, gid = key:match'^%s*(.-)%s*(%-?%d*)%s*$'
    			gid = tonumber(gid) or ''
    		else prefix = '' end
    		if groups[gid] == nil then groups[gid] = {} end
    		tmp = tonumber(prefix)
    		if tmp ~= nil then
    			if tmp < 1 then prefix = tmp - 1 else prefix = tmp end
    		end
    		groups[gid][prefix] = val
    	end
    	return groups
    end
    
    
    -- Concatenate the numerical keys from the table of parameters to the numerical
    -- keys from the table of options; non-numerical keys from the table of options
    -- will prevail over colliding non-numerical keys from the table of parameters
    local function concat_params (ctx)
    	local tbl = ctx.params
    	local size = table.maxn(ctx.pipe)
    	local retval = {}
    	if ctx.subset == 1 then
    		-- We need only the sequence
    		for key, val in ipairs(tbl) do retval[key + size] = val end
    	else
    		if ctx.subset == -1 then
    			for key, val in ipairs(tbl) do tbl[key] = nil end
    		end
    		for key, val in pairs(tbl) do
    			if type(key) == 'number' then retval[key + size] = val
    			else retval[key] = val end
    		end
    	end
    	for key, val in pairs(ctx.pipe) do retval[key] = 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 tbl[key] = 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
    				nums[nn] = key
    			else
    				nw = nw + 1
    				words[nw] = key
    			end
    		end
    		table.sort(nums)
    		table.sort(words, natural_sort)
    		if ctx.sorttype == 2 then
    			for idx = 1, nw do fn(words[idx], tbl[words[idx]]) end
    			for idx = 1, nn do fn(nums[idx], tbl[nums[idx]]) end
    			return
    		end
    		for idx = 1, nn do fn(nums[idx], tbl[nums[idx]]) end
    		for idx = 1, nw do fn(words[idx], tbl[words[idx]]) end
    		return
    	end
    	if ctx.subset ~= -1 then
    		for key, val in ipairs(tbl) do
    			fn(key, val)
    			tbl[key] = 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(ctx.luaname ..
    		': The two directives ‘non-sequential’ and ‘sequential’ are in contradiction with each other', 0) end
    	if ctx.sorttype > 0 then error(ctx.luaname ..
    		': 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(ctx.luaname ..
    		': 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(ctx.luaname ..
    		': The ‘all_sorted’ directive is redundant after ‘sequential’', 0) end
    	if ctx.sorttype == 2 then error(ctx.luaname ..
    		': 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(ctx.luaname ..
    		': The ‘reassorted’ directive is redundant after ‘sequential’', 0) end
    	if ctx.sorttype == 1 then error(ctx.luaname ..
    		': 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 = opts[1]
    	if cmd ~= nil then
    		cmd = cmd:gsub('%s+', ''):gsub('/+', '/'):match'^/*(.*[^/])'
    	end
    	if cmd == nil then error(ctx.luaname ..
    		', ‘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
    				ctx[val] = opts[argc]
    				dest[key] = nil
    			end
    			argc = argc + 1
    		else
    			vname = memoryslots[string.char(chr)]
    			if vname == nil then error(ctx.luaname ..
    				', ‘setting’: Unknown slot ‘' ..
    				string.char(chr) .. '’', 0) end
    			table.insert(dest, vname)
    		end
    	end
    	for key, val in ipairs(dest) do ctx[val] = opts[argc] 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
    			indices[newlen] = key
    			store[key] = val
    			tbl[key] = nil
    		end
    	end
    	table.sort(indices)
    	for idx = 1, newlen do tbl[idx] = store[indices[idx]] 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 < nmin then nmin = key end
    				nmax = key
    			elseif key > nmax then nmax = key
    			elseif key < nmin then nmin = key end
    			nnums = nnums + 1
    			tmp[key] = val
    		end
    	end
    	if nmax ~= nil and nmax - nmin > nnums then
    		ctx.n_available = ctx.n_available + nmin + nnums - nmax
    		if ctx.n_available < 0 then error(ctx.luaname ..
    			', ‘filling_the_gaps’: It is possible to fill at most ' ..
    			tostring(maxfill) .. ' parameters', 0) end
    		for idx = nmin, nmax, 1 do tbl[idx] = '' end
    		for key, val in pairs(tmp) do tbl[key] = val end
    	end
    	return context_iterate(ctx, 1)
    end
    
    
    -- Syntax:  #invoke:params|clearing|pipe to
    library.clearing = function (ctx)
    	local tbl = ctx.params
    	local numericals = {}
    	for key, val in pairs(tbl) do
    		if type(key) == 'number' then
    			numericals[key] = val
    			tbl[key] = nil
    		end
    	end
    	for key, val in ipairs(numericals) do tbl[key] = val end
    	return context_iterate(ctx, 1)
    end
    
    
    -- Syntax:  #invoke:params|cutting|left cut|right cut|pipe to
    library.cutting = function (ctx)
    	local lcut = tonumber(ctx.pipe[1])
    	if lcut == nil then error(ctx.luaname ..
    		', ‘cutting’: Left cut must be a number', 0) end
    	local rcut = tonumber(ctx.pipe[2])
    	if rcut == nil then error(ctx.luaname ..
    		', ‘cutting’: Right cut must be a number', 0) end
    	local tbl = ctx.params
    	local len = #tbl
    	if lcut < 0 then lcut = len + lcut end
    	if rcut < 0 then rcut = len + rcut end
    	local tot = lcut + rcut
    	if tot > 0 then
    		local cache = {}
    		if tot >= len then
    			for key in ipairs(tbl) do tbl[key] = nil end
    			tot = len
    		else
    			for idx = len - rcut + 1, len, 1 do tbl[idx] = nil end
    			for idx = 1, lcut, 1 do tbl[idx] = nil end
    		end
    		for key, val in pairs(tbl) do
    			if type(key) == 'number' and key > 0 then
    				if key > len then cache[key - tot] = val
    				else cache[key - lcut] = val end
    				tbl[key] = nil
    			end
    		end
    		for key, val in pairs(cache) do tbl[key] = 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.pipe[1])
    	if lcut == nil then error(ctx.luaname ..
    		', ‘cropping’: Left crop must be a number', 0) end
    	local rcut = tonumber(ctx.pipe[2])
    	if rcut == nil then error(ctx.luaname ..
    		', ‘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 < nmin then nmin = key end
    		end
    	end
    	if nmin ~= nil then
    		local len = nmax - nmin + 1
    		if lcut < 0 then lcut = len + lcut end
    		if rcut < 0 then rcut = len + rcut end
    		if lcut + rcut - len > -1 then
    			for key in pairs(tbl) do
    				if type(key) == 'number' then tbl[key] = nil end
    			end
    		elseif lcut + rcut > 0 then
    			for idx = nmax - rcut + 1, nmax do tbl[idx] = nil end
    			for idx = nmin, nmin + lcut - 1 do tbl[idx] = nil end
    			local lshift = nmin + lcut - 1
    			if lshift > 0 then
    				for idx = lshift + 1, nmax, 1 do
    					tbl[idx - lshift] = tbl[idx]
    					tbl[idx] = 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.pipe[1])
    	if idx == nil then error(ctx.luaname ..
    		', ‘purging’: Start offset must be a number', 0) end
    	local len = tonumber(ctx.pipe[2])
    	if len == nil then error(ctx.luaname ..
    		', ‘purging’: Length must be a number', 0) end
    	local tbl = ctx.params
    	if len < 1 then
    		len = len + table.maxn(tbl)
    		if idx > 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.pipe[1])
    	if last == nil then error(ctx.luaname ..
    		', ‘backpurging’: Start offset must be a number', 0) end
    	local len = tonumber(ctx.pipe[2])
    	if len == nil then error(ctx.luaname ..
    		', ‘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 < idx) then idx = key end
    		end
    		if idx == nil then return context_iterate(ctx, 3) end
    		idx = idx - len
    		if last < idx then return context_iterate(ctx, 3) end
    		len = last - idx + 1
    	end
    	ctx.params = copy_table_reduced(ctx.params, idx, len)
    	return context_iterate(ctx, 3)
    end
    
    
    -- Syntax:  #invoke:params|rotating|pipe to
    library.rotating = function (ctx)
    	local tbl = ctx.params
    	local numericals = {}
    	local nmax = 0
    	for key, val in pairs(tbl) do
    		if type(key) == 'number' then
    			numericals[key] = val
    			tbl[key] = nil
    			if key > nmax then nmax = key end
    		end
    	end
    	for key, val in pairs(numericals) do tbl[nmax - 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 < 2 then return library.rotating(ctx) end
    	local numericals = {}
    	for key, val in pairs(tbl) do
    		if type(key) == 'number' then
    			numericals[key] = val
    			tbl[key] = nil
    		end
    	end
    	for key, val in pairs(numericals) do tbl[shift - key] = val end
    	return context_iterate(ctx, 1)
    end
    ]]--
    
    
    -- Syntax:  #invoke:params|mirroring|pipe to
    --[[
    library.mirroring = function (ctx)
    	local tbl = ctx.params
    	local numericals = {}
    	local nmax
    	local nmin
    	for key, val in pairs(tbl) do
    		if type(key) == 'number' then
    			numericals[key] = val
    			tbl[key] = nil
    			if nmax == nil then
    				nmax = key
    				nmin = key
    			elseif key > nmax then nmax = key
    			elseif key < nmin then nmin = key end
    		end
    	end
    	for key, val in pairs(numericals) do tbl[nmax + nmin - key] = val end
    	return context_iterate(ctx, 1)
    end
    ]]--
    
    
    -- Syntax:  #invoke:params|swapping|pipe to
    --[[
    library.swapping = function (ctx)
    	local tbl = ctx.params
    	local cache = {}
    	local nsize = 0
    	local tmp
    	for key in pairs(tbl) do
    		if type(key) == 'number' then
    			nsize = nsize + 1
    			cache[nsize] = key
    		end
    	end
    	table.sort(cache)
    	for idx = math.floor(nsize / 2), 1, -1 do
    		tmp = tbl[cache[idx] ]
    		tbl[cache[idx] ] = tbl[cache[nsize - idx + 1] ]
    		tbl[cache[nsize - idx + 1] ] = tmp
    	end
    	return context_iterate(ctx, 1)
    end
    ]]--
    
    
    -- Syntax:  #invoke:params|sorting_sequential_values|[criterion]|pipe to
    library.sorting_sequential_values = function (ctx)
    	local sortfn
    	if ctx.pipe[1] ~= nil then sortfn = sortfunctions[ctx.pipe[1]] end
    	if sortfn then table.sort(ctx.params, sortfn)
    	else table.sort(ctx.params) end -- i.e. either `false` or `nil`
    	if sortfn == nil then return context_iterate(ctx, 1) end
    	return context_iterate(ctx, 2)
    end
    
    
    -- Syntax:  #invoke:params|inserting|position|how many|...|pipe to
    --[[
    library.inserting = function (ctx)
    	-- NOTE: `ctx.params` might be the original metatable! As a modifier,
    	-- this function MUST create a copy of it before returning
    	local idx = tonumber(ctx.pipe[1])
    	if idx == nil then error(ctx.luaname ..
    		', ‘inserting’: Position must be a number', 0) end
    	local len = tonumber(ctx.pipe[2])
    	if len == nil or len < 1 then error(ctx.luaname ..
    		', ‘inserting’: The amount must be a number greater than zero', 0) end
    	local opts = ctx.pipe
    	local tbl = copy_table_expanded(ctx.params, idx, len)
    	for key = idx, idx + len - 1 do tbl[key] = opts[key - idx + 3] end
    	ctx.params = tbl
    	return context_iterate(ctx, len + 3)
    end
    ]]--
    
    
    -- Syntax:  #invoke:params|imposing|name|value|pipe to
    library.imposing = function (ctx)
    	if ctx.pipe[1] == nil then error(ctx.luaname ..
    		', ‘imposing’: Missing parameter name to impose', 0) end
    	local key = ctx.pipe[1]:match'^%s*(.-)%s*$'
    	ctx.params[tonumber(key) or key] = ctx.pipe[2]
    	return context_iterate(ctx, 3)
    end
    
    
    -- Syntax:  #invoke:params|providing|name|value|pipe to
    library.providing = function (ctx)
    	if ctx.pipe[1] == nil then error(ctx.luaname ..
    		', ‘providing’: Missing parameter name to provide', 0) end
    	local key = ctx.pipe[1]:match'^%s*(.-)%s*$'
    	key = tonumber(key) or key
    	if ctx.params[key] == nil then ctx.params[key] = ctx.pipe[2] end
    	return context_iterate(ctx, 3)
    end
    
    
    -- Syntax:  #invoke:params|discarding|name|[how many]|pipe to
    library.discarding = function (ctx)
    	if ctx.pipe[1] == nil then error(ctx.luaname ..
    		', ‘discarding’: Missing parameter name to discard', 0) end
    	local key = ctx.pipe[1]
    	local len = tonumber(ctx.pipe[2])
    	if len == nil then
    		ctx.params[tonumber(key) or key:match'^%s*(.-)%s*$'] = nil
    		return context_iterate(ctx, 2)
    	end
    	key = tonumber(key)
    	if key == nil then error(ctx.luaname ..
    		', ‘discarding’: A range was provided, but the initial parameter name is not numerical', 0) end
    	if len < 1 then error(ctx.luaname ..
    		', ‘discarding’: A range can only be a number greater than zero', 0) end
    	for idx = key, key + len - 1 do ctx.params[idx] = nil end
    	return context_iterate(ctx, 3)
    end
    
    
    -- Syntax:  #invoke:params|with_name_matching|target 1|[plain flag 1]|[or]
    --            |[target 2]|[plain flag 2]|[or]|[...]|[target N]|[plain flag
    --            N]|pipe to
    library.with_name_matching = function (ctx)
    	local tbl = ctx.params
    	local targets, argc = parse_pattern_args(ctx, targets,
    		'with_name_matching')
    	local nomatch
    	for key in pairs(tbl) do
    		nomatch = true
    		for _, ptn in ipairs(targets) do
    			if not ptn[3] then
    				if string.find(key, ptn[1], 1, ptn[2]) then
    					nomatch = false
    					break
    				end
    			elseif key == ptn[1] then
    				nomatch = false
    				break
    			end
    		end
    		if nomatch then tbl[key] = nil end
    	end
    	return context_iterate(ctx, argc)
    end
    
    
    -- Syntax:  #invoke:params|with_name_not_matching|target 1|[plain flag 1]
    --            |[and]|[target 2]|[plain flag 2]|[and]|[...]|[target N]|[plain
    --            flag N]|pipe to
    library.with_name_not_matching = function (ctx)
    	local tbl = ctx.params
    	local targets, argc = parse_pattern_args(ctx, targets,
    		'with_name_not_matching')
    	local yesmatch
    	for key in pairs(tbl) do
    		yesmatch = true
    		for _, ptn in ipairs(targets) do
    			if ptn[3] then
    				if key ~= ptn[1] then
    					yesmatch = false
    					break
    				end
    			elseif not string.find(key, ptn[1], 1, ptn[2]) then
    				yesmatch = false
    				break
    			end
    		end
    		if yesmatch then tbl[key] = nil end
    	end
    	return context_iterate(ctx, argc)
    end
    
    
    -- Syntax:  #invoke:params|with_value_matching|target 1|[plain flag 1]|[or]
    --            |[target 2]|[plain flag 2]|[or]|[...]|[target N]|[plain flag
    --            N]|pipe to
    library.with_value_matching = function (ctx)
    	local tbl = ctx.params
    	local targets, argc = parse_pattern_args(ctx, targets,
    		'with_value_matching')
    	local nomatch
    	for key, val in pairs(tbl) do
    		nomatch = true
    		for _, ptn in ipairs(targets) do
    			if ptn[3] then
    				if val == ptn[1] then
    					nomatch = false
    					break
    				end
    			elseif string.find(val, ptn[1], 1, ptn[2]) then
    				nomatch = false
    				break
    			end
    		end
    		if nomatch then tbl[key] = nil end
    	end
    	return context_iterate(ctx, argc)
    end
    
    
    -- Syntax:  #invoke:params|with_value_not_matching|target 1|[plain flag 1]
    --            |[and]|[target 2]|[plain flag 2]|[and]|[...]|[target N]|[plain
    --            flag N]|pipe to
    library.with_value_not_matching = function (ctx)
    	local tbl = ctx.params
    	local targets, argc = parse_pattern_args(ctx, targets,
    		'with_value_not_matching')
    	local yesmatch
    	for key, val in pairs(tbl) do
    		yesmatch = true
    		for _, ptn in ipairs(targets) do
    			if ptn[3] then
    				if val ~= ptn[1] then
    					yesmatch = false
    					break
    				end
    			elseif not string.find(val, ptn[1], 1, ptn[2]) then
    				yesmatch = false
    				break
    			end
    		end
    		if yesmatch then tbl[key] = nil end
    	end
    	return context_iterate(ctx, argc)
    end
    
    
    -- Syntax:  #invoke:params|trimming_values|pipe to
    library.trimming_values = function (ctx)
    	local tbl = ctx.params
    	for key, val in pairs(tbl) do tbl[key] = val:match'^%s*(.-)%s*$' end
    	return context_iterate(ctx, 1)
    end
    
    
    -- Syntax:  #invoke:params|mapping_by_calling|template name|[call
    --            style]|[let]|[...][number of additional parameters]|[parameter
    --            1]|[parameter 2]|[...]|[parameter N]|pipe to
    library.mapping_by_calling = function (ctx)
    	local opts = ctx.pipe
    	local tname
    	if opts[1] ~= nil then tname = opts[1]:match'^%s*(.*%S)' end
    	if tname == nil then error(ctx.luaname ..
    		', ‘mapping_by_calling’: No template name was provided', 0) end
    	local margs, argc, looptype, karg, varg = parse_callback_args(opts, 1,
    		mapping_styles.values_only)
    	local model = { title = tname, args = margs }
    	value_maps[looptype](ctx.params, margs, karg, varg, function ()
    		return ctx.frame:expandTemplate(model)
    	end)
    	return context_iterate(ctx, argc)
    end
    
    
    -- Syntax:  #invoke:params|mapping_by_invoking|module name|function
    --            name|[call style]|[let]|[...]|[number of additional
    --            arguments]|[argument 1]|[argument 2]|[...]|[argument N]|pipe to
    library.mapping_by_invoking = function (ctx)
    	local opts = ctx.pipe
    	local mname
    	local fname
    	if opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end
    	if mname == nil then error(ctx.luaname ..
    		', ‘mapping_by_invoking’: No module name was provided', 0) end
    	if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end
    	if fname == nil then error(ctx.luaname ..
    		', ‘mapping_by_invoking’: No function name was provided', 0) end
    	local margs, argc, looptype, karg, varg = parse_callback_args(opts, 2,
    		mapping_styles.values_only)
    	local model = { title = 'Module:' .. mname, args = margs }
    	local mfunc = require(model.title)[fname]
    	if mfunc == nil then error(ctx.luaname ..
    		', ‘mapping_by_invoking’: The function ‘' .. fname ..
    		'’ does not exist', 0) end
    	value_maps[looptype](ctx.params, margs, karg, varg, function ()
    		return mfunc(ctx.frame:newChild(model))
    	end)
    	return context_iterate(ctx, argc)
    end
    
    
    -- Syntax:  #invoke:params|mapping_by_magic|parser function|[call
    --            style]|[let]|[...][number of additional arguments]|[argument
    --            1]|[argument 2]|[...]|[argument N]|pipe to
    library.mapping_by_magic = function (ctx)
    	local opts = ctx.pipe
    	local magic
    	if opts[1] ~= nil then magic = opts[1]:match'^%s*(.*%S)' end
    	if magic == nil then error(ctx.luaname ..
    		', ‘mapping_by_magic’: No parser function was provided', 0) end
    	local margs, argc, looptype, karg, varg = parse_callback_args(opts, 1,
    		mapping_styles.values_only)
    	value_maps[looptype](ctx.params, margs, karg, varg, function ()
    		return ctx.frame:callParserFunction(magic, margs)
    	end)
    	return context_iterate(ctx, argc)
    end
    
    
    -- Syntax:  #invoke:params|mapping_by_replacing|target|replace|[count]|[plain
    --            flag]|pipe to
    library.mapping_by_replacing = function (ctx)
    	local ptn, repl, nmax, is_strict, argc, die =
    		parse_replace_args(ctx.pipe, 'mapping_by_replacing')
    	if die then return context_iterate(ctx, argc) end
    	local tbl = ctx.params
    	if is_strict then
    		for key, val in pairs(tbl) do
    			if val == ptn then tbl[key] = repl end
    		end
    	else
    		if flg == 2 then
    			-- Copied from Module:String's `str._escapePattern()`
    			ptn = ptn:gsub('[%(%)%.%%%+%-%*%?%[%^%$%]]', '%%%0')
    		end
    		for key, val in pairs(tbl) do
    			tbl[key] = val:gsub(ptn, repl, nmax)
    		end
    	end
    	return context_iterate(ctx, argc)
    end
    
    
    -- Syntax:  #invoke:params|renaming_by_calling|template name|[call
    --            style]|[let]|[...][number of additional parameters]|[parameter
    --            1]|[parameter 2]|[...]|[parameter N]|pipe to
    library.renaming_by_calling = function (ctx)
    	local opts = ctx.pipe
    	local tname
    	if opts[1] ~= nil then tname = opts[1]:match'^%s*(.*%S)' end
    	if tname == nil then error(ctx.luaname ..
    		', ‘renaming_by_calling’: No template name was provided', 0) end
    	local rargs, argc, looptype, karg, varg = parse_callback_args(opts, 1,
    		mapping_styles.names_only)
    	local model = { title = tname, args = rargs }
    	map_names(ctx.params, rargs, karg, varg, looptype, function ()
    		return ctx.frame:expandTemplate(model)
    	end)
    	return context_iterate(ctx, argc)
    end
    
    
    -- Syntax:  #invoke:params|renaming_by_invoking|module name|function
    --            name|[call style]|[let]|[...]|[number of additional
    --            arguments]|[argument 1]|[argument 2]|[...]|[argument N]|pipe to
    library.renaming_by_invoking = function (ctx)
    	local opts = ctx.pipe
    	local mname
    	local fname
    	if opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end
    	if mname == nil then error(ctx.luaname ..
    		', ‘renaming_by_invoking’: No module name was provided', 0) end
    	if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end
    	if fname == nil then error(ctx.luaname ..
    		', ‘renaming_by_invoking’: No function name was provided', 0) end
    	local rargs, argc, looptype, karg, varg = parse_callback_args(opts, 2,
    		mapping_styles.names_only)
    	local model = { title = 'Module:' .. mname, args = rargs }
    	local mfunc = require(model.title)[fname]
    	if mfunc == nil then error(ctx.luaname ..
    		', ‘renaming_by_invoking’: The function ‘' .. fname ..
    		'’ does not exist', 0) end
    	map_names(ctx.params, rargs, karg, varg, looptype, function ()
    		return mfunc(ctx.frame:newChild(model))
    	end)
    	return context_iterate(ctx, argc)
    end
    
    
    -- Syntax:  #invoke:params|renaming_by_magic|parser function|[call
    --            style]|[let]|[...][number of additional arguments]|[argument
    --            1]|[argument 2]|[...]|[argument N]|pipe to
    library.renaming_by_magic = function (ctx)
    	local opts = ctx.pipe
    	local magic
    	if opts[1] ~= nil then magic = opts[1]:match'^%s*(.*%S)' end
    	if magic == nil then error(ctx.luaname ..
    		', ‘renaming_by_magic’: No parser function was provided', 0) end
    	local rargs, argc, looptype, karg, varg = parse_callback_args(opts, 1,
    		mapping_styles.names_only)
    	map_names(ctx.params, rargs, karg, varg, looptype, function ()
    		return ctx.frame:callParserFunction(magic, rargs)
    	end)
    	return context_iterate(ctx, argc)
    end
    
    
    -- Syntax:  #invoke:params|renaming_by_replacing|target|replace|[count]|[plain
    --            flag]|pipe to
    library.renaming_by_replacing = function (ctx)
    	local ptn, repl, nmax, is_strict, argc, die =
    		parse_replace_args(ctx.pipe, 'renaming_by_replacing')
    	if die then return context_iterate(ctx, argc) end
    	local tbl = ctx.params
    	if is_strict then
    		local key = tonumber(ptn) or ptn:match'^%s*(.-)%s*$'
    		local val = tbl[key]
    		tbl[key] = nil
    		tbl[tonumber(repl) or repl:match'^%s*(.-)%s*$'] = val
    	else
    		if flg == 2 then
    			-- Copied from Module:String's `str._escapePattern()`
    			ptn = ptn:gsub('[%(%)%.%%%+%-%*%?%[%^%$%]]', '%%%0')
    		end
    		local cache = {}
    		for key, val in pairs(tbl) do
    			steal_if_renamed(val, tbl, key, cache,
    				tostring(key):gsub(ptn, repl, nmax))
    		end
    		for key, val in pairs(cache) do tbl[key] = val end
    	end
    	return context_iterate(ctx, argc)
    end
    
    
    -- Syntax:  #invoke:params|grouping_by_calling|template
    --            name|[let]|[...]|[number of additional arguments]|[argument
    --            1]|[argument 2]|[...]|[argument N]|pipe to
    library.grouping_by_calling = function (ctx)
    	-- NOTE: `ctx.params` might be the original metatable! As a modifier,
    	-- this function MUST create a copy of it before returning
    	local opts = ctx.pipe
    	local tmp
    	if opts[1] ~= nil then tmp = opts[1]:match'^%s*(.*%S)' end
    	if tmp == nil then error(ctx.luaname ..
    		', ‘grouping_by_calling’: No template name was provided', 0) end
    	local model = { title = tmp }
    	local tmp, argc = parse_child_args(opts, 2, 0)
    	local gargs = {}
    	for key, val in pairs(tmp) do
    		if type(key) == 'number' and key < 1 then gargs[key - 1] = val
    		else gargs[key] = val end
    	end
    	local groups = make_groups(ctx.params)
    	for gid, group in pairs(groups) do
    		for key, val in pairs(gargs) do group[key] = val end
    		group[0] = gid
    		model.args = group
    		groups[gid] = ctx.frame:expandTemplate(model)
    	end
    	ctx.params = groups
    	return context_iterate(ctx, argc)
    end
    
    
    
    	--[[ Functions ]]--
    	-----------------------------
    
    
    -- Syntax:  #invoke:params|count
    library.count = function (ctx)
    	-- NOTE: `ctx.pipe` and `ctx.params` might be the original metatables!
    	local retval = 0
    	for _ in ctx.iterfunc(ctx.params) do retval = retval + 1 end
    	if ctx.subset == -1 then retval = retval - #ctx.params end
    	ctx.text = retval
    	return false
    end
    
    
    -- Syntax:  #invoke:args|concat_and_call|template name|[prepend 1]|[prepend 2]
    --            |[...]|[item n]|[named item 1=value 1]|[...]|[named item n=value
    --            n]|[...]
    library.concat_and_call = function (ctx)
    	-- NOTE: `ctx.params` might be the original metatable!
    	local opts = ctx.pipe
    	local tname
    	if opts[1] ~= nil then tname = opts[1]:match'^%s*(.*%S)' end
    	if tname == nil then error(ctx.luaname ..
    		', ‘concat_and_call’: No template name was provided', 0) end
    	remove_numerical_keys(opts, 1, 1)
    	ctx.text = ctx.frame:expandTemplate{
    		title = tname,
    		args = concat_params(ctx)
    	}
    	return false
    end
    
    
    -- Syntax:  #invoke:args|concat_and_invoke|module name|function name|[prepend
    --            1]|[prepend 2]|[...]|[item n]|[named item 1=value 1]|[...]|[named
    --            item n=value n]|[...]
    library.concat_and_invoke = function (ctx)
    	-- NOTE: `ctx.params` might be the original metatable!
    	local opts = ctx.pipe
    	local mname
    	local fname
    	if opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end
    	if mname == nil then error(ctx.luaname ..
    		', ‘concat_and_invoke’: No module name was provided', 0) end
    	if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end
    	if fname == nil then error(ctx.luaname ..
    		', ‘concat_and_invoke’: No function name was provided', 0) end
    	remove_numerical_keys(opts, 1, 2)
    	local mfunc = require('Module:' .. mname)[fname]
    	if mfunc == nil then error(ctx.luaname ..
    		', ‘concat_and_invoke’: The function ‘' .. fname ..
    		'’ does not exist', 0) end
    	ctx.text = mfunc(ctx.frame:newChild{
    		title = 'Module:' .. fname,
    		args = concat_params(ctx)
    	})
    	return false
    end
    
    
    -- Syntax:  #invoke:args|concat_and_magic|parser function|[prepend 1]|[prepend
    --            2]|[...]|[item n]|[named item 1=value 1]|[...]|[named item n=
    --            value n]|[...]
    library.concat_and_magic = function (ctx)
    	-- NOTE: `ctx.params` might be the original metatable!
    	local opts = ctx.pipe
    	local magic
    	if opts[1] ~= nil then magic = opts[1]:match'^%s*(.*%S)' end
    	if magic == nil then error(ctx.luaname ..
    		', ‘concat_and_magic’: No parser function was provided', 0) end
    	remove_numerical_keys(opts, 1, 1)
    	ctx.text = ctx.frame:callParserFunction(magic, concat_params(ctx))
    	return false
    end
    
    
    -- Syntax:  #invoke:params|value_of|parameter name
    library.value_of = function (ctx)
    	-- NOTE: `ctx.pipe` and `ctx.params` might be the original metatables!
    	local opts = ctx.pipe
    	local kstr
    	if opts[1] ~= nil then kstr = opts[1]:match'^%s*(.*%S)' end
    	if kstr == nil then error(ctx.luaname ..
    		', ‘value_of’: No parameter name was provided', 0) end
    	local knum = tonumber(kstr)
    	local len = #ctx.params
    	local val = ctx.params[knum or kstr]
    	if val ~= nil and (
    		ctx.subset ~= -1 or knum == nil or knum > len or knum < 1
    	) and (
    		ctx.subset ~= 1 or (knum ~= nil and knum <= len and 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)
    			ret[nss + 1] = pps
    			ret[nss + 2] = key
    			ret[nss + 3] = kvs
    			ret[nss + 4] = val
    			nss = nss + 4
    		end
    	)
    	if nss > 0 then
    		if nss > 4 and ctx.lastsep ~= nil then
    			ret[nss - 3] = ctx.lastsep
    		end
    		ret[1] = ctx.header or ''
    		if ctx.footer ~= nil then ret[nss + 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!
    	local pps = ctx.itersep or ''
    	local ret = {}
    	local nss = 0
    	flush_params(
    		ctx,
    		function (key, val)
    			ret[nss + 1] = pps
    			ret[nss + 2] = val
    			nss = nss + 2
    		end
    	)
    	if nss > 0 then
    		if nss > 2 and ctx.lastsep ~= nil then
    			ret[nss - 1] = ctx.lastsep
    		end
    		ret[1] = ctx.header or ''
    		if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end
    		ctx.text = table.concat(ret)
    		return false
    	end
    	ctx.text = ctx.ifngiven or ''
    	return false
    end
    
    
    -- Syntax:  #invoke:params|for_each|wikitext
    library.for_each = function (ctx)
    	-- NOTE: `ctx.pipe` might be the original metatable!
    	local txt = ctx.pipe[1] or ''
    	local pps = ctx.itersep or ''
    	local ret = {}
    	local nss = 0
    	flush_params(
    		ctx,
    		function (key, val)
    			ret[nss + 1] = pps
    			ret[nss + 2] = txt:gsub('%$#', key):gsub('%$@', val)
    			nss = nss + 2
    		end
    	)
    	if nss > 0 then
    		if nss > 2 and ctx.lastsep ~= nil then
    			ret[nss - 1] = ctx.lastsep
    		end
    		ret[1] = ctx.header or ''
    		if ctx.footer ~= nil then ret[nss + 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 opts[1] ~= nil then tname = opts[1]:match'^%s*(.*%S)' end
    	if tname == nil then error(ctx.luaname ..
    		', ‘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)
    			opts[1] = key
    			opts[2] = val
    			ret[nss + 1] = ccs
    			ret[nss + 2] = ctx.frame:expandTemplate(model)
    			nss = nss + 2
    		end
    	)
    	if nss > 0 then
    		if nss > 2 and ctx.lastsep ~= nil then
    			ret[nss - 1] = ctx.lastsep
    		end
    		ret[1] = ctx.header or ''
    		if ctx.footer ~= nil then ret[nss + 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 opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end
    	if mname == nil then error(ctx.luaname ..
    		', ‘invoke_for_each’: No module name was provided', 0) end
    	if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end
    	if fname == nil then error(ctx.luaname ..
    		', ‘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)
    			opts[1] = key
    			opts[2] = val
    			ret[nss + 1] = ccs
    			ret[nss + 2] = mfunc(ctx.frame:newChild(model))
    			nss = nss + 2
    		end
    	)
    	if nss > 0 then
    		if nss > 2 and ctx.lastsep ~= nil then
    			ret[nss - 1] = ctx.lastsep
    		end
    		ret[1] = ctx.header or ''
    		if ctx.footer ~= nil then ret[nss + 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 opts[1] ~= nil then magic = opts[1]:match'^%s*(.*%S)' end
    	if magic == nil then error(ctx.luaname ..
    		', ‘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)
    			opts[1] = key
    			opts[2] = val
    			ret[nss + 1] = ccs
    			ret[nss + 2] = ctx.frame:callParserFunction(magic,
    				opts)
    			nss = nss + 2
    		end
    	)
    	if nss > 0 then
    		if nss > 2 and ctx.lastsep ~= nil then
    			ret[nss - 1] = ctx.lastsep
    		end
    		ret[1] = ctx.header or ''
    		if ctx.footer ~= nil then ret[nss + 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 opts[1] ~= nil then tname = opts[1]:match'^%s*(.*%S)' end
    	if tname == nil then error(ctx.luaname ..
    		', ‘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)
    			opts[1] = val
    			ret[nss + 1] = ccs
    			ret[nss + 2] = ctx.frame:expandTemplate(model)
    			nss = nss + 2
    		end
    	)
    	if nss > 0 then
    		if nss > 2 and ctx.lastsep ~= nil then
    			ret[nss - 1] = ctx.lastsep
    		end
    		ret[1] = ctx.header or ''
    		if ctx.footer ~= nil then ret[nss + 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 opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end
    	if mname == nil then error(ctx.luaname ..
    		', ‘invoke_for_each_value’: No module name was provided', 0) end
    	if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end
    	if fname == nil then error(ctx.luaname ..
    		', ‘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_numerical_keys(opts, 1, 1)
    	flush_params(
    		ctx,
    		function (key, val)
    			opts[1] = val
    			ret[nss + 1] = ccs
    			ret[nss + 2] = mfunc(ctx.frame:newChild(model))
    			nss = nss + 2
    		end
    	)
    	if nss > 0 then
    		if nss > 2 and ctx.lastsep ~= nil then
    			ret[nss - 1] = ctx.lastsep
    		end
    		ret[1] = ctx.header or ''
    		if ctx.footer ~= nil then ret[nss + 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 opts[1] ~= nil then magic = opts[1]:match'^%s*(.*%S)' end
    	if magic == nil then error(ctx.luaname ..
    		', ‘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)
    			opts[1] = val
    			ret[nss + 1] = ccs
    			ret[nss + 2] = ctx.frame:callParserFunction(magic,
    				opts)
    			nss = nss + 2
    		end
    	)
    	if nss > 0 then
    		if nss > 2 and ctx.lastsep ~= nil then
    			ret[nss - 1] = ctx.lastsep
    		end
    		ret[1] = ctx.header or ''
    		if ctx.footer ~= nil then ret[nss + 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 opts[1] ~= nil then tmp = opts[1]:match'^%s*(.*%S)' end
    	if tmp == nil then error(ctx.luaname ..
    		', ‘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 opts[key - 1] = val
    		else opts[key] = 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 group[key] = val end
    			group[0] = gid
    			model.args = group
    			ret[nss + 1] = ccs
    			ret[nss + 2] = ctx.frame:expandTemplate(model)
    			nss = nss + 2
    		end
    	)
    	if nss > 0 then
    		if nss > 2 and ctx.lastsep ~= nil then
    			ret[nss - 1] = ctx.lastsep
    		end
    		ret[1] = ctx.header or ''
    		if ctx.footer ~= nil then ret[nss + 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()
    	ctx.frame = 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(static_iface, {
    	__index = function (iface, _fname_)
    		local ctx = context_new()
    		local fname = _fname_:match'^%s*(.*%S)'
    		if fname == nil then error(ctx.luaname ..
    			': You must specify a function to call', 0) end
    		if library[fname] == nil then error(ctx.luaname ..
    			': The function ‘' .. fname .. '’ does not exist', 0) end
    		local func = library[fname]
    		return function (frame)
    			ctx.frame = frame:getParent()
    			ctx.pipe = copy_or_ref_table(frame.args,
    				refpipe[fname])
    			ctx.params = copy_or_ref_table(ctx.frame.args,
    				refparams[fname])
    			main_loop(ctx, func)
    			return ctx.text
    		end
    	end
    })