Module:Params: Difference between revisions
From All Skies Encyclopaedia
| imported>Grufo  (Fix collisions in `call_for_each_group`) | imported>Grufo   (The directives ‘with_pattern_isep’, ‘with_plain_isep’, ‘with_pattern_psep’ and ‘with_plain_psep’ have been renamed respectively to ‘splitter_pattern’, ‘splitter_string’, ‘setter_pattern’, and ‘setter_string’) | ||
| (29 intermediate revisions by the same user not shown) | |||
| Line 11: | Line 11: | ||
| -- Helper function for `string.gsub()` (for managing zero-padded numbers) | -- Helper function for `string.gsub()` (for managing zero-padded numbers) | ||
| function zero_padded(str) | local function zero_padded (str) | ||
| 	return ( | 	return ('%03d%s'):format(#str, str) | ||
| end | end | ||
| -- Helper function for `table.sort()` (for natural sorting) | -- Helper function for `table.sort()` (for natural sorting) | ||
| function natural_sort(var1, var2) | local function natural_sort (var1, var2) | ||
| 	return tostring(var1):gsub( | 	return tostring(var1):gsub('%d+', zero_padded) < | ||
| 		tostring(var2):gsub( | 		tostring(var2):gsub('%d+', zero_padded) | ||
| end | end | ||
| -- Return a copy or a reference to a table | -- Return a copy or a reference to a table | ||
| local function copy_or_ref_table(src, refonly) | local function copy_or_ref_table (src, refonly) | ||
| 	if refonly then return src end | 	if refonly then return src end | ||
| 	newtab = {} | 	newtab = {} | ||
| Line 32: | Line 32: | ||
| -- Remove some numeric elements from a table, shifting everything to the left | |||
| local function remove_numeric_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 | |||
| 	--[[ Module's private environment ]]-- | |||
| 	-------------------------------------- | |||
| -- 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 | |||
| -- Special user-given keywords (functions and modifiers MUST avoid these names) | |||
| -- 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 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 | |||
| 		dest[realkey] = val | |||
| 		src[skey] = nil | |||
| 	end | |||
| end | |||
| 	--[[ Public strings ]]-- | |||
| 	------------------------ | |||
| -- Special match keywords (functions and modifiers MUST avoid these names) | |||
| local mkeywords = { | local mkeywords = { | ||
| 	['or'] = 0, | 	['or'] = 0, | ||
| 	pattern = 1, | |||
| 	plain = 2, | |||
| 	strict = 3 | |||
| } | } | ||
| Line 48: | Line 131: | ||
| -- Sort functions (functions and modifiers MUST avoid these names) | -- Sort functions (functions and modifiers MUST avoid these names) | ||
| local sortfunctions = { | 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 | -- Memory slots (functions and modifiers MUST avoid these names) | ||
| local memoryslots = { | local memoryslots = { | ||
| 	i = 'itersep', | 	i = 'itersep', | ||
| Line 64: | Line 176: | ||
| -- Possible trimming modes for the `parsing` modifier | |||
| -- Directive that can only be invoked as first arguments | |||
| local  | local trim_parse_opts = { | ||
| 	trim_none = { false, false }, | |||
| 	new = 'modifier', | |||
| 	trim_positional = { false, true }, | |||
| 	self = 'function', | |||
| 	trim_named = { true, false }, | |||
| 	trim_all = { true, true } | |||
| } | } | ||
| -- Possible string modes for the iteration separator in the `parsing` and | |||
| -- Maximum number of numerical parameters that can be filled, if missing (we | |||
| -- `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 | -- chose an arbitrary number for this constant; you can discuss about its | ||
| -- optimal value at Module talk:Params) | -- optimal value at Module talk:Params) | ||
| Line 81: | Line 255: | ||
| -- Functions and modifiers that can only be invoked in first position | |||
| -- Prepare the context | |||
| local static_iface = {} | |||
| local function context_new(frame) | |||
| -- Create a new context | |||
| local function context_new (frame) | |||
| 	local ctx = {} | 	local ctx = {} | ||
| 	ctx. | 	ctx.frame = frame | ||
| 	ctx.oparams = frame.args | |||
| 	ctx.firstposonly = static_iface | |||
| 	ctx.iterfunc = pairs | 	ctx.iterfunc = pairs | ||
| 	ctx. | 	ctx.sorttype = 0 | ||
| 	ctx.n_parents = 0 | |||
| 	ctx.n_children = 0 | |||
| 	ctx.n_available = maxfill | |||
| 	return ctx | 	return ctx | ||
| end | |||
| -- Prepare the context | |||
| local function context_init(frame, funcname, refpipe, refparams) | |||
| 	local ctx = context_new(frame) | |||
| 	ctx.pipe = copy_or_ref_table(frame.args, refpipe) | |||
| 	ctx.params = copy_or_ref_table(ctx.frame.args, refparams) | |||
| 	return funcname(ctx) | |||
| end | end | ||
| -- Move to the next action within the user-given list | -- Move to the next action within the user-given list | ||
| local function context_iterate(ctx, n_forward) | local function context_iterate (ctx, n_forward) | ||
| 	local nextfn | 	local nextfn | ||
| 	if ctx.pipe[n_forward] ~= nil then | 	if ctx.pipe[n_forward] ~= nil then | ||
| 		nextfn = ctx.pipe[n_forward]:match'^%s*(.*%S)' | 		nextfn = ctx.pipe[n_forward]:match'^%s*(.*%S)' | ||
| 	end | 	end | ||
| 	if nextfn == nil then | 	if nextfn == nil then error(modulename .. | ||
| 		': You must specify a function to call', 0) end | |||
| 	end | |||
| 	if library[nextfn] == nil then | 	if library[nextfn] == nil then | ||
| 		if firstposonly[nextfn] == nil then | 		if ctx.firstposonly[nextfn] == nil then error(modulename .. | ||
| 			': The function ‘' .. nextfn .. '’ does not exist', 0) | |||
| 		else error(modulename .. ': The ‘' .. nextfn .. | |||
| 				'’ does not exist', 0) | |||
| 			'’ directive can only appear in first position', 0) | |||
| 		else | |||
| 			error(ctx.luaname .. ': The ‘' .. nextfn .. '’ ' .. | |||
| 				firstposonly[nextfn] .. | |||
| 				' can only be invoked in first position', 0) | |||
| 		end | 		end | ||
| 	end | 	end | ||
| 	remove_numeric_keys(ctx.pipe, 1, n_forward) | |||
| 	for idx = n_forward, 1, -1 do table.remove(ctx.pipe, idx) end | |||
| 	return library[nextfn] | 	return library[nextfn] | ||
| end | end | ||
| -- Main loop | |||
| -- Concatenate the numerical keys from the table of parameters to the numerical | |||
| local function main_loop (ctx, start_with) | |||
| -- keys from the table of options; non-numerical keys from the table of options | |||
| 	local fn = start_with | |||
| -- will prevail over colliding non-numerical keys from the table of parameters | |||
| 	repeat fn = fn(ctx) until not fn | |||
| local function concat_params(ctx) | |||
| 	if ctx.n_parents > 0 then error(modulename .. | |||
| 	local shift = table.maxn(ctx.pipe) | |||
| 		': One or more ‘merging_substack’ directives are missing', 0) end | |||
| 	local newargs = {} | |||
| 	if ctx. | 	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 | |||
| 		-- We need only the sequence | |||
| 		for key, val in ipairs(ctx.params) do | |||
| 			newargs[key + shift] = val | |||
| 		end | |||
| 	else | |||
| 		if ctx.subset == -1 then | |||
| 			for key, val in ipairs(ctx.params) do | |||
| 				ctx.params[key] = nil | |||
| 			end | |||
| 		end | |||
| 		for key, val in pairs(ctx.params) do | |||
| 			if type(key) == 'number' then | |||
| 				newargs[key + shift] = val | |||
| 			else | |||
| 				newargs[key] = val | |||
| 			end | |||
| 		end | |||
| 	end | |||
| 	for key, val in pairs(ctx.pipe) do newargs[key] = val end | |||
| 	return newargs | |||
| end | end | ||
| -- Add a new stack of parameters to `ctx.children` | |||
| -- Flush the parameters by calling a custom function for each value (after this | |||
| local function push_cloned_stack (ctx, tbl) | |||
| -- function has been invoked `ctx.params` will no longer be usable) | |||
| local  | 	local newparams = {} | ||
| 	local  | 	local currsnap = ctx.n_children + 1 | ||
| 	if ctx. | 	if ctx.children == nil then ctx.children = { newparams } | ||
| 	else ctx.children[currsnap] = newparams end | |||
| 		for key, val in ipairs(tbl) do fn(key, val) end | |||
| 	for key, val in pairs(tbl) do newparams[key] = val end | |||
| 		return | |||
| 	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 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 | 	end | ||
| 	tmp = tonumber(src[pin]) | |||
| 	if ctx.subset == -1 then | |||
| 	if tmp ~= nil then | |||
| 		for key, val in ipairs(tbl) do tbl[key] = nil end | |||
| 		if tmp < 0 then tmp = -1 end | |||
| 		local shf = append_after - pin | |||
| 		for idx = pin + 1, pin + tmp do tbl[idx + shf] = src[idx] end | |||
| 		pin = pin + tmp + 1 | |||
| 	end | 	end | ||
| 	if  | 	if names ~= nil then | ||
| 		for key, val in pairs(names) do tbl[key] = val end | |||
| 		local nums = {} | |||
| 		local words = {} | |||
| 		local nlen = 0 | |||
| 		local wlen = 0 | |||
| 		for key, val in pairs(tbl) do | |||
| 			if type(key) == 'number' then | |||
| 				nlen = nlen + 1 | |||
| 				nums[nlen] = key | |||
| 			else | |||
| 				wlen = wlen + 1 | |||
| 				words[wlen] = key | |||
| 			end | |||
| 		end | |||
| 		table.sort(nums) | |||
| 		table.sort(words, natural_sort) | |||
| 		for idx = 1, nlen do fn(nums[idx], tbl[nums[idx]]) end | |||
| 		for idx = 1, wlen do fn(words[idx], tbl[words[idx]]) end | |||
| 		return | |||
| 	end | 	end | ||
| 	return tbl, pin | |||
| 	if ctx.subset ~= -1 then | |||
| end | |||
| 		for key, val in ipairs(tbl) do | |||
| 			fn(key, val) | |||
| 			tbl[key] = nil | |||
| -- Load the optional arguments of some of the `mapping_*` and `renaming_*` | |||
| 		end | |||
| -- class of modifiers | |||
| local function load_callback_opts (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 | 	end | ||
| 	tmp = style[7] | |||
| 	for key, val in pairs(tbl) do fn(key, val) end | |||
| 	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 = load_child_opts(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 load_replace_args (opts, fname) | |||
| 	if opts[1] == nil then error(modulename .. | |||
| 		', ‘' .. fname .. '’: No pattern string was given', 0) end | |||
| 	if opts[2] == nil then error(modulename .. | |||
| 		', ‘' .. 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 | end | ||
| -- Parse the arguments of the `with_*_matching` class of modifiers | -- Parse the arguments of the `with_*_matching` class of modifiers | ||
| local function  | local function load_pattern_args (opts, fname) | ||
| 	local state = 0 | 	local state = 0 | ||
| 	local cnt = 1 | 	local cnt = 1 | ||
| 	local keyw | 	local keyw | ||
| 	local nptns = 0 | 	local nptns = 0 | ||
| 	local ptns = {} | |||
| 	for _, val in ipairs(opts) do | 	for _, val in ipairs(opts) do | ||
| 		if state == 0 then | 		if state == 0 then | ||
| Line 219: | Line 429: | ||
| 		cnt = cnt + 1 | 		cnt = cnt + 1 | ||
| 	end | 	end | ||
| 	if state == 0 then error( | 	if state == 0 then error(modulename .. ', ‘' .. fname .. | ||
| 		'’: No pattern was given', 0) end | |||
| 	return cnt | |||
| 	return ptns, nptns, cnt | |||
| end | end | ||
| --  | -- Load the optional arguments of the `parsing` and `reinterpreting` modifiers | ||
| function  | 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 = opts[argc] | |||
| 		if tmp == nil then break end | |||
| 		tmp = tmp:match'^%s*(.-)%s*$' | |||
| 		if optslots[1] ~= nil and trim_parse_opts[tmp] ~= nil then | |||
| 			tmp = trim_parse_opts[tmp] | |||
| 			trimn = tmp[1] | |||
| 			trimu = tmp[2] | |||
| 			optslots[1] = nil | |||
| 		elseif optslots[2] ~= nil and isep_parse_opts[tmp] ~= nil then | |||
| 			argc = argc + 1 | |||
| 			iplain = isep_parse_opts[tmp] | |||
| 			isp = opts[argc] | |||
| 			optslots[2] = nil | |||
| 		elseif optslots[3] ~= nil and psep_parse_opts[tmp] ~= nil then | |||
| 			argc = argc + 1 | |||
| 			pplain = psep_parse_opts[tmp] | |||
| 			psp = opts[argc] | |||
| 			optslots[3] = nil | |||
| 		else break end | |||
| 		argc = argc + 1 | |||
| 	until noptslots < 1 | |||
| 	return isp, iplain, psp, pplain, trimn, trimu, argc | |||
| 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 = {} | 	local cache = {} | ||
| 	name_thieves_maps[looptype](cache, tbl, rargs, karg, varg, fn) | |||
| 	local pshift | |||
| 	for key, val in pairs(cache) do tbl[key] = val end | |||
| 	local nshift | |||
| end | |||
| 	if offset > 0 then | |||
| 		pshift = length | |||
| 		nshift = 0 | |||
| -- Return a new table that contains `src` regrouped according to the numeric | |||
| 	elseif offset + length > 1 then | |||
| -- suffixes in its keys | |||
| 		pshift = offset + length - 1 | |||
| local function make_groups (src) | |||
| 		nshift = 1 - offset | |||
| 	-- 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 | |||
| -- Populate a table by parsing a parameter string | |||
| local function parse_parameter_string (tbl, str, isp, ipl, psp, ppl, trn, tru) | |||
| 	local key | |||
| 	local val | |||
| 	local spos1 | |||
| 	local spos2 | |||
| 	local pos1 | |||
| 	local pos2 | |||
| 	local pos3 = 0 | |||
| 	local idx = 1 | |||
| 	local lenplone = #str + 1 | |||
| 	if isp == nil or isp == '' then | |||
| 		if psp == nil or psp == '' then | |||
| 			if tru then tbl[idx] = str:match'^%s*(.-)%s*$' | |||
| 			else tbl[idx] = str end | |||
| 			return tbl | |||
| 		end | |||
| 		spos1, spos2 = str:find(psp, 1, ppl) | |||
| 		if spos1 == nil then | |||
| 			key = idx | |||
| 			if tru then val = str:match'^%s*(.-)%s*$' | |||
| 			else val = str end | |||
| 			idx = idx + 1 | |||
| 		else | |||
| 			key = str:sub(1, spos1 - 1) | |||
| 			key = tonumber(key) or key:match'^%s*(.-)%s*$' | |||
| 			val = str:sub(spos2 + 1) | |||
| 			if trn then val = val:match'^%s*(.-)%s*$' end | |||
| 		end | |||
| 		tbl[key] = val | |||
| 		return tbl | |||
| 	end | |||
| 	if psp == nil or psp == '' then | |||
| 		repeat | |||
| 			pos1 = pos3 + 1 | |||
| 			pos2, pos3 = str:find(isp, pos1, ipl) | |||
| 			val = str:sub(pos1, (pos2 or lenplone) - 1) | |||
| 			if tru then val = val:match'^%s*(.-)%s*$' end | |||
| 			tbl[idx] = val | |||
| 			idx = idx + 1 | |||
| 		until pos2 == nil | |||
| 		return tbl | |||
| 	end | |||
| 	repeat | |||
| 		pos1 = pos3 + 1 | |||
| 		pos2, pos3 = str:find(isp, pos1, ipl) | |||
| 		val = str:sub(pos1, (pos2 or lenplone) - 1) | |||
| 		spos1, spos2 = val:find(psp, 1, ppl) | |||
| 		if spos1 == nil then | |||
| 			key = idx | |||
| 			if tru then val = val:match'^%s*(.-)%s*$' end | |||
| 			idx = idx + 1 | |||
| 		else | |||
| 			key = val:sub(1, spos1 - 1) | |||
| 			key = tonumber(key) or key:match'^%s*(.-)%s*$' | |||
| 			val = val:sub(spos2 + 1) | |||
| 			if trn then val = val:match'^%s*(.-)%s*$' end | |||
| 		end | |||
| 		tbl[key] = val | |||
| 	until pos2 == nil | |||
| 	return tbl | |||
| end | |||
| -- Concatenate the numeric keys from the table of parameters to the numeric | |||
| -- keys from the table of options; non-numeric keys from the table of options | |||
| -- will prevail over colliding non-numeric keys from the table of parameters | |||
| local function concat_params (ctx) | |||
| 	local tbl = ctx.params | |||
| 	local nmax = 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 + nmax] = val end | |||
| 	else | 	else | ||
| 		if ctx.subset == -1 then | |||
| 		pshift = 0 | |||
| 			for key in ipairs(tbl) do tbl[key] = nil end | |||
| 		nshift = length | |||
| 		end | |||
| 		for key, val in pairs(tbl) do | |||
| 			if type(key) == 'number' and key > 0 then | |||
| 				retval[key + nmax] = val | |||
| 			else retval[key] = val end | |||
| 		end | |||
| 	end | 	end | ||
| 	for key, val in pairs( | 	for key, val in pairs(ctx.pipe) do retval[key] = val end | ||
| 	return retval | |||
| 		if type(key) == 'number' then | |||
| end | |||
| 			if key > 0 and key >= offset then | |||
| 				if key >= offset + length then | |||
| 					cache[key - pshift] = val | |||
| -- Flush the parameters by calling a custom function for each value (after this | |||
| 				end | |||
| -- function has been invoked `ctx.params` will be no longer usable) | |||
| 				tbl[key] = nil | |||
| local function flush_params (ctx, fn) | |||
| 			elseif key < 1 and key < offset + length then | |||
| 	local tbl = ctx.params | |||
| 				if key < offset then | |||
| 	if ctx.subset == 1 then | |||
| 					cache[key + nshift] = val | |||
| 		for key, val in ipairs(tbl) do fn(key, val) end | |||
| 				end | |||
| 		return | |||
| 				tbl[key] = nil | |||
| 	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 | ||
| 		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 | 	end | ||
| 	if ctx.subset ~= -1 then | |||
| 	for key, val in pairs(cache) do tbl[key] = val end | |||
| 		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 | end | ||
| 	--[[  | 	--[[ Modifiers ]]-- | ||
| 	----------------------------- | 	----------------------------- | ||
| --  | -- Syntax:  #invoke:params|sequential|pipe to | ||
| library.sequential = function(ctx) | library.sequential = function (ctx) | ||
| 	if ctx.subset == -1 then error( | 	if ctx.subset == -1 then error(modulename .. | ||
| 		': The two directives ‘non-sequential’ and ‘sequential’ are in contradiction with each other', 0) end | |||
| 	if ctx.dosort then error(ctx.luaname .. ': The ‘all_sorted’ directive is redundant when followed by ‘sequential’', 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.iterfunc = ipairs | ||
| 	ctx.subset = 1 | 	ctx.subset = 1 | ||
| Line 273: | Line 716: | ||
| --  | -- Syntax:  #invoke:params|non-sequential|pipe to | ||
| library['non-sequential'] = function(ctx) | library['non-sequential'] = function (ctx) | ||
| 	if ctx.subset == 1 then error( | 	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.iterfunc = pairs | ||
| 	ctx.subset = -1 | 	ctx.subset = -1 | ||
| Line 282: | Line 726: | ||
| --  | -- Syntax:  #invoke:params|all_sorted|pipe to | ||
| library.all_sorted = function(ctx) | library.all_sorted = function (ctx) | ||
| 	if ctx.subset == 1 then error( | 	if ctx.subset == 1 then error(modulename .. | ||
| 		': The ‘all_sorted’ directive is redundant after ‘sequential’', 0) end | |||
| 	ctx.dosort = true | |||
| 	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) | 	return context_iterate(ctx, 1) | ||
| end | end | ||
| -- Syntax:  #invoke:params|reassorted|pipe to | |||
| -- See iface.setting() | |||
| library. | 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 opts = ctx.pipe | ||
| 	local cmd | 	local cmd = opts[1] | ||
| 	if  | 	if cmd ~= nil then | ||
| 		cmd =  | 		cmd = cmd:gsub('%s+', ''):gsub('/+', '/'):match'^/*(.*[^/])' | ||
| 	end | 	end | ||
| 	if cmd == nil then error( | 	if cmd == nil then error(modulename .. | ||
| 		', ‘setting’: No directive was given', 0) end | |||
| 	local sep = string.byte('/') | 	local sep = string.byte('/') | ||
| 	local argc = 2 | 	local argc = 2 | ||
| Line 313: | Line 772: | ||
| 		else | 		else | ||
| 			vname = memoryslots[string.char(chr)] | 			vname = memoryslots[string.char(chr)] | ||
| 			if vname == nil then error( | 			if vname == nil then error(modulename .. | ||
| 				', ‘setting’: Unknown slot ‘' .. | |||
| 				string.char(chr) .. '"', 0) end | |||
| 				string.char(chr) .. '’', 0) end | |||
| 			table.insert(dest, vname) | 			table.insert(dest, vname) | ||
| 		end | 		end | ||
| Line 323: | Line 783: | ||
| --  | -- Syntax:  #invoke:params|squeezing|pipe to | ||
| library.squeezing = function(ctx) | library.squeezing = function (ctx) | ||
| 	local tbl = ctx.params | 	local tbl = ctx.params | ||
| 	local store = {} | 	local store = {} | ||
| Line 343: | Line 803: | ||
| --  | -- Syntax:  #invoke:params|filling_the_gaps|pipe to | ||
| library.filling_the_gaps = function(ctx) | library.filling_the_gaps = function (ctx) | ||
| 	local tbl = ctx.params | 	local tbl = ctx.params | ||
| 	local nmin = 1 | 	local nmin = 1 | ||
| Line 356: | Line 816: | ||
| 				nmax = key | 				nmax = key | ||
| 			elseif key > nmax then nmax = key | 			elseif key > nmax then nmax = key | ||
| 			elseif key < nmin then nmin = key | 			elseif key < nmin then nmin = key end | ||
| 			end | |||
| 			nnums = nnums + 1 | 			nnums = nnums + 1 | ||
| 			tmp[key] = val | 			tmp[key] = val | ||
| Line 363: | Line 822: | ||
| 	end | 	end | ||
| 	if nmax ~= nil and nmax - nmin > nnums then | 	if nmax ~= nil and nmax - nmin > nnums then | ||
| 		ctx.n_available = ctx.n_available + nmin + nnums - nmax | |||
| 		if maxfill + nmin + nnums < nmax then error(ctx.luaname .. ', ‘filling_the_gaps’: It is possible to fill at most ' .. tostring(maxfill) .. ' parameters', 0) end | |||
| 		if ctx.n_available < 0 then error(modulename .. | |||
| 			', ‘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 idx = nmin, nmax, 1 do tbl[idx] = '' end | ||
| 		for key, val in pairs(tmp) do tbl[key] = val end | 		for key, val in pairs(tmp) do tbl[key] = val end | ||
| Line 371: | Line 833: | ||
| --  | -- Syntax:  #invoke:params|clearing|pipe to | ||
| library.clearing = function(ctx) | library.clearing = function (ctx) | ||
| 	local tbl = ctx.params | 	local tbl = ctx.params | ||
| 	local  | 	local numerics = {} | ||
| 	for key, val in pairs(tbl) do | 	for key, val in pairs(tbl) do | ||
| 		if type(key) == 'number' then | 		if type(key) == 'number' then | ||
| 			numerics[key] = val | |||
| 			tbl[key] = nil | 			tbl[key] = nil | ||
| 		end | 		end | ||
| 	end | 	end | ||
| 	for key, val in ipairs( | 	for key, val in ipairs(numerics) do tbl[key] = val end | ||
| 	return context_iterate(ctx, 1) | 	return context_iterate(ctx, 1) | ||
| end | end | ||
| -- Syntax:  #invoke:params|cutting|left cut|right cut|pipe to | |||
| -- See iface.cutting() | |||
| library.cutting = function(ctx) | library.cutting = function (ctx) | ||
| 	local lcut = tonumber(ctx.pipe[1]) | 	local lcut = tonumber(ctx.pipe[1]) | ||
| 	if lcut == nil then error( | 	if lcut == nil then error(modulename .. | ||
| 		', ‘cutting’: Left cut must be a number', 0) end | |||
| 	local rcut = tonumber(ctx.pipe[2]) | 	local rcut = tonumber(ctx.pipe[2]) | ||
| 	if rcut == nil then error( | 	if rcut == nil then error(modulename .. | ||
| 		', ‘cutting’: Right cut must be a number', 0) end | |||
| 	local tbl = ctx.params | 	local tbl = ctx.params | ||
| 	local len = #tbl | 	local len = #tbl | ||
| Line 408: | Line 872: | ||
| 		for key, val in pairs(tbl) do | 		for key, val in pairs(tbl) do | ||
| 			if type(key) == 'number' and key > 0 then | 			if type(key) == 'number' and key > 0 then | ||
| 				if key > len then | 				if key > len then cache[key - tot] = val | ||
| 				else cache[key - lcut] = val end | |||
| 				else | |||
| 					cache[key - lcut] = val | |||
| 				end | |||
| 				tbl[key] = nil | 				tbl[key] = nil | ||
| 			end | 			end | ||
| Line 422: | Line 883: | ||
| -- Syntax:  #invoke:params|cropping|left crop|right crop|pipe to | |||
| -- See iface.cropping() | |||
| library.cropping = function(ctx) | library.cropping = function (ctx) | ||
| 	local lcut = tonumber(ctx.pipe[1]) | 	local lcut = tonumber(ctx.pipe[1]) | ||
| 	if lcut == nil then error( | 	if lcut == nil then error(modulename .. | ||
| 		', ‘cropping’: Left crop must be a number', 0) end | |||
| 	local rcut = tonumber(ctx.pipe[2]) | 	local rcut = tonumber(ctx.pipe[2]) | ||
| 	if rcut == nil then error( | 	if rcut == nil then error(modulename .. | ||
| 		', ‘cropping’: Right crop must be a number', 0) end | |||
| 	local tbl = ctx.params | 	local tbl = ctx.params | ||
| 	local nmin | 	local nmin | ||
| Line 437: | Line 900: | ||
| 				nmax = key | 				nmax = key | ||
| 			elseif key > nmax then nmax = key | 			elseif key > nmax then nmax = key | ||
| 			elseif key < nmin then nmin = key | 			elseif key < nmin then nmin = key end | ||
| 			end | |||
| 		end | 		end | ||
| 	end | 	end | ||
| Line 450: | Line 912: | ||
| 			end | 			end | ||
| 		elseif lcut + rcut > 0 then | 		elseif lcut + rcut > 0 then | ||
| 			for idx = nmax - rcut + 1, nmax | 			for idx = nmax - rcut + 1, nmax do tbl[idx] = nil end | ||
| 			for idx = nmin, nmin + lcut - | 			for idx = nmin, nmin + lcut - 1 do tbl[idx] = nil end | ||
| 			local lshift = nmin + lcut - 1 | 			local lshift = nmin + lcut - 1 | ||
| 			if lshift > 0 then | 			if lshift > 0 then | ||
| Line 465: | Line 927: | ||
| -- Syntax:  #invoke:params|purging|start offset|length|pipe to | |||
| -- See iface.purging() | |||
| library.purging = function(ctx) | library.purging = function (ctx) | ||
| 	local  | 	local idx = tonumber(ctx.pipe[1]) | ||
| 	if  | 	if idx == nil then error(modulename .. | ||
| 		', ‘purging’: Start offset must be a number', 0) end | |||
| 	local size = tonumber(ctx.pipe[2]) | |||
| 	local len = tonumber(ctx.pipe[2]) | |||
| 	if size == nil then error(ctx.luaname .. ', ‘purging’: Length must be a number', 0) end | |||
| 	if len == nil then error(modulename .. | |||
| 		', ‘purging’: Length must be a number', 0) end | |||
| 	local tbl = ctx.params | 	local tbl = ctx.params | ||
| 	if  | 	if len < 1 then | ||
| 		len = len + table.maxn(tbl) | |||
| 		if  | 		if idx > len then return context_iterate(ctx, 3) end | ||
| 		len = len - idx + 1 | |||
| 	end | 	end | ||
| 	ctx.params = copy_table_reduced(tbl, idx, len) | |||
| 	purge_args(tbl, first, size) | |||
| 	return context_iterate(ctx, 3) | 	return context_iterate(ctx, 3) | ||
| end | end | ||
| --  | -- Syntax:  #invoke:params|backpurging|start offset|length|pipe to | ||
| library.backpurging = function(ctx) | library.backpurging = function (ctx) | ||
| 	local last = tonumber(ctx.pipe[1]) | 	local last = tonumber(ctx.pipe[1]) | ||
| 	if last == nil then error( | 	if last == nil then error(modulename .. | ||
| 		', ‘backpurging’: Start offset must be a number', 0) end | |||
| 	local size = tonumber(ctx.pipe[2]) | |||
| 	local len = tonumber(ctx.pipe[2]) | |||
| 	if size == nil then error(ctx.luaname .. ', ‘backpurging’: Length must be a number', 0) end | |||
| 	if len == nil then error(modulename .. | |||
| 	local first | |||
| 		', ‘backpurging’: Length must be a number', 0) end | |||
| 	local idx | |||
| 	local tbl = ctx.params | 	local tbl = ctx.params | ||
| 	if  | 	if len > 0 then | ||
| 		idx = last - len + 1 | |||
| 	else | 	else | ||
| 		for key in pairs(tbl) do | 		for key in pairs(tbl) do | ||
| 			if type(key) == 'number' and ( | 			if type(key) == 'number' and (idx == nil or | ||
| 				key <  | 				key < idx) then idx = key end | ||
| 		end | 		end | ||
| 		if  | 		if idx == nil then return context_iterate(ctx, 3) end | ||
| 		idx = idx - len | |||
| 		if last <  | 		if last < idx then return context_iterate(ctx, 3) end | ||
| 		len = last - idx + 1 | |||
| 	end | 	end | ||
| 	ctx.params = copy_table_reduced(ctx.params, idx, len) | |||
| 	return context_iterate(ctx, 3) | 	return context_iterate(ctx, 3) | ||
| end | end | ||
| --  | -- Syntax:  #invoke:params|rotating|pipe to | ||
| library.rotating = function(ctx) | library.rotating = function (ctx) | ||
| 	local tbl = ctx.params | 	local tbl = ctx.params | ||
| 	local  | 	local numerics = {} | ||
| 	local nmax = 0 | 	local nmax = 0 | ||
| 	for key, val in pairs(tbl) do | 	for key, val in pairs(tbl) do | ||
| 		if type(key) == 'number' then | 		if type(key) == 'number' then | ||
| 			numerics[key] = val | |||
| 			tbl[key] = nil | 			tbl[key] = nil | ||
| 			if key > nmax then nmax = key end | 			if key > nmax then nmax = key end | ||
| 		end | 		end | ||
| 	end | 	end | ||
| 	for key, val in pairs( | 	for key, val in pairs(numerics) do tbl[nmax - key + 1] = val end | ||
| 	return context_iterate(ctx, 1) | 	return context_iterate(ctx, 1) | ||
| end | end | ||
| --  | -- Syntax:  #invoke:params|pivoting|pipe to | ||
| --[[ | --[[ | ||
| library.pivoting = function(ctx) | library.pivoting = function (ctx) | ||
| 	local tbl = ctx.params | 	local tbl = ctx.params | ||
| 	local shift = #tbl + 1 | 	local shift = #tbl + 1 | ||
| 	if shift < 2 then return library.rotating(ctx) end | 	if shift < 2 then return library.rotating(ctx) end | ||
| 	local  | 	local numerics = {} | ||
| 	for key, val in pairs(tbl) do | 	for key, val in pairs(tbl) do | ||
| 		if type(key) == 'number' then | 		if type(key) == 'number' then | ||
| 			numerics[key] = val | |||
| 			tbl[key] = nil | 			tbl[key] = nil | ||
| 		end | 		end | ||
| 	end | 	end | ||
| 	for key, val in pairs( | 	for key, val in pairs(numerics) do tbl[shift - key] = val end | ||
| 	return context_iterate(ctx, 1) | 	return context_iterate(ctx, 1) | ||
| end | end | ||
| Line 543: | Line 1,009: | ||
| --  | -- Syntax:  #invoke:params|mirroring|pipe to | ||
| --[[ | --[[ | ||
| library.mirroring = function(ctx) | library.mirroring = function (ctx) | ||
| 	local tbl = ctx.params | 	local tbl = ctx.params | ||
| 	local  | 	local numerics = {} | ||
| 	local nmax | 	local nmax | ||
| 	local nmin | 	local nmin | ||
| 	for key, val in pairs(tbl) do | 	for key, val in pairs(tbl) do | ||
| 		if type(key) == 'number' then | 		if type(key) == 'number' then | ||
| 			numerics[key] = val | |||
| 			tbl[key] = nil | 			tbl[key] = nil | ||
| 			if nmax == nil then | 			if nmax == nil then | ||
| Line 558: | Line 1,024: | ||
| 				nmin = key | 				nmin = key | ||
| 			elseif key > nmax then nmax = key | 			elseif key > nmax then nmax = key | ||
| 			elseif key < nmin then nmin = key | 			elseif key < nmin then nmin = key end | ||
| 			end | |||
| 		end | 		end | ||
| 	end | 	end | ||
| 	for key, val in pairs( | 	for key, val in pairs(numerics) do tbl[nmax + nmin - key] = val end | ||
| 	return context_iterate(ctx, 1) | 	return context_iterate(ctx, 1) | ||
| end | end | ||
| Line 568: | Line 1,033: | ||
| --  | -- Syntax:  #invoke:params|swapping|pipe to | ||
| --[[ | --[[ | ||
| library.swapping = function(ctx) | library.swapping = function (ctx) | ||
| 	local tbl = ctx.params | 	local tbl = ctx.params | ||
| 	local cache = {} | 	local cache = {} | ||
| Line 592: | Line 1,057: | ||
| --  | -- Syntax:  #invoke:params|sorting_sequential_values|[criterion]|pipe to | ||
| library.sorting_sequential_values = function(ctx) | library.sorting_sequential_values = function (ctx) | ||
| 	local sortfn | 	local sortfn | ||
| 	if ctx.pipe[1] ~= nil then sortfn = sortfunctions[ctx.pipe[1]] end | 	if ctx.pipe[1] ~= nil then sortfn = sortfunctions[ctx.pipe[1]] end | ||
| 	if sortfn then | 	if sortfn then table.sort(ctx.params, sortfn) | ||
| 	else table.sort(ctx.params) end -- i.e. either `false` or `nil` | |||
| 	else | |||
| 		table.sort(ctx.params) | |||
| 	end | |||
| 	if sortfn == nil then return context_iterate(ctx, 1) end | 	if sortfn == nil then return context_iterate(ctx, 1) end | ||
| 	return context_iterate(ctx, 2) | 	return context_iterate(ctx, 2) | ||
| Line 606: | Line 1,068: | ||
| -- Syntax:  #invoke:params|inserting|position|how many|...|pipe to | |||
| -- See iface.with_name_matching() | |||
| --[[ | |||
| library.with_name_matching = function(ctx) | |||
| 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(modulename .. | |||
| 		', ‘inserting’: Position must be a number', 0) end | |||
| 	local len = tonumber(ctx.pipe[2]) | |||
| 	if len == nil or len < 1 then error(modulename .. | |||
| 		', ‘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(modulename .. | |||
| 		', ‘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(modulename .. | |||
| 		', ‘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(modulename .. | |||
| 		', ‘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(modulename .. | |||
| 		', ‘discarding’: A range was provided, but the initial parameter name is not numeric', 0) end | |||
| 	if len < 1 then error(modulename .. | |||
| 		', ‘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|excluding_non-numeric_names|pipe to | |||
| library['excluding_non-numeric_names'] = function (ctx) | |||
| 	local tmp = ctx.params | |||
| 	for key, val in pairs(tmp) do | |||
| 		if type(key) ~= 'number' then tmp[key] = nil end | |||
| 	end | |||
| 	return context_iterate(ctx, 1) | |||
| end | |||
| -- Syntax:  #invoke:params|excluding_numeric_names|pipe to | |||
| library.excluding_numeric_names = function (ctx) | |||
| 	local tmp = ctx.params | |||
| 	for key, val in pairs(tmp) do | |||
| 		if type(key) == 'number' then tmp[key] = nil end | |||
| 	end | |||
| 	return context_iterate(ctx, 1) | |||
| 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) | |||
| 	-- NOTE: `ctx.params` might be the original metatable! As a modifier, | |||
| 	-- this function MUST create a copy of it before returning | |||
| 	local targets, nptns, argc = load_pattern_args(ctx.pipe, targets, | |||
| 		'with_name_matching') | |||
| 	local tmp | |||
| 	local ptn | |||
| 	local tbl = ctx.params | 	local tbl = ctx.params | ||
| 	local  | 	local newparams = {} | ||
| 	for idx = 1, nptns do | |||
| 	local argc = parse_match_args(ctx.pipe, patterns, 'with_name_matching') | |||
| 		ptn = targets[idx] | |||
| 	local nomatch | |||
| 		if ptn[3] then | |||
| 	for key in pairs(tbl) do | |||
| 			tmp = tonumber(ptn[1]) or ptn[1] | |||
| 		nomatch = true | |||
| 			newparams[tmp] = tbl[tmp] | |||
| 		for _, ptn in ipairs(patterns) do | |||
| 		else | |||
| 			if not ptn[3] then | |||
| 			for key, val in pairs(tbl) do | |||
| 				if tostring(key):find(ptn[1], 1, ptn[2]) then | |||
| 					nomatch = false | |||
| 					newparams[key] = val | |||
| 					break | |||
| 				end | 				end | ||
| 			elseif key == ptn[1] then | |||
| 				nomatch = false | |||
| 				break | |||
| 			end | 			end | ||
| 		end | 		end | ||
| 		if nomatch then tbl[key] = nil end | |||
| 	end | 	end | ||
| 	ctx.params = newparams | |||
| 	return context_iterate(ctx, argc) | 	return context_iterate(ctx, argc) | ||
| end | end | ||
| --  | -- Syntax:  #invoke:params|with_name_not_matching|target 1|[plain flag 1] | ||
| --            |[and]|[target 2]|[plain flag 2]|[and]|[...]|[target N]|[plain | |||
| library.with_name_not_matching = function(ctx) | |||
| --            flag N]|pipe to | |||
| 	local tbl = ctx.params | |||
| library.with_name_not_matching = function (ctx) | |||
| 	local patterns = {} | |||
| 	local argc =  | 	local targets, nptns, argc = load_pattern_args(ctx.pipe, targets, | ||
| 		'with_name_not_matching') | 		'with_name_not_matching') | ||
| 	local tbl = ctx.params | |||
| 	if nptns == 1 and targets[1][3] then | |||
| 		local tmp = targets[1][1] | |||
| 		tbl[tonumber(tmp) or tmp] = nil | |||
| 		return context_iterate(ctx, argc) | |||
| 	end | |||
| 	local yesmatch | 	local yesmatch | ||
| 	local ptn | |||
| 	for key in pairs(tbl) do | 	for key in pairs(tbl) do | ||
| 		yesmatch = true | 		yesmatch = true | ||
| 		for  | 		for idx = 1, nptns do | ||
| 			ptn = targets[idx] | |||
| 			if ptn[3] then | |||
| 				if tostring(key) ~= ptn[1] then | |||
| 					yesmatch = false | 					yesmatch = false | ||
| 					break | 					break | ||
| 				end | 				end | ||
| 			elseif key  | 			elseif not tostring(key):find(ptn[1], 1, ptn[2]) then | ||
| 				yesmatch = false | 				yesmatch = false | ||
| 				break | 				break | ||
| Line 657: | Line 1,213: | ||
| --  | -- Syntax:  #invoke:params|with_value_matching|target 1|[plain flag 1]|[or] | ||
| --            |[target 2]|[plain flag 2]|[or]|[...]|[target N]|[plain flag | |||
| library.with_value_matching = function(ctx) | |||
| --            N]|pipe to | |||
| library.with_value_matching = function (ctx) | |||
| 	local tbl = ctx.params | 	local tbl = ctx.params | ||
| 	local targets, nptns, argc = load_pattern_args(ctx.pipe, targets, | |||
| 	local patterns = {} | |||
| 	local argc = parse_match_args(ctx.pipe, patterns, | |||
| 		'with_value_matching') | 		'with_value_matching') | ||
| 	local nomatch | 	local nomatch | ||
| 	local ptn | |||
| 	for key, val in pairs(tbl) do | 	for key, val in pairs(tbl) do | ||
| 		nomatch = true | 		nomatch = true | ||
| 		for  | 		for idx = 1, nptns do | ||
| 			ptn = targets[idx] | |||
| 			if ptn[3] then | |||
| 				if val == ptn[1] then | |||
| 					nomatch = false | 					nomatch = false | ||
| 					break | 					break | ||
| 				end | 				end | ||
| 			elseif val  | 			elseif val:find(ptn[1], 1, ptn[2]) then | ||
| 				nomatch = false | 				nomatch = false | ||
| 				break | 				break | ||
| Line 683: | Line 1,242: | ||
| --  | -- Syntax:  #invoke:params|with_value_not_matching|target 1|[plain flag 1] | ||
| --            |[and]|[target 2]|[plain flag 2]|[and]|[...]|[target N]|[plain | |||
| library.with_value_not_matching = function(ctx) | |||
| --            flag N]|pipe to | |||
| library.with_value_not_matching = function (ctx) | |||
| 	local tbl = ctx.params | 	local tbl = ctx.params | ||
| 	local targets, nptns, argc = load_pattern_args(ctx.pipe, targets, | |||
| 	local patterns = {} | |||
| 	local argc = parse_match_args(ctx.pipe, patterns, | |||
| 		'with_value_not_matching') | 		'with_value_not_matching') | ||
| 	local yesmatch | 	local yesmatch | ||
| 	local ptn | |||
| 	for key, val in pairs(tbl) do | 	for key, val in pairs(tbl) do | ||
| 		yesmatch = true | 		yesmatch = true | ||
| 		for  | 		for idx = 1, nptns do | ||
| 			ptn = targets[idx] | |||
| 			if ptn[3] then | |||
| 				if val ~= ptn[1] then | |||
| 					yesmatch = false | 					yesmatch = false | ||
| 					break | 					break | ||
| 				end | 				end | ||
| 			elseif val  | 			elseif not val:find(ptn[1], 1, ptn[2]) then | ||
| 				yesmatch = false | 				yesmatch = false | ||
| 				break | 				break | ||
| Line 709: | Line 1,271: | ||
| --  | -- Syntax:  #invoke:params|trimming_values|pipe to | ||
| library.trimming_values = function(ctx) | library.trimming_values = function (ctx) | ||
| 	local tbl = ctx.params | 	local tbl = ctx.params | ||
| 	for key, val in pairs(tbl) do tbl[key] = val:match'^%s*(.-)%s*$' end | 	for key, val in pairs(tbl) do tbl[key] = val:match'^%s*(.-)%s*$' end | ||
| Line 717: | Line 1,279: | ||
| --  | -- Syntax:  #invoke:params|mapping_by_calling|template name|[call | ||
| --            style]|[let]|[...][number of additional parameters]|[parameter | |||
| library.mapping_by_calling = function(ctx) | |||
| --            1]|[parameter 2]|[...]|[parameter N]|pipe to | |||
| library.mapping_by_calling = function (ctx) | |||
| 	local opts = ctx.pipe | 	local opts = ctx.pipe | ||
| 	local tname | 	local tname | ||
| 	if opts[1] ~= nil then tname = opts[1]:match'^%s*(.*%S)' end | 	if opts[1] ~= nil then tname = opts[1]:match'^%s*(.*%S)' end | ||
| 	if tname == nil then error( | 	if tname == nil then error(modulename .. | ||
| 		', ‘mapping_by_calling’: No template name was provided', 0) end | |||
| 	local nargs | |||
| 	local margs =  | 	local margs, argc, looptype, karg, varg = load_callback_opts(opts, 1, | ||
| 		mapping_styles.values_only) | |||
| 	local tmp = tonumber(opts[2]) | |||
| 	if tmp == nil then | |||
| 		nargs = 1 | |||
| 	elseif tmp < 1 then | |||
| 		nargs = 2 | |||
| 	else | |||
| 		nargs = tmp + 2 | |||
| 		for idx = 3, nargs do margs[idx] = opts[idx] end | |||
| 	end | |||
| 	local model = { title = tname, args = margs } | 	local model = { title = tname, args = margs } | ||
| 	value_maps[looptype](ctx.params, margs, karg, varg, function () | |||
| 	local tbl = ctx.params | |||
| 		return ctx.frame:expandTemplate(model) | |||
| 	for key, val in pairs(tbl) do | |||
| 	end) | |||
| 		margs[1] = key | |||
| 	return context_iterate(ctx, argc) | |||
| 		margs[2] = val | |||
| 		tbl[key] = ctx.frame:expandTemplate(model) | |||
| 	end | |||
| 	return context_iterate(ctx, nargs + 1) | |||
| end | end | ||
| --  | -- Syntax:  #invoke:params|mapping_by_invoking|module name|function | ||
| --            name|[call style]|[let]|[...]|[number of additional | |||
| library.mapping_by_invoking = function(ctx) | |||
| --            arguments]|[argument 1]|[argument 2]|[...]|[argument N]|pipe to | |||
| library.mapping_by_invoking = function (ctx) | |||
| 	local opts = ctx.pipe | 	local opts = ctx.pipe | ||
| 	local mname | 	local mname | ||
| 	local fname | 	local fname | ||
| 	if opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end | 	if opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end | ||
| 	if mname == nil then error( | 	if mname == nil then error(modulename .. | ||
| 		', ‘mapping_by_invoking’: No module name was provided', 0) end | |||
| 	if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end | 	if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end | ||
| 	if fname == nil then error( | 	if fname == nil then error(modulename .. | ||
| 		', ‘mapping_by_invoking’: No function name was provided', 0) end | |||
| 	local nargs | |||
| 	local margs =  | 	local margs, argc, looptype, karg, varg = load_callback_opts(opts, 2, | ||
| 		mapping_styles.values_only) | |||
| 	local tmp = tonumber(opts[3]) | |||
| 	if tmp == nil then | |||
| 		nargs = 2 | |||
| 	elseif tmp < 1 then | |||
| 		nargs = 3 | |||
| 	else | |||
| 		nargs = tmp + 3 | |||
| 		for idx = 4, nargs do margs[idx - 1] = opts[idx] end | |||
| 	end | |||
| 	local model = { title = 'Module:' .. mname, args = margs } | 	local model = { title = 'Module:' .. mname, args = margs } | ||
| 	local mfunc = require(model.title)[fname] | 	local mfunc = require(model.title)[fname] | ||
| 	if mfunc == nil then error(modulename .. | |||
| 		', ‘mapping_by_invoking’: The function ‘' .. fname .. | |||
| 		'’ does not exist', 0) end | |||
| 	value_maps[looptype](ctx.params, margs, karg, varg, function () | |||
| 		return tostring(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(modulename .. | |||
| 		', ‘mapping_by_magic’: No parser function was provided', 0) end | |||
| 	local margs, argc, looptype, karg, varg = load_callback_opts(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 = | |||
| 		load_replace_args(ctx.pipe, 'mapping_by_replacing') | |||
| 	if die then return context_iterate(ctx, argc) end | |||
| 	local tbl = ctx.params | 	local tbl = ctx.params | ||
| 	if is_strict then | |||
| 	for key, val in pairs(tbl) do | |||
| 		for key, val in pairs(tbl) do | |||
| 		margs[1] = key | |||
| 			if val == ptn then tbl[key] = repl end | |||
| 		margs[2] = val | |||
| 		end | |||
| 		tbl[key] = mfunc(ctx.frame:newChild(model)) | |||
| 	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 | 	end | ||
| 	return context_iterate(ctx,  | 	return context_iterate(ctx, argc) | ||
| end | end | ||
| -- Syntax:  #invoke:params|renaming_by_calling|template name|[call | |||
| -- See iface.mapping_blindly_by_calling() | |||
| --            style]|[let]|[...][number of additional parameters]|[parameter | |||
| library.mapping_blindly_by_calling = function(ctx) | |||
| --            1]|[parameter 2]|[...]|[parameter N]|pipe to | |||
| library.renaming_by_calling = function (ctx) | |||
| 	local opts = ctx.pipe | 	local opts = ctx.pipe | ||
| 	local tname | 	local tname | ||
| 	if opts[1] ~= nil then tname = opts[1]:match'^%s*(.*%S)' end | 	if opts[1] ~= nil then tname = opts[1]:match'^%s*(.*%S)' end | ||
| 	if tname == nil then error( | 	if tname == nil then error(modulename .. | ||
| 		', ‘renaming_by_calling’: No template name was provided', 0) end | |||
| 	local nargs | |||
| 	local rargs, argc, looptype, karg, varg = load_callback_opts(opts, 1, | |||
| 	local margs = {} | |||
| 		mapping_styles.names_only) | |||
| 	local tmp = tonumber(opts[2]) | |||
| 	local model = { title = tname, args = rargs } | |||
| 	if tmp == nil then | |||
| 	map_names(ctx.params, rargs, karg, varg, looptype, function () | |||
| 		nargs = 1 | |||
| 		return ctx.frame:expandTemplate(model) | |||
| 	elseif tmp < 1 then | |||
| 	end) | |||
| 		nargs = 2 | |||
| 	return context_iterate(ctx, argc) | |||
| 	else | |||
| 		nargs = tmp + 2 | |||
| 		for idx = 3, nargs do margs[idx - 1] = opts[idx] end | |||
| 	end | |||
| 	local model = { title = tname, args = margs } | |||
| 	local tbl = ctx.params | |||
| 	for key, val in pairs(tbl) do | |||
| 		margs[1] = val | |||
| 		tbl[key] = ctx.frame:expandTemplate(model) | |||
| 	end | |||
| 	return context_iterate(ctx, nargs + 1) | |||
| end | end | ||
| -- Syntax:  #invoke:params|renaming_by_invoking|module name|function | |||
| -- See iface.mapping_blindly_by_invoking() | |||
| --            name|[call style]|[let]|[...]|[number of additional | |||
| library.mapping_blindly_by_invoking = function(ctx) | |||
| --            arguments]|[argument 1]|[argument 2]|[...]|[argument N]|pipe to | |||
| library.renaming_by_invoking = function (ctx) | |||
| 	local opts = ctx.pipe | 	local opts = ctx.pipe | ||
| 	local mname | 	local mname | ||
| 	local fname | 	local fname | ||
| 	if opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end | 	if opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end | ||
| 	if mname == nil then error( | 	if mname == nil then error(modulename .. | ||
| 		', ‘renaming_by_invoking’: No module name was provided', 0) end | |||
| 	if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end | 	if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end | ||
| 	if fname == nil then error( | 	if fname == nil then error(modulename .. | ||
| 		', ‘renaming_by_invoking’: No function name was provided', 0) end | |||
| 	local nargs | |||
| 	local rargs, argc, looptype, karg, varg = load_callback_opts(opts, 2, | |||
| 	local margs = {} | |||
| 		mapping_styles.names_only) | |||
| 	local tmp = tonumber(opts[3]) | |||
| 	local model = { title = 'Module:' .. mname, args = rargs } | |||
| 	if tmp == nil then | |||
| 		nargs = 2 | |||
| 	elseif tmp < 1 then | |||
| 		nargs = 3 | |||
| 	else | |||
| 		nargs = tmp + 3 | |||
| 		for idx = 4, nargs do margs[idx - 2] = opts[idx] end | |||
| 	end | |||
| 	local model = { title = 'Module:' .. mname, args = margs } | |||
| 	local mfunc = require(model.title)[fname] | 	local mfunc = require(model.title)[fname] | ||
| 	if mfunc == nil then error(modulename .. | |||
| 		', ‘renaming_by_invoking’: The function ‘' .. fname .. | |||
| 		'’ does not exist', 0) end | |||
| 	map_names(ctx.params, rargs, karg, varg, looptype, function () | |||
| 		local tmp = mfunc(ctx.frame:newChild(model)) | |||
| 		return tonumber(tmp) or tostring(tmp) | |||
| 	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(modulename .. | |||
| 		', ‘renaming_by_magic’: No parser function was provided', 0) end | |||
| 	local rargs, argc, looptype, karg, varg = load_callback_opts(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 = | |||
| 		load_replace_args(ctx.pipe, 'renaming_by_replacing') | |||
| 	if die then return context_iterate(ctx, argc) end | |||
| 	local tbl = ctx.params | 	local tbl = ctx.params | ||
| 	if is_strict then | |||
| 	for key, val in pairs(tbl) do | |||
| 		local key = tonumber(ptn) or ptn:match'^%s*(.-)%s*$' | |||
| 		margs[1] = val | |||
| 		local val = tbl[key] | |||
| 		tbl[key] = mfunc(ctx.frame:newChild(model)) | |||
| 		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 | 	end | ||
| 	return context_iterate(ctx,  | 	return context_iterate(ctx, argc) | ||
| end | end | ||
| -- Syntax:  #invoke:params|grouping_by_calling|template | |||
| -- See iface.renaming_blindly_by_calling() | |||
| --            name|[let]|[...]|[number of additional arguments]|[argument | |||
| library.renaming_blindly_by_calling = function(ctx) | |||
| --            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 opts = ctx.pipe | ||
| 	local  | 	local tmp | ||
| 	if opts[1] ~= nil then  | 	if opts[1] ~= nil then tmp = opts[1]:match'^%s*(.*%S)' end | ||
| 	if  | 	if tmp == nil then error(modulename .. | ||
| 		', ‘grouping_by_calling’: No template name was provided', 0) end | |||
| 	local nargs | |||
| 	local  | 	local model = { title = tmp } | ||
| 	local tmp =  | 	local tmp, argc = load_child_opts(opts, 2, 0) | ||
| 	local gargs = {} | |||
| 	for key, val in pairs(tmp) do | |||
| 		nargs = 1 | |||
| 		if type(key) == 'number' and key < 1 then gargs[key - 1] = val | |||
| 	elseif tmp < 1 then | |||
| 		else gargs[key] = val end | |||
| 		nargs = 2 | |||
| 	else | |||
| 		nargs = tmp + 2 | |||
| 		for idx = 3, nargs do margs[idx - 1] = opts[idx] end | |||
| 	end | 	end | ||
| 	local  | 	local groups = make_groups(ctx.params) | ||
| 	for gid, group in pairs(groups) do | |||
| 	local tbl = {} | |||
| 		for key, val in pairs(gargs) do group[key] = val end | |||
| 		group[0] = gid | |||
| 		model.args = group | |||
| 		tmp = ctx.frame:expandTemplate(model):match'^%s*(.-)%s*$' | |||
| 		groups[gid] = ctx.frame:expandTemplate(model) | |||
| 		tbl[tonumber(tmp) or tmp] = val | |||
| 	end | 	end | ||
| 	ctx.params =  | 	ctx.params = groups | ||
| 	return context_iterate(ctx,  | 	return context_iterate(ctx, argc) | ||
| end | end | ||
| -- Syntax:  #invoke:params|parsing|string to parse|[trim flag]|[iteration | |||
| -- See iface.renaming_blindly_by_invoking() | |||
| --            delimiter setter]|[...]|[key-value delimiter setter]|[...]|pipe to | |||
| library.renaming_blindly_by_invoking = function(ctx) | |||
| library.parsing = function (ctx) | |||
| 	local opts = ctx.pipe | 	local opts = ctx.pipe | ||
| 	if opts[1] == nil then error(modulename .. | |||
| 	local mname | |||
| 		', ‘parsing’: No string to parse was provided', 0) end | |||
| 	local fname | |||
| 	local isep, iplain, psep, pplain, trimnamed, trimunnamed, argc = | |||
| 	if opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end | |||
| 		load_parse_opts(opts, 2) | |||
| 	if mname == nil then error(ctx.luaname .. ', ‘renaming_blindly_by_invoking’: No module name was provided', 0) end | |||
| 	parse_parameter_string(ctx.params, opts[1], isep, iplain, psep, pplain, | |||
| 	if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end | |||
| 		trimnamed, trimunnamed) | |||
| 	if fname == nil then error(ctx.luaname .. ', ‘renaming_blindly_by_invoking’: No function name was provided', 0) end | |||
| 	return context_iterate(ctx, argc) | |||
| 	local nargs | |||
| end | |||
| 	local margs = {} | |||
| 	local tmp = tonumber(opts[3]) | |||
| 	if tmp == nil then | |||
| -- Syntax:  #invoke:params|reinterpreting|parameter to reinterpret|[trim | |||
| 		nargs = 2 | |||
| --            flag]|[iteration delimiter setter]|[...]|[key-value delimiter | |||
| 	elseif tmp < 1 then | |||
| --            setter]|[...]|pipe to | |||
| 		nargs = 3 | |||
| library.reinterpreting = function (ctx) | |||
| 	else | |||
| 	local opts = ctx.pipe | |||
| 		nargs = tmp + 3 | |||
| 	if opts[1] == nil then error(modulename .. | |||
| 		for idx = 4, nargs do margs[idx - 2] = opts[idx] end | |||
| 		', ‘reinterpreting’: No parameter to reinterpret was provided', 0) end | |||
| 	local isep, iplain, psep, pplain, trimnamed, trimunnamed, argc = | |||
| 		load_parse_opts(opts, 2) | |||
| 	local tbl = ctx.params | |||
| 	local tmp = tonumber(opts[1]) or opts[1]:match'^%s*(.-)%s*$' | |||
| 	local str = tbl[tmp] | |||
| 	if str ~= nil then | |||
| 		tbl[tmp] = nil | |||
| 		parse_parameter_string(tbl, str, isep, iplain, psep, pplain, | |||
| 			trimnamed, trimunnamed) | |||
| 	end | 	end | ||
| 	return context_iterate(ctx, argc) | |||
| 	local model = { title = 'Module:' .. mname, args = margs } | |||
| end | |||
| 	local mfunc = require(model.title)[fname] | |||
| 	local tbl = {} | |||
| 	for key, val in pairs(ctx.params) do | |||
| -- Syntax:  #invoke:params|combining_by_calling|template name|new parameter | |||
| 		margs[1] = key | |||
| --            name|pipe to | |||
| 		tmp = mfunc(ctx.frame:newChild(model)):match'^%s*(.-)%s*$' | |||
| library.combining_by_calling = function (ctx) | |||
| 		tbl[tonumber(tmp) or tmp] = val | |||
| 	-- NOTE: `ctx.params` might be the original metatable! As a modifier, | |||
| 	-- this function MUST create a copy of it before returning | |||
| 	local tname = ctx.pipe[1] | |||
| 	if tname ~= nil then tname = tname:match'^%s*(.*%S)' | |||
| 	else error(modulename .. | |||
| 		', ‘combining_by_calling’: No template name was provided', 0) end | |||
| 	local merge_into = ctx.pipe[2] | |||
| 	if merge_into == nil then error(modulename .. | |||
| 		', ‘combining_by_calling’: No parameter name was provided', 0) end | |||
| 	merge_into = tonumber(merge_into) or merge_into:match'^%s*(.-)%s*$' | |||
| 	ctx.params = { | |||
| 		[merge_into] = ctx.frame:expandTemplate{ | |||
| 			title = tname, | |||
| 			args = ctx.params | |||
| 		} | |||
| 	} | |||
| 	return context_iterate(ctx, 3) | |||
| end | |||
| -- Syntax:  #invoke:params|snapshotting|pipe to | |||
| library.snapshotting = function (ctx) | |||
| 	push_cloned_stack(ctx, ctx.params) | |||
| 	return context_iterate(ctx, 1) | |||
| end | |||
| -- Syntax:  #invoke:params|remembering|pipe to | |||
| library.remembering = function (ctx) | |||
| 	push_cloned_stack(ctx, ctx.oparams) | |||
| 	return context_iterate(ctx, 1) | |||
| end | |||
| -- Syntax:  #invoke:params|entering_substack|[new]|pipe to | |||
| library.entering_substack = function (ctx) | |||
| 	local tbl = ctx.params | |||
| 	local ncurrparent = ctx.n_parents + 1 | |||
| 	if ctx.parents == nil then ctx.parents = { tbl } | |||
| 	else ctx.parents[ncurrparent] = tbl end | |||
| 	ctx.n_parents = ncurrparent | |||
| 	if ctx.pipe[1] ~= nil and ctx.pipe[1]:match'^%s*new%s*$' then | |||
| 		ctx.params = {} | |||
| 		return context_iterate(ctx, 2) | |||
| 	end | 	end | ||
| 	local currsnap = ctx.n_children | |||
| 	ctx.params = tbl | |||
| 	if currsnap > 0 then | |||
| 	return context_iterate(ctx, nargs + 1) | |||
| 		ctx.params = ctx.children[currsnap] | |||
| 		ctx.children[currsnap] = nil | |||
| 		ctx.n_children = currsnap - 1 | |||
| 	else | |||
| 		local newparams = {} | |||
| 		for key, val in pairs(tbl) do newparams[key] = val end | |||
| 		ctx.params = newparams | |||
| 	end | |||
| 	return context_iterate(ctx, 1) | |||
| end | end | ||
| -- Syntax:  #invoke:params|pulling|parameter name|pipe to | |||
| library.pulling = function (ctx) | |||
| 	local opts = ctx.pipe | |||
| 	if opts[1] == nil then error(modulename .. | |||
| 		', ‘pulling’: No parameter to pull was provided', 0) end | |||
| 	local parent | |||
| 	local tmp = ctx.n_parents | |||
| 	if tmp < 1 then parent = ctx.oparams else parent = ctx.parents[tmp] end | |||
| 	tmp = tonumber(opts[1]) or opts[1]:match'^%s*(.-)%s*$' | |||
| 	if parent[tmp] ~= nil then ctx.params[tmp] = parent[tmp] end | |||
| 	return context_iterate(ctx, 2) | |||
| end | |||
| 	--[[ Library's functions ]]-- | |||
| -- Syntax:  #invoke:params|detaching_substack|pipe to | |||
| library.detaching_substack = function (ctx) | |||
| 	local ncurrparent = ctx.n_parents | |||
| 	if ncurrparent < 1 then error(modulename .. | |||
| 		', ‘detaching_substack’: No substack has been created', 0) end | |||
| 	local parent = ctx.parents[ncurrparent] | |||
| 	for key in pairs(ctx.params) do parent[key] = nil end | |||
| 	return context_iterate(ctx, 1) | |||
| end | |||
| -- Syntax:  #invoke:params|leaving_substack|pipe to | |||
| library.leaving_substack = function (ctx) | |||
| 	local ncurrparent = ctx.n_parents | |||
| 	if ncurrparent < 1 then error(modulename .. | |||
| 		', ‘leaving_substack’: No substack has been created', 0) end | |||
| 	local currsnap = ctx.n_children + 1 | |||
| 	if ctx.children == nil then ctx.children = { ctx.params } | |||
| 	else ctx.children[currsnap] = ctx.params end | |||
| 	ctx.params = ctx.parents[ncurrparent] | |||
| 	ctx.parents[ncurrparent] = nil | |||
| 	ctx.n_parents = ncurrparent - 1 | |||
| 	ctx.n_children = currsnap | |||
| 	return context_iterate(ctx, 1) | |||
| end | |||
| -- Syntax:  #invoke:params|merging_substack|pipe to | |||
| library.merging_substack = function (ctx) | |||
| 	local ncurrparent = ctx.n_parents | |||
| 	if ncurrparent < 1 then error(modulename .. | |||
| 		', ‘merging_substack’: No substack has been created', 0) end | |||
| 	local parent = ctx.parents[ncurrparent] | |||
| 	local child = ctx.params | |||
| 	ctx.params = parent | |||
| 	ctx.parents[ncurrparent] = nil | |||
| 	ctx.n_parents = ncurrparent - 1 | |||
| 	for key, val in pairs(child) do parent[key] = val end | |||
| 	return context_iterate(ctx, 1) | |||
| end | |||
| -- Syntax:  #invoke:params|flushing|pipe to | |||
| library.flushing = function (ctx) | |||
| 	if ctx.n_children < 1 then error(modulename .. | |||
| 		', ‘flushing’: There are no substacks to flush', 0) end | |||
| 	local parent = ctx.params | |||
| 	local currsnap = ctx.n_children | |||
| 	for key, val in pairs(ctx.children[currsnap]) do parent[key] = val end | |||
| 	ctx.children[currsnap] = nil | |||
| 	ctx.n_children = currsnap - 1 | |||
| 	return context_iterate(ctx, 1) | |||
| end | |||
| 	--[[ Functions ]]-- | |||
| 	----------------------------- | 	----------------------------- | ||
| --  | -- Syntax:  #invoke:params|count | ||
| library.count = function(ctx) | library.count = function (ctx) | ||
| 	-- NOTE: `ctx.pipe` and `ctx.params` might be the original metatables! | |||
| 	local count = 0 | |||
| 	local retval = 0 | |||
| 	for _ in ctx.iterfunc(ctx.params) do count = count + 1 end | |||
| 	for _ in ctx.iterfunc(ctx.params) do retval = retval + 1 end | |||
| 	if ctx.subset == -1 then retval = retval - #ctx.params end | |||
| 	return count | |||
| 	ctx.text = retval | |||
| 	return false | |||
| end | end | ||
| --  | -- Syntax:  #invoke:args|concat_and_call|template name|[prepend 1]|[prepend 2] | ||
| --            |[...]|[item n]|[named item 1=value 1]|[...]|[named item n=value | |||
| library.concat_and_call = function(ctx) | |||
| --            n]|[...] | |||
| library.concat_and_call = function (ctx) | |||
| 	-- NOTE: `ctx.params` might be the original metatable! | |||
| 	local opts = ctx.pipe | 	local opts = ctx.pipe | ||
| 	local tname | 	local tname | ||
| 	if opts[1] ~= nil then tname = opts[1]:match'^%s*(.*%S)' end | 	if opts[1] ~= nil then tname = opts[1]:match'^%s*(.*%S)' end | ||
| 	if tname == nil then error( | 	if tname == nil then error(modulename .. | ||
| 		', ‘concat_and_call’: No template name was provided', 0) end | |||
| 	table.remove(opts, 1) | |||
| 	remove_numeric_keys(opts, 1, 1) | |||
| 	return ctx.frame:expandTemplate{ | |||
| 	ctx.text = ctx.frame:expandTemplate{ | |||
| 		title = tname, | 		title = tname, | ||
| 		args = concat_params(ctx) | 		args = concat_params(ctx) | ||
| 	} | 	} | ||
| 	return false | |||
| end | end | ||
| --  | -- Syntax:  #invoke:args|concat_and_invoke|module name|function name|[prepend | ||
| --            1]|[prepend 2]|[...]|[item n]|[named item 1=value 1]|[...]|[named | |||
| library.concat_and_invoke = function(ctx) | |||
| --            item n=value n]|[...] | |||
| library.concat_and_invoke = function (ctx) | |||
| 	-- NOTE: `ctx.params` might be the original metatable! | |||
| 	local opts = ctx.pipe | 	local opts = ctx.pipe | ||
| 	local mname | 	local mname | ||
| 	local fname | 	local fname | ||
| 	if opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end | 	if opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end | ||
| 	if mname == nil then error( | 	if mname == nil then error(modulename .. | ||
| 		', ‘concat_and_invoke’: No module name was provided', 0) end | |||
| 	if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end | 	if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end | ||
| 	if fname == nil then error( | 	if fname == nil then error(modulename .. | ||
| 		', ‘concat_and_invoke’: No function name was provided', 0) end | |||
| 	table.remove(opts, 2) | |||
| 	remove_numeric_keys(opts, 1, 2) | |||
| 	local mfunc = require('Module:' .. mname)[fname] | |||
| 	if mfunc == nil then error(modulename .. | |||
| 		', ‘concat_and_invoke’: The function ‘' .. fname .. | |||
| 		'’ does not exist', 0) end | |||
| 	ctx.text = mfunc(ctx.frame:newChild{ | |||
| 		title = 'Module:' .. fname, | 		title = 'Module:' .. fname, | ||
| 		args = concat_params(ctx) | 		args = concat_params(ctx) | ||
| 	}) | 	}) | ||
| 	return false | |||
| end | end | ||
| --  | -- Syntax:  #invoke:args|concat_and_magic|parser function|[prepend 1]|[prepend | ||
| --            2]|[...]|[item n]|[named item 1=value 1]|[...]|[named item n= | |||
| library.concat_and_magic = function(ctx) | |||
| --            value n]|[...] | |||
| library.concat_and_magic = function (ctx) | |||
| 	-- NOTE: `ctx.params` might be the original metatable! | |||
| 	local opts = ctx.pipe | 	local opts = ctx.pipe | ||
| 	local magic | 	local magic | ||
| 	if opts[1] ~= nil then magic = opts[1]:match'^%s*(.*%S)' end | 	if opts[1] ~= nil then magic = opts[1]:match'^%s*(.*%S)' end | ||
| 	if magic == nil then error( | 	if magic == nil then error(modulename .. | ||
| 		', ‘concat_and_magic’: No parser function was provided', 0) end | |||
| 	table.remove(opts, 1) | |||
| 	remove_numeric_keys(opts, 1, 1) | |||
| 	return ctx.frame:callParserFunction(magic, concat_params(ctx)) | |||
| 	ctx.text = ctx.frame:callParserFunction(magic, concat_params(ctx)) | |||
| 	return false | |||
| end | end | ||
| --  | -- Syntax:  #invoke:params|value_of|parameter name | ||
| library.value_of = function(ctx) | library.value_of = function (ctx) | ||
| 	-- NOTE: `ctx.pipe` and `ctx.params` might be the original metatables! | |||
| 	local opts = ctx.pipe | 	local opts = ctx.pipe | ||
| 	local  | 	local kstr | ||
| 	if opts[1] ~= nil then  | 	if opts[1] ~= nil then kstr = opts[1]:match'^%s*(.*%S)' end | ||
| 	if  | 	if kstr == nil then error(modulename .. | ||
| 		', ‘value_of’: No parameter name was provided', 0) end | |||
| 	local keynum = tonumber(keystr) | |||
| 	local  | 	local knum = tonumber(kstr) | ||
| 	local len = #ctx.params  -- No worries: unused when in first position | |||
| 	if ( | |||
| 	local val = ctx.params[knum or kstr] | |||
| 		ctx.subset == -1 and keynum ~= nil and len >= keynum | |||
| 	if val ~= nil and ( | |||
| 	) or ( | |||
| 		ctx.subset  | 		ctx.subset ~= -1 or knum == nil or knum > len or knum < 1 | ||
| 	) and ( | |||
| 	) then return (ctx.ifngiven or '') end | |||
| 		ctx.subset ~= 1 or (knum ~= nil and knum <= len and knum > 0) | |||
| 	local val = ctx.params[keynum or keystr] | |||
| 	) then | |||
| 	if val == nil then return (ctx.ifngiven or '') end | |||
| 		ctx.text = (ctx.header or '') .. val .. (ctx.footer or '') | |||
| 		return false | |||
| 	end | |||
| 	ctx.text = ctx.ifngiven or '' | |||
| 	return false | |||
| end | end | ||
| --  | -- Syntax:  #invoke:params|list | ||
| library.list = function(ctx) | library.list = function (ctx) | ||
| 	-- NOTE: `ctx.pipe` might be the original metatable! | |||
| 	local kvs = ctx.pairsep or '' | 	local kvs = ctx.pairsep or '' | ||
| 	local pps = ctx.itersep or '' | 	local pps = ctx.itersep or '' | ||
| Line 981: | Line 1,767: | ||
| 	flush_params( | 	flush_params( | ||
| 		ctx, | 		ctx, | ||
| 		function(key, val) | 		function (key, val) | ||
| 			ret[nss + 1] = pps | 			ret[nss + 1] = pps | ||
| 			ret[nss + 2] = key | 			ret[nss + 2] = key | ||
| Line 995: | Line 1,781: | ||
| 		ret[1] = ctx.header or '' | 		ret[1] = ctx.header or '' | ||
| 		if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end | 		if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end | ||
| 		ctx.text = table.concat(ret) | |||
| 		return false | |||
| 	end | 	end | ||
| 	ctx.text = ctx.ifngiven or '' | |||
| 	return false | |||
| end | end | ||
| --  | -- Syntax:  #invoke:params|list_values | ||
| library.list_values = function(ctx) | 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 pps = ctx.itersep or '' | ||
| 	local ret = {} | 	local ret = {} | ||
| Line 1,008: | Line 1,798: | ||
| 	flush_params( | 	flush_params( | ||
| 		ctx, | 		ctx, | ||
| 		function(key, val) | 		function (key, val) | ||
| 			ret[nss + 1] = pps | 			ret[nss + 1] = pps | ||
| 			ret[nss + 2] = val | 			ret[nss + 2] = val | ||
| Line 1,020: | Line 1,810: | ||
| 		ret[1] = ctx.header or '' | 		ret[1] = ctx.header or '' | ||
| 		if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end | 		if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end | ||
| 		ctx.text = table.concat(ret) | |||
| 		return false | |||
| 	end | 	end | ||
| 	ctx.text = ctx.ifngiven or '' | |||
| 	return false | |||
| end | end | ||
| -- Syntax:  #invoke:params|coins|[first coin = value 1]|[second coin = value | |||
| -- See iface.for_each() | |||
| --            2]|[...]|[last coin = value N] | |||
| library.for_each = function(ctx) | |||
| 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 tbl[key] = opts[tonumber(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 | |||
| 		tbl[key] = opts[tmp] | |||
| 		opts[tmp] = 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.pipe[1] or '' | 	local txt = ctx.pipe[1] or '' | ||
| 	local pps = ctx.itersep or '' | 	local pps = ctx.itersep or '' | ||
| Line 1,034: | Line 1,853: | ||
| 	flush_params( | 	flush_params( | ||
| 		ctx, | 		ctx, | ||
| 		function(key, val) | 		function (key, val) | ||
| 			ret[nss + 1] = pps | 			ret[nss + 1] = pps | ||
| 			ret[nss + 2] = txt:gsub('%$#', key):gsub('%$@', val) | 			ret[nss + 2] = txt:gsub('%$#', key):gsub('%$@', val) | ||
| Line 1,046: | Line 1,865: | ||
| 		ret[1] = ctx.header or '' | 		ret[1] = ctx.header or '' | ||
| 		if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end | 		if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end | ||
| 		ctx.text = table.concat(ret) | |||
| 		return false | |||
| 	end | 	end | ||
| 	ctx.text = ctx.ifngiven or '' | |||
| 	return false | |||
| end | end | ||
| --  | -- Syntax:  #invoke:params|call_for_each|template name|[append 1]|[append 2] | ||
| --            |[...]|[append n]|[named param 1=value 1]|[...]|[named param | |||
| library.call_for_each = function(ctx) | |||
| --            n=value n]|[...] | |||
| library.call_for_each = function (ctx) | |||
| 	local opts = ctx.pipe | 	local opts = ctx.pipe | ||
| 	local tname | 	local tname | ||
| 	if opts[1] ~= nil then tname = opts[1]:match'^%s*(.*%S)' end | 	if opts[1] ~= nil then tname = opts[1]:match'^%s*(.*%S)' end | ||
| 	if tname == nil then error( | 	if tname == nil then error(modulename .. | ||
| 		', ‘call_for_each’: No template name was provided', 0) end | |||
| 	local model = { title = tname, args = opts } | 	local model = { title = tname, args = opts } | ||
| 	local ccs = ctx.itersep or '' | 	local ccs = ctx.itersep or '' | ||
| Line 1,065: | Line 1,889: | ||
| 	flush_params( | 	flush_params( | ||
| 		ctx, | 		ctx, | ||
| 		function(key, val) | 		function (key, val) | ||
| 			opts[1] = key | 			opts[1] = key | ||
| 			opts[2] = val | 			opts[2] = val | ||
| Line 1,079: | Line 1,903: | ||
| 		ret[1] = ctx.header or '' | 		ret[1] = ctx.header or '' | ||
| 		if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end | 		if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end | ||
| 		ctx.text = table.concat(ret) | |||
| 		return false | |||
| 	end | 	end | ||
| 	ctx.text = ctx.ifngiven or '' | |||
| 	return false | |||
| end | end | ||
| --  | -- Syntax:  #invoke:params|invoke_for_each|module name|module function|[append | ||
| --            1]|[append 2]|[...]|[append n]|[named param 1=value 1]|[...] | |||
| library.invoke_for_each = function(ctx) | |||
| --            |[named param n=value n]|[...] | |||
| library.invoke_for_each = function (ctx) | |||
| 	local opts = ctx.pipe | 	local opts = ctx.pipe | ||
| 	local mname | 	local mname | ||
| 	local fname | 	local fname | ||
| 	if opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end | 	if opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end | ||
| 	if mname == nil then error( | 	if mname == nil then error(modulename .. | ||
| 		', ‘invoke_for_each’: No module name was provided', 0) end | |||
| 	if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end | 	if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end | ||
| 	if fname == nil then error( | 	if fname == nil then error(modulename .. | ||
| 		', ‘invoke_for_each’: No function name was provided', 0) end | |||
| 	local model = { title = 'Module:' .. mname, args = opts } | 	local model = { title = 'Module:' .. mname, args = opts } | ||
| 	local mfunc = require(model.title)[fname] | 	local mfunc = require(model.title)[fname] | ||
| Line 1,101: | Line 1,931: | ||
| 	flush_params( | 	flush_params( | ||
| 		ctx, | 		ctx, | ||
| 		function(key, val) | 		function (key, val) | ||
| 			opts[1] = key | 			opts[1] = key | ||
| 			opts[2] = val | 			opts[2] = val | ||
| Line 1,115: | Line 1,945: | ||
| 		ret[1] = ctx.header or '' | 		ret[1] = ctx.header or '' | ||
| 		if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end | 		if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end | ||
| 		ctx.text = table.concat(ret) | |||
| 		return false | |||
| 	end | 	end | ||
| 	ctx.text = ctx.ifngiven or '' | |||
| 	return false | |||
| end | end | ||
| --  | -- Syntax:  #invoke:params|magic_for_each|parser function|[append 1]|[append 2] | ||
| --            |[...]|[append n]|[named param 1=value 1]|[...]|[named param | |||
| library.magic_for_each = function(ctx) | |||
| --            n=value n]|[...] | |||
| library.magic_for_each = function (ctx) | |||
| 	local opts = ctx.pipe | 	local opts = ctx.pipe | ||
| 	local magic | 	local magic | ||
| 	if opts[1] ~= nil then magic = opts[1]:match'^%s*(.*%S)' end | 	if opts[1] ~= nil then magic = opts[1]:match'^%s*(.*%S)' end | ||
| 	if magic == nil then error( | 	if magic == nil then error(modulename .. | ||
| 		', ‘magic_for_each’: No parser function was provided', 0) end | |||
| 	local ccs = ctx.itersep or '' | 	local ccs = ctx.itersep or '' | ||
| 	local ret = {} | 	local ret = {} | ||
| Line 1,133: | Line 1,968: | ||
| 	flush_params( | 	flush_params( | ||
| 		ctx, | 		ctx, | ||
| 		function(key, val) | 		function (key, val) | ||
| 			opts[1] = key | 			opts[1] = key | ||
| 			opts[2] = val | 			opts[2] = val | ||
| Line 1,148: | Line 1,983: | ||
| 		ret[1] = ctx.header or '' | 		ret[1] = ctx.header or '' | ||
| 		if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end | 		if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end | ||
| 		ctx.text = table.concat(ret) | |||
| 		return false | |||
| 	end | 	end | ||
| 	ctx.text = ctx.ifngiven or '' | |||
| 	return false | |||
| end | end | ||
| --  | -- Syntax:  #invoke:params|call_for_each_value|template name|[append 1]|[append | ||
| --            2]|[...]|[append n]|[named param 1=value 1]|[...]|[named param | |||
| library.call_for_each_value = function(ctx) | |||
| --            n=value n]|[...] | |||
| library.call_for_each_value = function (ctx) | |||
| 	local opts = ctx.pipe | 	local opts = ctx.pipe | ||
| 	local tname | 	local tname | ||
| 	if opts[1] ~= nil then tname = opts[1]:match'^%s*(.*%S)' end | 	if opts[1] ~= nil then tname = opts[1]:match'^%s*(.*%S)' end | ||
| 	if tname == nil then error( | 	if tname == nil then error(modulename .. | ||
| 		', ‘call_for_each_value’: No template name was provided', 0) end | |||
| 	local model = { title = tname, args = opts } | 	local model = { title = tname, args = opts } | ||
| 	local ccs = ctx.itersep or '' | 	local ccs = ctx.itersep or '' | ||
| Line 1,166: | Line 2,006: | ||
| 	flush_params( | 	flush_params( | ||
| 		ctx, | 		ctx, | ||
| 		function(key, val) | 		function (key, val) | ||
| 			opts[1] = val | 			opts[1] = val | ||
| 			ret[nss + 1] = ccs | 			ret[nss + 1] = ccs | ||
| Line 1,179: | Line 2,019: | ||
| 		ret[1] = ctx.header or '' | 		ret[1] = ctx.header or '' | ||
| 		if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end | 		if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end | ||
| 		ctx.text = table.concat(ret) | |||
| 		return false | |||
| 	end | 	end | ||
| 	ctx.text = ctx.ifngiven or '' | |||
| 	return false | |||
| end | end | ||
| --  | -- Syntax:  #invoke:params|invoke_for_each_value|module name|[append 1]|[append | ||
| --            2]|[...]|[append n]|[named param 1=value 1]|[...]|[named param | |||
| library.invoke_for_each_value = function(ctx) | |||
| --            n=value n]|[...] | |||
| library.invoke_for_each_value = function (ctx) | |||
| 	local opts = ctx.pipe | 	local opts = ctx.pipe | ||
| 	local mname | 	local mname | ||
| 	local fname | 	local fname | ||
| 	if opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end | 	if opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end | ||
| 	if mname == nil then error( | 	if mname == nil then error(modulename .. | ||
| 		', ‘invoke_for_each_value’: No module name was provided', 0) end | |||
| 	if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end | 	if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end | ||
| 	if fname == nil then error( | 	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 model = { title = 'Module:' .. mname, args = opts } | ||
| 	local mfunc = require(model.title)[fname] | 	local mfunc = require(model.title)[fname] | ||
| Line 1,199: | Line 2,045: | ||
| 	local ret = {} | 	local ret = {} | ||
| 	local nss = 0 | 	local nss = 0 | ||
| 	remove_numeric_keys(opts, 1, 1) | |||
| 	flush_params( | 	flush_params( | ||
| 		ctx, | 		ctx, | ||
| 		function(key, val) | 		function (key, val) | ||
| 			opts[1] = val | 			opts[1] = val | ||
| 			ret[nss + 1] = ccs | 			ret[nss + 1] = ccs | ||
| Line 1,215: | Line 2,061: | ||
| 		ret[1] = ctx.header or '' | 		ret[1] = ctx.header or '' | ||
| 		if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end | 		if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end | ||
| 		ctx.text = table.concat(ret) | |||
| 		return false | |||
| 	end | 	end | ||
| 	ctx.text = ctx.ifngiven or '' | |||
| 	return false | |||
| end | end | ||
| --  | -- Syntax:  #invoke:params|magic_for_each_value|parser function|[append 1] | ||
| --            |[append 2]|[...]|[append n]|[named param 1=value 1]|[...]|[named | |||
| library.magic_for_each_value = function(ctx) | |||
| --            param n=value n]|[...] | |||
| library.magic_for_each_value = function (ctx) | |||
| 	local opts = ctx.pipe | 	local opts = ctx.pipe | ||
| 	local magic | 	local magic | ||
| 	if opts[1] ~= nil then magic = opts[1]:match'^%s*(.*%S)' end | 	if opts[1] ~= nil then magic = opts[1]:match'^%s*(.*%S)' end | ||
| 	if magic == nil then error( | 	if magic == nil then error(modulename .. | ||
| 		', ‘magic_for_each_value’: No parser function was provided', 0) end | |||
| 	local ccs = ctx.itersep or '' | 	local ccs = ctx.itersep or '' | ||
| 	local ret = {} | 	local ret = {} | ||
| Line 1,233: | Line 2,083: | ||
| 	flush_params( | 	flush_params( | ||
| 		ctx, | 		ctx, | ||
| 		function(key, val) | 		function (key, val) | ||
| 			opts[1] = val | 			opts[1] = val | ||
| 			ret[nss + 1] = ccs | 			ret[nss + 1] = ccs | ||
| Line 1,247: | Line 2,097: | ||
| 		ret[1] = ctx.header or '' | 		ret[1] = ctx.header or '' | ||
| 		if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end | 		if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end | ||
| 		ctx.text = table.concat(ret) | |||
| 		return false | |||
| 	end | 	end | ||
| 	ctx.text = ctx.ifngiven or '' | |||
| 	return false | |||
| end | end | ||
| --  | -- Syntax:  #invoke:params|call_for_each_group|template name|[append 1]|[append | ||
| --            2]|[...]|[append n]|[named param 1=value 1]|[...]|[named param | |||
| library.call_for_each_group = function(ctx) | |||
| --            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 opts = ctx.pipe | ||
| 	local tmp | 	local tmp | ||
| 	if opts[1] ~= nil then tmp = opts[1]:match'^%s*(.*%S)' end | 	if opts[1] ~= nil then tmp = opts[1]:match'^%s*(.*%S)' end | ||
| 	if tmp == nil then error( | 	if tmp == nil then error(modulename .. | ||
| 		', ‘call_for_each_group’: No template name was provided', 0) end | |||
| 	local model = { title = tmp } | 	local model = { title = tmp } | ||
| 	local ccs = ctx.itersep or '' | 	local ccs = ctx.itersep or '' | ||
| 	local nss = 0 | 	local nss = 0 | ||
| 	local prefix | |||
| 	local gid | |||
| 	local groups = {} | |||
| 	local ret = {} | 	local ret = {} | ||
| 	opts = {} | |||
| 	for key, val in pairs(ctx.params) do | |||
| 	for key, val in pairs(ctx.pipe) do | |||
| 		prefix, gid = tostring(key):match'^(.-)%s*(%-?%d*)$' | |||
| 		if type(key) == 'number' then opts[key - 1] = val | |||
| 		gid = tonumber(gid) or '' | |||
| 		else opts[key] = val end | |||
| 		tmp = tonumber(prefix) | |||
| 		if tmp ~= nil then | |||
| 			if tmp < 2 then prefix = tmp - 1 else prefix = tmp end | |||
| 		end | |||
| 		groups[gid][prefix] = val | |||
| 	end | 	end | ||
| 	ctx. | 	ctx.pipe = opts | ||
| 	ctx.params = make_groups(ctx.params) | |||
| 	flush_params( | 	flush_params( | ||
| 		ctx, | 		ctx, | ||
| 		function(gid, group) | 		function (gid, group) | ||
| 			for key, val in pairs(opts) do group[key] = val end | 			for key, val in pairs(opts) do group[key] = val end | ||
| 			group[ | 			group[0] = gid | ||
| 			model.args = group | 			model.args = group | ||
| 			ret[nss + 1] = ccs | 			ret[nss + 1] = ccs | ||
| Line 1,294: | Line 2,143: | ||
| 		ret[1] = ctx.header or '' | 		ret[1] = ctx.header or '' | ||
| 		if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end | 		if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end | ||
| 		ctx.text = table.concat(ret) | |||
| 		return false | |||
| 	end | 	end | ||
| 	ctx.text = ctx.ifngiven or '' | |||
| 	return false | |||
| end | end | ||
| Line 1,305: | Line 2,156: | ||
| 	---    ________________________________    --- | 	---    ________________________________    --- | ||
| 	---                                        --- | 	---                                        --- | ||
| -- The public table of functions | |||
| local iface = {} | |||
| 	--[[ Modifiers ]]-- | |||
| 	------------------- | |||
| -- Syntax:  #invoke:params|sequential|pipe to | |||
| iface.sequential = function(frame) | |||
| 	return context_init(frame, library.sequential, false, false) | |||
| end | |||
| -- Syntax:  #invoke:params|non-sequential|pipe to | |||
| iface['non-sequential'] = function(frame) | |||
| 	return context_init(frame, library['non-sequential'], false, false) | |||
| end | |||
| -- Syntax:  #invoke:params|sort|pipe to | |||
| iface.all_sorted = function(frame) | |||
| 	return context_init(frame, library.all_sorted, false, false) | |||
| end | |||
| -- Syntax:  #invoke:params|setting|directives|...|pipe to | |||
| iface.setting = function(frame) | |||
| 	return context_init(frame, library.setting, false, false) | |||
| end | |||
| -- Syntax:  #invoke:params|squeezing|pipe to | |||
| iface.squeezing = function(frame) | |||
| 	return context_init(frame, library.squeezing, false, false) | |||
| end | |||
| -- Syntax:  #invoke:params|filling_the_gaps|pipe to | |||
| iface.filling_the_gaps = function(frame) | |||
| 	return context_init(frame, library.filling_the_gaps, false, false) | |||
| end | |||
| -- Syntax:  #invoke:params|clearing|pipe to | |||
| iface.clearing = function(frame) | |||
| 	return context_init(frame, library.clearing, false, false) | |||
| end | |||
| -- Syntax:  #invoke:params|cutting|left cut|right cut|pipe to | |||
| iface.cutting = function(frame) | |||
| 	return context_init(frame, library.cutting, false, false) | |||
| end | |||
| -- Syntax:  #invoke:params|cropping|left crop|right crop|pipe to | |||
| iface.cropping = function(frame) | |||
| 	return context_init(frame, library.cropping, false, false) | |||
| end | |||
| -- Syntax:  #invoke:params|purging|start offset|length|pipe to | |||
| iface.purging = function(frame) | |||
| 	return context_init(frame, library.purging, false, false) | |||
| end | |||
| -- Syntax:  #invoke:params|backpurging|start offset|length|pipe to | |||
| iface.backpurging = function(frame) | |||
| 	return context_init(frame, library.backpurging, false, false) | |||
| end | |||
| -- Syntax:  #invoke:params|rotating|pipe to | |||
| iface.rotating = function(frame) | |||
| 	return context_init(frame, library.rotating, false, false) | |||
| end | |||
| -- Syntax:  #invoke:params|pivoting|pipe to | |||
| --[[ | |||
| iface.pivoting = function(frame) | |||
| 	return context_init(frame, library.pivoting, false, false) | |||
| end | |||
| ]] | |||
| -- Syntax:  #invoke:params|mirroring|pipe to | |||
| --[[ | |||
| iface.mirroring = function(frame) | |||
| 	return context_init(frame, library.mirroring, false, false) | |||
| end | |||
| ]] | |||
| -- Syntax:  #invoke:params|swapping|pipe to | |||
| --[[ | |||
| iface.swapping = function(frame) | |||
| 	return context_init(frame, library.swapping, false, false) | |||
| end | |||
| ]]-- | |||
| -- Syntax:  #invoke:params|sorting_sequential_values|[criterion]|pipe to | |||
| iface.sorting_sequential_values = function(frame) | |||
| 	return context_init(frame, library.sorting_sequential_values, false, false) | |||
| end | |||
| -- Syntax:  #invoke:params|with_name_matching|pattern 1|[plain flag 1]|[or] | |||
| --            |[pattern 2]|[plain flag 2]|[or]|[...]|[pattern N]|[plain flag | |||
| --            N]|pipe to | |||
| iface.with_name_matching = function(frame) | |||
| 	return context_init(frame, library.with_name_matching, false, false) | |||
| end | |||
| -- Syntax:  #invoke:params|with_name_not_matching|pattern 1|[plain flag 1] | |||
| --            |[and]|[pattern 2]|[plain flag 2]|[and]|[...]|[pattern N]|[plain | |||
| --            flag N]|pipe to | |||
| iface.with_name_not_matching = function(frame) | |||
| 	return context_init(frame, library.with_name_not_matching, false, | |||
| 		false) | |||
| end | |||
| -- Syntax:  #invoke:params|with_value_matching|pattern 1|[plain flag 1]|[or] | |||
| --            |[pattern 2]|[plain flag 2]|[or]|[...]|[pattern N]|[plain flag | |||
| --            N]|pipe to | |||
| iface.with_value_matching = function(frame) | |||
| 	return context_init(frame, library.with_value_matching, false, false) | |||
| end | |||
| -- Syntax:  #invoke:params|with_value_not_matching|pattern 1|[plain flag 1] | |||
| --            |[and]|[pattern 2]|[plain flag 2]|[and]|[...]|[pattern N]|[plain | |||
| --            flag N]|pipe to | |||
| iface.with_value_not_matching = function(frame) | |||
| 	return context_init(frame, library.with_value_not_matching, false, | |||
| 		false) | |||
| end | |||
| -- Syntax:  #invoke:params|trimming_values|pipe to | |||
| iface.trimming_values = function(frame) | |||
| 	return context_init(frame, library.trimming_values, false, false) | |||
| end | |||
| -- Syntax:  #invoke:params|mapping_by_calling|template name|[number of | |||
| --            additional arguments]|[argument 1]|[argument 2]|[...]|[argument | |||
| --            N]|pipe to | |||
| iface.mapping_by_calling = function(frame) | |||
| 	return context_init(frame, library.mapping_by_calling, false, false) | |||
| end | |||
| -- Syntax:  #invoke:params|mapping_by_invoking|module name|function | |||
| --            name|[number of additional arguments]|[argument 1]|[argument | |||
| --            2]|[...]|[argument N]|pipe to | |||
| iface.mapping_by_invoking = function(frame) | |||
| 	return context_init(frame, library.mapping_by_invoking, false, false) | |||
| end | |||
| -- Syntax:  #invoke:params|mapping_blindly_by_calling|template | |||
| --            name|[number of additional arguments]|[argument 1]|[argument | |||
| --            2]|[...]|[argument N]|pipe to | |||
| iface.mapping_blindly_by_calling = function(frame) | |||
| 	return context_init(frame, library.mapping_blindly_by_calling, false, | |||
| 		false) | |||
| end | |||
| -- Syntax:  #invoke:params|mapping_blindly_by_invoking|module | |||
| --            name|function name|[number of additional arguments]|[argument | |||
| --            1]|[argument 2]|[...]|[argument N]|pipe to | |||
| iface.mapping_blindly_by_invoking = function(frame) | |||
| 	return context_init(frame, library.mapping_blindly_by_invoking, false, | |||
| 		false) | |||
| end | |||
| -- Syntax:  #invoke:params|renaming_blindly_by_calling|template | |||
| --            name|[number of additional arguments]|[argument 1]|[argument | |||
| --            2]|[...]|[argument N]|pipe to | |||
| iface.renaming_blindly_by_calling = function(frame) | |||
| 	return context_init(frame, library.renaming_blindly_by_calling, false, | |||
| 		false) | |||
| end | |||
| -- Syntax:  #invoke:params|renaming_blindly_by_invoking|module | |||
| --            name|function name|[number of additional arguments]|[argument | |||
| --            1]|[argument 2]|[...]|[argument N]|pipe to | |||
| iface.renaming_blindly_by_invoking = function(frame) | |||
| 	return context_init(frame, library.renaming_blindly_by_invoking, false, | |||
| 		false) | |||
| end | |||
| Line 1,517: | Line 2,164: | ||
| -- Syntax:  #invoke:params|new|pipe to | -- Syntax:  #invoke:params|new|pipe to | ||
| static_iface.new = function (frame) | |||
| --[[ | |||
| 	local ctx = context_new(frame:getParent()) | |||
| 	local ctx = context_new(frame) | |||
| 	ctx.pipe = copy_or_ref_table(frame.args, false) | 	ctx.pipe = copy_or_ref_table(frame.args, false) | ||
| 	ctx.params = {} | 	ctx.params = {} | ||
| 	main_loop(ctx, context_iterate(ctx, 1)) | |||
| 	return ctx.text | |||
| end | end | ||
| ]]-- | |||
| 	--[[ First-position-only functions ]]-- | |||
| 	--------------------------------------- | |||
| 	--[[ Functions ]]-- | |||
| 	------------------- | |||
| -- Syntax:  #invoke:params|self | |||
| static_iface.self = function (frame) | |||
| -- Syntax:  #invoke:params|count | |||
| 	return frame:getParent():getTitle() | |||
| iface.count = function(frame) | |||
| 	return context_init(frame, library.count, true, true) | |||
| end | 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]|[...] | |||
| iface.concat_and_call = function(frame) | |||
| 	return context_init(frame, library.concat_and_call, false, true) | |||
| end | |||
| 	--[[ Public metatable of functions ]]-- | |||
| -- 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]|[...] | |||
| iface.concat_and_invoke = function(frame) | |||
| 	return context_init(frame, library.concat_and_invoke, false, true) | |||
| 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]|[...] | |||
| iface.concat_and_magic = function(frame) | |||
| 	return context_init(frame, library.concat_and_magic, false, true) | |||
| end | |||
| -- Syntax:  #invoke:params|value_of|parameter name | |||
| iface.value_of = function(frame) | |||
| 	return context_init(frame, library.value_of, true, true) | |||
| end | |||
| -- Syntax:  #invoke:params|list | |||
| iface.list = function(frame) | |||
| 	return context_init(frame, library.list, true, false) | |||
| end | |||
| -- Syntax:  #invoke:params|list_values | |||
| iface.list_values = function(frame) | |||
| 	return context_init(frame, library.list_values, true, false) | |||
| end | |||
| -- Syntax:  #invoke:params|for_each|wikitext | |||
| iface.for_each = function(frame) | |||
| 	return context_init(frame, library.for_each, true, 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]|[...] | |||
| iface.call_for_each = function(frame) | |||
| 	return context_init(frame, library.call_for_each, false, 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]|[...] | |||
| iface.invoke_for_each = function(frame) | |||
| 	return context_init(frame, library.invoke_for_each, false, 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]|[...] | |||
| iface.magic_for_each = function(frame) | |||
| 	return context_init(frame, library.magic_for_each, false, 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]|[...] | |||
| iface.call_for_each_value = function(frame) | |||
| 	return context_init(frame, library.call_for_each_value, false, 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]|[...] | |||
| iface.invoke_for_each_value = function(frame) | |||
| 	return context_init(frame, library.invoke_for_each_value, false, 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]|[...] | |||
| iface.magic_for_each_value = function(frame) | |||
| 	return context_init(frame, library.magic_for_each_value, false, 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]|[...] | |||
| iface.call_for_each_group = function(frame) | |||
| 	return context_init(frame, library.call_for_each_group, false, false) | |||
| end | |||
| 	--[[ First-position-only functions ]]-- | |||
| 	--------------------------------------- | 	--------------------------------------- | ||
| return setmetatable({}, { | |||
| -- Syntax:  #invoke:params|self | |||
| 	__index = function (_, query) | |||
| 		local fname = query:match'^%s*(.*%S)' | |||
| 	return frame:getParent():getTitle() | |||
| 		if fname == nil then error(modulename .. | |||
| end | |||
| 			': You must specify a function to call', 0) end | |||
| 		local func = static_iface[fname] | |||
| 		if func ~= nil then return func end | |||
| return iface | |||
| 		func = library[fname] | |||
| 		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, | |||
| 				refpipe[fname]) | |||
| 			ctx.params = copy_or_ref_table(ctx.oparams, | |||
| 				refparams[fname]) | |||
| 			main_loop(ctx, func) | |||
| 			return ctx.text | |||
| 		end | |||
| 	end | |||
| }) | |||
Latest revision as of 02:37, 22 April 2025
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 some numeric elements from a table, shifting everything to the left
local function remove_numeric_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 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
		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'
}
-- 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.pipe[n_forward] ~= nil then
		nextfn = ctx.pipe[n_forward]:match'^%s*(.*%S)'
	end
	if nextfn == nil then error(modulename ..
		': You must specify a function to call', 0) end
	if library[nextfn] == nil then
		if ctx.firstposonly[nextfn] == 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 library[nextfn]
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.children[currsnap] = newparams end
	for key, val in pairs(tbl) do newparams[key] = 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 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 tbl[idx + shf] = src[idx] end
		pin = pin + tmp + 1
	end
	if names ~= nil then
		for key, val in pairs(names) do tbl[key] = val end
	end
	return tbl, pin
end
-- Load the optional arguments of some of the `mapping_*` and `renaming_*`
-- class of modifiers
local function load_callback_opts (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 = load_child_opts(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 load_replace_args (opts, fname)
	if opts[1] == nil then error(modulename ..
		', ‘' .. fname .. '’: No pattern string was given', 0) end
	if opts[2] == nil then error(modulename ..
		', ‘' .. 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 load_pattern_args (opts, fname)
	local state = 0
	local cnt = 1
	local keyw
	local nptns = 0
	local ptns = {}
	for _, val in ipairs(opts) 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(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 = opts[argc]
		if tmp == nil then break end
		tmp = tmp:match'^%s*(.-)%s*$'
		if optslots[1] ~= nil and trim_parse_opts[tmp] ~= nil then
			tmp = trim_parse_opts[tmp]
			trimn = tmp[1]
			trimu = tmp[2]
			optslots[1] = nil
		elseif optslots[2] ~= nil and isep_parse_opts[tmp] ~= nil then
			argc = argc + 1
			iplain = isep_parse_opts[tmp]
			isp = opts[argc]
			optslots[2] = nil
		elseif optslots[3] ~= nil and psep_parse_opts[tmp] ~= nil then
			argc = argc + 1
			pplain = psep_parse_opts[tmp]
			psp = opts[argc]
			optslots[3] = nil
		else break end
		argc = argc + 1
	until noptslots < 1
	return isp, iplain, psp, pplain, trimn, trimu, argc
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 numeric
-- 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
-- Populate a table by parsing a parameter string
local function parse_parameter_string (tbl, str, isp, ipl, psp, ppl, trn, tru)
	local key
	local val
	local spos1
	local spos2
	local pos1
	local pos2
	local pos3 = 0
	local idx = 1
	local lenplone = #str + 1
	if isp == nil or isp == '' then
		if psp == nil or psp == '' then
			if tru then tbl[idx] = str:match'^%s*(.-)%s*$'
			else tbl[idx] = str end
			return tbl
		end
		spos1, spos2 = str:find(psp, 1, ppl)
		if spos1 == nil then
			key = idx
			if tru then val = str:match'^%s*(.-)%s*$'
			else val = str end
			idx = idx + 1
		else
			key = str:sub(1, spos1 - 1)
			key = tonumber(key) or key:match'^%s*(.-)%s*$'
			val = str:sub(spos2 + 1)
			if trn then val = val:match'^%s*(.-)%s*$' end
		end
		tbl[key] = val
		return tbl
	end
	if psp == nil or psp == '' then
		repeat
			pos1 = pos3 + 1
			pos2, pos3 = str:find(isp, pos1, ipl)
			val = str:sub(pos1, (pos2 or lenplone) - 1)
			if tru then val = val:match'^%s*(.-)%s*$' end
			tbl[idx] = val
			idx = idx + 1
		until pos2 == nil
		return tbl
	end
	repeat
		pos1 = pos3 + 1
		pos2, pos3 = str:find(isp, pos1, ipl)
		val = str:sub(pos1, (pos2 or lenplone) - 1)
		spos1, spos2 = val:find(psp, 1, ppl)
		if spos1 == nil then
			key = idx
			if tru then val = val:match'^%s*(.-)%s*$' end
			idx = idx + 1
		else
			key = val:sub(1, spos1 - 1)
			key = tonumber(key) or key:match'^%s*(.-)%s*$'
			val = val:sub(spos2 + 1)
			if trn then val = val:match'^%s*(.-)%s*$' end
		end
		tbl[key] = val
	until pos2 == nil
	return tbl
end
-- Concatenate the numeric keys from the table of parameters to the numeric
-- keys from the table of options; non-numeric keys from the table of options
-- will prevail over colliding non-numeric keys from the table of parameters
local function concat_params (ctx)
	local tbl = ctx.params
	local nmax = 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 + nmax] = val end
	else
		if ctx.subset == -1 then
			for key in ipairs(tbl) do tbl[key] = nil end
		end
		for key, val in pairs(tbl) do
			if type(key) == 'number' and key > 0 then
				retval[key + nmax] = 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(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 = opts[1]
	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
				ctx[val] = opts[argc]
				dest[key] = nil
			end
			argc = argc + 1
		else
			vname = memoryslots[string.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 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(modulename ..
			', ‘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 numerics = {}
	for key, val in pairs(tbl) do
		if type(key) == 'number' then
			numerics[key] = val
			tbl[key] = nil
		end
	end
	for key, val in ipairs(numerics) 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(modulename ..
		', ‘cutting’: Left cut must be a number', 0) end
	local rcut = tonumber(ctx.pipe[2])
	if rcut == nil then error(modulename ..
		', ‘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(modulename ..
		', ‘cropping’: Left crop must be a number', 0) end
	local rcut = tonumber(ctx.pipe[2])
	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 < 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(modulename ..
		', ‘purging’: Start offset must be a number', 0) end
	local len = tonumber(ctx.pipe[2])
	if len == nil then error(modulename ..
		', ‘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(modulename ..
		', ‘backpurging’: Start offset must be a number', 0) end
	local len = tonumber(ctx.pipe[2])
	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 < 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 numerics = {}
	local nmax = 0
	for key, val in pairs(tbl) do
		if type(key) == 'number' then
			numerics[key] = val
			tbl[key] = nil
			if key > nmax then nmax = key end
		end
	end
	for key, val in pairs(numerics) 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 numerics = {}
	for key, val in pairs(tbl) do
		if type(key) == 'number' then
			numerics[key] = val
			tbl[key] = nil
		end
	end
	for key, val in pairs(numerics) 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 numerics = {}
	local nmax
	local nmin
	for key, val in pairs(tbl) do
		if type(key) == 'number' then
			numerics[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(numerics) 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(modulename ..
		', ‘inserting’: Position must be a number', 0) end
	local len = tonumber(ctx.pipe[2])
	if len == nil or len < 1 then error(modulename ..
		', ‘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(modulename ..
		', ‘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(modulename ..
		', ‘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(modulename ..
		', ‘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(modulename ..
		', ‘discarding’: A range was provided, but the initial parameter name is not numeric', 0) end
	if len < 1 then error(modulename ..
		', ‘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|excluding_non-numeric_names|pipe to
library['excluding_non-numeric_names'] = function (ctx)
	local tmp = ctx.params
	for key, val in pairs(tmp) do
		if type(key) ~= 'number' then tmp[key] = nil end
	end
	return context_iterate(ctx, 1)
end
-- Syntax:  #invoke:params|excluding_numeric_names|pipe to
library.excluding_numeric_names = function (ctx)
	local tmp = ctx.params
	for key, val in pairs(tmp) do
		if type(key) == 'number' then tmp[key] = nil end
	end
	return context_iterate(ctx, 1)
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)
	-- NOTE: `ctx.params` might be the original metatable! As a modifier,
	-- this function MUST create a copy of it before returning
	local targets, nptns, argc = load_pattern_args(ctx.pipe, targets,
		'with_name_matching')
	local tmp
	local ptn
	local tbl = ctx.params
	local newparams = {}
	for idx = 1, nptns do
		ptn = targets[idx]
		if ptn[3] then
			tmp = tonumber(ptn[1]) or ptn[1]
			newparams[tmp] = tbl[tmp]
		else
			for key, val in pairs(tbl) do
				if tostring(key):find(ptn[1], 1, ptn[2]) then
					newparams[key] = val
				end
			end
		end
	end
	ctx.params = newparams
	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 targets, nptns, argc = load_pattern_args(ctx.pipe, targets,
		'with_name_not_matching')
	local tbl = ctx.params
	if nptns == 1 and targets[1][3] then
		local tmp = targets[1][1]
		tbl[tonumber(tmp) or tmp] = nil
		return context_iterate(ctx, argc)
	end
	local yesmatch
	local ptn
	for key in pairs(tbl) do
		yesmatch = true
		for idx = 1, nptns do
			ptn = targets[idx]
			if ptn[3] then
				if tostring(key) ~= ptn[1] then
					yesmatch = false
					break
				end
			elseif not tostring(key):find(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, nptns, argc = load_pattern_args(ctx.pipe, targets,
		'with_value_matching')
	local nomatch
	local ptn
	for key, val in pairs(tbl) do
		nomatch = true
		for idx = 1, nptns do
			ptn = targets[idx]
			if ptn[3] then
				if val == ptn[1] then
					nomatch = false
					break
				end
			elseif val:find(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, nptns, argc = load_pattern_args(ctx.pipe, targets,
		'with_value_not_matching')
	local yesmatch
	local ptn
	for key, val in pairs(tbl) do
		yesmatch = true
		for idx = 1, nptns do
			ptn = targets[idx]
			if ptn[3] then
				if val ~= ptn[1] then
					yesmatch = false
					break
				end
			elseif not val:find(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(modulename ..
		', ‘mapping_by_calling’: No template name was provided', 0) end
	local margs, argc, looptype, karg, varg = load_callback_opts(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(modulename ..
		', ‘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(modulename ..
		', ‘mapping_by_invoking’: No function name was provided', 0) end
	local margs, argc, looptype, karg, varg = load_callback_opts(opts, 2,
		mapping_styles.values_only)
	local model = { title = 'Module:' .. mname, args = margs }
	local mfunc = require(model.title)[fname]
	if mfunc == nil then error(modulename ..
		', ‘mapping_by_invoking’: The function ‘' .. fname ..
		'’ does not exist', 0) end
	value_maps[looptype](ctx.params, margs, karg, varg, function ()
		return tostring(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(modulename ..
		', ‘mapping_by_magic’: No parser function was provided', 0) end
	local margs, argc, looptype, karg, varg = load_callback_opts(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 =
		load_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(modulename ..
		', ‘renaming_by_calling’: No template name was provided', 0) end
	local rargs, argc, looptype, karg, varg = load_callback_opts(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(modulename ..
		', ‘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(modulename ..
		', ‘renaming_by_invoking’: No function name was provided', 0) end
	local rargs, argc, looptype, karg, varg = load_callback_opts(opts, 2,
		mapping_styles.names_only)
	local model = { title = 'Module:' .. mname, args = rargs }
	local mfunc = require(model.title)[fname]
	if mfunc == nil then error(modulename ..
		', ‘renaming_by_invoking’: The function ‘' .. fname ..
		'’ does not exist', 0) end
	map_names(ctx.params, rargs, karg, varg, looptype, function ()
		local tmp = mfunc(ctx.frame:newChild(model))
		return tonumber(tmp) or tostring(tmp)
	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(modulename ..
		', ‘renaming_by_magic’: No parser function was provided', 0) end
	local rargs, argc, looptype, karg, varg = load_callback_opts(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 =
		load_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(modulename ..
		', ‘grouping_by_calling’: No template name was provided', 0) end
	local model = { title = tmp }
	local tmp, argc = load_child_opts(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
-- Syntax:  #invoke:params|parsing|string to parse|[trim flag]|[iteration
--            delimiter setter]|[...]|[key-value delimiter setter]|[...]|pipe to
library.parsing = function (ctx)
	local opts = ctx.pipe
	if opts[1] == nil then error(modulename ..
		', ‘parsing’: No string to parse was provided', 0) end
	local isep, iplain, psep, pplain, trimnamed, trimunnamed, argc =
		load_parse_opts(opts, 2)
	parse_parameter_string(ctx.params, opts[1], isep, iplain, psep, pplain,
		trimnamed, trimunnamed)
	return context_iterate(ctx, argc)
end
-- Syntax:  #invoke:params|reinterpreting|parameter to reinterpret|[trim
--            flag]|[iteration delimiter setter]|[...]|[key-value delimiter
--            setter]|[...]|pipe to
library.reinterpreting = function (ctx)
	local opts = ctx.pipe
	if opts[1] == nil then error(modulename ..
		', ‘reinterpreting’: No parameter to reinterpret was provided', 0) end
	local isep, iplain, psep, pplain, trimnamed, trimunnamed, argc =
		load_parse_opts(opts, 2)
	local tbl = ctx.params
	local tmp = tonumber(opts[1]) or opts[1]:match'^%s*(.-)%s*$'
	local str = tbl[tmp]
	if str ~= nil then
		tbl[tmp] = nil
		parse_parameter_string(tbl, str, isep, iplain, psep, pplain,
			trimnamed, trimunnamed)
	end
	return context_iterate(ctx, argc)
end
-- Syntax:  #invoke:params|combining_by_calling|template name|new parameter
--            name|pipe to
library.combining_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 tname = ctx.pipe[1]
	if tname ~= nil then tname = tname:match'^%s*(.*%S)'
	else error(modulename ..
		', ‘combining_by_calling’: No template name was provided', 0) end
	local merge_into = ctx.pipe[2]
	if merge_into == nil then error(modulename ..
		', ‘combining_by_calling’: No parameter name was provided', 0) end
	merge_into = tonumber(merge_into) or merge_into:match'^%s*(.-)%s*$'
	ctx.params = {
		[merge_into] = ctx.frame:expandTemplate{
			title = tname,
			args = ctx.params
		}
	}
	return context_iterate(ctx, 3)
end
-- Syntax:  #invoke:params|snapshotting|pipe to
library.snapshotting = function (ctx)
	push_cloned_stack(ctx, ctx.params)
	return context_iterate(ctx, 1)
end
-- Syntax:  #invoke:params|remembering|pipe to
library.remembering = function (ctx)
	push_cloned_stack(ctx, ctx.oparams)
	return context_iterate(ctx, 1)
end
-- Syntax:  #invoke:params|entering_substack|[new]|pipe to
library.entering_substack = function (ctx)
	local tbl = ctx.params
	local ncurrparent = ctx.n_parents + 1
	if ctx.parents == nil then ctx.parents = { tbl }
	else ctx.parents[ncurrparent] = tbl end
	ctx.n_parents = ncurrparent
	if ctx.pipe[1] ~= nil and ctx.pipe[1]:match'^%s*new%s*$' then
		ctx.params = {}
		return context_iterate(ctx, 2)
	end
	local currsnap = ctx.n_children
	if currsnap > 0 then
		ctx.params = ctx.children[currsnap]
		ctx.children[currsnap] = nil
		ctx.n_children = currsnap - 1
	else
		local newparams = {}
		for key, val in pairs(tbl) do newparams[key] = 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 opts[1] == nil then error(modulename ..
		', ‘pulling’: No parameter to pull was provided', 0) end
	local parent
	local tmp = ctx.n_parents
	if tmp < 1 then parent = ctx.oparams else parent = ctx.parents[tmp] end
	tmp = tonumber(opts[1]) or opts[1]:match'^%s*(.-)%s*$'
	if parent[tmp] ~= nil then ctx.params[tmp] = parent[tmp] end
	return context_iterate(ctx, 2)
end
-- Syntax:  #invoke:params|detaching_substack|pipe to
library.detaching_substack = function (ctx)
	local ncurrparent = ctx.n_parents
	if ncurrparent < 1 then error(modulename ..
		', ‘detaching_substack’: No substack has been created', 0) end
	local parent = ctx.parents[ncurrparent]
	for key in pairs(ctx.params) do parent[key] = nil end
	return context_iterate(ctx, 1)
end
-- Syntax:  #invoke:params|leaving_substack|pipe to
library.leaving_substack = function (ctx)
	local ncurrparent = ctx.n_parents
	if ncurrparent < 1 then error(modulename ..
		', ‘leaving_substack’: No substack has been created', 0) end
	local currsnap = ctx.n_children + 1
	if ctx.children == nil then ctx.children = { ctx.params }
	else ctx.children[currsnap] = ctx.params end
	ctx.params = ctx.parents[ncurrparent]
	ctx.parents[ncurrparent] = nil
	ctx.n_parents = ncurrparent - 1
	ctx.n_children = currsnap
	return context_iterate(ctx, 1)
end
-- Syntax:  #invoke:params|merging_substack|pipe to
library.merging_substack = function (ctx)
	local ncurrparent = ctx.n_parents
	if ncurrparent < 1 then error(modulename ..
		', ‘merging_substack’: No substack has been created', 0) end
	local parent = ctx.parents[ncurrparent]
	local child = ctx.params
	ctx.params = parent
	ctx.parents[ncurrparent] = nil
	ctx.n_parents = ncurrparent - 1
	for key, val in pairs(child) do parent[key] = val end
	return context_iterate(ctx, 1)
end
-- Syntax:  #invoke:params|flushing|pipe to
library.flushing = function (ctx)
	if ctx.n_children < 1 then error(modulename ..
		', ‘flushing’: There are no substacks to flush', 0) end
	local parent = ctx.params
	local currsnap = ctx.n_children
	for key, val in pairs(ctx.children[currsnap]) do parent[key] = val end
	ctx.children[currsnap] = nil
	ctx.n_children = currsnap - 1
	return context_iterate(ctx, 1)
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(modulename ..
		', ‘concat_and_call’: No template name was provided', 0) end
	remove_numeric_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(modulename ..
		', ‘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(modulename ..
		', ‘concat_and_invoke’: No function name was provided', 0) end
	remove_numeric_keys(opts, 1, 2)
	local mfunc = require('Module:' .. mname)[fname]
	if mfunc == nil then error(modulename ..
		', ‘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(modulename ..
		', ‘concat_and_magic’: No parser function was provided', 0) end
	remove_numeric_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(modulename ..
		', ‘value_of’: No parameter name was provided', 0) end
	local knum = tonumber(kstr)
	local len = #ctx.params  -- No worries: unused when in first position
	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!
	-- 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)
			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|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 tbl[key] = opts[tonumber(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
		tbl[key] = opts[tmp]
		opts[tmp] = 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.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(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)
			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(modulename ..
		', ‘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(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)
			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(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)
			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(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)
			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(modulename ..
		', ‘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(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)
			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(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)
			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(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 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(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_iface[fname]
		if func ~= nil then return func end
		func = library[fname]
		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,
				refpipe[fname])
			ctx.params = copy_or_ref_table(ctx.oparams,
				refparams[fname])
			main_loop(ctx, func)
			return ctx.text
		end
	end
})







