enhance json module
This commit is contained in:
parent
55e18c5f30
commit
df9e0cb21b
@ -13,10 +13,15 @@ import (
|
||||
//go:embed sandbox.lua
|
||||
var sandboxLuaCode string
|
||||
|
||||
//go:embed json.lua
|
||||
var jsonLuaCode string
|
||||
|
||||
// Global bytecode cache to improve performance
|
||||
var (
|
||||
sandboxBytecode atomic.Pointer[[]byte]
|
||||
bytecodeOnce sync.Once
|
||||
sandboxBytecode atomic.Pointer[[]byte]
|
||||
jsonBytecode atomic.Pointer[[]byte]
|
||||
bytecodeOnce sync.Once
|
||||
jsonBytecodeOnce sync.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))
|
||||
}
|
||||
|
||||
// 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
|
||||
func loadSandboxIntoState(state *luajit.State, verbose bool) error {
|
||||
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()
|
||||
if bytecode != nil && len(*bytecode) > 0 {
|
||||
if verbose {
|
||||
@ -53,9 +112,9 @@ func loadSandboxIntoState(state *luajit.State, verbose bool) error {
|
||||
return state.LoadAndRunBytecode(*bytecode, "sandbox.lua")
|
||||
}
|
||||
|
||||
// Fallback to direct execution
|
||||
// Fallback
|
||||
if verbose {
|
||||
logger.Warning("Using non-precompiled sandbox.lua (bytecode compilation failed)")
|
||||
logger.Warning("Using non-precompiled sandbox.lua")
|
||||
}
|
||||
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)
|
||||
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 = function(obj)
|
||||
if type(obj) ~= 'table' then return obj end
|
||||
|
Loading…
x
Reference in New Issue
Block a user