Module:ObjectArg

-- for Module:Test2

local PUNC = { ['['] = 'liststart', [']'] = 'listend', ['{'] = 'hashstart', ['}'] = 'hashend', ['='] = 'assoc', [';'] = 'sep', }

local consume = function (str, sb) local b, e, v = string.find(str, '^%s*([%[%]%{%}%=%;])%s*', sb, false) if v then return e + 1, PUNC[v], v	end b, e, v = string.find(str, '^%s*([^%[%]%{%}%=%;]*[^%[%]%{%}%=%;%s]+)%s*', sb, false) if v then return e + 1, 'tag', v	end b, e, v = string.find(str, '^%s*$', sb, false) if b then return b, 'end', nil end end

local uconsume = function (str, sb) local b, e, v = mw.ustring.find(str, '^%s*([%[%]%{%}%=%;])%s*', sb, false) if v then return e + 1, PUNC[v], v	end b, e, v = mw.ustring.find(str, '^%s*([^%[%]%{%}%=%;]*[^%[%]%{%}%=%;%s]+)%s*', sb, false) if v then return e + 1, 'tag', v	end b, e, v = mw.ustring.find(str, '^%s*$', sb, false) if b then return b, 'end', nil end end

--[=[ LL(1) parser table generated at http://jsmachines.sourceforge.net/machines/ll1.html with the following grammar:

VALUE -> TAG VALUE -> liststart LIST listend VALUE -> hashstart HASH hashend

VALUE0 -> '' VALUE0 -> VALUE

HASH -> '' HASH -> TAG assoc VALUE0 HASH_CONT

HASH_CONT -> '' HASH_CONT -> sep HASH

LIST -> VALUE0 LIST_CONT

LIST_CONT -> '' LIST_CONT -> sep LIST

TAG -> tag ]=]

local LL_TABLE = { VALUE = { liststart = {'liststart', 'LIST', 'listend'}, hashstart = {'hashstart', 'HASH', 'hashend'}, tag = {'TAG'}, },	VALUE0 = { liststart = {'VALUE'}, listend = {}, hashstart = {'VALUE'}, hashend = {}, sep = {}, tag = {'VALUE'}, },	HASH = { hashend = {}, tag = {'TAG', 'assoc', 'VALUE0', 'HASH_CONT'}, },	HASH_CONT = { hashend = {}, sep = {'sep', 'HASH'}, },	LIST = { liststart = {'VALUE0', 'LIST_CONT'}, listend = {'VALUE0', 'LIST_CONT'}, hashstart = {'VALUE0', 'LIST_CONT'}, sep = {'VALUE0', 'LIST_CONT'}, tag = {'VALUE0', 'LIST_CONT'}, },	LIST_CONT = { listend = {}, sep = {'sep', 'LIST'}, },	TAG = { tag = {'tag'}, }, }

local makeBuilder = function local cxt = –

local addValue = function (val) val = tonumber(val) or val local c = cxt[#cxt] if c.kind == 'list' then c.obj[c.i] = val elseif c.kind == 'hash' then if not c.k then c.k = val else c.obj[c.k] = val end else c.obj = val end end

return { accept = function (_, typ, val) if typ == 'liststart' then cxt[#cxt + 1] = {kind = 'list', obj = {}, i = 1} elseif typ == 'hashstart' then cxt[#cxt + 1] = {kind = 'hash', obj = {}} elseif typ == 'listend' or typ == 'hashend' then addValue(table.remove(cxt, #cxt).obj) elseif typ == 'sep' then local c = cxt[#cxt] if c.kind == 'list' then c.i = c.i + 1 elseif c.kind == 'hash' then c.k = nil end elseif typ == 'tag' then addValue(val) end end, build = function (_) return cxt[1].obj end, } end

local parse = function (str) local stack = {'end', 'VALUE'} local b = 1 local builder = makeBuilder

while #stack > 0 do		local b2, typ, val = consume(str, b)		if typ == stack[#stack] then builder:accept(typ, val) b = b2			stack[#stack] = nil else local rule = LL_TABLE[stack[#stack]] rule = rule and rule[typ] if rule then stack[#stack] = nil for i = #rule, 1, -1 do					stack[#stack + 1] = rule[i] end else return nil, ('Failed to parse object near position %d, stack: (%s)'):format(b, table.concat(stack, ' ')) end end end

return builder:build end

local uparse = function (str) local stack = {'end', 'VALUE'} local str2 = str local b = 1 local builder = makeBuilder

while #stack > 0 do		local b2, typ, val = uconsume(str2, 1) if typ == stack[#stack] then builder:accept(typ, val) b = b + b2 - 1 str2 = mw.ustring.sub(str2, b2) stack[#stack] = nil else local rule = LL_TABLE[stack[#stack]] rule = rule and rule[typ] if rule then stack[#stack] = nil for i = #rule, 1, -1 do					stack[#stack + 1] = rule[i] end else return nil, ('Failed to parse object near position %d, stack: (%s)'):format(b, table.concat(stack, ' ')) end end end

return builder:build end

return {parse = parse, uparse = uparse}