Module:Params

From Zoophilia Wiki
Revision as of 23:45, 2 September 2025 by SockyPaws (talk | contribs) (Omport complex module from English Wikipedia)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigationJump to search

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

require[[strict]]

  ---                                        ---
  ---     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
  local newtab = {}
  for key, val in pairs(src) do newtab[key] = val end
  return newtab
end

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

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

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

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

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

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

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

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

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

A value of `-1` indicates that no meaningful value is stored (i.e. `nil`)
]]--
local mapping_styles = {
  names_and_values = { 3, 2, 2, 1, 2, -1, -1 },
  values_and_names = { 3, 2, 2, 2, 1, -1, -1 },
  values_only = { 1, 2, 1, -1, 1, -1, -1 },
  names_only = { 2, 2, 1, 1, -1, -1, -1 },
  names_and_values_as = { 3, 4, 0, -1, -1, 2, 3 },
  names_only_as = { 2, 3, 0, -1, -1, 2, -1 },
  values_only_as = { 1, 3, 0, -1, -1, -1, 2 },
  blindly = { 0, 2, 0, -1, -1, -1, -1 }
}

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

-- Possible trimming modes for the `parsing` modifier
local trim_parse_opts = {
  trim_none = { false, false },
  trim_positional = { false, true },
  trim_named = { true, false },
  trim_all = { true, true }
}

-- Possible string modes for the iteration separator in the `parsing` and
-- `reinterpreting` modifiers
local isep_parse_opts = {
  splitter_pattern = false,
  splitter_string = true
}

-- Possible string modes for the key-value separator in the `parsing` and
-- `reinterpreting` modifiers
local psep_parse_opts = {
  setter_pattern = false,
  setter_string = true
}

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

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

-- Hard-coded name of the module (to avoid going through `frame:getTitle()`)
local modulename = 'Module:Params'

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

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

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

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

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

-- Create a new context
local function context_new (frame)
  local ctx = {}
  ctx.frame = frame
  ctx.oparams = frame.args
  ctx.firstposonly = static_iface
  ctx.iterfunc = pairs
  ctx.sorttype = 0
  ctx.n_parents = 0
  ctx.n_children = 0
  ctx.n_available = maxfill
  return ctx
end

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

-- Main loop
local function main_loop (ctx, start_with)
  local fn = start_with
  repeat fn = fn(ctx) until not fn
  if ctx.n_parents > 0 then error(modulename ..
    ': One or more ‘merging_substack’ directives are missing', 0) end
  if ctx.n_children > 0 then error(modulename ..
    ', For some of the snapshots either the ‘flushing’ directive is missing or a group has not been properly closed with ‘merging_substack’', 0) end
end

-- Add a new stack of parameters to `ctx.children`
local function push_cloned_stack (ctx, tbl)
  local newparams = {}
  local currsnap = ctx.n_children + 1
  if ctx.children == nil then ctx.children = { newparams }
  else ctx.children[currsnap] = newparams end
  for key, val in pairs(tbl) do newparams[key] = val end
  ctx.n_children = currsnap
end

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

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

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

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

-- Load the optional arguments of the `parsing` and `reinterpreting` modifiers
local function load_parse_opts (opts, start_from)
  local argc = start_from
  local tmp
  local optslots = { true, true, true }
  local noptslots = 3
  local trimn = true
  local trimu = false
  local iplain = true
  local pplain = true
  local isp = '|'
  local psp = '='
  repeat
    noptslots = noptslots - 1
    tmp = opts[argc]
    if tmp == nil then break end
    tmp = tmp:match'^%s*(.-)%s*$'
    if optslots[1] ~= nil and trim_parse_opts[tmp] ~= nil then
      tmp = trim_parse_opts[tmp]
      trimn = tmp[1]
      trimu = tmp[2]
      optslots[1] = nil
    elseif optslots[2] ~= nil and isep_parse_opts[tmp] ~= nil then
      argc = argc + 1
      iplain = isep_parse_opts[tmp]
      isp = opts[argc]
      optslots[2] = nil
    elseif optslots[3] ~= nil and psep_parse_opts[tmp] ~= nil then
      argc = argc + 1
      pplain = psep_parse_opts[tmp]
      psp = opts[argc]
      optslots[3] = nil
    else break end
    argc = argc + 1
  until noptslots < 1
  return isp, iplain, psp, pplain, trimn, trimu, argc
