Module:Buffer/sandbox explained

--

-- Performs type validationlocal 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 endend

local MBpairslocal noOp = function enddo 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 = -- 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 endend

local parent, rawkey, specdo --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 = --shared meta less memory parent = setmetatable(mkv) rawkey = setmetatable(mkv) spec = setmetatable(mkv)end

local MB, MBi, MBmix, buffHTML, gfuncs, noCache, Elementdo --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 = 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, if type(seps[n])

'table' then if buffHTML and rawget(seps[n], buffHTML) then return ... end setmetatable(seps,)[n] = nil end return ..., seps end

local _inHTMLdo 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, do buffHTML[k] = v or MB[k] endend

local nonSelf, BHi =, buffHTML.__indexdo local g g = setmetatable(nonSelf, g) setmetatable(BHi, g)endfor 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) endenddo 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',)), nodes end return join or tostring(HTML) end for k, v in next, do BHi[k] = vend end do local htmlArg, skip, outFuncs =, do local out local function func(nodes, ...) return out(parent[nodes], ...) end outFuncs = setmetatable end Element = local tempMeta = 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.hugeend

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 lastHTMLend

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 =,, 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(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(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) endend 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 endend

local lib; MBi = setmetatableend

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 tend -- :_all always passes two args

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