// JSON value model + writer from `modules/std/json.sx`. // // Builds a representative value — a nested object holding a string with // every escape kind (quote, newline, tab, backslash, a raw control byte), // integers spanning zero / a small negative / a small positive / s64 MIN // (-9223372036854775808) / s64 MAX (9223372036854775807), a bool, null, an // array, and a nested object — then serializes it two ways and asserts the // EXACT bytes: // // 1. into a caller-owned `[]u8` buffer (returns bytes written), // 2. streaming straight to a file through an 8-byte staging buffer // (small on purpose, so the writer flushes many times and no // whole-document string is ever held). // // Both must yield byte-for-byte the same pinned document, with keys in // INSERTION ORDER. A too-small buffer must raise `error.Overflow` rather // than truncate. The model is built through an explicit Arena allocator // and freed in one `deinit`; the writer path allocates nothing. #import "modules/std.sx"; #import "modules/std/mem.sx"; // `Allocator` is non-transitive: name it, import it. #import "modules/std/json.sx"; #import "modules/std/fs.sx"; // The exact document the writer must produce (insertion order, escaping). EXPECT :: "{\"name\":\"a\\\"b\\n\",\"tab\":\"x\\ty\",\"bs\":\"c\\\\d\",\"ctrl\":\"\\u0001\",\"n\":-7,\"zero\":0,\"pos\":7,\"min\":-9223372036854775808,\"max\":9223372036854775807,\"ok\":true,\"nil\":null,\"xs\":[1,-2,3],\"nested\":{\"k\":\"v\"}}"; report :: (label: string, ok: bool) { if ok { print("{}: ok\n", label); } else { print("{}: FAIL\n", label); } } build :: (alloc: Allocator) -> Value { // A raw control byte (0x01) viewed as a 1-byte string — exercises the // `\u00XX` path that has no named shorthand. String values are VIEWS, // so the bytes must outlive the writes: back them with `alloc` (the // arena), not a local that dies when `build` returns. cbytes : [*]u8 = xx alloc.alloc_bytes(1); cbytes[0] = 1; ctrl := string.{ ptr = cbytes, len = 1 }; nested : Object = .{}; nested.put("k", .str("v"), alloc); xs : Array = .{}; xs.add(.int_(1), alloc); xs.add(.int_(0 - 2), alloc); xs.add(.int_(3), alloc); obj : Object = .{}; obj.put("name", .str("a\"b\n"), alloc); // quote + newline obj.put("tab", .str("x\ty"), alloc); // tab obj.put("bs", .str("c\\d"), alloc); // backslash obj.put("ctrl", .str(ctrl), alloc); // raw control byte ->  obj.put("n", .int_(0 - 7), alloc); // small negative int obj.put("zero", .int_(0), alloc); // zero obj.put("pos", .int_(7), alloc); // small positive int // s64 MIN: its magnitude (9223372036854775808) is not a representable // positive s64 literal, so build it from MAX-positive minus one. obj.put("min", .int_(0 - 9223372036854775807 - 1), alloc); obj.put("max", .int_(9223372036854775807), alloc); // s64 MAX obj.put("ok", .bool_(true), alloc); obj.put("nil", .null_, alloc); obj.put("xs", .array(xs), alloc); obj.put("nested", .object(nested), alloc); return .object(obj); } main :: () -> ! { gpa := GPA.init(); arena := Arena.init(xx gpa, 4096); defer arena.deinit(); root := build(xx arena); // 1. Write into a caller buffer; assert exact bytes + byte count. buf : [512]u8 = ---; n := try write_to_buffer(root, string.{ ptr = @buf[0], len = 512 }); view := string.{ ptr = @buf[0], len = n }; print("doc: {}\n", view); report("buffer-exact", view == EXPECT); report("buffer-len", n == EXPECT.len); // 2. A buffer that is one byte too small must raise Overflow. tight : []u8 = string.{ ptr = @buf[256], len = EXPECT.len - 1 }; _, oerr := write_to_buffer(root, tight); report("overflow-raised", oerr == error.Overflow); // 3. Stream to a file through a tiny staging buffer (forces flushes); // read it back and assert it equals the same document. Write into the // repo-local, gitignored scratch dir and unlink afterwards so nothing // leaks and concurrent runs don't fight over a shared /tmp name. if !create_dir_all(".sx-tmp") { print("mkdir: FAIL\n"); return; } path := ".sx-tmp/sx_0713_json.json"; fh := open_file(path, .write); if fh == null { print("open: FAIL\n"); return; } f := fh!; stage : [8]u8 = ---; try write_to_file(root, @f, string.{ ptr = @stage[0], len = 8 }); f.close(); back := read_file(path); delete_file(path); if back == null { print("file-read: FAIL\n"); return; } report("file-exact", back! == EXPECT); return; }