Module:Buffer and Module:Buffer/sandbox: Difference between pages
Appearance
(Difference between pages)
Content deleted Content added
m Protected Module:Buffer: High-risk Lua module ([Edit=Allow only template editors and admins] (indefinite) [Move=Allow only template editors and admins] (indefinite)) |
m sp |
||
Line 15: | Line 15: | ||
https://en.wikipedia.org/wiki/Module:Buffer |
https://en.wikipedia.org/wiki/Module:Buffer |
||
https://en.wikipedia.org/wiki/User:Codehydro |
https://en.wikipedia.org/wiki/User:Codehydro |
||
=============================--]] |
============================= -- ]] |
||
local function Valid(v)--type validation |
|||
-- Performs type validation |
|||
if v and v~=true then--reject nil/boolean; faster than 2 type() comparisons |
|||
local function Valid(v) |
|||
local str = tostring(v)--functions not filtered since unlikely passed by accident (Scribunto does not have userdata/thread types) |
|||
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) |
|||
if str~='' then return str end--numbers are coerced to string per table.concat op; appending in string form saves ops on repeat concat |
|||
--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 |
||
end |
end |
||
local noOp, MBpairs = function()end do local iMap, vMap, oMap, pIter, pOther, pFast, Next--Map |
|||
local MBpairs |
|||
local function init()--init = noOp after first run |
|||
local noOp = function() end |
|||
function Next(t) return next, t end--slightly faster to do this than to use select() |
|||
do |
|||
function pIter(t, k) k = (iMap[t] or MBpairs(t, true) and iMap[t])[not k and 1 or vMap[t][k]] return k, t[k] end--don't use rawget; accepting unmapped tables does not measurably affect performance. |
|||
local iMap, vMap, oMap, pIter, pOther, pFast, Next --Map |
|||
function pOther(t, k) k = (oMap[t] or MBpairs(t, true) and oMap[t])[nil==k and 1 or vMap[t][k]] return k, t[k] end--comparison to nil because false is a valid key |
|||
local function init() -- init = noOp after first run |
|||
function pFast(t, k) k = not k and 1 or k < (vMap[t] or #t) and k + 1 or nil return k, t[k] end--mapless iterator; almost as fast as native ipairs; slight performance penalty when length not cached |
|||
function Next(t) |
|||
--k and k < (vMap[t] or #t) and k + 1 or not k and 1 or nil return k, t[k] end--mapless iterator; almost as fast as native ipairs; slight performance penalty when length not cached |
|||
return next, t -- slightly faster to do this than to use select() |
|||
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) |
|||
end |
|||
init, iMap, vMap, oMap = noOp, setmetatable({}, mk), setmetatable({}, mk), setmetatable({}, mk)--iMap is numeric keys, oMap is non-numeric keys, and vMap points to next key |
|||
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 |
end |
||
function MBpairs(t, ...)--pairs always iterates in order |
|||
function MBpairs(t, ...) -- pairs always iterates in order |
|||
local iter, ex = ... |
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 |
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 |
vMap[t] = #t return pFast, t, nil |
||
elseif ... or not vMap[t] or select('#', ...)~=1 then |
elseif ... or not vMap[t] or select('#', ...) ~= 1 then |
||
local ti, tn, to, n = {}, {}, {}, #t--reduces table lookups |
local ti, tn, to, n = {}, {}, {}, #t --reduces table lookups |
||
iMap[t], vMap[t], oMap[t] = ti, tn, to |
iMap[t], vMap[t], oMap[t] = ti, tn, to |
||
for k = 1, n do |
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 |
for k in (ex or Next)(t) do |
||
if not tn[k] then |
if not tn[k] then |
||
table.insert(tonumber(k) ~= k and to or ti, k) |
|||
end |
|||
end |
end |
||
if #ti~=n then |
if #ti ~= n then |
||
table.sort(ti) |
table.sort(ti) |
||
for k = 1, #ti do |
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 |
||
for k = 1, #to do tn[to[k]] = k + 1 end |
|||
end |
end |
||
return iter and pIter or oMap[t] and pOther or noOp, t--noOp for mapless |
return iter and pIter or oMap[t] and pOther or noOp, t --noOp for mapless |
||
end |
end |
||
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) |
|||
local parent, rawkey, spec |
|||
local mkv = {__mode='kv', __call=function(t,k,v)t[k]=v return k end}--shared meta for Buffer parent property, raw mode, and specialized functions |
|||
do |
|||
parent, rawkey, spec = setmetatable({}, mkv), setmetatable({}, mkv), setmetatable({}, mkv)--shared meta less memory |
|||
--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 |
end |
||
local MB, MBi, MBmix, buffHTML, gfuncs, noCache, Element |
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 |
local _stream |
||
do |
|||
local stream --keep stream near top of scope |
|||
local function init(f)--init = noOp after first run |
|||
local function init(f) --init = noOp after first run |
|||
local function each(self, ...) |
local function each(self, ...) |
||
for k = 1, select('#', ...) do |
for k = 1, select('#', ...) do |
||
k = Valid(select(k, ...))--slightly faster than table.insert(self, (Valid(select(k, ...)))) |
k = Valid(select(k, ...)) -- slightly faster than table.insert(self, (Valid(select(k, ...)))) |
||
if k then |
if k then |
||
table.insert(self, k) |
|||
end |
|||
end |
end |
||
return self |
return self |
||
end |
end |
||
init |
init = noOp |
||
stream = { |
|||
__call = function(t, v) v = v and Valid(v) return v and table.insert(t, v) or t end,--last_concat cleared before entering stream mode |
|||
__call = function(t, v) |
|||
__index = function(t, i) return i=='each' and each or MB.__index(t, i) and setmetatable(t, MB)[i] end,--no table look up minimizes resources to retrieve the only stream function |
|||
v = v and Valid(v) |
|||
__tostring = function(t) return setmetatable(t, MB)() end |
|||
--last_concat cleared before entering stream mode |
|||
} for k, v in next, MB do stream[k] = stream[k] or v end |
|||
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)) |
setmetatable(stream, getmetatable(MB)) |
||
end |
end |
||
function _stream(self, ...) self.last_concat = init() return setmetatable(self, stream):each(...) end |
|||
function _stream(self, ...) |
|||
init() |
|||
self.last_concat = nil |
|||
return setmetatable(self, stream):each(...) |
|||
end |
|||
end |
end |
||
local function isMBfunc(Buffer, s, ...)--helper for :getParent()-like methods (including getBuffer which does not return a parent) |
|||
-- helper for :getParent()-like methods (including getBuffer which does not return a parent) |
|||
return s and (select('#', ...)==0 and--eventually should figure out to make this work for :getHTML which is very similar |
|||
local function isMBfunc(Buffer, s, ...) |
|||
(not rawkey[s] and tostring(s):match'^_.*' and MB.__index(Buffer, s) and MB.__index(Buffer, s)(Buffer) or MBmix(Buffer, s))--unprefixed function names append as a string |
|||
--eventually should figure out to make this work for :getHTML which is very similar |
|||
or assert(MB.__index(Buffer, s), ('" %s " does not match any available Module:Buffer function'):format(s))(Buffer, ...)--getParent is a one-way trip so one-time assert not expensive |
|||
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 |
) or Buffer |
||
end |
end |
||
local function MBselect(n, ...)--helper for :_out and :_str |
|||
-- helper for :_out and :_str |
|||
local function MBselect(n, ...) |
|||
local n, seps = n - 1, {select(2, ...)} |
local n, seps = n - 1, {select(2, ...)} |
||
if type(seps[n])=='table' then |
if type(seps[n])=='table' then |
||
if buffHTML and rawget(seps[n], buffHTML) 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 |
|||
setmetatable(seps, { |
|||
__index = setmetatable(seps[n], { |
|||
__index = function(t) |
|||
return rawget(t, 1) |
|||
end |
|||
}) |
|||
})[n] = nil |
|||
end |
end |
||
return ..., seps |
return ..., seps |
||
end |
end |
||
local _inHTML do local lastBuffer, lastHTML |
|||
local _inHTML |
|||
local function init(...)--init replaced and new version called on return |
|||
do |
|||
local create, mwFunc = mw.html.create do |
|||
local lastBuffer, lastHTML |
|||
local mwHTMLmeta = getmetatable(create()) |
|||
local function init(...) -- init replaced and new version called on return |
|||
buffHTML, mwFunc, _inHTML = setmetatable(mw.clone(mwHTMLmeta), getmetatable(MB)), mwHTMLmeta.__index--buffHTML declared near top of module; remove _inHTML from outer scope |
|||
local create, mwFunc = mw.html.create |
|||
function init(nodes, ...) |
|||
do |
|||
local name, args, tag = select(... and type(...)=='table' and 1 or 2, nil, ...) |
|||
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 |
|||
if nodes then table.insert(nodes, tag.parent and tag or rawset(tag, 'parent', parent[nodes])) end |
|||
function init(nodes, ...) |
|||
if args then |
|||
local name, args, tag = select(... and type(...) == 'table' and 1 or 2, nil, ...) |
|||
local a, b = args.selfClosing, args.parent |
|||
tag = create(Valid(name), args) |
|||
args.selfClosing, args.parent = nil |
|||
if nodes then |
|||
if next(args) then Element._add(parent(tag.nodes, tag), args) end |
|||
table.insert(nodes, tag.parent and tag or rawset(tag, 'parent', parent[nodes])) |
|||
end |
|||
if args then |
|||
return tag |
|||
local a, b = args.selfClosing, args.parent |
|||
args.selfClosing, args.parent = nil |
|||
if next(args) then |
|||
Element._add(parent(tag.nodes, tag), args) |
|||
end |
end |
||
args.selfClosing, args.parent = a, b -- in case args is reused |
|||
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 |
end |
||
return tag |
|||
local nonSelf, BHi = {tag=true,done=true,allDone=true}, buffHTML.__index do local g |
|||
end |
|||
g = {__index = function(t, i) |
|||
for k, v in next, {[mw] = mwHTMLmeta, |
|||
if gfuncs and gfuncs[i] then g.__index, gfuncs = gfuncs return g.__index[i] end |
|||
__call = function(h, v) |
|||
end} |
|||
return MBmix(spec[h.nodes] and h.nodes or spec(setmetatable(parent(h.nodes, h), MB), Element), v) |
|||
setmetatable(nonSelf, g) |
|||
end, |
|||
setmetatable(BHi, g) |
|||
__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 |
end |
||
lastHTML, HTML = HTML, HTML:allDone() |
|||
for k in next, nonSelf do--any HTML objects returned by these funcs will be granted Module:Buffer enhancements |
|||
local n, ops, seps = select('#', ...) |
|||
if n > 1 then |
|||
BHi[k] = function(t, ...) local HTML = func(t, ...) return parent[HTML] and HTML or setmetatable(parent(HTML, t), buffHTML) end |
|||
local ops, seps = MBselect(n, ...) |
|||
return parent[HTML]:_in(joinNode(HTML, rawget(seps, 0))):_out(ops, rawset(seps, buffHTML, true)) |
|||
end |
end |
||
return parent[HTML]:_(joinNode(HTML, ...)) |
|||
end, |
|||
local nodes, join = HTML.nodes |
|||
_str = function(HTML, ...) |
|||
if noCache and rawkey[sep] or Valid(sep) then join, HTML.nodes = tostring(rawset(HTML, 'nodes', {MB.__call(nodes, sep)})), nodes end |
|||
-- does not set lastHTML |
|||
return join or tostring(HTML) |
|||
if ... == 0 then |
|||
end |
|||
return joinNode(HTML, select(2, ...)) |
|||
end -- passing 0 strings without calling allDone() |
|||
getParent = function(HTML, ...) lastHTML = HTML return MBi.getParent(HTML:allDone(), ...) end,--return to Buffer that created the HTML tree |
|||
local HTML, n = HTML:allDone(), select('#', ...) |
|||
if n > 1 then |
|||
killParent = function(HTML, ...) MBi.killParent(HTML:allDone(), ...) return HTML end, |
|||
local ops, seps = MBselect(n, ...) |
|||
return parent[HTML]:_in(joinNode(HTML, rawget(seps, 1))):_str(ops, rawset(seps, buffHTML, true)) |
|||
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 |
end |
||
return joinNode(HTML, ...) |
|||
do local htmlArg, skip, outFuncs = {parent=true,selfClosing=true,tagName=true}, {} |
|||
end, |
|||
do local out local function func(nodes, ...) return out(parent[nodes], ...) end |
|||
_parent = function(HTML, ...) |
|||
outFuncs = setmetatable({ |
|||
table.insert(HTML.nodes, parent[HTML:allDone()]:_str(...)) |
|||
tag = function(nodes, ...) return parent(setmetatable(init(nodes, ...), buffHTML), parent[nodes]) end, |
|||
return HTML |
|||
done = function(b, ops) |
|||
end |
|||
b = parent[b] |
|||
} do |
|||
while b.parent and ops~=0 do b, ops = b.parent, ops and ops - 1 or 0 end |
|||
BHi[k] = v |
|||
return b |
|||
end |
|||
end |
|||
}, {__index = function(nodes, i) |
|||
do |
|||
if rawget(BHi, i) then out = BHi[i] return func end--rawget to exclude globals |
|||
local htmlArg, skip, outFuncs = {parent = true,selfClosing = true,tagName = true}, {} |
|||
end}) |
|||
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 |
end |
||
}, {__index = function(nodes, i) |
|||
Element = { |
|||
if rawget(BHi, i) then |
|||
out = BHi[i] |
|||
for k, v in MBpairs(t), t, skip[t] do (v~=true and MBmix or noOp)(nodes, v) end |
|||
return func |
|||
local HTML = parent[nodes] for k, v in MBpairs(t, false) do |
|||
end -- rawget to exclude globals |
|||
if htmlArg[k] then HTML[k] = v |
|||
end}) |
|||
elseif v and v~=true then |
|||
end |
|||
if nonSelf[k] then |
|||
Element = { |
|||
if k=='tag' then |
|||
_add = function(nodes, t) |
|||
if type(v)=='table' then |
|||
for k, v in MBpairs(t), t, skip[t] do |
|||
skip[v], k = 1, rawset(create(Valid(v[1])), 'parent', HTML) |
|||
(v ~= true and MBmix or noOp)(nodes, v) |
|||
Element._add(spec(parent(k.nodes, k, table.insert(nodes, k)), Element), v) |
|||
end |
|||
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 |
|||
local HTML = parent[nodes] for k, v in MBpairs(t, false) do |
|||
if not k.tagName then k.styles, k.attributes = nil end |
|||
if htmlArg[k] then |
|||
else table.insert(nodes, create(v)) end |
|||
HTML[k] = v |
|||
elseif v and v ~= true then |
|||
if k=='done' and tonumber(v)~=v and v[1] and tonumber(v[1])==v[1] then skip[v] = 1 end |
|||
if nonSelf[k] then |
|||
MBmix(outFuncs[k](nodes, skip[v] and v[1]).nodes, v) |
|||
if k == 'tag' then |
|||
if type(v) == 'table' then |
|||
k = MBi[k](nodes, unpack(v, 1, rawset(skip, v, k=='_B' and 1 or 2)[v])) |
|||
skip[v], k = 1, rawset(create(Valid(v[1])), 'parent', HTML) |
|||
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. |
|||
Element._add(spec(parent(k.nodes, k, table.insert(nodes, k)), Element), v) |
|||
else MBi[k](nodes, v) end--k probably == '_G' or '_R' |
|||
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 |
|||
else |
|||
if not k.tagName then |
|||
k.styles, k.attributes = nil |
|||
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 |
|||
end |
|||
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 |
|||
else table.insert(nodes, create(v)) |
|||
end |
|||
end |
|||
elseif rawget(Element, k) or rawget(MBi, k) then |
|||
elseif mwFunc[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 |
|||
if k == 'done' and tonumber(v) ~= v and v[1] and tonumber(v[1]) == v[1] then |
|||
else (Element[k] or MBi[k])(nodes, unpack(v, 1, table.maxn(v))) end--v is definately a table |
|||
skip[v] = 1 |
|||
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 |
||
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 |
end |
||
elseif rawget(Element, k) or rawget(MBi, k) then |
|||
return nodes |
|||
if tonumber(v) == v or v[1] == nil or getmetatable(v) then |
|||
end |
|||
(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))) |
|||
local tempMeta = {mode='v', copy={styles=true,attributes=true}} |
|||
end -- v is definitely a table |
|||
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 |
|||
else mwFunc.css(HTML, k:gsub('_', '-', 1), tostring(v)) |
|||
rawkey[setmetatable(Element, {__index = outFuncs, __concat=function(Element, v) return setmetatable({nodes=spec({}, Element),orig=parent[v]}, tempMeta) end})] = math.huge |
|||
end -- oddly enough, :_add clocked its fastest runtime after adding auto-gsub as a feature |
|||
end |
|||
skip[v] = nil |
|||
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 |
end |
||
return lastHTML |
|||
end |
end |
||
return nodes |
|||
function MBi:_html(...) return MBi._(self, lastHTML, select(spec[self]==Element and select('#', ...)==0 and 1 or 2, true, ...)) end |
|||
end |
|||
return init(...) |
|||
} |
|||
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 |
end |
||
function _inHTML(self, ...) |
function _inHTML(self, ...) |
||
local HTML = init(nil, ...) |
local HTML = init(nil, ...) |
||
if HTML.selfClosing and spec[self]==Element then |
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 |
|||
lastBuffer, lastHTML = self, setmetatable(parent(HTML, self), buffHTML) -- set after 'args' table processed by :_add |
|||
return HTML |
return HTML |
||
end |
end |
||
end |
end |
||
local _var, unbuild do |
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 |
|||
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 |
end |
||
function |
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] |
|||
for k, v in MBpairs(sep, nil) do |
|||
end |
|||
k = getmetatable(v) if k and (k==build or k==alt) then shift(v.a or v, -v.c) 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 |
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 |
end |
||
function _var(self, ...) |
function _var(self, ...) |
||
local obj if ... and ...~=true then |
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 |
||
return obj and MBi._(self, obj, nil, true) or self |
|||
end |
end |
||
end |
|||
local lib; MBi = setmetatable({stream = _stream, |
|||
_inHTML = _inHTML, |
|||
local lib; MBi = setmetatable({stream = _stream, |
|||
_var = _var, |
|||
_inHTML = _inHTML, |
|||
_ = function(self, v, ...) |
|||
_var = _var, |
|||
local at, raw = select(select('#', ...)==1 and ...==true and 1 or 2, nil, ...) |
|||
_ = function(self, v, ...) |
|||
if raw then rawkey[self] = math.huge else v = Valid(v) end |
|||
local at, raw = select(select('#', ...) == 1 and ... == true and 1 or 2, nil, ...) |
|||
if v or raw then |
|||
if 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' |
|||
rawkey[self] = math.huge else v = Valid(v) |
|||
at, self.last_concat = at and (tonumber(at)~=at and raw + at or at) |
|||
end |
|||
table.insert(self, select(at and 1 or 2, at, v)) |
|||
if v or raw then |
|||
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 |
|||
if at or rawkey[self] then |
|||
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) |
|||
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' |
|||
end, |
|||
at, self.last_concat = at and (tonumber(at) ~= at and raw + at or at) |
|||
_nil = function(self, at, ...) |
|||
table.insert(self, select(at and 1 or 2, at, v)) |
|||
if ...~=true and ...~=false then--faster than type(...) ~= 'boolean' |
|||
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) |
|||
local n, v = tonumber(at), ... |
|||
return self |
|||
if n~=at then |
|||
end, |
|||
if n then n = #self + at |
|||
_nil = function(self, at, ...) |
|||
elseif at~=true and select('#', ...)==0 then v, n = at, #self end |
|||
if ... ~= true and ... ~= false then |
|||
end |
|||
-- faster than type(...) ~= 'boolean' |
|||
if n then |
|||
if not at or at == '0' then |
|||
self[#self] = ... if ... then |
|||
else self[math.floor(n)], rawkey[self] = v, math.huge end--floor position for consistency with Table library |
|||
rawkey[self] = math.huge |
|||
end |
|||
end |
|||
else |
|||
self.last_concat = nil |
|||
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 |
||
end |
|||
return self |
|||
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, |
end, |
||
_all = function(self, t, valKey) |
_all = function(self, t, valKey) |
||
for k, v in MBpairs(t) do |
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 |
for k, v in valKey and MBpairs(t, false) or noOp, t do |
||
if tonumber(v) then |
if tonumber(v) then |
||
MBi._(self, k, v) -- self not always a buffer |
|||
elseif rawget(MBi, k) and v and v~=true then |
elseif rawget(MBi, k) and v and v ~= true then |
||
if v[1]==nil or getmetatable(v) then |
if v[1] == nil or getmetatable(v) then |
||
MBi[k](self, v) |
|||
else MBi[k](self, unpack(v, 1, table.maxn(v))) |
else MBi[k](self, unpack(v, 1, table.maxn(v))) |
||
end |
|||
end |
end |
||
end |
end |
||
Line 321: | Line 574: | ||
local k, ops, seps, r = 2, MBselect(n, ...) |
local k, ops, seps, r = 2, MBselect(n, ...) |
||
r = MB(t(seps[1])) |
r = MB(t(seps[1])) |
||
while parent[t] and ops > 1 and r:_(parent[t](seps[k]), 1) do |
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) |
return table.concat(r, seps[k] or nil) |
||
end |
end |
||
return MB.__call(t, ...) |
return MB.__call(t, ...) |
||
end, |
end, |
||
_in = function (self, ...) |
_in = function (self, ...) |
||
return parent(MB(...), self) |
|||
end, |
|||
_out = function(t, ...) |
_out = function(t, ...) |
||
if ... == 0 then |
|||
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* |
|||
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('#', ...) |
local n = select('#', ...) |
||
if n > 1 then |
if n > 1 then |
||
local k, ops, seps = 1, MBselect(n, ...) |
local k, ops, seps = 1, MBselect(n, ...) |
||
while parent[t] and ops > 0 do |
while parent[t] and ops > 0 do |
||
t, k, ops = parent[t]:_(t(seps[k])), k + 1, ops - 1 |
|||
elseif parent[t] then |
end |
||
elseif parent[t] then |
|||
return parent[t]:_(t(...)) |
|||
end |
|||
return t |
return t |
||
end, |
end, |
||
_cc = function(self, clear, copy, meta) |
_cc = function(self, clear, copy, meta) |
||
if clear then |
if clear then |
||
if rawequal(clear, copy) 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 |
elseif copy == true then |
||
copy = self |
|||
if clear~=0 then |
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 |
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 |
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) |
|||
else |
|||
if self==false or copy and type(copy)=='table' then--self==false means copy is a table (saves a type op for recursive calls) |
|||
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) |
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 |
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) |
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 |
e, spec[MBi._cc], parent[null] = i - 1, null, clear |
||
for k = 1, e do |
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 |
|||
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 |
end |
||
for k, v in next, copy do |
for k, v in next, copy do |
||
rawset(clear, k, type(v) == 'table' and MBi._cc(false, 0, v) or v) |
|||
elseif copy then |
end |
||
elseif copy then |
|||
rawset(clear, 1, (Valid(copy))) |
|||
end |
|||
rawkey[setmetatable(clear, meta)], parent[clear] = rawkey[copy], parent[copy] |
rawkey[setmetatable(clear, meta)], parent[clear] = rawkey[copy], parent[copy] |
||
end |
end |
||
return self and rawset(self, 'last_concat', nil) or clear |
return self and rawset(self, 'last_concat', nil) or clear |
||
end, |
end, |
||
_parent = function(self, ...) |
_parent = function(self, ...) |
||
return parent[self] and MBi._(self, parent[self]:_str(...)) or self |
|||
end, |
|||
getParent = function(self, ...) |
getParent = function(self, ...) |
||
return isMBfunc(parent[self] or parent[parent(self, setmetatable({}, MB))], ...) |
|||
end, |
|||
killParent = function(self, ...) |
killParent = function(self, ...) |
||
return parent[self] and isMBfunc(parent[self], ...) and parent(self) or self |
|||
end, |
|||
_build = function(self, t) |
_build = function(self, t) |
||
table.insert(t, self()) |
|||
end, -- for compatibility with mw.html:node() |
|||
last_concat = false--prevent library check |
last_concat = false -- prevent library check |
||
}, {__index = function(t, i)--import string, mw.text, and mw.ustring libraries on an as-needed basis |
}, {__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 |
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]), ...))) |
|||
return f(tostring(s), ...)--not using ternary/logical operators here to allow multiple return values |
|||
end |
end |
||
return f(tostring(s), ...) -- not using ternary/logical operators here to allow multiple return values |
|||
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 |
||
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! |
|||
end}) |
|||
or function(self, ...)return lib(self, func, ...)end)[i] |
|||
end |
|||
end}) |
|||
end |
end |
||
function MBmix(t, v, ...) |
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) |
local _G, new_G = _G -- localize _G for console testing (console _G ~= module _G) |
||
return setmetatable({__index = function(t, i) |
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 |
|||
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 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 |
||
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 |
end |
||
return table.concat(t, sep, i and math.max(i, 1), j and math.min(j, #t)) |
|||
end |
end |
||
if rawsep and not raw.s then |
|||
return MB.__tostring(t) |
|||
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, |
end, |
||
__tostring = function(t) |
__tostring = function(t) |
||
if t.last_concat then |
if t.last_concat then |
||
return t.last_concat |
|||
end |
|||
local r = rawkey[spec[t]] or rawkey[t] |
local r = rawkey[spec[t]] or rawkey[t] |
||
r = table.concat(r and r>=#t and MBi._all({}, t) or t) |
r = table.concat(r and r >= #t and MBi._all({}, t) or t) |
||
return (noCache or rawset(t, 'last_concat', r)) and r |
return (noCache or rawset(t, 'last_concat', r)) and r |
||
end, |
end, |
||
__concat = function(a, b) |
__concat = function(a, b) |
||
if buffHTML then |
if buffHTML then |
||
for k = 1, 2 do |
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 v and spec[v] and spec[v] == Element then |
||
if parent[v].selfClosing then |
if parent[v].selfClosing then |
||
if rawequal(a, b) 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) |
b, a = select(k, b, parent[v], a) |
||
else local temp = Element .. v --helper method; returns a mirror of parent[v] |
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) |
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 |
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 |
||
Line 427: | Line 736: | ||
__pairs = MBpairs, |
__pairs = MBpairs, |
||
__ipairs = MBpairs, |
__ipairs = MBpairs, |
||
__eq = function(a, b) |
__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, |
}, {__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 |
|||
return self, ... -- avoiding __eq with rawequal(self,save) is overkill since buffers can self-save without being passed as save |
|||
end, |
|||
end, |
|||
_R = function(self, i, v, m) |
|||
if i~='new_G' then |
_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}) |
|||
elseif not v or v == true or v._G ~= _G then |
|||
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 |
|||
new_G = setmetatable(v ~= true and v or {}, {__call = mG.__call, __index = v ~= true and m ~= true and (m or new_G) or nil}) |
|||
return self |
|||
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, |
|||
end -- setting Nil.__index is noOp |
|||
_2 = function(self, ...) |
|||
return self |
|||
if new_G[...]~=nil then return new_G[...] end--higher priority so Buffer:_G('new_G', ...) can prevent an overwrite |
|||
end, |
|||
if ...=='new_G' then return rawset((select('#', ...)~=1 and MBi._R(new_G, ...) or new_G), '_G', _G) end |
|||
_2 = function(self, ...) |
|||
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 |
|||
if new_G[...] ~= nil then |
|||
end, |
|||
return new_G[...] |
|||
_B = function(self, v) return v or v==nil and Nil end |
|||
end -- higher priority so Buffer:_G('new_G', ...) can prevent an overwrite |
|||
} for k, v in next, gfuncs do MBi[k] = v end |
|||
if ... == 'new_G' then |
|||
setmetatable(Nil,{__concat=MB.__concat,__newindex=noOp,__call=noOp,__tostring=noOp,__metatable=MB,__index=setmetatable({_B=MBi._B,_=function()return Nil end,last_concat=''}, |
|||
return rawset((select('#', ...) ~= 1 and MBi._R(new_G, ...) or new_G), '_G', _G) |
|||
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 |
|||
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 |
end |
||
local new = setmetatable({}, self) |
local new = setmetatable({}, self) |
||
if ... and (...)==new_G then |
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 |
return ... and MBi._(new, ...) or new |
||
end, |
end, |
||
__index = function(t, i) |
__index = function(t, i) |
||
MB = MB or t |
MB = MB or t |
||
return MBi[i] and function(...) |
|||
return MBi[i](setmetatable({}, t), select(... == t and 2 or 1,...)) |
|||
end |
|||
end |
end |
||
}) |
}) |