Here naverokê

Modul:Params

Ji Wîkîpediya, ensîklopediya azad.

The {{#invoke:params}} module is designed to be adopted by those templates that want to have a deep control of their parameters. It is particularly useful to variadic templates, to which it offers the possibility to count, list, map and propagate the parameters received without knowing their number in advance.

The module offers elegant shortcuts to non variadic templates as well. Outside templates it has virtually no applications; hence, if you plan to make experiments, make sure to do them from within a template, or you will not be able to see much. Under ./testcases you can find helper templates that can be specifically used for testing the module's capabilities in flexible ways (see in particular the {{./testcases/tmulti}} template).

information Not: In case your template uses this module, please add {{lua|Module:Params}} to its documentation page, so that if breaking changes will be introduced in the future the template will be easily traceable.

General usage

[çavkaniyê biguhêre]

Among the possibilities that the module offers there is that of performing a series of actions after novel arguments have been concatenated to templates' incoming parameters. As this makes it necessary to keep the argument slots clean from interference, instead of named arguments in order to specify options this module uses piping functions (i.e. functions that expect to be piped instead of returning to the caller), or modifiers. This creates a syntax similar to the following example:

{{#invoke:params|[modifier]|[...]|[modifier]|[...]|function|[...]}}

For instance, as the name suggests, the list function lists the parameters wherewith a template was called. By default it does not add delimiters, but returns an indistinct blob of text in which keys and values are sticked to each other. However, by using the setting modifier, we are able to declare a key-value delimiter (p) and an iteration delimiter (i). And so, if we imagined a template named {{Example template}} containing the following wikitext,

{{#invoke:params|setting|i/p|<br />|: |list}}

and such template were called with the following arguments,

{{Example template
| Beast of Bodmin = A large feline inhabiting Bodmin Moor
| Morgawr = A sea serpent
| Owlman = A giant owl-like creature
}}

the following result would be produced:

Owlman: A giant owl-like creature
Beast of Bodmin: A large feline inhabiting Bodmin Moor
Morgawr: A sea serpent

We can also do more sophisticated things; for instance, by exploiting the possibility to set a header (h) and a footer (f), we can transform the previous code into a generator of definition lists,

{{#invoke:params|setting|h/p/i/f|<dl><dt>|</dt><dd>|</dd><dt>|</dd></dl>|list}}

thus yielding:

Beast of Bodmin
A large feline inhabiting Bodmin Moor
Morgawr
A sea serpent
Owlman
A giant owl-like creature

By placing the with_name_matching modifier before the list function we will be able to filter some parameters out – such as, for instance, all parameter names that do not end with an “n”:

{{#invoke:params|with_name_matching|n$|setting|h/p/i/f|<dl><dt>|</dt><dd>|</dd><dt>|</dd></dl>|list}}

Thus, the previous code will produce:

Beast of Bodmin
A large feline inhabiting Bodmin Moor
Owlman
A giant owl-like creature

This mechanism has the intrinsic advantage that it allows to concatenate infinite modifiers. And so, in order to get the accurate result that we want to obtain we could write:

{{#invoke:params|non-sequential|with_name_matching|^B|with_name_matching|n$|with_value_matching|feline|setting|h/p/i/f|<dl><dt>|</dt><dd>|</dd><dt>|</dd></dl>|list}}

The two modifiers sequential and non-sequential refer to a technical jargon used in wikitext: given a parameter list, the subgroup of sequential parameters is constituted by the largest group of consecutive numerical parameters starting from |1= – this is known as the parameters' “sequence”. A parameter list that does not have a first parameter specified does not possess a sequence.

Here follows the list of functions. You might want to see also § Modifiers.

Function self
Num. of arguments0
Not affected byAny modifier
See also
{{FULLPAGENAME}}
Brief
Returns the name of the template that is calling this module
Syntax
{{#invoke:params|self}}

This argumentless function guarantees that the name of the template invoking this module is shown, regardless if this is transcluded or not.

As a possible example, if a Wikipedia page named Page X contained only a transclusion of a template named {{Foo bar}}, and the latter contained the following wikitext,

{{#invoke:params|self}}

{{FULLPAGENAME}}

if we visited Template:Foo bar we would see,

Template:Foo bar

Template:Foo bar

whereas if we visited Page X we would see:

Template:Foo bar

Page X

Therefore by writing

{{#ifeq:{{#invoke:params|self}}|{{FULLPAGENAME}}
	|Page is not being transcluded
	|Page is being transcluded
}}

it is possible to understand whether a page is being transcluded or not.

If Page X transcluded {{Foo bar 2}} and the latter were a redirect to {{Foo bar}}, we would still see

Template:Foo bar

Page X

A typical use case of this function is that of providing stable links for editing transcluded templates. E.g.:

{{edit|{{#invoke:params|self}}|edit this template}}

Another possible use case is that of transcluding a subtemplate. E.g.:

{{{{#invoke:params|self}}/my subtemplate|foo|bar}}
Function count
Num. of arguments0
Often preceeded bysequential
Not affected byall_sorted,
mapping…_by_calling,
mapping…by_invoking,
…blindly_by_calling,
…lindly_by_invoking
See also
{{#invoke:ParameterCount}}
Brief
Count the number of parameters wherewith a template was called
Syntax
{{#invoke:params|count}}

This function does not take arguments.

The number that this function yields depends on the modifiers that precede it. For instance, in a template that is called with both named and unnamed arguments,

{{#invoke:params|count}}

and

{{#invoke:params|sequential|count}}

will return different results.

concat_and_call

[çavkaniyê biguhêre]
Function concat_and_call
Num. of argumentsAd libitum
Not affected byall_sorted
See also
concat_and_invoke, concat_and_magic
Brief
Prepend numerical arguments to the current parameters or impose non-numerical arguments, then propagate everything to a custom template
Syntax
{{#invoke:params|concat_and_call|template name|[prepend 1]|[prepend 2]|[...]|[prepend n]|[named item 1=value 1]|[...]|[named item n=value n]|[...]}}

For example, if our {{Example template}} had the following code,

{{#invoke:params|concat_and_call|foo bar|elbow|earth|room|7=classy|hello=not today}}

and were called with,

{{Example template
| one
| two
| three
| hello = world
| wind = spicy
}}

the following call to the {{Foo bar}} template would be performed:

{{Foo bar
| elbow
| earth
| room
| 7 = classy
| 8 = one
| 9 = two
| 10 = three
| wind = spicy
| hello = not today
}}

If no other argument besides the template name are provided this function simply echoes the current parameters to another template.

information Not: All arguments passed to this function except the template name are not trimmed of their leading and trailing spaces. The concat_and_call function name itself, however, will be trimmed of its surrounding spaces.

concat_and_invoke

[çavkaniyê biguhêre]
Function concat_and_invoke
Num. of argumentsAd libitum
Not affected byall_sorted
See also
concat_and_call, concat_and_magic
Brief
Prepend numerical arguments to the current parameters, or impose non-numerical arguments; then propagate everything to a custom module
Syntax
{{#invoke:params|concat_and_invoke|module name|function name|[prepend 1]|[prepend 2]|[...]|[prepend n]|[named item 1=value 1]|[...]|[named item n=value n]|[...]}}

Exactly like concat_and_call, but invokes a module instead of calling a template.

information Not: All arguments passed to this function except the module name and the function name are not trimmed of their leading and trailing spaces. The concat_and_invoke function name itself, however, will be trimmed of its surrounding spaces.

concat_and_magic

[çavkaniyê biguhêre]
Function concat_and_magic
Num. of argumentsAd libitum
Not affected byall_sorted
See also
concat_and_call, concat_and_invoke
Brief
Prepend numerical arguments to the current parameters, or impose non-numerical arguments; then propagate everything to a custom parser function
Syntax
{{#invoke:params|concat_and_magic|parser function|[prepend 1]|[prepend 2]|[...]|[prepend n]|[named item 1=value 1]|[...]|[named item n=value n]|[...]}}

Exactly like concat_and_call, but calls a parser function instead of a template.

information Not: All arguments passed to this function except the magic word are not trimmed of their leading and trailing spaces. The concat_and_magic function name itself, however, will be trimmed of its surrounding spaces.

Function value_of
Num. of arguments1
Relevant runtime variablesh, f, n
Not affected byall_sorted
See also
list_values
Brief
Get the value of a single parameter
Syntax
{{#invoke:params|value_of|parameter name}}

Without modifiers this function is similar to writing {{{parameter name|}}}. With modifiers, however, it allows to reach parameters that would be unreachable without knowing their number in advance. For instance, writing

{{#invoke:params|cutting|-2|0|value_of|1}}

will expand to the value of the second-last sequential parameter, independently of how many parameters the template was called with. If no matching parameter is found this function expands to nothing. A header (h), a footer (f), and a fallback text (n) can be declared via the setting modifier – the strings assigned to the key-value pair delimiter (p), the iteration delimiter (i) and the last iteration delimiter (l) will be ignored.

Function list
Num. of argumentsAd libitum
SortableYes
Relevant runtime variablesh, p, i, l, f, n
See also
list_values
Brief
List the template parameters (both their names and their values)
Syntax
{{#invoke:params|list}}

This function does not take arguments.

If the setting modifier was not placed earlier, this function will not add delimiters, but will return an indistinct blob of text in which keys and values are sticked to each other. A header (h), a key-value pair delimiter (p), an iteration delimiter (i), a last iteration delimiter (l), a footer (f), and a fallback text (n) can be declared via setting.

For example, the following code

{{#invoke:params|setting|h/i/p/f/n|'''Parameters passed:''' |); | (|)|'''No parameters were passed'''|list}}

will generate an output similar to the following.

Parameters passed: Owlman (A giant owl-like creature); Beast of Bodmin (A large feline inhabiting Bodmin Moor); Morgawr (A sea serpent)
Function list_values
Num. of argumentsAd libitum
SortableYes
Often preceeded bysequential
Relevant runtime variablesh, i, l, f, n
See also
list, value_of, {{#invoke:separated entries}}
Brief
List the values of the incoming parameters
Syntax
{{#invoke:params|list_values}}

This function does not take arguments.

The sequential modifier often accompanies this function. If the setting modifier was not placed earlier, this function will not add delimiters, but will return an indistinct blob of text in which values are sticked to each other. A header (h), an iteration delimiter (i), a last iteration delimiter (l), a footer (f), and a fallback text (n) can be declared via setting – the string assigned to the key-value pair delimiter (p) will be ignored.

For example, the following code

{{#invoke:params|setting|h/i/p/f/n|'''Parameters passed:''' |); | (|)|'''No parameters were passed'''|list_values}}

will generate an output similar to the following.

Values of parameters passed: A giant owl-like creature; A large feline inhabiting Bodmin Moor; A sea serpent.

call_for_each

[çavkaniyê biguhêre]
Function call_for_each
Num. of argumentsAd libitum
SortableYes
Relevant runtime variablesh, i, l, f, n
See also
call_for_each_value, invoke_for_each, magic_for_each, {{#invoke:for loop}}, {{for loop}}
Brief
For each parameter passed to the caller template, call a custom template with at least two parameters (key and value)
Syntax
{{#invoke:params|call_for_each|template name|[append 1]|[append 2]|[...]|[append n]|[named param 1=value 1]|[...]|[named param n=value n]|[...] }}

All unnamed parameters following the template name will be placed after the key-value pair. Named parameters will be passed verbatim. A header (h), an iteration delimiter (i), a last iteration delimiter (l), a footer (f), and a fallback text (n) can be declared via the setting modifier – the string assigned to the key-value pair delimiter (p) will be ignored.

Calling a template for each key-value pair with

{{#invoke:params|sequential|call_for_each|foobar}}

will be different from writing

{{#invoke:params|sequential|for_each|{{foobar|$#|$@}}}}

In the first example each key-value pair will be passed to the {{foobar}} template, while in the second example the $# and $@ tokens will be expanded after the {{foobar}} template has been called. In most cases this will make no difference, however there are several situations where it will lead to nonsensical results.

information Not: All arguments passed to this function except the template name are not trimmed of their leading and trailing spaces. The call_for_each function name itself, however, will be trimmed of its surrounding spaces.

invoke_for_each

[çavkaniyê biguhêre]
Function invoke_for_each
Num. of argumentsAd libitum
SortableYes
Relevant runtime variablesh, i, l, f, n
See also
invoke_for_each_value, call_for_each, magic_for_each
Brief
For each parameter passed to the caller template, invoke a custom module function with at least two arguments (key and value)
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]|[...]}}

Exactly like call_for_each, but invokes a module instead of calling a template.

Invoking a module function for each key-value pair with

{{#invoke:params|sequential|invoke_for_each|foobar|main}}

will be different from writing

{{#invoke:params|sequential|for_each|{{#invoke:foobar|main|$#|$@}}}}

In the first example each key-value pair will be passed to the {{#invoke:foobar|main}} module function, while in the second example the $# and $@ tokens will be expanded after the module function has been invoked. There might be cases in which this will make no difference, however there are several situations where it will lead to nonsensical results.

information Not: All arguments passed to this function except the module name and the function name are not trimmed of their leading and trailing spaces. The invoke_for_each function name itself, however, will be trimmed of its surrounding spaces.

magic_for_each

[çavkaniyê biguhêre]
Function magic_for_each
Num. of argumentsAd libitum
SortableYes
Relevant runtime variablesh, i, l, f, n
See also
magic_for_each_value, call_for_each, invoke_for_each
Brief
For each parameter passed to the caller template, call a magic word with at least two arguments (key and value)
Syntax
{{#invoke:params|magic_for_each|parser function|[append 1]|[append 2]|[...]|[append n]|[named param 1=value 1]|[...]|[named param n=value n]|[...]}}

Exactly like call_for_each, but calls a parser function instead of a template.

information Not: All arguments passed to this function except the magic word are not trimmed of their leading and trailing spaces. The magic_for_each function name itself, however, will be trimmed of its surrounding spaces.

call_for_each_value

[çavkaniyê biguhêre]
Function call_for_each_value
Num. of argumentsAd libitum
SortableYes
Often preceeded bysequential
Relevant runtime variablesh, i, l, f, n
See also
call_for_each, invoke_for_each_value, magic_for_each_value, {{#invoke:for loop}}, {{for loop}}
Brief
For each parameter passed to the caller template, call a custom template with at least one parameter (i.e. the parameter's value)
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]|[...]}}

The sequential modifier often accompanies this function. All unnamed parameters following the template name will be appended after the value parameter. Named parameters will be passed verbatim. A header (h), an iteration delimiter (i), a last iteration delimiter (l), a footer (f), and a fallback text (n) can be declared via the setting modifier – the string assigned to the key-value pair delimiter (p) will be ignored.

For example, calling {{tl}} with each parameter can be done by writing

{{#invoke:params|sequential|setting|i|, |call_for_each_value|tl}}

This will be different from writing

{{#invoke:params|sequential|setting|i|, |for_each|{{tl|$@}}}}

In the first example each value will be passed to the {{tl}} template, while in the second example the $@ token will be expanded after the {{tl}} template has been called. Here this will make no difference, however there are several situations where it will lead to nonsensical results.

information Not: All arguments passed to this function except the template name are not trimmed of their leading and trailing spaces. The call_for_each_value function name itself, however, will be trimmed of its surrounding spaces.

invoke_for_each_value

[çavkaniyê biguhêre]
Function invoke_for_each_value
Num. of argumentsAd libitum
SortableYes
Often preceeded bysequential
Relevant runtime variablesh, i, l, f, n
See also
call_for_each_value, invoke_for_each, magic_for_each_value
Brief
For each parameter passed to the caller template, invoke a custom module function with at least one argument (i.e. the parameter's value)
Syntax
{{#invoke:params|invoke_for_each_value|module name|module function|[append 1]|[append 2]|[...]|[append n]|[named param 1=value 1]|[...]|[named param n=value n]|[...]}}

Exactly like call_for_each_value, but invokes a module instead of calling a template.

Invoking a module function for each value with

{{#invoke:params|sequential|invoke_for_each_value|foobar|main}}

will be different from writing

{{#invoke:params|sequential|for_each|{{#invoke:foobar|main|$@}}}}

In the first example each value will be passed to the {{#invoke:foobar|main}} module function, while in the second example the $@ token will be expanded after the module function has been invoked. There might be cases in which this will make no difference, however there are several situations where it will lead to nonsensical results.

information Not: All arguments passed to this function except the module name and the function name are not trimmed of their leading and trailing spaces. The invoke_for_each_value function name itself, however, will be trimmed of its surrounding spaces.

magic_for_each_value

[çavkaniyê biguhêre]
Function magic_for_each_value
Num. of argumentsAd libitum
SortableYes
Often preceeded bysequential
Relevant runtime variablesh, i, l, f, n
See also
call_for_each_value, invoke_for_each_value, magic_for_each
Brief
For each parameter passed to the caller template, call a magic word with at least one argument (i.e. the parameter's value)
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]|[...]}}

Exactly like call_for_each_value, but calls a parser function instead of a template.

For example, if a template had the following code,

{{#invoke:params|sequential|setting|ih|&preloadparams%5b%5d{{=}}|magic_for_each_value|urlencode|QUERY}}

and were transcluded as {{example template|hello world|àèìòù|foo bar}}, the {{urlencode:...|QUERY}} parser function would be called for each incoming parameter as first argument and with QUERY as second argument, and finally the returned text would be prefixed with &preloadparams%5b%5d=. This would generate,

&preloadparams%5b%5d=hello+world&preloadparams%5b%5d=%C3%A0%C3%A8%C3%AC%C3%B2%C3%B9&preloadparams%5b%5d=foo+bar

which can be used to allow the creation of pages with preloaded text and parameters.

information Not: All arguments passed to this function except the magic word are not trimmed of their leading and trailing spaces. The magic_for_each_value function name itself, however, will be trimmed of its surrounding spaces.

Function for_each
Num. of arguments1
SortableYes
Relevant runtime variablesh, i, l, f, n
See also
list, list_values, {{#invoke:for nowiki}}, {{for nowiki}}
Brief
For each parameter passed to the caller template, expand all occurrences of $# and $@ within a given text as key and value respectively
Syntax
{{#invoke:params|for_each|wikitext}}

Example:

{{#invoke:params|for_each|Arg name: $#, Arg value: $@}}

The text returned by this function is not expanded further (currently this module does not offer an expand_for_each function). If you need wikitext expansion, use concat_and_call to propagate the incoming parameters altogether to the {{for nowiki}} template. Example:

{{#invoke:params|sequential|concat_and_call|for nowiki|[separator]|<nowiki>{{{i}}} is {{urlencode:{{{1}}}|QUERY}}</nowiki>}}

information Not: The argument passed to this function is not trimmed of its leading and trailing spaces. The for_each function name itself, however, will be trimmed of its surrounding spaces.

Modifiers (piping functions)

[çavkaniyê biguhêre]

The following are modifiers, i.e. functions that expect to be piped instead of returning to the caller. Each of them can be followed by either another modifier or a non-piping function. The actions that modifiers do are done sequentially, in the same order chosen during the invocation of this module. Some modifiers, however, after signaling their presence to the modifiers that might follow, add their action to the queue of actions that will be done last (e.g. sequential, non-sequential, all_sorted).

Modifier sequential
Num. of arguments0
RepeatableNo
Conflicts withnon-sequential, all_sorted
See also
non-sequential, all_sorted, squeezing
Brief
Reduce the parameter list to the subgroup of consecutive parameters that follow |1=
Syntax
{{#invoke:params|sequential|pipe function name}}

Example:

{{#invoke:params|sequential|count}}

This modifier does not take arguments besides the name of the function that will follow.

Using sequential together with non-sequential will generate an error.

information Not: Like non-sequential, the sequential modifier permanently marks a query. For instance, writing {{#invoke:params|sequential|with_name_not_matching|1|...}} will first mark the query as “sequential”, then will discard the first element from the sequence (leaving all the others intact). And so, no matter how many other parameters will be present, nothing will be shown.

non-sequential

[çavkaniyê biguhêre]
Modifier non-sequential
Num. of arguments0
RepeatableNo
Conflicts withsequential
See also
sequential, all_sorted
Brief
Reduce the parameter list by discarding the subgroup of consecutive parameters that follow |1=
Syntax
{{#invoke:params|non-sequential|pipe function name}}

Example:

{{#invoke:params|non-sequential|setting|ih/p|{{!}}|{{=}}|list}}

This modifier does not take arguments besides the name of the function that will follow.

Using non-sequential together with sequential will generate an error.

information Not: Like sequential, the non-sequential modifier permanently marks a query, and no matter what transformations will follow (see squeezing) the parameters' “sequence” will not be shown.

Modifier all_sorted
Num. of arguments0
RepeatableNo
Conflicts withsequential
Has no effects oncount, value_of, concat_and_call, concat_and_invoke, concat_and_magic
See also
sequential
Brief
When the time will come, all parameters will be dispatched sorted: first the numerical ones, then the rest in alphabetical order
Syntax
{{#invoke:params|all_sorted|pipe function name}}

Example:

{{#invoke:params|all_sorted|setting|ih/p|{{!}}|{{=}}|list}}

This modifier does not take arguments besides the name of the function that will follow.

Normally only sequential parameters are dispatched sorted, whereas non-sequential ones are dispatched randomly. The all_sorted modifier ensures that nothing is left out of (alphabetical) order. Attention must be paid to the fact that parameters whose name is a negative number will appear first. To avoid this the squeezing modifier can be used.[1]

The all_sorted modifier only affects the way parameters are shown, but has no effects on functions that do not iterate or cannot impose an order, such as:

information Not: The all_sorted modifier cannot be used with functions that propagate several parameters together in a single call, like concat_and_call, concat_and_invoke, and concat_and_magic, because during a call the order of arguments is always lost. For the same reason, it is not possible to guess the order of named parameters a template was invoked with.

Modifier setting
Num. of arguments2–7 (variable)
RepeatableYes
Memory slots
p Key-value delimiter
i Iteration delimiter
l Last iteration delimiter
h Header text
f Footer text
n Fallback text
Brief
Define glue strings
Syntax
{{#invoke:params|setting|directives|...|pipe function name}}

This modifier allows to set some internal variables that will be used by functions. It takes a variable number of arguments, relying on the first argument to understand how many other arguments to read. A few examples will introduce it better than words:

  • {{#invoke:params|setting|i|{{!}}|list_values}}
    ↳ Set the value of iteration delimiter to |, then list all values
  • {{#invoke:params|setting|ih|{{!}}|list_values}}
    ↳ Set the value of both header text and iteration delimiter to |, then list all values
  • {{#invoke:params|setting|ih/p|{{!}}|{{=}}|list}}
    ↳ Set the value of both header text and iteration delimiter to |, set key-value pair delimiter to =, then list all parameters
  • {{#invoke:params|setting|ih/p/n|{{!}}|{{=}}|No parameters were passed|list}}
    ↳ Set the value of both header text and iteration delimiter to |, set key-value pair delimiter to =, set fallback text to No parameters were passed, then list all parameters

The first argument is a slash-separated list of lists of slots to assign; one slot is referred by exactly one character and each list of slots maps exactly one argument. A slot indicates which internal variable to set. If more than one slot is aggregated within the same slash-separated list the same text will be assigned to more than one variable.

The slots available are the following:

Slots Variable Description
p Key-value pair delimiter The string of text that will be placed between each parameter name and its value; it is never inserted by functions that only iterate between values, or by functions that pass the key-value pairs to external calls.
i Iteration delimiter The string of text that will be placed between each iteration; it is never inserted unless there are two or more parameters to show when l is not given, or three or more parameters when l is given.
l Last iteration delimiter The string of text that will be placed between the second last and the last iteration; it is never inserted unless there are two or more parameters to show; if omitted defaults to i.
h Header text The string of text that will be placed before the iteration begins; it is never inserted if there are no parameters to show.
f Footer text The string of text that will be placed after the iteration is over; it is never inserted if there are no parameters to show.
n Fallback text The string of text that will be placed if there are no parameters to show.

All space characters in the directives arguments are discarded. Therefore writing {{#invoke:params|setting|ih/p|...}} is equivalent to writing

{{#invoke:params|setting| i
	h	/ p |...}}

In theory, instead of assigning different slots at once (i.e. {{...|setting|ih/p|{{!}}|{{=}}|...}}), it is possible to write separate invocations of setting for each variable, as in {{...|setting|ih|{{!}}|setting|p|{{=}}...}}. This method however will be slightly less efficient.

Sometimes it might be necessary to make the values assigned depend on conditional expressions. For instance, the following imaginary {{Foobar see also}} template uses the #ifexpr parser function to properly show the “and” conjunction and possibly an Oxford comma when more than two page names are provided:

{{Hatnote|{{{altphrase|Foobar see also}}}: {{#if:{{{1|}}}
	|{{#invoke:params|sequential|squeezing|setting|i/l|, |{{#ifexpr:{{#invoke:params|sequential|squeezing|count}} > 2|,}} and |trimming_values|for_each|[[$@]]}}
	|{{Error|{{tl|Foobar see also}} requires at least one page name}}
}}}}

You can find this example at {{./doc/examples/Oxford comma}}. E.g., {{module:params/doc/examples/Oxford comma|Latin|English|German|Italian}} will generate

Modul:Params/doc/examples/Oxford comma

information Not: The setting modifier will be trimmed of its surrounding spaces. The directives argument will be stripped of all space characters, including internal spaces. All the other arguments passed to this modifier will be parsed verbatim (i.e. leading and trailing spaces will not be removed).

Modifier squeezing
Num. of arguments0
RepeatableYes
See also
sequential
Brief
Rearrange all parameters that have numerical names to form a compact sequence starting from 1, keeping the same order
Syntax
{{#invoke:params|squeezing|pipe function name}}

Example:

{{#invoke:params|squeezing|sequential|setting|i/p|<br />|: |list}}

This modifier does not take arguments besides the name of the function that will follow.

The following three concatenations will lead to the same result of discarding all parameters with numerical names:

  1. {{...|non-sequential|squeezing|...}}
  2. {{...|squeezing|non-sequential|...}}
  3. {{...|with_name_not_matching|^%-?%d+$|...}}
Modifier cutting
Num. of arguments2
RepeatableYes
See also
sequential, squeezing
Brief
Remove zero or more parameters from the beginning and the end of the parameter list
Syntax
{{#invoke:params|cutting|left trim|right trim|pipe function name}}

The first argument indicates how many sequential parameters must be removed from the beginning of the parameter list, the second argument indicates how many sequential parameters must be removed from the end of the parameter list. If any of the two arguments contains a negative number its absolute value indicates what must be left on the other side – i.e. {{#invoke:params|cutting|-3|0|list}} indicates that the last three arguments must not be discarded.

Example:

{{#invoke:params|cutting|0|2|sequential|call_for_each_value|example template}}

If the absolute value of the sum of the two arguments (left and right cut) is greater than the number of sequential parameters available, the behavior will be the same as if the sum had been equal to the number of sequential parameters available, both when this is a positive value and when it is a negative value (with opposite results). After the desired sequential parameters have been discarded, all numerical parameters will be shifted accordingly.

In some cases it might be necessary to concatenate more than one invocation of the cutting modifier. For instance, the following code prints the last unnamed parameter passed, but only if at least two parameters were passed:

{{#invoke:params|sequential|cutting|1|0|cutting|-1|0|list_values}}

information Suggestion: Although {{#invoke:params|cutting|-1|1|...}} de facto gets rid of all sequential parameters, it is clearer and more idiomatic to write {{#invoke:params|non-sequential|...}} to obtain the same effect. Writing instead {{#invoke:params|sequential|cutting|-1|1|...}} will leave zero arguments to show.

with_name_matching

[çavkaniyê biguhêre]
Modifier with_name_matching
Num. of argumentsAd libitum
RepeatableYes
See also
with_name_not_matching, with_value_matching, with_value_not_matching
Brief
Discard all parameters whose name does not match any of the given patterns
Syntax
{{#invoke:params|with_name_matching|pattern 1|[plain flag 1]|[or]|[pattern 2]|[plain flag 2]|[or]|[...]|[pattern N]|[plain flag N]|pipe function name}}

Internally this modifier uses Lua's string.find() function to find whether parameter names match against given patterns; therefore, unless a target string is set to plain, please use the same syntax of Lua patterns. The plain flag can be either plain or omitted. When omitted it is assumed that the target string is a Lua pattern.

To express a logical OR the or keyword is available. To express a logical AND instead, concatenate more invocations of with_name_matching.

For the sake of argument we will imagine that we are invoking with_name_matching from within the {{Infobox artery}} template, and this is being called with the following parameters:

{{Infobox artery
| Name = Pulmonary artery
| Latin = truncus pulmonalis, arteria pulmonalis
| Image = {{Heart diagram 250px}}
| Caption = Anterior (frontal) view of the opened heart. (Pulmonary artery upper right.)
| Image2 = Alveoli diagram.png
| Caption2 = Diagram of the alveoli with both cross-section and external view.
| BranchFrom = [[right ventricle]]
| BranchTo =
| Vein = [[pulmonary vein]]
| Precursor = truncus arteriosus
| Supplies =
}}

Test cases:

  • List only the parameters whose names match against the ^Image pattern:
    {{#invoke:params|setting|ih/p|{{!}}|{{=}}|with_name_matching|^Image|list}}
    |Image={{Heart diagram 250px}}|Image2=Alveoli diagram.png
  • List the parameters whose names match against both patterns ^Image and %d+$:
    {{#invoke:params|setting|ih/p|{{!}}|{{=}}|with_name_matching|^Image|with_name_matching|%d+$|list}}
    |Image2=Alveoli diagram.png
  • List the parameters whose names match against either the ^Name or the ^Latin$ pattern:
    {{#invoke:params|setting|ih/p|{{!}}|{{=}}|with_name_matching|^Name$|or|^Latin$|list}}
    |Latin=truncus pulmonalis, arteria pulmonalis|Name=Pulmonary artery
  • List the parameters whose names match against either the ma plain string or the me$ pattern:
    {{#invoke:params|setting|ih/p|{{!}}|{{=}}|with_name_matching|ma|plain|or|me$|list}}
    |Image={{Heart diagram 250px}}|Name=Pulmonary artery|Image2=Alveoli diagram.png

information Not: The pattern arguments passed to this function are not trimmed of their leading and trailing spaces. The or and plain keywords, and the with_name_matching function name itself, however, will be trimmed of their surrounding spaces.

with_name_not_matching

[çavkaniyê biguhêre]
Modifier with_name_not_matching
Num. of argumentsAd libitum
RepeatableYes
See also
with_name_matching, with_value_matching, with_value_not_matching
Brief
Discard all parameters whose name matches all the given patterns
Syntax
{{#invoke:params|with_name_not_matching|pattern 1|[plain flag 1]|[and]|[pattern 2]|[plain flag 2]|[and]|[...]|[pattern N]|[plain flag N]|pipe function name}}

Internally this modifier uses Lua's string.find() function to find whether parameter names match against given patterns; therefore, unless a target string is set to plain, please use the same syntax of Lua patterns. The plain flag can be either plain or omitted. When omitted it is assumed that the target string is a Lua pattern.

To express a logical OR the or keyword is available. To express a logical AND instead, concatenate more invocations of with_name_not_matching.

For the sake of argument we will imagine that we are invoking with_name_not_matching from within the {{Infobox artery}} template, and this is being transcluded using the same parameters that we had imagined in the previous example at with_name_matching:

  • List only the parameters whose names do not match against the a pattern:
    {{#invoke:params|setting|ih/p|{{!}}|{{=}}|with_name_not_matching|a|list}}
    |Precursor=truncus arteriosus|Supplies=|Vein=pulmonary vein
  • List the parameters whose names do not match against the a plain string and do not match against the l plain string either:
    {{#invoke:params|setting|ih/p|{{!}}|{{=}}|with_name_not_matching|a|plain|with_name_not_matching|l|plain|list}}
    |Precursor=truncus arteriosus|Vein=pulmonary vein
  • List the parameters whose names do not match against either the a plain string or the n plain string:
    {{#invoke:params|setting|ih/p|{{!}}|{{=}}|with_name_not_matching|a|plain|or|n|plain|list}}
    |Precursor=truncus arteriosus|Supplies=|Image={{Heart diagram 250px}}|Name=Pulmonary artery|Image2=Alveoli diagram.png|Vein=pulmonary vein

It is possible to use this function to check for unknown parameters:

{{#ifexpr:{{#invoke:params|with_name_not_matching|^hello$|with_name_not_matching|^wind$|count}} > 0
	|{{#invoke:Error|error|Error: The only parameters accepted are {{para|hello}} and {{para|wind}}.}}
	|Everything is good: do something
}}

For simple cases like this, however, specialized modules are available; you might want to have a look at:

information Not: The pattern arguments passed to this function are not trimmed of their leading and trailing spaces. The or and plain keywords, and the with_name_not_matching function name itself, however, will be trimmed of their surrounding spaces.

with_value_matching

[çavkaniyê biguhêre]
Modifier with_value_matching
Num. of argumentsAd libitum
RepeatableYes
See also
with_name_matching, with_name_not_matching, with_value_not_matching
Brief
Discard all parameters whose value does not match any of the given patterns
Syntax
{{#invoke:params|with_value_matching|pattern 1|[plain flag 1]|[or]|[pattern 2]|[plain flag 2]|[or]|[...]|[pattern N]|[plain flag N]|pipe function name}}

Exactly like with_name_matching, but applied to parameter values instead of names.

Internally this modifier uses Lua's string.find() function to find whether parameter names match against given patterns; therefore, unless a target string is set to plain, please use the same syntax of Lua patterns. The plain flag can be either plain or omitted. When omitted it is assumed that the target string is a Lua pattern.

Example:

{{#invoke:params|with_value_matching|banana|count}}

information Not: The pattern arguments passed to this function are not trimmed of their leading and trailing spaces. The or and plain keywords, and the with_value_matching function name itself, however, will be trimmed of their surrounding spaces.

with_value_not_matching

[çavkaniyê biguhêre]
Modifier with_value_not_matching
Num. of argumentsAd libitum
RepeatableYes
See also
with_name_matching, with_name_not_matching, with_value_matching
Brief
Discard all parameters whose value matches all the given patterns
Syntax
{{#invoke:params|with_value_not_matching|pattern 1|[plain flag 1]|[and]|[pattern 2]|[plain flag 2]|[and]|[...]|[pattern N]|[plain flag N]|pipe function name}}

Exactly like with_name_not_matching, but applied to parameter values instead of names.

Internally this modifier uses Lua's string.find() function to find whether parameter names match against given patterns; therefore, unless a target string is set to plain, please use the same syntax of Lua patterns. The plain flag can be either plain or omitted. When omitted it is assumed that the target string is a Lua pattern.

For instance, before calling list, the following code will get rid of all blank parameters (i.e. parameters whose values contain only zero or more spaces):

{{#invoke:params|with_value_not_matching|^%s*$|setting|hi/p|{{!}}|{{=}}|list}}

information Not: The pattern arguments passed to this function are not trimmed of their leading and trailing spaces. The or and plain keywords, and the with_value_not_matching function name itself, however, will be trimmed of their surrounding spaces.

trimming_values

[çavkaniyê biguhêre]
Modifier trimming_values
Num. of arguments0
RepeatableNo
Brief
Remove leading and trailing spaces from values
Syntax
{{#invoke:params|trimming_values|pipe function name}}

This modifier does not take arguments besides the name of the function that will follow.

Most modifiers are order-dependent, therefore placing trimming_values in different positions can generate different results. For instance, imagining our {{Example template}} being called with the following spaced arguments: {{Example template| wanna | be | my | friend | ? }}. If {{Example template}} contained the following code,

{{#invoke:params|with_value_matching|%s+$|trimming_values|setting|i/p|{{!}}|{{=}}|list}}

the following text would be printed: 1=wanna|2=be|3=my|4=friend|5=?. But if instead it contained the following code,

{{#invoke:params|trimming_values|with_value_matching|%s+$|setting|i/p|{{!}}|{{=}}|list}}

no arguments would be shown.

Order affects also performance, and how many values will be trimmed of their leading and trailing spaces will depend on where trimming_values is placed. For instance, if a template were invoked with 50 parameters and its code contained {{#invoke:params|trimming_values|cutting|-1|0|list}}, first all its values would be trimmed of leading and trailing blank spaces and then its first 49 parameters would be discarded. On the other hand, writing {{#invoke:params|cutting|-1|0|trimming_values|list}} would first discard 49 parameters and then trim the only value left, resulting in a more efficient code. As a general rule, placing trimming_values as the last modifier is usually the best choice.

Placing trimming_values together with non-sequential will result in an empty call with no effects, because non-sequential parameters are stripped of their leading and trailing spaces by default.

Using trimming_values makes this module behave like many Wikipedia modules behave. For example, if we wanted to emulate {{#invoke:Separated entries|main}}, writing

{{#invoke:params|sequential|squeezing|trimming_values|setting|i|XXXX|list_values}}

will be equivalent to writing,

{{#invoke:separated entries|main|separator=XXXX}}

whereas writing

{{#invoke:params|sequential|squeezing|trimming_values|setting|i/l|XXXX|YYYY|list_values}}

will be equivalent to writing

{{#invoke:separated entries|main|separator=XXXX|conjunction=YYYY}}

The {{./doc/trim and call}} example template shows how to call any arbitrary template trimming all parameters beforehand.

mapping_values_by_calling

[çavkaniyê biguhêre]
Modifier mapping_values_by_calling
Num. of argumentsAd libitum
RepeatableYes
See also
mapping_values_by_invoking, mapping_values_blindly_by_calling, mapping_values_blindly_by_invoking
Brief
Map all parameter values, replacing their content with the expansion of a given template repeatedly called with at least two arguments (the parameter's name and value)
Syntax
{{#invoke:params|mapping_values_by_calling|template name|[number of additional arguments]|[argument 1]|[argument 2]|[...]|[argument N]|pipe function name}}

This modifier (temporarily) changes the contents of the parameters the current template is being called with, replacing each of them with the text returned by another template. The latter will be repeatedly called with at least two parameters: key and value. If the template name is followed by a number, this will be parsed as the amount of additional parameters to pass. For instance, before listing all parameters,

{{#invoke:params|mapping_values_by_calling|foobar|setting|i/p|{{!}}|{{=}}|list}}

will replace each value with the expansion of {{foobar|NAME|VALUE}} (where NAME and VALUE indicate each different name and value).

On the other hand,

{{#invoke:params|mapping_values_by_calling|foobar|2|hello|world|setting|i/p|{{!}}|{{=}}|list}}

will do the same, but using the expansion of {{foobar|NAME|VALUE|hello|world}}.

There are no mechanisms for passing non-sequential or non-numeric additional parameters to the mapping template. A similar function, mapping_values_blindly_by_calling, omits the first parameter (i.e. the parameter's name).

information Not: All arguments passed to this modifier except the template name and the number of additional arguments are not trimmed of their leading and trailing spaces. The mapping_values_by_calling modifier name itself, however, will be trimmed of its surrounding spaces.

mapping_values_by_invoking

[çavkaniyê biguhêre]
Modifier mapping_values_by_invoking
Num. of argumentsAd libitum
RepeatableYes
See also
mapping_values_by_calling, mapping_values_blindly_by_calling, mapping_values_blindly_by_invoking
Brief
Map all parameter values, replacing their content with the text returned by a given module function repeatedly invoked with at least two arguments (the parameter's name and value)
Syntax
{{#invoke:params|mapping_values_by_invoking|template name|[number of additional arguments]|[argument 1]|[argument 2]|[...]|[argument N]|pipe function name}}

This modifier (temporarily) changes the contents of the parameters the current template is being called with, replacing each of them with text returned by a custom module function. The latter will be repeatedly invoked with at least two arguments: key and value. If the function name is followed by a number, this will be parsed as the amount of additional arguments to pass. For instance, before listing all parameters,

{{#invoke:params|mapping_values_by_invoking|foobar|main|setting|i/p|{{!}}|{{=}}|list}}

will replace each value with the expansion of {{#invoke:foobar|main|NAME|VALUE}} (where NAME and VALUE indicate each different name and value).

On the other hand,

{{#invoke:params|mapping_values_by_invoking|foobar|main|2|hello|world|setting|i/p|{{!}}|{{=}}|list}}

will do the same, but using the expansion of {{#invoke:foobar|main|NAME|VALUE|hello|world}}.

There are no mechanisms for passing non-sequential or non-numeric additional arguments to the mapping module. A similar function, mapping_values_blindly_by_invoking, omits the first argument (i.e. the parameter's name).

information Not: All arguments passed to this modifier except the module name, the function name and the number of additional arguments are not trimmed of their leading and trailing spaces. The mapping_values_by_invoking modifier name itself, however, will be trimmed of its surrounding spaces.

mapping_values_blindly_by_calling

[çavkaniyê biguhêre]
Modifier mapping_values_blindly_by_calling
Num. of argumentsAd libitum
RepeatableYes
See also
mapping_values_by_calling, mapping_values_by_invoking, mapping_values_blindly_by_invoking
Brief
Map all parameter values, replacing their content with the expansion of a given template repeatedly called with at least one argument (the parameter's value)
Syntax
{{#invoke:params|mapping_values_blindly_by_calling|template name|[number of additional arguments]|[argument 1]|[argument 2]|[...]|[argument N]|pipe function name}}

This modifier (temporarily) changes the contents of the parameters the current template is being called with, replacing each of them with the text returned by another template. The latter will be repeatedly called with at least one parameter: the parameter's value. If the template name is followed by a number, this will be parsed as the amount of additional parameters to pass. For instance, before listing all parameters,

{{#invoke:params|mapping_values_blindly_by_calling|foobar|setting|i/p|{{!}}|{{=}}|list}}

will replace each value with the expansion of {{foobar|VALUE}} (where VALUE indicates each different value).

On the other hand,

{{#invoke:params|mapping_values_blindly_by_calling|foobar|2|hello|world|setting|i/p|{{!}}|{{=}}|list}}

will do the same, but using the expansion of {{foobar|VALUE|hello|world}}.

There are no mechanisms for passing non-sequential or non-numeric additional parameters to the mapping template. A similar function, mapping_values_by_calling, passes the parameter's name as well, as first argument.

information Not: All arguments passed to this modifier except the template name and the number of additional arguments are not trimmed of their leading and trailing spaces. The mapping_values_blindly_by_calling modifier name itself, however, will be trimmed of its surrounding spaces.

mapping_values_blindly_by_invoking

[çavkaniyê biguhêre]
Modifier mapping_values_blindly_by_invoking
Num. of argumentsAd libitum
RepeatableYes
See also
mapping_values_by_calling, mapping_values_by_invoking, mapping_values_blindly_by_calling
Brief
Map all parameter values, replacing their content with the text returned by a given module function repeatedly invoked with at least one argument (the parameter's value)
Syntax
{{#invoke:params|mapping_values_blindly_by_invoking|template name|[number of additional arguments]|[argument 1]|[argument 2]|[...]|[argument N]|pipe function name}}

This modifier (temporarily) changes the contents of the parameters the current template is being called with, replacing each of them with text returned by a custom module function. The latter will be repeatedly invoked with at least one argument: the parameter's value. If the function name is followed by a number, this will be parsed as the amount of additional arguments to pass. For instance, before listing all parameters,

{{#invoke:params|mapping_values_blindly_by_invoking|foobar|main|setting|i/p|{{!}}|{{=}}|list}}

will replace each value with the expansion of {{#invoke:foobar|main|VALUE}} (where VALUE indicates each different value).

On the other hand,

{{#invoke:params|mapping_values_blindly_by_invoking|foobar|main|2|hello|world|setting|i/p|{{!}}|{{=}}|list}}

will do the same, but using the expansion of {{#invoke:foobar|main|VALUE|hello|world}}.

There are no mechanisms for passing non-sequential or non-numeric additional arguments to the mapping module. A similar function, mapping_values_by_invoking, passes the parameter's name as well, as first argument.

information Not: All arguments passed to this modifier except the module name, the function name and the number of additional arguments are not trimmed of their leading and trailing spaces. The mapping_values_blindly_by_invoking modifier name itself, however, will be trimmed of its surrounding spaces.

  1. ^ To be precise, the order will not be strictly alphabetical, because this would imply that a template called with the following parameters {{foobar|-4=you|9=wanna|.=me?|11=marry|-8=do}} would see them reordered as follows: {{foobar|-8=do|-4=you|.=me?|9=wanna|11=marry}} (with the dot in the middle between negative and positive numbers). To avoid this, numbers are always displayd first (i.e. {{foobar|-8=do|-4=you|9=wanna|11=marry|.=me?}}).

	---                                        ---
	---     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
})