Module:Params

---                                       ---	---     LOCAL ENVIRONMENT                  --- ---   ________________________________    ---	---                                        ---

-- Abstract utilities --

-- Helper function for `string.gsub` (for managing zero-padded numbers) function zero_padded(str) return ("%03d%s"):format(#str, str) end

-- Helper function for `table.sort` (for natural sorting) function natural_sort(var1, var2) return tostring(var1):gsub("%d+", zero_padded) < tostring(var2):gsub("%d+", zero_padded) end

-- Return a copy or a reference to a table local function copy_or_ref_table(src, refonly) if refonly then return src end newtab = {} for key, val in pairs(src) do newtab[key] = val end return newtab end

-- Remove numerical elements from a table, shifting everything to the left function remove_numerical_keys(tbl, idx, len) local cache = {} local tmp = idx + len - 1 for key, val in pairs(tbl) do		if type(key) == 'number' and key >= idx then if key > tmp then cache[key - len] = val end tbl[key] = nil end end for key, val in pairs(cache) do tbl[key] = val end end

-- Make a reduced copy of a table (shifting in both directions if necessary) 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) function copy_table_expanded(tbl, idx, len) local ret = {} local tmp = idx + len - 1 if idx > 0 then for key, val in pairs(tbl) do			if type(key) ~= 'number' or key < idx then ret[key] = val else ret[key + len] = val end end elseif tmp > 0 then local nshift = idx - 1 for key, val in pairs(tbl) do			if type(key) ~= 'number' then ret[key] = val elseif key > 0 then ret[key + tmp] = val elseif key < 1 then ret[key + nshift] = val end end else for key, val in pairs(tbl) do			if type(key) ~= 'number' or key > tmp then ret[key] = val else ret[key - len] = val end end end return ret end

-- Move a key from a table to another, but only if under a different name and -- always parsing numerical strings as numbers 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, -- Simply uncommenting enables the option plain = 2, strict = 3 }

-- Sort functions (functions and modifiers MUST avoid these names) local sortfunctions = { --alphabetically = false, -- Simply uncommenting enables the option naturally = natural_sort }

-- Callback styles for the `mapping_*` and `renaming_*` class of modifiers -- (functions and modifiers MUST avoid these names) --[[

Meanings of the columns:

col[1] = Loop type (0-3) col[2] = Number of module arguments that the style requires (1-3) col[3] = Minimum number of sequential parameters passed to the callback col[4] = Name of the callback parameter where to place each parameter name col[5] = Name of the callback parameter where to place each parameter value col[6] = Argument in the modifier's invocation that will override `col[4]` col[7] = Argument in the modifier's invocation that will override `col[5]`

A value of `-1` indicates that no meaningful value is stored (i.e. `nil`)

]]-- local mapping_styles = { names_and_values = { 3, 2, 2, 1, 2, -1, -1 }, values_and_names = { 3, 2, 2, 2, 1, -1, -1 }, values_only = { 1, 2, 1, -1, 1, -1, -1 }, names_only = { 2, 2, 1, 1, -1, -1, -1 }, names_and_values_as = { 3, 4, 0, -1, -1, 2, 3 }, names_only_as = { 2, 3, 0, -1, -1, 2, -1 }, values_only_as = { 1, 3, 0, -1, -1, -1, 2 }, blindly = { 0, 2, 0, -1, -1, -1, -1 } }

-- Memory slots (functions and modifiers MUST avoid these names) local memoryslots = { i = 'itersep', l = 'lastsep', p = 'pairsep', h = 'header', f = 'footer', n = 'ifngiven' }

-- Functions and modifiers MUST avoid these names too: `let`

-- Module's private environment -- --

-- Maximum number of numerical parameters that can be filled, if missing (we -- chose an arbitrary number for this constant; you can discuss about its -- optimal value at Module talk:Params) local maxfill = 1024

-- The private table of functions local library = {}

-- Functions that can only be invoked in first position local static_iface = {}

-- Create a new context local function context_new local ctx = {} ctx.luaname = 'Module:Params'	-- or `frame:getTitle` -- ctx.iterfunc = pairs ctx.firstposonly = static_iface ctx.n_available = maxfill return ctx end

-- Move to the next action within the user-given list local function context_iterate(ctx, n_forward) local nextfn if ctx.pipe[n_forward] ~= nil then nextfn = ctx.pipe[n_forward]:match'^%s*(.*%S)' end if nextfn == nil then error(ctx.luaname ..		': You must specify a function to call', 0) end if library[nextfn] == nil then if ctx.firstposonly[nextfn] == nil then error(ctx.luaname ..			': The function ‘' .. nextfn .. '’ does not exist', 0) else error(ctx.luaname .. ': The ‘' .. nextfn ..			'’ directive can only appear in first position', 0) end end remove_numerical_keys(ctx.pipe, 1, n_forward) return library[nextfn] end

-- Main loop local function main_loop(ctx, start_with) local fn = start_with repeat fn = fn(ctx) until not fn end

