144 lines
3.7 KiB
Lua
144 lines
3.7 KiB
Lua
-- 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 codedStringToTable(str)
|
|
-- special case: hadle empty tables (our parser would break here)
|
|
if str:find("^{%s*}$") then return {} end
|
|
|
|
local function parseValue(s)
|
|
s = s:match("^%s*(.-)%s*$") -- trim whitespace
|
|
|
|
if s == "nil" then
|
|
return nil
|
|
elseif s == "true" then
|
|
return true
|
|
elseif s == "false" then
|
|
return false
|
|
elseif tonumber(s) then
|
|
return tonumber(s)
|
|
elseif s:match("^\".*\"$") or s:match("^'.*'$") then
|
|
return decode(s:sub(2, -2))
|
|
elseif s:match("^{.*}$") then
|
|
return codedStringToTable(s)
|
|
else
|
|
error("Unrecognized value: " .. s)
|
|
end
|
|
end
|
|
|
|
local lua_keywords = {
|
|
["and"] = true,
|
|
["break"] = true,
|
|
["do"] = true,
|
|
["else"] = true,
|
|
["elseif"] = true,
|
|
["end"] = true,
|
|
["false"] = true,
|
|
["for"] = true,
|
|
["function"] = true,
|
|
["goto"] = true,
|
|
["if"] = true,
|
|
["in"] = true,
|
|
["local"] = true,
|
|
["nil"] = true,
|
|
["not"] = true,
|
|
["or"] = true,
|
|
["repeat"] = true,
|
|
["return"] = true,
|
|
["then"] = true,
|
|
["true"] = true,
|
|
["until"] = true,
|
|
["while"] = true,
|
|
}
|
|
|
|
local function parseKey(s)
|
|
s = s:match("^%s*(.-)%s*$") -- trim whitespace
|
|
if s:match("^%[(.*)%]$") then
|
|
return parseValue(s:match("^%[(.*)%]$"))
|
|
end
|
|
s = s:match("^[%a_][%a%d_]*$") -- make sure the string is a valid lua identifier
|
|
if s and not lua_keywords[s] then
|
|
return s
|
|
end
|
|
error("Unrecognized key: " .. s)
|
|
end
|
|
|
|
local tbl = {}
|
|
local key, value
|
|
local next_key = 1
|
|
local depth = 0
|
|
local buffer = ""
|
|
|
|
local function getNextKey()
|
|
while tbl[next_key] ~= nil do
|
|
next_key = next_key + 1
|
|
end
|
|
key = next_key
|
|
next_key = next_key + 1
|
|
return key
|
|
end
|
|
|
|
for i = 1, #str do
|
|
local char = str:sub(i, i)
|
|
|
|
if char == '{' then
|
|
if depth > 0 then
|
|
buffer = buffer .. char
|
|
end
|
|
depth = depth + 1
|
|
elseif char == '}' then
|
|
depth = depth - 1
|
|
if depth > 0 then
|
|
buffer = buffer .. char
|
|
elseif depth == 0 and not buffer:find("^%s*$") then
|
|
value = parseValue(buffer)
|
|
buffer = ""
|
|
end
|
|
elseif (char == ',' or char == ";") and depth == 1 then
|
|
value = parseValue(buffer)
|
|
if key == nil then
|
|
key = getNextKey()
|
|
end
|
|
|
|
tbl[key] = value
|
|
key, value = nil, nil
|
|
buffer = ""
|
|
elseif char == '=' and depth == 1 then
|
|
key = parseKey(buffer)
|
|
buffer = ""
|
|
else
|
|
buffer = buffer .. char
|
|
end
|
|
end
|
|
|
|
if key == nil then
|
|
key = getNextKey()
|
|
end
|
|
tbl[key] = value
|
|
|
|
return tbl
|
|
end
|
|
|
|
local function stringToTable(str)
|
|
str = code(str)
|
|
return codedStringToTable(str)
|
|
end
|
|
|
|
function test_string_to_table.s2t2(str)
|
|
return stringToTable(str)
|
|
end |