Files
spl/stage2/utils/json.spl
T

838 lines
25 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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();
}