113 lines
3.3 KiB
Lua
113 lines
3.3 KiB
Lua
local nested_tables = {}
|
|
|
|
-- strings might contain characters that are part of the table syntax.
|
|
-- We need to mask them, otherwise they might break our parser
|
|
-- based on https://www.lua.org/pil/20.4.html
|
|
local function code(s)
|
|
-- mask all escaped characters
|
|
return s:gsub("\\(.)", function (c)
|
|
return string.format("\\%03d", string.byte(c))
|
|
end):gsub("([\"'])(.-)%1", function (_, match) -- detect all strings
|
|
return "'" .. match:gsub("([,;{}%[%]=])", function (c)
|
|
return string.format("\\%03d", string.byte(c))
|
|
end) .. "'"
|
|
end)
|
|
end
|
|
|
|
local function decode(s)
|
|
return s:gsub("\\(%d%d%d)", function (d)
|
|
return string.char(d)
|
|
end)
|
|
end
|
|
|
|
|
|
local function parse_value(str)
|
|
if str == "nil" then
|
|
return true, nil
|
|
elseif str == "true" then
|
|
return true, true
|
|
elseif str == "false" then
|
|
return true, false
|
|
elseif str:find("^([\"']).-%1$") then
|
|
return true, decode(str:sub(2, -2))
|
|
elseif tonumber(str) then
|
|
return true, tonumber(str)
|
|
elseif nested_tables[str] then
|
|
return true, nested_tables[str]
|
|
end
|
|
return false, string.format("unable to parse '%s'", str)
|
|
end
|
|
|
|
local function parse_key(str)
|
|
if str:find("^%[.*%]$") then
|
|
-- if in brackets, we can threat it like a value
|
|
local success, key = parse_value(str:sub(2, -2))
|
|
if not success then
|
|
return false, key
|
|
end
|
|
if key == nil then
|
|
return false, "nil cannot be a key"
|
|
end
|
|
return true, key
|
|
end
|
|
-- make sure the key is a valid identifier
|
|
if str:find("^[%a_][%a%d_]*$") then
|
|
return true, str
|
|
end
|
|
return false, string.format("'%s' is not a valid key", str)
|
|
end
|
|
|
|
local function parse_flat_table(str)
|
|
if str:find("{%s*}") then return true, {} end
|
|
str = str:gsub("^{(.-)[,;]?%s*}$", "%1,")
|
|
local next_index = 1
|
|
|
|
local res = {}
|
|
for pair in str:gmatch("(.-)[,;]") do
|
|
local key, value, success
|
|
if pair:find("=") then
|
|
key, value = pair:match("^(.-)=(.*)$")
|
|
key = key:gsub("^%s*(.-)%s*$", "%1")
|
|
success, key = parse_key(key)
|
|
if not success then return false, key end
|
|
else
|
|
while res[next_index] ~= nil do
|
|
next_index = next_index + 1
|
|
end
|
|
key = next_index
|
|
next_index = next_index + 1
|
|
value = pair
|
|
end
|
|
value = value:gsub("^%s*(.-)%s*$", "%1")
|
|
success, value = parse_value(value)
|
|
if not success then return false, value end
|
|
|
|
res[key] = value
|
|
end
|
|
|
|
return true, res
|
|
end
|
|
|
|
local function stringToTable(str)
|
|
str = code(str)
|
|
|
|
nested_tables = {}
|
|
local success = true
|
|
local tbl
|
|
|
|
local replacements = 0
|
|
local table_count = 0
|
|
repeat
|
|
str, replacements = str:gsub("({[^{}]*})", function (match)
|
|
table_count = table_count + 1
|
|
success, tbl = parse_flat_table(match)
|
|
nested_tables["\\t" .. tostring(table_count)] = tbl
|
|
return "\\t" .. tostring(table_count)
|
|
end)
|
|
until replacements == 0 or success == false
|
|
return success and (nested_tables[str] or "not a table") or tbl
|
|
end
|
|
|
|
function test_string_to_table.s2ttour(str)
|
|
return stringToTable(str)
|
|
end |