Files
sx/examples/0713-modules-json-writer.sx
agra d8076b9333 lang: rename signed integer types sN -> iN
Surface rename of the signed integer family: s1..s64 become i1..i64
(u1..u64, usize, isize unchanged). 'string' keeps the s-prefix arm in
name classification; width parsing moves to the i-prefix arm next to
isize.

Internal TypeId tags follow the surface (.s8/.s16/.s32/.s64 ->
.i8/.i16/.i32/.i64), as do mono-key mangle fragments (ptr_i64,
tu_i64_bool) and all display/diagnostic formatting (i{d}).

Migrated in the same sweep: stdlib + examples + issue repros + FFI C
companions (shared symbol names like ffi_id_i64), expected
stdout/stderr/ir snapshots, specs.md, readme.md, CLAUDE.md/AGENTS.md,
implementation_plan.md, docs/, issue writeups. Vendored stb_image and
historical flow state left untouched.

zig build test: 426/426; examples suite: 595/595.
2026-06-12 09:31:53 +03:00

108 lines
4.6 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 / i64 MIN
// (-9223372036854775808) / i64 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
// i64 MIN: its magnitude (9223372036854775808) is not a representable
// positive i64 literal, so build it from MAX-positive minus one.
obj.put("min", .int_(0 - 9223372036854775807 - 1), alloc);
obj.put("max", .int_(9223372036854775807), alloc); // i64 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;
}