end

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

-- Private table for `map_names()`
local name_thieves = {
  [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[looptype](cache, tbl, rargs, karg, varg, fn)
  for key, val in pairs(cache) do tbl[key] = val end
end

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

-- Split into parts a string containing the `$#` and `$@` placeholders and
-- return the information as a skeleton table, a canvas table and a length
local function parse_placeholder_string (target)
  local skel = {}
  local canvas = {}
  local idx = 1
  local s_pos = 1
  local e_pos = string.find(target, '%$[@#]', 1, false)
  while e_pos ~= nil do
    canvas[idx] = target:sub(s_pos, e_pos - 1)
    skel[idx + 1] = target:sub(e_pos, e_pos + 1) == '$@'
    idx = idx + 2
    s_pos = e_pos + 2
    e_pos = string.find(target, '%$[@#]', s_pos, false)
  end
  if (s_pos > target:len()) then idx = idx - 1
  else canvas[idx] = target:sub(s_pos) end
  return skel, canvas, idx
end

-- Populate a table by parsing a parameter string
local function parse_parameter_string (tbl, str, isp, ipl, psp, ppl, trn, tru)
  local key
  local val
  local spos1
  local spos2
  local pos1
  local pos2
  local pos3 = 0
  local idx = 1
  local lenplone = #str + 1
  if isp == nil or isp == '' then
    if psp == nil or psp == '' then
      if tru then tbl[idx] = str:match'^%s*(.-)%s*$'
      else tbl[idx] = str end
      return tbl
    end
    spos1, spos2 = str:find(psp, 1, ppl)
    if spos1 == nil then
      key = idx
      if tru then val = str:match'^%s*(.-)%s*$'
      else val = str end
      idx = idx + 1
    else
      key = str:sub(1, spos1 - 1)
      key = tonumber(key) or key:match'^%s*(.-)%s*$'
      val = str:sub(spos2 + 1)
      if trn then val = val:match'^%s*(.-)%s*$' end
    end
    tbl[key] = val
    return tbl
  end
  if psp == nil or psp == '' then
    repeat
      pos1 = pos3 + 1
      pos2, pos3 = str:find(isp, pos1, ipl)
      val = str:sub(pos1, (pos2 or lenplone) - 1)
      if tru then val = val:match'^%s*(.-)%s*$' end
      tbl[idx] = val
      idx = idx + 1
    until pos2 == nil
    return tbl
  end
  repeat
    pos1 = pos3 + 1
    pos2, pos3 = str:find(isp, pos1, ipl)
    val = str:sub(pos1, (pos2 or lenplone) - 1)
    spos1, spos2 = val:find(psp, 1, ppl)
    if spos1 == nil then
      key = idx
      if tru then val = val:match'^%s*(.-)%s*$' end
      idx = idx + 1
    else
      key = val:sub(1, spos1 - 1)
      key = tonumber(key) or key:match'^%s*(.-)%s*$'
      val = val:sub(spos2 + 1)
      if trn then val = val:match'^%s*(.-)%s*$' end
    end
    tbl[key] = val
  until pos2 == nil
  return tbl
end

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

-- Syntax:  #invoke:params|excluding_non-numeric_names|pipe to
library['excluding_non-numeric_names'] = function (ctx)
  local tmp = ctx.params
  for key, val in pairs(tmp) do
    if type(key) ~= 'number' then tmp[key] = nil end
  end
  return context_iterate(ctx, 1)
end

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

-- Syntax:  #invoke:params|with_name_matching|target 1|[plain flag 1]|[or]
--            |[target 2]|[plain flag 2]|[or]|[...]|[target N]|[plain flag
--            N]|pipe to
library.with_name_matching = function (ctx)
  -- NOTE: `ctx.params` might be the original metatable! As a modifier,
  -- this function MUST create a copy of it before returning
  local targets, nptns, argc = load_pattern_args(ctx.pipe,
    'with_name_matching')
  local tmp
  local ptn
  local tbl = ctx.params
  local newparams = {}
  for idx = 1, nptns do
    ptn = targets[idx]
    if ptn[3] then
      tmp = tonumber(ptn[1]) or ptn[1]
      newparams[tmp] = tbl[tmp]
    else
      for key, val in pairs(tbl) do
        if tostring(key):find(ptn[1], 1, ptn[2]) then
          newparams[key] = val
        end
      end
    end
  end
  ctx.params = newparams
  return context_iterate(ctx, argc)
end

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

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

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

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

-- Syntax:  #invoke:params|converting_values_to_lowercase|pipe to
library.converting_values_to_lowercase = function (ctx)
  local tbl = ctx.params
  for key, val in pairs(tbl) do tbl[key] = val:lower() end
  return context_iterate(ctx, 1)
end

-- Syntax:  #invoke:params|converting_values_to_uppercase|pipe to
library.converting_values_to_uppercase = function (ctx)
  local tbl = ctx.params
  for key, val in pairs(tbl) do tbl[key] = val:upper() end
  return context_iterate(ctx, 1)
end

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

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

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

-- Syntax:  #invoke:params|mapping_by_replacing|target|replace|[count]|[plain
--            flag]|pipe to
library.mapping_by_replacing = function (ctx)
  local ptn, repl, nmax, flg, argc, die =
    load_replace_args(ctx.pipe, 'mapping_by_replacing')
  if die then return context_iterate(ctx, argc) end
  local tbl = ctx.params
  if flg == 3 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|mapping_by_mixing|mixing string|pipe to
library.mapping_by_mixing = function (ctx)
  if ctx.pipe[1] == nil then error(modulename ..
    ', ‘mapping_by_mixing’: No mixing string was provided', 0) end
  local mix = ctx.pipe[1]
  local tbl = ctx.params
  if mix == '$#' then
    for key in pairs(tbl) do tbl[key] = key end
    return context_iterate(ctx, 2)
  end
  local skel, cnv, n_parts = parse_placeholder_string(mix)
  for key, val in pairs(tbl) do
    for idx = 2, n_parts, 2 do
      if skel[idx] then cnv[idx] = val else cnv[idx] = tostring(key) end
    end
    tbl[key] = table.concat(cnv)
  end
  return context_iterate(ctx, 2)
end

-- Syntax:  #invoke:params|mapping_to_names|pipe to
--[[
library.mapping_to_names = function (ctx)
  local tbl = ctx.params
  for key in pairs(tbl) do tbl[key] = key end
  return context_iterate(ctx, 1)
end
]]--

-- Syntax:  #invoke:params|converting_names_to_lowercase|pipe to
library.converting_names_to_lowercase = function (ctx)
  -- NOTE: `ctx.params` might be the original metatable! As a modifier,
  -- this function MUST create a copy of it before returning
  local cache = {}
  for key, val in pairs(ctx.params) do
    if type(key) == 'string' then cache[key:lower()] = val else
    cache[key] = val end
  end
  ctx.params = cache
  return context_iterate(ctx, 1)
end

-- Syntax:  #invoke:params|converting_names_to_uppercase|pipe to
library.converting_names_to_uppercase = function (ctx)
  -- NOTE: `ctx.params` might be the original metatable! As a modifier,
  -- this function MUST create a copy of it before returning
  local cache = {}
  for key, val in pairs(ctx.params) do
    if type(key) == 'string' then cache[key:upper()] = val else
    cache[key] = val end
  end
  ctx.params = cache
  return context_iterate(ctx, 1)
end

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

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

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

-- Syntax:  #invoke:params|renaming_by_replacing|target|replace|[count]|[plain
--          flag]|pipe to
library.renaming_by_replacing = function (ctx)
  local ptn, repl, nmax, flg, argc, die =
    load_replace_args(ctx.pipe, 'renaming_by_replacing')
  if die then return context_iterate(ctx, argc) end
  local tbl = ctx.params
  if flg == 3 then
    local key = tonumber(ptn) or ptn:match'^%s*(.-)%s*$'
    local val = tbl[key]
    if val ~= nil then
      tbl[key] = nil
      tbl[tonumber(repl) or repl:match'^%s*(.-)%s*$'] = val
    end
  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|renaming_by_mixing|mixing string|pipe to
