Module:Params: Difference between revisions

From All Skies Encyclopaedia
imported>Grufo
(Add support for the `just` flag to the `with_*_matching` class of modifiers)
imported>Grufo
(The new modifier ‘providing’ has been added to the module)
 
(40 intermediate revisions by the same user not shown)
Line 5: Line 5:





-- Special user-given keywords (functions and modifiers MUST avoid these names)
--[[ Abstract utilities ]]--
----------------------------


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


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


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


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


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


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


-- Move a key from a table to another, but only if under a different name and
-- always parsing numerical strings as numbers
local function steal_if_renamed (val, src, skey, dest, dkey)
local realkey = tonumber(dkey) or dkey:match'^%s*(.-)%s*$'
if skey ~= realkey then
dest[realkey] = val
src[skey] = nil
end
end



--[[ Public strings ]]--
------------------------


-- Special match keywords (functions and modifiers MUST avoid these names)
local mkeywords = {
local mkeywords = {
['or'] = 0,
['or'] = 0,
--['pattern'] = 1,
pattern = 1,
['plain'] = 2,
plain = 2,
['just'] = 3
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
-- Memory slots (functions and modifiers MUST avoid these names)
local memoryslots = {
local memoryslots = {
i = 'itersep',
i = 'itersep',
Line 25: Line 176:




-- Functions and modifiers MUST avoid these names too: `let`
-- Directive that can only be invoked as first arguments

local firstposonly = {

new = 'modifier',

self = 'function',
--[[ Module's private environment ]]--
--------------------------------------


-- Functions listed here declare that they don't need the `frame.args`
-- metatable to be copied into a regular table; if they are modifiers they also
-- guarantee that they will make available their own (modified) copy
local refpipe = {
count = true,
value_of = true,
list = true,
list_values = true,
for_each = true,
call_for_each_group = true
}
}


-- Functions listed here declare that they don't need the
-- `frame:getParent().args` metatable to be copied into a regular table; if
-- they are modifiers they also guarantee that they will make available their
-- own (modified) copy
local refparams = {
--inserting = true,
grouping_by_calling = true,
count = true,
concat_and_call = true,
concat_and_invoke = true,
concat_and_magic = true,
value_of = true,
call_for_each_group = true
}


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




Line 36: Line 223:




-- Functions that can only be invoked in first position
-- Return a copy or a reference to a table
local static_iface = {}
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




-- Prepare the context
-- Create a new context
local function context_new(frame)
local function context_new ()
local ctx = {}
local ctx = {}
ctx.luaname = 'Module:Params' --[[ or `frame:getTitle()` ]]--
ctx.luaname = 'Module:Params' --[[ or `frame:getTitle()` ]]--
ctx.iterfunc = pairs
ctx.iterfunc = pairs
ctx.frame = frame:getParent()
ctx.sorttype = 0
ctx.firstposonly = static_iface
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(ctx.luaname ..
error(ctx.luaname .. ': You must specify a function to call', 0)
': 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(ctx.luaname ..
error(ctx.luaname .. ': The function ‘' .. nextfn ..
': The function ‘' .. nextfn .. '’ does not exist', 0)
else error(ctx.luaname .. ': 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_numerical_keys(ctx.pipe, 1, n_forward)
for idx = n_forward, 1, -1 do table.remove(ctx.pipe, idx) end
return library[nextfn](ctx)
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)
local shift = table.maxn(ctx.pipe)
local newargs = {}
if ctx.subset == 1 then
-- 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




-- Parse user arguments of type `...|[let]|[...][number of additional
-- Helper function for `string.gsub()` (for managing zero-padded numbers)
-- parameters]|[parameter 1]|[parameter 2]|[...]`
function zero_padded(str)
local function parse_child_args (src, start_from, append_after)
return ("%03d%s"):format(#str, str)
local names
local tmp
local dest = {}
local pin = start_from
if src[pin] ~= nil and src[pin]:match'^%s*let%s*$' then
names = {}
repeat
tmp = src[pin + 1] or ''
names[tonumber(tmp) or tmp:match'^%s*(.-)%s*$' or ''] =
src[pin + 2]
pin = pin + 3
until src[pin] == nil or not src[pin]:match'^%s*let%s*$'
end
tmp = tonumber(src[pin])
if tmp ~= nil then
if tmp < 0 then tmp = -1 end
local shf = append_after - pin
for idx = pin + 1, pin + tmp do dest[idx + shf] = src[idx] end
pin = pin + tmp + 1
end
if names ~= nil then
for key, val in pairs(names) do dest[key] = val end
end
return dest, pin
end
end




-- Parse the arguments of some of the `mapping_*` and `renaming_*` class of
-- Helper function for `table.sort()` (for natural sorting)
-- modifiers
function natural_sort(var1, var2)
local function parse_callback_args (src, n_skip, default_style)
return tostring(var1):gsub("%d+", zero_padded) <
local style
tostring(var2):gsub("%d+", zero_padded)
local shf
end
local tmp = src[n_skip + 1]

if tmp ~= nil then style = mapping_styles[tmp:match'^%s*(.-)%s*$'] end

if style == nil then
-- Flush the parameters by calling a custom function for each value (after this
style = default_style
-- function has been invoked `ctx.params` will no longer be usable)
shf = n_skip - 1
local function flush_params(ctx, fn)
local tbl = ctx.params
else shf = n_skip end
local n_exist = style[3]
if ctx.subset == 1 then
local karg = style[4]
for key, val in ipairs(tbl) do fn(key, val) end
local varg = style[5]
return
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]
if ctx.subset == -1 then
if tmp > -1 then
for key, val in ipairs(tbl) do tbl[key] = nil end
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
end
local dest, nargs = parse_child_args(src, style[2] + shf, n_exist)
if ctx.dosort then
tmp = style[1]
local nums = {}
if (tmp == 3 or tmp == 2) and dest[karg] ~= nil then
local words = {}
tmp = tmp - 2 end
local nlen = 0
if (tmp == 3 or tmp == 1) and dest[varg] ~= nil then
local wlen = 0
for key, val in pairs(tbl) do
tmp = tmp - 1 end
return dest, nargs, tmp, karg, varg
if type(key) == 'number' then
end
nlen = nlen + 1
nums[nlen] = key
else
wlen = wlen + 1
words[wlen] = key
end



end
-- Parse the arguments of some of the `mapping_*` and `renaming_*` class of
table.sort(nums)
-- modifiers
table.sort(words, natural_sort)
local function parse_replace_args (opts, fname)
for idx = 1, nlen do fn(nums[idx], tbl[nums[idx]]) end
if opts[1] == nil then error(ctx.luaname ..
for idx = 1, wlen do fn(words[idx], tbl[words[idx]]) end
', ‘' .. fname .. '’: No pattern string was given', 0) end
return
if opts[2] == nil then error(ctx.luaname ..
end
', ‘' .. fname .. '’: No replacement string was given', 0) end
if ctx.subset ~= -1 then
local ptn = opts[1]
for key, val in ipairs(tbl) do
local repl = opts[2]
fn(key, val)
local argc = 3
tbl[key] = nil
local nmax = tonumber(opts[3])
end
if nmax ~= nil or (opts[3] or ''):match'^%s*$' ~= nil then argc = 4 end
end
local flg = opts[argc]
for key, val in pairs(tbl) do fn(key, val) end
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 parse_match_args(opts, ptns, fname)
local function parse_pattern_args (ctx, 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(ctx.pipe) do
if state == 0 then
if state == 0 then
nptns = nptns + 1
nptns = nptns + 1
Line 197: Line 379:
cnt = cnt + 1
cnt = cnt + 1
end
end
if state == 0 then error(ctx.luaname .. ', ‘' .. fname .. '’: No pattern was given', 0) end
if state == 0 then error(ctx.luaname .. ', ‘' .. fname ..
'’: No pattern was given', 0) end
return cnt
return ptns, cnt
end
end




-- Map parameters' values using a custom callback and a referenced table
-- Remove numerical arguments (not necessarily sequential ones)
local value_maps = {
function purge_args(tbl, offset, length)
[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 numerical
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


-- 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
else
if ctx.subset == -1 then
pshift = 0
for key, val in ipairs(tbl) do tbl[key] = nil end
nshift = length
end
for key, val in pairs(tbl) do
if type(key) == 'number' then retval[key + size] = val
else retval[key] = val end
end
end
end
for key, val in pairs(tbl) do
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






--[[ Library's modifiers ]]--
--[[ Modifiers ]]--
--------------------------------
-----------------------------




-- See iface.sequential()
-- Syntax: #invoke:params|sequential|pipe to
library.sequential = function(ctx)
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.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
if ctx.sorttype > 0 then error(ctx.luaname ..
': The ‘all_sorted’ and ‘reassorted’ directives are redundant when followed by ‘sequential’', 0) end
ctx.iterfunc = ipairs
ctx.iterfunc = ipairs
ctx.subset = 1
ctx.subset = 1
Line 251: Line 562:




-- See iface['non-sequential']()
-- Syntax: #invoke:params|non-sequential|pipe to
library['non-sequential'] = function(ctx)
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
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.iterfunc = pairs
ctx.subset = -1
ctx.subset = -1
Line 260: Line 572:




-- See iface.all_sorted()
-- Syntax: #invoke:params|all_sorted|pipe to
library.all_sorted = function(ctx)
library.all_sorted = function (ctx)
if ctx.subset == 1 then error(ctx.luaname .. ': The ‘all_sorted’ directive is redundant after ‘sequential’', 0) end
if ctx.subset == 1 then error(ctx.luaname ..
': The ‘all_sorted’ directive is redundant after ‘sequential’', 0) end
ctx.dosort = true
if ctx.sorttype == 2 then error(ctx.luaname ..
': The two directives ‘reassorted’ and ‘sequential’ are in contradiction with each other', 0) end
ctx.sorttype = 1
return context_iterate(ctx, 1)
return context_iterate(ctx, 1)
end
end




-- Syntax: #invoke:params|reassorted|pipe to
-- See iface.setting()
library.setting = function(ctx)
library.reassorted = function (ctx)
if ctx.subset == 1 then error(ctx.luaname ..
': The ‘reassorted’ directive is redundant after ‘sequential’', 0) end
if ctx.sorttype == 1 then error(ctx.luaname ..
': The two directives ‘sequential’ and ‘reassorted’ are in contradiction with each other', 0) end
ctx.sorttype = 2
return context_iterate(ctx, 1)
end


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




-- See iface.squeezing()
-- 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 321: Line 649:




-- Syntax: #invoke:params|filling_the_gaps|pipe to
-- See iface.cutting()
library.cutting = function(ctx)
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])
local lcut = tonumber(ctx.pipe[1])
if lcut == nil then error(ctx.luaname .. ', ‘cutting’: Left cut must be a number', 0) end
if lcut == nil then error(ctx.luaname ..
', ‘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(ctx.luaname .. ', ‘cutting’: Right cut must be a number', 0) end
if rcut == nil then error(ctx.luaname ..
', ‘cutting’: Right cut must be a number', 0) end
local tbl = ctx.params
local tbl = ctx.params
local len = #tbl
local len = #tbl
Line 343: Line 718:
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
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 357: Line 729:




-- 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(ctx.luaname .. ', ‘cropping’: Left crop must be a number', 0) end
if lcut == nil then error(ctx.luaname ..
', ‘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(ctx.luaname .. ', ‘cropping’: Right crop must be a number', 0) end
if rcut == nil then error(ctx.luaname ..
', ‘cropping’: Right crop must be a number', 0) end
local tbl = ctx.params
local tbl = ctx.params
local nmin
local nmin
Line 372: Line 746:
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 385: Line 758:
end
end
elseif lcut + rcut > 0 then
elseif lcut + rcut > 0 then
for idx = nmax - rcut + 1, nmax, 1 do tbl[idx] = nil end
for idx = nmax - rcut + 1, nmax do tbl[idx] = nil end
for idx = nmin, nmin + lcut - 1, 1 do tbl[idx] = nil end
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 400: Line 773:




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




-- See iface.backpurging()
-- 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(ctx.luaname .. ', ‘backpurging’: Start offset must be a number', 0) end
if last == nil then error(ctx.luaname ..
', ‘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(ctx.luaname ..
local first
', ‘backpurging’: Length must be a number', 0) end
local idx
local tbl = ctx.params
local tbl = ctx.params
if size > 0 then
if len > 0 then
first = last - size + 1
idx = last - len + 1
else
else
for key in pairs(tbl) do
for key in pairs(tbl) do
if type(key) == 'number' and (first == nil or
if type(key) == 'number' and (idx == nil or
key < first) then first = key end
key < idx) then idx = key end
end
end
if first == nil then return context_iterate(ctx, 3) end
if idx == nil then return context_iterate(ctx, 3) end
first = first - size
idx = idx - len
if last < first then return context_iterate(ctx, 3) end
if last < idx then return context_iterate(ctx, 3) end
size = last - first + 1
len = last - idx + 1
end
end
purge_args(ctx.params, first, size)
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
-- See iface.reversing()
library.reversing = function(ctx)
library.rotating = function (ctx)
local tbl = ctx.params
local tbl = ctx.params
local numericals = {}
local numericals = {}
local nmax
local nmax = 0
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
numericals[key] = val
numericals[key] = val
tbl[key] = nil
tbl[key] = nil
if nmax == nil then
if key > nmax then nmax = key end
nmax = key
nmin = key
elseif key > nmax then nmax = key
elseif key < nmin then nmin = key
end
end
end
end
end
for key, val in pairs(numericals) do tbl[nmax + nmin - key] = val end
for key, val in pairs(numericals) 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
-- See iface.mirroring()
--[[
library.mirroring = 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.reversing(ctx) end
if shift < 2 then return library.rotating(ctx) end
local numericals = {}
local numericals = {}
for key, val in pairs(tbl) do
for key, val in pairs(tbl) do
Line 480: Line 852:
return context_iterate(ctx, 1)
return context_iterate(ctx, 1)
end
end
]]--


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




-- See iface.swapping()
-- 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 506: Line 903:




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


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


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


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


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


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

end
end
if nomatch then tbl[key] = nil end
if nomatch then tbl[key] = nil end
Line 529: Line 1,002:




-- See iface.with_name_not_matching()
-- 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
library.with_name_not_matching = function (ctx)
local tbl = ctx.params
local tbl = ctx.params
local targets, argc = parse_pattern_args(ctx, targets,
local patterns = {}
local argc = parse_match_args(ctx.pipe, patterns,
'with_name_not_matching')
'with_name_not_matching')
local yesmatch
local yesmatch
for key in pairs(tbl) do
for key in pairs(tbl) do
yesmatch = true
yesmatch = true
for _, ptn in ipairs(patterns) do
for _, ptn in ipairs(targets) do
if (
if ptn[3] then
ptn[3] == true and key:len() ~= ptn[1]:len()
if key ~= ptn[1] then
yesmatch = false
) or not string.find(key, ptn[1], 1, ptn[2]) then
break
end
elseif not string.find(key, ptn[1], 1, ptn[2]) then
yesmatch = false
yesmatch = false
break
break
Line 552: Line 1,029:




-- See iface.with_value_matching()
-- 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, argc = parse_pattern_args(ctx, targets,
local patterns = {}
local argc = parse_match_args(ctx.pipe, patterns,
'with_value_matching')
'with_value_matching')
local nomatch
local nomatch
for key, val in pairs(tbl) do
for key, val in pairs(tbl) do
nomatch = true
nomatch = true
for _, ptn in ipairs(patterns) do
for _, ptn in ipairs(targets) do
if (
if ptn[3] then
ptn[3] ~= true or val:len() == ptn[1]:len()
if val == ptn[1] then
nomatch = false
) and string.find(val, ptn[1], 1, ptn[2]) then
break
end
elseif string.find(val, ptn[1], 1, ptn[2]) then
nomatch = false
nomatch = false
break
break
Line 575: Line 1,056:




-- See iface.with_value_not_matching()
-- 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, argc = parse_pattern_args(ctx, targets,
local patterns = {}
local argc = parse_match_args(ctx.pipe, patterns,
'with_value_not_matching')
'with_value_not_matching')
local yesmatch
local yesmatch
for key, val in pairs(tbl) do
for key, val in pairs(tbl) do
yesmatch = true
yesmatch = true
for _, ptn in ipairs(patterns) do
for _, ptn in ipairs(targets) do
if (
if ptn[3] then
ptn[3] == true and val:len() ~= ptn[1]:len()
if val ~= ptn[1] then
yesmatch = false
) or not string.find(val, ptn[1], 1, ptn[2]) then
break
end
elseif not string.find(val, ptn[1], 1, ptn[2]) then
yesmatch = false
yesmatch = false
break
break
Line 598: Line 1,083:




-- See iface.trimming_values()
-- 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 606: Line 1,091:




-- See iface.mapping_by_calling()
-- 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(ctx.luaname .. ', ‘mapping_by_calling’: No template name was provided', 0) end
if tname == nil then error(ctx.luaname ..
', ‘mapping_by_calling’: No template name was provided', 0) end
local nargs
local margs = {}
local margs, argc, looptype, karg, varg = parse_callback_args(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




-- See iface.mapping_by_invoking()
-- 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(ctx.luaname .. ', ‘mapping_by_invoking’: No module name was provided', 0) 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 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
if fname == nil then error(ctx.luaname ..
', ‘mapping_by_invoking’: No function name was provided', 0) end
local nargs
local margs = {}
local margs, argc, looptype, karg, varg = parse_callback_args(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(ctx.luaname ..
', ‘mapping_by_invoking’: The function ‘' .. fname ..
'’ does not exist', 0) end
value_maps[looptype](ctx.params, margs, karg, varg, function ()
return mfunc(ctx.frame:newChild(model))
end)
return context_iterate(ctx, argc)
end


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


-- Syntax: #invoke:params|mapping_by_replacing|target|replace|[count]|[plain
-- flag]|pipe to
library.mapping_by_replacing = function (ctx)
local ptn, repl, nmax, is_strict, argc, die =
parse_replace_args(ctx.pipe, 'mapping_by_replacing')
if die then return context_iterate(ctx, argc) end
local tbl = ctx.params
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, nargs + 1)
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(ctx.luaname .. ', ‘mapping_blindly_by_calling’: No template name was provided', 0) end
if tname == nil then error(ctx.luaname ..
', ‘renaming_by_calling’: No template name was provided', 0) end
local nargs
local rargs, argc, looptype, karg, varg = parse_callback_args(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(ctx.luaname .. ', ‘mapping_blindly_by_invoking’: No module name was provided', 0) 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 opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end
if fname == nil then error(ctx.luaname .. ', ‘mapping_blindly_by_invoking’: No function name was provided', 0) end
if fname == nil then error(ctx.luaname ..
', ‘renaming_by_invoking’: No function name was provided', 0) end
local nargs
local rargs, argc, looptype, karg, varg = parse_callback_args(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(ctx.luaname ..
local tbl = ctx.params
', ‘renaming_by_invoking’: The function ‘' .. fname ..
for key, val in pairs(tbl) do
'’ does not exist', 0) end
margs[1] = val
map_names(ctx.params, rargs, karg, varg, looptype, function ()
tbl[key] = mfunc(ctx.frame:newChild(model))
return mfunc(ctx.frame:newChild(model))
end
end)
return context_iterate(ctx, nargs + 1)
return context_iterate(ctx, argc)
end
end




-- Syntax: #invoke:params|renaming_by_magic|parser function|[call
-- See iface.renaming_blindly_by_calling()
-- style]|[let]|[...][number of additional arguments]|[argument
library.renaming_blindly_by_calling = function(ctx)
-- 1]|[argument 2]|[...]|[argument N]|pipe to
library.renaming_by_magic = function (ctx)
local opts = ctx.pipe
local opts = ctx.pipe
local tname
local magic
if opts[1] ~= nil then tname = opts[1]:match'^%s*(.*%S)' end
if opts[1] ~= nil then magic = opts[1]:match'^%s*(.*%S)' end
if tname == nil then error(ctx.luaname .. ', ‘renaming_blindly_by_calling’: No template name was provided', 0) end
if magic == nil then error(ctx.luaname ..
', ‘renaming_by_magic’: No parser function was provided', 0) end
local nargs
local rargs, argc, looptype, karg, varg = parse_callback_args(opts, 1,
local margs = {}
mapping_styles.names_only)
local tmp = tonumber(opts[2])
map_names(ctx.params, rargs, karg, varg, looptype, function ()
if tmp == nil then
return ctx.frame:callParserFunction(magic, rargs)
nargs = 1
end)
elseif tmp < 1 then
return context_iterate(ctx, argc)
nargs = 2
end


-- Syntax: #invoke:params|renaming_by_replacing|target|replace|[count]|[plain
-- flag]|pipe to
library.renaming_by_replacing = function (ctx)
local ptn, repl, nmax, is_strict, argc, die =
parse_replace_args(ctx.pipe, 'renaming_by_replacing')
if die then return context_iterate(ctx, argc) end
local tbl = ctx.params
if is_strict then
local key = tonumber(ptn) or ptn:match'^%s*(.-)%s*$'
local val = tbl[key]
tbl[key] = nil
tbl[tonumber(repl) or repl:match'^%s*(.-)%s*$'] = val
else
else
nargs = tmp + 2
if flg == 2 then
-- Copied from Module:String's `str._escapePattern()`
for idx = 3, nargs do margs[idx - 1] = opts[idx] end
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, argc)
local model = { title = tname, args = margs }
local tbl = {}
for key, val in pairs(ctx.params) do
margs[1] = key
tmp = ctx.frame:expandTemplate(model)
tbl[tonumber(tmp) or tmp] = val
end
ctx.params = tbl
return context_iterate(ctx, nargs + 1)
end
end




-- Syntax: #invoke:params|grouping_by_calling|template
-- See iface.renaming_blindly_by_invoking()
-- name|[let]|[...]|[number of additional arguments]|[argument
library.renaming_blindly_by_invoking = 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 mname
local tmp
if opts[1] ~= nil then tmp = opts[1]:match'^%s*(.*%S)' end
local fname
if opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end
if tmp == nil then error(ctx.luaname ..
if mname == nil then error(ctx.luaname .. ', ‘renaming_blindly_by_invoking’: No module name was provided', 0) end
', ‘grouping_by_calling’: No template name was provided', 0) end
local model = { title = tmp }
if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end
local tmp, argc = parse_child_args(opts, 2, 0)
if fname == nil then error(ctx.luaname .. ', ‘renaming_blindly_by_invoking’: No function name was provided', 0) end
local nargs
local gargs = {}
for key, val in pairs(tmp) do
local margs = {}
if type(key) == 'number' and key < 1 then gargs[key - 1] = val
local tmp = tonumber(opts[3])
else gargs[key] = val end
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
end
local groups = make_groups(ctx.params)
local model = { title = 'Module:' .. mname, args = margs }
for gid, group in pairs(groups) do
local mfunc = require(model.title)[fname]
for key, val in pairs(gargs) do group[key] = val end
local tbl = {}
group[0] = gid
for key, val in pairs(ctx.params) do
margs[1] = key
model.args = group
tmp = mfunc(ctx.frame:newChild(model))
groups[gid] = ctx.frame:expandTemplate(model)
tbl[tonumber(tmp) or tmp] = val
end
end
ctx.params = tbl
ctx.params = groups
return context_iterate(ctx, nargs + 1)
return context_iterate(ctx, argc)
end
end






--[[ Library's functions ]]--
--[[ Functions ]]--
------------------------------------
-----------------------------




-- See iface.count()
-- 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
if ctx.subset == -1 then count = count - #ctx.params 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




-- See iface.concat_and_call()
-- 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(ctx.luaname .. ', ‘concat_and_call’: No template name was provided', 0) end
if tname == nil then error(ctx.luaname ..
', ‘concat_and_call’: No template name was provided', 0) end
table.remove(opts, 1)
remove_numerical_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




-- See iface.concat_and_invoke()
-- 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(ctx.luaname .. ', ‘concat_and_invoke’: No module name was provided', 0) 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 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
if fname == nil then error(ctx.luaname ..
', ‘concat_and_invoke’: No function name was provided', 0) end
table.remove(opts, 2)
table.remove(opts, 1)
remove_numerical_keys(opts, 1, 2)
return require('Module:' .. mname)[fname](ctx.frame:newChild{
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,
title = 'Module:' .. fname,
args = concat_params(ctx)
args = concat_params(ctx)
})
})
return false
end
end




-- See iface.concat_and_magic()
-- 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(ctx.luaname .. ', ‘concat_and_magic’: No parser function was provided', 0) end
if magic == nil then error(ctx.luaname ..
', ‘concat_and_magic’: No parser function was provided', 0) end
table.remove(opts, 1)
remove_numerical_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




-- See iface.value_of()
-- 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 keystr
local kstr
if opts[1] ~= nil then keystr = opts[1]:match'^%s*(.*%S)' end
if opts[1] ~= nil then kstr = opts[1]:match'^%s*(.*%S)' end
if keystr == nil then error(ctx.luaname .. ', ‘value_of’: No parameter name was provided', 0) end
if kstr == nil then error(ctx.luaname ..
', ‘value_of’: No parameter name was provided', 0) end
local keynum = tonumber(keystr)
local knum = tonumber(kstr)
local len = #ctx.params
local len = #ctx.params
local val = ctx.params[knum or kstr]
if (
ctx.subset == -1 and keynum ~= nil and len >= keynum
if val ~= nil and (
ctx.subset ~= -1 or knum == nil or knum > len or knum < 1
) or (
) and (
ctx.subset == 1 and (keynum == nil or len < keynum)
ctx.subset ~= 1 or (knum ~= nil and knum <= len and knum > 0)
) then return (ctx.ifngiven or '') end
) then
local val = ctx.params[keynum or keystr]
if val == nil then return (ctx.ifngiven or '') end
ctx.text = (ctx.header or '') .. val .. (ctx.footer or '')
return false
return (ctx.header or '') .. val .. (ctx.footer or '')
end
ctx.text = ctx.ifngiven or ''
return false
end
end




-- See iface.list()
-- 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 870: Line 1,412:
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 884: Line 1,426:
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
return table.concat(ret)
ctx.text = table.concat(ret)
return false
end
end
return ctx.ifngiven or ''
ctx.text = ctx.ifngiven or ''
return false
end
end




-- See iface.list_values()
-- Syntax: #invoke:params|list_values
library.list_values = function(ctx)
library.list_values = function (ctx)
-- NOTE: `ctx.pipe` might be the original metatable!
local pps = ctx.itersep or ''
local pps = ctx.itersep or ''
local ret = {}
local ret = {}
Line 897: Line 1,442:
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 909: Line 1,454:
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
return table.concat(ret)
ctx.text = table.concat(ret)
return false
end
end
return ctx.ifngiven or ''
ctx.text = ctx.ifngiven or ''
return false
end
end




-- See iface.for_each()
-- Syntax: #invoke:params|for_each|wikitext
library.for_each = function(ctx)
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 923: Line 1,471:
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 935: Line 1,483:
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
return table.concat(ret)
ctx.text = table.concat(ret)
return false
end
end
return ctx.ifngiven or ''
ctx.text = ctx.ifngiven or ''
return false
end
end




-- See iface.call_for_each()
-- 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(ctx.luaname .. ', ‘call_for_each’: No template name was provided', 0) 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 model = { title = tname, args = opts }
local ccs = ctx.itersep or ''
local ccs = ctx.itersep or ''
Line 954: Line 1,507:
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 968: Line 1,521:
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
return table.concat(ret)
ctx.text = table.concat(ret)
return false
end
end
return ctx.ifngiven or ''
ctx.text = ctx.ifngiven or ''
return false
end
end




-- See iface.invoke_for_each()
-- 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(ctx.luaname .. ', ‘invoke_for_each’: No module name was provided', 0) 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 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
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 model = { title = 'Module:' .. mname, args = opts }
local mfunc = require(model.title)[fname]
local mfunc = require(model.title)[fname]
Line 990: Line 1,549:
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,004: Line 1,563:
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
return table.concat(ret)
ctx.text = table.concat(ret)
return false
end
end
return ctx.ifngiven or ''
ctx.text = ctx.ifngiven or ''
return false
end
end




-- See iface.magic_for_each()
-- 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(ctx.luaname .. ', ‘magic_for_each’: No parser function was provided', 0) end
if magic == nil then error(ctx.luaname ..
', ‘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,022: Line 1,586:
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,037: Line 1,601:
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
return table.concat(ret)
ctx.text = table.concat(ret)
return false
end
end
return ctx.ifngiven or ''
ctx.text = ctx.ifngiven or ''
return false
end
end




-- See iface.call_for_each_value()
-- 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(ctx.luaname .. ', ‘call_for_each_value’: No template name was provided', 0) 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 model = { title = tname, args = opts }
local ccs = ctx.itersep or ''
local ccs = ctx.itersep or ''
Line 1,055: Line 1,624:
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,068: Line 1,637:
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
return table.concat(ret)
ctx.text = table.concat(ret)
return false
end
end
return ctx.ifngiven or ''
ctx.text = ctx.ifngiven or ''
return false
end
end




-- See iface.invoke_for_each_value()
-- 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(ctx.luaname .. ', ‘invoke_for_each_value’: No module name was provided', 0) 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 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
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 model = { title = 'Module:' .. mname, args = opts }
local mfunc = require(model.title)[fname]
local mfunc = require(model.title)[fname]
Line 1,088: Line 1,663:
local ret = {}
local ret = {}
local nss = 0
local nss = 0
table.remove(opts, 1)
remove_numerical_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,104: Line 1,679:
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
return table.concat(ret)
ctx.text = table.concat(ret)
return false
end
end
return ctx.ifngiven or ''
ctx.text = ctx.ifngiven or ''
return false

end
end




-- See iface.magic_for_each_value()
-- 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(ctx.luaname .. ', ‘magic_for_each_value’: No parser function was provided', 0) 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 ccs = ctx.itersep or ''
local ret = {}
local ret = {}
Line 1,122: Line 1,701:
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,136: Line 1,715:
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
return table.concat(ret)
ctx.text = table.concat(ret)
return false
end
end
return ctx.ifngiven or ''
ctx.text = ctx.ifngiven or ''
return false
end


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


Line 1,149: Line 1,776:




-- The public table of functions
local iface = {}


--[[ First-position-only modifiers ]]--
---------------------------------------




-- Syntax: #invoke:params|new|pipe to
--[[ Modifiers ]]--
------------------------------------


-- Syntax: #invoke:params|sequential|function name
iface.sequential = function(frame)
return context_init(frame, library.sequential, false, false)
end


-- Syntax: #invoke:params|non-sequential|function name
iface['non-sequential'] = function(frame)
return context_init(frame, library['non-sequential'], false, false)
end


-- Syntax: #invoke:params|sort|function name
iface.all_sorted = function(frame)
return context_init(frame, library.all_sorted, false, false)
end


-- Syntax: #invoke:params|setting|directives|...|function name
iface.setting = function(frame)
return context_init(frame, library.setting, false, false)
end


-- Syntax: #invoke:params|squeezing|function name
iface.squeezing = function(frame)
return context_init(frame, library.squeezing, false, false)
end


-- Syntax: #invoke:params|cutting|left cut|right cut|function name
iface.cutting = function(frame)
return context_init(frame, library.cutting, false, false)
end


-- Syntax: #invoke:params|cropping|left crop|right crop|function name
iface.cropping = function(frame)
return context_init(frame, library.cropping, false, false)
end


-- Syntax: #invoke:params|purging|start offset|length|function name
iface.purging = function(frame)
return context_init(frame, library.purging, false, false)
end


-- Syntax: #invoke:params|backpurging|start offset|length|function name
iface.backpurging = function(frame)
return context_init(frame, library.backpurging, false, false)
end


-- Syntax: #invoke:params|reversing|function name
iface.reversing = function(frame)
return context_init(frame, library.reversing, false, false)
end


-- Syntax: #invoke:params|mirroring|function name
iface.mirroring = function(frame)
return context_init(frame, library.mirroring, false, false)
end


-- Syntax: #invoke:params|swapping|function name
--[[
--[[
iface.swapping = function(frame)
static_iface.new = function (frame)
local ctx = context_new()
return context_init(frame, library.swapping, false, false)
ctx.frame = frame:getParent()
end
]]--


-- Syntax: #invoke:params|with_name_matching|pattern 1|[plain flag 1]|[or]
-- |[pattern 2]|[plain flag 2]|[or]|[...]|[pattern N]|[plain flag
-- N]|function name
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]|function name
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]|function name
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]|function name
iface.with_value_not_matching = function(frame)
return context_init(frame, library.with_value_not_matching, false,
false)
end


-- Syntax: #invoke:params|trimming_values|function name
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]|function name
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]|function name
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]|function name
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]|function name
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]|function name
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]|function name
iface.renaming_blindly_by_invoking = function(frame)
return context_init(frame, library.renaming_blindly_by_invoking, false,
false)
end




--[[ First position only ]]--
----------------------------------------


-- Syntax: #invoke:params|new|function name
--[[
iface.new = function(frame)
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 = {}
return context_iterate(ctx, 1)
main_loop(ctx, context_iterate(ctx, 1))
return ctx.text
end
end
]]--
]]--
Line 1,342: Line 1,795:




--[[ 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


return setmetatable(static_iface, {

__index = function (iface, _fname_)
-- Syntax: #invoke:args|concat_and_magic|parser function|[prepend 1]|[prepend
local ctx = context_new()
-- 2]|[...]|[item n]|[named item 1=value 1]|[...]|[named item n=
local fname = _fname_:match'^%s*(.*%S)'
-- value n]|[...]
if fname == nil then error(ctx.luaname ..
iface.concat_and_magic = function(frame)
': You must specify a function to call', 0) end
return context_init(frame, library.concat_and_magic, false, true)
if library[fname] == nil then error(ctx.luaname ..
end
': The function ‘' .. fname .. '’ does not exist', 0) end

local func = library[fname]

return function (frame)
-- Syntax: #invoke:params|value_of|parameter name
iface.value_of = function(frame)
ctx.frame = frame:getParent()
ctx.pipe = copy_or_ref_table(frame.args,
return context_init(frame, library.value_of, true, true)
refpipe[fname])
end
ctx.params = copy_or_ref_table(ctx.frame.args,

refparams[fname])

main_loop(ctx, func)
-- Syntax: #invoke:params|list
return ctx.text
iface.list = function(frame)
end
return context_init(frame, library.list, true, false)
end
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




--[[ First position only ]]--
----------------------------------------


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


return iface

Latest revision as of 18:56, 1 October 2024

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

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



	--[[ Abstract utilities ]]--
	----------------------------


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


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


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


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


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


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


-- Move a key from a table to another, but only if under a different name and
-- always parsing numerical strings as numbers
local function steal_if_renamed (val, src, skey, dest, dkey)
	local realkey = tonumber(dkey) or dkey:match'^%s*(.-)%s*$'
	if skey ~= realkey then
		dest[realkey] = val
		src[skey] = nil
	end
end



	--[[ Public strings ]]--
	------------------------


-- Special match keywords (functions and modifiers MUST avoid these names)
local mkeywords = {
	['or'] = 0,
	pattern = 1,
	plain = 2,
	strict = 3
}


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


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

Meanings of the columns:

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

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

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


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


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



	--[[ Module's private environment ]]--
	--------------------------------------


-- Functions listed here declare that they don't need the `frame.args`
-- metatable to be copied into a regular table; if they are modifiers they also
-- guarantee that they will make available their own (modified) copy
local refpipe = {
	count = true,
	value_of = true,
	list = true,
	list_values = true,
	for_each = true,
	call_for_each_group = true
}


-- Functions listed here declare that they don't need the
-- `frame:getParent().args` metatable to be copied into a regular table; if 
-- they are modifiers they also guarantee that they will make available their
-- own (modified) copy
local refparams = {
	--inserting = true,
	grouping_by_calling = true,
	count = true,
	concat_and_call = true,
	concat_and_invoke = true,
	concat_and_magic = true,
	value_of = true,
	call_for_each_group = true
}


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


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


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


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


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


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


-- Parse user arguments of type `...|[let]|[...][number of additional
-- parameters]|[parameter 1]|[parameter 2]|[...]`
local function parse_child_args (src, start_from, append_after)
	local names
	local tmp
	local dest = {}
	local pin = start_from
	if src[pin] ~= nil and src[pin]:match'^%s*let%s*$' then
		names = {}
		repeat
			tmp = src[pin + 1] or ''
			names[tonumber(tmp) or tmp:match'^%s*(.-)%s*$' or ''] =
				src[pin + 2]
			pin = pin + 3
		until src[pin] == nil or not src[pin]:match'^%s*let%s*$'
	end
	tmp = tonumber(src[pin])
	if tmp ~= nil then
		if tmp < 0 then tmp = -1 end
		local shf = append_after - pin
		for idx = pin + 1, pin + tmp do dest[idx + shf] = src[idx] end
		pin = pin + tmp + 1
	end
	if names ~= nil then
		for key, val in pairs(names) do dest[key] = val end
	end
	return dest, pin
end


-- Parse the arguments of some of the `mapping_*` and `renaming_*` class of
-- modifiers
local function parse_callback_args (src, n_skip, default_style)
	local style
	local shf
	local tmp = src[n_skip + 1]
	if tmp ~= nil then style = mapping_styles[tmp:match'^%s*(.-)%s*$'] end
	if style == nil then
		style = default_style
		shf = n_skip - 1
	else shf = n_skip end
	local n_exist = style[3]
	local karg = style[4]
	local varg = style[5]
	tmp = style[6]
	if tmp > -1 then
		tmp = src[tmp + shf]
		karg = tonumber(tmp)
		if karg == nil then karg = tmp:match'^%s*(.-)%s*$'
		else n_exist = math.max(n_exist, karg) end
	end
	tmp = style[7]
	if tmp > -1 then
		tmp = src[tmp + shf]
		varg = tonumber(tmp)
		if varg == nil then varg = tmp:match'^%s*(.-)%s*$'
		else n_exist = math.max(n_exist, varg) end
	end
	local dest, nargs = parse_child_args(src, style[2] + shf, n_exist)
	tmp = style[1]
	if (tmp == 3 or tmp == 2) and dest[karg] ~= nil then
		tmp = tmp - 2 end
	if (tmp == 3 or tmp == 1) and dest[varg] ~= nil then
		tmp = tmp - 1 end
	return dest, nargs, tmp, karg, varg
end


-- Parse the arguments of some of the `mapping_*` and `renaming_*` class of
-- modifiers
local function parse_replace_args (opts, fname)
	if opts[1] == nil then error(ctx.luaname ..
		', ‘' .. fname .. '’: No pattern string was given', 0) end
	if opts[2] == nil then error(ctx.luaname ..
		', ‘' .. fname .. '’: No replacement string was given', 0) end
	local ptn = opts[1]
	local repl = opts[2]
	local argc = 3
	local nmax = tonumber(opts[3])
	if nmax ~= nil or (opts[3] or ''):match'^%s*$' ~= nil then argc = 4 end
	local flg = opts[argc]
	if flg ~= nil then flg = mkeywords[flg:match'^%s*(.-)%s*$'] end
	if flg == 0 then flg = nil elseif flg ~= nil then argc = argc + 1 end
	return ptn, repl, nmax, flg == 3, argc, (nmax ~= nil and nmax < 1) or
		(flg == 3 and ptn == repl)
end


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


-- Map parameters' values using a custom callback and a referenced table
local value_maps = {
	[0] = function (tbl, margs, karg, varg, fn)
		for key in pairs(tbl) do tbl[key] = fn() end
	end,
	[1] = function (tbl, margs, karg, varg, fn)
		for key, val in pairs(tbl) do
			margs[varg] = val
			tbl[key] = fn()
		end
	end,
	[2] = function (tbl, margs, karg, varg, fn)
		for key in pairs(tbl) do
			margs[karg] = key
			tbl[key] = fn()
		end
	end,
	[3] = function (tbl, margs, karg, varg, fn)
		for key, val in pairs(tbl) do
			margs[karg] = key
			margs[varg] = val
			tbl[key] = fn()
		end
	end
}


-- Private table for `map_names()`
local name_thieves_maps = {
	[0] = function (cache, tbl, rargs, karg, varg, fn)
		for key, val in pairs(tbl) do
			steal_if_renamed(val, tbl, key, cache, fn())
		end
	end,
	[1] = function (cache, tbl, rargs, karg, varg, fn)
		for key, val in pairs(tbl) do
			rargs[varg] = val
			steal_if_renamed(val, tbl, key, cache, fn())
		end
	end,
	[2] = function (cache, tbl, rargs, karg, varg, fn)
		for key, val in pairs(tbl) do
			rargs[karg] = key
			steal_if_renamed(val, tbl, key, cache, fn())
		end
	end,
	[3] = function (cache, tbl, rargs, karg, varg, fn)
		for key, val in pairs(tbl) do
			rargs[karg] = key
			rargs[varg] = val
			steal_if_renamed(val, tbl, key, cache, fn())
		end
	end
}


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


-- Return a new table that contains `src` regrouped according to the numerical
-- suffixes in its keys
local function make_groups (src)
	-- NOTE: `src` might be the original metatable!
	local tmp
	local prefix
	local gid
	local groups = {}
	for key, val in pairs(src) do
		-- `key` must only be a string or a number...
		gid = tonumber(key)
		if gid == nil then
			prefix, gid = key:match'^%s*(.-)%s*(%-?%d*)%s*$'
			gid = tonumber(gid) or ''
		else prefix = '' end
		if groups[gid] == nil then groups[gid] = {} end
		tmp = tonumber(prefix)
		if tmp ~= nil then
			if tmp < 1 then prefix = tmp - 1 else prefix = tmp end
		end
		groups[gid][prefix] = val
	end
	return groups
end


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


-- Flush the parameters by calling a custom function for each value (after this
-- function has been invoked `ctx.params` will be no longer usable)
local function flush_params (ctx, fn)
	local tbl = ctx.params
	if ctx.subset == 1 then
		for key, val in ipairs(tbl) do fn(key, val) end
		return
	end
	if ctx.subset == -1 then
		for key, val in ipairs(tbl) do tbl[key] = nil end
	end
	if ctx.sorttype > 0 then
		local nums = {}
		local words = {}
		local nn = 0
		local nw = 0
		for key, val in pairs(tbl) do
			if type(key) == 'number' then
				nn = nn + 1
				nums[nn] = key
			else
				nw = nw + 1
				words[nw] = key
			end
		end
		table.sort(nums)
		table.sort(words, natural_sort)
		if ctx.sorttype == 2 then
			for idx = 1, nw do fn(words[idx], tbl[words[idx]]) end
			for idx = 1, nn do fn(nums[idx], tbl[nums[idx]]) end
			return
		end
		for idx = 1, nn do fn(nums[idx], tbl[nums[idx]]) end
		for idx = 1, nw do fn(words[idx], tbl[words[idx]]) end
		return
	end
	if ctx.subset ~= -1 then
		for key, val in ipairs(tbl) do
			fn(key, val)
			tbl[key] = nil
		end
	end
	for key, val in pairs(tbl) do fn(key, val) end
end



	--[[ Modifiers ]]--
	-----------------------------


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


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


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


-- Syntax:  #invoke:params|reassorted|pipe to
library.reassorted = function (ctx)
	if ctx.subset == 1 then error(ctx.luaname ..
		': The ‘reassorted’ directive is redundant after ‘sequential’', 0) end
	if ctx.sorttype == 1 then error(ctx.luaname ..
		': The two directives ‘sequential’ and ‘reassorted’ are in contradiction with each other', 0) end
	ctx.sorttype = 2
	return context_iterate(ctx, 1)
end


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


-- Syntax:  #invoke:params|mapping_by_replacing|target|replace|[count]|[plain
--            flag]|pipe to
library.mapping_by_replacing = function (ctx)
	local ptn, repl, nmax, is_strict, argc, die =
		parse_replace_args(ctx.pipe, 'mapping_by_replacing')
	if die then return context_iterate(ctx, argc) end
	local tbl = ctx.params
	if is_strict then
		for key, val in pairs(tbl) do
			if val == ptn then tbl[key] = repl end
		end
	else
		if flg == 2 then
			-- Copied from Module:String's `str._escapePattern()`
			ptn = ptn:gsub('[%(%)%.%%%+%-%*%?%[%^%$%]]', '%%%0')
		end
		for key, val in pairs(tbl) do
			tbl[key] = val:gsub(ptn, repl, nmax)
		end
	end
	return context_iterate(ctx, argc)
end


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


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


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


-- Syntax:  #invoke:params|renaming_by_replacing|target|replace|[count]|[plain
--            flag]|pipe to
library.renaming_by_replacing = function (ctx)
	local ptn, repl, nmax, is_strict, argc, die =
		parse_replace_args(ctx.pipe, 'renaming_by_replacing')
	if die then return context_iterate(ctx, argc) end
	local tbl = ctx.params
	if is_strict then
		local key = tonumber(ptn) or ptn:match'^%s*(.-)%s*$'
		local val = tbl[key]
		tbl[key] = nil
		tbl[tonumber(repl) or repl:match'^%s*(.-)%s*$'] = val
	else
		if flg == 2 then
			-- Copied from Module:String's `str._escapePattern()`
			ptn = ptn:gsub('[%(%)%.%%%+%-%*%?%[%^%$%]]', '%%%0')
		end
		local cache = {}
		for key, val in pairs(tbl) do
			steal_if_renamed(val, tbl, key, cache,
				tostring(key):gsub(ptn, repl, nmax))
		end
		for key, val in pairs(cache) do tbl[key] = val end
	end
	return context_iterate(ctx, argc)
end


-- Syntax:  #invoke:params|grouping_by_calling|template
--            name|[let]|[...]|[number of additional arguments]|[argument
--            1]|[argument 2]|[...]|[argument N]|pipe to
library.grouping_by_calling = function (ctx)
	-- NOTE: `ctx.params` might be the original metatable! As a modifier,
	-- this function MUST create a copy of it before returning
	local opts = ctx.pipe
	local tmp
	if opts[1] ~= nil then tmp = opts[1]:match'^%s*(.*%S)' end
	if tmp == nil then error(ctx.luaname ..
		', ‘grouping_by_calling’: No template name was provided', 0) end
	local model = { title = tmp }
	local tmp, argc = parse_child_args(opts, 2, 0)
	local gargs = {}
	for key, val in pairs(tmp) do
		if type(key) == 'number' and key < 1 then gargs[key - 1] = val
		else gargs[key] = val end
	end
	local groups = make_groups(ctx.params)
	for gid, group in pairs(groups) do
		for key, val in pairs(gargs) do group[key] = val end
		group[0] = gid
		model.args = group
		groups[gid] = ctx.frame:expandTemplate(model)
	end
	ctx.params = groups
	return context_iterate(ctx, argc)
end



	--[[ Functions ]]--
	-----------------------------


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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



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



	--[[ First-position-only modifiers ]]--
	---------------------------------------


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



	--[[ First-position-only functions ]]--
	---------------------------------------


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



	--[[ Public metatable of functions ]]--
	---------------------------------------


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