We are currently performing an upgrade to our software. This upgrade will bring MediaWiki from version 1.31 to 1.33. While the upgrade is being performed on your wiki it will be in read-only mode. For more information check here.

Module:ObjectArg

From Fire Emblem Heroes Wiki
(Redirected from ObjectArg)
Jump to: navigation, search
Template-info.svg Documentation

Test cases

Lua metamodule which parses complex object values. Exposes a single function, parse, which accepts a JSON-like string and returns the Lua representation of that string. The syntax of such values is described below. (The mw library already provides functions for generating and parsing real JSON strings.)

The function returns 2 values:

  • If the function successfully parses the input string, the first return value contains the represented object and the second return value is nil.
  • If parsing fails, the first return value is nil and the second return value is an error message string.

Tag[edit source]

A tag is any string not containing the special characters [, ], {, }, =, or ;. Whenever possible, the module converts numeric strings to numbers using the built-in tonumber function (in base 10). ASCII whitespace characters around tags are trimmed.

List[edit source]

A list consists of zero or more values enclosed in square brackets and separated by semicolons. Values may be empty, in which case the resulting Lua table ceases to be a valid Lua sequence.

Hash[edit source]

A hash consists of zero or more tag-value pairs enclosed in curly brackets and separated by semicolons. The tag and value are separated by the equals sign. Values may be empty, in which case the resulting Lua table does not contain an element with that value's corresponding key. Keys cannot be empty. Nested hashes coming from template arguments may avoid prematurely terminating the template, by separating consecutive closing curly brackets with whitespace or a semicolon.

Value[edit source]

A value is either a tag, a list, or a hash. The top-level value is wholly represented by the input string, and cannot be empty.

Examples[edit source]

local parse = require 'Module:ObjectArg'.parse

mw.logObject(parse [[1]]) -- 1
mw.logObject(parse [[a]]) -- "a"
mw.logObject(parse [["1"]]) -- "\"1\""
mw.logObject(parse [[
[a;b;;d;]
]]) -- {"a", "b", [4] = "d"}
mw.logObject(parse [[
{weapon=;assist=Draw Back;base stats=[35;33;35;18;28]}
]]) -- {assist = "Draw Back", ["base stats"] = {35, 33, 35, 18, 28}}
mw.logObject(parse [[
{1=a;2=b;4=d}
]]) -- {"a", "b", [4] = "d"}

Formal grammar[edit source]

The following LL(1) grammar fully specifies the syntax of the strings accepted by this module, except that all ASCII whitespace characters between tokens are trimmed.

V → T
  | [ L ]
  | { H }

V0 → V
   | ε

H → T = V0 H'
  | ε

H' → ; H
   | ε

L → V0 L'

L' → ; L
   | ε

T → /[^=;\{\}\[\]]+/
-- 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}