library.renaming_by_mixing = function (ctx)
  -- NOTE: `ctx.params` might be the original metatable! As a modifier,
  -- this function MUST create a copy of it before returning
  if ctx.pipe[1] == nil then error(modulename ..
    ', ‘renaming_by_mixing’: No mixing string was provided', 0) end
  local mix = ctx.pipe[1]:match'^%s*(.-)%s*$'
  local cache = {}
  if mix == '$@' then
    for _, val in pairs(ctx.params) do cache[val] = val end
  else
    local skel, canvas, n_parts = parse_placeholder_string(mix)
    local tmp
    for key, val in pairs(ctx.params) do
      for idx = 2, n_parts, 2 do
        if skel[idx] then canvas[idx] = val
        else canvas[idx] = tostring(key) end
      end
      tmp = table.concat(canvas)
      cache[tonumber(tmp) or tmp] = val
    end
  end
  ctx.params = cache
  return context_iterate(ctx, 2)
end

-- Syntax:  #invoke:params|renaming_to_values|pipe to
--[[
library.renaming_to_values = function (ctx)
  -- NOTE: `ctx.params` might be the original metatable! As a modifier,
  -- this function MUST create a copy of it before returning
  local cache = {}
  for _, val in pairs(ctx.params) do cache[val] = val end
  ctx.params = cache
  return context_iterate(ctx, 1)
end
]]--

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

