This commit is contained in:
tour 2024-09-13 15:27:52 +02:00
parent c0aaedae59
commit e9d96b20d9
2 changed files with 70 additions and 27 deletions

View File

@ -1,4 +1,26 @@
local function stringToTable(str)
-- 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
@ -11,66 +33,87 @@ local function stringToTable(str)
elseif tonumber(s) then
return tonumber(s)
elseif s:match("^\".*\"$") or s:match("^'.*'$") then
return s:sub(2, -2):gsub("\\(.)", "%1") -- handle escape sequences
elseif s:match("^%b{}$") then
return stringToTable(s)
return decode(s:sub(2, -2))
elseif s:match("^{.*}$") then
return codedStringToTable(s)
else
error("Unrecognized value: " .. s)
end
end
local function parseKey(s)
s = s:match("^%s*(.-)%s*$") -- trim whitespace
if s:match("^%[(.*)%]$") then
return parseValue(s:match("^%[(.*)%]$"))
elseif s:find("^[%a_][%a%d_]*$") then -- make sure the string is a valid lua identifier
return s
else
error("Unrecognized key: " .. s)
end
end
local tbl = {}
local key, value
local next_key = 1
local inKey = true
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 buffer ~= "" then
value = buffer
elseif depth == 0 and not buffer:find("^%s*$") then
value = parseValue(buffer)
buffer = ""
end
elseif char == ',' and depth == 1 then
if inKey then
key = parseValue(buffer)
else
value = parseValue(buffer)
tbl[key] = value
elseif (char == ',' or char == ";") and depth == 1 then
value = parseValue(buffer)
if key == nil then
key = getNextKey()
end
buffer = ""
inKey = not inKey
tbl[key] = value
key, value = nil, nil
buffer = ""
elseif char == '=' and depth == 1 then
key = parseValue(buffer)
key = parseKey(buffer)
buffer = ""
inKey = false
else
buffer = buffer .. char
end
if depth == 1 and i == #str and buffer ~= "" then
value = parseValue(buffer)
tbl[key] = value
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

View File

@ -22,7 +22,7 @@ local tests = {
'{[-42.42e42] = 42.42E-42}',
'{1, 2, 3, }',
-- invalid tables, the code should return an error
-- --[[
--[[
"{a = 1",
"{'unclosed string\\'}",
"{invalid key = 3}",
@ -48,6 +48,6 @@ local function test(alg)
end
-- test("s2t1")
-- test("s2t2")
test("s2t2")
-- test("s2t3")
test("s2ttour")