Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions include/qjson.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ typedef struct {
} qjson_iter;

const char* qjson_strerror(int code);
size_t qjson_format_error(int code, size_t offset, size_t extra,
const char* buf, size_t buf_len,
char* out, size_t out_len);
size_t qjson_doc_last_error_offset(const qjson_doc* doc);

qjson_doc* qjson_parse(const uint8_t* buf, size_t len, qjson_error* err_out);
qjson_doc* qjson_parse_ex(const uint8_t* buf, size_t len,
Expand Down
101 changes: 71 additions & 30 deletions lua/qjson.lua
Original file line number Diff line number Diff line change
Expand Up @@ -42,28 +42,68 @@ _M.ERR = ERR
local Doc = {}; Doc.__index = Doc
local Cursor = {}; Cursor.__index = Cursor

local function check_err(rc)
local MODE_EAGER = 0
local MODE_LAZY = 1
local DEFAULT_MAX_DEPTH = 1024
local MAX_MAX_DEPTH = 4096
local SIZE_MAX = ffi.cast("size_t", -1)
local EXPECT_CONTAINER = ffi.cast("size_t", -2)

local function extra_or_none(extra)
if extra == nil then return SIZE_MAX end
return extra
end

local function format_error(code, offset, extra, buf)
local buf_len = 0
if type(buf) == "string" then
buf_len = #buf
else
buf = nil
end
extra = extra_or_none(extra)
local needed = tonumber(C.qjson_format_error(code, offset, extra, buf, buf_len, nil, 0))
local out = ffi.new("char[?]", needed + 1)
local written = tonumber(C.qjson_format_error(code, offset, extra, buf, buf_len, out, needed + 1))
return ffi.string(out, written)
end

local function check_access(doc, rc, expected_type)
if rc == 0 then return true end
if rc == NOT_FOUND then return false end
error("qjson: " .. ffi.string(C.qjson_strerror(rc)))
local offset = SIZE_MAX
local source = nil
if doc and doc._ptr ~= nil then
offset = C.qjson_doc_last_error_offset(doc._ptr)
source = doc._hold
end
local msg = format_error(rc, offset, expected_type, source)
error("qjson: " .. msg)
end

local opts_box = ffi.new("qjson_options[1]")

local MODE_EAGER = 0
local MODE_LAZY = 1
local SIZE_MAX = ffi.cast("size_t", -1)
local function effective_max_depth(raw)
if raw == 0 then
return DEFAULT_MAX_DEPTH
end
if raw > MAX_MAX_DEPTH then
return MAX_MAX_DEPTH
end
return raw
end

local function parse_error_message(err)
local msg = ffi.string(C.qjson_strerror(err.code))
if err.offset ~= SIZE_MAX then
msg = msg .. " at byte " .. tostring(tonumber(err.offset))
local function parse_error_message(err, json_str, max_depth)
local extra
if err.code == ERR.NESTING_TOO_DEEP then
extra = max_depth or DEFAULT_MAX_DEPTH
end
return msg
return format_error(err.code, err.offset, extra, json_str)
end

function _M.parse(json_str, opts)
local ptr
local effective_depth = DEFAULT_MAX_DEPTH
if opts == nil then
ptr = C.qjson_parse(json_str, #json_str, err_box)
else
Expand All @@ -78,12 +118,13 @@ function _M.parse(json_str, opts)
if type(max_depth) ~= "number" or max_depth < 0 or max_depth ~= math.floor(max_depth) then
error("qjson.parse: opts.max_depth must be a non-negative integer")
end
effective_depth = effective_max_depth(max_depth)
opts_box[0].mode = lazy and MODE_LAZY or MODE_EAGER
opts_box[0].max_depth = max_depth
ptr = C.qjson_parse_ex(json_str, #json_str, opts_box, err_box)
end
if ptr == nil then
error("qjson: " .. parse_error_message(err_box[0]))
error("qjson: " .. parse_error_message(err_box[0], json_str, effective_depth))
end
return setmetatable({
_ptr = ffi.gc(ptr, C.qjson_free),
Expand All @@ -93,122 +134,122 @@ end

function Doc:get_str(path)
local rc = C.qjson_get_str(self._ptr, path, #path, strp_box, size_box)
if not check_err(rc) then return nil end
if not check_access(self, rc, _M.T_STR) then return nil end
return ffi.string(strp_box[0], size_box[0])
end

function Doc:get_i64(path)
local rc = C.qjson_get_i64(self._ptr, path, #path, i64_box)
if not check_err(rc) then return nil end
if not check_access(self, rc, _M.T_NUM) then return nil end
return i64_box[0]
end

function Doc:get_u64(path)
local rc = C.qjson_get_u64(self._ptr, path, #path, u64_box)
if not check_err(rc) then return nil end
if not check_access(self, rc, _M.T_NUM) then return nil end
return u64_box[0]
end

function Doc:get_f64(path)
local rc = C.qjson_get_f64(self._ptr, path, #path, f64_box)
if not check_err(rc) then return nil end
if not check_access(self, rc, _M.T_NUM) then return nil end
return f64_box[0]
end

function Doc:get_bool(path)
local rc = C.qjson_get_bool(self._ptr, path, #path, bool_box)
if not check_err(rc) then return nil end
if not check_access(self, rc, _M.T_BOOL) then return nil end
return bool_box[0] ~= 0
end

function Doc:is_null(path)
local rc = C.qjson_is_null(self._ptr, path, #path, bool_box)
if not check_err(rc) then return nil end
if not check_access(self, rc) then return nil end
return bool_box[0] ~= 0
end

function Doc:typeof(path)
local rc = C.qjson_typeof(self._ptr, path, #path, type_box)
if not check_err(rc) then return nil end
if not check_access(self, rc) then return nil end
return type_box[0]
end

function Doc:len(path)
local rc = C.qjson_len(self._ptr, path, #path, size_box)
if not check_err(rc) then return nil end
if not check_access(self, rc, EXPECT_CONTAINER) then return nil end
return tonumber(size_box[0])
end

function Doc:open(path)
local rc = C.qjson_open(self._ptr, path, #path, cur_box)
if not check_err(rc) then return nil end
if not check_access(self, rc) then return nil end
return setmetatable({ _cur = cur_box[0], _doc = self }, Cursor)
end

function Cursor:get_str(path)
path = path or ""
local rc = C.qjson_cursor_get_str(self._cur, path, #path, strp_box, size_box)
if not check_err(rc) then return nil end
if not check_access(self._doc, rc, _M.T_STR) then return nil end
return ffi.string(strp_box[0], size_box[0])
end

function Cursor:get_i64(path)
path = path or ""
local rc = C.qjson_cursor_get_i64(self._cur, path, #path, i64_box)
if not check_err(rc) then return nil end
if not check_access(self._doc, rc, _M.T_NUM) then return nil end
return i64_box[0]
end

function Cursor:get_u64(path)
path = path or ""
local rc = C.qjson_cursor_get_u64(self._cur, path, #path, u64_box)
if not check_err(rc) then return nil end
if not check_access(self._doc, rc, _M.T_NUM) then return nil end
return u64_box[0]
end

function Cursor:get_f64(path)
path = path or ""
local rc = C.qjson_cursor_get_f64(self._cur, path, #path, f64_box)
if not check_err(rc) then return nil end
if not check_access(self._doc, rc, _M.T_NUM) then return nil end
return f64_box[0]
end

function Cursor:get_bool(path)
path = path or ""
local rc = C.qjson_cursor_get_bool(self._cur, path, #path, bool_box)
if not check_err(rc) then return nil end
if not check_access(self._doc, rc, _M.T_BOOL) then return nil end
return bool_box[0] ~= 0
end

function Cursor:typeof(path)
path = path or ""
local rc = C.qjson_cursor_typeof(self._cur, path, #path, type_box)
if not check_err(rc) then return nil end
if not check_access(self._doc, rc) then return nil end
return type_box[0]
end

function Cursor:len(path)
path = path or ""
local rc = C.qjson_cursor_len(self._cur, path, #path, size_box)
if not check_err(rc) then return nil end
if not check_access(self._doc, rc, EXPECT_CONTAINER) then return nil end
return tonumber(size_box[0])
end

function Cursor:open(path)
local rc = C.qjson_cursor_open(self._cur, path, #path, cur_box)
if not check_err(rc) then return nil end
if not check_access(self._doc, rc) then return nil end
return setmetatable({ _cur = cur_box[0], _doc = self._doc }, Cursor)
end

function Cursor:field(key)
local rc = C.qjson_cursor_field(self._cur, key, #key, cur_box)
if not check_err(rc) then return nil end
if not check_access(self._doc, rc, _M.T_OBJ) then return nil end
return setmetatable({ _cur = cur_box[0], _doc = self._doc }, Cursor)
end

function Cursor:index(i)
local rc = C.qjson_cursor_index(self._cur, i, cur_box)
if not check_err(rc) then return nil end
if not check_access(self._doc, rc, _M.T_ARR) then return nil end
return setmetatable({ _cur = cur_box[0], _doc = self._doc }, Cursor)
end

Expand Down
6 changes: 6 additions & 0 deletions lua/qjson/lib.lua
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ typedef struct {
} qjson_options;

const char* qjson_strerror(int code);
size_t qjson_format_error(int code, size_t offset, size_t extra,
const char* buf, size_t buf_len,
char* out, size_t out_len);
size_t qjson_doc_last_error_offset(const qjson_doc* doc);
qjson_doc* qjson_parse (const uint8_t* buf, size_t len, qjson_error* err_out);
qjson_doc* qjson_parse_ex(const uint8_t* buf, size_t len,
const qjson_options* opts, qjson_error* err_out);
Expand Down Expand Up @@ -63,6 +67,8 @@ local attempts = {}
local last_error
local required_symbols = {
"qjson_strerror",
"qjson_format_error",
"qjson_doc_last_error_offset",
"qjson_parse",
"qjson_parse_ex",
"qjson_free",
Expand Down
Loading
Loading