feat: add json parser
This commit is contained in:
202
src/lib/json.lua
Normal file
202
src/lib/json.lua
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
local json = {}
|
||||||
|
|
||||||
|
json.null = {}
|
||||||
|
|
||||||
|
local function trim(str)
|
||||||
|
return string.gsub(str, "^%s+", "")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function startswith(str, prefix)
|
||||||
|
return str:sub(1, prefix:len()) == prefix
|
||||||
|
end
|
||||||
|
|
||||||
|
local function endswith(str, suffix)
|
||||||
|
return str:sub(str:len() - suffix:len() + 1) == suffix
|
||||||
|
end
|
||||||
|
|
||||||
|
local function errFactory(prefix)
|
||||||
|
return function (message)
|
||||||
|
print(prefix .. ": " .. message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parseObj(data)
|
||||||
|
local printErr = errFactory("Error while parsing object")
|
||||||
|
local obj = {}
|
||||||
|
local c = ""
|
||||||
|
local key = ""
|
||||||
|
local keyEnd, value
|
||||||
|
local l = -1
|
||||||
|
while true do
|
||||||
|
if data:len() == l then
|
||||||
|
printErr("infinite loop")
|
||||||
|
return data, nil
|
||||||
|
end
|
||||||
|
l = data:len()
|
||||||
|
data = trim(data)
|
||||||
|
if data:len() == 0 then
|
||||||
|
printErr("incomplete object")
|
||||||
|
return data, nil
|
||||||
|
end
|
||||||
|
c = data:sub(1, 1)
|
||||||
|
data = data:sub(2)
|
||||||
|
if c == "}" then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
if c ~= '"' then
|
||||||
|
printErr("key must be a string")
|
||||||
|
return data, nil
|
||||||
|
end
|
||||||
|
keyEnd = data:find('"')
|
||||||
|
if not keyEnd then
|
||||||
|
printErr("unclosed key")
|
||||||
|
return data, nil
|
||||||
|
end
|
||||||
|
key = data:sub(1, keyEnd - 1)
|
||||||
|
data = trim(data:sub(keyEnd + 1))
|
||||||
|
c = data:sub(1, 1)
|
||||||
|
if c ~= ":" then
|
||||||
|
printErr("missing colon after key")
|
||||||
|
return data, nil
|
||||||
|
end
|
||||||
|
data = data:sub(2)
|
||||||
|
data, value = json.parse(data)
|
||||||
|
if value == nil then
|
||||||
|
printErr("malformed value")
|
||||||
|
return data, nil
|
||||||
|
end
|
||||||
|
data = trim(data)
|
||||||
|
obj[key] = value
|
||||||
|
|
||||||
|
c = data:sub(1, 1)
|
||||||
|
if c == "," then
|
||||||
|
data = trim(data:sub(2))
|
||||||
|
else
|
||||||
|
if data:sub(1, 1) ~= "}" then
|
||||||
|
printErr("missing comma")
|
||||||
|
return data, nil
|
||||||
|
end
|
||||||
|
data = trim(data:sub(2))
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return data, obj
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parseList(data)
|
||||||
|
local printErr = errFactory("Error while parsing array")
|
||||||
|
local list = {}
|
||||||
|
local c
|
||||||
|
local value
|
||||||
|
while true do
|
||||||
|
data = trim(data)
|
||||||
|
if data:len() == 0 then
|
||||||
|
printErr("incomplete array")
|
||||||
|
return data, nil
|
||||||
|
end
|
||||||
|
c = data:sub(1, 1)
|
||||||
|
if c == "]" then
|
||||||
|
data = data:sub(2)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
data, value = json.parse(data)
|
||||||
|
if value == nil then
|
||||||
|
printErr("malformed value")
|
||||||
|
return data, nil
|
||||||
|
end
|
||||||
|
data = trim(data)
|
||||||
|
list[#list + 1] = value
|
||||||
|
|
||||||
|
c = data:sub(1, 1)
|
||||||
|
if c == "," then
|
||||||
|
data = trim(data:sub(2))
|
||||||
|
else
|
||||||
|
if data:sub(1, 1) ~= "]" then
|
||||||
|
printErr("missing comma")
|
||||||
|
return data, nil
|
||||||
|
end
|
||||||
|
data = trim(data:sub(2))
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return data, list
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parseString(data)
|
||||||
|
local printErr = errFactory("Error while parsing string")
|
||||||
|
local str = ""
|
||||||
|
local c = ""
|
||||||
|
local escape = false
|
||||||
|
local escapeMap = {
|
||||||
|
b = "\b",
|
||||||
|
f = "\f",
|
||||||
|
n = "\n",
|
||||||
|
r = "\r",
|
||||||
|
t = "\t"
|
||||||
|
}
|
||||||
|
|
||||||
|
while data:len() ~= 0 do
|
||||||
|
c = data:sub(1, 1)
|
||||||
|
data = data:sub(2)
|
||||||
|
if c == '"' and not escape then
|
||||||
|
return data, str
|
||||||
|
end
|
||||||
|
if escape then
|
||||||
|
str = str .. (escapeMap[c] or c)
|
||||||
|
escape = false
|
||||||
|
else
|
||||||
|
if c == "\\" then
|
||||||
|
escape = true
|
||||||
|
else
|
||||||
|
str = str .. c
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
printErr("unclosed string")
|
||||||
|
return data, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function json.parse(data)
|
||||||
|
data = data:gsub("^%s+", "")
|
||||||
|
if data:len() == 0 then
|
||||||
|
printError("Empty JSON")
|
||||||
|
return data, nil
|
||||||
|
end
|
||||||
|
local c = data:sub(1, 1)
|
||||||
|
local data2 = data:sub(2)
|
||||||
|
if c == "{" then
|
||||||
|
return parseObj(data2)
|
||||||
|
elseif c == "[" then
|
||||||
|
return parseList(data2)
|
||||||
|
elseif startswith(data, "true") then
|
||||||
|
return data:sub(5), true
|
||||||
|
elseif startswith(data, "false") then
|
||||||
|
return data:sub(6), false
|
||||||
|
elseif startswith(data, "null") then
|
||||||
|
return data:sub(5), json.null
|
||||||
|
elseif c == '"' then
|
||||||
|
return parseString(data2)
|
||||||
|
end
|
||||||
|
|
||||||
|
local m = data:match("^-?%d+%.?%d*[eE]?[+-]?%d*")
|
||||||
|
if m then
|
||||||
|
data = data:sub(m:len() + 1)
|
||||||
|
return data, tonumber(m)
|
||||||
|
else
|
||||||
|
printError("Malformed JSON")
|
||||||
|
return data, nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function json.loads(data)
|
||||||
|
local remaining, res = json.parse(data)
|
||||||
|
if res == nil then
|
||||||
|
printError("An error occured")
|
||||||
|
end
|
||||||
|
if remaining:len() ~= 0 then
|
||||||
|
print("Extra characters: " .. remaining)
|
||||||
|
end
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
|
||||||
|
return json
|
||||||
Reference in New Issue
Block a user