-- Syntax:  #invoke:params|parsing|string to parse|[trim flag]|[iteration
--          delimiter setter]|[...]|[key-value delimiter setter]|[...]|pipe to
library.parsing = function (ctx)
  local opts = ctx.pipe
  if opts[1] == nil then error(modulename ..
    ', ‘parsing’: No string to parse was provided', 0) end
  local isep, iplain, psep, pplain, trimnamed, trimunnamed, argc =
    load_parse_opts(opts, 2)
  parse_parameter_string(ctx.params, opts[1], isep, iplain, psep, pplain,
    trimnamed, trimunnamed)
  return context_iterate(ctx, argc)
end

-- Syntax:  #invoke:params|reinterpreting|parameter to reinterpret|[trim
--          flag]|[iteration delimiter setter]|[...]|[key-value delimiter
--          setter]|[...]|pipe to
library.reinterpreting = function (ctx)
  local opts = ctx.pipe
  if opts[1] == nil then error(modulename ..
    ', ‘reinterpreting’: No parameter to reinterpret was provided', 0) end
  local isep, iplain, psep, pplain, trimnamed, trimunnamed, argc =
    load_parse_opts(opts, 2)
  local tbl = ctx.params
  local tmp = tonumber(opts[1]) or opts[1]:match'^%s*(.-)%s*$'
  local str = tbl[tmp]
  if str ~= nil then
    tbl[tmp] = nil
    parse_parameter_string(tbl, str, isep, iplain, psep, pplain,
      trimnamed, trimunnamed)
  end
  return context_iterate(ctx, argc)
end

