| ... | ... | @@ -24,8 +24,10 @@ const ArrayList = std.ArrayList; |
| 24 | 24 | const BitStack = std.BitStack; |
| 25 | 25 | const Stringify = @This(); |
| 26 | 26 | |
| 27 | | const OBJECT_MODE = 0; |
| 28 | | const ARRAY_MODE = 1; |
| 27 | const IndentationMode = enum(u1) { |
| 28 | object = 0, |
| 29 | array = 1, |
| 30 | }; |
| 29 | 31 | |
| 30 | 32 | writer: *std.io.BufferedWriter, |
| 31 | 33 | options: Options = .{}, |
| ... | ... | @@ -78,22 +80,22 @@ else |
| 78 | 80 | pub fn beginArray(self: *Stringify) anyerror!void { |
| 79 | 81 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); |
| 80 | 82 | try self.valueStart(); |
| 81 | | try self.stream.writeByte('['); |
| 82 | | try self.pushIndentation(ARRAY_MODE); |
| 83 | try self.writer.writeByte('['); |
| 84 | try self.pushIndentation(.array); |
| 83 | 85 | self.next_punctuation = .none; |
| 84 | 86 | } |
| 85 | 87 | |
| 86 | 88 | pub fn beginObject(self: *Stringify) anyerror!void { |
| 87 | 89 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); |
| 88 | 90 | try self.valueStart(); |
| 89 | | try self.stream.writeByte('{'); |
| 90 | | try self.pushIndentation(OBJECT_MODE); |
| 91 | try self.writer.writeByte('{'); |
| 92 | try self.pushIndentation(.object); |
| 91 | 93 | self.next_punctuation = .none; |
| 92 | 94 | } |
| 93 | 95 | |
| 94 | 96 | pub fn endArray(self: *Stringify) anyerror!void { |
| 95 | 97 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); |
| 96 | | self.popIndentation(ARRAY_MODE); |
| 98 | self.popIndentation(.array); |
| 97 | 99 | switch (self.next_punctuation) { |
| 98 | 100 | .none => {}, |
| 99 | 101 | .comma => { |
| ... | ... | @@ -101,13 +103,13 @@ pub fn endArray(self: *Stringify) anyerror!void { |
| 101 | 103 | }, |
| 102 | 104 | .the_beginning, .colon => unreachable, |
| 103 | 105 | } |
| 104 | | try self.stream.writeByte(']'); |
| 106 | try self.writer.writeByte(']'); |
| 105 | 107 | self.valueDone(); |
| 106 | 108 | } |
| 107 | 109 | |
| 108 | 110 | pub fn endObject(self: *Stringify) anyerror!void { |
| 109 | 111 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); |
| 110 | | self.popIndentation(OBJECT_MODE); |
| 112 | self.popIndentation(.object); |
| 111 | 113 | switch (self.next_punctuation) { |
| 112 | 114 | .none => {}, |
| 113 | 115 | .comma => { |
| ... | ... | @@ -115,24 +117,24 @@ pub fn endObject(self: *Stringify) anyerror!void { |
| 115 | 117 | }, |
| 116 | 118 | .the_beginning, .colon => unreachable, |
| 117 | 119 | } |
| 118 | | try self.stream.writeByte('}'); |
| 120 | try self.writer.writeByte('}'); |
| 119 | 121 | self.valueDone(); |
| 120 | 122 | } |
| 121 | 123 | |
| 122 | | fn pushIndentation(self: *Stringify, mode: u1) !void { |
| 124 | fn pushIndentation(self: *Stringify, mode: IndentationMode) !void { |
| 123 | 125 | switch (safety_checks) { |
| 124 | 126 | .checked_to_fixed_depth => { |
| 125 | | BitStack.pushWithStateAssumeCapacity(&self.nesting_stack, &self.indent_level, mode); |
| 127 | BitStack.pushWithStateAssumeCapacity(&self.nesting_stack, &self.indent_level, @intFromEnum(mode)); |
| 126 | 128 | }, |
| 127 | 129 | .assumed_correct => { |
| 128 | 130 | self.indent_level += 1; |
| 129 | 131 | }, |
| 130 | 132 | } |
| 131 | 133 | } |
| 132 | | fn popIndentation(self: *Stringify, assert_its_this_one: u1) void { |
| 134 | fn popIndentation(self: *Stringify, expected_mode: IndentationMode) void { |
| 133 | 135 | switch (safety_checks) { |
| 134 | 136 | .checked_to_fixed_depth => { |
| 135 | | assert(BitStack.popWithState(&self.nesting_stack, &self.indent_level) == assert_its_this_one); |
| 137 | assert(BitStack.popWithState(&self.nesting_stack, &self.indent_level) == @intFromEnum(expected_mode)); |
| 136 | 138 | }, |
| 137 | 139 | .assumed_correct => { |
| 138 | 140 | self.indent_level -= 1; |
| ... | ... | @@ -154,8 +156,8 @@ fn indent(self: *Stringify) !void { |
| 154 | 156 | break :blk self.indent_level; |
| 155 | 157 | }, |
| 156 | 158 | }; |
| 157 | | try self.stream.writeByte('\n'); |
| 158 | | try self.stream.writeByteNTimes(char, n_chars); |
| 159 | try self.writer.writeByte('\n'); |
| 160 | try self.writer.splatByteAll(char, n_chars); |
| 159 | 161 | } |
| 160 | 162 | |
| 161 | 163 | fn valueStart(self: *Stringify) !void { |
| ... | ... | @@ -178,13 +180,13 @@ fn valueStartAssumeTypeOk(self: *Stringify) !void { |
| 178 | 180 | }, |
| 179 | 181 | .comma => { |
| 180 | 182 | // Subsequent item in a container. |
| 181 | | try self.stream.writeByte(','); |
| 183 | try self.writer.writeByte(','); |
| 182 | 184 | try self.indent(); |
| 183 | 185 | }, |
| 184 | 186 | .colon => { |
| 185 | | try self.stream.writeByte(':'); |
| 187 | try self.writer.writeByte(':'); |
| 186 | 188 | if (self.options.whitespace != .minified) { |
| 187 | | try self.stream.writeByte(' '); |
| 189 | try self.writer.writeByte(' '); |
| 188 | 190 | } |
| 189 | 191 | }, |
| 190 | 192 | } |
| ... | ... | @@ -197,7 +199,7 @@ fn valueDone(self: *Stringify) void { |
| 197 | 199 | fn isObjectKeyExpected(self: *const Stringify) ?bool { |
| 198 | 200 | switch (safety_checks) { |
| 199 | 201 | .checked_to_fixed_depth => return self.indent_level > 0 and |
| 200 | | BitStack.peekWithState(&self.nesting_stack, self.indent_level) == OBJECT_MODE and |
| 202 | BitStack.peekWithState(&self.nesting_stack, self.indent_level) == @intFromEnum(IndentationMode.object) and |
| 201 | 203 | self.next_punctuation != .colon, |
| 202 | 204 | .assumed_correct => return null, |
| 203 | 205 | } |
| ... | ... | @@ -214,7 +216,7 @@ fn isComplete(self: *const Stringify) bool { |
| 214 | 216 | pub fn print(self: *Stringify, comptime fmt: []const u8, args: anytype) anyerror!void { |
| 215 | 217 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); |
| 216 | 218 | try self.valueStart(); |
| 217 | | try self.stream.print(fmt, args); |
| 219 | try self.writer.print(fmt, args); |
| 218 | 220 | self.valueDone(); |
| 219 | 221 | } |
| 220 | 222 | |
| ... | ... | @@ -224,7 +226,6 @@ test print { |
| 224 | 226 | out.initFixed(&out_buf); |
| 225 | 227 | |
| 226 | 228 | var w: Stringify = .{ .writer = &out, .options = .{ .whitespace = .indent_2 } }; |
| 227 | | defer w.deinit(); |
| 228 | 229 | |
| 229 | 230 | try w.beginObject(); |
| 230 | 231 | try w.objectField("a"); |
| ... | ... | @@ -248,8 +249,8 @@ test print { |
| 248 | 249 | try std.testing.expectEqualStrings(expected, out.getWritten()); |
| 249 | 250 | } |
| 250 | 251 | |
| 251 | | /// An alternative to calling `write` that allows you to write directly to the `.stream` field, e.g. with `.stream.writeAll()`. |
| 252 | | /// Call `beginWriteRaw()`, then write a complete value (including any quotes if necessary) directly to the `.stream` field, |
| 252 | /// An alternative to calling `write` that allows you to write directly to the `.writer` field, e.g. with `.writer.writeAll()`. |
| 253 | /// Call `beginWriteRaw()`, then write a complete value (including any quotes if necessary) directly to the `.writer` field, |
| 253 | 254 | /// then call `endWriteRaw()`. |
| 254 | 255 | /// This can be useful for streaming very long strings into the output without needing it all buffered in memory. |
| 255 | 256 | pub fn beginWriteRaw(self: *Stringify) !void { |
| ... | ... | @@ -276,7 +277,7 @@ pub fn endWriteRaw(self: *Stringify) void { |
| 276 | 277 | pub fn objectField(self: *Stringify, key: []const u8) anyerror!void { |
| 277 | 278 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); |
| 278 | 279 | try self.objectFieldStart(); |
| 279 | | try encodeJsonString(key, self.options, self.stream); |
| 280 | try encodeJsonString(key, self.options, self.writer); |
| 280 | 281 | self.next_punctuation = .colon; |
| 281 | 282 | } |
| 282 | 283 | /// See `Stringify` for when to call this method. |
| ... | ... | @@ -287,12 +288,12 @@ pub fn objectFieldRaw(self: *Stringify, quoted_key: []const u8) anyerror!void { |
| 287 | 288 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); |
| 288 | 289 | assert(quoted_key.len >= 2 and quoted_key[0] == '"' and quoted_key[quoted_key.len - 1] == '"'); // quoted_key should be "quoted". |
| 289 | 290 | try self.objectFieldStart(); |
| 290 | | try self.stream.writeAll(quoted_key); |
| 291 | try self.writer.writeAll(quoted_key); |
| 291 | 292 | self.next_punctuation = .colon; |
| 292 | 293 | } |
| 293 | 294 | |
| 294 | 295 | /// In the rare case that you need to write very long object field names, |
| 295 | | /// this is an alternative to `objectField` and `objectFieldRaw` that allows you to write directly to the `.stream` field |
| 296 | /// this is an alternative to `objectField` and `objectFieldRaw` that allows you to write directly to the `.writer` field |
| 296 | 297 | /// similar to `beginWriteRaw`. |
| 297 | 298 | /// Call `endObjectFieldRaw()` when you're done. |
| 298 | 299 | pub fn beginObjectFieldRaw(self: *Stringify) !void { |
| ... | ... | @@ -351,9 +352,9 @@ pub fn write(self: *Stringify, v: anytype) anyerror!void { |
| 351 | 352 | if (self.options.emit_nonportable_numbers_as_strings and |
| 352 | 353 | (v <= -(1 << 53) or v >= (1 << 53))) |
| 353 | 354 | { |
| 354 | | try self.stream.print("\"{}\"", .{v}); |
| 355 | try self.writer.print("\"{}\"", .{v}); |
| 355 | 356 | } else { |
| 356 | | try self.stream.print("{}", .{v}); |
| 357 | try self.writer.print("{}", .{v}); |
| 357 | 358 | } |
| 358 | 359 | self.valueDone(); |
| 359 | 360 | return; |
| ... | ... | @@ -364,25 +365,25 @@ pub fn write(self: *Stringify, v: anytype) anyerror!void { |
| 364 | 365 | .float, .comptime_float => { |
| 365 | 366 | if (@as(f64, @floatCast(v)) == v) { |
| 366 | 367 | try self.valueStart(); |
| 367 | | try self.stream.print("{}", .{@as(f64, @floatCast(v))}); |
| 368 | try self.writer.print("{}", .{@as(f64, @floatCast(v))}); |
| 368 | 369 | self.valueDone(); |
| 369 | 370 | return; |
| 370 | 371 | } |
| 371 | 372 | try self.valueStart(); |
| 372 | | try self.stream.print("\"{}\"", .{v}); |
| 373 | try self.writer.print("\"{}\"", .{v}); |
| 373 | 374 | self.valueDone(); |
| 374 | 375 | return; |
| 375 | 376 | }, |
| 376 | 377 | |
| 377 | 378 | .bool => { |
| 378 | 379 | try self.valueStart(); |
| 379 | | try self.stream.writeAll(if (v) "true" else "false"); |
| 380 | try self.writer.writeAll(if (v) "true" else "false"); |
| 380 | 381 | self.valueDone(); |
| 381 | 382 | return; |
| 382 | 383 | }, |
| 383 | 384 | .null => { |
| 384 | 385 | try self.valueStart(); |
| 385 | | try self.stream.writeAll("null"); |
| 386 | try self.writer.writeAll("null"); |
| 386 | 387 | self.valueDone(); |
| 387 | 388 | return; |
| 388 | 389 | }, |
| ... | ... | @@ -529,7 +530,7 @@ pub fn write(self: *Stringify, v: anytype) anyerror!void { |
| 529 | 530 | |
| 530 | 531 | fn stringValue(self: *Stringify, s: []const u8) !void { |
| 531 | 532 | try self.valueStart(); |
| 532 | | try encodeJsonString(s, self.options, self.stream); |
| 533 | try encodeJsonString(s, self.options, self.writer); |
| 533 | 534 | self.valueDone(); |
| 534 | 535 | } |
| 535 | 536 | |
| ... | ... | @@ -564,7 +565,7 @@ pub const Options = struct { |
| 564 | 565 | emit_nonportable_numbers_as_strings: bool = false, |
| 565 | 566 | }; |
| 566 | 567 | |
| 567 | | /// Writes the given value to the `std.io.Writer` stream. |
| 568 | /// Writes the given value to the `std.io.Writer` writer. |
| 568 | 569 | /// See `Stringify` for how the given value is serialized into JSON. |
| 569 | 570 | /// The maximum nesting depth of the output JSON document is 256. |
| 570 | 571 | pub fn value(v: anytype, options: Options, writer: *std.io.BufferedWriter) anyerror!void { |
| ... | ... | @@ -616,7 +617,7 @@ pub fn valueAlloc(gpa: Allocator, v: anytype, options: Options) error{OutOfMemor |
| 616 | 617 | var aw: std.io.AllocatingWriter = undefined; |
| 617 | 618 | const writer = aw.init(gpa); |
| 618 | 619 | defer aw.deinit(); |
| 619 | | try value(v, options, writer); |
| 620 | value(v, options, writer) catch return error.OutOfMemory; // TODO: try @errorCast(...) |
| 620 | 621 | return aw.toOwnedSlice(); |
| 621 | 622 | } |
| 622 | 623 | |
| ... | ... | @@ -631,27 +632,27 @@ test valueAlloc { |
| 631 | 632 | try std.testing.expectEqualStrings(expected, actual); |
| 632 | 633 | } |
| 633 | 634 | |
| 634 | | fn outputUnicodeEscape(codepoint: u21, out_stream: *std.io.BufferedWriter) !void { |
| 635 | fn outputUnicodeEscape(codepoint: u21, bw: *std.io.BufferedWriter) anyerror!void { |
| 635 | 636 | if (codepoint <= 0xFFFF) { |
| 636 | 637 | // If the character is in the Basic Multilingual Plane (U+0000 through U+FFFF), |
| 637 | 638 | // then it may be represented as a six-character sequence: a reverse solidus, followed |
| 638 | 639 | // by the lowercase letter u, followed by four hexadecimal digits that encode the character's code point. |
| 639 | | try out_stream.writeAll("\\u"); |
| 640 | | try std.fmt.formatIntValue(codepoint, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream); |
| 640 | try bw.writeAll("\\u"); |
| 641 | try bw.printInt("x", .{ .width = 4, .fill = '0' }, codepoint); |
| 641 | 642 | } else { |
| 642 | 643 | assert(codepoint <= 0x10FFFF); |
| 643 | 644 | // To escape an extended character that is not in the Basic Multilingual Plane, |
| 644 | 645 | // the character is represented as a 12-character sequence, encoding the UTF-16 surrogate pair. |
| 645 | 646 | const high = @as(u16, @intCast((codepoint - 0x10000) >> 10)) + 0xD800; |
| 646 | 647 | const low = @as(u16, @intCast(codepoint & 0x3FF)) + 0xDC00; |
| 647 | | try out_stream.writeAll("\\u"); |
| 648 | | try std.fmt.formatIntValue(high, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream); |
| 649 | | try out_stream.writeAll("\\u"); |
| 650 | | try std.fmt.formatIntValue(low, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream); |
| 648 | try bw.writeAll("\\u"); |
| 649 | try bw.printInt("x", .{ .width = 4, .fill = '0' }, high); |
| 650 | try bw.writeAll("\\u"); |
| 651 | try bw.printInt("x", .{ .width = 4, .fill = '0' }, low); |
| 651 | 652 | } |
| 652 | 653 | } |
| 653 | 654 | |
| 654 | | fn outputSpecialEscape(c: u8, writer: *std.io.BufferedWriter) !void { |
| 655 | fn outputSpecialEscape(c: u8, writer: *std.io.BufferedWriter) anyerror!void { |
| 655 | 656 | switch (c) { |
| 656 | 657 | '\\' => try writer.writeAll("\\\\"), |
| 657 | 658 | '\"' => try writer.writeAll("\\\""), |
| ... | ... | @@ -665,14 +666,14 @@ fn outputSpecialEscape(c: u8, writer: *std.io.BufferedWriter) !void { |
| 665 | 666 | } |
| 666 | 667 | |
| 667 | 668 | /// Write `string` to `writer` as a JSON encoded string. |
| 668 | | pub fn encodeJsonString(string: []const u8, options: Options, writer: *std.io.BufferedWriter) !void { |
| 669 | pub fn encodeJsonString(string: []const u8, options: Options, writer: *std.io.BufferedWriter) anyerror!void { |
| 669 | 670 | try writer.writeByte('\"'); |
| 670 | 671 | try encodeJsonStringChars(string, options, writer); |
| 671 | 672 | try writer.writeByte('\"'); |
| 672 | 673 | } |
| 673 | 674 | |
| 674 | 675 | /// Write `chars` to `writer` as JSON encoded string characters. |
| 675 | | pub fn encodeJsonStringChars(chars: []const u8, options: Options, writer: *std.io.BufferedWriter) !void { |
| 676 | pub fn encodeJsonStringChars(chars: []const u8, options: Options, writer: *std.io.BufferedWriter) anyerror!void { |
| 676 | 677 | var write_cursor: usize = 0; |
| 677 | 678 | var i: usize = 0; |
| 678 | 679 | if (options.escape_unicode) { |
| ... | ... | @@ -721,8 +722,8 @@ test "json write stream" { |
| 721 | 722 | try testBasicWriteStream(&w); |
| 722 | 723 | } |
| 723 | 724 | |
| 724 | | fn testBasicWriteStream(w: *Stringify, out: *std.io.BufferedWriter) !void { |
| 725 | | out.reset(); |
| 725 | fn testBasicWriteStream(w: *Stringify) anyerror!void { |
| 726 | w.writer.reset(); |
| 726 | 727 | |
| 727 | 728 | try w.beginObject(); |
| 728 | 729 | |
| ... | ... | @@ -765,7 +766,7 @@ fn testBasicWriteStream(w: *Stringify, out: *std.io.BufferedWriter) !void { |
| 765 | 766 | \\ "float": 3.5e0 |
| 766 | 767 | \\} |
| 767 | 768 | ; |
| 768 | | try std.testing.expectEqualStrings(expected, out.getWritten()); |
| 769 | try std.testing.expectEqualStrings(expected, w.writer.getWritten()); |
| 769 | 770 | } |
| 770 | 771 | |
| 771 | 772 | fn getJsonObject(allocator: std.mem.Allocator) !std.json.Value { |
| ... | ... | @@ -967,59 +968,11 @@ test "stringify struct with custom stringifier" { |
| 967 | 968 | } |
| 968 | 969 | |
| 969 | 970 | fn testStringify(expected: []const u8, v: anytype, options: Options) !void { |
| 970 | | const ValidationWriter = struct { |
| 971 | | const Self = @This(); |
| 972 | | pub const Writer = std.io.Writer(*Self, Error, Self.write); |
| 973 | | pub const Error = error{ |
| 974 | | TooMuchData, |
| 975 | | DifferentData, |
| 976 | | }; |
| 977 | | |
| 978 | | expected_remaining: []const u8, |
| 979 | | |
| 980 | | fn init(exp: []const u8) Self { |
| 981 | | return .{ .expected_remaining = exp }; |
| 982 | | } |
| 983 | | |
| 984 | | pub fn writer(self: *Self) Writer { |
| 985 | | return .{ .context = self }; |
| 986 | | } |
| 987 | | |
| 988 | | fn write(self: *Self, bytes: []const u8) Error!usize { |
| 989 | | if (self.expected_remaining.len < bytes.len) { |
| 990 | | std.debug.print( |
| 991 | | \\====== expected this output: ========= |
| 992 | | \\{s} |
| 993 | | \\======== instead found this: ========= |
| 994 | | \\{s} |
| 995 | | \\====================================== |
| 996 | | , .{ |
| 997 | | self.expected_remaining, |
| 998 | | bytes, |
| 999 | | }); |
| 1000 | | return error.TooMuchData; |
| 1001 | | } |
| 1002 | | if (!std.mem.eql(u8, self.expected_remaining[0..bytes.len], bytes)) { |
| 1003 | | std.debug.print( |
| 1004 | | \\====== expected this output: ========= |
| 1005 | | \\{s} |
| 1006 | | \\======== instead found this: ========= |
| 1007 | | \\{s} |
| 1008 | | \\====================================== |
| 1009 | | , .{ |
| 1010 | | self.expected_remaining[0..bytes.len], |
| 1011 | | bytes, |
| 1012 | | }); |
| 1013 | | return error.DifferentData; |
| 1014 | | } |
| 1015 | | self.expected_remaining = self.expected_remaining[bytes.len..]; |
| 1016 | | return bytes.len; |
| 1017 | | } |
| 1018 | | }; |
| 1019 | | |
| 1020 | | var vos = ValidationWriter.init(expected); |
| 1021 | | try value(v, options, vos.writer()); |
| 1022 | | if (vos.expected_remaining.len > 0) return error.NotEnoughData; |
| 971 | var buffer: [4096]u8 = undefined; |
| 972 | var bw: std.io.BufferedWriter = undefined; |
| 973 | bw.initFixed(&buffer); |
| 974 | try value(v, options, &bw); |
| 975 | try std.testing.expectEqualStrings(expected, bw.getWritten()); |
| 1023 | 976 | } |
| 1024 | 977 | |
| 1025 | 978 | test "raw streaming" { |
| ... | ... | @@ -1030,12 +983,12 @@ test "raw streaming" { |
| 1030 | 983 | var w: Stringify = .{ .writer = &out, .options = .{ .whitespace = .indent_2 } }; |
| 1031 | 984 | try w.beginObject(); |
| 1032 | 985 | try w.beginObjectFieldRaw(); |
| 1033 | | try w.stream.writeAll("\"long"); |
| 1034 | | try w.stream.writeAll(" key\""); |
| 986 | try w.writer.writeAll("\"long"); |
| 987 | try w.writer.writeAll(" key\""); |
| 1035 | 988 | w.endObjectFieldRaw(); |
| 1036 | 989 | try w.beginWriteRaw(); |
| 1037 | | try w.stream.writeAll("\"long"); |
| 1038 | | try w.stream.writeAll(" value\""); |
| 990 | try w.writer.writeAll("\"long"); |
| 991 | try w.writer.writeAll(" value\""); |
| 1039 | 992 | w.endWriteRaw(); |
| 1040 | 993 | try w.endObject(); |
| 1041 | 994 | |