authorgravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2024-06-01 18:52:38 -07:00
committergravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2024-06-01 18:52:38 -07:00
log2754216a8fbf5fcf95498157cfcf24832e8a2404
tree6d81df01e656f6e888b52a61f33c75b0f99e7d38
parent032680a09a80700a90e356f1fe4035c96e619b33

add document traversal and printing functions


2 files changed, 240 insertions(+), 0 deletions(-)

json.zig+185
......@@ -476,6 +476,8 @@ const Parser = struct {
476476 }
477477};
478478
479pub threadlocal var doc: ?*const Document = null;
480
479481pub const Document = struct {
480482 extras: []const u8,
481483 root: ValueIndex,
......@@ -483,6 +485,22 @@ pub const Document = struct {
483485 pub fn deinit(this: *const Document, alloc: std.mem.Allocator) void {
484486 alloc.free(this.extras);
485487 }
488
489 pub fn acquire(this: *const Document) void {
490 std.debug.assert(doc == null);
491 doc = this;
492 }
493
494 pub fn release(this: *const Document) void {
495 std.debug.assert(doc == this);
496 doc = null;
497 }
498
499 pub fn format(this: *const Document, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
500 _ = fmt;
501 _ = options;
502 return std.fmt.format(writer, "{}", .{this.root});
503 }
486504};
487505
488506pub const ValueIndex = enum(u32) {
......@@ -494,6 +512,47 @@ pub const ValueIndex = enum(u32) {
494512 empty_array = 9,
495513 empty_object = 14,
496514 _,
515
516 pub fn format(this: ValueIndex, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
517 _ = fmt;
518 _ = options;
519 return std.fmt.format(writer, "{}", .{this.v()});
520 }
521
522 pub fn v(this: ValueIndex) Value {
523 std.debug.assert(this != .zero);
524 return switch (@as(Value.Tag, @enumFromInt(doc.?.extras[@intFromEnum(this)]))) {
525 .zero => .zero,
526 .null => .null,
527 .true => .true,
528 .false => .false,
529 inline .object, .array, .string, .number => |t| @unionInit(Value, @tagName(t), @enumFromInt(@intFromEnum(this))),
530 };
531 }
532
533 pub fn string(this: ValueIndex) []const u8 {
534 return this.v().string.to();
535 }
536
537 pub fn array(this: ValueIndex) []align(1) const ValueIndex {
538 return this.v().array.to();
539 }
540
541 pub fn object(this: ValueIndex) ObjectIndex {
542 return this.v().object;
543 }
544
545 pub fn number(this: ValueIndex) NumberIndex {
546 return this.v().number;
547 }
548
549 pub fn boolean(this: ValueIndex) bool {
550 return switch (this) {
551 .true => true,
552 .false => false,
553 else => unreachable,
554 };
555 }
497556};
498557
499558pub const Value = union(enum(u8)) {
......@@ -507,20 +566,146 @@ pub const Value = union(enum(u8)) {
507566 number: NumberIndex,
508567
509568 const Tag = std.meta.Tag(@This());
569
570 pub fn format(this: Value, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
571 _ = options;
572 _ = fmt;
573 return switch (this) {
574 .zero => unreachable,
575 .null => std.fmt.format(writer, "null", .{}),
576 .true => std.fmt.format(writer, "true", .{}),
577 .false => std.fmt.format(writer, "false", .{}),
578 inline .object, .array, .string, .number => |t| std.fmt.format(writer, "{}", .{t}),
579 };
580 }
510581};
511582
512583pub const StringIndex = enum(u32) {
513584 _,
585
586 pub fn format(this: StringIndex, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
587 _ = options;
588 _ = fmt;
589 try writer.writeAll("\"");
590 try writer.writeAll(this.to());
591 try writer.writeAll("\"");
592 }
593
594 pub fn to(this: StringIndex) []const u8 {
595 var d = doc.?.extras.ptr[@intFromEnum(this)..];
596 std.debug.assert(@as(Value.Tag, @enumFromInt(d[0])) == .string);
597 const len: u32 = @bitCast(d[1..5].*);
598 return d[5..][0..len];
599 }
514600};
515601
516602pub const ArrayIndex = enum(u32) {
517603 _,
604
605 pub fn format(this: ArrayIndex, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
606 _ = options;
607 _ = fmt;
608
609 const items = this.to();
610 try writer.writeAll("[");
611 for (items, 0..) |item, i| {
612 if (i > 0) try writer.writeAll(",");
613 try writer.print("{}", .{item});
614 }
615 try writer.writeAll("]");
616 }
617
618 pub fn to(this: ArrayIndex) []align(1) const ValueIndex {
619 var d = doc.?.extras.ptr[@intFromEnum(this)..];
620 std.debug.assert(@as(Value.Tag, @enumFromInt(d[0])) == .array);
621 const len: u32 = @bitCast(d[1..5].*);
622 const e: [*]align(1) const ValueIndex = @ptrCast(d[5..]);
623 return e[0..len];
624 }
518625};
519626
520627pub const ObjectIndex = enum(u32) {
521628 _,
629
630 pub fn format(this: ObjectIndex, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
631 _ = options;
632 _ = fmt;
633 const keys, const values = this.to();
634 try writer.writeAll("{");
635 for (keys, values, 0..) |k, v, i| {
636 if (i > 0) try writer.writeAll(",");
637 try writer.print("{}", .{k});
638 try writer.writeAll(":");
639 try writer.print("{}", .{v});
640 }
641 try writer.writeAll("}");
642 }
643
644 pub fn to(this: ObjectIndex) struct { []align(1) const StringIndex, []align(1) const ValueIndex } {
645 var d = doc.?.extras.ptr[@intFromEnum(this)..];
646 std.debug.assert(@as(Value.Tag, @enumFromInt(d[0])) == .object);
647 const len: u32 = @bitCast(d[1..5].*);
648 const k: [*]align(1) const StringIndex = @ptrCast(d[5..]);
649 const v: [*]align(1) const ValueIndex = @ptrCast(k + len);
650 return .{ k[0..len], v[0..len] };
651 }
652
653 pub fn get(this: ObjectIndex, needle: []const u8, comptime tag: Value.Tag) ?ValueIndex {
654 const keys, const values = this.to();
655 for (keys, values) |k, v| {
656 if (std.mem.eql(u8, needle, k.to())) {
657 if (v.v() == tag) {
658 return v;
659 }
660 }
661 }
662 return null;
663 }
664
665 pub fn getO(this: ObjectIndex, needle: []const u8) ?ObjectIndex {
666 return if (this.get(needle, .object)) |v| v.object() else null;
667 }
668
669 pub fn getA(this: ObjectIndex, needle: []const u8) ?[]align(1) const ValueIndex {
670 return if (this.get(needle, .array)) |v| v.array() else null;
671 }
672
673 pub fn getS(this: ObjectIndex, needle: []const u8) ?[]const u8 {
674 return if (this.get(needle, .string)) |v| v.string() else null;
675 }
676
677 pub fn getN(this: ObjectIndex, needle: []const u8) ?NumberIndex {
678 return if (this.get(needle, .number)) |v| v.number() else null;
679 }
680
681 pub fn getB(this: ObjectIndex, needle: []const u8) ?bool {
682 if (this.get(needle, .true)) |v| return v.boolean();
683 if (this.get(needle, .false)) |v| return v.boolean();
684 return null;
685 }
522686};
523687
524688pub const NumberIndex = enum(u32) {
525689 _,
690
691 pub fn format(this: NumberIndex, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
692 _ = options;
693 _ = fmt;
694 try writer.writeAll(this.to());
695 }
696
697 pub fn to(this: NumberIndex) []const u8 {
698 var d = doc.?.extras.ptr[@intFromEnum(this)..];
699 std.debug.assert(@as(Value.Tag, @enumFromInt(d[0])) == .number);
700 const len: u32 = @bitCast(d[1..5].*);
701 return d[5..][0..len];
702 }
703
704 pub fn get(this: NumberIndex, comptime T: type) T {
705 return switch (@typeInfo(T)) {
706 .Int => std.fmt.parseInt(T, this.to(), 10) catch unreachable,
707 .Float => std.fmt.parseFloat(T, this.to()) catch unreachable,
708 else => @compileError("not a number type"),
709 };
710 }
526711};
test.zig+55
......@@ -565,3 +565,58 @@ test { try expectFail(JSONTestSuite_root ++ "/n_structure_UTF8_BOM_no_data.json"
565565test { try expectFail(JSONTestSuite_root ++ "/n_structure_whitespace_formfeed.json"); }
566566test { try expectFail(JSONTestSuite_root ++ "/n_structure_whitespace_U+2060_word_joiner.json"); }
567567// zig fmt: on
568
569fn expectCanonical(buffer: []const u8) !void {
570 const alloc = std.testing.allocator;
571 var fbs = std.io.fixedBufferStream(buffer);
572 var doc = try json.parse(alloc, "", fbs.reader(), .{ .support_trailing_commas = true, .maximum_depth = 100 });
573 defer doc.deinit(alloc);
574 doc.acquire();
575 defer doc.release();
576 try std.testing.expectFmt(buffer, "{}", .{doc});
577}
578
579// zig fmt: off
580test { try expectCanonical("{}"); }
581test { try expectCanonical("[]"); }
582test { try expectCanonical("\"aba\""); }
583test { try expectCanonical("4.3"); }
584test { try expectCanonical("true"); }
585test { try expectCanonical("false"); }
586test { try expectCanonical("null"); }
587test { try expectCanonical("[7,8,9]"); }
588// zig fmt: on
589
590test {
591 const alloc = std.testing.allocator;
592 var fbs = std.io.fixedBufferStream(
593 \\["abc",456,"ghi",{"foo":"bar"}]
594 );
595 var doc = try json.parse(alloc, "", fbs.reader(), .{ .support_trailing_commas = true, .maximum_depth = 100 });
596 defer doc.deinit(alloc);
597 doc.acquire();
598 defer doc.release();
599
600 const a = doc.root.array();
601 try std.testing.expectEqual(4, a.len);
602 try std.testing.expectEqualStrings("abc", a[0].string());
603 try std.testing.expectEqual(456, a[1].number().get(u16));
604 try std.testing.expectEqualStrings("ghi", a[2].string());
605 try std.testing.expectEqualStrings("bar", a[3].object().getS("foo").?);
606}
607
608test {
609 try expectCanonical(
610 \\{"asd":"sdf","dfg":"fgh"}
611 );
612}
613test {
614 try expectCanonical(
615 \\{"min":-1.0e+28,"max":1.0e+28}
616 );
617}
618test {
619 try expectCanonical(
620 \\[{"type":"interface","name":"A","inheritance":null,"members":[{"type":"operation","name":"toWord","idlType":{"type":"return-type","extAttrs":[],"generic":"","nullable":false,"union":false,"idlType":"DOMString"},"arguments":[{"type":"argument","name":"index","extAttrs":[],"idlType":{"type":"argument-type","extAttrs":[],"generic":"","nullable":false,"union":false,"idlType":"unsigned long"},"default":null,"optional":false,"variadic":false}],"extAttrs":[],"special":"getter"}],"extAttrs":[{"type":"extended-attribute","name":"Exposed","rhs":{"type":"identifier","value":"Window"},"arguments":[]}],"partial":false}]
621 );
622}