-- Parse the arguments of the `mapping_*` and `renaming_*` class of modifiers function parse_child_args(dest, 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 names local nargs local pin = style[2] + shf 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 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 shf = n_exist - pin for idx = pin + 1, pin + tmp do dest[idx + shf] = src[idx] end nargs = pin + tmp + 1 else nargs = pin end if names ~= nil then for key, val in pairs(names) do dest[key] = val end end 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 nargs, tmp, karg, varg end

-- Parse the arguments of the `with_*_matching` class of modifiers local function parse_pattern_args(ctx, ptns, fname) local state = 0 local cnt = 1 local keyw local nptns = 0 for _, val in ipairs(ctx.pipe) do		if state == 0 then nptns = nptns + 1 ptns[nptns] = { val, false, false } state = -1 else keyw = val:match'^%s*(.*%S)' if keyw == nil or mkeywords[keyw] == nil or (				state > 0 and mkeywords[keyw] > 0			) then break else state = mkeywords[keyw] if state > 1 then ptns[nptns][2] = true end if state == 3 then ptns[nptns][3] = true end end end cnt = cnt + 1 end if state == 0 then error(ctx.luaname .. ', ‘' .. fname ..		'’: No pattern was given', 0) end return cnt end

-- Map parameters' values using a custom callback and a referenced table function map_values(tbl, margs, karg, varg, looptype, fn) if looptype == 1 then for key, val in pairs(tbl) do			margs[varg] = val tbl[key] = fn end elseif looptype == 3 then for key, val in pairs(tbl) do			margs[karg] = key margs[varg] = val tbl[key] = fn end elseif looptype == 2 then for key in pairs(tbl) do			margs[karg] = key tbl[key] = fn end elseif looptype == 0 then for key in pairs(tbl) do			tbl[key] = fn end end end

-- Map parameters' names using a custom callback and a referenced table function map_names(tbl, rargs, karg, varg, looptype, fn) local cache = {} if looptype == 2 then for key, val in pairs(tbl) do			rargs[karg] = key steal_if_renamed(val, tbl, key, cache, fn) end elseif looptype == 3 then for key, val in pairs(tbl) do			rargs[karg] = key rargs[varg] = val steal_if_renamed(val, tbl, key, cache, fn) end elseif looptype == 1 then for key, val in pairs(tbl) do			rargs[varg] = val steal_if_renamed(val, tbl, key, cache, fn) end elseif looptype == 0 then for key, val in pairs(tbl) do			steal_if_renamed(val, tbl, key, cache, fn) end end for key, val in pairs(cache) do tbl[key] = val end end

-- Concatenate the numerical keys from the table of parameters to the numerical -- keys from the table of options; non-numerical keys from the table of options -- will prevail over colliding non-numerical keys from the table of parameters local function concat_params(ctx) local tbl = ctx.params local size = table.maxn(ctx.pipe) local retval = {} if ctx.subset == 1 then -- We need only the sequence for key, val in ipairs(tbl) do retval[key + size] = val end else if ctx.subset == -1 then for key, val in ipairs(tbl) do tbl[key] = nil end end for key, val in pairs(tbl) do			if type(key) == 'number' then retval[key + size] = val else retval[key] = val end end end for key, val in pairs(ctx.pipe) do retval[key] = val end return retval end

-- Flush the parameters by calling a custom function for each value (after this -- function has been invoked `ctx.params` will be no longer usable) local function flush_params(ctx, fn) local tbl = ctx.params if ctx.subset == 1 then for key, val in ipairs(tbl) do fn(key, val) end return end if ctx.subset == -1 then for key, val in ipairs(tbl) do tbl[key] = nil end end if ctx.dosort then 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 if ctx.subset ~= -1 then for key, val in ipairs(tbl) do			fn(key, val) tbl[key] = nil end end for key, val in pairs(tbl) do fn(key, val) end end

-- Modifiers -- -

-- Syntax: #invoke:params|sequential|pipe to library.sequential = function(ctx) if ctx.subset == -1 then error(ctx.luaname ..		': The two directives ‘non-sequential’ and ‘sequential’ are in contradiction with each other', 0) end if ctx.dosort then error(ctx.luaname ..		': The ‘all_sorted’ directive is redundant when followed by ‘sequential’', 0) end ctx.iterfunc = ipairs ctx.subset = 1 return context_iterate(ctx, 1) end

-- Syntax: #invoke:params|non-sequential|pipe to library['non-sequential'] = function(ctx) if ctx.subset == 1 then error(ctx.luaname ..		': The two directives ‘sequential’ and ‘non-sequential’ are in contradiction with each other', 0) end ctx.iterfunc = pairs ctx.subset = -1 return context_iterate(ctx, 1) end

-- Syntax: #invoke:params|sort|pipe to library.all_sorted = function(ctx) if ctx.subset == 1 then error(ctx.luaname ..		': The ‘all_sorted’ directive is redundant after ‘sequential’', 0) end ctx.dosort = true return context_iterate(ctx, 1) end

-- Syntax: #invoke:params|setting|directives|...|pipe to library.setting = function(ctx) local opts = ctx.pipe local cmd = opts[1] if cmd ~= nil then cmd = cmd:gsub('%s+', ''):gsub('/+', '/'):match'^/*(.*[^/])' end if cmd == nil then error(ctx.luaname ..		', ‘setting’: No directive was given', 0) end local sep = string.byte('/') local argc = 2 local dest = {} local vname local chr for idx = 1, #cmd do		chr = cmd:byte(idx) if chr == sep then for key, val in ipairs(dest) do				ctx[val] = opts[argc] dest[key] = nil end argc = argc + 1 else vname = memoryslots[string.char(chr)] if vname == nil then error(ctx.luaname ..				', ‘setting’: Unknown slot "' ..				string.char(chr) .. '"', 0) end table.insert(dest, vname) end end for key, val in ipairs(dest) do ctx[val] = opts[argc] end return context_iterate(ctx, argc + 1) end

-- Syntax: #invoke:params|squeezing|pipe to library.squeezing = function(ctx) local tbl = ctx.params local store = {} local indices = {} local newlen = 0 for key, val in pairs(tbl) do		if type(key) == 'number' then newlen = newlen + 1 indices[newlen] = key store[key] = val tbl[key] = nil end end table.sort(indices) for idx = 1, newlen do tbl[idx] = store[indices[idx]] end return context_iterate(ctx, 1) end

-- Syntax: #invoke:params|filling_the_gaps|pipe to library.filling_the_gaps = function(ctx) local tbl = ctx.params local nmin = 1 local nmax = nil local nnums = -1 local tmp = {} for key, val in pairs(tbl) do		if type(key) == 'number' then if nmax == nil then if key < nmin then nmin = key end nmax = key elseif key > nmax then nmax = key elseif key < nmin then nmin = key end nnums = nnums + 1 tmp[key] = val end end if nmax ~= nil and nmax - nmin > nnums then ctx.n_available = ctx.n_available + nmin + nnums - nmax if ctx.n_available < 0 then error(ctx.luaname ..			', ‘filling_the_gaps’: It is possible to fill at most ' ..			tostring(maxfill) .. ' parameters', 0) end for idx = nmin, nmax, 1 do tbl[idx] = '' end for key, val in pairs(tmp) do tbl[key] = val end end return context_iterate(ctx, 1) end

-- Syntax: #invoke:params|clearing|pipe to library.clearing = function(ctx) local tbl = ctx.params local numericals = {} for key, val in pairs(tbl) do		if type(key) == 'number' then numericals[key] = val tbl[key] = nil end end for key, val in ipairs(numericals) do tbl[key] = val end return context_iterate(ctx, 1) end

-- Syntax: #invoke:params|cutting|left cut|right cut|pipe to library.cutting = function(ctx) local lcut = tonumber(ctx.pipe[1]) if lcut == nil then error(ctx.luaname ..		', ‘cutting’: Left cut must be a number', 0) end local rcut = tonumber(ctx.pipe[2]) if rcut == nil then error(ctx.luaname ..		', ‘cutting’: Right cut must be a number', 0) end local tbl = ctx.params local len = #tbl if lcut < 0 then lcut = len + lcut end if rcut < 0 then rcut = len + rcut end local tot = lcut + rcut if tot > 0 then local cache = {} if tot >= len then for key in ipairs(tbl) do tbl[key] = nil end tot = len else for idx = len - rcut + 1, len, 1 do tbl[idx] = nil end for idx = 1, lcut, 1 do tbl[idx] = nil end end for key, val in pairs(tbl) do			if type(key) == 'number' and key > 0 then if key > len then cache[key - tot] = val else cache[key - lcut] = val end tbl[key] = nil end end for key, val in pairs(cache) do tbl[key] = val end end return context_iterate(ctx, 3) end

-- Syntax: #invoke:params|cropping|left crop|right crop|pipe to library.cropping = function(ctx) local lcut = tonumber(ctx.pipe[1]) if lcut == nil then error(ctx.luaname ..		', ‘cropping’: Left crop must be a number', 0) end local rcut = tonumber(ctx.pipe[2]) if rcut == nil then error(ctx.luaname ..		', ‘cropping’: Right crop must be a number', 0) end local tbl = ctx.params local nmin local nmax for key in pairs(tbl) do		if type(key) == 'number' then if nmin == nil then nmin = key nmax = key elseif key > nmax then nmax = key elseif key < nmin then nmin = key end end end if nmin ~= nil then local len = nmax - nmin + 1 if lcut < 0 then lcut = len + lcut end if rcut < 0 then rcut = len + rcut end if lcut + rcut - len > -1 then for key in pairs(tbl) do				if type(key) == 'number' then tbl[key] = nil end end elseif lcut + rcut > 0 then for idx = nmax - rcut + 1, nmax do tbl[idx] = nil end for idx = nmin, nmin + lcut - 1 do tbl[idx] = nil end local lshift = nmin + lcut - 1 if lshift > 0 then for idx = lshift + 1, nmax, 1 do					tbl[idx - lshift] = tbl[idx] tbl[idx] = nil end end end end return context_iterate(ctx, 3) end

-- Syntax: #invoke:params|purging|start offset|length|pipe to library.purging = function(ctx) local idx = tonumber(ctx.pipe[1]) if idx == nil then error(ctx.luaname ..		', ‘purging’: Start offset must be a number', 0) end local len = tonumber(ctx.pipe[2]) if len == nil then error(ctx.luaname ..		', ‘purging’: Length must be a number', 0) end local tbl = ctx.params if len < 1 then len = len + table.maxn(tbl) if idx > len then return context_iterate(ctx, 3) end len = len - idx + 1 end ctx.params = copy_table_reduced(tbl, idx, len) return context_iterate(ctx, 3) end

-- Syntax: #invoke:params|backpurging|start offset|length|pipe to library.backpurging = function(ctx) local last = tonumber(ctx.pipe[1]) if last == nil then error(ctx.luaname ..		', ‘backpurging’: Start offset must be a number', 0) end local len = tonumber(ctx.pipe[2]) if len == nil then error(ctx.luaname ..		', ‘backpurging’: Length must be a number', 0) end local idx local tbl = ctx.params if len > 0 then idx = last - len + 1 else for key in pairs(tbl) do			if type(key) == 'number' and (idx == nil or				key < idx) then idx = key end end if idx == nil then return context_iterate(ctx, 3) end idx = idx - len if last < idx then return context_iterate(ctx, 3) end len = last - idx + 1 end ctx.params = copy_table_reduced(ctx.params, idx, len) return context_iterate(ctx, 3) end

-- Syntax: #invoke:params|rotating|pipe to library.rotating = function(ctx) local tbl = ctx.params local numericals = {} local nmax = 0 for key, val in pairs(tbl) do		if type(key) == 'number' then numericals[key] = val tbl[key] = nil if key > nmax then nmax = key end end end for key, val in pairs(numericals) do tbl[nmax - key + 1] = val end return context_iterate(ctx, 1) end

-- Syntax: #invoke:params|pivoting|pipe to -- library.pivoting = function(ctx)	local tbl = ctx.params	local shift = --

-- Syntax: #invoke:params|mirroring|pipe to -- library.mirroring = function(ctx)	local tbl = ctx.params	local numericals = {}	local nmax	local nmin	for key, val in pairs(tbl) do		if type(key) == 'number' then			numericals[key] = val			tbl[key] = nil			if nmax == nil then				nmax = key				nmin = key			elseif key > nmax then nmax = key			elseif key < nmin then nmin = key end		end	end	for key, val in pairs(numericals) do tbl[nmax + nmin - key] = val end	return context_iterate(ctx, 1) end --

-- Syntax: #invoke:params|swapping|pipe to -- library.swapping = function(ctx)	local tbl = ctx.params	local cache = {}	local nsize = 0	local tmp	for key in pairs(tbl) do		if type(key) == 'number' then			nsize = nsize + 1			cache[nsize] = key		end	end	table.sort(cache)	for idx = math.floor(nsize / 2), 1, -1 do		tmp = tbl[cache[idx] ]		tbl[cache[idx] ] = tbl[cache[nsize - idx + 1] ]		tbl[cache[nsize - idx + 1] ] = tmp	end	return context_iterate(ctx, 1) end --

-- Syntax: #invoke:params|sorting_sequential_values|[criterion]|pipe to library.sorting_sequential_values = function(ctx) local sortfn if ctx.pipe[1] ~= nil then sortfn = sortfunctions[ctx.pipe[1]] end if sortfn then table.sort(ctx.params, sortfn) else table.sort(ctx.params) end -- i.e. either `false` or `nil` if sortfn == nil then return context_iterate(ctx, 1) end return context_iterate(ctx, 2) end

-- Syntax: #invoke:params|inserting|position|how many|...|pipe to -- library.inserting = function(ctx)	-- NOTE: `ctx.params` might be the original metatable! As a modifier,	-- this function MUST create a copy of it before returning	local idx = tonumber(ctx.pipe[1])	if idx == nil then error(ctx.luaname ..		', ‘inserting’: Position must be a number', 0) end	local len = tonumber(ctx.pipe[2])	if len == nil or len < 1 then error(ctx.luaname ..		', ‘inserting’: The amount must be a number greater than zero', 0) end	local opts = ctx.pipe	local tbl = copy_table_expanded(ctx.params, idx, len)	for key = idx, idx + len - 1 do tbl[key] = opts[key - idx + 3] end	ctx.params = tbl	return context_iterate(ctx, len + 3) end --

-- Syntax: #invoke:params|imposing|name|value|pipe to library.imposing = function(ctx) if ctx.pipe[1] == nil then error(ctx.luaname ..		', ‘imposing’: Missing parameter name to impose', 0) end local key = ctx.pipe[1]:match'^%s*(.-)%s*$' ctx.params[tonumber(key) or key] = ctx.pipe[2] return context_iterate(ctx, 3) end

-- Syntax: #invoke:params|discarding|name|[how many]|pipe to library.discarding = function(ctx) if ctx.pipe[1] == nil then error(ctx.luaname ..		', ‘discarding’: Missing parameter name to discard', 0) end local key = ctx.pipe[1] local len = tonumber(ctx.pipe[2]) if len == nil then ctx.params[tonumber(key) or key:match'^%s*(.-)%s*$'] = nil return context_iterate(ctx, 2) end key = tonumber(key) if key == nil then error(ctx.luaname ..		', ‘discarding’: A range was provided, but the initial parameter name is not numerical', 0) end if len < 1 then error(ctx.luaname ..		', ‘discarding’: A range can only be a number greater than zero', 0) end for idx = key, key + len - 1 do ctx.params[idx] = nil end return context_iterate(ctx, 3) end

-- Syntax: #invoke:params|with_name_matching|pattern 1|[plain flag 1]|[or] --           |[pattern 2]|[plain flag 2]|[or]|[...]|[pattern N]|[plain flag --           N]|pipe to library.with_name_matching = function(ctx) local tbl = ctx.params local patterns = {} local argc = parse_pattern_args(ctx, patterns, 'with_name_matching') local nomatch for key in pairs(tbl) do		nomatch = true for _, ptn in ipairs(patterns) do			if not ptn[3] then if string.find(key, ptn[1], 1, ptn[2]) then nomatch = false break end elseif key == ptn[1] then nomatch = false break end end if nomatch then tbl[key] = nil end end return context_iterate(ctx, argc) end

-- Syntax: #invoke:params|with_name_not_matching|pattern 1|[plain flag 1] --           |[and]|[pattern 2]|[plain flag 2]|[and]|[...]|[pattern N]|[plain --           flag N]|pipe to library.with_name_not_matching = function(ctx) local tbl = ctx.params local patterns = {} local argc = parse_pattern_args(ctx, patterns,		'with_name_not_matching') local yesmatch for key in pairs(tbl) do		yesmatch = true for _, ptn in ipairs(patterns) do			if ptn[3] then if key ~= ptn[1] then yesmatch = false break end elseif not string.find(key, ptn[1], 1, ptn[2]) then yesmatch = false break end end if yesmatch then tbl[key] = nil end end return context_iterate(ctx, argc) end

-- Syntax: #invoke:params|with_value_matching|pattern 1|[plain flag 1]|[or] --           |[pattern 2]|[plain flag 2]|[or]|[...]|[pattern N]|[plain flag --           N]|pipe to library.with_value_matching = function(ctx) local tbl = ctx.params local patterns = {} local argc = parse_pattern_args(ctx, patterns, 'with_value_matching') local nomatch for key, val in pairs(tbl) do		nomatch = true for _, ptn in ipairs(patterns) do			if ptn[3] then if val == ptn[1] then nomatch = false break end elseif string.find(val, ptn[1], 1, ptn[2]) then nomatch = false break end end if nomatch then tbl[key] = nil end end return context_iterate(ctx, argc) end

-- Syntax: #invoke:params|with_value_not_matching|pattern 1|[plain flag 1] --           |[and]|[pattern 2]|[plain flag 2]|[and]|[...]|[pattern N]|[plain --           flag N]|pipe to library.with_value_not_matching = function(ctx) local tbl = ctx.params local patterns = {} local argc = parse_pattern_args(ctx, patterns,		'with_value_not_matching') local yesmatch for key, val in pairs(tbl) do		yesmatch = true for _, ptn in ipairs(patterns) do			if ptn[3] then if val ~= ptn[1] then yesmatch = false break end elseif not string.find(val, ptn[1], 1, ptn[2]) then yesmatch = false break end end if yesmatch then tbl[key] = nil end end return context_iterate(ctx, argc) end

-- Syntax: #invoke:params|trimming_values|pipe to library.trimming_values = function(ctx) local tbl = ctx.params for key, val in pairs(tbl) do tbl[key] = val:match'^%s*(.-)%s*$' end return context_iterate(ctx, 1) end

-- Syntax: #invoke:params|mapping_by_calling|template name|[call --           style]|[let]|[...][number of additional parameters]|[parameter --           1]|[parameter 2]|[...]|[parameter N]|pipe to library.mapping_by_calling = function(ctx) local opts = ctx.pipe local tname if opts[1] ~= nil then tname = opts[1]:match'^%s*(.*%S)' end if tname == nil then error(ctx.luaname ..		', ‘mapping_by_calling’: No template name was provided', 0) end local margs = {} local argc, looptype, karg, varg = parse_child_args(margs, opts, 1,		mapping_styles.values_only) local model = { title = tname, args = margs } map_values(ctx.params, margs, karg, varg, looptype, function		return ctx.frame:expandTemplate(model)	end) return context_iterate(ctx, argc) end

-- Syntax: #invoke:params|mapping_by_invoking|module name|function --           name|[call style]|[let]|[...]|[number of additional --           arguments]|[argument 1]|[argument 2]|[...]|[argument N]|pipe to library.mapping_by_invoking = function(ctx) local opts = ctx.pipe local mname local fname if opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end if mname == nil then error(ctx.luaname ..		', ‘mapping_by_invoking’: No module name was provided', 0) end if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end if fname == nil then error(ctx.luaname ..		', ‘mapping_by_invoking’: No function name was provided', 0) end local margs = {} local argc, looptype, karg, varg = parse_child_args(margs, opts, 2,		mapping_styles.values_only) local model = { title = 'Module:' .. mname, args = margs } local mfunc = require(model.title)[fname] if mfunc == nil then error(ctx.luaname ..		', ‘mapping_by_invoking’: The function ‘' .. fname ..		'’ does not exist', 0) end map_values(ctx.params, margs, karg, varg, looptype, function		return mfunc(ctx.frame:newChild(model))	end) return context_iterate(ctx, argc) end

-- Syntax: #invoke:params|mapping_by_magic|parser function|[call --           style]|[let]|[...][number of additional arguments]|[argument --           1]|[argument 2]|[...]|[argument N]|pipe to library.mapping_by_magic = function(ctx) local opts = ctx.pipe local magic if opts[1] ~= nil then magic = opts[1]:match'^%s*(.*%S)' end if magic == nil then error(ctx.luaname ..		', ‘mapping_by_magic’: No parser function was provided', 0) end local margs = {} local argc, looptype, karg, varg = parse_child_args(margs, opts, 1,		mapping_styles.values_only) map_values(ctx.params, margs, karg, varg, looptype, function		return ctx.frame:callParserFunction(magic, margs)	end) return context_iterate(ctx, argc) end

-- Syntax: #invoke:params|renaming_by_calling|template name|[call --           style]|[let]|[...][number of additional parameters]|[parameter --           1]|[parameter 2]|[...]|[parameter N]|pipe to library.renaming_by_calling = function(ctx) local opts = ctx.pipe local tname if opts[1] ~= nil then tname = opts[1]:match'^%s*(.*%S)' end if tname == nil then error(ctx.luaname ..		', ‘renaming_by_calling’: No template name was provided', 0) end local rargs = {} local argc, looptype, karg, varg = parse_child_args(rargs, opts, 1,		mapping_styles.names_only) local model = { title = tname, args = rargs } map_names(ctx.params, rargs, karg, varg, looptype, function		return ctx.frame:expandTemplate(model)	end) return context_iterate(ctx, argc) end

-- Syntax: #invoke:params|renaming_by_invoking|module name|function --           name|[call style]|[let]|[...]|[number of additional --           arguments]|[argument 1]|[argument 2]|[...]|[argument N]|pipe to library.renaming_by_invoking = function(ctx) local opts = ctx.pipe local mname local fname if opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end if mname == nil then error(ctx.luaname ..		', ‘renaming_by_invoking’: No module name was provided', 0) end if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end if fname == nil then error(ctx.luaname ..		', ‘renaming_by_invoking’: No function name was provided', 0) end local rargs = {} local argc, looptype, karg, varg = parse_child_args(rargs, opts, 2,		mapping_styles.names_only) local model = { title = 'Module:' .. mname, args = rargs } local mfunc = require(model.title)[fname] if mfunc == nil then error(ctx.luaname ..		', ‘renaming_by_invoking’: The function ‘' .. fname ..		'’ does not exist', 0) end map_names(ctx.params, rargs, karg, varg, looptype, function		return mfunc(ctx.frame:newChild(model))	end) return context_iterate(ctx, argc) end

-- Syntax: #invoke:params|renaming_by_magic|parser function|[call --           style]|[let]|[...][number of additional arguments]|[argument --           1]|[argument 2]|[...]|[argument N]|pipe to library.renaming_by_magic = function(ctx) local opts = ctx.pipe local magic if opts[1] ~= nil then magic = opts[1]:match'^%s*(.*%S)' end if magic == nil then error(ctx.luaname ..		', ‘renaming_by_magic’: No parser function was provided', 0) end local rargs = {} local argc, looptype, karg, varg = parse_child_args(rargs, 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

-- Functions -- -

-- Syntax: #invoke:params|count library.count = function(ctx) -- NOTE: `ctx.pipe` and `ctx.params` might be the original metatables! local retval = 0 for _ in ctx.iterfunc(ctx.params) do retval = retval + 1 end if ctx.subset == -1 then retval = retval - #ctx.params end ctx.text = retval return false end

-- Syntax: #invoke:args|concat_and_call|template name|[prepend 1]|[prepend 2] --           |[...]|[item n]|[named item 1=value 1]|[...]|[named item n=value --           n]|[...] library.concat_and_call = function(ctx) -- NOTE: `ctx.params` might be the original metatable! local opts = ctx.pipe local tname if opts[1] ~= nil then tname = opts[1]:match'^%s*(.*%S)' end if tname == nil then error(ctx.luaname ..		', ‘concat_and_call’: No template name was provided', 0) end remove_numerical_keys(opts, 1, 1) ctx.text = ctx.frame:expandTemplate{ title = tname, args = concat_params(ctx) }	return false end

-- Syntax: #invoke:args|concat_and_invoke|module name|function name|[prepend --           1]|[prepend 2]|[...]|[item n]|[named item 1=value 1]|[...]|[named --           item n=value n]|[...] library.concat_and_invoke = function(ctx) -- NOTE: `ctx.params` might be the original metatable! local opts = ctx.pipe local mname local fname if opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end if mname == nil then error(ctx.luaname ..		', ‘concat_and_invoke’: No module name was provided', 0) end if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end if fname == nil then error(ctx.luaname ..		', ‘concat_and_invoke’: No function name was provided', 0) end remove_numerical_keys(opts, 1, 2) local mfunc = require('Module:' .. mname)[fname] if mfunc == nil then error(ctx.luaname ..		', ‘concat_and_invoke’: The function ‘' .. fname ..		'’ does not exist', 0) end ctx.text = mfunc(ctx.frame:newChild{		title = 'Module:' .. fname,		args = concat_params(ctx)	}) return false end

-- Syntax: #invoke:args|concat_and_magic|parser function|[prepend 1]|[prepend --           2]|[...]|[item n]|[named item 1=value 1]|[...]|[named item n= --            value n]|[...] library.concat_and_magic = function(ctx) -- NOTE: `ctx.params` might be the original metatable! local opts = ctx.pipe local magic if opts[1] ~= nil then magic = opts[1]:match'^%s*(.*%S)' end if magic == nil then error(ctx.luaname ..		', ‘concat_and_magic’: No parser function was provided', 0) end remove_numerical_keys(opts, 1, 1) ctx.text = ctx.frame:callParserFunction(magic, concat_params(ctx)) return false end

-- Syntax: #invoke:params|value_of|parameter name library.value_of = function(ctx) -- NOTE: `ctx.pipe` and `ctx.params` might be the original metatables! local opts = ctx.pipe local kstr if opts[1] ~= nil then kstr = opts[1]:match'^%s*(.*%S)' end if kstr == nil then error(ctx.luaname ..		', ‘value_of’: No parameter name was provided', 0) end local knum = tonumber(kstr) local len = #ctx.params local val = ctx.params[knum or kstr] if val ~= nil and (		ctx.subset ~= -1 or knum == nil or knum > len or knum < 1	) and (		ctx.subset ~= 1 or (knum ~= nil and knum <= len and knum > 0)	) then ctx.text = (ctx.header or '') .. val .. (ctx.footer or '') return false end ctx.text = ctx.ifngiven or '' return false end

-- Syntax: #invoke:params|list library.list = function(ctx) -- NOTE: `ctx.pipe` might be the original metatable! local kvs = ctx.pairsep or '' local pps = ctx.itersep or '' local ret = {} local nss = 0 flush_params(		ctx,		function(key, val)			ret[nss + 1] = pps			ret[nss + 2] = key			ret[nss + 3] = kvs			ret[nss + 4] = val			nss = nss + 4		end	) if nss > 0 then if nss > 4 and ctx.lastsep ~= nil then ret[nss - 3] = ctx.lastsep end ret[1] = ctx.header or '' if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end ctx.text = table.concat(ret) return false end ctx.text = ctx.ifngiven or '' return false end

-- Syntax: #invoke:params|list_values library.list_values = function(ctx) -- NOTE: `ctx.pipe` might be the original metatable! local pps = ctx.itersep or '' local ret = {} local nss = 0 flush_params(		ctx,		function(key, val)			ret[nss + 1] = pps			ret[nss + 2] = val			nss = nss + 2		end	) if nss > 0 then if nss > 2 and ctx.lastsep ~= nil then ret[nss - 1] = ctx.lastsep end ret[1] = ctx.header or '' if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end ctx.text = table.concat(ret) return false end ctx.text = ctx.ifngiven or '' return false end

-- Syntax: #invoke:params|for_each|wikitext library.for_each = function(ctx) -- NOTE: `ctx.pipe` might be the original metatable! local txt = ctx.pipe[1] or '' local pps = ctx.itersep or '' local ret = {} local nss = 0 flush_params(		ctx,		function(key, val)			ret[nss + 1] = pps			ret[nss + 2] = txt:gsub('%$#', key):gsub('%$@', val)			nss = nss + 2		end	) if nss > 0 then if nss > 2 and ctx.lastsep ~= nil then ret[nss - 1] = ctx.lastsep end ret[1] = ctx.header or '' if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end ctx.text = table.concat(ret) return false end ctx.text = ctx.ifngiven or '' return false end

-- Syntax: #invoke:params|call_for_each|template name|[append 1]|[append 2] --           |[...]|[append n]|[named param 1=value 1]|[...]|[named param --           n=value n]|[...] library.call_for_each = function(ctx) local opts = ctx.pipe local tname if opts[1] ~= nil then tname = opts[1]:match'^%s*(.*%S)' end if tname == nil then error(ctx.luaname ..		', ‘call_for_each’: No template name was provided', 0) end local model = { title = tname, args = opts } local ccs = ctx.itersep or '' local ret = {} local nss = 0 table.insert(opts, 1, true) flush_params(		ctx,		function(key, val)			opts[1] = key			opts[2] = val			ret[nss + 1] = ccs			ret[nss + 2] = ctx.frame:expandTemplate(model)			nss = nss + 2		end	) if nss > 0 then if nss > 2 and ctx.lastsep ~= nil then ret[nss - 1] = ctx.lastsep end ret[1] = ctx.header or '' if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end ctx.text = table.concat(ret) return false end ctx.text = ctx.ifngiven or '' return false end

-- Syntax: #invoke:params|invoke_for_each|module name|module function|[append --           1]|[append 2]|[...]|[append n]|[named param 1=value 1]|[...] --           |[named param n=value n]|[...] library.invoke_for_each = function(ctx) local opts = ctx.pipe local mname local fname if opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end if mname == nil then error(ctx.luaname ..		', ‘invoke_for_each’: No module name was provided', 0) end if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end if fname == nil then error(ctx.luaname ..		', ‘invoke_for_each’: No function name was provided', 0) end local model = { title = 'Module:' .. mname, args = opts } local mfunc = require(model.title)[fname] local ccs = ctx.itersep or '' local ret = {} local nss = 0 flush_params(		ctx,		function(key, val)			opts[1] = key			opts[2] = val			ret[nss + 1] = ccs			ret[nss + 2] = mfunc(ctx.frame:newChild(model))			nss = nss + 2		end	) if nss > 0 then if nss > 2 and ctx.lastsep ~= nil then ret[nss - 1] = ctx.lastsep end ret[1] = ctx.header or '' if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end ctx.text = table.concat(ret) return false end ctx.text = ctx.ifngiven or '' return false end

-- Syntax: #invoke:params|magic_for_each|parser function|[append 1]|[append 2] --           |[...]|[append n]|[named param 1=value 1]|[...]|[named param --           n=value n]|[...] library.magic_for_each = function(ctx) local opts = ctx.pipe local magic if opts[1] ~= nil then magic = opts[1]:match'^%s*(.*%S)' end if magic == nil then error(ctx.luaname ..		', ‘magic_for_each’: No parser function was provided', 0) end local ccs = ctx.itersep or '' local ret = {} local nss = 0 table.insert(opts, 1, true) flush_params(		ctx,		function(key, val)			opts[1] = key			opts[2] = val			ret[nss + 1] = ccs			ret[nss + 2] = ctx.frame:callParserFunction(magic, opts)			nss = nss + 2		end	) if nss > 0 then if nss > 2 and ctx.lastsep ~= nil then ret[nss - 1] = ctx.lastsep end ret[1] = ctx.header or '' if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end ctx.text = table.concat(ret) return false end ctx.text = ctx.ifngiven or '' return false end

-- Syntax: #invoke:params|call_for_each_value|template name|[append 1]|[append --           2]|[...]|[append n]|[named param 1=value 1]|[...]|[named param --           n=value n]|[...] library.call_for_each_value = function(ctx) local opts = ctx.pipe local tname if opts[1] ~= nil then tname = opts[1]:match'^%s*(.*%S)' end if tname == nil then error(ctx.luaname ..		', ‘call_for_each_value’: No template name was provided', 0) end local model = { title = tname, args = opts } local ccs = ctx.itersep or '' local ret = {} local nss = 0 flush_params(		ctx,		function(key, val)			opts[1] = val			ret[nss + 1] = ccs			ret[nss + 2] = ctx.frame:expandTemplate(model)			nss = nss + 2		end	) if nss > 0 then if nss > 2 and ctx.lastsep ~= nil then ret[nss - 1] = ctx.lastsep end ret[1] = ctx.header or '' if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end ctx.text = table.concat(ret) return false end ctx.text = ctx.ifngiven or '' return false end

-- Syntax: #invoke:params|invoke_for_each_value|module name|[append 1]|[append --           2]|[...]|[append n]|[named param 1=value 1]|[...]|[named param --           n=value n]|[...] library.invoke_for_each_value = function(ctx) local opts = ctx.pipe local mname local fname if opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end if mname == nil then error(ctx.luaname ..		', ‘invoke_for_each_value’: No module name was provided', 0) end if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end if fname == nil then error(ctx.luaname ..		', ‘invoke_for_each_value’: No function name was provided', 0) end local model = { title = 'Module:' .. mname, args = opts } local mfunc = require(model.title)[fname] local ccs = ctx.itersep or '' local ret = {} local nss = 0 remove_numerical_keys(opts, 1, 1) flush_params(		ctx,		function(key, val)			opts[1] = val			ret[nss + 1] = ccs			ret[nss + 2] = mfunc(ctx.frame:newChild(model))			nss = nss + 2		end	) if nss > 0 then if nss > 2 and ctx.lastsep ~= nil then ret[nss - 1] = ctx.lastsep end ret[1] = ctx.header or '' if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end ctx.text = table.concat(ret) return false end ctx.text = ctx.ifngiven or '' return false end

-- Syntax: #invoke:params|magic_for_each_value|parser function|[append 1] --           |[append 2]|[...]|[append n]|[named param 1=value 1]|[...]|[named --           param n=value n]|[...] library.magic_for_each_value = function(ctx) local opts = ctx.pipe local magic if opts[1] ~= nil then magic = opts[1]:match'^%s*(.*%S)' end if magic == nil then error(ctx.luaname ..		', ‘magic_for_each_value’: No parser function was provided', 0) end local ccs = ctx.itersep or '' local ret = {} local nss = 0 flush_params(		ctx,		function(key, val)			opts[1] = val			ret[nss + 1] = ccs			ret[nss + 2] = ctx.frame:callParserFunction(magic, opts)			nss = nss + 2		end	) if nss > 0 then if nss > 2 and ctx.lastsep ~= nil then ret[nss - 1] = ctx.lastsep end ret[1] = ctx.header or '' if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end ctx.text = table.concat(ret) return false end ctx.text = ctx.ifngiven or '' return false end

-- Syntax: #invoke:params|call_for_each_group|template name|[append 1]|[append --           2]|[...]|[append n]|[named param 1=value 1]|[...]|[named param --           n=value n]|[...] library.call_for_each_group = function(ctx) -- NOTE: `ctx.pipe` and `ctx.params` might be the original metatables! local opts = ctx.pipe local tmp if opts[1] ~= nil then tmp = opts[1]:match'^%s*(.*%S)' end if tmp == nil then error(ctx.luaname ..		', ‘call_for_each_group’: No template name was provided', 0) end local model = { title = tmp } local ccs = ctx.itersep or '' local nss = 0 local prefix local gid local groups = {} 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 for key, val in pairs(ctx.params) do		prefix, gid = tostring(key):match'^%s*(.-)%s*(%-?%d*)%s*$' gid = tonumber(gid) or '' 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 ctx.params = groups flush_params(		ctx,		function(gid, group)			for key, val in pairs(opts) do group[key] = val end			group[0] = gid			model.args = group			ret[nss + 1] = ccs			ret[nss + 2] = ctx.frame:expandTemplate(model)			nss = nss + 2		end	) if nss > 0 then if nss > 2 and ctx.lastsep ~= nil then ret[nss - 1] = ctx.lastsep end ret[1] = ctx.header or '' if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end ctx.text = table.concat(ret) return false end ctx.text = ctx.ifngiven or '' return false end

---                                       ---	---     PUBLIC ENVIRONMENT                 --- ---   ________________________________    ---	---                                        ---

-- First-position-only modifiers -- ---

-- Syntax: #invoke:params|new|pipe to -- static_iface.new = function(frame)	local ctx = context_new	ctx.frame = frame:getParent	ctx.pipe = copy_or_ref_table(frame.args, false)	ctx.params = {}	main_loop(ctx, context_iterate(ctx, 1))	return ctx.text end --

-- First-position-only functions -- ---

-- Syntax: #invoke:params|self static_iface.self = function(frame) return frame:getParent:getTitle end

-- Public metatable of functions -- ---

return setmetatable(static_iface, {	__index = function(iface, _fname_)		local ctx = context_new		local fname = _fname_:match'^%s*(.*%S)'		if fname == nil then error(ctx.luaname .. ': You must specify a function to call', 0) end		if library[fname] == nil then error(ctx.luaname .. ': The function ‘' .. fname .. '’ does not exist', 0) end		return function(frame)			local func = library[fname]			local refpipe = {				count = true,				value_of = true,				list = true,				list_values = true,				for_each = true,				call_for_each_group = true			}			local refparams = {				--inserting = true,				count = true,				concat_and_call = true,				concat_and_invoke = true,				concat_and_magic = true,				value_of = true,				call_for_each_group = true			}			ctx.frame = frame:getParent			ctx.pipe = copy_or_ref_table(frame.args, refpipe[fname])			ctx.params = copy_or_ref_table(ctx.frame.args, refparams[fname])			main_loop(ctx, func)			return ctx.text		end	end })