std/json: value model + zero-alloc writer with stable key order
Add library/modules/std/json.sx — the JSON value model and writer
(reader lands in a later step).
Value model: a tagged union over null/bool/integer(s64)/string/array/
object. Objects are an ORDERED list of (key,value) pairs preserving
INSERTION ORDER (no hash map, never sorted/deduped). Integers only — no
fraction/exponent this milestone.
Heap discipline:
- Scalars carry no heap; string values are VIEWS into caller memory
(never copied into the node).
- Composite nodes (Array/Object) own growable child storage, allocated
through an EXPLICIT allocator parameter on the builder methods
(arr.add(v, alloc) / obj.put(key, val, alloc), mirroring List.append)
— never the implicit context allocator.
- The writer adds ZERO output allocations: it emits into a caller-
provided Sink, either a fixed []u8 buffer (overflow raises, never
truncates) or streaming straight to an fs.File through a small caller
staging buffer (no whole-document string; peak memory O(staging)).
Integer digits format in a stack [20]u8; s64 MIN is handled by
formatting in negative space. Sink/IO/overflow surface on the !
error channel.
examples/0713-modules-json-writer.sx builds a nested object + array +
string with every escape kind + negative int + bool + null, then asserts
the EXACT bytes (insertion order, escaping) from both the buffer sink and
the file-streaming sink, plus the overflow-raises path.
This commit is contained in:
95
examples/0713-modules-json-writer.sx
Normal file
95
examples/0713-modules-json-writer.sx
Normal file
@@ -0,0 +1,95 @@
|
||||
// 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),
|
||||
// a negative integer, 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/json.sx";
|
||||
#import "modules/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,\"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(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); // negative int
|
||||
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.
|
||||
path := "/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);
|
||||
if back == null { print("file-read: FAIL\n"); return; }
|
||||
report("file-exact", back! == EXPECT);
|
||||
delete_file(path);
|
||||
return;
|
||||
}
|
||||
1
examples/expected/0713-modules-json-writer.exit
Normal file
1
examples/expected/0713-modules-json-writer.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
0
examples/expected/0713-modules-json-writer.stderr
Normal file
0
examples/expected/0713-modules-json-writer.stderr
Normal file
5
examples/expected/0713-modules-json-writer.stdout
Normal file
5
examples/expected/0713-modules-json-writer.stdout
Normal file
@@ -0,0 +1,5 @@
|
||||
doc: {"name":"a\"b\n","tab":"x\ty","bs":"c\\d","ctrl":"\u0001","n":-7,"ok":true,"nil":null,"xs":[1,-2,3],"nested":{"k":"v"}}
|
||||
buffer-exact: ok
|
||||
buffer-len: ok
|
||||
overflow-raised: ok
|
||||
file-exact: ok
|
||||
Reference in New Issue
Block a user