fixes + new algo #1
4
.luacheckrc
Normal file
4
.luacheckrc
Normal file
@ -0,0 +1,4 @@
|
||||
std = "lua51+luajit+minetest"
|
||||
|
||||
globals = {"test_string_to_table"}
|
||||
read_globals = {"DIR_DELIM"}
|
130
alg1.lua
130
alg1.lua
@ -1,62 +1,140 @@
|
||||
local function stringToTable(str)
|
||||
local function parseValue(s, i)
|
||||
s = s:match("^%s*(.-)%s*$", i) -- Trim leading/trailing spaces
|
||||
i = 1
|
||||
local parseTable -- defined later, but we need it in parseValue
|
||||
|
||||
local function parseValue(s, idx)
|
||||
s = s:match("^%s*(.-)%s*$", idx) -- Trim leading/trailing spaces
|
||||
|
||||
if s:sub(1, 1) == "{" then
|
||||
return parseTable(s)
|
||||
elseif s:sub(1, 4) == "true" then
|
||||
return true, 5
|
||||
return true, 4
|
||||
elseif s:sub(1, 5) == "false" then
|
||||
return false, 6
|
||||
return false, 5
|
||||
elseif s:sub(1, 3) == "nil" then
|
||||
return nil, 4
|
||||
elseif s:match("^%d+%.?%d*") then
|
||||
local num = tonumber(s:match("^%d+%.?%d*"))
|
||||
return num, #tostring(num) + 1
|
||||
return nil, 3
|
||||
elseif s:sub(1, 1) == '"' or s:sub(1, 1) == "'" then
|
||||
local quote = s:sub(1, 1)
|
||||
local value = ""
|
||||
for i = 2, #s do
|
||||
if s:sub(i, i) == quote and s:sub(i-1, i-1) ~= "\\" then
|
||||
return value, i + 1
|
||||
if s:sub(i, i) == quote then
|
||||
local j = i - 1
|
||||
local escaped = false
|
||||
while (s:sub(j,j)) == "\\" do
|
||||
escaped = not escaped
|
||||
j = j - 1
|
||||
end
|
||||
if not escaped then
|
||||
value = value:gsub("\\(.)", "%1")
|
||||
return value, i
|
||||
end
|
||||
end
|
||||
value = value .. s:sub(i, i)
|
||||
end
|
||||
error("Unclosed string")
|
||||
end
|
||||
local _, next_i = s:find("^[+-]?%d+%.?%d*[eE][+-]?%d+%s*")
|
||||
if not next_i then
|
||||
_, next_i = s:find("^[+-]?%d+%.?%d*%s*")
|
||||
end
|
||||
if next_i then
|
||||
return tonumber(s:match("^[+-]?%d+%.?%d*[eE][+-]?%d+") or s:match("^([+-]?%d+%.?%d*)")), next_i
|
||||
else
|
||||
print(s)
|
||||
error("Invalid value")
|
||||
end
|
||||
end
|
||||
|
||||
local function parseTable(s)
|
||||
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, i)
|
||||
s = s:match("^%s*(.-)%s*$", i) -- Trim leading/trailing spaces
|
||||
if s:find("^[%a_]") then
|
||||
local key = s:match("^[%a%d_]*")
|
||||
if not lua_keywords[key] then
|
||||
return key, #key
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function parseTable(s)
|
||||
local tbl = {}
|
||||
local i = 2 -- Start after the opening '{'
|
||||
local key = nil
|
||||
local key, value
|
||||
local value_found -- we cannot check for value, bc {nil} is valid
|
||||
local next_key = 1
|
||||
local next_i
|
||||
|
||||
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
|
||||
|
||||
while i <= #s do
|
||||
if s:sub(i, i) == "}" then
|
||||
return tbl, i + 1
|
||||
elseif s:sub(i, i) == "," then
|
||||
local char = s:sub(i, i)
|
||||
if char == "}" then
|
||||
return tbl, i
|
||||
elseif char == " " then
|
||||
i = i + 1
|
||||
elseif s:sub(i, i) == "[" then
|
||||
local value, next_i = parseValue(s:sub(i + 1))
|
||||
i = i + next_i
|
||||
elseif char == "," or char == ";" then
|
||||
if key ~= nil or (not value_found) then
|
||||
error("value expected")
|
||||
end
|
||||
value_found = false
|
||||
i = i + 1
|
||||
elseif char == "[" then
|
||||
if key ~= nil then
|
||||
error("Expected value, not a key")
|
||||
end
|
||||
key, next_i = parseValue(s:sub(i + 1))
|
||||
i = i + next_i + 1
|
||||
if s:sub(i, i) ~= "]" then
|
||||
error("Expected closing bracket")
|
||||
end
|
||||
key = value
|
||||
i = i + 1
|
||||
elseif key == nil then
|
||||
local value, next_i = parseValue(s:sub(i))
|
||||
key = value
|
||||
i = i + next_i - 1
|
||||
elseif s:sub(i, i) == "=" then
|
||||
key, next_i = parseKey(s:sub(i))
|
||||
if not key then -- not a valid key
|
||||
value, next_i = parseValue(s:sub(i))
|
||||
key = getNextKey()
|
||||
tbl[key] = value
|
||||
value_found = true
|
||||
key = nil
|
||||
end
|
||||
i = i + next_i
|
||||
elseif char == "=" then
|
||||
i = i + 1
|
||||
local value, next_i = parseValue(s:sub(i))
|
||||
value, next_i = parseValue(s:sub(i))
|
||||
tbl[key] = value
|
||||
value_found = true
|
||||
key = nil
|
||||
i = i + next_i - 1
|
||||
i = i + next_i + 1
|
||||
else
|
||||
error("Unexpected character: " .. s:sub(i, i))
|
||||
end
|
||||
|
118
alg2.lua
118
alg2.lua
@ -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,20 +33,66 @@ 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 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 inKey = true
|
||||
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)
|
||||
|
||||
@ -33,44 +101,44 @@ local function stringToTable(str)
|
||||
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
|
138
algtour.lua
Normal file
138
algtour.lua
Normal file
@ -0,0 +1,138 @@
|
||||
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 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 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_]*$") and not lua_keywords[str] 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:match("^{(.-)[,;]?%s*}$") .. ","
|
||||
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:match("^%s*(.-)%s*$")
|
||||
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:match("^%s*(.-)%s*$")
|
||||
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
|
||||
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
|
30
init.lua
30
init.lua
@ -5,16 +5,32 @@ test_string_to_table.modpath = minetest.get_modpath("test_string_to_table") .. D
|
||||
dofile(test_string_to_table.modpath .. "alg1.lua")
|
||||
dofile(test_string_to_table.modpath .. "alg2.lua")
|
||||
dofile(test_string_to_table.modpath .. "alg3.lua")
|
||||
dofile(test_string_to_table.modpath .. "algtour.lua")
|
||||
|
||||
local tests = {
|
||||
'{1, 2, 3}',
|
||||
'{a = 1, b = 2, c = 3}',
|
||||
'{a = 1; b = 2; c = 3}',
|
||||
'{a = {b = {c = 3}}}',
|
||||
'{1, "hello", true, false, nil, 3.14}',
|
||||
'{[1] = "one", [2] = "two", ["three"] = 3}',
|
||||
'{"quoted string", \'single quoted\'}',
|
||||
'{"quoted string with an escaped quote \\"", \'single quoted\'}',
|
||||
'{nested = {tables = {are = {supported = true}}}}',
|
||||
"{key1 = 123, key2 = true, key3 = {nestedKey1 = 'value1', nestedKey2 = false}}"
|
||||
"{key1 = 123, key2 = true, key3 = {nestedKey1 = 'value1', nestedKey2 = false}}",
|
||||
"{}", "{ }",
|
||||
"{[{'tables can be'}] = 'keys too'}",
|
||||
'{\'{table = "that", "contains", a = "string", ["looking"] = {"like", "a"; "table"}}\'}',
|
||||
'{[-42.42e42] = 42.42E-42}',
|
||||
'{1, 2, 3, }',
|
||||
-- invalid tables, the code should return an error
|
||||
--[[
|
||||
"{a = 1",
|
||||
"{'unclosed string\\'}",
|
||||
"{invalid key = 3}",
|
||||
"{false = 2}", -- invalid key
|
||||
"{1 = 2}", -- invalid key
|
||||
"{,}",
|
||||
"{[nil] = 1}"
|
||||
--]]
|
||||
}
|
||||
|
||||
|
||||
@ -29,8 +45,10 @@ local function test(alg)
|
||||
end
|
||||
local t2 = minetest.get_us_time()
|
||||
minetest.log("action",alg .. " = " .. (t2-t1))
|
||||
-- print(dump(outcome))
|
||||
end
|
||||
|
||||
-- test("s2t1")
|
||||
-- test("s2t2")
|
||||
test("s2t3")
|
||||
test("s2t1")
|
||||
test("s2t2")
|
||||
-- test("s2t3")
|
||||
test("s2ttour")
|
Loading…
Reference in New Issue
Block a user