838 lines
25 KiB
Plaintext
838 lines
25 KiB
Plaintext
// Expect retcode = 0
|
||
// 轻量 JSON 解析 / 输出器(递归下降 + 节点池),用于验证底层库的拼装,未来服务 LSP。
|
||
// 设计:
|
||
// - 输入 ByteView,字符串/数字以“源切片”借用,不做解码,round-trip 保真;
|
||
// - 节点池 Vec[JsonNode];数组/对象子项存 Vec[JsonChild],保留插入顺序;
|
||
// - 错误带字节位置(error.spl);输出写 ByteBuffer;缩进/数字用 ByteBuffer/fmt。
|
||
// 限制:不做字符串转义解码、数字不做数值化(原样保存);递归/输出深度上限见 max_depth()。
|
||
|
||
type .{ .Error, .Status, .make, .at } = @import("error.spl");
|
||
type .{ .ByteView, .ViewResult, .FindResult } = @import("byte_view.spl");
|
||
type .{ .Vec } = @import("vec.spl");
|
||
type .{ .ByteBuffer } = @import("byte_buffer.spl");
|
||
type .{ .is_digit, .is_hex_digit, .is_space } = @import("ascii.spl");
|
||
type .{ .checked_mul } = @import("memory.spl");
|
||
type .{ .append_i64, .append_u64 } = @import("fmt.spl");
|
||
|
||
type JsonKind = enum {
|
||
Null,
|
||
True,
|
||
False,
|
||
Number,
|
||
String,
|
||
Array,
|
||
Object,
|
||
}
|
||
|
||
// 数组元素或对象成员。数组时 key 为空视图;对象时 key 为源中的原始键文本。
|
||
type JsonChild = struct {
|
||
key: ByteView,
|
||
val: usize,
|
||
}
|
||
|
||
// 节点:kind 决定有效载荷字段,其余字段保持空。访问方必须按 kind 读取:
|
||
// Null/True/False -> text 空、children 空;
|
||
// Number/String -> text 为源切片、children 空;
|
||
// Array/Object -> children 保存子项、text 空。
|
||
type JsonNode = struct {
|
||
kind: JsonKind,
|
||
text: ByteView,
|
||
children: Vec,
|
||
}
|
||
|
||
// 解析结果文档:nodes 拥有全部节点;root 为根节点下标。
|
||
type Json = struct {
|
||
nodes: Vec,
|
||
root: usize,
|
||
}
|
||
|
||
type JsonResult = enum {
|
||
Ok: Json,
|
||
Err: Error,
|
||
}
|
||
|
||
type NodeResult = enum {
|
||
Ok: usize,
|
||
Err: Error,
|
||
}
|
||
|
||
type Parser = struct {
|
||
src: ByteView,
|
||
pos: usize,
|
||
nodes: Vec,
|
||
}
|
||
|
||
// 递归/输出深度上限:防止病态输入耗尽调用栈。256 层足够覆盖正常 JSON。
|
||
fn max_depth(): usize {
|
||
ret 256;
|
||
}
|
||
|
||
fn blank_node(kind: JsonKind): JsonNode {
|
||
ret JsonNode {
|
||
.kind = kind,
|
||
.text = ByteView.empty(),
|
||
.children = Vec.blank(@sizeof(JsonChild), 8),
|
||
};
|
||
}
|
||
|
||
fn intern_node(p: *Parser, node: JsonNode): NodeResult {
|
||
var index = p.nodes.len;
|
||
var n = node;
|
||
match Vec.push(&p.nodes, &n as *u8) {
|
||
.Ok => {},
|
||
.Err(error) => { ret .Err(error); },
|
||
}
|
||
ret .Ok(index);
|
||
}
|
||
|
||
fn node_at(doc: *Json, index: usize): *JsonNode {
|
||
match Vec.get(&doc.nodes, index) {
|
||
.Ok(ptr) => { ret ptr as *JsonNode; },
|
||
.Err(error) => { ret null; }, // 越界/无效池统一返回 null,调用方判空
|
||
}
|
||
ret null;
|
||
}
|
||
|
||
fn skip_ws(p: *Parser) {
|
||
while p.pos < p.src.len && is_space(p.src.ptr[p.pos]) {
|
||
p.pos = p.pos + 1;
|
||
}
|
||
}
|
||
|
||
// -1 表示 EOF。
|
||
fn peek(p: *Parser): i32 {
|
||
if p.pos >= p.src.len { ret -1; }
|
||
ret p.src.ptr[p.pos] as i32;
|
||
}
|
||
|
||
// 当前字符是否等于 ch(EOF 时 false)。
|
||
fn at_char(p: *Parser, ch: u8): bool {
|
||
var c = peek(p);
|
||
if c < 0 { ret false; }
|
||
ret (c as u8) == ch;
|
||
}
|
||
|
||
fn parse_string_raw(p: *Parser): ViewResult {
|
||
if !at_char(p, '"') { ret .Err(at(.InvalidFormat, p.pos)); }
|
||
p.pos = p.pos + 1;
|
||
var start = p.pos;
|
||
while p.pos < p.src.len {
|
||
var c = p.src.ptr[p.pos];
|
||
if c == '"' { break; }
|
||
if c == '\\' { // '\\'
|
||
p.pos = p.pos + 1;
|
||
if p.pos >= p.src.len { ret .Err(at(.UnexpectedEnd, p.pos)); }
|
||
if p.src.ptr[p.pos] == 'u' { // 'u'
|
||
var k: usize = 0;
|
||
while k < 4 {
|
||
p.pos = p.pos + 1;
|
||
if p.pos >= p.src.len { ret .Err(at(.UnexpectedEnd, p.pos)); }
|
||
if !is_hex_digit(p.src.ptr[p.pos]) { ret .Err(at(.InvalidDigit, p.pos)); }
|
||
k = k + 1;
|
||
}
|
||
}
|
||
} else if c < 32 {
|
||
// JSON 只禁止未转义的 U+0000..U+001F;0x7F 合法,不能用 ascii.is_control。
|
||
ret .Err(at(.InvalidFormat, p.pos));
|
||
}
|
||
p.pos = p.pos + 1;
|
||
}
|
||
if p.pos >= p.src.len { ret .Err(at(.UnexpectedEnd, p.pos)); }
|
||
var end = p.pos;
|
||
p.pos = p.pos + 1; // 收尾引号
|
||
ret ByteView.slice(p.src, start, end);
|
||
}
|
||
|
||
fn parse_literal(p: *Parser, word: []u8, kind: JsonKind): NodeResult {
|
||
var i: usize = 0;
|
||
while i < word.len {
|
||
if p.pos + i >= p.src.len { ret .Err(at(.UnexpectedEnd, p.src.len)); }
|
||
if p.src.ptr[p.pos + i] != word.ptr[i] {
|
||
ret .Err(at(.InvalidFormat, p.pos + i));
|
||
}
|
||
i = i + 1;
|
||
}
|
||
p.pos = p.pos + word.len;
|
||
var node = blank_node(kind);
|
||
ret intern_node(p, node);
|
||
}
|
||
|
||
fn parse_number(p: *Parser): NodeResult {
|
||
var start = p.pos;
|
||
if at_char(p, '-') { p.pos = p.pos + 1; } // '-'
|
||
if p.pos >= p.src.len || !is_digit(p.src.ptr[p.pos]) {
|
||
ret .Err(at(.InvalidDigit, p.pos));
|
||
}
|
||
if p.src.ptr[p.pos] == '0' { // 前导 0 后面不能接数字
|
||
p.pos = p.pos + 1;
|
||
} else {
|
||
while p.pos < p.src.len && is_digit(p.src.ptr[p.pos]) {
|
||
p.pos = p.pos + 1;
|
||
}
|
||
}
|
||
if p.pos < p.src.len && p.src.ptr[p.pos] == '.' { // '.'
|
||
p.pos = p.pos + 1;
|
||
if p.pos >= p.src.len || !is_digit(p.src.ptr[p.pos]) {
|
||
ret .Err(at(.InvalidDigit, p.pos));
|
||
}
|
||
while p.pos < p.src.len && is_digit(p.src.ptr[p.pos]) {
|
||
p.pos = p.pos + 1;
|
||
}
|
||
}
|
||
if p.pos < p.src.len && (p.src.ptr[p.pos] == 'e' || p.src.ptr[p.pos] == 'E') { // e/E
|
||
p.pos = p.pos + 1;
|
||
if p.pos < p.src.len && (p.src.ptr[p.pos] == '+' || p.src.ptr[p.pos] == '-') {
|
||
p.pos = p.pos + 1;
|
||
}
|
||
if p.pos >= p.src.len || !is_digit(p.src.ptr[p.pos]) {
|
||
ret .Err(at(.InvalidDigit, p.pos));
|
||
}
|
||
while p.pos < p.src.len && is_digit(p.src.ptr[p.pos]) {
|
||
p.pos = p.pos + 1;
|
||
}
|
||
}
|
||
|
||
var text = ByteView.slice(p.src, start, p.pos);
|
||
match text {
|
||
.Ok(v) => {
|
||
var node = blank_node(.Number);
|
||
node.text = v;
|
||
ret intern_node(p, node);
|
||
},
|
||
.Err(error) => { ret .Err(error); },
|
||
}
|
||
}
|
||
|
||
fn parse_array(p: *Parser, depth: usize): NodeResult {
|
||
if depth >= max_depth() { ret .Err(at(.OutOfRange, p.pos)); }
|
||
p.pos = p.pos + 1; // '['
|
||
var kids = Vec.blank(@sizeof(JsonChild), 8);
|
||
|
||
skip_ws(p);
|
||
if at_char(p, ']') { // ']'
|
||
p.pos = p.pos + 1;
|
||
var empty = blank_node(.Array);
|
||
empty.children = kids;
|
||
match intern_node(p, empty) {
|
||
.Ok(index) => { ret .Ok(index); },
|
||
.Err(error) => { Vec.drop(&empty.children); ret .Err(error); },
|
||
}
|
||
}
|
||
|
||
while true {
|
||
var element = parse_value(p, depth + 1);
|
||
var index: usize = 0;
|
||
match element {
|
||
.Ok(v) => { index = v; },
|
||
.Err(error) => { Vec.drop(&kids); ret .Err(error); },
|
||
}
|
||
var child = JsonChild { .key = ByteView.empty(), .val = index };
|
||
match Vec.push(&kids, &child as *u8) {
|
||
.Ok => {},
|
||
.Err(error) => { Vec.drop(&kids); ret .Err(error); },
|
||
}
|
||
|
||
skip_ws(p);
|
||
if at_char(p, ',') { // ','
|
||
p.pos = p.pos + 1;
|
||
continue;
|
||
}
|
||
if at_char(p, ']') { // ']'
|
||
p.pos = p.pos + 1;
|
||
break;
|
||
}
|
||
Vec.drop(&kids);
|
||
ret .Err(at(.InvalidFormat, p.pos));
|
||
}
|
||
|
||
var node = blank_node(.Array);
|
||
node.children = kids;
|
||
match intern_node(p, node) {
|
||
.Ok(index) => { ret .Ok(index); },
|
||
.Err(error) => { Vec.drop(&node.children); ret .Err(error); },
|
||
}
|
||
}
|
||
|
||
fn parse_object(p: *Parser, depth: usize): NodeResult {
|
||
if depth >= max_depth() { ret .Err(at(.OutOfRange, p.pos)); }
|
||
p.pos = p.pos + 1; // '{'
|
||
var kids = Vec.blank(@sizeof(JsonChild), 8);
|
||
|
||
skip_ws(p);
|
||
if at_char(p, '}') { // '}'
|
||
p.pos = p.pos + 1;
|
||
var empty = blank_node(.Object);
|
||
empty.children = kids;
|
||
match intern_node(p, empty) {
|
||
.Ok(index) => { ret .Ok(index); },
|
||
.Err(error) => { Vec.drop(&empty.children); ret .Err(error); },
|
||
}
|
||
}
|
||
|
||
while true {
|
||
skip_ws(p);
|
||
var key_view = ByteView.empty();
|
||
match parse_string_raw(p) {
|
||
.Ok(v) => { key_view = v; },
|
||
.Err(error) => { Vec.drop(&kids); ret .Err(error); },
|
||
}
|
||
|
||
skip_ws(p);
|
||
if !at_char(p, ':') { // ':'
|
||
Vec.drop(&kids);
|
||
ret .Err(at(.InvalidFormat, p.pos));
|
||
}
|
||
p.pos = p.pos + 1;
|
||
|
||
var value = parse_value(p, depth + 1);
|
||
var index: usize = 0;
|
||
match value {
|
||
.Ok(v) => { index = v; },
|
||
.Err(error) => { Vec.drop(&kids); ret .Err(error); },
|
||
}
|
||
|
||
var child = JsonChild { .key = key_view, .val = index };
|
||
match Vec.push(&kids, &child as *u8) {
|
||
.Ok => {},
|
||
.Err(error) => { Vec.drop(&kids); ret .Err(error); },
|
||
}
|
||
|
||
skip_ws(p);
|
||
if at_char(p, ',') { // ','
|
||
p.pos = p.pos + 1;
|
||
continue;
|
||
}
|
||
if at_char(p, '}') { // '}'
|
||
p.pos = p.pos + 1;
|
||
break;
|
||
}
|
||
Vec.drop(&kids);
|
||
ret .Err(at(.InvalidFormat, p.pos));
|
||
}
|
||
|
||
var node = blank_node(.Object);
|
||
node.children = kids;
|
||
match intern_node(p, node) {
|
||
.Ok(index) => { ret .Ok(index); },
|
||
.Err(error) => { Vec.drop(&node.children); ret .Err(error); },
|
||
}
|
||
}
|
||
|
||
fn parse_value(p: *Parser, depth: usize): NodeResult {
|
||
skip_ws(p);
|
||
var c = peek(p);
|
||
if c < 0 { ret .Err(at(.UnexpectedEnd, p.pos)); }
|
||
var ch = c as u8;
|
||
|
||
if ch == '"' { // '"'
|
||
var view = ByteView.empty();
|
||
match parse_string_raw(p) {
|
||
.Ok(v) => { view = v; },
|
||
.Err(error) => { ret .Err(error); },
|
||
}
|
||
var node = blank_node(.String);
|
||
node.text = view;
|
||
ret intern_node(p, node);
|
||
}
|
||
if ch == '{' { ret parse_object(p, depth); } // '{'
|
||
if ch == '[' { ret parse_array(p, depth); } // '['
|
||
if ch == 't' { ret parse_literal(p, "true", .True); }
|
||
if ch == 'f' { ret parse_literal(p, "false", .False); }
|
||
if ch == 'n' { ret parse_literal(p, "null", .Null); }
|
||
ret parse_number(p);
|
||
}
|
||
|
||
fn drop_node_pool(pool: *Vec) {
|
||
var i: usize = 0;
|
||
while i < pool.*.len {
|
||
match Vec.get(pool, i) {
|
||
.Ok(ptr) => {
|
||
var n = ptr as *JsonNode;
|
||
Vec.drop(&n.children);
|
||
},
|
||
// i < pool.len 且池有效,get 不会失败;失败时无额外资源可释放。
|
||
.Err(error) => {},
|
||
}
|
||
i = i + 1;
|
||
}
|
||
Vec.drop(pool);
|
||
}
|
||
|
||
fn parse(src: ByteView): JsonResult {
|
||
var p = Parser {
|
||
.src = src,
|
||
.pos = 0,
|
||
.nodes = Vec.blank(@sizeof(JsonNode), 8),
|
||
};
|
||
|
||
skip_ws(&p);
|
||
var root: usize = 0;
|
||
var result = parse_value(&p, 0);
|
||
match result {
|
||
.Ok(index) => { root = index; },
|
||
.Err(error) => {
|
||
drop_node_pool(&p.nodes);
|
||
ret .Err(error);
|
||
},
|
||
}
|
||
|
||
skip_ws(&p);
|
||
if p.pos != src.len {
|
||
drop_node_pool(&p.nodes);
|
||
ret .Err(at(.InvalidFormat, p.pos));
|
||
}
|
||
|
||
ret .Ok(Json { .nodes = p.nodes, .root = root });
|
||
}
|
||
|
||
fn drop(doc: *Json) {
|
||
drop_node_pool(&doc.nodes);
|
||
}
|
||
|
||
fn root_index(doc: *Json): usize {
|
||
ret doc.root;
|
||
}
|
||
|
||
// 枚举 tag 取整(stage1 枚举 == 有缺陷,用 @enum2int 比较更稳妥)。
|
||
fn kind_code(kind: JsonKind): usize {
|
||
ret @enum2int(kind) as usize;
|
||
}
|
||
|
||
fn text_of(doc: *Json, index: usize): ByteView {
|
||
var n = node_at(doc, index);
|
||
if n == null { ret ByteView.empty(); }
|
||
ret n.text;
|
||
}
|
||
|
||
fn array_count(doc: *Json, index: usize): usize {
|
||
var n = node_at(doc, index);
|
||
if n == null { ret 0; }
|
||
match n.kind {
|
||
.Array => { ret n.children.len; },
|
||
_ => { ret 0; },
|
||
}
|
||
ret 0;
|
||
}
|
||
|
||
fn array_at(doc: *Json, index: usize, i: usize): usize {
|
||
var n = node_at(doc, index);
|
||
if n == null { ret ~(0 as usize); }
|
||
match n.kind {
|
||
.Array => {
|
||
if i >= n.children.len { ret ~(0 as usize); }
|
||
match Vec.get(&n.children, i) {
|
||
.Ok(ptr) => { ret (ptr as *JsonChild).val; },
|
||
.Err(error) => { ret ~(0 as usize); },
|
||
}
|
||
},
|
||
_ => { ret ~(0 as usize); },
|
||
}
|
||
ret ~(0 as usize);
|
||
}
|
||
|
||
// 对象按键查找(线性,保持插入顺序)。找到返回节点下标。
|
||
fn object_find(doc: *Json, index: usize, key: ByteView): FindResult {
|
||
var n = node_at(doc, index);
|
||
if n == null { ret .NotFound; }
|
||
match n.kind {
|
||
.Object => {
|
||
var i: usize = 0;
|
||
var count = n.children.len;
|
||
while i < count {
|
||
match Vec.get(&n.children, i) {
|
||
.Ok(ptr) => {
|
||
var child = ptr as *JsonChild;
|
||
if ByteView.eq(child.key, key) { ret .Found(child.val); }
|
||
},
|
||
// i < count == children.len,get 不会失败。
|
||
.Err(error) => {},
|
||
}
|
||
i = i + 1;
|
||
}
|
||
ret .NotFound;
|
||
},
|
||
_ => { ret .NotFound; },
|
||
}
|
||
ret .NotFound;
|
||
}
|
||
|
||
fn emit_quoted(out: *ByteBuffer, text: ByteView): Status {
|
||
match ByteBuffer.append_byte(out, '"') { // '"'
|
||
.Ok => {},
|
||
.Err(error) => { ret .Err(error); },
|
||
}
|
||
match ByteBuffer.append(out, ByteView.as_slice(text)) {
|
||
.Ok => {},
|
||
.Err(error) => { ret .Err(error); },
|
||
}
|
||
ret ByteBuffer.append_byte(out, '"');
|
||
}
|
||
|
||
fn emit_indent(out: *ByteBuffer, indent: usize, depth: usize): Status {
|
||
match ByteBuffer.append_byte(out, '\n') { // '\n'
|
||
.Ok => {},
|
||
.Err(error) => { ret .Err(error); },
|
||
}
|
||
var spaces: usize = 0;
|
||
match checked_mul(depth, indent) {
|
||
.Ok(value) => { spaces = value; },
|
||
.Err(error) => { ret .Err(error); },
|
||
}
|
||
ret ByteBuffer.append_repeat(out, 32, spaces);
|
||
}
|
||
|
||
fn emit(out: *ByteBuffer, doc: *Json, index: usize, indent: usize, depth: usize): Status {
|
||
if depth >= max_depth() { ret .Err(make(.OutOfRange)); }
|
||
var n = node_at(doc, index);
|
||
if n == null { ret .Err(make(.OutOfRange)); }
|
||
|
||
match n.kind {
|
||
.Null => { ret ByteBuffer.append(out, "null"); },
|
||
.True => { ret ByteBuffer.append(out, "true"); },
|
||
.False => { ret ByteBuffer.append(out, "false"); },
|
||
.Number => { ret ByteBuffer.append(out, ByteView.as_slice(n.text)); },
|
||
.String => { ret emit_quoted(out, n.text); },
|
||
.Array => { ret emit_children(out, doc, n, false, indent, depth); },
|
||
.Object => { ret emit_children(out, doc, n, true, indent, depth); },
|
||
}
|
||
ret .Ok;
|
||
}
|
||
|
||
fn emit_children(out: *ByteBuffer, doc: *Json, n: *JsonNode, is_object: bool, indent: usize,
|
||
depth: usize): Status {
|
||
var open_byte: u8 = '['; // '['
|
||
var close_byte: u8 = ']'; // ']'
|
||
if is_object {
|
||
open_byte = '{'; // '{'
|
||
close_byte = '}'; // '}'
|
||
}
|
||
|
||
match ByteBuffer.append_byte(out, open_byte) {
|
||
.Ok => {},
|
||
.Err(error) => { ret .Err(error); },
|
||
}
|
||
|
||
var count = n.children.len;
|
||
if count == 0 {
|
||
ret ByteBuffer.append_byte(out, close_byte);
|
||
}
|
||
|
||
var i: usize = 0;
|
||
while i < count {
|
||
if i != 0 {
|
||
match ByteBuffer.append_byte(out, ',') { // ','
|
||
.Ok => {},
|
||
.Err(error) => { ret .Err(error); },
|
||
}
|
||
}
|
||
if indent != 0 {
|
||
match emit_indent(out, indent, depth + 1) {
|
||
.Ok => {},
|
||
.Err(error) => { ret .Err(error); },
|
||
}
|
||
}
|
||
|
||
match Vec.get(&n.children, i) {
|
||
.Ok(ptr) => {
|
||
var child = ptr as *JsonChild;
|
||
if is_object {
|
||
match emit_quoted(out, child.key) {
|
||
.Ok => {},
|
||
.Err(error) => { ret .Err(error); },
|
||
}
|
||
match ByteBuffer.append_byte(out, ':') { // ':'
|
||
.Ok => {},
|
||
.Err(error) => { ret .Err(error); },
|
||
}
|
||
if indent != 0 {
|
||
match ByteBuffer.append_byte(out, ' ') { // ' '
|
||
.Ok => {},
|
||
.Err(error) => { ret .Err(error); },
|
||
}
|
||
}
|
||
}
|
||
match emit(out, doc, child.val, indent, depth + 1) {
|
||
.Ok => {},
|
||
.Err(error) => { ret .Err(error); },
|
||
}
|
||
},
|
||
.Err(error) => { ret .Err(error); },
|
||
}
|
||
i = i + 1;
|
||
}
|
||
|
||
if indent != 0 {
|
||
match emit_indent(out, indent, depth) {
|
||
.Ok => {},
|
||
.Err(error) => { ret .Err(error); },
|
||
}
|
||
}
|
||
ret ByteBuffer.append_byte(out, close_byte);
|
||
}
|
||
|
||
fn emit_compact(out: *ByteBuffer, doc: *Json): Status {
|
||
ret emit(out, doc, doc.root, 0, 0);
|
||
}
|
||
|
||
fn emit_pretty(out: *ByteBuffer, doc: *Json, indent: usize): Status {
|
||
ret emit(out, doc, doc.root, indent, 0);
|
||
}
|
||
|
||
// ---- 构造辅助(写 JSON 片段;字符串需由调用方保证已转义)----
|
||
|
||
fn write_int(out: *ByteBuffer, value: i64): Status {
|
||
ret append_i64(out, value, 10, false);
|
||
}
|
||
|
||
fn write_u64(out: *ByteBuffer, value: u64): Status {
|
||
ret append_u64(out, value, 10, false);
|
||
}
|
||
|
||
// ---- 测试辅助 ----
|
||
|
||
fn expect_ok(result: Status) {
|
||
match result {
|
||
.Ok => {},
|
||
.Err(error) => { @assert(false, "unexpected json error"); },
|
||
}
|
||
}
|
||
|
||
fn expect_bytes(out: *ByteBuffer, expected: []u8) {
|
||
var actual = ByteBuffer.as_slice(out.*);
|
||
@assert(actual.len == expected.len, "emitted length");
|
||
var i: usize = 0;
|
||
while i < expected.len {
|
||
@assert(actual.ptr[i] == expected.ptr[i], "emitted byte");
|
||
i = i + 1;
|
||
}
|
||
}
|
||
|
||
fn expect_parse_error(result: JsonResult, pos: usize) {
|
||
match result {
|
||
.Ok(doc) => { @assert(false, "expected parse error"); },
|
||
.Err(error) => {
|
||
@assert(error.has_position, "positioned error");
|
||
@assert(error.position == pos, "error position");
|
||
},
|
||
}
|
||
}
|
||
|
||
fn expect_kind(doc: *Json, index: usize, kind: JsonKind) {
|
||
var n = node_at(doc, index);
|
||
@assert(n != null, "node exists");
|
||
@assert(kind_code(n.kind) == kind_code(kind), "node kind");
|
||
}
|
||
|
||
fn expect_text(doc: *Json, index: usize, expected: []u8) {
|
||
var got = text_of(doc, index);
|
||
var want = ByteView.from_slice(expected);
|
||
@assert(ByteView.eq(got, want), "node text");
|
||
}
|
||
|
||
fn test_scalars() {
|
||
var r = parse(ByteView.from_slice("null"));
|
||
match r {
|
||
.Ok(doc) => { expect_kind(&doc, root_index(&doc), .Null); drop(&doc); },
|
||
.Err(error) => { @assert(false, "parse null"); },
|
||
}
|
||
|
||
r = parse(ByteView.from_slice("true"));
|
||
match r {
|
||
.Ok(doc) => { expect_kind(&doc, root_index(&doc), .True); drop(&doc); },
|
||
.Err(error) => { @assert(false, "parse true"); },
|
||
}
|
||
|
||
r = parse(ByteView.from_slice("false"));
|
||
match r {
|
||
.Ok(doc) => { expect_kind(&doc, root_index(&doc), .False); drop(&doc); },
|
||
.Err(error) => { @assert(false, "parse false"); },
|
||
}
|
||
|
||
r = parse(ByteView.from_slice("-12.5e2"));
|
||
match r {
|
||
.Ok(doc) => {
|
||
var idx = root_index(&doc);
|
||
expect_kind(&doc, idx, .Number);
|
||
expect_text(&doc, idx, "-12.5e2");
|
||
drop(&doc);
|
||
},
|
||
.Err(error) => { @assert(false, "parse number"); },
|
||
}
|
||
|
||
r = parse(ByteView.from_slice("\"hi\\n\""));
|
||
match r {
|
||
.Ok(doc) => {
|
||
var idx = root_index(&doc);
|
||
expect_kind(&doc, idx, .String);
|
||
expect_text(&doc, idx, "hi\\n"); // 原始切片,未解码
|
||
drop(&doc);
|
||
},
|
||
.Err(error) => { @assert(false, "parse string"); },
|
||
}
|
||
}
|
||
|
||
fn test_containers_and_lookup() {
|
||
var text = "{\"a\":1,\"b\":[true,null,\"x\"],\"c\":{\"d\":2}}";
|
||
var r = parse(ByteView.from_slice(text));
|
||
match r {
|
||
.Ok(doc) => {
|
||
var root = root_index(&doc);
|
||
expect_kind(&doc, root, .Object);
|
||
|
||
var a = ByteView.from_slice("a");
|
||
match object_find(&doc, root, a) {
|
||
.Found(idx) => {
|
||
expect_kind(&doc, idx, .Number);
|
||
expect_text(&doc, idx, "1");
|
||
},
|
||
.NotFound => { @assert(false, "find a"); },
|
||
}
|
||
|
||
var b = ByteView.from_slice("b");
|
||
var bidx: usize = 0;
|
||
match object_find(&doc, root, b) {
|
||
.Found(idx) => { bidx = idx; },
|
||
.NotFound => { @assert(false, "find b"); },
|
||
}
|
||
expect_kind(&doc, bidx, .Array);
|
||
@assert(array_count(&doc, bidx) == 3, "array count");
|
||
expect_kind(&doc, array_at(&doc, bidx, 0), .True);
|
||
expect_kind(&doc, array_at(&doc, bidx, 1), .Null);
|
||
expect_text(&doc, array_at(&doc, bidx, 2), "x");
|
||
|
||
var c = ByteView.from_slice("c");
|
||
var cidx: usize = 0;
|
||
match object_find(&doc, root, c) {
|
||
.Found(idx) => { cidx = idx; },
|
||
.NotFound => { @assert(false, "find c"); },
|
||
}
|
||
var d = ByteView.from_slice("d");
|
||
match object_find(&doc, cidx, d) {
|
||
.Found(idx) => { expect_text(&doc, idx, "2"); },
|
||
.NotFound => { @assert(false, "find c.d"); },
|
||
}
|
||
|
||
var missing = ByteView.from_slice("zzz");
|
||
match object_find(&doc, root, missing) {
|
||
.Found(idx) => { @assert(false, "unexpected find"); },
|
||
.NotFound => {},
|
||
}
|
||
|
||
drop(&doc);
|
||
},
|
||
.Err(error) => { @assert(false, "parse object"); },
|
||
}
|
||
}
|
||
|
||
fn test_roundtrip_compact() {
|
||
var text = "{\"a\":1,\"b\":[true,null,\"x\\n\"],\"c\":{},\"d\":[],\"e\":-3.5e-2}";
|
||
var r = parse(ByteView.from_slice(text));
|
||
match r {
|
||
.Ok(doc) => {
|
||
var out = ByteBuffer.new();
|
||
expect_ok(emit_compact(&out, &doc));
|
||
expect_bytes(&out, text);
|
||
ByteBuffer.drop(&out);
|
||
drop(&doc);
|
||
},
|
||
.Err(error) => { @assert(false, "parse for roundtrip"); },
|
||
}
|
||
}
|
||
|
||
fn test_pretty() {
|
||
var text = "{\"a\":1,\"b\":[1,2]}";
|
||
var r = parse(ByteView.from_slice(text));
|
||
match r {
|
||
.Ok(doc) => {
|
||
var out = ByteBuffer.new();
|
||
expect_ok(emit_pretty(&out, &doc, 2));
|
||
var expected = "{\n \"a\": 1,\n \"b\": [\n 1,\n 2\n ]\n}";
|
||
expect_bytes(&out, expected);
|
||
ByteBuffer.drop(&out);
|
||
drop(&doc);
|
||
},
|
||
.Err(error) => { @assert(false, "parse pretty"); },
|
||
}
|
||
}
|
||
|
||
fn test_errors() {
|
||
expect_parse_error(parse(ByteView.from_slice("")), 0);
|
||
expect_parse_error(parse(ByteView.from_slice("\"abc")), 4);
|
||
expect_parse_error(parse(ByteView.from_slice("tru")), 3);
|
||
expect_parse_error(parse(ByteView.from_slice("[1,]")), 3);
|
||
expect_parse_error(parse(ByteView.from_slice("{\"a\":}")), 5);
|
||
expect_parse_error(parse(ByteView.from_slice("1 2")), 2);
|
||
expect_parse_error(parse(ByteView.from_slice("01")), 1);
|
||
}
|
||
|
||
fn test_depth_limit() {
|
||
var out = ByteBuffer.new();
|
||
defer { ByteBuffer.drop(&out); }
|
||
var i: usize = 0;
|
||
while i < max_depth() + 8 {
|
||
expect_ok(ByteBuffer.append_byte(&out, '['));
|
||
i = i + 1;
|
||
}
|
||
i = 0;
|
||
while i < max_depth() + 8 {
|
||
expect_ok(ByteBuffer.append_byte(&out, ']'));
|
||
i = i + 1;
|
||
}
|
||
match parse(ByteView.from_slice(ByteBuffer.as_slice(out))) {
|
||
.Ok(doc) => { drop(&doc); @assert(false, "deep input should be rejected"); },
|
||
.Err(error) => {
|
||
match error.kind {
|
||
.OutOfRange => {},
|
||
_ => { @assert(false, "depth limit error kind"); },
|
||
}
|
||
},
|
||
}
|
||
}
|
||
|
||
fn test_construction_helpers() {
|
||
var out = ByteBuffer.new();
|
||
defer { ByteBuffer.drop(&out); }
|
||
expect_ok(ByteBuffer.append(&out, "{\"id\":"));
|
||
expect_ok(write_u64(&out, 18446744073709551615));
|
||
expect_ok(ByteBuffer.append(&out, ",\"n\":"));
|
||
expect_ok(write_int(&out, -7));
|
||
expect_ok(ByteBuffer.append(&out, "}"));
|
||
expect_bytes(&out, "{\"id\":18446744073709551615,\"n\":-7}");
|
||
}
|
||
|
||
fn test_large_array() {
|
||
var out = ByteBuffer.new();
|
||
defer { ByteBuffer.drop(&out); }
|
||
expect_ok(ByteBuffer.append_byte(&out, '[')); // '['
|
||
var i: usize = 0;
|
||
while i < 1000 {
|
||
if i != 0 { expect_ok(ByteBuffer.append_byte(&out, ',')); }
|
||
expect_ok(ByteBuffer.append_byte(&out, '1')); // '1'
|
||
i = i + 1;
|
||
}
|
||
expect_ok(ByteBuffer.append_byte(&out, ']')); // ']'
|
||
|
||
var text = ByteBuffer.as_slice(out);
|
||
var r = parse(ByteView.from_slice(text));
|
||
match r {
|
||
.Ok(doc) => {
|
||
expect_kind(&doc, root_index(&doc), .Array);
|
||
@assert(array_count(&doc, root_index(&doc)) == 1000, "large array count");
|
||
expect_kind(&doc, array_at(&doc, root_index(&doc), 999), .Number);
|
||
drop(&doc);
|
||
},
|
||
.Err(error) => { @assert(false, "parse large array"); },
|
||
}
|
||
}
|
||
|
||
fn main() {
|
||
test_scalars();
|
||
test_containers_and_lookup();
|
||
test_roundtrip_compact();
|
||
test_pretty();
|
||
test_errors();
|
||
test_depth_limit();
|
||
test_construction_helpers();
|
||
test_large_array();
|
||
}
|