enhance json module
This commit is contained in:
parent
55e18c5f30
commit
df9e0cb21b
@ -13,10 +13,15 @@ import (
|
|||||||
//go:embed sandbox.lua
|
//go:embed sandbox.lua
|
||||||
var sandboxLuaCode string
|
var sandboxLuaCode string
|
||||||
|
|
||||||
|
//go:embed json.lua
|
||||||
|
var jsonLuaCode string
|
||||||
|
|
||||||
// Global bytecode cache to improve performance
|
// Global bytecode cache to improve performance
|
||||||
var (
|
var (
|
||||||
sandboxBytecode atomic.Pointer[[]byte]
|
sandboxBytecode atomic.Pointer[[]byte]
|
||||||
bytecodeOnce sync.Once
|
jsonBytecode atomic.Pointer[[]byte]
|
||||||
|
bytecodeOnce sync.Once
|
||||||
|
jsonBytecodeOnce sync.Once
|
||||||
)
|
)
|
||||||
|
|
||||||
// precompileSandboxCode compiles the sandbox.lua code to bytecode once
|
// precompileSandboxCode compiles the sandbox.lua code to bytecode once
|
||||||
@ -41,10 +46,64 @@ func precompileSandboxCode() {
|
|||||||
logger.Debug("Successfully precompiled sandbox.lua to bytecode (%d bytes)", len(code))
|
logger.Debug("Successfully precompiled sandbox.lua to bytecode (%d bytes)", len(code))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// precompileJsonModule compiles the json.lua code to bytecode once
|
||||||
|
func precompileJsonModule() {
|
||||||
|
tempState := luajit.New()
|
||||||
|
if tempState == nil {
|
||||||
|
logger.Fatal("Failed to create temp Lua state for JSON module compilation")
|
||||||
|
}
|
||||||
|
defer tempState.Close()
|
||||||
|
defer tempState.Cleanup()
|
||||||
|
|
||||||
|
code, err := tempState.CompileBytecode(jsonLuaCode, "json.lua")
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to compile JSON module: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bytecode := make([]byte, len(code))
|
||||||
|
copy(bytecode, code)
|
||||||
|
jsonBytecode.Store(&bytecode)
|
||||||
|
|
||||||
|
logger.Debug("Successfully precompiled json.lua to bytecode (%d bytes)", len(code))
|
||||||
|
}
|
||||||
|
|
||||||
// loadSandboxIntoState loads the sandbox code into a Lua state
|
// loadSandboxIntoState loads the sandbox code into a Lua state
|
||||||
func loadSandboxIntoState(state *luajit.State, verbose bool) error {
|
func loadSandboxIntoState(state *luajit.State, verbose bool) error {
|
||||||
bytecodeOnce.Do(precompileSandboxCode)
|
bytecodeOnce.Do(precompileSandboxCode)
|
||||||
|
jsonBytecodeOnce.Do(precompileJsonModule)
|
||||||
|
|
||||||
|
// First load and execute the JSON module
|
||||||
|
jsBytecode := jsonBytecode.Load()
|
||||||
|
if jsBytecode != nil && len(*jsBytecode) > 0 {
|
||||||
|
if verbose {
|
||||||
|
logger.Debug("Loading json.lua from precompiled bytecode")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute JSON module bytecode and store result to _G.json
|
||||||
|
if err := state.LoadBytecode(*jsBytecode, "json.lua"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute with 1 result and store to _G.json
|
||||||
|
if err := state.RunBytecodeWithResults(1); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The module table is now on top of stack, set it to _G.json
|
||||||
|
state.SetGlobal("json")
|
||||||
|
} else {
|
||||||
|
// Fallback - compile and execute JSON module directly
|
||||||
|
if verbose {
|
||||||
|
logger.Warning("Using non-precompiled json.lua")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := state.DoString(jsonLuaCode); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then load sandbox
|
||||||
bytecode := sandboxBytecode.Load()
|
bytecode := sandboxBytecode.Load()
|
||||||
if bytecode != nil && len(*bytecode) > 0 {
|
if bytecode != nil && len(*bytecode) > 0 {
|
||||||
if verbose {
|
if verbose {
|
||||||
@ -53,9 +112,9 @@ func loadSandboxIntoState(state *luajit.State, verbose bool) error {
|
|||||||
return state.LoadAndRunBytecode(*bytecode, "sandbox.lua")
|
return state.LoadAndRunBytecode(*bytecode, "sandbox.lua")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to direct execution
|
// Fallback
|
||||||
if verbose {
|
if verbose {
|
||||||
logger.Warning("Using non-precompiled sandbox.lua (bytecode compilation failed)")
|
logger.Warning("Using non-precompiled sandbox.lua")
|
||||||
}
|
}
|
||||||
return state.DoString(sandboxLuaCode)
|
return state.DoString(sandboxLuaCode)
|
||||||
}
|
}
|
||||||
|
433
core/runner/json.lua
Normal file
433
core/runner/json.lua
Normal file
@ -0,0 +1,433 @@
|
|||||||
|
-- json.lua: High-performance JSON module for Moonshark
|
||||||
|
local json = {}
|
||||||
|
|
||||||
|
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
|
||||||
|
local escape_chars = {
|
||||||
|
['"'] = '\\"', ['\\'] = '\\\\',
|
||||||
|
['\n'] = '\\n', ['\r'] = '\\r', ['\t'] = '\\t'
|
||||||
|
}
|
||||||
|
return '"' .. data:gsub('[\\"\n\r\t]', escape_chars) .. '"'
|
||||||
|
end
|
||||||
|
|
||||||
|
if t == "table" then
|
||||||
|
local isArray = true
|
||||||
|
local count = 0
|
||||||
|
local max_index = 0
|
||||||
|
|
||||||
|
for k, _ in pairs(data) do
|
||||||
|
count = count + 1
|
||||||
|
if type(k) == "number" and k > 0 and math.floor(k) == k then
|
||||||
|
max_index = math.max(max_index, k)
|
||||||
|
else
|
||||||
|
isArray = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local result = {}
|
||||||
|
|
||||||
|
if isArray then
|
||||||
|
for i, v in ipairs(data) do
|
||||||
|
result[i] = json.encode(v)
|
||||||
|
end
|
||||||
|
return "[" .. table.concat(result, ",") .. "]"
|
||||||
|
else
|
||||||
|
local size = 0
|
||||||
|
for k, v in pairs(data) do
|
||||||
|
if type(k) == "string" and type(v) ~= "function" and type(v) ~= "userdata" then
|
||||||
|
size = size + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
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(json)
|
||||||
|
local pos = 1
|
||||||
|
local len = #json
|
||||||
|
|
||||||
|
-- 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 = json: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 = json:byte(pos)
|
||||||
|
|
||||||
|
if b == b_backslash then
|
||||||
|
-- Add the chunk before the escape character
|
||||||
|
if pos > start then
|
||||||
|
result[result_pos] = json:sub(start, pos - 1)
|
||||||
|
result_pos = result_pos + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
pos = pos + 1
|
||||||
|
if pos > len then
|
||||||
|
error("Unterminated string escape")
|
||||||
|
end
|
||||||
|
|
||||||
|
c = json: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] = json: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] = json: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 = json:byte(pos)
|
||||||
|
|
||||||
|
-- Skip any sign
|
||||||
|
if b == b_minus then
|
||||||
|
pos = pos + 1
|
||||||
|
if pos > len then
|
||||||
|
error("Malformed number")
|
||||||
|
end
|
||||||
|
b = json: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 = json: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 json:byte(pos) < b_0 or json:byte(pos) > b_9 then
|
||||||
|
error("Malformed number")
|
||||||
|
end
|
||||||
|
|
||||||
|
repeat
|
||||||
|
pos = pos + 1
|
||||||
|
if pos > len then break end
|
||||||
|
b = json: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 = json:byte(pos)
|
||||||
|
if b == b_plus or b == b_minus then
|
||||||
|
pos = pos + 1
|
||||||
|
if pos > len then
|
||||||
|
error("Malformed number")
|
||||||
|
end
|
||||||
|
b = json: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 = json:byte(pos)
|
||||||
|
until b < b_0 or b > b_9
|
||||||
|
end
|
||||||
|
|
||||||
|
return tonumber(json: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 json:byte(pos) == b_rcurly then
|
||||||
|
pos = pos + 1
|
||||||
|
return obj
|
||||||
|
end
|
||||||
|
|
||||||
|
while pos <= len do
|
||||||
|
skip()
|
||||||
|
|
||||||
|
if json:byte(pos) ~= b_quote then
|
||||||
|
error("Expected string key")
|
||||||
|
end
|
||||||
|
|
||||||
|
local key = parse_string()
|
||||||
|
skip()
|
||||||
|
|
||||||
|
if json:byte(pos) ~= b_colon then
|
||||||
|
error("Expected colon")
|
||||||
|
end
|
||||||
|
pos = pos + 1
|
||||||
|
|
||||||
|
obj[key] = parse_value()
|
||||||
|
skip()
|
||||||
|
|
||||||
|
local b = json: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 json: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 = json: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 = json: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 json:sub(pos, pos + 3) == "null" then
|
||||||
|
pos = pos + 4
|
||||||
|
return nil
|
||||||
|
elseif b == string.byte('t') and pos + 3 <= len and json:sub(pos, pos + 3) == "true" then
|
||||||
|
pos = pos + 4
|
||||||
|
return true
|
||||||
|
elseif b == string.byte('f') and pos + 4 <= len and json: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
|
||||||
|
|
||||||
|
return json
|
@ -386,14 +386,6 @@ local util = {
|
|||||||
return __generate_token(length or 32)
|
return __generate_token(length or 32)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
json_encode = function(string)
|
|
||||||
return __json_marshal(string or "{}")
|
|
||||||
end,
|
|
||||||
|
|
||||||
json_decode = function(string)
|
|
||||||
return __json_unmarshal(string or "{}")
|
|
||||||
end,
|
|
||||||
|
|
||||||
-- Deep copy of tables
|
-- Deep copy of tables
|
||||||
deep_copy = function(obj)
|
deep_copy = function(obj)
|
||||||
if type(obj) ~= 'table' then return obj end
|
if type(obj) ~= 'table' then return obj end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user