local floor = math.floor
local gsub, strsub, strbyte, strchar, strfind, strformat =
string.gsub, string.sub, string.byte, string.char,
string.find, string.format
local concat = table.concat
local escapecodes = {
["\""] = "\\\"",
["\\"] = "\\\\",
["\b"] = "\\b",
["\f"] = "\\f",
["\n"] = "\\n",
["\r"] = "\\r",
["\t"] = "\\t"
}
local escapechars = {
["\""] = "\"",
["\\"] = "\\",
["/"] = "/",
["b"] = "\b",
["f"] = "\f",
["n"] = "\n",
["r"] = "\r",
["t"] = "\t"
}
local function unichar(value)
if value < 0 then
return nil
elseif value <= 0x007f then
return strchar(value)
elseif value <= 0x07ff then
return strchar(0xc0 + floor(value / 0x40),
0x80 + (floor(value) % 0x40))
elseif value <= 0xffff then
return strchar(0xe0 + floor(value / 0x1000),
0x80 + (floor(value / 0x40) % 0x40),
0x80 + (floor(value) % 0x40))
elseif value <= 0x10ffff then
return strchar(0xf0 + floor(value / 0x40000),
0x80 + (floor(value / 0x1000) % 0x40),
0x80 + (floor(value / 0x40) % 0x40),
0x80 + (floor(value) % 0x40))
else
return nil
end
end
local function escapeutf8(uchar)
local value = escapecodes[uchar]
if value then
return value
end
local a, b, c, d = strbyte(uchar, 1, 4)
a, b, c, d = a or 0, b or 0, c or 0, d or 0
local char
if a <= 0x7f then
char = a
elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then
char = (a - 0xc0) * 0x40 + b - 0x80
elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then
char = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80
elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then
char = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80
else
return ""
end
if char <= 0xffff then
return strformat("\\u%.4x", char)
elseif char <= 0x10ffff then
char = char - 0x10000
local highsur, lowsur = 0xD800 + floor(char / 0x400), 0xDC00 + (char % 0x400)
return strformat("\\u%.4x\\u%.4x", highsur, lowsur)
else
return ""
end
end
local function fsub(str, pattern, repl)
if strfind(str, pattern) then
return gsub(str, pattern, repl)
else
return str
end
end
local function scanstring(str, pos)
local lastpos = pos + 1
local buffer, n = {}, 0
while true do
local nextpos = strfind(str, "[\"\\]", lastpos)
if not nextpos then
return nil, pos
end
if nextpos > lastpos then
n = n + 1
buffer[n] = strsub(str, lastpos, nextpos - 1)
end
if strsub(str, nextpos, nextpos) == "\"" then
lastpos = nextpos + 1
break
else
local escchar = strsub(str, nextpos + 1, nextpos + 1)
local value
if escchar == "u" then
value = tonumber(strsub(str, nextpos + 2, nextpos + 5), 16)
if value then
local value2
if 0xD800 <= value and value <= 0xDBff then
if strsub(str, nextpos + 6, nextpos + 7) == "\\u" then
value2 = tonumber(strsub(str, nextpos + 8, nextpos + 11), 16)
if value2 and 0xDC00 <= value2 and value2 <= 0xDFFF then
value = (value - 0xD800) * 0x400 + (value2 - 0xDC00) + 0x10000
else
value2 = nil end
end
end
value = value and unichar(value)
if value then
if value2 then
lastpos = nextpos + 12
else
lastpos = nextpos + 6
end
end
end
end
if not value then
value = escapechars[escchar] or escchar
lastpos = nextpos + 2
end
n = n + 1
buffer[n] = value
end
end
if n == 1 then
return buffer[1], lastpos
elseif n > 1 then
return concat(buffer), lastpos
else
return "", lastpos
end
end
local function quotestring(value)
value = fsub(value, "[%z\1-\31\"\\\127]", escapeutf8)
if strfind(value, "[\194\216\220\225\226\239]") then
value = fsub(value, "\194[\128-\159\173]", escapeutf8)
value = fsub(value, "\216[\128-\132]", escapeutf8)
value = fsub(value, "\220\143", escapeutf8)
value = fsub(value, "\225\158[\180\181]", escapeutf8)
value = fsub(value, "\226\128[\140-\143\168\175]", escapeutf8)
value = fsub(value, "\226\129[\160-\175]", escapeutf8)
value = fsub(value, "\239\187\191", escapeutf8)
value = fsub(value, "\239\191[\176\191]", escapeutf8)
end
return "\"" .. value .. "\""
end
local function json_encode(_, brat_string)
return base_string:new(quotestring(brat_string._lua_string))
end
local function json_decode(_, brat_string, pos)
local result, new_pos = scanstring(brat_string._lua_string, pos)
if result and result ~= "" then
return array:new(base_string:new(result), new_pos - 1)
else
return object.__null
end
end
object:export(json_encode, "json_encode");
object:export(json_decode, "json_decode");