Module:Buffer/sandbox

--[[=============================

This Module was written by Alexander Zhikun He, also known as, User:Codehydro on the English Wikipedia

All methods were developed independently and any resemblance to other string buffer libraries would be coincidental.

Furthermore, many methods will not work when compiled by standard Lua libraries as they depend on behaviors unique to

the MediaMiki Scribunto mod, which, for example, has a getmetatable() method that always returns nil on non-tables.

https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual

Source code comments may be thin at some points because they are intended to be supplemented by the documentation page:

https://en.wikipedia.org/wiki/Module:Buffer/doc

Licensed under Creative Commons Attribution-ShareAlike 3.0 Unported License

https://en.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License

https://en.wikipedia.org/wiki/Module:Buffer

https://en.wikipedia.org/wiki/User:Codehydro

============================= -- ]]

-- Performs type validation

local function Valid(v)

if v and v ~= true then --reject nil/boolean; faster than 2 type() comparisons

local str = tostring(v) --functions not filtered since unlikely passed by accident (Scribunto does not have userdata/thread types)

--tostring(string-type) returns same ref; same refs compare faster than type()

if str ~= v and str == 'table' then

return rawget(v, 1) and table.concat(v)

end

--numbers are coerced to string per table.concat op; appending in string form saves ops on repeat concat

if str ~= '' then

return str

end

end

end

local MBpairs

local noOp = function() end

do

local iMap, vMap, oMap, pIter, pOther, pFast, Next --Map

local function init() -- init = noOp after first run

function Next(t)

return next, t -- slightly faster to do this than to use select()

end

function pIter(t, k)

-- don't use rawget; accepting unmapped tables does not measurably affect performance.

k = (iMap[t] or MBpairs(t, true) and iMap[t])[not k and 1 or vMap[t][k]]

return k, t[k]

end

function pOther(t, k)

-- comparison to nil because false is a valid key

k = (oMap[t] or MBpairs(t, true) and oMap[t])[nil==k and 1 or vMap[t][k]]

return k, t[k]

end

function pFast(t, k)

-- mapless iterator; almost as fast as native ipairs; slight performance penalty when length not cached

