Drastically improve parsing and unparsing speed and fix bug
The parsing code still isn't easy to read but at least it's faster and not horribly bugged (I hope).
This commit is contained in:
parent
1d92fcf28b
commit
8fca50e407
132
core.lua
132
core.lua
@ -31,63 +31,77 @@ local formspec_ast, minetest = formspec_ast, formspec_ast.minetest
|
||||
|
||||
-- Parse a formspec into a "raw" non-AST state.
|
||||
-- Input: size[5,2]button[0,0;5,1;name;Label] image[0,1;1,1;air.png]
|
||||
-- Output: {{'size', '5', '2'}, {'button', '0,0', '5,1', 'name', 'label'},
|
||||
-- {'image', '0,1', '1,1', 'air.png'}}
|
||||
-- Output:
|
||||
-- {
|
||||
-- {type='size', '5', '2'},
|
||||
-- {type='button', {'0', '0'}, {'5', '1'}, 'name', 'label'},
|
||||
-- {type='image', {'0', '1'}, {'1', '1'}, 'air.png'},
|
||||
-- }
|
||||
local table_concat = table.concat
|
||||
local function raw_parse(spec)
|
||||
local res = {}
|
||||
while spec do
|
||||
-- Get the first element
|
||||
local name
|
||||
name, spec = spec:match('([%w_%-:]*[^%s\\])%s*(%[.*)')
|
||||
if not name or not spec then return res end
|
||||
local elem = {}
|
||||
elem[1] = name
|
||||
local end_idx = 0
|
||||
local bracket_idx
|
||||
local spec_length = #spec
|
||||
|
||||
-- Get the parameters
|
||||
local s, e = spec:find('[^\\]%]')
|
||||
local rawargs
|
||||
if s and e then
|
||||
rawargs, spec = spec:sub(2, s), spec:sub(e + 1)
|
||||
else
|
||||
rawargs, spec = spec:sub(2), false
|
||||
end
|
||||
while end_idx < spec_length do
|
||||
-- Get the element type
|
||||
bracket_idx = spec:find('[', end_idx + 1, true)
|
||||
if not bracket_idx then break end
|
||||
local parts = {type = spec:sub(end_idx + 1, bracket_idx - 1):trim()}
|
||||
|
||||
-- Split everything
|
||||
-- TODO: Make this a RegEx
|
||||
local i = ''
|
||||
-- This tries and avoids creating small strings where possible
|
||||
end_idx = spec_length + 1
|
||||
local part = {}
|
||||
local esc = false
|
||||
local inner = {}
|
||||
for c = 1, #rawargs do
|
||||
local char = rawargs:sub(c, c)
|
||||
local start_idx = bracket_idx + 1
|
||||
for idx = bracket_idx, spec_length do
|
||||
local byte = spec:byte(idx)
|
||||
if esc then
|
||||
-- The current character is escaped
|
||||
esc = false
|
||||
i = i .. char
|
||||
elseif char == '\\' then
|
||||
esc = true
|
||||
elseif char == ';' then
|
||||
start_idx = idx
|
||||
elseif byte == 0x5c then
|
||||
-- Backslashes
|
||||
part[#part + 1] = spec:sub(start_idx, idx - 1)
|
||||
esc = true
|
||||
elseif byte == 0x3b then
|
||||
-- Semicolons
|
||||
part[#part + 1] = spec:sub(start_idx, idx - 1)
|
||||
start_idx = idx + 1
|
||||
if #inner > 0 then
|
||||
table.insert(inner, i)
|
||||
table.insert(elem, inner)
|
||||
inner[#inner + 1] = table_concat(part)
|
||||
parts[#parts + 1] = inner
|
||||
inner = {}
|
||||
else
|
||||
table.insert(elem, i)
|
||||
parts[#parts + 1] = table_concat(part)
|
||||
end
|
||||
i = ''
|
||||
elseif char == ',' then
|
||||
table.insert(inner, i)
|
||||
i = ''
|
||||
else
|
||||
i = i .. char
|
||||
part = {}
|
||||
elseif byte == 0x2c then
|
||||
-- Commas
|
||||
part[#part + 1] = spec:sub(start_idx, idx - 1)
|
||||
start_idx = idx + 1
|
||||
inner[#inner + 1] = table_concat(part)
|
||||
part = {}
|
||||
elseif byte == 0x5d then
|
||||
-- ]
|
||||
end_idx = idx
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- Add the last part
|
||||
part[#part + 1] = spec:sub(start_idx, end_idx - 1)
|
||||
if #inner > 0 then
|
||||
table.insert(inner, i)
|
||||
table.insert(elem, inner)
|
||||
inner[#inner + 1] = table_concat(part)
|
||||
parts[#parts + 1] = inner
|
||||
else
|
||||
table.insert(elem, i)
|
||||
parts[#parts + 1] = table_concat(part)
|
||||
end
|
||||
|
||||
table.insert(res, elem)
|
||||
res[#res + 1] = parts
|
||||
end
|
||||
|
||||
return res
|
||||
@ -96,22 +110,24 @@ end
|
||||
-- Unparse raw formspecs
|
||||
-- WARNING: This will modify the table passed to it.
|
||||
local function raw_unparse(data)
|
||||
local res = ''
|
||||
for _, elem in ipairs(data) do
|
||||
for i = 2, #elem do
|
||||
if type(elem[i]) == 'table' then
|
||||
for j, e in ipairs(elem[i]) do
|
||||
elem[i][j] = minetest.formspec_escape(e)
|
||||
local res = {}
|
||||
for _, parts in ipairs(data) do
|
||||
res[#res + 1] = parts.type
|
||||
for i = 1, #parts do
|
||||
if type(parts[i]) == 'table' then
|
||||
for j, e in ipairs(parts[i]) do
|
||||
parts[i][j] = minetest.formspec_escape(e)
|
||||
end
|
||||
elem[i] = table.concat(elem[i], ',')
|
||||
parts[i] = table_concat(parts[i], ',')
|
||||
else
|
||||
elem[i] = minetest.formspec_escape(elem[i])
|
||||
parts[i] = minetest.formspec_escape(parts[i])
|
||||
end
|
||||
end
|
||||
res = res .. table.remove(elem, 1) .. '[' ..
|
||||
table.concat(elem, ';') .. ']'
|
||||
res[#res + 1] = '['
|
||||
res[#res + 1] = table_concat(parts, ';')
|
||||
res[#res + 1] = ']'
|
||||
end
|
||||
return res
|
||||
return table_concat(res)
|
||||
end
|
||||
|
||||
-- Elements
|
||||
@ -186,7 +202,7 @@ local function parse_value(elems, template)
|
||||
local func = types[obj[2]] or types.undefined
|
||||
local elem = elems[i]
|
||||
if type(elem) == 'table' then
|
||||
elem = table.concat(elem, ',')
|
||||
elem = table_concat(elem, ',')
|
||||
end
|
||||
res[obj[1]] = func(elem, obj[1])
|
||||
else
|
||||
@ -237,7 +253,6 @@ end
|
||||
|
||||
local parse_mt
|
||||
local function parse_elem(elem, custom_handlers)
|
||||
elem.type = table.remove(elem, 1)
|
||||
local data = elements[elem.type]
|
||||
if not data then
|
||||
if not custom_handlers or not custom_handlers[elem.type] then
|
||||
@ -252,7 +267,6 @@ local function parse_elem(elem, custom_handlers)
|
||||
if good then
|
||||
return ast_elem, true
|
||||
else
|
||||
table.insert(elem, 1, elem.type)
|
||||
return nil, 'Invalid element "' .. raw_unparse({elem}) .. '": ' ..
|
||||
tostring(ast_elem)
|
||||
end
|
||||
@ -274,7 +288,6 @@ local function parse_elem(elem, custom_handlers)
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(elem, 1, elem.type)
|
||||
return nil, 'Invalid element "' .. raw_unparse({elem}) .. '": ' ..
|
||||
tostring(ast_elem)
|
||||
end
|
||||
@ -318,8 +331,8 @@ end
|
||||
-- }
|
||||
|
||||
|
||||
function formspec_ast.parse(spec, custom_handlers)
|
||||
spec = raw_parse(spec)
|
||||
function formspec_ast.parse(fs, custom_handlers)
|
||||
local spec = raw_parse(fs)
|
||||
local res = {formspec_version=1}
|
||||
local containers = {}
|
||||
local container = res
|
||||
@ -463,7 +476,7 @@ local function unparse_elem(elem, res, force)
|
||||
good, raw_elem = pcall(unparse_value, elem, template)
|
||||
end
|
||||
if good then
|
||||
table.insert(raw_elem, 1, elem.type)
|
||||
raw_elem.type = elem.type
|
||||
table.insert(possible_elems, raw_elem)
|
||||
end
|
||||
end
|
||||
@ -508,7 +521,10 @@ end
|
||||
function formspec_ast.unparse(tree)
|
||||
local raw_spec = {}
|
||||
if tree.formspec_version and tree.formspec_version ~= 1 then
|
||||
raw_spec[1] = {'formspec_version', tostring(tree.formspec_version)}
|
||||
raw_spec[1] = {
|
||||
type = 'formspec_version',
|
||||
tostring(tree.formspec_version)
|
||||
}
|
||||
end
|
||||
|
||||
for _, elem in ipairs(tree) do
|
||||
@ -536,7 +552,7 @@ function parse_mt:__index(key)
|
||||
if func then
|
||||
return function(obj)
|
||||
if type(obj) == 'table' then
|
||||
obj = table.concat(obj, ',')
|
||||
obj = table_concat(obj, ',')
|
||||
end
|
||||
return func(obj or '')
|
||||
end
|
||||
|
18
tests.lua
18
tests.lua
@ -64,12 +64,23 @@ local function test_parse_unparse(fs, expected_tree)
|
||||
assert_equal(fs, unparsed_fs)
|
||||
end
|
||||
|
||||
test_parse_unparse([=[label[123,456;yay abc def\, ghi\; jkl mno \]\\]]=], {
|
||||
formspec_version = 1,
|
||||
{
|
||||
type = 'label',
|
||||
x = 123,
|
||||
y = 456,
|
||||
label = 'yay abc def, ghi; jkl mno ]\\'
|
||||
}
|
||||
})
|
||||
|
||||
local fs = [[
|
||||
formspec_version[2]
|
||||
size[5,2]
|
||||
padding[1,2]
|
||||
no_prepend[]
|
||||
container[1,1]
|
||||
label[0,0;Containers are fun]
|
||||
label[0,0;Containers are fun\]\\]
|
||||
container[-1,-1]
|
||||
button[0.5,0;4,1;name;Label]
|
||||
container_end[]
|
||||
@ -109,6 +120,9 @@ test_parse_unparse(fs, {
|
||||
x = 1,
|
||||
y = 2,
|
||||
},
|
||||
{
|
||||
type = "no_prepend"
|
||||
},
|
||||
{
|
||||
type = "container",
|
||||
x = 1,
|
||||
@ -117,7 +131,7 @@ test_parse_unparse(fs, {
|
||||
type = "label",
|
||||
x = 0,
|
||||
y = 0,
|
||||
label = "Containers are fun",
|
||||
label = "Containers are fun]\\",
|
||||
},
|
||||
{
|
||||
type = "container",
|
||||
|
Loading…
Reference in New Issue
Block a user