Module:Buffer/doc
![]() | This is a documentation subpage for Module:Buffer. It may contain usage information, categories and other content that is not part of the original module page. |
![]() | Do not "beautify" the source of this metamodule. Its unconventional syntax was developed using the scientific method for performance. For example, v2~=true and v2~=false , though longer than type(v2)~='boolean' , is about 8 times faster per op. Though the
type op is normally trivial, the way this is used could cause any inefficiency to be multiplied by over a billion.
[note 1] |
This was originally developed to optimize string concatenation as a helper method within Module:Asbox, but has been generalized for all modules.
The interface for Module:Buffer objects is similar to that of mw.html
objects in that you may build complex strings with independent child nodes. In most cases, you may use Buffer objects like a normal string, including using ..
operator (though
Buffer:_
has the same role, but potentially over 10 times faster than ..
). See also: #String, mw.ustring, and mw.text libraries
Additionally, there are several specialized forms and extended objects, described further in their respective sections:
Last but not least, this module has an ordered __pairs which can be more thorough than ipairs and pairs. (Even reads nil keys!) The logical uniqueness of this iterator may be reason enough to assimilate Module:Buffer.
Usage
require'Module:Buffer'
require'Module:Buffer'
( _G, name, ... )
Creates a new Module:Buffer object when the module returned by require is called as a function—i.e., there is no 'main'.
Because public methods with useless parenthesis are a pet peeve of this developer, this forwards arguments to
Buffer:_
;
if that op is not desired, you may chain a Buffer object function directly to the module and the self-action will be redirected to a new Buffer object—
i.e require'Module:Buffer':_inHTML'table'
is equivalent to require'Module:Buffer'():_inHTML'table'
.[note 2]
The global variable
_G
is "magic" when passed as the first arg. Such enables the global functions and,
if followed by a name
string, will declare the new Buffer with that name in the global scope. If the argument following name is a table with no metatable,
it and any other
varargs are forwarded to
Buffer:_all
; otherwise, as long as the first vararg is not nil or false, this passes them to
Buffer:_
.[note 3] The _G passed may also gain a __call metamethod (details at _G object).
As a final note, if you
require text from a module which returns a Buffer object, it may be more efficient to create a new Buffer via chaining
:_in
()
after a require statement for the other module and use
Buffer:_parent
()
at the point where you would append the required text. (Best add
{{{1}}}
so future editors won't hunt for function ...:_in
at the other module)
Basic functions
Buffer object
Buffer:_str for advanced string conversion.
Get a Buffer as a
string via a function call on the Buffer object (as opposed to a call on the module).
This is basically shorthand for
table.concat( Buffer, ... )
, or, with no args,
tostring( Buffer )
.
However, if this Buffer holds raw values[note 4] or non-sequential values,
then the return is built from an iteration of its values through new-
Buffer:_all( Buffer )
Unconventionally, any i
or j of type
string would be treated as relative to length;
that is,
Buffer( '-', -1, '-3' )
is equivalent to
Buffer( '-', -1, #Buffer - 3 )
(obviating the need to set a local Buffer to use the length operator).
Note you may append a Buffer object without tostring coercion to an mw.html object via
mw.html:node
(though not mw.html:wikitext because of type checking).
Buffer.last_concat
When strung without a (valid) sep
, the result is cached at Buffer.last_concat
.
Until purged, future calls to return that Buffer as a string will return this index instead.[note 5]
This should clear automatically whenever a Buffer object function changes the contents of a Buffer.
You may manually purge the cache by setting this key to nil, as well as by passing nothing to
Buffer:_cc()
Buffer:_
Buffer:stream for a faster, simpler version of this op.
Appends a value to the Buffer. In rough terms, Buffer:_'string1':_'string2'
is the same as Buffer = Buffer..'string1'..'string2'
.
(It may help to imagine :_
as a ..
that has stood up and is now casting a shadow.)
If passed an invalid value
listed below, this is a no-op:
-
boolean
-
nil
- empty
string, or any table such that
tostring( table )
returns an empty string (or nil/false)
-
table without a __tostring metamethod and which
table[1]
is nil or false
A table with no __tostring will pass through
table.concat
before insertion. An
error may be thrown if the table would cause table.concat to error.
(Use
Buffer:_all
instead for such tables.)
When passed pos
of type
number, the argument is identical to pos for
table.insert( table, pos, value )
.
In fact, assuming a valid value,
Buffer:_( 'string', 1 )
is exactly the same as
table.insert( Buffer, 1, 'string' )
.
Just like with the position arguments of Buffer(), any pos of type
string would be treated as relative to length.
Set raw
to true to force append a value without tostring coercion, including invalid values.[note 4]
If given only two (non-self) arguments with the second being a boolean, then the second is read as raw instead.
Buffer:_nil
Buffer:_nil( pos, replacement )
Removes the value buffered at pos
. As with
Buffer:_
, a string pos string is treated as #Buffer + pos
.
If a non-boolean replacement
is provided, then it will replace the value indexed at pos. Passing a boolean as the second argument is a no-op.
When replacement is nil, the op is basically
table.remove( Buffer, pos )
. As with the positional arguments of other Buffer methods,
any numerical string pos is added to length, such that
Buffer:_nil'0'
removes the last item.
Note the only type check on replacement is a direct comparison to both booleans (nil is implied).
Any other type, including strings, sets the Buffer to raw mode.
A pos that is omitted, nil, or false has the same effect as though '0'
(or #
Buffer
) were passed.
Given only one non-numerical argument which evaluates true but
tonumber would return it as nil , this treats it as though it were passed as the second.
If passed a non-numerical pos with any other argument, including nil, this is effectively a no op (though may still purge the cache).
Buffer:_all
Buffer:_all( { ..., value = pos, functionName = args, ... }, nanKeys )
Takes a table value
, iterates through all number keys in order, appending each valid value to the end of the Buffer.
In contrast to
ipairs
, this starts at the most negative key (down to -inf) and ends at the most positive index,
continuing through any nil keys and includes non-integer number keys. (Note: despite more thorough iteration,
the runtime of Module:Buffer.__pairs is almost statisically indistinguishable from that of ipairs in most cases. Details at Performance and require'Module:Buffer'.__pairs.)
A table value that has no metatable will have its contents iterated by this function before moving on to the next value. All other data types are processed by
Buffer:_
.
By default, this ignores non-number keys unless nanKeys
evaluates true. If so, non-number keys are processed after number keys.
Keep in mind such keys are iterated in no particular order, though an order may be imposed by wrapping each pair in a table indexed at a number key.
If given a value = pos
pair, defined as a number or number string indexed at a non-number key,
then they will be passed as the value
and pos
arguments for
Buffer:_. Thus,
Buffer:_all({1,2,3,'... done',[3.5]=variable and 4 or {four='1',zero=1}},true)
produces the same result as:
Buffer:_(1):_(2):_(3)
if variable then
Buffer:_(4)
else
Buffer:_'four':_('zero',1)--vs :_all{four='1',zero=1}; less redundant would be
end -- :_all{'four',zero=1}, but that doesn't demo string numbers
Buffer:_'... done'
--returns "1234... done" if variable evaluates true or "zero123four... done" if false
If a non-number key points to a value that cannot be coerced into a coerced into a number then the pair may be treated as functionName = args
,
when functionName matches a Buffer object function and args is not boolean.
If args is such that value[1]
evaluates true, then this will pass the return of
unpack( value, 1, table.maxn(value) )
to the named function; otherwise, the value is passed as is.
[note 6]
Buffer:_in
Buffer:_in( _G, name, save, ... )
Passes any arguments to Module:Buffer to create a new Buffer object, sets an external reference to the parent Buffer and returns the child.[note 7]
This does not append the child to the parent. (See
Buffer:_out)
Also, be aware that Buffer parent references are weak. Thus, if you were to (re-)set a local variable that is currently set to the parent, such could trigger immediate garbage collection on the parent.
Buffer:_out
Buffer:_out( ops, sep–list, { default–sep, ..., [#] = sep } )
Joins a Buffer with sep
and appends result to its parent, which is returned.
If no parent is found, this is a no-op and returns the same Buffer.
When given two or more arguments, this reads the first as ops
—the number of :_out() operations to perform.[note 8] This applies the first sep in sep-list
for the current Buffer,
the second for its parent, the third for its grandparent, and so on.
If the last item is a
table, then any nil in sep-list default to table[1]
;
any false mean "no-sep".[note 9] The table may be the second arg (i.e. sep-list may be omitted).
If it has other keys, then table[n] would apply instead of table[1], making these synonymous:
Buffer:_out( 4, nil, nil, nil, ' and ', {', '} )
and
Buffer:_out( 4, {', ', [4] = ' and '} )
The number 0
is "magic" when passed as the first arg (even by itself),
joining and appending to the same Buffer after it has been emptied.
This is the only method by which a Buffer in raw mode may lose that status. Parent references are preserved.
Buffer:_str
Buffer:_str( ops, sep–list, { default–sep, ..., [#] = sep } )
Joins a Buffer with sep
and returns the string.
This uses the same helper method as
Buffer:_out
to handle multiple arguments, with which, if provided, this creates a new temporary Buffer to which this appends the results of the number of generations specified by ops
, with each ancestor in front of its descendants. This then performs one additional concat op using the sep at ops + 1
and returns the result. If a parent-less Buffer is reached before ops, then the sep that follows its op number will separate the generations.
When no valid sep is given, this returns a string identical to what would append to the next ancestor, if those arguments were passed to Buffer:_out instead and one additional :_out() op performed.
However, because this does not actually append child Buffers to their parent, the result may differ where this would insert the sep at ops + 1 and Buffer:_out would place the parent's sep between it and its child instead.
Buffer:_cc
Buffer:_cc( clear, copy, meta )
Nils all keys of the table referenced by clear
and unsets its metatable. If clear evaluates false, this simply purges the cache at
Buffer.last_concat
.
If given a table to copy
, this will duplicate all key-value pairs of copy into clear, cloning any table value recursively via Buffer:_cc(0, value)
. This returns the Buffer unless passed the number 0
as clear, which causes this to create a new table and return that instead. Passing true
as copy is equivalent to passing the Buffer itself. If copy is not a table, then it will be set as the first item in clear as long as it is not false.
While this may resemble
mw.clone
, there are several differences, namely that this:
- Gives clear the same metatable as copy (or sets
meta
, if given) as opposed to a "clone" of the metatable.
- Conserves Length attribute (though empty strings may replace some nil keys[note 10])
-
Rawsets values and iterates without invoking any __pairs metamethod.
- Includes Buffer parent and raw attributes (stored externally)
To obtain the key-value pairs left as empty strings in the previous copy op in a table, simply call this again such with value such that rawequal(clear, copy)
is true; call :getParent() on this table returns clear (useful after leeaving the local scope which referenced clear).
Buffer:_parent
Buffer:_parent( ops, sep–list, { default–sep, ..., [#] = sep } )
- To skip generations without breaking the Buffer chain, see #global functions.
Resembling the reverse of
Buffer:_out
, this calls
Buffer:_str
on the Buffer's parent with the arguments given and appends the strung ancestor(s) to the current Buffer, which is returned.
The parent is unaffected by this operation and may still be retrieved via
Buffer:_out
or re-appended again with this function.
Buffer:getParent
- Note that there is no 'getChild' method[note 7]
Returns parent Buffer, or, if none exists, creates a new Buffer and returns it as the adopted parent. As with
Buffer:_in
, this does not automatically append the adoptive child to the new parent.
If the first argument is a string matching the name of a Buffer object (or #library) function, this calls the function on the parent and forwards any additional
varargs.
When passed exactly one argument that is not a Buffer function name that starts with _
, this calls
Buffer:_( value )
Buffer:killParent
Unsets Buffer parent reference.
If passed any args, they are forwarded to the current parent, if one exists, via Buffer:getParent as a "parting gift". In either case, returns the current Buffer.
This is not necessary for
garbage collection since Buffer parent references are weak. Rather, use this when it is desirable to assign a new parent via Buffer:getParent or, for example, to prevent the inclusion of an ancestor when passing
math.huge
as ops for functions such as
Buffer:_out (more useful when recycling Module:Buffer from another Module).
Stream mode
Buffer:stream
Switches a Buffer to stream mode. While streaming, the __call metamethod will append values to the end of the Buffer
instead of the usual op.
Aside from that, there is only one other function: Stream–
Buffer:each
.
Any args passed to Buffer:stream are forwarded to that function for a reason that should be evident when you finish reading this very short section.
No special action is needed to exit this mode. The normal call to string op is restored upon the use of any regular Buffer function
or any operation which coerces the Buffer into a string (e.g. the ..
operator).
Stream-Buffer object
Stream-Buffer objects accept only one argument which they append if valid. That is, the op is a streamlined version of
Buffer:_
sans the pos and raw args, improving performance by about 50 percent (or, in one test versus ..
, over 2000 percent).
This also exploits the syntactic sugar of function calls to append a series of string literals (and tables) with nothing between them
(or only ASCII space chars if desired).
For example, both A and B will produce identical strings:
local A = require'Module:Buffer':stream'A string of text may flow''with nothing between each string' 'or perhaps only a space'
'or even tab and line-break characters''and continue to append individually''for use with a joiner'
local B = require'Module:Buffer':_'A string of text may flow':_'with nothing between each string' :_ 'or perhaps only a space'
:_'or even tab and line-break characters':_'and continue to append individually':_'for use with a joiner'
mw.log(A==B, A:_str' ')
true A string of text may flow with nothing between each string or perhaps only a space or even tab and line-break characters and continue to append individually for use with a joiner
Stream-Buffer:each
Appends an undetermined number of valid values to the Buffer object.[note 11]
If the above line gives you Déjà vu, that is because it is drawn from
mw.html:wikitext.
However, unlike mw.html:wikitext, this does not stop at the first nil value.[note 12]
Also, despite a more nuanced handling of non-string objects, this is over twice as fast as its mw.html analog and should be preferred
for appending multiple strings to (Stream-)Element-Buffers.
HTML library extension
Upon the first call to
Buffer:_inHTML
, Module:Buffer clones the mw.html object metatable, adding Module:Buffer's __eq and __concat metamethods along with a few additional functions.
Objects with this modified metatable are referred to as Buffer-HTML objects. Yet, though dressed in bells and whistles, they are only named with Buffer as an adjective since they lack most Buffer object functions.
In contrast, the Element-Buffer (returned by the function call on a Buffer-HTML object) is a true Buffer object with specialized "tricks" allowing complex structures to be built via both mw.html and Buffer object methods as well as through a builder that perhaps marries the best of both worlds.
Buffer functions for HTML
Buffer:_inHTML
Buffer:_inHTML( tagName, args )
Accepts the same parameters as
mw.html.create to create and return a modified mw.html object. As with
Buffer:_in
, this does not append the child object to the parent Buffer and instead sets a Buffer-style parent reference.
An exception to the above is when chaining this to an Element-Buffer and such produces a selfClosing tag; when both conditions are met, this appends the tag and returns to the same Buffer.[note 13]
Unlike mw.html.create, if args
has keys other than args.parent
and args.selfClosing
, it will pass for further processing through Element-BufferBuffer:_add (a cousin of
Buffer:_all that handles mw.html functions). Moreover, if passed a table where mw.html.create expects tagName, this treats it as args instead.
Finally, this does not automatically set the mw.html.parent
reference, making this an alternative to mw.html:tag
Buffer:getHTML
Buffer:getHTML( functionName, ... )
Available only after
Buffer:_inHTML
is used the first time.
Accepts the same arguments as
Buffer:getParent
, however this instead return the last Buffer-HTML object ("lastHTML") created, or, if available, the lastHTML passed to any of the following functions:
Buffer-HTML:_out (except when passed 0
)
Buffer:_html
Available only after
Buffer:_inHTML
is used the first time.
This (re-)appends the last Buffer-HTML object to the current Buffer object. The raw
and pos
args are generally the same as those in
Buffer:_
.
When called with no arguments on an Element-Buffer, this appends lastHTML without string coercion. Be warned however that if the Element-Buffer belongs to lastHTML or one of its tags, such will cause an infinite loop, which can be avoided by passing an explicit nil to append lastHTML as a string.[note 14]
HTML object functions
Buffer-HTML objects may be used like any mw.html object. (In fact, merely replacing mw.html.create
with require'Module:Buffer':_inHTML
in an existing Module should produce the same output.)
Most mw.html functions are unchanged, except
:tag
,
:done
, and
:allDone
are embedded in a wrapper function that checks whether they return a normal mw.html object. If so, switches the metatable to convert it to a Buffer-HTML object and sets a parent reference.
[note 15]
As a side bonus, the ..
may be used on Buffer-mw.html objects directly (no
tostring
needed).
Buffer-HTML object
Call this object as a function to return its .nodes
index[note 16], which this converts to an Element-Buffer object, granting it the same metatable as regular Module:Buffer objects (as well as several additional "tricks") and assigning the Buffer-HTML as its parent Buffer.[note 17]
This takes one argument which is forwarded to its Element-Buffer. Tables pass to the specialized HTML builder Element-
Buffer:_add
. All other #valid values are appended to its Element-Buffer via
Buffer:_
.
You cannot chain call regular Buffer functions on a Buffer-HTML object; however, since mw.html functions cannot read Buffer-style parent references, modified versions of methods that return the parent Buffer are available to Buffer-HTML without having to call into the Element-Buffer. For convenience,
mw.html:allDone
is called automatically prior to the op, though after the lastHTML
reference has been set for
Buffer:getHTML
.[note 18]
0 if passed 0
as the first argument, these do not redirect the self-action via :allDone()
, however, these always return a Buffer-HTML object (think Element-Buffer:done(0)
). For :_out only, the "magic" number zero permanently consolidates Buffer-HTML.nodes
(whether or not converted) just like :_out(0)
on a normal Buffer except it would not unset raw mode on an Element-Buffer.
$ When passed a valid sep for the first op, these temporarily[0] switch the Element-Buffer (or unconverted .nodes) with a table containing only the concatenated string before stringing the Buffer-HTML in the first op. As with the non-HTML versions, only :_out appends the string to the parent Buffer.
^ This takes arguments for
Buffer:_str
and calls it on the parent Buffer of the HTML tree, returning and appending the result to the same Buffer-HTML object. In other words, the "auto-allDone" doesn't really apply here, or at least not in the same sense as with the other functions. (As a reminder, Buffer:_inHTML does not append to the parent Buffer those Buffer-HTML objects which it returns.)
In addition to the above, global functions may be available to Buffer-HTML if enabled; these functions are the same for all Module:Buffer objects—i.e. the self action is never redirected.
Element-Buffer functions
Element-Buffer object
Sharing the same metatable as with regular Buffer objects, Element-Buffers concatenate the same way when called
to produce a string analogous to the JavaScript DOM "innerHTML" property.
In other words, when strung, it is generally the contents of the Buffer-HTML object without the "outerHTML" or tag.
There are exceptions to this "innerHTML" behavior. For instance, as appended to another object via
mw.html:node
, an Element-Buffer and its Buffer-HTML
are interchangeable (though appending the former via
Buffer:_
only includes the inner result).
Also, using the concatenation operator ..
on an Element-Buffer includes its tag in a manner depending on if it is selfClosing:
- For most tags, the conjoined string is placed inside the tag, e.g.
Buffer:_inHTML'p' 'inner text' .. 1
returns '<p>inner text1</p>'
.
- For selfClosers, the
..
op redirects to its Buffer-HTML, e.g. insert :_add{selfClosing=true}
in the above example before .. 1
to produce '<p />1'
.
You may use most Buffer object functions normally, however if there is a Buffer-HTML version,
it instead behaves as though chained on the outer HTML object.[note 19]
You may also chain any mw.html object function. Unless otherwise indicated, such returns a wrapper method that merely redirects the self-action to the outside Buffer-HTML.[note 20]
As a final note, Element-Buffers are in permanent raw mode since it is expected that some mw.html method (e.g. :tag and :node) may or will append non-string elements.[note 21]
Element-Buffer:done
When passed nothing, this should behave just like
mw.html:done
as called the "outer" HTML object—returning
Buffer-HTML.parent
, if available, or Buffer-HTML if not.
However, this has been re-designed to accept ops
, the number of :done() operations to perform. Thus,
Element–Buffer:done(4)
is equivalent to Buffer–HTML:done():done():done():done()
.
Pass 0
(zero) as dones to return to the Element-Buffer's direct HTML container.
Finally, keep in mind that Buffer-HTML objects use the original mw.html:done (albeit in a light wrapper).
Element-Buffer:tag
Element-
Buffer:tag( tagName, args )
Element-
Buffer:tag{ args–list }
This uses the same helper method as
Buffer:_inHTML to handle arguments and produce new Buffer-HTML objects,
selectively passing args to
Buffer:_add when it contains keys not used by
mw.html.create.
As may be expected, this differs from Buffer:_inHTML in that this actually appends the tag and will set a mw.html-style parent reference.
This also lacks the other function's "auto-done" feature for selfClosing tags.
As with the other Element-Buffer remake of an mw.html method, the features described here do not apply to the version used by Buffer-HTML objects.
Element-Buffer:_add
Element-
Buffer:_add{ wikitext–list, { args–list }, ..., arg = value, functionName = args }
Takes a table as its only argument. This then thoroughly iterates all number keys from lowest† to highest using this module's custom __pairs method.
Most values append as wikitext if valid. If a table is indexed at a number key, this recursively iterates the table before moving on to the next key.
After processing all number key-value pairs, this then iterates the other (non-number) keys. For those naming a core Buffer object function,
this selectively unpacks args
in a manner described at
Buffer:_all
when that function is passed the nanKey parameter (excepting that this does not read
numbers as pos, i.e. treats them the same way as strings).
This also accepts keys naming HTML and global functions as well as mw.html arguments.
Thus, Element–Buffer:_add{ tag = 'br', 'text'}
appends a BR tag after the text and
Element–Buffer:_add{ {tag = 'br' }, 'text'}
appends the BR before the text.
Note however that how this handles args for such keys depends on the particular function or argument named:
args.argName
Element–Buffer:_add{ arg = value }
The effect of passing args with keys such as args.selfClosing
and args.parent
is the same as though args were passed to
mw.html.create
.
This also takes one additional arg, i.e. args.tagName
, which value replaces the original tagName of the HTML object (or, if false, removes the tag).
Note that these are the only keys for which a boolean arg would result in a op. (For Buffer object functions that do not no-op when passed only a boolean, place the boolean in an args table for unpacking.)
args.cssName
Element–Buffer:_add{ cssName = cssValue }
A non-number key and value pair may default as the cssName
and cssValue
parameters for
mw.html:css
when
the key matches none of the three argName keys nor the name of any available function for Buffer and mw.html objects.
This sends non-boolean cssValue though
tostring prior to forwarding it to mw.html:css. Because this is the default,
any typoed or non-string key goes to mw.html:css as cssName (without string coercion). Functions not yet loaded also end up there.
The form Element-Buffer:_add{ css = { cssName = cssValue } }
also works if sacrificing performance to reduce ambiguity is your thing (or if clearing a previously set value;
see example at #args.htmlFunction for more details).
args.tag†
Element–Buffer:_add{ tag = tagName }
Element–Buffer:_add{ tag = { tagName, args–list } }
Set the key args.tag
to a string and this calls
mw.html.create with it as the tagName argument.
This then raw inserts the returned mw.html object ("tag"), emulating the effect of
mw.html:tag minus parent references, which are unnecessary.
Pair the args.tag key with a table value and this calls mw.html.create with table[1]
as tagName (or nil if #invalid), appending it as described above for string values,
but also pointing tag.parent
to the Element-Buffer's parent as well as temporarily setting the tag as the parent Buffer of tag.nodes
.
This then treats tag.nodes as a pseudo-Element-Buffer, recursing tag.nodes as "self" and the table as args,
though only iterating keys not equal to 1
(or less).[*]
Note this appends normal mw.html objects. That said, most Buffer functions named in args-list should still work as though the tag and tag.nodes
were Buffer objects.[note 22]
Upon completing a recursive iteration for args.tag, this checks if the tag is selfClosing, in which case, this saves memory (which can improve performance) by setting tag.nodes to nil.
Likewise, if its tagName property evaluates false, this nils tag.styles and tag.attributes. Such presumes those properties will not be modified afterwards,
so use mw.html:tag outside of Element-Buffer:_add if such is not the case.
args.done†
Element–Buffer:_add{ done = wikitext }
Element–Buffer:_add{ done = { ops, args–list } }
Similar to args.tag, this treats the first index of the table as the ops argument of Element-
Buffer:done
.
After calling that function, this then iterates all subsequent keys in a recursive call on the Element-Buffer of the Buffer-HTML object returned.
args.allDone
Element–Buffer:_add{ allDone = wikitext }
Element–Buffer:_add{ allDone = { args–list } }
Similar to the previous two, except that the first index is not used as an argument; thus, the entire table is iterated.
args.globalFunction‡
Element–Buffer:_add{ globalFunction = name }
Element–Buffer:_add{ globalFunction = { name, save, args–list } }
Element–Buffer:_add{ _B = { var, args–list } }
If the global functions have been loaded and a key matches one, this calls the function with the first two arguments
unpacked from the paired args-list table.
This then recursively iterates the list, excluding keys less than or equal to 2
, with whatever object is returned as self.
However, if the returned object has a metatable and object.nodes
exists, the self will be object.nodes instead.
Because
Buffer:_B
takes only one argument, args._B only unpacks the first index and starts the iteration after that key.
If neither of the first two keys evaluate true, this assumes the paired value is a string for use as the name argument for the function matching its key.[note 23] In that case, the current call stack's self
(an Element-Buffer or tag.nodes if this was called indirectly) is self' for the global function.
args.htmlFunction
Element–Buffer:_add{ htmlFunction = object }
Element–Buffer:_add{ htmlFunction = { arg-list, name = value } }
If args.key matches an mw.html object function that does not have its own args section,
this checks if the associated object is table. If not a table, or if object.nodes
evaluates true,
this calls the mw.html function with the object as the only argument.
For table objects without an object.nodes, this iterates the table (non-recursively), repeatedly calling the named mw.html function
with one or two arguments depending on key's type in each loop. Non-number key-value pairs are both passed as arguments.
For number indicies, only the value is passed. Boolean values are a no-op.
Unlike with most implementions of Module:Buffer's __pairs, this first loops through non-number keys,
followed by number keys (still ordered from lowest to highest). Thus, something like Element–Buffer:_add{ css = { 'width', width = '1em' } }
is equivalent to
Element–Buffer:css( 'width', '1em' ):css( 'width' )()
, setting the width attribute, then unsetting it for a net no-op, and returning the Element-Buffer
(though the practical purpose of such is a mystery for this developer).
† Unlike with tables passed directly, recursive iterations
for functions marked with † start at the smallest number greater than 1
instead of negative infinity.
Such was decided for performance and coding simplicity on the reasoning that any practical purpose for indexing at a key less than one may be accomplished
by instead indexing it at any of the trillion or so floating point values between one and two—e.g. 1.01
.
‡ The iteration may start after 2
for some global functions.
Loadable convenience extensions
The methods here are loaded on demand or depend on subroutines which need initialization.
Whereas most Module:Buffer methods are built for performance, these methods perform tasks
which generally require less CPU usage if handled outside of Module:Buffer (i.e. via local variable declarations).
That said, they can greatly simplify the structure of the modules which employ them by doing, in a fluent manner,
many tasks which may otherwise force an awkward interruption in Buffer call chains.
Global functions
The nodes of many chainable constructors are traversed by methods such as
HTML library's
mw.html:done
or this module's
getParent
which only go in one direction. While fine for returning to an ancestor,
such functions are unable to navigate to nodes which are a child, sibling, or cousin.
When multiple variables may each affect multiple nodes, a common solution is to assign a local variable
to each node and then break out of call chain to switch objects; the alternative being a convoluted series of logical operators.[note 24]
The global functions, as described further below, were developed to simplify the construction and maintenance
of multi-conditional structures by providing in-chain variable declaration and navigation.
These methods are enabled when your global variable is passed to the module—either in the initial call to require'Module:Buffer'
(further instructions in that section) or to
Buffer:_in
which calls the Module:Buffer metatable to produce a new Buffer object.[note 25]
Initializing this extension also adds a __call metamethod to the global _G.
Buffer:_G
Pass name
and save
to assign the object passed as save to a global variable via
rawset( _G, name, save )
.[note 26]
Pass only name and this substitutes self for save to assign the Buffer object to _G[name]
instead.
Give an explicit nil as save to unset a global. This returns the Buffer object as well as any argument given after name.
This is a no-op when name is nil or a boolean, or, when save (eventually) evaluates true and
rawequal( save, rawget(new_G, name) )
also returns true.
If the named global already exists, this "backs up" the old value by moving it to the meta __index of the global table,
setting a new metatable if none exists.[note 27] Retrieving the old value requires unsetting the new one via
Buffer:_R
(more details in that section).
If overwritten a third time, the first value is discarded and the second takes its place in the back up.
If a metaglobal variable exists but the global is nil, this sets the global without unsetting the metaglobal (i.e. does not back up a nil global).
An exception is when this is given an explicit nil as save and only the metaglobal exists;
thus, passing nil twice for the same name, unsets the key in both the global table and its metaindex.
Buffer:_R
Buffer:_R( 'new_G', var, metaindex )
This
rawset with the global table as the first argument and name
and save
as the second and third, respectively,
returning the Buffer object for call chaining.[example 1] This is a no-op if name is nil or a boolean.
Note that Buffer methods use a local variable new_G
as a proxy for the global table _G;
though not a global index, the string 'new_G'
is a "magic word"
that changes the destination for future save for this and Buffer:_G.
Pass a table as var
(same place as save) to set as the new new_G.
Any table such that var._G == _G
is treated as a (former) new_G object.
This gets the metatable of former proxies and sets a new table
with the
_G object __call method on non-new_G tables. Then, this, if third parameter metaindex
equals:
- nil — backs up the current proxy as the metaindex of the next (though this no-ops if var equals new_G to avoid cyclical indexing).
- false — leaves the metaindex intact (replacing the current proxy without back-up)
- true — unsets the metaindex of the next proxy
- any other value — sets that value as the metaindex of the next proxy. (Note new_G._G is not set until it is returned by
To omit or to pass nil/false as var has the same effect as
Buffer:_R( 'new_G', {} )
.
Pass true instead and this treats it as though passed as metaindex, creating a new proxy without backing up the incumbent.
Buffer:_2
This returns the value indexed at key name
in the global table. If it does not exist, this forwards both arguments to
Buffer:_G
and returns the saved value (which may be itself).
In other words,
Buffer:_2( name, save )
is roughly equivalent to _G[name] = _G[name] or save or save==nil and Buffer
.
The string 'new_G'
will return the Module:Buffer local variable new_G
,
used as a proxy for the global variable _G. Given more than one argument, this forwards arguments to
Buffer:_R
to assign another proxy global before returning the (newly-deposed) proxy. This then sets new_G._G
to the
original _G object for call chaining. (See § chain call in _G object).
Buffer:_B
Takes only one argument and returns that argument.
Assuming the only X declared is _G.X
and new_G equals _G, then
Buffer:_B(X)
and
Buffer:_2'X'
are equivalent.[note 28]
When passed a variable that does not exist, this returns the Buffer nil object:
Buffer-nil object
Buffer-nil()
Buffer-nil:anyName():_B( var )
The Buffer nil object is unique. Calling this as a function returns nothing (in contrast, calling an empty Buffer object returns an empty string).
This does however have the Module:Buffer __concat metamethod, which treats this the same way as any invalid object (i.e. ignores it).
Appending this via
mw.html:node
or
Buffer:_
has the same effect as appending nil.
Passing this to
tostring returns nil instead of the string 'nil'.
The only real Buffer method in its meta __index is
Buffer:_B
, however, any non-numerical key string retrieves a function that only returns the Buffer nil object for call chaining.
In a sense, you can think of Buffer:_B(var):...
as an if var~=nil then var:...
block around the following chain that ends in the the next :_B().
If cloned, the clone will be a normal Buffer object.
_G object
The first _G variable passed to this module is given a __call metamethod that self-
rawsets and returns in a manner
that depends on whether it was called directly or from a chain.[example 2] This module conserves any pre-existing metatable and alters no metamethod other than __call.
direct call
_G( k, v )
_G'string'
When called, the _G object self-sets any string passed as k
with whatever is passed as v
.
This returns v, or nil if omitted (unlike with rawset, an explicit nil is not necessary to unset a variable with direct calls).
Note that k must be a string to declare or unset a global in this op.
Tables passed as the first argument are treated as though this were executed via a call chain (discussed shortly).
Passing k which is not one of those two types will throw an error.
chain call
chained-object:_G( k, v )
chained-object:_G'string'
When used in a call chain, this rawsets the key-value pair in the chained object and returns that object.
The _G object may chain itself when returning _G is desired for another op instead of v.
In contrast to the direct op, the in-chain op will index non-string k values. Moreover, this only unsets object[k] when passed an explicitly nil v.
If v is omitted in-chain, this uses the chained object as the missing argument; thus, (chained) object:_G'string'
has identical effect and return to _G('string', object)
.
The same __call method is given to new_G objects created by Buffer:_R, however the direct call only works if its metaindex is the _G object.
Any table such that table._G
points to the _G object may chain save to itself regardless of metaindex.
Though the behavior of the chain op when v is omitted may be a dead ringer to that of Buffer:_G
when save is omitted and new_G is the chained object, mind that the Buffer object function sets keys in new_G variable
rather than the chained (Buffer) object; in other words, this is unaffected by Buffer:_R re-assigning new_G to another table.
Also, this does not have the back up behavior of Buffer:_G.
Buffer-variable object
Buffer:_var
Raw appends a Buffer-variable object, which may appear as a different value each time the object is converted to a string.[example 3]
Initialize a Buffer-variable object by passing as var
a:
- number - which, when strung the first time, appears as that number and reappears as
var + change
the next time it is strung.
- string - that transforms into the next ASCII character via
string.char( var:byte() + change )
.
- table - to return the first (non-nil) item, then the second, and so on as determined by
next( table )
, looping back to the first item after reaching the last. (Note the change argument does not apply to table-based Buffer-variables.)
- custom function - to be set as the _build and __tostring method of a variable-object, though instructions for coding such functions are beyond the scope of this manual.
Re-append the same variable object by passing true
as the first argument.
For non-table-based variables, you may specify change to append a sister version
which transforms the value at the rate specified. Sister changes are cumulative. Thus,
if the original is re-strung after a sister, its value will differ from that of its last appearance
by the sum of the original and sister rates.
Apply a change without appending a new variable object to the Buffer by passing false
.
The shift is effective immediately and may affect previously appended variable objects not yet finalized.
Pass only false (i.e., omit change) to produce the same effect as stringing the original once.
Note that the false-change is the only change table-based Buffer variables will honor.[note 29]
Pass nothing to append a version which simply repeats the result of the last stringing. While generally identical in effect to
the object generated by :_var(true, 0), the Buffer-variable will return nothing if strung before any of its sisters.
If passed an explicit nil as the first argument, this is no-op. If passed a boolean before any Buffer-variable has been initialized,
this is also a no-op. Note that any op disables future caching at
Buffer.last_concat for all Buffer objects
in your module (and in any module which may require it).
String, mw.ustring, and mw.text libraries
Modified ..
operator
Buffer .. value
Buffer-HTML .. value
This is akin to '''new-buffer'':_all
{ Buffer, value}
or
tostring( Buffer )
.. value
. HTML objects created by a Buffer may also be concatenated in this manner.
Buffer-HTML .. value
value .. Element-Buffer
Concatenate an Element-Buffer to another value to return the result inside the tag, such that:
local Buff = require'Module:Buffer':_inHTML'div'{'Section ',color='red'}
return {Buff..1,Buff..2,Buff..3}
Can be a rapid way of generating:
local section = {}
for k = 1, 3 do
table.insert(section, tostring(mw.html.create'div':css{color='red'}:wikitext('Section ', k)))
end
return section
require'Module:Buffer'.__pairs
Tips and style recommendations
- If joining Buffer with a string immediately after
:_'text'
, place a space between 'string' and the separator and use double/single quote marks to . (i.e. :_'text' " "
instead of :_'text'' '
or :_'text'(' ')
)
- Saving Module:Buffer locally, e.g.
local Buffer =
require'Module:Buffer'
, though fine, is often unnecessary since all Buffer objects can create new buffers via
For
Buffer:_
- Treat
:_
as though it were a ..
op. Wrapping strings with unnecessary ()
is akin to ( 'string1' ) .. ( 'string2' ) .. ( 'string3' )
.
- Most uses of
raw
can be avoided through careful planning with the pos
argument. That said, the performance decrease from raw is unlikely to be significant for modules transcluded on less 100,000 pages. In short, reduction in server load from avoiding raw may not be worth it if such makes the code harder to maintain.
- To insert an empty string as a placeholder for a separator without setting
raw
, pass a table containing only a empty string, like so:
Buffer:_{''}
- Raw appending a non-table is pointless since no other Scribunto type can tostring differently afterwards. However, this developer believes you are smart enough that
raw and type(v)=='table'
is a waste of server resources. (Such checks are why
mw.html:wikitext takes twice as much time to append a list of strings as Stream-Buffer:each despite their near-identical roles in an Element-Stream-Buffer).
For
Buffer:_out
and
Buffer:_str
- Something like
Buffer_str(2, false, A, false, {C, B})
will use variable A
as the parent's separator, or B
instead if A is nil, or C
if both A and B are nil.
For
Buffer:_all
- Appending values in multiple locations is one of the primary reasons why the nanKeys argument exists. While passing a boolean directly will cause an error, you can do something like...
- this:
Buffer:_all({condition and {_nil={'0', 'replacement'},Front=1,getParent='from child'}}}, true)
- versus:
Buffer:_nil('0', condition and 'replacement' or false):_(condition and 'Front', 1):getParent(condition and 'from child'):_B(child)
.
For
Buffer:_cc
- If the table reference passed as
clear
was appended raw in multiple positions, this is akin to performing
Buffer:_nil
at all positions simultaneously. (May be easier than trying to come up with a
string.gsub
pattern)
- Inserting a named empty table is raw as a placeholder to be populated later via this function may be easier than calculating pos argument of
For
Buffer:_inHTML
Buffer:_( mw.html.create'br' )
is roughly 6 times more efficient than
Buffer:_inHTML'br':_out()
, at least in terms of server CPU usage. (Though Buffer:_'<br />'
is 25 and 4 times more efficient than both examples, respectively.)
- Buffer:_inHTML is slower on the first run due to initialization. After the first run, the efficiency of Buffer:_inHTML improves by a factor of 4 (though not quite as fast as the other two).
Performance
Examples
- ^
The following demonstates how, by combining Buffer:_R and Buffer:_G, the global variable v can be declared, backed-up and replaced, replaced without back-up, restored from back-up, and removed completely:
require'Module:Buffer'
(_G,'v') -- call module with global functions enabled and declare new buffer as v
:_'A' -- append 'A' to the returned buffer
:_G('v', 1):_(v) -- _G.v = 1, shift old value (the buffer) to metaglobal.__index
:_R('v', 2):_(v) -- _G.v = 2, discard old value (1) without back-up
:_R'v':_(v) -- unset _G.v, which now defaults to metaglobal.__index.v (the buffer)
:_G('v', nil)", " -- remove back-up and join the buffer with a separator
..' and '..tostring(v)-- returns 'A, 1, 2, A12 and nil'
- ^ Saving a new_G object globally via a chain call
can prevent conflict. The follow example has a hypothetical "Module:X" that may overwrite globals declared by your module or
unwittingly discard your new_G when it passes _G to Module:Buffer (passing _G to this module resets new_G to the global table
even when the global functions are already enabled):
return require'Module:Buffer'(_G)--Return before require to show intent to return a Buffer object; chain cannot be broken
:_R(frame.args.title and --store values outside global scope if invoked with title parameter
'new_G')
:_G'myBuff' --save current Buffer in new_G
:_2'new_G' --retrieve new_G
:_G'my_G' --save new_G as global my_G
:_G('t', --save title object as my_G.t for later re-use
mw.title.new(frame.args.title or frame.args.page)
).myBuff --go to my_G.myBuff (my_G lacks the Buffer:_2 method, but doesn't need it)
:stream(my_G.t.exists --just arbitrary code to show how in-line storage may be used without breaking the chain
or warning(my_G.t), --local function warning() declared before return
require'Module:X'.main(my_G.t),
my_G.t.isSubpage and subpage(my_G.t),
... )
:_R('new_G', my_G) --set my_G as new_G again and have the new_G from Module:X as its metaindex
:_(frame.args.detail and
my_G.info
:_(frame.args.detail)--append Buffer object from Module:X's new_G.info if args.details and it exists; append detail param to result
or my_G.summary) --just append summary from Module:X if not invoked with detail param.
:_B(t and --use global t as a shorthand for "if not frame.args.title then" (t only declared a global in line 2 if no title given)
myBuff
:stream(frame.args.page,
frame.args.details2,
mw.html.create'br',
require'Module:Y'.main(frame))
or my_G.myBuff --place results in a table if invoked with title param (alternative chain call branches within Buffer:_B)
:_inHTML'table'(_addArgs)
:_parent()
)
- ^
The following contrived example demonstrates most features of
Buffer:_var:
local H, sep = require'Module:Buffer':_inHTML('div',{'Heading ',_var={nil,'odd','even'},color='blue',['text-decoration']='underline'})
:_out():_html(true):_html(true):_html(true)
sep = H:_in'This is ':_var():_' - ':_var'A':_var(3,-1):_'\n'
return H:_in(H(sep)):_(sep)
:_'math:':_var():_'+ 5 =':_var(true,5):_';':_var():_out(0,' '):_var(false):_'- 1 = ':_var()
--[[ produces:
<div style="color:blue;text-decoration:underline">Heading odd</div>This is odd - A3
<div style="color:blue;text-decoration:underline">Heading even</div>This is even - B2
<div style="color:blue;text-decoration:underline">Heading odd</div>This is odd - C1
<div style="color:blue;text-decoration:underline">Heading even</div> This is even - D0
math: 0 + 5 = 5 ; 5 - 1 = 4 --]]
Notes
Non-literal interpretations of the source code (that is, more opinion than fact) are provided here to offer additional clarity.
Overly technical details may be found here as well when including such caveats appears more likely to confuse than help those advanced-but-not-quite-fluent in Lua.
Though commentary was deemed unneccessary for the Scribunto manual, Scribunto methods are "safe" for beginners and
offer extensive in-code help in the form of
error messages. In contrast, Module:Buffer methods,
intended for intermediate-advanced coders, are built with "safeties off"—i.e., minimal type filtering and custom error warnings—to maximize performance
(though the rare
assert
may be found in methods which seldom need to be used more than once and where the default error message
seemed exceptionally vague or difficult to trace).
- ^ For instance, Module:Asbox is transcluded on about 2 million pages, which each have Asbox using Buffer functions on 10-30 variables, some of which may be strings generated by other Modules that may eventually use Module:Buffer several times. Finally, throw in the fact that many pages transclude Asbox multiple times, and you can see how a few microseconds per op could translate to hours for the job queue.
- ^ For your convience, the self operator
:
and .
are interchangable when used on the Module directly, though the self-op is required for nearly all other interactions with objects created by the Module.
- ^
Passing arguments after name is primary used when
Buffer:_in
is indirectly called by
Buffer:_all
or Element-
Buffer:_add
. For example:
require'Module:Buffer'(_G,'arg'):_all({'arg',arg and {' was saved:' ,_in={_G, 't', ' awesome'}}}, true):_(t and {t(), t..'r', t..'st'})
produces: arg was saved: awesome awesomer awesomest
- ^ a b
Appending a value "raw"—i.e. without string type coercion— sets "raw mode", which incurs a performance penalty
because future tostring ops would have to re-validate every value in the Buffer via
Buffer:_all
to insert them in a new table before passing that to table.concat (vs. passing itself directly).
That said, re-stringing a raw Buffer is still usually several times faster than using the ..
op to join an equivalent number of strings.
(See #Tips for ways to avoid using raw)
- ^
Using
Buffer:_var prevents future caching on all Buffers, though Buffers which already
unmodified Buffers will continue to return their cached version
- ^ In other words, if args is a string or a table without [1] set, it will be passed as the only argument.
Further note it is not possible to pass a
functionName = args
pair where args is numerical since such would be read as value = pos
.
Finally, passing a function type as args will throw an error message.
- ^ a b
There is no 'getChild' method. If a child needed after returning to the parent, set it locally
or use
Buffer:_G
prior to returning or it may become irretrievable. (No, Codehydro did not get lazy.
Rather, this allows garbage collection on children with no further purpose.)
- ^
For #performance, the first arg is not type checked but read as ops only when multiple args are present (or if it is the number 0);
i.e.,
Buffer:_out(2)
uses 2
as a separator. To append to the Nth ancestor with no separator,
use Buffer:_outs(N, nil)
.
- ^ Empty strings would produce the same output as false, however,
Lua string literals create objects that take up memory until garbage collected.
- ^
For example, given
{nil, 'string'}
as copy, Buffer:_cc(clear, copy)
makes #clear
equal 2
,
whereas #mw.clone{nil, 'string'}
equals 0
(as of March 2015).
This replicates length by filling clear halfway to the length of copy (the minimum needed to 'trick' Lua) and then setting nil every key that would not trigger recalculation.
As a result, keys that would resize clear when set nil are left as empty strings. Such should be fairly rare;
given tables representing every possible way to position a single nil key for all lengths between 2 and 32 (inclusive), only 8.39 percent of such tables would have its nil copied as an empty string instead.
Also note that tables returned from Buffer:_(0, copy)
have length declared on creation instead, and thus won't have extra strings attached.
The odds can be estimated using , where is the upper limit that an arbitrary nil key from copy of length ranging from 1 to is imaged as an empty string.
- ^ This is no different than calling the Stream-Buffer object directly
with each item in the
expression list
; however, after noting how numbers and variables (too shy to skinny dip without parenthesis)
could look rather odd swimming fully clothed in a stream of naked strings, this was made for those whose aesthetics prefer
Stream-
Buffer:each('A',arg,'B',arg2)
over Stream–Buffer'A'(arg)'B'(arg2)
.
As a side note, it is best practice to pass number strings instead of number literals (i.e. Buffer:stream'1'
instead of Buffer:stream(1)
)
as such improves performance and is perhaps more aesthetically pleasing in this mode.
- ^
If you want something like
:wikitext('string1', varName, 'string2')
such that varName is shorthand for an if
statement that
appends varName and 'string2' when the former is not nil, use :each('string1', {varName, 'string2'})
instead.
- ^ That is, Element-Buffer
:_inHTML'br'
may be shorthand for :tag'br':done()()
when planning to continue using Buffer object functions. This "auto-done and back" for selfClosing tags does not apply to non-element Buffers in order not to encourage the use of Buffer:_inHTML for simple tags as per #Tips and style recommendations.
- ^
Buffer-HTML:_parent
serves a similar role, but appends Buffer objects rather than HTML objects. Also Buffer-HTML:_parent only appends the string form of its ancestor object(s).
- ^ Buffer(-HTML) objects reference their parent differently from mw.html objects. Passing a normal mw.html object to Buffer:_inHTML as
args.parent
and then calling
:done
the object created, followed by
Buffer:getParent
on the adopted parent, may return the "child." This is a feature rather than a bug.
- ^ —the internal table which holds elements appended via
mw.html:wikitext,
mw.html:tag, and
mw.html:node
- ^ Passing arguments for
Element-
Buffer:_add
via the args parameter of
Buffer:_inHTML
and
Buffer-HTML:tag
during the creation of a new Buffer-HTML object is the only way to use most Buffer object functions on the new HTML object without converting its .nodes
into an Element-Buffer.
- ^ That is, Buffer:getHTML may be used to return to the child node, though the trade off is that you may still need to call mw.html:allDone before using these methods in order to append the full HTML tree via Buffer:_html.
- ^ While Buffer-HTML objects may use #global functions,
there is no separate Buffer-HTML version. In other words, the self-action of a global function on an Element-Buffer is not redirected.
- ^
mw.html:allDone
is doubly wrapped for Element-Buffers, with the other wrapper setting a Buffer parent reference as described at
Buffer:_inHTML
.
Furthermore, Element-
Buffer:tag
and Element-
Buffer:done
do not call their mw.html namesakes at all, as detailed in their respective sections.
- ^ However,
since the mw.html __tostring method never calls the Element-Buffer as a function, raw mode is not expected to reduce performance when appending the Buffer-HTML (or Element-Buffer via mw.html:node)
to another object.
- ^ However,
some Buffer methods may not work properly after appending objects via mw.html functions to the psuedo-Buffer.
For example, { tag = {'div', 'List:', foo1, foo2, foo3, _out = { 0, '\n*' } } }
could produce a div with each foo as bulleted item.
But, if foo1 were { tag = { 'b', 'text' } }
, then
Buffer:_out may fail when appending
table.concat with the non-string/number element.
A workaround is to add the pair _ = {true, true}
to set raw mode on the div's tag.nodes; another is to replace foo1 with mw.html.create'b':wikitext'text'
,
which appends in string form.
- ^
A caveat of this unconventional type checking is that pairing an args.globalFunction with a number value will throw an error
(which shouldn't be a problem since numbers make poor names for global variables).
- ^
For example, mw.html objects may append a node that isn't actually "done" if a condition which affects the node also appends text after the node;
i.e. the node must be appended prematurely so that it appears before the text. Occasionally, this can get confusing for future editors
when a node is several generations removed from the declaration statement:
local x = mw.html.create():tag ... :tag'td' ... :tag'p':wikitext( ... ):tag'br':done():done()
—i.e. is x the TD, or did I miss an element in the ellipsis?
- ^
Global function are not enabled by default for various reasons:
- Relatively few modules would benefit from these methods since most templates are one-dimensional (i.e. contain few if any nested conditional statements).
- Loading them to the Module:Buffer meta index means more items that must be sifted through each time a specific function has to be retrieved for execution.
- Lua checks the global scope last; thus retrieving values from that scope takes longer than it would if they were stored in the local scope.
- If used to excess, the global scope may get so cluttered as to slow the retrieval of basic Lua functions (e.g.
type
or
pairs
) even after Buffer functions are no longer used.
It should be mentioned however that variable retrieval even even in a relatively cluttered global scope is fairly trivial. In fact, early versions of Module:Buffer
used globals extensively (and actually had no locals declared before the final return, or rather the entire module was just one long return statement).
In contrast, the current version nests many do ... end
blocks to limit scope size.
Yet,
Buffer:_
, a core function which has changed little, is only a modest 10 percent faster than itself in the last unscoped version (not published);
then again, perhaps the benefit of scope dieting has been masked by much greater total number of variables required by new features?
- ^
Actually, the first argument to rawset is a local variable
new_G
which generally equals _G but not always, to be detailed in a later section.
- ^ If the meta global has an __index which is a function (as is the case after requiring Module:No globals),
the back-up op aborts without throwing an error.
- ^
Dubbing this a "global function" is bit of a misnomer since this never retrieves anything from the global table.
While designed for in-chain navigation to Buffer objects that were self-declared as globals,
this returns any local reference or literal passed as well (allowing
Element-
Buffer:_add
to execute Buffer methods on non-Buffer objects args._B
).
- ^
False cycles tables based on
# instead of
next
, which may diverge or error if the table contains nil items.