k = not k and 1 or k < (vMap[t] or #t) and k + 1 or nil

return k, t[k]

end

local mk = {__mode = 'k'} -- use mode 'k'; found that mode 'kv' sometimes garbage collects maps mid-loop (may not error because iterators auto re-map, but that's expensive)

init = noOp

iMap = setmetatable({}, mk) -- numeric keys

vMap = setmetatable({}, mk) -- next key

oMap = setmetatable({}, mk) -- non-numeric keys

end

function MBpairs(t, ...) -- pairs always iterates in order

local iter, ex = ...

init()

iter = iter == nil

if iter and not oMap[t] and ex==nil and rawget(t, 1)~=nil and next(t, #t)==nil then--while possible to miss keys, more thorough check would negate the benefit of pFast

vMap[t] = #t return pFast, t, nil

elseif ... or not vMap[t] or select('#', ...) ~= 1 then

local ti, tn, to, n = {}, {}, {}, #t --reduces table lookups

iMap[t], vMap[t], oMap[t] = ti, tn, to

for k = 1, n do

--stage one avoids number type checking op in stage two for most numeric keys

ti[k], tn[k] = k, k + 1

end

for k in (ex or Next)(t) do

if not tn[k] then

table.insert(tonumber(k) ~= k and to or ti, k)

end

end

if #ti ~= n then

table.sort(ti)

for k = 1, #ti do

-- somewhat wasteful, but trying to avoid overwriting can be even more expensive

tn[ti[k]] = k + 1

end

end

for k = 1, #to do

tn[to[k]] = k + 1

end

end

return iter and pIter or oMap[t] and pOther or noOp, t --noOp for mapless

end

end

local parent, rawkey, spec

do

--new scope for variables not reused outside (reduces number of var names that need to checked outside of scope)

--shared meta for Buffer parent property, raw mode, and specialized functions

local mkv = {

__mode = 'kv',

__call = function(t,k,v)

t[k] = v

return k

end

}

--shared meta less memory

parent = setmetatable({}, mkv)

rawkey = setmetatable({}, mkv)

spec = setmetatable({}, mkv)

end

local MB, MBi, MBmix, buffHTML, gfuncs, noCache, Element

do

--minimize number of locals per scope to reduce time spent sifting through irrelevant variable names

local _stream

do

local stream --keep stream near top of scope

local function init(f) --init = noOp after first run

local function each(self, ...)

for k = 1, select('#', ...) do

k = Valid(select(k, ...)) -- slightly faster than table.insert(self, (Valid(select(k, ...))))

if k then

table.insert(self, k)

end

end

return self

end

init = noOp

stream = {

__call = function(t, v)

v = v and Valid(v)

--last_concat cleared before entering stream mode

return v and table.insert(t, v) or t

end,

__index = function(t, i)

--no table look up minimizes resources to retrieve the only stream function

return i == 'each' and each or MB.__index(t, i) and setmetatable(t, MB)[i]

end,

__tostring = function(t)

return setmetatable(t, MB)()

end

}

for k, v in next, MB do

stream[k] = stream[k] or v

end

setmetatable(stream, getmetatable(MB))

end

function _stream(self, ...)

init()

self.last_concat = nil

return setmetatable(self, stream):each(...)

end

end

-- helper for :getParent()-like methods (including getBuffer which does not return a parent)

local function isMBfunc(Buffer, s, ...)

--eventually should figure out to make this work for :getHTML which is very similar

return s and

(

select('#', ...) == 0 and

(

--unprefixed function names append as a string

not rawkey[s] and

tostring(s):match('^_.*') and

MB.__index(Buffer, s) and

MB.__index(Buffer, s)(Buffer) or

MBmix(Buffer, s)

) or assert( --getParent is a one-way trip so one-time assert not expensive

MB.__index(Buffer, s),

('" %s " does not match any available Module:Buffer function'):format(s)

)(Buffer, ...)

) or Buffer

end

-- helper for :_out and :_str

local function MBselect(n, ...)

local n, seps = n - 1, {select(2, ...)}

if type(seps[n])=='table' then

if buffHTML and rawget(seps[n], buffHTML) then

return ...

end

setmetatable(seps, {

__index = setmetatable(seps[n], {

__index = function(t)

return rawget(t, 1)

end

})

})[n] = nil

end

return ..., seps

end

local _inHTML

do

local lastBuffer, lastHTML

local function init(...) -- init replaced and new version called on return

local create, mwFunc = mw.html.create

do

local mwHTMLmeta = getmetatable(create())

buffHTML, mwFunc, _inHTML = setmetatable(mw.clone(mwHTMLmeta), getmetatable(MB)), mwHTMLmeta.__index -- buffHTML declared near top of module; remove _inHTML from outer scope

function init(nodes, ...)

local name, args, tag = select(... and type(...) == 'table' and 1 or 2, nil, ...)

tag = create(Valid(name), args)

if nodes then

table.insert(nodes, tag.parent and tag or rawset(tag, 'parent', parent[nodes]))

end

if args then

local a, b = args.selfClosing, args.parent

args.selfClosing, args.parent = nil

if next(args) then

Element._add(parent(tag.nodes, tag), args)

end

args.selfClosing, args.parent = a, b -- in case args is reused

end

return tag

end

for k, v in next, {[mw] = mwHTMLmeta,

__call = function(h, v)

return MBmix(spec[h.nodes] and h.nodes or spec(setmetatable(parent(h.nodes, h), MB), Element), v)

end,

__concat = false, -- false means take from MB

__eq = false

} do

buffHTML[k] = v or MB[k]

end

end

local nonSelf, BHi = {tag = true,done = true,allDone = true}, buffHTML.__index

do

local g

g = {__index = function(t, i)

if gfuncs and gfuncs[i] then

g.__index, gfuncs = gfuncs

return g.__index[i]

end

end}

setmetatable(nonSelf, g)

setmetatable(BHi, g)

end

for k in next, nonSelf do

-- any HTML objects returned by these funcs will be granted Module:Buffer enhancements

local func = mwFunc[k]

BHi[k] = function(t, ...)

local HTML = func(t, ...)

return parent[HTML] and HTML or setmetatable(parent(HTML, t), buffHTML)

end

end

do

local function joinNode(HTML, sep)

local nodes, join = HTML.nodes

if noCache and rawkey[sep] or Valid(sep) then

join, HTML.nodes = tostring(rawset(HTML, 'nodes', {MB.__call(nodes, sep)})), nodes

end

return join or tostring(HTML)

end

for k, v in next, {

getParent = function(HTML, ...)

lastHTML = HTML

return MBi.getParent(HTML:allDone(), ...)

end, -- return to Buffer that created the HTML tree

getBuffer = function(HTML, ...)

lastHTML = HTML

return isMBfunc(lastBuffer, ...)

end, -- return to last used

killParent = function(HTML, ...)

MBi.killParent(HTML:allDone(), ...)

return HTML

end,

_out = function(HTML, ...)

if ... == 0 then

MBi._out(HTML.nodes, ...)

return HTML

end

lastHTML, HTML = HTML, HTML:allDone()

local n, ops, seps = select('#', ...)

if n > 1 then

local ops, seps = MBselect(n, ...)

return parent[HTML]:_in(joinNode(HTML, rawget(seps, 0))):_out(ops, rawset(seps, buffHTML, true))

end

return parent[HTML]:_(joinNode(HTML, ...))

end,

_str = function(HTML, ...)

-- does not set lastHTML

if ... == 0 then

return joinNode(HTML, select(2, ...))

end -- passing 0 strings without calling allDone()

local HTML, n = HTML:allDone(), select('#', ...)

if n > 1 then

local ops, seps = MBselect(n, ...)

return parent[HTML]:_in(joinNode(HTML, rawget(seps, 1))):_str(ops, rawset(seps, buffHTML, true))

end

return joinNode(HTML, ...)

end,

_parent = function(HTML, ...)

table.insert(HTML.nodes, parent[HTML:allDone()]:_str(...))

return HTML

end

} do

BHi[k] = v

end

end

do

local htmlArg, skip, outFuncs = {parent = true,selfClosing = true,tagName = true}, {}

do

local out local function func(nodes, ...)

return out(parent[nodes], ...)

end

outFuncs = setmetatable({

tag = function(nodes, ...)

return parent(setmetatable(init(nodes, ...), buffHTML), parent[nodes])

end,

done = function(b, ops)

b = parent[b]

while b.parent and ops ~= 0 do

b, ops = b.parent, ops and ops - 1 or 0

end

return b

end

}, {__index = function(nodes, i)

if rawget(BHi, i) then

out = BHi[i]

return func

end -- rawget to exclude globals

end})

end

Element = {

_add = function(nodes, t)

for k, v in MBpairs(t), t, skip[t] do

(v ~= true and MBmix or noOp)(nodes, v)

end

local HTML = parent[nodes] for k, v in MBpairs(t, false) do

if htmlArg[k] then

HTML[k] = v

elseif v and v ~= true then

if nonSelf[k] then

if k == 'tag' then

if type(v) == 'table' then

skip[v], k = 1, rawset(create(Valid(v[1])), 'parent', HTML)

Element._add(spec(parent(k.nodes, k, table.insert(nodes, k)), Element), v)

if k.selfClosing then

k.nodes = nil else spec[k.nodes], parent[k.nodes] = nil

end -- free memory/reduce clutter; parent ref will auto-unset when k.nodes is nil

if not k.tagName then

k.styles, k.attributes = nil

end

else table.insert(nodes, create(v))

end

elseif mwFunc[k] then

if k == 'done' and tonumber(v) ~= v and v[1] and tonumber(v[1]) == v[1] then

skip[v] = 1

end

MBmix(outFuncs[k](nodes, skip[v] and v[1]).nodes, v)

elseif v[1] or v[2] then

k = MBi[k](nodes, unpack(v, 1, rawset(skip, v, k == '_B' and 1 or 2)[v]))

Element._add(getmetatable(k) and rawget(k, 'nodes') or k, v) -- if k is not a table, then v should not contain any extra keys or this may error.

else MBi[k](nodes, v)

end -- k probably == '_G' or '_R'

elseif mwFunc[k] then

if type(v) ~= 'table' or rawget(v, 'nodes') then

mwFunc[k](HTML, v)

else

local css = k == 'css'

for x, y in MBpairs(v, true) do

(y and y ~= true and mwFunc[k] or noOp)(HTML, css and x:gsub('_', '-') or x, y)

end -- iterate non-numbers first

for _, y in MBpairs(v, nil) do

(y and y ~= true and mwFunc[k] or noOp)(HTML, y)

end -- don't bother with gsub since text must be quoted anyhow

end

elseif rawget(Element, k) or rawget(MBi, k) then

if tonumber(v) == v or v[1] == nil or getmetatable(v) then

(Element[k] or MBi[k])(nodes, v) -- v is probably string-able object, or a table to be handled by :_all

else (Element[k] or MBi[k])(nodes, unpack(v, 1, table.maxn(v)))

end -- v is definitely a table

else mwFunc.css(HTML, k:gsub('_', '-', 1), tostring(v))

end -- oddly enough, :_add clocked its fastest runtime after adding auto-gsub as a feature

skip[v] = nil

end

end

return nodes

end

}

local tempMeta = {mode = 'v', copy = {styles = true,attributes = true}}

function tempMeta.__index(t, i)

return tempMeta.copy[i] and rawset(t, i, MBi._cc(false, 0, t.orig[i]))[i] or t.orig[i]

end

rawkey[setmetatable(Element, {__index = outFuncs, __concat = function(Element, v)

return setmetatable({nodes = spec({}, Element),orig = parent[v]}, tempMeta)

end})] = math.huge

end

function MBi:getHTML(...)

lastBuffer = self

if ... then

if select('#', ...) == 1 then

return not rawkey[s] and tostring(...):match'^_' and BHi[...] and BHi[...](lastHTML) or lastHTML(...)

else

return assert(BHi[...], ('" %s " does not match any mw.html or Buffer-mw.html function'):format(tostring(...)))(lastHTML, select(2, ...))

end

end

return lastHTML

end

function MBi:_html(...)

return MBi._(self, lastHTML, select(spec[self] == Element and select('#', ...) == 0 and 1 or 2, true, ...))

end

return init(...)

end

function _inHTML(self, ...)

local HTML = init(nil, ...)

if HTML.selfClosing and spec[self] == Element then

self.last_concat = table.insert(self, HTML)

return self

end

lastBuffer, lastHTML = self, setmetatable(parent(HTML, self), buffHTML) -- set after 'args' table processed by :_add

return HTML

end

end

local _var, unbuild do

local prev, rebuild

local function init(...) -- init replaced before return

local function pick(b, v)

return b and table.insert(b, v) or v

end

local function c(a, num)

return rawset(a.a or a, 0, a[0] and a[0] + a.c or num and a[1] or a[1]:byte())[0]

end

local same, build, alt = {__tostring = function(a, b)

return a.a[0] and pick(b, a.a.string and string.char(a.a[0]) or a.a.table and a.a[1][a.a[0]] or a.a[0])

end}, {

__index = {c = 1},

__tostring = function(t)

return t:_build()

end,

table = function(a, b)

local i = next(a[1], a[0]) or a[0] == #a[1] and next(a[1])

return pick(b, rawset(a.a or a, 0, i)[1][i])

end, -- change rate (a.c) ignored since users control the table's contents

number = function(a, b)

return pick(b, c(a, true))

end,

string = function(a, b)

return pick(b, string.char(c(a)))

end

}, {__index = function(a, i)

return a.a[i]

end, __tostring = function(a, b)

return (rawget(a, 0) and a[0] == tostring(a[0]) and rawset(a, 0, a[0]:byte()) or a).a._build(a, b) end}

local function shift(t, c)

t[0] = t[0] and t[0] + c or t:_build() and t[0] - t.c + c

if t.table then

t[0] = (t[0] - 1) % #t[1] + 1

end

end

function rebuild(...)

local v, c = ...

if v or select('#', ...) == 0 then

if v and not c then

return prev

end

local meta, c = select(v and 1 or 3, alt, c, same, 0)

return setmetatable({a = prev, _build = meta.__tostring, c = c}, meta)

elseif v == nil then

-- no-op

elseif c then

shift(prev, c) -- v == false

else prev:_build()

end

end

init, noCache = function(v, c)

prev = setmetatable({v, c = c, _build = build[type(v)] or v, [type(v)] = true, alt = {}}, build)

return prev

end, true

return init(...)

end

function unbuild(sep)

for k, v in MBpairs(sep, nil) do

k = getmetatable(v) if k and (k == build or k == alt) then

shift(v.a or v, -v.c)

end

end

end

function _var(self, ...)

local obj if ... and ... ~= true then

obj = init(...)

elseif prev then

if ... ~= false then

obj = rebuild(...)

else rebuild(...)

end

end

return obj and MBi._(self, obj, nil, true) or self

end

end

local lib; MBi = setmetatable({stream = _stream,

_inHTML = _inHTML,

_var = _var,

_ = function(self, v, ...)

local at, raw = select(select('#', ...) == 1 and ... == true and 1 or 2, nil, ...)

if raw then

rawkey[self] = math.huge else v = Valid(v)

end

if v or raw then

if at or rawkey[self] then

raw = #self

end -- if length increases by more than one after table.insert, then set rawkey[self] = math.huge; rawkey[self] may be equal to a previous 'at'

at, self.last_concat = at and (tonumber(at) ~= at and raw + at or at)

table.insert(self, select(at and 1 or 2, at, v))

if at and at < 0 or raw and #self - raw > 1 then

rawkey[self] = math.huge elseif at and #self == raw then rawkey[self] = rawkey[self] and math.max(rawkey[self], at) or at

end

end -- above line looks bizarre because one table.insert op may make length jump from 0 to 8: local wtf = {[2] = 2,[4] = 4,[8] = 8}mw.log(#wtf,table.insert(wtf,1),#wtf)

return self

end,

_nil = function(self, at, ...)

if ... ~= true and ... ~= false then

-- faster than type(...) ~= 'boolean'

if not at or at == '0' then

self[#self] = ... if ... then

rawkey[self] = math.huge

end

else

local n, v = tonumber(at), ...

if n ~= at then

if n then

n = #self + at

elseif at ~= true and select('#', ...) == 0 then

v, n = at, #self

end

end

if n then

if v == nil and n > 0 then

table.remove(self, n)

else self[math.floor(n)], rawkey[self] = v, math.huge

end -- floor position for consistency with Table library

end

end

self.last_concat = nil

end

return self

end,

_all = function(self, t, valKey)

for k, v in MBpairs(t) do

MBmix(self, v, valKey)

end

for k, v in valKey and MBpairs(t, false) or noOp, t do

if tonumber(v) then

MBi._(self, k, v) -- self not always a buffer

elseif rawget(MBi, k) and v and v ~= true then

if v[1] == nil or getmetatable(v) then

MBi[k](self, v)

else MBi[k](self, unpack(v, 1, table.maxn(v)))

end

end

end

return self

end,

_str = function(t, ...)

local n = select('#', ...)

if n > 1 then

local k, ops, seps, r = 2, MBselect(n, ...)

r = MB(t(seps[1]))

while parent[t] and ops > 1 and r:_(parent[t](seps[k]), 1) do

t, k, ops = parent[t], k + 1, ops - 1

end

return table.concat(r, seps[k] or nil)

end

return MB.__call(t, ...)

end,

_in = function (self, ...)

return parent(MB(...), self)

end,

_out = function(t, ...)

if ... == 0 then

return parent(t, parent[t], MBi._cc(t, t, MB.__call(t, (select(2, ...))), getmetatable(t)))

end -- love how :_cc needed nothing new to implement this *self pat on back*

local n = select('#', ...)

if n > 1 then

local k, ops, seps = 1, MBselect(n, ...)

while parent[t] and ops > 0 do

t, k, ops = parent[t]:_(t(seps[k])), k + 1, ops - 1

end

elseif parent[t] then

return parent[t]:_(t(...))

end

return t

end,

_cc = function(self, clear, copy, meta)

if clear then

if rawequal(clear, copy) then

return self, spec[MBi._cc] and setmetatable(spec[MBi._cc], MB) -- rawequal to avoid re-string via __eq in case both are different Buffer objects

elseif copy == true then

copy = self

end

if clear ~= 0 then

assert(type(clear) == 'table', debug.traceback('Buffer:_cc can only "clear" tables. Did you forget to call with a colon?', 2)) -- errors can be hard to trace without this

for k in self and next or noOp, clear do

rawset(clear, k, nil)

end

else

return MBi._cc(false, {unpack(copy)}, copy)

end -- copy length w/o empty strings; recursion to avoid self = false causing garbage collection (non-weak child may exist)

if self == false or copy and type(copy) == 'table' then

-- self == false means copy is a table (saves a type op for recursive calls)

meta = meta or getmetatable(copy)

if self and #copy > 1 then

-- preserves length with empty strings; developed from studying http://www.lua.org/source/5.1/ltable.c.html

local n, null, i, e = #copy, {}, math.ldexp(2, select(2, math.frexp(#copy)) - 2)

e, spec[MBi._cc], parent[null] = i - 1, null, clear

for k = 1, e do

table.insert(clear, false)

end

while i <= n do

table.insert(clear, i, ) i, null[i] = i + math.ldexp(2, select(2, math.frexp(n - i)) - 2),

end

for k = 1, e do

rawset(clear, k, nil)

end

end

for k, v in next, copy do

rawset(clear, k, type(v) == 'table' and MBi._cc(false, 0, v) or v)

end

elseif copy then

rawset(clear, 1, (Valid(copy)))

end

rawkey[setmetatable(clear, meta)], parent[clear] = rawkey[copy], parent[copy]

end

return self and rawset(self, 'last_concat', nil) or clear

end,

_parent = function(self, ...)

return parent[self] and MBi._(self, parent[self]:_str(...)) or self

end,

getParent = function(self, ...)

return isMBfunc(parent[self] or parent[parent(self, setmetatable({}, MB))], ...)

end,

killParent = function(self, ...)

return parent[self] and isMBfunc(parent[self], ...) and parent(self) or self

end,

_build = function(self, t)

table.insert(t, self())

end, -- for compatibility with mw.html:node()

last_concat = false -- prevent library check

}, {__index = function(t, i)

-- import string, mw.text, and mw.ustring libraries on an as-needed basis

local func = string[i] or mw.text[i] or mw.ustring[i] or type(i) == 'string' and mw.ustring[i:match'^u(.+)'] if func then

lib = lib or function (s, f, ...)

if parent[s] and next(s) == nil then

return s:_((f(tostring(parent[Element and (spec[s] == Element and s:allDone() or spec[parent[s]] == Element and parent[s]) or s]), ...)))

end

return f(tostring(s), ...) -- not using ternary/logical operators here to allow multiple return values

end

return rawset(t, i, i:match'^u?gsub' and function(self, p, r, ...)return lib(self, func, p, r or '', ...)end -- Why are ugsub/gsub special? because empty strings are against my religion!

or function(self, ...)return lib(self, func, ...)end)[i]

end

end})

end

function MBmix(t, v, ...)

return v and ((type(v) ~= 'table' or getmetatable(v)) and MBi._(t, v) or (select('#', ...) == 0 and spec[t] and spec[t]._add or MBi._all)(t, v, ...)) or t

end -- :_all always passes two args

local _G, new_G = _G -- localize _G for console testing (console _G ~= module _G)

return setmetatable({__index = function(t, i)

return spec[t] and spec[t][i] or MBi[i]

end,

__call = function(t, ...)

local rawsep, sep, i, j, raw = noCache and rawkey[...] and ..., ...

if i or j or rawsep or Valid(sep) then

raw, sep, i, j = rawkey[spec[t]] or rawkey[t], rawsep or Valid(sep), i and (i ~= tonumber(i) and i + #t or i), j and (j ~= tonumber(j) and j + #t or j)

if rawsep or raw and (raw >= (j or #t) or i < 1) then

raw, i, j = {}, i and math.floor(i), j and math.floor(j) -- floor for consistency with table.concat(t, sep, i, j), which ignores decimals

raw.lc, t.last_concat = t.last_concat -- temporarily unset last_concat to prevent disqualification from mapless iteration

for k, v in MBpairs(t) do

if raw[1] or not i or k >= i then

if j and k > j then break

end

if raw.s then

raw.s = table.insert(raw, tostring(sep))

end -- if sep contains v and v is a Buffer-variable, sep must be strung before v

k = Valid(v) if k then

raw.s = rawsep or sep and raw[1] and table.insert(raw, sep)

table.insert(raw, k)

end

end

end

if rawsep and not raw.s then

raw[#raw] = unbuild(sep)

end -- unbuild rawsep if final index in t was invalid

t.last_concat = raw.lc

return table.concat(raw)

end

return table.concat(t, sep, i and math.max(i, 1), j and math.min(j, #t))

end

return MB.__tostring(t)

end,

__tostring = function(t)

if t.last_concat then

return t.last_concat

end

local r = rawkey[spec[t]] or rawkey[t]

r = table.concat(r and r >= #t and MBi._all({}, t) or t)

return (noCache or rawset(t, 'last_concat', r)) and r

end,

__concat = function(a, b)

if buffHTML then

for k = 1, 2 do

local v = select(k, a, b) -- faster than for k, v in pairs{a, b} do

if v and spec[v] and spec[v] == Element then

if parent[v].selfClosing then

if rawequal(a, b) then

return (not noCache or parent[v].tagName) and v:_str(0):rep(2) or v:_str(0)..v:_str(0)

end -- rawequal avoids premature tostring of Buffer:_var objects;

b, a = select(k, b, parent[v], a)

else local temp = Element .. v --helper method; returns a mirror of parent[v]

MBmix(MBmix(parent(temp.nodes, temp), a), k == 1 and spec[b] == Element and parent[b] or b)

return buffHTML.__tostring(setmetatable(temp, {__index = parent[v], __mode = 'v'})) -- switch from tempMeta to avoid MBi._cc op of styles/attributes

end

end

end

end

return table.concat(MBmix(MBmix({}, a), b))

end,

__pairs = MBpairs,

__ipairs = MBpairs,

__eq = function(a, b)

return tostring(a) == tostring(b)

end -- avoid a == b in this module; use rawequal(a,b) when they may be different Buffers (premature tostring waste ops and is bad for Buffer:_var)

}, {__tostring = function()return''end,

__call = function(self, ...)

MB = MB or self

if new_G then

if ... and _G and ... == _G then new_G = ...

end

elseif ... and (... == _G or type(...) == 'table' and (...)._G == ...) then

local Nil, mG = {}, (...):getmetatable() or (...):setmetatable{}:getmetatable()

new_G, _G, gfuncs = ..., ..., { -- gfuncs stored for Buffer:_inHTML; new_G is a is a Module:Buffer local declared just before the final return statement.

_G = function(self, i, ...)

local X, save = rawget(new_G, i), select('#', ...) == 0 and self or ...

if i and i ~= true and not (X and save and rawequal(X, save)) and rawset(new_G, i, save) and (X ~= nil or save == nil and new_G[i] ~= nil) then

-- rawequal in case X is another buffer

local mG = getmetatable(new_G) or {__call = mG.__call}

if mG.__index then

pcall(rawset, mG.__index, i, X)

else mG.__index = setmetatable(new_G, mG) and {[i] = X}

end

end

return self, ... -- avoiding __eq with rawequal(self,save) is overkill since buffers can self-save without being passed as save

end,

_R = function(self, i, v, m)

if i ~= 'new_G' then

if i and i ~= true then rawset(new_G, i , v)

end

elseif not v or v == true or v._G ~= _G then

new_G = setmetatable(v ~= true and v or {}, {__call = mG.__call, __index = v ~= true and m ~= true and (m or new_G) or nil})

else new_G, (not m and (m ~= nil or v == new_G) and Nil or getmetatable(v)).__index = v, m ~= true and (m or new_G) or nil

end -- setting Nil.__index is noOp

return self

end,

_2 = function(self, ...)

if new_G[...] ~= nil then

return new_G[...]

end -- higher priority so Buffer:_G('new_G', ...) can prevent an overwrite

if ... == 'new_G' then

return rawset((select('#', ...) ~= 1 and MBi._R(new_G, ...) or new_G), '_G', _G)

end

return select(select('#', ...) == 1 and 1 or 2, self:_G(...)) --return only one value; 'return select(2, self:_G(...)) or self' doesn't work for returning nil

end,

_B = function(self, v)

return v or v == nil and Nil

end

} for k, v in next, gfuncs do

MBi[k] = v

end

setmetatable(Nil,{__concat = MB.__concat,__newindex = noOp,__call = noOp,__tostring = noOp,__metatable = MB,__index = setmetatable({_B = MBi._B,_ = function()return Nil

end,last_concat = ''},

{__index = function(t,i)return (MBi[i] or i and not tonumber(i)) and t._ or nil

end})})

function mG.__call(G, k, ...)

return (k._G or G.type(k) == 'table') and (G.select('#', ...) ~= 1 and G.rawset(k, ...) or G:rawset(..., k) and k) or G:rawset(k, (...)) and ...

end

end

local new = setmetatable({}, self)

if ... and (...) == new_G then

return select(2, ...) and MBmix(new:_G((select(2, ...))), select(3, ...)) or new

end

return ... and MBi._(new, ...) or new

end,

__index = function(t, i)

MB = MB or t

return MBi[i] and function(...)

return MBi[i](setmetatable({}, t), select(... == t and 2 or 1,...))

end

end

})