-- Syntax:  #invoke:params|mixing_names_and_values|mixing string|pipe to
library.mixing_names_and_values = function (ctx)
  -- NOTE: `ctx.params` might be the original metatable! As a modifier,
  -- this function MUST create a copy of it before returning
  if ctx.pipe[1] == nil then error(modulename ..
    ', ‘mixing_names_and_values’: No mixing string was provided for parameter names', 0) end
  if ctx.pipe[2] == nil then error(modulename ..
    ', ‘mixing_names_and_values’: No mixing string was provided for parameter values', 0) end
  local mix_k = ctx.pipe[1]:match'^%s*(.-)%s*$'
  local mix_v = ctx.pipe[2]
  local cache = {}
  if mix_k == '$@' and mix_v == '$@' then
    for _, val in pairs(ctx.params) do cache[val] = val end
  elseif mix_k == '$@' and mix_v == '$#' then
    for key, val in pairs(ctx.params) do cache[val] = key end
  elseif mix_k == '$#' and mix_v == '$#' then
    for _, val in pairs(ctx.params) do cache[key] = key end
  else
    local skel_k, cnv_k, n_parts_k = parse_placeholder_string(mix_k)
    local skel_v, cnv_v, n_parts_v = parse_placeholder_string(mix_v)
    local tmp
    for key, val in pairs(ctx.params) do
      tmp = tostring(key)
      for idx = 2, n_parts_k, 2 do
        if skel_k[idx] then cnv_k[idx] = val else cnv_k[idx] = tmp end
      end
      for idx = 2, n_parts_v, 2 do
        if skel_v[idx] then cnv_v[idx] = val else cnv_v[idx] = tmp end
      end
      tmp = table.concat(cnv_k)
      cache[tonumber(tmp) or tmp] = table.concat(cnv_v)
    end
  end
  ctx.params = cache
  return context_iterate(ctx, 3)
end

-- Syntax:  #invoke:params|swapping_names_and_values|pipe to
--[[
library.swapping_names_and_values = function (ctx)
  -- NOTE: `ctx.params` might be the original metatable! As a modifier,
  -- this function MUST create a copy of it before returning
  local cache = {}
  for key, val in pairs(ctx.params) do cache[val] = key end
  ctx.params = cache
  return context_iterate(ctx, 1)
end
]]--

-- Syntax:  #invoke:params|combining_by_calling|template name|new parameter
--          name|pipe to
library.combining_by_calling = function (ctx)
  -- NOTE: `ctx.params` might be the original metatable! As a modifier,
  -- this function MUST create a copy of it before returning
  local tname = ctx.pipe[1]
  if tname ~= nil then tname = tname:match'^%s*(.*%S)'
  else error(modulename ..
    ', ‘combining_by_calling’: No template name was provided', 0) end
  local merge_into = ctx.pipe[2]
  if merge_into == nil then error(modulename ..
    ', ‘combining_by_calling’: No parameter name was provided', 0) end
  merge_into = tonumber(merge_into) or merge_into:match'^%s*(.-)%s*$'
  ctx.params = {
    [merge_into] = ctx.frame:expandTemplate{
      title = tname,
      args = ctx.params
    }
  }
  return context_iterate(ctx, 3)
end

-- Syntax:  #invoke:params|snapshotting|pipe to
library.snapshotting = function (ctx)
  push_cloned_stack(ctx, ctx.params)
  return context_iterate(ctx, 1)
end

-- Syntax:  #invoke:params|remembering|pipe to
library.remembering = function (ctx)
  push_cloned_stack(ctx, ctx.oparams)
  return context_iterate(ctx, 1)
end

-- Syntax:  #invoke:params|entering_substack|[new]|pipe to
library.entering_substack = function (ctx)
  local tbl = ctx.params
  local ncurrparent = ctx.n_parents + 1
  if ctx.parents == nil then ctx.parents = { tbl }
  else ctx.parents[ncurrparent] = tbl end
  ctx.n_parents = ncurrparent
  if ctx.pipe[1] ~= nil and ctx.pipe[1]:match'^%s*new%s*$' then
    ctx.params = {}
    return context_iterate(ctx, 2)
  end
  local currsnap = ctx.n_children
  if currsnap > 0 then
    ctx.params = ctx.children[currsnap]
    ctx.children[currsnap] = nil
    ctx.n_children = currsnap - 1
  else
    local newparams = {}
    for key, val in pairs(tbl) do newparams[key] = val end
    ctx.params = newparams
  end
  return context_iterate(ctx, 1)
