-- 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