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' , runs about 8 times faster. (While
type is generally inexpensive, any "drag" on this metamodule is multiplied by the millions of pages that transclude it.) |
This module was originally developed to optimize string concatenation in Module:Asbox but can be used in any module.
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 ..
).
Buffers can also be appended to mw.html
objects via
mw.html:node
(though not mw.html:wikitext because of type checking). (See also #usage with string/mw.text libraries)
Basic usage
require'Module:Buffer'
require'Module:Buffer'
( _G, name, save, ... )
Creates a new Module:Buffer object when it (the module) is called as a function—i.e., there is no 'main'.
Initialize the module with your "local" global variable
_G
prior to creating any Buffer objects to enable global functions. If passed _G then the next two
varargs will pass to
Buffer:_G
and any extra will pass to
Buffer:_
. If initialized without _G then all varargs will pass to
Buffer:_
( ... )
You may also use most Buffer object functions directly on the module—i.e require'Module:Buffer':function{...}
is equivalent to require'Module:Buffer'():function{...}
. The self operator, :
, and .
are interchangable when used on the Module, but is required for all other interactions with Buffer objects (other than
Buffer.last_concat
).
Buffer
Buffer:_str
for advanced string conversion.
Get Buffer as type
string by performing a function call on the Buffer object (as opposed to a call on the Module). Calling a Buffer is basically shorthand for
table.concat
, or, with no args, ( Buffer, ... )
tostring
.
( Buffer )
However, if your Buffer contains raw objects or out-of-sequence values, then the return string would be the result of empty-buffer:_all
( Buffer )( ... )
instead.[note 1]
Caching behavior and how to 'purge' | Buffer.last_concat
When strung without a separator, the result may be retrieved via Buffer.last_concat
. Future tostring operations on the Buffer will return Buffer.last_concat until it is modified. You may purge the cache by setting this key to nil, by appending a valid value and immediately removing it:
Buffer:_(0)
- _nil()
, as well as by passing an empty table to
Buffer:_c
{}
Buffer:_
- See also [[Module::stream/doc#Stream mode|
Stream mode
]] 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
-
table without a
__tostring metamethod
and whichtable[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 all other value, the result of
tostring
would be inserted so long as it is not an empty string.
( value )
Set raw
to true to force value in Buffer without tostring coercion, including invalid values.[note 1]
When passed pos
of type
number, the argument is identical to pos for
table.insert
. In fact, assuming a valid value, ( table, pos, value )
Buffer:_
( 'string', 1 )
is exactly the same as
table.insert
.
( Buffer, 1, 'string' )
Unconventionally, a pos of type
string is treated as relative to length; that is,
Buffer:_
( 'string', '-1' )
is equivalent to
Buffer:_
( 'string', #Buffer - 1 )
(obviating the need to set a local Buffer to use the length operator). 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 replacement
is provided, then this will replace the value at the pos index as long as replacement is not a boolean, in which case, this is a no-op.
When replacement is nil, the op is simply
table.remove
with string pos relative to length. Note however there is no further type checking on replacement, so, if nil nor boolean, then Buffer will be set to raw.
( Buffer, pos )
Pos cannot be omitted if replacement is passed, though a pos that is nil will be treated as
.
#Buffer
Buffer:_all
Buffer:_all
( { value, value = pos, ... }, 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
, the iteration starts at the most negative key (down to -inf), continues through any nil keys, until it reaches the most positive index and includes non-integer number keys. (Note: despite more thorough iteration, the runtime of Module:Buffer's iterator is almost statistically indistinguishable from that of ipairs. Details at #Performance and #Using the iterator outside of buffer.)
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:_
.
The iteration excludes non-number keys unless nanKeys
evaluates true. However, keep in mind non-number keys are iterated after number keys in no particular order, though order may be imposed by wrapping each pair in a table indexed at a number key.
If the value at the index is either a number or a number string then the key and value pairs will be passed as the value
and pos
argument of
Buffer:_
, respectively. Thus,
Buffer:_all
({1,2,3,'... done',[3.5]=variable and 4 or {four='1',zero=1}},true)
Buffer:_(1):_(2):_(3)
if variable then
Buffer:_(4)
else
Buffer:_'four':_('zero',1)
end
Buffer:_'... done'
If the nanKey iteration encounters a value that cannot be coerced into a number and which is not boolean, then, if the key matches the name of a Buffer function, the match will be called. The arguments, if value[1]
evaluates true, will be the return of
unpack
; otherwise, the value is passed as is.[note 2] For example:
( value, 1, table.maxn(value) )
p(_G,'arg', true):_all({'arg',arg==true and {'==true: ' ,_in={_G, 't', nil, ' awesome'}}}, true):_(t and {t(), t..'r', t..'st'})
produces: 'arg==true: awesome awesomer awesomest'
Buffer:_in
Buffer:_in
( _G, name, save, ... )
Creates and returns a new Buffer object. This does not not append the new Buffer to the parent. (See next section)
More precisely, it re-calls the Module:Buffer instance which created the Buffer object with the passed arguments and then adds a reference in the new Buffer that allows it to retrieve its parent.[note 3] Do not pass _G if the Module:Buffer instance was not initialized with global functions enabled.
Buffer:_out
Buffer:_out
( outs, sep-list, { default-sep, [out] = sep, ... } )
Joins Buffer with sep
and appends result to the parent Buffer. Returns the parent. If no parent is found, this is a no-op and returns the same Buffer.
If given more than one (non-self) argument, then the first is read as outs
—the number of :_out() to perform.[note 4] Each additional argument in sep-list
is applied as sep for that :_out operation. That is, the first sep applies to the current Buffer, the second to its parent Buffer, the third to its grandparent, and so on.
If the last vararg is a table, its first index will be applied as the default key for all nil varargs. The table may immediately follow outs (i.e. sep-list may be omitted). If it contains other keys, then the value of key N would be applied as sep for the Nth :_out() instead of default-sep, making the following two snippets equivalent:
Buffer:_out
( 4, nil, nil, nil, ' and ', {', '} )
and
Buffer:_out
( 4, {', ', [4] = ' and '} )
. A false index signifies that default-sep should not be used for that generation (an empty string will do the same).
Buffer:_str
Buffer:_str
( generations, sep-list, { default-sep, [generation] = sep, ... } )
Joins a Buffer with sep
and returns the string. Varargs are handled by the same function as
Buffer:_out
, which, if provided, this will create a new temporary Buffer and backtrack the number of generations
specified, inserting each ancestor in front of its descendants in the temporary Buffer. The sep indexed at generations + 1
will be used as the joiner for the temporary Buffer (unless the first ancestor is reached before the specified number of generations, in which case it is the index following that of the original generation).
Unlike :_out, this does not append the child into the parent. As such, even with the same arguments, it may return a different result than would be obtained from stringing the return of :_out since each parents' sep is not used to join parent and child.
Buffer:_parent
Buffer:_parent
( outs, sep-list, { default-sep, [out] = sep, ...} )
Similar to
Buffer:_out
except, instead of apending the Buffer to its parent, this calls
Buffer:_str
on the parent(s) and appends the result.
Buffer:getParent
Buffer:getParent
( functionName, ... )
Returns parent Buffer, or, if none exists, sets a newly created Buffer as the 'parent' and returns the adopted parent.Cite error: A <ref>
tag is missing the closing </ref>
(see the help page).
Buffer:_c
Nils all keys of the table referenced by clear
and unsets its metatable. If passed an empty table, this simply purges the cache at
Buffer.last_concat
.
If given a table to copy
, it will also duplicate all key-value pairs of copy into clear, passing any value of type table through
mw.clone
. The former will also acquire the latter's metatable, though, if copy is an mw.html object
, Module:Buffer's augmented version would be set instead.
If copy is not a table, then it will be inserted as the first index of the cleared table so long as copy is not nil or false.
Stream mode
Buffer:stream
Switches the Buffer to stream mode. In this mode, the Buffer call operation, instead of returning a string, now acts as streamlined version of
Buffer:_
.
When streaming, you may append a sequence of strings 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':_str' '
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':_str' '
=mw.dumpObject{A, A==B}
table#1 {
"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",
true,
}
Aside from saving two characters per string, this mode runs about 50 percent faster (which says a lot considering :_ is much faster than the .. op). Despite the lack of any operator between each call, this is still a function call; in other words, you must still wrap numbers and variables in parentheses ()
.[note 5]
Returning to normal mode
No explicit action is needed to exit stream 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.
HTML extension
Buffer:_inHTML
Buffer:_inHTML
( tagName, args )
Creates an augmented
mw.html object
. The arguments are identical to that of
mw.html.create
.
Enhancements are as follows:
- Allows
..
op to be used on Buffer-mw.html objects directly (notostring
needed). - If initialized, will store tags and wikitext in an Element-Buffer, with which you may use Module:Buffer object functions to append (and remove, etc.) values.
- Element-Buffer objects may use
Element-Buffer:_add
, which greatly reduces the code size needed to build an equivalent mw.html object.
HTML-Buffer
Converts the Buffer-HTML's holding table into an Element-Buffer Appends text or tags
Global functions
Modified ..
operator
Buffer .. value
Buffered-HTML-object .. 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.
HTML-Buffer .. value
value .. Element-Buffer
Using 'all' pairs outside of buffer
String, mw.ustring, and mw.text functions
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 thepos
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:_{''}
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)
.
- this:
For
Buffer:_c
- 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
Performance
Notes
- ^ a b Setting a Buffer to raw incurs performance penalty for all future tostring ops as it must re-validate each indexed value through
Buffer:_all
to 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) - ^ In other words, if the value is a non-number string or a table without [1] set, the value will be passed as the only arg. A function value throws an error message.
- ^ However the parent will not contain a reference to the child. Calling
Buffer:getParent
on the child without first referencing it with either the #global functions or a local variable will cause the child to become irretrievable. This is intentional as setting a reference would preventGarbage collection
on child Buffers that have no futher purpose. - ^ The first argument is not type checked. For #performance, it is read as outs only when there are multiple varargs. In short, use
Buffer:_outs
if you desire to append N generations to their parent with no separator.( ''N'', nil )
- ^ Instead of passing a number type, pass a number string (i.e.
Buffer:stream'1'
instead ofBuffer:stream(1)
). Such improves performance (and is more aesthetically pleasing in this mode).