end

-- Syntax:  #invoke:params|pulling|parameter name|pipe to
library.pulling = function (ctx)
  local opts = ctx.pipe
  if opts[1] == nil then error(modulename ..
    ', ‘pulling’: No parameter to pull was provided', 0) end
  local parent
  local tmp = ctx.n_parents
  if tmp < 1 then parent = ctx.oparams else parent = ctx.parents[tmp] end
  tmp = tonumber(opts[1]) or opts[1]:match'^%s*(.-)%s*$'
  if parent[tmp] ~= nil then ctx.params[tmp] = parent[tmp] end
  return context_iterate(ctx, 2)
end

-- Syntax:  #invoke:params|detaching_substack|pipe to
library.detaching_substack = function (ctx)
  local ncurrparent = ctx.n_parents
  if ncurrparent < 1 then error(modulename ..
    ', ‘detaching_substack’: No substack has been created', 0) end
  local parent = ctx.parents[ncurrparent]
  for key in pairs(ctx.params) do parent[key] = nil end
  return context_iterate(ctx, 1)
end

-- Syntax:  #invoke:params|leaving_substack|pipe to
library.leaving_substack = function (ctx)
  local ncurrparent = ctx.n_parents
  if ncurrparent < 1 then error(modulename ..
    ', ‘leaving_substack’: No substack has been created', 0) end
  local currsnap = ctx.n_children + 1
  if ctx.children == nil then ctx.children = { ctx.params }
  else ctx.children[currsnap] = ctx.params end
  ctx.params = ctx.parents[ncurrparent]
  ctx.parents[ncurrparent] = nil
  ctx.n_parents = ncurrparent - 1
  ctx.n_children = currsnap
  return context_iterate(ctx, 1)
end

-- Syntax:  #invoke:params|merging_substack|pipe to
library.merging_substack = function (ctx)
  local ncurrparent = ctx.n_parents
  if ncurrparent < 1 then error(modulename ..
    ', ‘merging_substack’: No substack has been created', 0) end
  local parent = ctx.parents[ncurrparent]
  local child = ctx.params
  ctx.params = parent
  ctx.parents[ncurrparent] = nil
  ctx.n_parents = ncurrparent - 1
  for key, val in pairs(child) do parent[key] = val end
  return context_iterate(ctx, 1)
end

-- Syntax:  #invoke:params|flushing|pipe to
library.flushing = function (ctx)
  if ctx.n_children < 1 then error(modulename ..
    ', ‘flushing’: There are no substacks to flush', 0) end
  local parent = ctx.params
  local currsnap = ctx.n_children
  for key, val in pairs(ctx.children[currsnap]) do parent[key] = val end
  ctx.children[currsnap] = nil
  ctx.n_children = currsnap - 1
  return context_iterate(ctx, 1)
end

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

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

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

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

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

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

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

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

-- Syntax:  #invoke:params|coins|[first coin = value 1]|[second coin = value
--            2]|[...]|[last coin = value N]
library.coins = function (ctx)
  -- NOTE: `ctx.pipe` might be the original metatable!
  local opts = ctx.pipe
  local tbl = ctx.params
  for key, val in pairs(tbl) do tbl[key] = opts[tonumber(val) or val] end
  return library.list_values(ctx)
end

-- Syntax:  #invoke:params|unique_coins|[first coin = value 1]|[second coin =
--            value 2]|[...]|[last coin = value N]
library.unique_coins = function (ctx)
  local opts = ctx.pipe
  local tbl = ctx.params
  local tmp
  for key, val in pairs(tbl) do
    tmp = tonumber(val) or val
    tbl[key] = opts[tmp]
    opts[tmp] = nil
  end
  return library.list_values(ctx)
end

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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