Moonshark/runner/lua/json.lua

422 lines
8.9 KiB
Lua

-- json.lua: High-performance JSON module for Moonshark
-- Pre-computed escape sequences to avoid recreating table
local escape_chars = {
['"'] = '\\"', ['\\'] = '\\\\',
['\n'] = '\\n', ['\r'] = '\\r', ['\t'] = '\\t'
}
function json_go_encode(value)
return __json_marshal(value)
end
function json_go_decode(str)
if type(str) ~= "string" then
error("json_decode: expected string, got " .. type(str), 2)
end
return __json_unmarshal(str)
end
function json_encode(data)
local t = type(data)
if t == "nil" then return "null" end
if t == "boolean" then return data and "true" or "false" end
if t == "number" then return tostring(data) end
if t == "string" then
return '"' .. data:gsub('[\\"\n\r\t]', escape_chars) .. '"'
end
if t == "table" then
local isArray = true
local count = 0
-- Check if it's an array in one pass
for k, _ in pairs(data) do
count = count + 1
if type(k) ~= "number" or k ~= count or k < 1 then
isArray = false
break
end
end
if isArray then
local result = {}
for i = 1, count do
result[i] = json_encode(data[i])
end
return "[" .. table.concat(result, ",") .. "]"
else
local result = {}
local index = 1
for k, v in pairs(data) do
if type(k) == "string" and type(v) ~= "function" and type(v) ~= "userdata" then
result[index] = json_encode(k) .. ":" .. json_encode(v)
index = index + 1
end
end
return "{" .. table.concat(result, ",") .. "}"
end
end
return "null" -- Unsupported type
end
function json_decode(data)
local pos = 1
local len = #data
-- Pre-compute byte values
local b_space = string.byte(' ')
local b_tab = string.byte('\t')
local b_cr = string.byte('\r')
local b_lf = string.byte('\n')
local b_quote = string.byte('"')
local b_backslash = string.byte('\\')
local b_slash = string.byte('/')
local b_lcurly = string.byte('{')
local b_rcurly = string.byte('}')
local b_lbracket = string.byte('[')
local b_rbracket = string.byte(']')
local b_colon = string.byte(':')
local b_comma = string.byte(',')
local b_0 = string.byte('0')
local b_9 = string.byte('9')
local b_minus = string.byte('-')
local b_plus = string.byte('+')
local b_dot = string.byte('.')
local b_e = string.byte('e')
local b_E = string.byte('E')
-- Skip whitespace more efficiently
local function skip()
local b
while pos <= len do
b = data:byte(pos)
if b > b_space or (b ~= b_space and b ~= b_tab and b ~= b_cr and b ~= b_lf) then
break
end
pos = pos + 1
end
end
-- Forward declarations
local parse_value, parse_string, parse_number, parse_object, parse_array
-- Parse a string more efficiently
parse_string = function()
pos = pos + 1 -- Skip opening quote
if pos > len then
error("Unterminated string")
end
-- Use a table to build the string
local result = {}
local result_pos = 1
local start = pos
local c, b
while pos <= len do
b = data:byte(pos)
if b == b_backslash then
-- Add the chunk before the escape character
if pos > start then
result[result_pos] = data:sub(start, pos - 1)
result_pos = result_pos + 1
end
pos = pos + 1
if pos > len then
error("Unterminated string escape")
end
c = data:byte(pos)
if c == b_quote then
result[result_pos] = '"'
elseif c == b_backslash then
result[result_pos] = '\\'
elseif c == b_slash then
result[result_pos] = '/'
elseif c == string.byte('b') then
result[result_pos] = '\b'
elseif c == string.byte('f') then
result[result_pos] = '\f'
elseif c == string.byte('n') then
result[result_pos] = '\n'
elseif c == string.byte('r') then
result[result_pos] = '\r'
elseif c == string.byte('t') then
result[result_pos] = '\t'
else
result[result_pos] = data:sub(pos, pos)
end
result_pos = result_pos + 1
pos = pos + 1
start = pos
elseif b == b_quote then
-- Add the final chunk
if pos > start then
result[result_pos] = data:sub(start, pos - 1)
result_pos = result_pos + 1
end
pos = pos + 1
return table.concat(result)
else
pos = pos + 1
end
end
error("Unterminated string")
end
-- Parse a number more efficiently
parse_number = function()
local start = pos
local b = data:byte(pos)
-- Skip any sign
if b == b_minus then
pos = pos + 1
if pos > len then
error("Malformed number")
end
b = data:byte(pos)
end
-- Integer part
if b < b_0 or b > b_9 then
error("Malformed number")
end
repeat
pos = pos + 1
if pos > len then break end
b = data:byte(pos)
until b < b_0 or b > b_9
-- Fractional part
if pos <= len and b == b_dot then
pos = pos + 1
if pos > len or data:byte(pos) < b_0 or data:byte(pos) > b_9 then
error("Malformed number")
end
repeat
pos = pos + 1
if pos > len then break end
b = data:byte(pos)
until b < b_0 or b > b_9
end
-- Exponent
if pos <= len and (b == b_e or b == b_E) then
pos = pos + 1
if pos > len then
error("Malformed number")
end
b = data:byte(pos)
if b == b_plus or b == b_minus then
pos = pos + 1
if pos > len then
error("Malformed number")
end
b = data:byte(pos)
end
if b < b_0 or b > b_9 then
error("Malformed number")
end
repeat
pos = pos + 1
if pos > len then break end
b = data:byte(pos)
until b < b_0 or b > b_9
end
return tonumber(data:sub(start, pos - 1))
end
-- Parse an object more efficiently
parse_object = function()
pos = pos + 1 -- Skip opening brace
local obj = {}
skip()
if pos <= len and data:byte(pos) == b_rcurly then
pos = pos + 1
return obj
end
while pos <= len do
skip()
if data:byte(pos) ~= b_quote then
error("Expected string key")
end
local key = parse_string()
skip()
if data:byte(pos) ~= b_colon then
error("Expected colon")
end
pos = pos + 1
obj[key] = parse_value()
skip()
local b = data:byte(pos)
if b == b_rcurly then
pos = pos + 1
return obj
end
if b ~= b_comma then
error("Expected comma or closing brace")
end
pos = pos + 1
end
error("Unterminated object")
end
-- Parse an array more efficiently
parse_array = function()
pos = pos + 1 -- Skip opening bracket
local arr = {}
local index = 1
skip()
if pos <= len and data:byte(pos) == b_rbracket then
pos = pos + 1
return arr
end
while pos <= len do
arr[index] = parse_value()
index = index + 1
skip()
local b = data:byte(pos)
if b == b_rbracket then
pos = pos + 1
return arr
end
if b ~= b_comma then
error("Expected comma or closing bracket")
end
pos = pos + 1
end
error("Unterminated array")
end
-- Parse a value more efficiently
parse_value = function()
skip()
if pos > len then
error("Unexpected end of input")
end
local b = data:byte(pos)
if b == b_quote then
return parse_string()
elseif b == b_lcurly then
return parse_object()
elseif b == b_lbracket then
return parse_array()
elseif b == string.byte('n') and pos + 3 <= len and data:sub(pos, pos + 3) == "null" then
pos = pos + 4
return nil
elseif b == string.byte('t') and pos + 3 <= len and data:sub(pos, pos + 3) == "true" then
pos = pos + 4
return true
elseif b == string.byte('f') and pos + 4 <= len and data:sub(pos, pos + 4) == "false" then
pos = pos + 5
return false
elseif b == b_minus or (b >= b_0 and b <= b_9) then
return parse_number()
else
error("Unexpected character: " .. string.char(b))
end
end
skip()
local result = parse_value()
skip()
if pos <= len then
error("Unexpected trailing characters")
end
return result
end
function json_is_valid(str)
if type(str) ~= "string" then return false end
local status, _ = pcall(json_decode, str)
return status
end
function json_pretty_print(value)
if type(value) == "string" then
value = json_decode(value)
end
local function stringify(val, indent, visited)
visited = visited or {}
indent = indent or 0
local spaces = string.rep(" ", indent)
if type(val) == "table" then
if visited[val] then return "{...}" end
visited[val] = true
local isArray = true
local i = 1
for k in pairs(val) do
if type(k) ~= "number" or k ~= i then
isArray = false
break
end
i = i + 1
end
local result = isArray and "[\n" or "{\n"
local first = true
if isArray then
for i, v in ipairs(val) do
if not first then result = result .. ",\n" end
first = false
result = result .. spaces .. " " .. stringify(v, indent + 1, visited)
end
else
for k, v in pairs(val) do
if not first then result = result .. ",\n" end
first = false
result = result .. spaces .. " \"" .. tostring(k) .. "\": " .. stringify(v, indent + 1, visited)
end
end
return result .. "\n" .. spaces .. (isArray and "]" or "}")
elseif type(val) == "string" then
return "\"" .. val:gsub('\\', '\\\\'):gsub('"', '\\"'):gsub('\n', '\\n') .. "\""
else
return tostring(val)
end
end
return stringify(value)
end