| author | |
| committer | |
| log | e43617e686f62af55d6663b695ec725e21bff210 |
| tree | 36281b6b7607d8d7744749d4924acf6730b0ed2f |
| parent | c58cce799932a5b9a735ac359794ec8bcde61633 |
| parent | 0fb7a0a94bf6f9e329008d8b5b819a8d1d7124b0 |
| signature |
update std.json and std.zon to new I/O API27 files changed, 4302 insertions(+), 4662 deletions(-)
lib/compiler/resinator/main.zig+7-5| ... | ... | @@ -290,12 +290,14 @@ pub fn main() !void { |
| 290 | 290 | }; |
| 291 | 291 | defer depfile.close(); |
| 292 | 292 | |
| 293 | const depfile_writer = depfile.deprecatedWriter(); | |
| 294 | var depfile_buffered_writer = std.io.bufferedWriter(depfile_writer); | |
| 293 | var depfile_buffer: [1024]u8 = undefined; | |
| 294 | var depfile_writer = depfile.writer(&depfile_buffer); | |
| 295 | 295 | switch (options.depfile_fmt) { |
| 296 | 296 | .json => { |
| 297 | var write_stream = std.json.writeStream(depfile_buffered_writer.writer(), .{ .whitespace = .indent_2 }); | |
| 298 | defer write_stream.deinit(); | |
| 297 | var write_stream: std.json.Stringify = .{ | |
| 298 | .writer = &depfile_writer.interface, | |
| 299 | .options = .{ .whitespace = .indent_2 }, | |
| 300 | }; | |
| 299 | 301 | |
| 300 | 302 | try write_stream.beginArray(); |
| 301 | 303 | for (dependencies_list.items) |dep_path| { |
| ... | ... | @@ -304,7 +306,7 @@ pub fn main() !void { |
| 304 | 306 | try write_stream.endArray(); |
| 305 | 307 | }, |
| 306 | 308 | } |
| 307 | try depfile_buffered_writer.flush(); | |
| 309 | try depfile_writer.interface.flush(); | |
| 308 | 310 | } |
| 309 | 311 | } |
| 310 | 312 |
lib/std/Build/Cache/Path.zig+5-3| ... | ... | @@ -161,17 +161,19 @@ pub fn formatEscapeString(path: Path, writer: *std.io.Writer) std.io.Writer.Erro |
| 161 | 161 | } |
| 162 | 162 | } |
| 163 | 163 | |
| 164 | /// Deprecated, use double quoted escape to print paths. | |
| 164 | 165 | pub fn fmtEscapeChar(path: Path) std.fmt.Formatter(Path, formatEscapeChar) { |
| 165 | 166 | return .{ .data = path }; |
| 166 | 167 | } |
| 167 | 168 | |
| 169 | /// Deprecated, use double quoted escape to print paths. | |
| 168 | 170 | pub fn formatEscapeChar(path: Path, writer: *std.io.Writer) std.io.Writer.Error!void { |
| 169 | 171 | if (path.root_dir.path) |p| { |
| 170 | try std.zig.charEscape(p, writer); | |
| 171 | if (path.sub_path.len > 0) try std.zig.charEscape(fs.path.sep_str, writer); | |
| 172 | for (p) |byte| try std.zig.charEscape(byte, writer); | |
| 173 | if (path.sub_path.len > 0) try writer.writeByte(fs.path.sep); | |
| 172 | 174 | } |
| 173 | 175 | if (path.sub_path.len > 0) { |
| 174 | try std.zig.charEscape(path.sub_path, writer); | |
| 176 | for (path.sub_path) |byte| try std.zig.charEscape(byte, writer); | |
| 175 | 177 | } |
| 176 | 178 | } |
| 177 | 179 |
lib/std/Io/Reader.zig+2-2| ... | ... | @@ -990,9 +990,9 @@ pub fn discardDelimiterLimit(r: *Reader, delimiter: u8, limit: Limit) DiscardDel |
| 990 | 990 | /// Returns `error.EndOfStream` if and only if there are fewer than `n` bytes |
| 991 | 991 | /// remaining. |
| 992 | 992 | /// |
| 993 | /// Asserts buffer capacity is at least `n`. | |
| 993 | /// If the end of stream is not encountered, asserts buffer capacity is at | |
| 994 | /// least `n`. | |
| 994 | 995 | pub fn fill(r: *Reader, n: usize) Error!void { |
| 995 | assert(n <= r.buffer.len); | |
| 996 | 996 | if (r.seek + n <= r.end) { |
| 997 | 997 | @branchHint(.likely); |
| 998 | 998 | return; |
lib/std/json.zig+59-44| ... | ... | @@ -10,8 +10,8 @@ |
| 10 | 10 | //! The high-level `stringify` serializes a Zig or `Value` type into JSON. |
| 11 | 11 | |
| 12 | 12 | const builtin = @import("builtin"); |
| 13 | const testing = @import("std").testing; | |
| 14 | const ArrayList = @import("std").ArrayList; | |
| 13 | const std = @import("std"); | |
| 14 | const testing = std.testing; | |
| 15 | 15 | |
| 16 | 16 | test Scanner { |
| 17 | 17 | var scanner = Scanner.initCompleteInput(testing.allocator, "{\"foo\": 123}\n"); |
| ... | ... | @@ -41,11 +41,13 @@ test Value { |
| 41 | 41 | try testing.expectEqualSlices(u8, "goes", parsed.value.object.get("anything").?.string); |
| 42 | 42 | } |
| 43 | 43 | |
| 44 | test writeStream { | |
| 45 | var out = ArrayList(u8).init(testing.allocator); | |
| 44 | test Stringify { | |
| 45 | var out: std.io.Writer.Allocating = .init(testing.allocator); | |
| 46 | var write_stream: Stringify = .{ | |
| 47 | .writer = &out.writer, | |
| 48 | .options = .{ .whitespace = .indent_2 }, | |
| 49 | }; | |
| 46 | 50 | defer out.deinit(); |
| 47 | var write_stream = writeStream(out.writer(), .{ .whitespace = .indent_2 }); | |
| 48 | defer write_stream.deinit(); | |
| 49 | 51 | try write_stream.beginObject(); |
| 50 | 52 | try write_stream.objectField("foo"); |
| 51 | 53 | try write_stream.write(123); |
| ... | ... | @@ -55,16 +57,7 @@ test writeStream { |
| 55 | 57 | \\ "foo": 123 |
| 56 | 58 | \\} |
| 57 | 59 | ; |
| 58 | try testing.expectEqualSlices(u8, expected, out.items); | |
| 59 | } | |
| 60 | ||
| 61 | test stringify { | |
| 62 | var out = ArrayList(u8).init(testing.allocator); | |
| 63 | defer out.deinit(); | |
| 64 | ||
| 65 | const T = struct { a: i32, b: []const u8 }; | |
| 66 | try stringify(T{ .a = 123, .b = "xy" }, .{}, out.writer()); | |
| 67 | try testing.expectEqualSlices(u8, "{\"a\":123,\"b\":\"xy\"}", out.items); | |
| 60 | try testing.expectEqualSlices(u8, expected, out.getWritten()); | |
| 68 | 61 | } |
| 69 | 62 | |
| 70 | 63 | pub const ObjectMap = @import("json/dynamic.zig").ObjectMap; |
| ... | ... | @@ -73,18 +66,18 @@ pub const Value = @import("json/dynamic.zig").Value; |
| 73 | 66 | |
| 74 | 67 | pub const ArrayHashMap = @import("json/hashmap.zig").ArrayHashMap; |
| 75 | 68 | |
| 76 | pub const validate = @import("json/scanner.zig").validate; | |
| 77 | pub const Error = @import("json/scanner.zig").Error; | |
| 78 | pub const reader = @import("json/scanner.zig").reader; | |
| 79 | pub const default_buffer_size = @import("json/scanner.zig").default_buffer_size; | |
| 80 | pub const Token = @import("json/scanner.zig").Token; | |
| 81 | pub const TokenType = @import("json/scanner.zig").TokenType; | |
| 82 | pub const Diagnostics = @import("json/scanner.zig").Diagnostics; | |
| 83 | pub const AllocWhen = @import("json/scanner.zig").AllocWhen; | |
| 84 | pub const default_max_value_len = @import("json/scanner.zig").default_max_value_len; | |
| 85 | pub const Reader = @import("json/scanner.zig").Reader; | |
| 86 | pub const Scanner = @import("json/scanner.zig").Scanner; | |
| 87 | pub const isNumberFormattedLikeAnInteger = @import("json/scanner.zig").isNumberFormattedLikeAnInteger; | |
| 69 | pub const Scanner = @import("json/Scanner.zig"); | |
| 70 | pub const validate = Scanner.validate; | |
| 71 | pub const Error = Scanner.Error; | |
| 72 | pub const reader = Scanner.reader; | |
| 73 | pub const default_buffer_size = Scanner.default_buffer_size; | |
| 74 | pub const Token = Scanner.Token; | |
| 75 | pub const TokenType = Scanner.TokenType; | |
| 76 | pub const Diagnostics = Scanner.Diagnostics; | |
| 77 | pub const AllocWhen = Scanner.AllocWhen; | |
| 78 | pub const default_max_value_len = Scanner.default_max_value_len; | |
| 79 | pub const Reader = Scanner.Reader; | |
| 80 | pub const isNumberFormattedLikeAnInteger = Scanner.isNumberFormattedLikeAnInteger; | |
| 88 | 81 | |
| 89 | 82 | pub const ParseOptions = @import("json/static.zig").ParseOptions; |
| 90 | 83 | pub const Parsed = @import("json/static.zig").Parsed; |
| ... | ... | @@ -99,27 +92,49 @@ pub const innerParseFromValue = @import("json/static.zig").innerParseFromValue; |
| 99 | 92 | pub const ParseError = @import("json/static.zig").ParseError; |
| 100 | 93 | pub const ParseFromValueError = @import("json/static.zig").ParseFromValueError; |
| 101 | 94 | |
| 102 | pub const StringifyOptions = @import("json/stringify.zig").StringifyOptions; | |
| 103 | pub const stringify = @import("json/stringify.zig").stringify; | |
| 104 | pub const stringifyMaxDepth = @import("json/stringify.zig").stringifyMaxDepth; | |
| 105 | pub const stringifyArbitraryDepth = @import("json/stringify.zig").stringifyArbitraryDepth; | |
| 106 | pub const stringifyAlloc = @import("json/stringify.zig").stringifyAlloc; | |
| 107 | pub const writeStream = @import("json/stringify.zig").writeStream; | |
| 108 | pub const writeStreamMaxDepth = @import("json/stringify.zig").writeStreamMaxDepth; | |
| 109 | pub const writeStreamArbitraryDepth = @import("json/stringify.zig").writeStreamArbitraryDepth; | |
| 110 | pub const WriteStream = @import("json/stringify.zig").WriteStream; | |
| 111 | pub const encodeJsonString = @import("json/stringify.zig").encodeJsonString; | |
| 112 | pub const encodeJsonStringChars = @import("json/stringify.zig").encodeJsonStringChars; | |
| 113 | ||
| 114 | pub const Formatter = @import("json/fmt.zig").Formatter; | |
| 115 | pub const fmt = @import("json/fmt.zig").fmt; | |
| 95 | pub const Stringify = @import("json/Stringify.zig"); | |
| 96 | ||
| 97 | /// Returns a formatter that formats the given value using stringify. | |
| 98 | pub fn fmt(value: anytype, options: Stringify.Options) Formatter(@TypeOf(value)) { | |
| 99 | return Formatter(@TypeOf(value)){ .value = value, .options = options }; | |
| 100 | } | |
| 101 | ||
| 102 | test fmt { | |
| 103 | const expectFmt = std.testing.expectFmt; | |
| 104 | try expectFmt("123", "{f}", .{fmt(@as(u32, 123), .{})}); | |
| 105 | try expectFmt( | |
| 106 | \\{"num":927,"msg":"hello","sub":{"mybool":true}} | |
| 107 | , "{f}", .{fmt(struct { | |
| 108 | num: u32, | |
| 109 | msg: []const u8, | |
| 110 | sub: struct { | |
| 111 | mybool: bool, | |
| 112 | }, | |
| 113 | }{ | |
| 114 | .num = 927, | |
| 115 | .msg = "hello", | |
| 116 | .sub = .{ .mybool = true }, | |
| 117 | }, .{})}); | |
| 118 | } | |
| 119 | ||
| 120 | /// Formats the given value using stringify. | |
| 121 | pub fn Formatter(comptime T: type) type { | |
| 122 | return struct { | |
| 123 | value: T, | |
| 124 | options: Stringify.Options, | |
| 125 | ||
| 126 | pub fn format(self: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void { | |
| 127 | try Stringify.value(self.value, self.options, writer); | |
| 128 | } | |
| 129 | }; | |
| 130 | } | |
| 116 | 131 | |
| 117 | 132 | test { |
| 118 | 133 | _ = @import("json/test.zig"); |
| 119 | _ = @import("json/scanner.zig"); | |
| 134 | _ = Scanner; | |
| 120 | 135 | _ = @import("json/dynamic.zig"); |
| 121 | 136 | _ = @import("json/hashmap.zig"); |
| 122 | 137 | _ = @import("json/static.zig"); |
| 123 | _ = @import("json/stringify.zig"); | |
| 138 | _ = Stringify; | |
| 124 | 139 | _ = @import("json/JSONTestSuite_test.zig"); |
| 125 | 140 | } |
lib/std/json/Scanner.zig created+1767| ... | ... | @@ -0,0 +1,1767 @@ |
| 1 | //! The lowest level parsing API in this package; | |
| 2 | //! supports streaming input with a low memory footprint. | |
| 3 | //! The memory requirement is `O(d)` where d is the nesting depth of `[]` or `{}` containers in the input. | |
| 4 | //! Specifically `d/8` bytes are required for this purpose, | |
| 5 | //! with some extra buffer according to the implementation of `std.ArrayList`. | |
| 6 | //! | |
| 7 | //! This scanner can emit partial tokens; see `std.json.Token`. | |
| 8 | //! The input to this class is a sequence of input buffers that you must supply one at a time. | |
| 9 | //! Call `feedInput()` with the first buffer, then call `next()` repeatedly until `error.BufferUnderrun` is returned. | |
| 10 | //! Then call `feedInput()` again and so forth. | |
| 11 | //! Call `endInput()` when the last input buffer has been given to `feedInput()`, either immediately after calling `feedInput()`, | |
| 12 | //! or when `error.BufferUnderrun` requests more data and there is no more. | |
| 13 | //! Be sure to call `next()` after calling `endInput()` until `Token.end_of_document` has been returned. | |
| 14 | //! | |
| 15 | //! Notes on standards compliance: https://datatracker.ietf.org/doc/html/rfc8259 | |
| 16 | //! * RFC 8259 requires JSON documents be valid UTF-8, | |
| 17 | //! but makes an allowance for systems that are "part of a closed ecosystem". | |
| 18 | //! I have no idea what that's supposed to mean in the context of a standard specification. | |
| 19 | //! This implementation requires inputs to be valid UTF-8. | |
| 20 | //! * RFC 8259 contradicts itself regarding whether lowercase is allowed in \u hex digits, | |
| 21 | //! but this is probably a bug in the spec, and it's clear that lowercase is meant to be allowed. | |
| 22 | //! (RFC 5234 defines HEXDIG to only allow uppercase.) | |
| 23 | //! * When RFC 8259 refers to a "character", I assume they really mean a "Unicode scalar value". | |
| 24 | //! See http://www.unicode.org/glossary/#unicode_scalar_value . | |
| 25 | //! * RFC 8259 doesn't explicitly disallow unpaired surrogate halves in \u escape sequences, | |
| 26 | //! but vaguely implies that \u escapes are for encoding Unicode "characters" (i.e. Unicode scalar values?), | |
| 27 | //! which would mean that unpaired surrogate halves are forbidden. | |
| 28 | //! By contrast ECMA-404 (a competing(/compatible?) JSON standard, which JavaScript's JSON.parse() conforms to) | |
| 29 | //! explicitly allows unpaired surrogate halves. | |
| 30 | //! This implementation forbids unpaired surrogate halves in \u sequences. | |
| 31 | //! If a high surrogate half appears in a \u sequence, | |
| 32 | //! then a low surrogate half must immediately follow in \u notation. | |
| 33 | //! * RFC 8259 allows implementations to "accept non-JSON forms or extensions". | |
| 34 | //! This implementation does not accept any of that. | |
| 35 | //! * RFC 8259 allows implementations to put limits on "the size of texts", | |
| 36 | //! "the maximum depth of nesting", "the range and precision of numbers", | |
| 37 | //! and "the length and character contents of strings". | |
| 38 | //! This low-level implementation does not limit these, | |
| 39 | //! except where noted above, and except that nesting depth requires memory allocation. | |
| 40 | //! Note that this low-level API does not interpret numbers numerically, | |
| 41 | //! but simply emits their source form for some higher level code to make sense of. | |
| 42 | //! * This low-level implementation allows duplicate object keys, | |
| 43 | //! and key/value pairs are emitted in the order they appear in the input. | |
| 44 | ||
| 45 | const Scanner = @This(); | |
| 46 | const std = @import("std"); | |
| 47 | ||
| 48 | const Allocator = std.mem.Allocator; | |
| 49 | const ArrayList = std.ArrayList; | |
| 50 | const assert = std.debug.assert; | |
| 51 | const BitStack = std.BitStack; | |
| 52 | ||
| 53 | state: State = .value, | |
| 54 | string_is_object_key: bool = false, | |
| 55 | stack: BitStack, | |
| 56 | value_start: usize = undefined, | |
| 57 | utf16_code_units: [2]u16 = undefined, | |
| 58 | ||
| 59 | input: []const u8 = "", | |
| 60 | cursor: usize = 0, | |
| 61 | is_end_of_input: bool = false, | |
| 62 | diagnostics: ?*Diagnostics = null, | |
| 63 | ||
| 64 | /// The allocator is only used to track `[]` and `{}` nesting levels. | |
| 65 | pub fn initStreaming(allocator: Allocator) @This() { | |
| 66 | return .{ | |
| 67 | .stack = BitStack.init(allocator), | |
| 68 | }; | |
| 69 | } | |
| 70 | /// Use this if your input is a single slice. | |
| 71 | /// This is effectively equivalent to: | |
| 72 | /// ``` | |
| 73 | /// initStreaming(allocator); | |
| 74 | /// feedInput(complete_input); | |
| 75 | /// endInput(); | |
| 76 | /// ``` | |
| 77 | pub fn initCompleteInput(allocator: Allocator, complete_input: []const u8) @This() { | |
| 78 | return .{ | |
| 79 | .stack = BitStack.init(allocator), | |
| 80 | .input = complete_input, | |
| 81 | .is_end_of_input = true, | |
| 82 | }; | |
| 83 | } | |
| 84 | pub fn deinit(self: *@This()) void { | |
| 85 | self.stack.deinit(); | |
| 86 | self.* = undefined; | |
| 87 | } | |
| 88 | ||
| 89 | pub fn enableDiagnostics(self: *@This(), diagnostics: *Diagnostics) void { | |
| 90 | diagnostics.cursor_pointer = &self.cursor; | |
| 91 | self.diagnostics = diagnostics; | |
| 92 | } | |
| 93 | ||
| 94 | /// Call this whenever you get `error.BufferUnderrun` from `next()`. | |
| 95 | /// When there is no more input to provide, call `endInput()`. | |
| 96 | pub fn feedInput(self: *@This(), input: []const u8) void { | |
| 97 | assert(self.cursor == self.input.len); // Not done with the last input slice. | |
| 98 | if (self.diagnostics) |diag| { | |
| 99 | diag.total_bytes_before_current_input += self.input.len; | |
| 100 | // This usually goes "negative" to measure how far before the beginning | |
| 101 | // of the new buffer the current line started. | |
| 102 | diag.line_start_cursor -%= self.cursor; | |
| 103 | } | |
| 104 | self.input = input; | |
| 105 | self.cursor = 0; | |
| 106 | self.value_start = 0; | |
| 107 | } | |
| 108 | /// Call this when you will no longer call `feedInput()` anymore. | |
| 109 | /// This can be called either immediately after the last `feedInput()`, | |
| 110 | /// or at any time afterward, such as when getting `error.BufferUnderrun` from `next()`. | |
| 111 | /// Don't forget to call `next*()` after `endInput()` until you get `.end_of_document`. | |
| 112 | pub fn endInput(self: *@This()) void { | |
| 113 | self.is_end_of_input = true; | |
| 114 | } | |
| 115 | ||
| 116 | pub const NextError = Error || Allocator.Error || error{BufferUnderrun}; | |
| 117 | pub const AllocError = Error || Allocator.Error || error{ValueTooLong}; | |
| 118 | pub const PeekError = Error || error{BufferUnderrun}; | |
| 119 | pub const SkipError = Error || Allocator.Error; | |
| 120 | pub const AllocIntoArrayListError = AllocError || error{BufferUnderrun}; | |
| 121 | ||
| 122 | /// Equivalent to `nextAllocMax(allocator, when, default_max_value_len);` | |
| 123 | /// This function is only available after `endInput()` (or `initCompleteInput()`) has been called. | |
| 124 | /// See also `std.json.Token` for documentation of `nextAlloc*()` function behavior. | |
| 125 | pub fn nextAlloc(self: *@This(), allocator: Allocator, when: AllocWhen) AllocError!Token { | |
| 126 | return self.nextAllocMax(allocator, when, default_max_value_len); | |
| 127 | } | |
| 128 | ||
| 129 | /// This function is only available after `endInput()` (or `initCompleteInput()`) has been called. | |
| 130 | /// See also `std.json.Token` for documentation of `nextAlloc*()` function behavior. | |
| 131 | pub fn nextAllocMax(self: *@This(), allocator: Allocator, when: AllocWhen, max_value_len: usize) AllocError!Token { | |
| 132 | assert(self.is_end_of_input); // This function is not available in streaming mode. | |
| 133 | const token_type = self.peekNextTokenType() catch |e| switch (e) { | |
| 134 | error.BufferUnderrun => unreachable, | |
| 135 | else => |err| return err, | |
| 136 | }; | |
| 137 | switch (token_type) { | |
| 138 | .number, .string => { | |
| 139 | var value_list = ArrayList(u8).init(allocator); | |
| 140 | errdefer { | |
| 141 | value_list.deinit(); | |
| 142 | } | |
| 143 | if (self.allocNextIntoArrayListMax(&value_list, when, max_value_len) catch |e| switch (e) { | |
| 144 | error.BufferUnderrun => unreachable, | |
| 145 | else => |err| return err, | |
| 146 | }) |slice| { | |
| 147 | return if (token_type == .number) | |
| 148 | Token{ .number = slice } | |
| 149 | else | |
| 150 | Token{ .string = slice }; | |
| 151 | } else { | |
| 152 | return if (token_type == .number) | |
| 153 | Token{ .allocated_number = try value_list.toOwnedSlice() } | |
| 154 | else | |
| 155 | Token{ .allocated_string = try value_list.toOwnedSlice() }; | |
| 156 | } | |
| 157 | }, | |
| 158 | ||
| 159 | // Simple tokens never alloc. | |
| 160 | .object_begin, | |
| 161 | .object_end, | |
| 162 | .array_begin, | |
| 163 | .array_end, | |
| 164 | .true, | |
| 165 | .false, | |
| 166 | .null, | |
| 167 | .end_of_document, | |
| 168 | => return self.next() catch |e| switch (e) { | |
| 169 | error.BufferUnderrun => unreachable, | |
| 170 | else => |err| return err, | |
| 171 | }, | |
| 172 | } | |
| 173 | } | |
| 174 | ||
| 175 | /// Equivalent to `allocNextIntoArrayListMax(value_list, when, default_max_value_len);` | |
| 176 | pub fn allocNextIntoArrayList(self: *@This(), value_list: *ArrayList(u8), when: AllocWhen) AllocIntoArrayListError!?[]const u8 { | |
| 177 | return self.allocNextIntoArrayListMax(value_list, when, default_max_value_len); | |
| 178 | } | |
| 179 | /// The next token type must be either `.number` or `.string`. See `peekNextTokenType()`. | |
| 180 | /// When allocation is not necessary with `.alloc_if_needed`, | |
| 181 | /// this method returns the content slice from the input buffer, and `value_list` is not touched. | |
| 182 | /// When allocation is necessary or with `.alloc_always`, this method concatenates partial tokens into the given `value_list`, | |
| 183 | /// and returns `null` once the final `.number` or `.string` token has been written into it. | |
| 184 | /// In case of an `error.BufferUnderrun`, partial values will be left in the given value_list. | |
| 185 | /// The given `value_list` is never reset by this method, so an `error.BufferUnderrun` situation | |
| 186 | /// can be resumed by passing the same array list in again. | |
| 187 | /// This method does not indicate whether the token content being returned is for a `.number` or `.string` token type; | |
| 188 | /// the caller of this method is expected to know which type of token is being processed. | |
| 189 | pub fn allocNextIntoArrayListMax(self: *@This(), value_list: *ArrayList(u8), when: AllocWhen, max_value_len: usize) AllocIntoArrayListError!?[]const u8 { | |
| 190 | while (true) { | |
| 191 | const token = try self.next(); | |
| 192 | switch (token) { | |
| 193 | // Accumulate partial values. | |
| 194 | .partial_number, .partial_string => |slice| { | |
| 195 | try appendSlice(value_list, slice, max_value_len); | |
| 196 | }, | |
| 197 | .partial_string_escaped_1 => |buf| { | |
| 198 | try appendSlice(value_list, buf[0..], max_value_len); | |
| 199 | }, | |
| 200 | .partial_string_escaped_2 => |buf| { | |
| 201 | try appendSlice(value_list, buf[0..], max_value_len); | |
| 202 | }, | |
| 203 | .partial_string_escaped_3 => |buf| { | |
| 204 | try appendSlice(value_list, buf[0..], max_value_len); | |
| 205 | }, | |
| 206 | .partial_string_escaped_4 => |buf| { | |
| 207 | try appendSlice(value_list, buf[0..], max_value_len); | |
| 208 | }, | |
| 209 | ||
| 210 | // Return complete values. | |
| 211 | .number => |slice| { | |
| 212 | if (when == .alloc_if_needed and value_list.items.len == 0) { | |
| 213 | // No alloc necessary. | |
| 214 | return slice; | |
| 215 | } | |
| 216 | try appendSlice(value_list, slice, max_value_len); | |
| 217 | // The token is complete. | |
| 218 | return null; | |
| 219 | }, | |
| 220 | .string => |slice| { | |
| 221 | if (when == .alloc_if_needed and value_list.items.len == 0) { | |
| 222 | // No alloc necessary. | |
| 223 | return slice; | |
| 224 | } | |
| 225 | try appendSlice(value_list, slice, max_value_len); | |
| 226 | // The token is complete. | |
| 227 | return null; | |
| 228 | }, | |
| 229 | ||
| 230 | .object_begin, | |
| 231 | .object_end, | |
| 232 | .array_begin, | |
| 233 | .array_end, | |
| 234 | .true, | |
| 235 | .false, | |
| 236 | .null, | |
| 237 | .end_of_document, | |
| 238 | => unreachable, // Only .number and .string token types are allowed here. Check peekNextTokenType() before calling this. | |
| 239 | ||
| 240 | .allocated_number, .allocated_string => unreachable, | |
| 241 | } | |
| 242 | } | |
| 243 | } | |
| 244 | ||
| 245 | /// This function is only available after `endInput()` (or `initCompleteInput()`) has been called. | |
| 246 | /// If the next token type is `.object_begin` or `.array_begin`, | |
| 247 | /// this function calls `next()` repeatedly until the corresponding `.object_end` or `.array_end` is found. | |
| 248 | /// If the next token type is `.number` or `.string`, | |
| 249 | /// this function calls `next()` repeatedly until the (non `.partial_*`) `.number` or `.string` token is found. | |
| 250 | /// If the next token type is `.true`, `.false`, or `.null`, this function calls `next()` once. | |
| 251 | /// The next token type must not be `.object_end`, `.array_end`, or `.end_of_document`; | |
| 252 | /// see `peekNextTokenType()`. | |
| 253 | pub fn skipValue(self: *@This()) SkipError!void { | |
| 254 | assert(self.is_end_of_input); // This function is not available in streaming mode. | |
| 255 | switch (self.peekNextTokenType() catch |e| switch (e) { | |
| 256 | error.BufferUnderrun => unreachable, | |
| 257 | else => |err| return err, | |
| 258 | }) { | |
| 259 | .object_begin, .array_begin => { | |
| 260 | self.skipUntilStackHeight(self.stackHeight()) catch |e| switch (e) { | |
| 261 | error.BufferUnderrun => unreachable, | |
| 262 | else => |err| return err, | |
| 263 | }; | |
| 264 | }, | |
| 265 | .number, .string => { | |
| 266 | while (true) { | |
| 267 | switch (self.next() catch |e| switch (e) { | |
| 268 | error.BufferUnderrun => unreachable, | |
| 269 | else => |err| return err, | |
| 270 | }) { | |
| 271 | .partial_number, | |
| 272 | .partial_string, | |
| 273 | .partial_string_escaped_1, | |
| 274 | .partial_string_escaped_2, | |
| 275 | .partial_string_escaped_3, | |
| 276 | .partial_string_escaped_4, | |
| 277 | => continue, | |
| 278 | ||
| 279 | .number, .string => break, | |
| 280 | ||
| 281 | else => unreachable, | |
| 282 | } | |
| 283 | } | |
| 284 | }, | |
| 285 | .true, .false, .null => { | |
| 286 | _ = self.next() catch |e| switch (e) { | |
| 287 | error.BufferUnderrun => unreachable, | |
| 288 | else => |err| return err, | |
| 289 | }; | |
| 290 | }, | |
| 291 | ||
| 292 | .object_end, .array_end, .end_of_document => unreachable, // Attempt to skip a non-value token. | |
| 293 | } | |
| 294 | } | |
| 295 | ||
| 296 | /// Skip tokens until an `.object_end` or `.array_end` token results in a `stackHeight()` equal the given stack height. | |
| 297 | /// Unlike `skipValue()`, this function is available in streaming mode. | |
| 298 | pub fn skipUntilStackHeight(self: *@This(), terminal_stack_height: usize) NextError!void { | |
| 299 | while (true) { | |
| 300 | switch (try self.next()) { | |
| 301 | .object_end, .array_end => { | |
| 302 | if (self.stackHeight() == terminal_stack_height) break; | |
| 303 | }, | |
| 304 | .end_of_document => unreachable, | |
| 305 | else => continue, | |
| 306 | } | |
| 307 | } | |
| 308 | } | |
| 309 | ||
| 310 | /// The depth of `{}` or `[]` nesting levels at the current position. | |
| 311 | pub fn stackHeight(self: *const @This()) usize { | |
| 312 | return self.stack.bit_len; | |
| 313 | } | |
| 314 | ||
| 315 | /// Pre allocate memory to hold the given number of nesting levels. | |
| 316 | /// `stackHeight()` up to the given number will not cause allocations. | |
| 317 | pub fn ensureTotalStackCapacity(self: *@This(), height: usize) Allocator.Error!void { | |
| 318 | try self.stack.ensureTotalCapacity(height); | |
| 319 | } | |
| 320 | ||
| 321 | /// See `std.json.Token` for documentation of this function. | |
| 322 | pub fn next(self: *@This()) NextError!Token { | |
| 323 | state_loop: while (true) { | |
| 324 | switch (self.state) { | |
| 325 | .value => { | |
| 326 | switch (try self.skipWhitespaceExpectByte()) { | |
| 327 | // Object, Array | |
| 328 | '{' => { | |
| 329 | try self.stack.push(OBJECT_MODE); | |
| 330 | self.cursor += 1; | |
| 331 | self.state = .object_start; | |
| 332 | return .object_begin; | |
| 333 | }, | |
| 334 | '[' => { | |
| 335 | try self.stack.push(ARRAY_MODE); | |
| 336 | self.cursor += 1; | |
| 337 | self.state = .array_start; | |
| 338 | return .array_begin; | |
| 339 | }, | |
| 340 | ||
| 341 | // String | |
| 342 | '"' => { | |
| 343 | self.cursor += 1; | |
| 344 | self.value_start = self.cursor; | |
| 345 | self.state = .string; | |
| 346 | continue :state_loop; | |
| 347 | }, | |
| 348 | ||
| 349 | // Number | |
| 350 | '1'...'9' => { | |
| 351 | self.value_start = self.cursor; | |
| 352 | self.cursor += 1; | |
| 353 | self.state = .number_int; | |
| 354 | continue :state_loop; | |
| 355 | }, | |
| 356 | '0' => { | |
| 357 | self.value_start = self.cursor; | |
| 358 | self.cursor += 1; | |
| 359 | self.state = .number_leading_zero; | |
| 360 | continue :state_loop; | |
| 361 | }, | |
| 362 | '-' => { | |
| 363 | self.value_start = self.cursor; | |
| 364 | self.cursor += 1; | |
| 365 | self.state = .number_minus; | |
| 366 | continue :state_loop; | |
| 367 | }, | |
| 368 | ||
| 369 | // literal values | |
| 370 | 't' => { | |
| 371 | self.cursor += 1; | |
| 372 | self.state = .literal_t; | |
| 373 | continue :state_loop; | |
| 374 | }, | |
| 375 | 'f' => { | |
| 376 | self.cursor += 1; | |
| 377 | self.state = .literal_f; | |
| 378 | continue :state_loop; | |
| 379 | }, | |
| 380 | 'n' => { | |
| 381 | self.cursor += 1; | |
| 382 | self.state = .literal_n; | |
| 383 | continue :state_loop; | |
| 384 | }, | |
| 385 | ||
| 386 | else => return error.SyntaxError, | |
| 387 | } | |
| 388 | }, | |
| 389 | ||
| 390 | .post_value => { | |
| 391 | if (try self.skipWhitespaceCheckEnd()) return .end_of_document; | |
| 392 | ||
| 393 | const c = self.input[self.cursor]; | |
| 394 | if (self.string_is_object_key) { | |
| 395 | self.string_is_object_key = false; | |
| 396 | switch (c) { | |
| 397 | ':' => { | |
| 398 | self.cursor += 1; | |
| 399 | self.state = .value; | |
| 400 | continue :state_loop; | |
| 401 | }, | |
| 402 | else => return error.SyntaxError, | |
| 403 | } | |
| 404 | } | |
| 405 | ||
| 406 | switch (c) { | |
| 407 | '}' => { | |
| 408 | if (self.stack.pop() != OBJECT_MODE) return error.SyntaxError; | |
| 409 | self.cursor += 1; | |
| 410 | // stay in .post_value state. | |
| 411 | return .object_end; | |
| 412 | }, | |
| 413 | ']' => { | |
| 414 | if (self.stack.pop() != ARRAY_MODE) return error.SyntaxError; | |
| 415 | self.cursor += 1; | |
| 416 | // stay in .post_value state. | |
| 417 | return .array_end; | |
| 418 | }, | |
| 419 | ',' => { | |
| 420 | switch (self.stack.peek()) { | |
| 421 | OBJECT_MODE => { | |
| 422 | self.state = .object_post_comma; | |
| 423 | }, | |
| 424 | ARRAY_MODE => { | |
| 425 | self.state = .value; | |
| 426 | }, | |
| 427 | } | |
| 428 | self.cursor += 1; | |
| 429 | continue :state_loop; | |
| 430 | }, | |
| 431 | else => return error.SyntaxError, | |
| 432 | } | |
| 433 | }, | |
| 434 | ||
| 435 | .object_start => { | |
| 436 | switch (try self.skipWhitespaceExpectByte()) { | |
| 437 | '"' => { | |
| 438 | self.cursor += 1; | |
| 439 | self.value_start = self.cursor; | |
| 440 | self.state = .string; | |
| 441 | self.string_is_object_key = true; | |
| 442 | continue :state_loop; | |
| 443 | }, | |
| 444 | '}' => { | |
| 445 | self.cursor += 1; | |
| 446 | _ = self.stack.pop(); | |
| 447 | self.state = .post_value; | |
| 448 | return .object_end; | |
| 449 | }, | |
| 450 | else => return error.SyntaxError, | |
| 451 | } | |
| 452 | }, | |
| 453 | .object_post_comma => { | |
| 454 | switch (try self.skipWhitespaceExpectByte()) { | |
| 455 | '"' => { | |
| 456 | self.cursor += 1; | |
| 457 | self.value_start = self.cursor; | |
| 458 | self.state = .string; | |
| 459 | self.string_is_object_key = true; | |
| 460 | continue :state_loop; | |
| 461 | }, | |
| 462 | else => return error.SyntaxError, | |
| 463 | } | |
| 464 | }, | |
| 465 | ||
| 466 | .array_start => { | |
| 467 | switch (try self.skipWhitespaceExpectByte()) { | |
| 468 | ']' => { | |
| 469 | self.cursor += 1; | |
| 470 | _ = self.stack.pop(); | |
| 471 | self.state = .post_value; | |
| 472 | return .array_end; | |
| 473 | }, | |
| 474 | else => { | |
| 475 | self.state = .value; | |
| 476 | continue :state_loop; | |
| 477 | }, | |
| 478 | } | |
| 479 | }, | |
| 480 | ||
| 481 | .number_minus => { | |
| 482 | if (self.cursor >= self.input.len) return self.endOfBufferInNumber(false); | |
| 483 | switch (self.input[self.cursor]) { | |
| 484 | '0' => { | |
| 485 | self.cursor += 1; | |
| 486 | self.state = .number_leading_zero; | |
| 487 | continue :state_loop; | |
| 488 | }, | |
| 489 | '1'...'9' => { | |
| 490 | self.cursor += 1; | |
| 491 | self.state = .number_int; | |
| 492 | continue :state_loop; | |
| 493 | }, | |
| 494 | else => return error.SyntaxError, | |
| 495 | } | |
| 496 | }, | |
| 497 | .number_leading_zero => { | |
| 498 | if (self.cursor >= self.input.len) return self.endOfBufferInNumber(true); | |
| 499 | switch (self.input[self.cursor]) { | |
| 500 | '.' => { | |
| 501 | self.cursor += 1; | |
| 502 | self.state = .number_post_dot; | |
| 503 | continue :state_loop; | |
| 504 | }, | |
| 505 | 'e', 'E' => { | |
| 506 | self.cursor += 1; | |
| 507 | self.state = .number_post_e; | |
| 508 | continue :state_loop; | |
| 509 | }, | |
| 510 | else => { | |
| 511 | self.state = .post_value; | |
| 512 | return Token{ .number = self.takeValueSlice() }; | |
| 513 | }, | |
| 514 | } | |
| 515 | }, | |
| 516 | .number_int => { | |
| 517 | while (self.cursor < self.input.len) : (self.cursor += 1) { | |
| 518 | switch (self.input[self.cursor]) { | |
| 519 | '0'...'9' => continue, | |
| 520 | '.' => { | |
| 521 | self.cursor += 1; | |
| 522 | self.state = .number_post_dot; | |
| 523 | continue :state_loop; | |
| 524 | }, | |
| 525 | 'e', 'E' => { | |
| 526 | self.cursor += 1; | |
| 527 | self.state = .number_post_e; | |
| 528 | continue :state_loop; | |
| 529 | }, | |
| 530 | else => { | |
| 531 | self.state = .post_value; | |
| 532 | return Token{ .number = self.takeValueSlice() }; | |
| 533 | }, | |
| 534 | } | |
| 535 | } | |
| 536 | return self.endOfBufferInNumber(true); | |
| 537 | }, | |
| 538 | .number_post_dot => { | |
| 539 | if (self.cursor >= self.input.len) return self.endOfBufferInNumber(false); | |
| 540 | switch (self.input[self.cursor]) { | |
| 541 | '0'...'9' => { | |
| 542 | self.cursor += 1; | |
| 543 | self.state = .number_frac; | |
| 544 | continue :state_loop; | |
| 545 | }, | |
| 546 | else => return error.SyntaxError, | |
| 547 | } | |
| 548 | }, | |
| 549 | .number_frac => { | |
| 550 | while (self.cursor < self.input.len) : (self.cursor += 1) { | |
| 551 | switch (self.input[self.cursor]) { | |
| 552 | '0'...'9' => continue, | |
| 553 | 'e', 'E' => { | |
| 554 | self.cursor += 1; | |
| 555 | self.state = .number_post_e; | |
| 556 | continue :state_loop; | |
| 557 | }, | |
| 558 | else => { | |
| 559 | self.state = .post_value; | |
| 560 | return Token{ .number = self.takeValueSlice() }; | |
| 561 | }, | |
| 562 | } | |
| 563 | } | |
| 564 | return self.endOfBufferInNumber(true); | |
| 565 | }, | |
| 566 | .number_post_e => { | |
| 567 | if (self.cursor >= self.input.len) return self.endOfBufferInNumber(false); | |
| 568 | switch (self.input[self.cursor]) { | |
| 569 | '0'...'9' => { | |
| 570 | self.cursor += 1; | |
| 571 | self.state = .number_exp; | |
| 572 | continue :state_loop; | |
| 573 | }, | |
| 574 | '+', '-' => { | |
| 575 | self.cursor += 1; | |
| 576 | self.state = .number_post_e_sign; | |
| 577 | continue :state_loop; | |
| 578 | }, | |
| 579 | else => return error.SyntaxError, | |
| 580 | } | |
| 581 | }, | |
| 582 | .number_post_e_sign => { | |
| 583 | if (self.cursor >= self.input.len) return self.endOfBufferInNumber(false); | |
| 584 | switch (self.input[self.cursor]) { | |
| 585 | '0'...'9' => { | |
| 586 | self.cursor += 1; | |
| 587 | self.state = .number_exp; | |
| 588 | continue :state_loop; | |
| 589 | }, | |
| 590 | else => return error.SyntaxError, | |
| 591 | } | |
| 592 | }, | |
| 593 | .number_exp => { | |
| 594 | while (self.cursor < self.input.len) : (self.cursor += 1) { | |
| 595 | switch (self.input[self.cursor]) { | |
| 596 | '0'...'9' => continue, | |
| 597 | else => { | |
| 598 | self.state = .post_value; | |
| 599 | return Token{ .number = self.takeValueSlice() }; | |
| 600 | }, | |
| 601 | } | |
| 602 | } | |
| 603 | return self.endOfBufferInNumber(true); | |
| 604 | }, | |
| 605 | ||
| 606 | .string => { | |
| 607 | while (self.cursor < self.input.len) : (self.cursor += 1) { | |
| 608 | switch (self.input[self.cursor]) { | |
| 609 | 0...0x1f => return error.SyntaxError, // Bare ASCII control code in string. | |
| 610 | ||
| 611 | // ASCII plain text. | |
| 612 | 0x20...('"' - 1), ('"' + 1)...('\\' - 1), ('\\' + 1)...0x7F => continue, | |
| 613 | ||
| 614 | // Special characters. | |
| 615 | '"' => { | |
| 616 | const result = Token{ .string = self.takeValueSlice() }; | |
| 617 | self.cursor += 1; | |
| 618 | self.state = .post_value; | |
| 619 | return result; | |
| 620 | }, | |
| 621 | '\\' => { | |
| 622 | const slice = self.takeValueSlice(); | |
| 623 | self.cursor += 1; | |
| 624 | self.state = .string_backslash; | |
| 625 | if (slice.len > 0) return Token{ .partial_string = slice }; | |
| 626 | continue :state_loop; | |
| 627 | }, | |
| 628 | ||
| 629 | // UTF-8 validation. | |
| 630 | // See http://unicode.org/mail-arch/unicode-ml/y2003-m02/att-0467/01-The_Algorithm_to_Valide_an_UTF-8_String | |
| 631 | 0xC2...0xDF => { | |
| 632 | self.cursor += 1; | |
| 633 | self.state = .string_utf8_last_byte; | |
| 634 | continue :state_loop; | |
| 635 | }, | |
| 636 | 0xE0 => { | |
| 637 | self.cursor += 1; | |
| 638 | self.state = .string_utf8_second_to_last_byte_guard_against_overlong; | |
| 639 | continue :state_loop; | |
| 640 | }, | |
| 641 | 0xE1...0xEC, 0xEE...0xEF => { | |
| 642 | self.cursor += 1; | |
| 643 | self.state = .string_utf8_second_to_last_byte; | |
| 644 | continue :state_loop; | |
| 645 | }, | |
| 646 | 0xED => { | |
| 647 | self.cursor += 1; | |
| 648 | self.state = .string_utf8_second_to_last_byte_guard_against_surrogate_half; | |
| 649 | continue :state_loop; | |
| 650 | }, | |
| 651 | 0xF0 => { | |
| 652 | self.cursor += 1; | |
| 653 | self.state = .string_utf8_third_to_last_byte_guard_against_overlong; | |
| 654 | continue :state_loop; | |
| 655 | }, | |
| 656 | 0xF1...0xF3 => { | |
| 657 | self.cursor += 1; | |
| 658 | self.state = .string_utf8_third_to_last_byte; | |
| 659 | continue :state_loop; | |
| 660 | }, | |
| 661 | 0xF4 => { | |
| 662 | self.cursor += 1; | |
| 663 | self.state = .string_utf8_third_to_last_byte_guard_against_too_large; | |
| 664 | continue :state_loop; | |
| 665 | }, | |
| 666 | 0x80...0xC1, 0xF5...0xFF => return error.SyntaxError, // Invalid UTF-8. | |
| 667 | } | |
| 668 | } | |
| 669 | if (self.is_end_of_input) return error.UnexpectedEndOfInput; | |
| 670 | const slice = self.takeValueSlice(); | |
| 671 | if (slice.len > 0) return Token{ .partial_string = slice }; | |
| 672 | return error.BufferUnderrun; | |
| 673 | }, | |
| 674 | .string_backslash => { | |
| 675 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 676 | switch (self.input[self.cursor]) { | |
| 677 | '"', '\\', '/' => { | |
| 678 | // Since these characters now represent themselves literally, | |
| 679 | // we can simply begin the next plaintext slice here. | |
| 680 | self.value_start = self.cursor; | |
| 681 | self.cursor += 1; | |
| 682 | self.state = .string; | |
| 683 | continue :state_loop; | |
| 684 | }, | |
| 685 | 'b' => { | |
| 686 | self.cursor += 1; | |
| 687 | self.value_start = self.cursor; | |
| 688 | self.state = .string; | |
| 689 | return Token{ .partial_string_escaped_1 = [_]u8{0x08} }; | |
| 690 | }, | |
| 691 | 'f' => { | |
| 692 | self.cursor += 1; | |
| 693 | self.value_start = self.cursor; | |
| 694 | self.state = .string; | |
| 695 | return Token{ .partial_string_escaped_1 = [_]u8{0x0c} }; | |
| 696 | }, | |
| 697 | 'n' => { | |
| 698 | self.cursor += 1; | |
| 699 | self.value_start = self.cursor; | |
| 700 | self.state = .string; | |
| 701 | return Token{ .partial_string_escaped_1 = [_]u8{'\n'} }; | |
| 702 | }, | |
| 703 | 'r' => { | |
| 704 | self.cursor += 1; | |
| 705 | self.value_start = self.cursor; | |
| 706 | self.state = .string; | |
| 707 | return Token{ .partial_string_escaped_1 = [_]u8{'\r'} }; | |
| 708 | }, | |
| 709 | 't' => { | |
| 710 | self.cursor += 1; | |
| 711 | self.value_start = self.cursor; | |
| 712 | self.state = .string; | |
| 713 | return Token{ .partial_string_escaped_1 = [_]u8{'\t'} }; | |
| 714 | }, | |
| 715 | 'u' => { | |
| 716 | self.cursor += 1; | |
| 717 | self.state = .string_backslash_u; | |
| 718 | continue :state_loop; | |
| 719 | }, | |
| 720 | else => return error.SyntaxError, | |
| 721 | } | |
| 722 | }, | |
| 723 | .string_backslash_u => { | |
| 724 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 725 | const c = self.input[self.cursor]; | |
| 726 | switch (c) { | |
| 727 | '0'...'9' => { | |
| 728 | self.utf16_code_units[0] = @as(u16, c - '0') << 12; | |
| 729 | }, | |
| 730 | 'A'...'F' => { | |
| 731 | self.utf16_code_units[0] = @as(u16, c - 'A' + 10) << 12; | |
| 732 | }, | |
| 733 | 'a'...'f' => { | |
| 734 | self.utf16_code_units[0] = @as(u16, c - 'a' + 10) << 12; | |
| 735 | }, | |
| 736 | else => return error.SyntaxError, | |
| 737 | } | |
| 738 | self.cursor += 1; | |
| 739 | self.state = .string_backslash_u_1; | |
| 740 | continue :state_loop; | |
| 741 | }, | |
| 742 | .string_backslash_u_1 => { | |
| 743 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 744 | const c = self.input[self.cursor]; | |
| 745 | switch (c) { | |
| 746 | '0'...'9' => { | |
| 747 | self.utf16_code_units[0] |= @as(u16, c - '0') << 8; | |
| 748 | }, | |
| 749 | 'A'...'F' => { | |
| 750 | self.utf16_code_units[0] |= @as(u16, c - 'A' + 10) << 8; | |
| 751 | }, | |
| 752 | 'a'...'f' => { | |
| 753 | self.utf16_code_units[0] |= @as(u16, c - 'a' + 10) << 8; | |
| 754 | }, | |
| 755 | else => return error.SyntaxError, | |
| 756 | } | |
| 757 | self.cursor += 1; | |
| 758 | self.state = .string_backslash_u_2; | |
| 759 | continue :state_loop; | |
| 760 | }, | |
| 761 | .string_backslash_u_2 => { | |
| 762 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 763 | const c = self.input[self.cursor]; | |
| 764 | switch (c) { | |
| 765 | '0'...'9' => { | |
| 766 | self.utf16_code_units[0] |= @as(u16, c - '0') << 4; | |
| 767 | }, | |
| 768 | 'A'...'F' => { | |
| 769 | self.utf16_code_units[0] |= @as(u16, c - 'A' + 10) << 4; | |
| 770 | }, | |
| 771 | 'a'...'f' => { | |
| 772 | self.utf16_code_units[0] |= @as(u16, c - 'a' + 10) << 4; | |
| 773 | }, | |
| 774 | else => return error.SyntaxError, | |
| 775 | } | |
| 776 | self.cursor += 1; | |
| 777 | self.state = .string_backslash_u_3; | |
| 778 | continue :state_loop; | |
| 779 | }, | |
| 780 | .string_backslash_u_3 => { | |
| 781 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 782 | const c = self.input[self.cursor]; | |
| 783 | switch (c) { | |
| 784 | '0'...'9' => { | |
| 785 | self.utf16_code_units[0] |= c - '0'; | |
| 786 | }, | |
| 787 | 'A'...'F' => { | |
| 788 | self.utf16_code_units[0] |= c - 'A' + 10; | |
| 789 | }, | |
| 790 | 'a'...'f' => { | |
| 791 | self.utf16_code_units[0] |= c - 'a' + 10; | |
| 792 | }, | |
| 793 | else => return error.SyntaxError, | |
| 794 | } | |
| 795 | self.cursor += 1; | |
| 796 | if (std.unicode.utf16IsHighSurrogate(self.utf16_code_units[0])) { | |
| 797 | self.state = .string_surrogate_half; | |
| 798 | continue :state_loop; | |
| 799 | } else if (std.unicode.utf16IsLowSurrogate(self.utf16_code_units[0])) { | |
| 800 | return error.SyntaxError; // Unexpected low surrogate half. | |
| 801 | } else { | |
| 802 | self.value_start = self.cursor; | |
| 803 | self.state = .string; | |
| 804 | return partialStringCodepoint(self.utf16_code_units[0]); | |
| 805 | } | |
| 806 | }, | |
| 807 | .string_surrogate_half => { | |
| 808 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 809 | switch (self.input[self.cursor]) { | |
| 810 | '\\' => { | |
| 811 | self.cursor += 1; | |
| 812 | self.state = .string_surrogate_half_backslash; | |
| 813 | continue :state_loop; | |
| 814 | }, | |
| 815 | else => return error.SyntaxError, // Expected low surrogate half. | |
| 816 | } | |
| 817 | }, | |
| 818 | .string_surrogate_half_backslash => { | |
| 819 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 820 | switch (self.input[self.cursor]) { | |
| 821 | 'u' => { | |
| 822 | self.cursor += 1; | |
| 823 | self.state = .string_surrogate_half_backslash_u; | |
| 824 | continue :state_loop; | |
| 825 | }, | |
| 826 | else => return error.SyntaxError, // Expected low surrogate half. | |
| 827 | } | |
| 828 | }, | |
| 829 | .string_surrogate_half_backslash_u => { | |
| 830 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 831 | switch (self.input[self.cursor]) { | |
| 832 | 'D', 'd' => { | |
| 833 | self.cursor += 1; | |
| 834 | self.utf16_code_units[1] = 0xD << 12; | |
| 835 | self.state = .string_surrogate_half_backslash_u_1; | |
| 836 | continue :state_loop; | |
| 837 | }, | |
| 838 | else => return error.SyntaxError, // Expected low surrogate half. | |
| 839 | } | |
| 840 | }, | |
| 841 | .string_surrogate_half_backslash_u_1 => { | |
| 842 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 843 | const c = self.input[self.cursor]; | |
| 844 | switch (c) { | |
| 845 | 'C'...'F' => { | |
| 846 | self.cursor += 1; | |
| 847 | self.utf16_code_units[1] |= @as(u16, c - 'A' + 10) << 8; | |
| 848 | self.state = .string_surrogate_half_backslash_u_2; | |
| 849 | continue :state_loop; | |
| 850 | }, | |
| 851 | 'c'...'f' => { | |
| 852 | self.cursor += 1; | |
| 853 | self.utf16_code_units[1] |= @as(u16, c - 'a' + 10) << 8; | |
| 854 | self.state = .string_surrogate_half_backslash_u_2; | |
| 855 | continue :state_loop; | |
| 856 | }, | |
| 857 | else => return error.SyntaxError, // Expected low surrogate half. | |
| 858 | } | |
| 859 | }, | |
| 860 | .string_surrogate_half_backslash_u_2 => { | |
| 861 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 862 | const c = self.input[self.cursor]; | |
| 863 | switch (c) { | |
| 864 | '0'...'9' => { | |
| 865 | self.cursor += 1; | |
| 866 | self.utf16_code_units[1] |= @as(u16, c - '0') << 4; | |
| 867 | self.state = .string_surrogate_half_backslash_u_3; | |
| 868 | continue :state_loop; | |
| 869 | }, | |
| 870 | 'A'...'F' => { | |
| 871 | self.cursor += 1; | |
| 872 | self.utf16_code_units[1] |= @as(u16, c - 'A' + 10) << 4; | |
| 873 | self.state = .string_surrogate_half_backslash_u_3; | |
| 874 | continue :state_loop; | |
| 875 | }, | |
| 876 | 'a'...'f' => { | |
| 877 | self.cursor += 1; | |
| 878 | self.utf16_code_units[1] |= @as(u16, c - 'a' + 10) << 4; | |
| 879 | self.state = .string_surrogate_half_backslash_u_3; | |
| 880 | continue :state_loop; | |
| 881 | }, | |
| 882 | else => return error.SyntaxError, | |
| 883 | } | |
| 884 | }, | |
| 885 | .string_surrogate_half_backslash_u_3 => { | |
| 886 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 887 | const c = self.input[self.cursor]; | |
| 888 | switch (c) { | |
| 889 | '0'...'9' => { | |
| 890 | self.utf16_code_units[1] |= c - '0'; | |
| 891 | }, | |
| 892 | 'A'...'F' => { | |
| 893 | self.utf16_code_units[1] |= c - 'A' + 10; | |
| 894 | }, | |
| 895 | 'a'...'f' => { | |
| 896 | self.utf16_code_units[1] |= c - 'a' + 10; | |
| 897 | }, | |
| 898 | else => return error.SyntaxError, | |
| 899 | } | |
| 900 | self.cursor += 1; | |
| 901 | self.value_start = self.cursor; | |
| 902 | self.state = .string; | |
| 903 | const code_point = std.unicode.utf16DecodeSurrogatePair(&self.utf16_code_units) catch unreachable; | |
| 904 | return partialStringCodepoint(code_point); | |
| 905 | }, | |
| 906 | ||
| 907 | .string_utf8_last_byte => { | |
| 908 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 909 | switch (self.input[self.cursor]) { | |
| 910 | 0x80...0xBF => { | |
| 911 | self.cursor += 1; | |
| 912 | self.state = .string; | |
| 913 | continue :state_loop; | |
| 914 | }, | |
| 915 | else => return error.SyntaxError, // Invalid UTF-8. | |
| 916 | } | |
| 917 | }, | |
| 918 | .string_utf8_second_to_last_byte => { | |
| 919 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 920 | switch (self.input[self.cursor]) { | |
| 921 | 0x80...0xBF => { | |
| 922 | self.cursor += 1; | |
| 923 | self.state = .string_utf8_last_byte; | |
| 924 | continue :state_loop; | |
| 925 | }, | |
| 926 | else => return error.SyntaxError, // Invalid UTF-8. | |
| 927 | } | |
| 928 | }, | |
| 929 | .string_utf8_second_to_last_byte_guard_against_overlong => { | |
| 930 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 931 | switch (self.input[self.cursor]) { | |
| 932 | 0xA0...0xBF => { | |
| 933 | self.cursor += 1; | |
| 934 | self.state = .string_utf8_last_byte; | |
| 935 | continue :state_loop; | |
| 936 | }, | |
| 937 | else => return error.SyntaxError, // Invalid UTF-8. | |
| 938 | } | |
| 939 | }, | |
| 940 | .string_utf8_second_to_last_byte_guard_against_surrogate_half => { | |
| 941 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 942 | switch (self.input[self.cursor]) { | |
| 943 | 0x80...0x9F => { | |
| 944 | self.cursor += 1; | |
| 945 | self.state = .string_utf8_last_byte; | |
| 946 | continue :state_loop; | |
| 947 | }, | |
| 948 | else => return error.SyntaxError, // Invalid UTF-8. | |
| 949 | } | |
| 950 | }, | |
| 951 | .string_utf8_third_to_last_byte => { | |
| 952 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 953 | switch (self.input[self.cursor]) { | |
| 954 | 0x80...0xBF => { | |
| 955 | self.cursor += 1; | |
| 956 | self.state = .string_utf8_second_to_last_byte; | |
| 957 | continue :state_loop; | |
| 958 | }, | |
| 959 | else => return error.SyntaxError, // Invalid UTF-8. | |
| 960 | } | |
| 961 | }, | |
| 962 | .string_utf8_third_to_last_byte_guard_against_overlong => { | |
| 963 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 964 | switch (self.input[self.cursor]) { | |
| 965 | 0x90...0xBF => { | |
| 966 | self.cursor += 1; | |
| 967 | self.state = .string_utf8_second_to_last_byte; | |
| 968 | continue :state_loop; | |
| 969 | }, | |
| 970 | else => return error.SyntaxError, // Invalid UTF-8. | |
| 971 | } | |
| 972 | }, | |
| 973 | .string_utf8_third_to_last_byte_guard_against_too_large => { | |
| 974 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 975 | switch (self.input[self.cursor]) { | |
| 976 | 0x80...0x8F => { | |
| 977 | self.cursor += 1; | |
| 978 | self.state = .string_utf8_second_to_last_byte; | |
| 979 | continue :state_loop; | |
| 980 | }, | |
| 981 | else => return error.SyntaxError, // Invalid UTF-8. | |
| 982 | } | |
| 983 | }, | |
| 984 | ||
| 985 | .literal_t => { | |
| 986 | switch (try self.expectByte()) { | |
| 987 | 'r' => { | |
| 988 | self.cursor += 1; | |
| 989 | self.state = .literal_tr; | |
| 990 | continue :state_loop; | |
| 991 | }, | |
| 992 | else => return error.SyntaxError, | |
| 993 | } | |
| 994 | }, | |
| 995 | .literal_tr => { | |
| 996 | switch (try self.expectByte()) { | |
| 997 | 'u' => { | |
| 998 | self.cursor += 1; | |
| 999 | self.state = .literal_tru; | |
| 1000 | continue :state_loop; | |
| 1001 | }, | |
| 1002 | else => return error.SyntaxError, | |
| 1003 | } | |
| 1004 | }, | |
| 1005 | .literal_tru => { | |
| 1006 | switch (try self.expectByte()) { | |
| 1007 | 'e' => { | |
| 1008 | self.cursor += 1; | |
| 1009 | self.state = .post_value; | |
| 1010 | return .true; | |
| 1011 | }, | |
| 1012 | else => return error.SyntaxError, | |
| 1013 | } | |
| 1014 | }, | |
| 1015 | .literal_f => { | |
| 1016 | switch (try self.expectByte()) { | |
| 1017 | 'a' => { | |
| 1018 | self.cursor += 1; | |
| 1019 | self.state = .literal_fa; | |
| 1020 | continue :state_loop; | |
| 1021 | }, | |
| 1022 | else => return error.SyntaxError, | |
| 1023 | } | |
| 1024 | }, | |
| 1025 | .literal_fa => { | |
| 1026 | switch (try self.expectByte()) { | |
| 1027 | 'l' => { | |
| 1028 | self.cursor += 1; | |
| 1029 | self.state = .literal_fal; | |
| 1030 | continue :state_loop; | |
| 1031 | }, | |
| 1032 | else => return error.SyntaxError, | |
| 1033 | } | |
| 1034 | }, | |
| 1035 | .literal_fal => { | |
| 1036 | switch (try self.expectByte()) { | |
| 1037 | 's' => { | |
| 1038 | self.cursor += 1; | |
| 1039 | self.state = .literal_fals; | |
| 1040 | continue :state_loop; | |
| 1041 | }, | |
| 1042 | else => return error.SyntaxError, | |
| 1043 | } | |
| 1044 | }, | |
| 1045 | .literal_fals => { | |
| 1046 | switch (try self.expectByte()) { | |
| 1047 | 'e' => { | |
| 1048 | self.cursor += 1; | |
| 1049 | self.state = .post_value; | |
| 1050 | return .false; | |
| 1051 | }, | |
| 1052 | else => return error.SyntaxError, | |
| 1053 | } | |
| 1054 | }, | |
| 1055 | .literal_n => { | |
| 1056 | switch (try self.expectByte()) { | |
| 1057 | 'u' => { | |
| 1058 | self.cursor += 1; | |
| 1059 | self.state = .literal_nu; | |
| 1060 | continue :state_loop; | |
| 1061 | }, | |
| 1062 | else => return error.SyntaxError, | |
| 1063 | } | |
| 1064 | }, | |
| 1065 | .literal_nu => { | |
| 1066 | switch (try self.expectByte()) { | |
| 1067 | 'l' => { | |
| 1068 | self.cursor += 1; | |
| 1069 | self.state = .literal_nul; | |
| 1070 | continue :state_loop; | |
| 1071 | }, | |
| 1072 | else => return error.SyntaxError, | |
| 1073 | } | |
| 1074 | }, | |
| 1075 | .literal_nul => { | |
| 1076 | switch (try self.expectByte()) { | |
| 1077 | 'l' => { | |
| 1078 | self.cursor += 1; | |
| 1079 | self.state = .post_value; | |
| 1080 | return .null; | |
| 1081 | }, | |
| 1082 | else => return error.SyntaxError, | |
| 1083 | } | |
| 1084 | }, | |
| 1085 | } | |
| 1086 | unreachable; | |
| 1087 | } | |
| 1088 | } | |
| 1089 | ||
| 1090 | /// Seeks ahead in the input until the first byte of the next token (or the end of the input) | |
| 1091 | /// determines which type of token will be returned from the next `next*()` call. | |
| 1092 | /// This function is idempotent, only advancing past commas, colons, and inter-token whitespace. | |
| 1093 | pub fn peekNextTokenType(self: *@This()) PeekError!TokenType { | |
| 1094 | state_loop: while (true) { | |
| 1095 | switch (self.state) { | |
| 1096 | .value => { | |
| 1097 | switch (try self.skipWhitespaceExpectByte()) { | |
| 1098 | '{' => return .object_begin, | |
| 1099 | '[' => return .array_begin, | |
| 1100 | '"' => return .string, | |
| 1101 | '-', '0'...'9' => return .number, | |
| 1102 | 't' => return .true, | |
| 1103 | 'f' => return .false, | |
| 1104 | 'n' => return .null, | |
| 1105 | else => return error.SyntaxError, | |
| 1106 | } | |
| 1107 | }, | |
| 1108 | ||
| 1109 | .post_value => { | |
| 1110 | if (try self.skipWhitespaceCheckEnd()) return .end_of_document; | |
| 1111 | ||
| 1112 | const c = self.input[self.cursor]; | |
| 1113 | if (self.string_is_object_key) { | |
| 1114 | self.string_is_object_key = false; | |
| 1115 | switch (c) { | |
| 1116 | ':' => { | |
| 1117 | self.cursor += 1; | |
| 1118 | self.state = .value; | |
| 1119 | continue :state_loop; | |
| 1120 | }, | |
| 1121 | else => return error.SyntaxError, | |
| 1122 | } | |
| 1123 | } | |
| 1124 | ||
| 1125 | switch (c) { | |
| 1126 | '}' => return .object_end, | |
| 1127 | ']' => return .array_end, | |
| 1128 | ',' => { | |
| 1129 | switch (self.stack.peek()) { | |
| 1130 | OBJECT_MODE => { | |
| 1131 | self.state = .object_post_comma; | |
| 1132 | }, | |
| 1133 | ARRAY_MODE => { | |
| 1134 | self.state = .value; | |
| 1135 | }, | |
| 1136 | } | |
| 1137 | self.cursor += 1; | |
| 1138 | continue :state_loop; | |
| 1139 | }, | |
| 1140 | else => return error.SyntaxError, | |
| 1141 | } | |
| 1142 | }, | |
| 1143 | ||
| 1144 | .object_start => { | |
| 1145 | switch (try self.skipWhitespaceExpectByte()) { | |
| 1146 | '"' => return .string, | |
| 1147 | '}' => return .object_end, | |
| 1148 | else => return error.SyntaxError, | |
| 1149 | } | |
| 1150 | }, | |
| 1151 | .object_post_comma => { | |
| 1152 | switch (try self.skipWhitespaceExpectByte()) { | |
| 1153 | '"' => return .string, | |
| 1154 | else => return error.SyntaxError, | |
| 1155 | } | |
| 1156 | }, | |
| 1157 | ||
| 1158 | .array_start => { | |
| 1159 | switch (try self.skipWhitespaceExpectByte()) { | |
| 1160 | ']' => return .array_end, | |
| 1161 | else => { | |
| 1162 | self.state = .value; | |
| 1163 | continue :state_loop; | |
| 1164 | }, | |
| 1165 | } | |
| 1166 | }, | |
| 1167 | ||
| 1168 | .number_minus, | |
| 1169 | .number_leading_zero, | |
| 1170 | .number_int, | |
| 1171 | .number_post_dot, | |
| 1172 | .number_frac, | |
| 1173 | .number_post_e, | |
| 1174 | .number_post_e_sign, | |
| 1175 | .number_exp, | |
| 1176 | => return .number, | |
| 1177 | ||
| 1178 | .string, | |
| 1179 | .string_backslash, | |
| 1180 | .string_backslash_u, | |
| 1181 | .string_backslash_u_1, | |
| 1182 | .string_backslash_u_2, | |
| 1183 | .string_backslash_u_3, | |
| 1184 | .string_surrogate_half, | |
| 1185 | .string_surrogate_half_backslash, | |
| 1186 | .string_surrogate_half_backslash_u, | |
| 1187 | .string_surrogate_half_backslash_u_1, | |
| 1188 | .string_surrogate_half_backslash_u_2, | |
| 1189 | .string_surrogate_half_backslash_u_3, | |
| 1190 | => return .string, | |
| 1191 | ||
| 1192 | .string_utf8_last_byte, | |
| 1193 | .string_utf8_second_to_last_byte, | |
| 1194 | .string_utf8_second_to_last_byte_guard_against_overlong, | |
| 1195 | .string_utf8_second_to_last_byte_guard_against_surrogate_half, | |
| 1196 | .string_utf8_third_to_last_byte, | |
| 1197 | .string_utf8_third_to_last_byte_guard_against_overlong, | |
| 1198 | .string_utf8_third_to_last_byte_guard_against_too_large, | |
| 1199 | => return .string, | |
| 1200 | ||
| 1201 | .literal_t, | |
| 1202 | .literal_tr, | |
| 1203 | .literal_tru, | |
| 1204 | => return .true, | |
| 1205 | .literal_f, | |
| 1206 | .literal_fa, | |
| 1207 | .literal_fal, | |
| 1208 | .literal_fals, | |
| 1209 | => return .false, | |
| 1210 | .literal_n, | |
| 1211 | .literal_nu, | |
| 1212 | .literal_nul, | |
| 1213 | => return .null, | |
| 1214 | } | |
| 1215 | unreachable; | |
| 1216 | } | |
| 1217 | } | |
| 1218 | ||
| 1219 | const State = enum { | |
| 1220 | value, | |
| 1221 | post_value, | |
| 1222 | ||
| 1223 | object_start, | |
| 1224 | object_post_comma, | |
| 1225 | ||
| 1226 | array_start, | |
| 1227 | ||
| 1228 | number_minus, | |
| 1229 | number_leading_zero, | |
| 1230 | number_int, | |
| 1231 | number_post_dot, | |
| 1232 | number_frac, | |
| 1233 | number_post_e, | |
| 1234 | number_post_e_sign, | |
| 1235 | number_exp, | |
| 1236 | ||
| 1237 | string, | |
| 1238 | string_backslash, | |
| 1239 | string_backslash_u, | |
| 1240 | string_backslash_u_1, | |
| 1241 | string_backslash_u_2, | |
| 1242 | string_backslash_u_3, | |
| 1243 | string_surrogate_half, | |
| 1244 | string_surrogate_half_backslash, | |
| 1245 | string_surrogate_half_backslash_u, | |
| 1246 | string_surrogate_half_backslash_u_1, | |
| 1247 | string_surrogate_half_backslash_u_2, | |
| 1248 | string_surrogate_half_backslash_u_3, | |
| 1249 | ||
| 1250 | // From http://unicode.org/mail-arch/unicode-ml/y2003-m02/att-0467/01-The_Algorithm_to_Valide_an_UTF-8_String | |
| 1251 | string_utf8_last_byte, // State A | |
| 1252 | string_utf8_second_to_last_byte, // State B | |
| 1253 | string_utf8_second_to_last_byte_guard_against_overlong, // State C | |
| 1254 | string_utf8_second_to_last_byte_guard_against_surrogate_half, // State D | |
| 1255 | string_utf8_third_to_last_byte, // State E | |
| 1256 | string_utf8_third_to_last_byte_guard_against_overlong, // State F | |
| 1257 | string_utf8_third_to_last_byte_guard_against_too_large, // State G | |
| 1258 | ||
| 1259 | literal_t, | |
| 1260 | literal_tr, | |
| 1261 | literal_tru, | |
| 1262 | literal_f, | |
| 1263 | literal_fa, | |
| 1264 | literal_fal, | |
| 1265 | literal_fals, | |
| 1266 | literal_n, | |
| 1267 | literal_nu, | |
| 1268 | literal_nul, | |
| 1269 | }; | |
| 1270 | ||
| 1271 | fn expectByte(self: *const @This()) !u8 { | |
| 1272 | if (self.cursor < self.input.len) { | |
| 1273 | return self.input[self.cursor]; | |
| 1274 | } | |
| 1275 | // No byte. | |
| 1276 | if (self.is_end_of_input) return error.UnexpectedEndOfInput; | |
| 1277 | return error.BufferUnderrun; | |
| 1278 | } | |
| 1279 | ||
| 1280 | fn skipWhitespace(self: *@This()) void { | |
| 1281 | while (self.cursor < self.input.len) : (self.cursor += 1) { | |
| 1282 | switch (self.input[self.cursor]) { | |
| 1283 | // Whitespace | |
| 1284 | ' ', '\t', '\r' => continue, | |
| 1285 | '\n' => { | |
| 1286 | if (self.diagnostics) |diag| { | |
| 1287 | diag.line_number += 1; | |
| 1288 | // This will count the newline itself, | |
| 1289 | // which means a straight-forward subtraction will give a 1-based column number. | |
| 1290 | diag.line_start_cursor = self.cursor; | |
| 1291 | } | |
| 1292 | continue; | |
| 1293 | }, | |
| 1294 | else => return, | |
| 1295 | } | |
| 1296 | } | |
| 1297 | } | |
| 1298 | ||
| 1299 | fn skipWhitespaceExpectByte(self: *@This()) !u8 { | |
| 1300 | self.skipWhitespace(); | |
| 1301 | return self.expectByte(); | |
| 1302 | } | |
| 1303 | ||
| 1304 | fn skipWhitespaceCheckEnd(self: *@This()) !bool { | |
| 1305 | self.skipWhitespace(); | |
| 1306 | if (self.cursor >= self.input.len) { | |
| 1307 | // End of buffer. | |
| 1308 | if (self.is_end_of_input) { | |
| 1309 | // End of everything. | |
| 1310 | if (self.stackHeight() == 0) { | |
| 1311 | // We did it! | |
| 1312 | return true; | |
| 1313 | } | |
| 1314 | return error.UnexpectedEndOfInput; | |
| 1315 | } | |
| 1316 | return error.BufferUnderrun; | |
| 1317 | } | |
| 1318 | if (self.stackHeight() == 0) return error.SyntaxError; | |
| 1319 | return false; | |
| 1320 | } | |
| 1321 | ||
| 1322 | fn takeValueSlice(self: *@This()) []const u8 { | |
| 1323 | const slice = self.input[self.value_start..self.cursor]; | |
| 1324 | self.value_start = self.cursor; | |
| 1325 | return slice; | |
| 1326 | } | |
| 1327 | fn takeValueSliceMinusTrailingOffset(self: *@This(), trailing_negative_offset: usize) []const u8 { | |
| 1328 | // Check if the escape sequence started before the current input buffer. | |
| 1329 | // (The algebra here is awkward to avoid unsigned underflow, | |
| 1330 | // but it's just making sure the slice on the next line isn't UB.) | |
| 1331 | if (self.cursor <= self.value_start + trailing_negative_offset) return ""; | |
| 1332 | const slice = self.input[self.value_start .. self.cursor - trailing_negative_offset]; | |
| 1333 | // When trailing_negative_offset is non-zero, setting self.value_start doesn't matter, | |
| 1334 | // because we always set it again while emitting the .partial_string_escaped_*. | |
| 1335 | self.value_start = self.cursor; | |
| 1336 | return slice; | |
| 1337 | } | |
| 1338 | ||
| 1339 | fn endOfBufferInNumber(self: *@This(), allow_end: bool) !Token { | |
| 1340 | const slice = self.takeValueSlice(); | |
| 1341 | if (self.is_end_of_input) { | |
| 1342 | if (!allow_end) return error.UnexpectedEndOfInput; | |
| 1343 | self.state = .post_value; | |
| 1344 | return Token{ .number = slice }; | |
| 1345 | } | |
| 1346 | if (slice.len == 0) return error.BufferUnderrun; | |
| 1347 | return Token{ .partial_number = slice }; | |
| 1348 | } | |
| 1349 | ||
| 1350 | fn endOfBufferInString(self: *@This()) !Token { | |
| 1351 | if (self.is_end_of_input) return error.UnexpectedEndOfInput; | |
| 1352 | const slice = self.takeValueSliceMinusTrailingOffset(switch (self.state) { | |
| 1353 | // Don't include the escape sequence in the partial string. | |
| 1354 | .string_backslash => 1, | |
| 1355 | .string_backslash_u => 2, | |
| 1356 | .string_backslash_u_1 => 3, | |
| 1357 | .string_backslash_u_2 => 4, | |
| 1358 | .string_backslash_u_3 => 5, | |
| 1359 | .string_surrogate_half => 6, | |
| 1360 | .string_surrogate_half_backslash => 7, | |
| 1361 | .string_surrogate_half_backslash_u => 8, | |
| 1362 | .string_surrogate_half_backslash_u_1 => 9, | |
| 1363 | .string_surrogate_half_backslash_u_2 => 10, | |
| 1364 | .string_surrogate_half_backslash_u_3 => 11, | |
| 1365 | ||
| 1366 | // Include everything up to the cursor otherwise. | |
| 1367 | .string, | |
| 1368 | .string_utf8_last_byte, | |
| 1369 | .string_utf8_second_to_last_byte, | |
| 1370 | .string_utf8_second_to_last_byte_guard_against_overlong, | |
| 1371 | .string_utf8_second_to_last_byte_guard_against_surrogate_half, | |
| 1372 | .string_utf8_third_to_last_byte, | |
| 1373 | .string_utf8_third_to_last_byte_guard_against_overlong, | |
| 1374 | .string_utf8_third_to_last_byte_guard_against_too_large, | |
| 1375 | => 0, | |
| 1376 | ||
| 1377 | else => unreachable, | |
| 1378 | }); | |
| 1379 | if (slice.len == 0) return error.BufferUnderrun; | |
| 1380 | return Token{ .partial_string = slice }; | |
| 1381 | } | |
| 1382 | ||
| 1383 | fn partialStringCodepoint(code_point: u21) Token { | |
| 1384 | var buf: [4]u8 = undefined; | |
| 1385 | switch (std.unicode.utf8Encode(code_point, &buf) catch unreachable) { | |
| 1386 | 1 => return Token{ .partial_string_escaped_1 = buf[0..1].* }, | |
| 1387 | 2 => return Token{ .partial_string_escaped_2 = buf[0..2].* }, | |
| 1388 | 3 => return Token{ .partial_string_escaped_3 = buf[0..3].* }, | |
| 1389 | 4 => return Token{ .partial_string_escaped_4 = buf[0..4].* }, | |
| 1390 | else => unreachable, | |
| 1391 | } | |
| 1392 | } | |
| 1393 | ||
| 1394 | /// Scan the input and check for malformed JSON. | |
| 1395 | /// On `SyntaxError` or `UnexpectedEndOfInput`, returns `false`. | |
| 1396 | /// Returns any errors from the allocator as-is, which is unlikely, | |
| 1397 | /// but can be caused by extreme nesting depth in the input. | |
| 1398 | pub fn validate(allocator: Allocator, s: []const u8) Allocator.Error!bool { | |
| 1399 | var scanner = Scanner.initCompleteInput(allocator, s); | |
| 1400 | defer scanner.deinit(); | |
| 1401 | ||
| 1402 | while (true) { | |
| 1403 | const token = scanner.next() catch |err| switch (err) { | |
| 1404 | error.SyntaxError, error.UnexpectedEndOfInput => return false, | |
| 1405 | error.OutOfMemory => return error.OutOfMemory, | |
| 1406 | error.BufferUnderrun => unreachable, | |
| 1407 | }; | |
| 1408 | if (token == .end_of_document) break; | |
| 1409 | } | |
| 1410 | ||
| 1411 | return true; | |
| 1412 | } | |
| 1413 | ||
| 1414 | /// The parsing errors are divided into two categories: | |
| 1415 | /// * `SyntaxError` is for clearly malformed JSON documents, | |
| 1416 | /// such as giving an input document that isn't JSON at all. | |
| 1417 | /// * `UnexpectedEndOfInput` is for signaling that everything's been | |
| 1418 | /// valid so far, but the input appears to be truncated for some reason. | |
| 1419 | /// Note that a completely empty (or whitespace-only) input will give `UnexpectedEndOfInput`. | |
| 1420 | pub const Error = error{ SyntaxError, UnexpectedEndOfInput }; | |
| 1421 | ||
| 1422 | /// Used by `json.reader`. | |
| 1423 | pub const default_buffer_size = 0x1000; | |
| 1424 | ||
| 1425 | /// The tokens emitted by `std.json.Scanner` and `std.json.Reader` `.next*()` functions follow this grammar: | |
| 1426 | /// ``` | |
| 1427 | /// <document> = <value> .end_of_document | |
| 1428 | /// <value> = | |
| 1429 | /// | <object> | |
| 1430 | /// | <array> | |
| 1431 | /// | <number> | |
| 1432 | /// | <string> | |
| 1433 | /// | .true | |
| 1434 | /// | .false | |
| 1435 | /// | .null | |
| 1436 | /// <object> = .object_begin ( <string> <value> )* .object_end | |
| 1437 | /// <array> = .array_begin ( <value> )* .array_end | |
| 1438 | /// <number> = <It depends. See below.> | |
| 1439 | /// <string> = <It depends. See below.> | |
| 1440 | /// ``` | |
| 1441 | /// | |
| 1442 | /// What you get for `<number>` and `<string>` values depends on which `next*()` method you call: | |
| 1443 | /// | |
| 1444 | /// ``` | |
| 1445 | /// next(): | |
| 1446 | /// <number> = ( .partial_number )* .number | |
| 1447 | /// <string> = ( <partial_string> )* .string | |
| 1448 | /// <partial_string> = | |
| 1449 | /// | .partial_string | |
| 1450 | /// | .partial_string_escaped_1 | |
| 1451 | /// | .partial_string_escaped_2 | |
| 1452 | /// | .partial_string_escaped_3 | |
| 1453 | /// | .partial_string_escaped_4 | |
| 1454 | /// | |
| 1455 | /// nextAlloc*(..., .alloc_always): | |
| 1456 | /// <number> = .allocated_number | |
| 1457 | /// <string> = .allocated_string | |
| 1458 | /// | |
| 1459 | /// nextAlloc*(..., .alloc_if_needed): | |
| 1460 | /// <number> = | |
| 1461 | /// | .number | |
| 1462 | /// | .allocated_number | |
| 1463 | /// <string> = | |
| 1464 | /// | .string | |
| 1465 | /// | .allocated_string | |
| 1466 | /// ``` | |
| 1467 | /// | |
| 1468 | /// For all tokens with a `[]const u8`, `[]u8`, or `[n]u8` payload, the payload represents the content of the value. | |
| 1469 | /// For number values, this is the representation of the number exactly as it appears in the input. | |
| 1470 | /// For strings, this is the content of the string after resolving escape sequences. | |
| 1471 | /// | |
| 1472 | /// For `.allocated_number` and `.allocated_string`, the `[]u8` payloads are allocations made with the given allocator. | |
| 1473 | /// You are responsible for managing that memory. `json.Reader.deinit()` does *not* free those allocations. | |
| 1474 | /// | |
| 1475 | /// The `.partial_*` tokens indicate that a value spans multiple input buffers or that a string contains escape sequences. | |
| 1476 | /// To get a complete value in memory, you need to concatenate the values yourself. | |
| 1477 | /// Calling `nextAlloc*()` does this for you, and returns an `.allocated_*` token with the result. | |
| 1478 | /// | |
| 1479 | /// For tokens with a `[]const u8` payload, the payload is a slice into the current input buffer. | |
| 1480 | /// The memory may become undefined during the next call to `json.Scanner.feedInput()` | |
| 1481 | /// or any `json.Reader` method whose return error set includes `json.Error`. | |
| 1482 | /// To keep the value persistently, it recommended to make a copy or to use `.alloc_always`, | |
| 1483 | /// which makes a copy for you. | |
| 1484 | /// | |
| 1485 | /// Note that `.number` and `.string` tokens that follow `.partial_*` tokens may have `0` length to indicate that | |
| 1486 | /// the previously partial value is completed with no additional bytes. | |
| 1487 | /// (This can happen when the break between input buffers happens to land on the exact end of a value. E.g. `"[1234"`, `"]"`.) | |
| 1488 | /// `.partial_*` tokens never have `0` length. | |
| 1489 | /// | |
| 1490 | /// The recommended strategy for using the different `next*()` methods is something like this: | |
| 1491 | /// | |
| 1492 | /// When you're expecting an object key, use `.alloc_if_needed`. | |
| 1493 | /// You often don't need a copy of the key string to persist; you might just check which field it is. | |
| 1494 | /// In the case that the key happens to require an allocation, free it immediately after checking it. | |
| 1495 | /// | |
| 1496 | /// When you're expecting a meaningful string value (such as on the right of a `:`), | |
| 1497 | /// use `.alloc_always` in order to keep the value valid throughout parsing the rest of the document. | |
| 1498 | /// | |
| 1499 | /// When you're expecting a number value, use `.alloc_if_needed`. | |
| 1500 | /// You're probably going to be parsing the string representation of the number into a numeric representation, | |
| 1501 | /// so you need the complete string representation only temporarily. | |
| 1502 | /// | |
| 1503 | /// When you're skipping an unrecognized value, use `skipValue()`. | |
| 1504 | pub const Token = union(enum) { | |
| 1505 | object_begin, | |
| 1506 | object_end, | |
| 1507 | array_begin, | |
| 1508 | array_end, | |
| 1509 | ||
| 1510 | true, | |
| 1511 | false, | |
| 1512 | null, | |
| 1513 | ||
| 1514 | number: []const u8, | |
| 1515 | partial_number: []const u8, | |
| 1516 | allocated_number: []u8, | |
| 1517 | ||
| 1518 | string: []const u8, | |
| 1519 | partial_string: []const u8, | |
| 1520 | partial_string_escaped_1: [1]u8, | |
| 1521 | partial_string_escaped_2: [2]u8, | |
| 1522 | partial_string_escaped_3: [3]u8, | |
| 1523 | partial_string_escaped_4: [4]u8, | |
| 1524 | allocated_string: []u8, | |
| 1525 | ||
| 1526 | end_of_document, | |
| 1527 | }; | |
| 1528 | ||
| 1529 | /// This is only used in `peekNextTokenType()` and gives a categorization based on the first byte of the next token that will be emitted from a `next*()` call. | |
| 1530 | pub const TokenType = enum { | |
| 1531 | object_begin, | |
| 1532 | object_end, | |
| 1533 | array_begin, | |
| 1534 | array_end, | |
| 1535 | true, | |
| 1536 | false, | |
| 1537 | null, | |
| 1538 | number, | |
| 1539 | string, | |
| 1540 | end_of_document, | |
| 1541 | }; | |
| 1542 | ||
| 1543 | /// To enable diagnostics, declare `var diagnostics = Diagnostics{};` then call `source.enableDiagnostics(&diagnostics);` | |
| 1544 | /// where `source` is either a `std.json.Reader` or a `std.json.Scanner` that has just been initialized. | |
| 1545 | /// At any time, notably just after an error, call `getLine()`, `getColumn()`, and/or `getByteOffset()` | |
| 1546 | /// to get meaningful information from this. | |
| 1547 | pub const Diagnostics = struct { | |
| 1548 | line_number: u64 = 1, | |
| 1549 | line_start_cursor: usize = @as(usize, @bitCast(@as(isize, -1))), // Start just "before" the input buffer to get a 1-based column for line 1. | |
| 1550 | total_bytes_before_current_input: u64 = 0, | |
| 1551 | cursor_pointer: *const usize = undefined, | |
| 1552 | ||
| 1553 | /// Starts at 1. | |
| 1554 | pub fn getLine(self: *const @This()) u64 { | |
| 1555 | return self.line_number; | |
| 1556 | } | |
| 1557 | /// Starts at 1. | |
| 1558 | pub fn getColumn(self: *const @This()) u64 { | |
| 1559 | return self.cursor_pointer.* -% self.line_start_cursor; | |
| 1560 | } | |
| 1561 | /// Starts at 0. Measures the byte offset since the start of the input. | |
| 1562 | pub fn getByteOffset(self: *const @This()) u64 { | |
| 1563 | return self.total_bytes_before_current_input + self.cursor_pointer.*; | |
| 1564 | } | |
| 1565 | }; | |
| 1566 | ||
| 1567 | /// See the documentation for `std.json.Token`. | |
| 1568 | pub const AllocWhen = enum { alloc_if_needed, alloc_always }; | |
| 1569 | ||
| 1570 | /// For security, the maximum size allocated to store a single string or number value is limited to 4MiB by default. | |
| 1571 | /// This limit can be specified by calling `nextAllocMax()` instead of `nextAlloc()`. | |
| 1572 | pub const default_max_value_len = 4 * 1024 * 1024; | |
| 1573 | ||
| 1574 | /// All `next*()` methods here handle `error.BufferUnderrun` from `std.json.Scanner`, and then read from the reader. | |
| 1575 | pub const Reader = struct { | |
| 1576 | scanner: Scanner, | |
| 1577 | reader: *std.Io.Reader, | |
| 1578 | ||
| 1579 | /// The allocator is only used to track `[]` and `{}` nesting levels. | |
| 1580 | pub fn init(allocator: Allocator, io_reader: *std.Io.Reader) @This() { | |
| 1581 | return .{ | |
| 1582 | .scanner = Scanner.initStreaming(allocator), | |
| 1583 | .reader = io_reader, | |
| 1584 | }; | |
| 1585 | } | |
| 1586 | pub fn deinit(self: *@This()) void { | |
| 1587 | self.scanner.deinit(); | |
| 1588 | self.* = undefined; | |
| 1589 | } | |
| 1590 | ||
| 1591 | /// Calls `std.json.Scanner.enableDiagnostics`. | |
| 1592 | pub fn enableDiagnostics(self: *@This(), diagnostics: *Diagnostics) void { | |
| 1593 | self.scanner.enableDiagnostics(diagnostics); | |
| 1594 | } | |
| 1595 | ||
| 1596 | pub const NextError = std.Io.Reader.Error || Error || Allocator.Error; | |
| 1597 | pub const SkipError = Reader.NextError; | |
| 1598 | pub const AllocError = Reader.NextError || error{ValueTooLong}; | |
| 1599 | pub const PeekError = std.Io.Reader.Error || Error; | |
| 1600 | ||
| 1601 | /// Equivalent to `nextAllocMax(allocator, when, default_max_value_len);` | |
| 1602 | /// See also `std.json.Token` for documentation of `nextAlloc*()` function behavior. | |
| 1603 | pub fn nextAlloc(self: *@This(), allocator: Allocator, when: AllocWhen) Reader.AllocError!Token { | |
| 1604 | return self.nextAllocMax(allocator, when, default_max_value_len); | |
| 1605 | } | |
| 1606 | /// See also `std.json.Token` for documentation of `nextAlloc*()` function behavior. | |
| 1607 | pub fn nextAllocMax(self: *@This(), allocator: Allocator, when: AllocWhen, max_value_len: usize) Reader.AllocError!Token { | |
| 1608 | const token_type = try self.peekNextTokenType(); | |
| 1609 | switch (token_type) { | |
| 1610 | .number, .string => { | |
| 1611 | var value_list = ArrayList(u8).init(allocator); | |
| 1612 | errdefer { | |
| 1613 | value_list.deinit(); | |
| 1614 | } | |
| 1615 | if (try self.allocNextIntoArrayListMax(&value_list, when, max_value_len)) |slice| { | |
| 1616 | return if (token_type == .number) | |
| 1617 | Token{ .number = slice } | |
| 1618 | else | |
| 1619 | Token{ .string = slice }; | |
| 1620 | } else { | |
| 1621 | return if (token_type == .number) | |
| 1622 | Token{ .allocated_number = try value_list.toOwnedSlice() } | |
| 1623 | else | |
| 1624 | Token{ .allocated_string = try value_list.toOwnedSlice() }; | |
| 1625 | } | |
| 1626 | }, | |
| 1627 | ||
| 1628 | // Simple tokens never alloc. | |
| 1629 | .object_begin, | |
| 1630 | .object_end, | |
| 1631 | .array_begin, | |
| 1632 | .array_end, | |
| 1633 | .true, | |
| 1634 | .false, | |
| 1635 | .null, | |
| 1636 | .end_of_document, | |
| 1637 | => return try self.next(), | |
| 1638 | } | |
| 1639 | } | |
| 1640 | ||
| 1641 | /// Equivalent to `allocNextIntoArrayListMax(value_list, when, default_max_value_len);` | |
| 1642 | pub fn allocNextIntoArrayList(self: *@This(), value_list: *ArrayList(u8), when: AllocWhen) Reader.AllocError!?[]const u8 { | |
| 1643 | return self.allocNextIntoArrayListMax(value_list, when, default_max_value_len); | |
| 1644 | } | |
| 1645 | /// Calls `std.json.Scanner.allocNextIntoArrayListMax` and handles `error.BufferUnderrun`. | |
| 1646 | pub fn allocNextIntoArrayListMax(self: *@This(), value_list: *ArrayList(u8), when: AllocWhen, max_value_len: usize) Reader.AllocError!?[]const u8 { | |
| 1647 | while (true) { | |
| 1648 | return self.scanner.allocNextIntoArrayListMax(value_list, when, max_value_len) catch |err| switch (err) { | |
| 1649 | error.BufferUnderrun => { | |
| 1650 | try self.refillBuffer(); | |
| 1651 | continue; | |
| 1652 | }, | |
| 1653 | else => |other_err| return other_err, | |
| 1654 | }; | |
| 1655 | } | |
| 1656 | } | |
| 1657 | ||
| 1658 | /// Like `std.json.Scanner.skipValue`, but handles `error.BufferUnderrun`. | |
| 1659 | pub fn skipValue(self: *@This()) Reader.SkipError!void { | |
| 1660 | switch (try self.peekNextTokenType()) { | |
| 1661 | .object_begin, .array_begin => { | |
| 1662 | try self.skipUntilStackHeight(self.stackHeight()); | |
| 1663 | }, | |
| 1664 | .number, .string => { | |
| 1665 | while (true) { | |
| 1666 | switch (try self.next()) { | |
| 1667 | .partial_number, | |
| 1668 | .partial_string, | |
| 1669 | .partial_string_escaped_1, | |
| 1670 | .partial_string_escaped_2, | |
| 1671 | .partial_string_escaped_3, | |
| 1672 | .partial_string_escaped_4, | |
| 1673 | => continue, | |
| 1674 | ||
| 1675 | .number, .string => break, | |
| 1676 | ||
| 1677 | else => unreachable, | |
| 1678 | } | |
| 1679 | } | |
| 1680 | }, | |
| 1681 | .true, .false, .null => { | |
| 1682 | _ = try self.next(); | |
| 1683 | }, | |
| 1684 | ||
| 1685 | .object_end, .array_end, .end_of_document => unreachable, // Attempt to skip a non-value token. | |
| 1686 | } | |
| 1687 | } | |
| 1688 | /// Like `std.json.Scanner.skipUntilStackHeight()` but handles `error.BufferUnderrun`. | |
| 1689 | pub fn skipUntilStackHeight(self: *@This(), terminal_stack_height: usize) Reader.NextError!void { | |
| 1690 | while (true) { | |
| 1691 | return self.scanner.skipUntilStackHeight(terminal_stack_height) catch |err| switch (err) { | |
| 1692 | error.BufferUnderrun => { | |
| 1693 | try self.refillBuffer(); | |
| 1694 | continue; | |
| 1695 | }, | |
| 1696 | else => |other_err| return other_err, | |
| 1697 | }; | |
| 1698 | } | |
| 1699 | } | |
| 1700 | ||
| 1701 | /// Calls `std.json.Scanner.stackHeight`. | |
| 1702 | pub fn stackHeight(self: *const @This()) usize { | |
| 1703 | return self.scanner.stackHeight(); | |
| 1704 | } | |
| 1705 | /// Calls `std.json.Scanner.ensureTotalStackCapacity`. | |
| 1706 | pub fn ensureTotalStackCapacity(self: *@This(), height: usize) Allocator.Error!void { | |
| 1707 | try self.scanner.ensureTotalStackCapacity(height); | |
| 1708 | } | |
| 1709 | ||
| 1710 | /// See `std.json.Token` for documentation of this function. | |
| 1711 | pub fn next(self: *@This()) Reader.NextError!Token { | |
| 1712 | while (true) { | |
| 1713 | return self.scanner.next() catch |err| switch (err) { | |
| 1714 | error.BufferUnderrun => { | |
| 1715 | try self.refillBuffer(); | |
| 1716 | continue; | |
| 1717 | }, | |
| 1718 | else => |other_err| return other_err, | |
| 1719 | }; | |
| 1720 | } | |
| 1721 | } | |
| 1722 | ||
| 1723 | /// See `std.json.Scanner.peekNextTokenType()`. | |
| 1724 | pub fn peekNextTokenType(self: *@This()) Reader.PeekError!TokenType { | |
| 1725 | while (true) { | |
| 1726 | return self.scanner.peekNextTokenType() catch |err| switch (err) { | |
| 1727 | error.BufferUnderrun => { | |
| 1728 | try self.refillBuffer(); | |
| 1729 | continue; | |
| 1730 | }, | |
| 1731 | else => |other_err| return other_err, | |
| 1732 | }; | |
| 1733 | } | |
| 1734 | } | |
| 1735 | ||
| 1736 | fn refillBuffer(self: *@This()) std.Io.Reader.Error!void { | |
| 1737 | const input = self.reader.peekGreedy(1) catch |err| switch (err) { | |
| 1738 | error.ReadFailed => return error.ReadFailed, | |
| 1739 | error.EndOfStream => return self.scanner.endInput(), | |
| 1740 | }; | |
| 1741 | self.reader.toss(input.len); | |
| 1742 | self.scanner.feedInput(input); | |
| 1743 | } | |
| 1744 | }; | |
| 1745 | ||
| 1746 | const OBJECT_MODE = 0; | |
| 1747 | const ARRAY_MODE = 1; | |
| 1748 | ||
| 1749 | fn appendSlice(list: *std.ArrayList(u8), buf: []const u8, max_value_len: usize) !void { | |
| 1750 | const new_len = std.math.add(usize, list.items.len, buf.len) catch return error.ValueTooLong; | |
| 1751 | if (new_len > max_value_len) return error.ValueTooLong; | |
| 1752 | try list.appendSlice(buf); | |
| 1753 | } | |
| 1754 | ||
| 1755 | /// For the slice you get from a `Token.number` or `Token.allocated_number`, | |
| 1756 | /// this function returns true if the number doesn't contain any fraction or exponent components, and is not `-0`. | |
| 1757 | /// Note, the numeric value encoded by the value may still be an integer, such as `1.0`. | |
| 1758 | /// This function is meant to give a hint about whether integer parsing or float parsing should be used on the value. | |
| 1759 | /// This function will not give meaningful results on non-numeric input. | |
| 1760 | pub fn isNumberFormattedLikeAnInteger(value: []const u8) bool { | |
| 1761 | if (std.mem.eql(u8, value, "-0")) return false; | |
| 1762 | return std.mem.indexOfAny(u8, value, ".eE") == null; | |
| 1763 | } | |
| 1764 | ||
| 1765 | test { | |
| 1766 | _ = @import("./scanner_test.zig"); | |
| 1767 | } |
lib/std/json/Stringify.zig created+999| ... | ... | @@ -0,0 +1,999 @@ |
| 1 | //! Writes JSON ([RFC8259](https://tools.ietf.org/html/rfc8259)) formatted data | |
| 2 | //! to a stream. | |
| 3 | //! | |
| 4 | //! The sequence of method calls to write JSON content must follow this grammar: | |
| 5 | //! ``` | |
| 6 | //! <once> = <value> | |
| 7 | //! <value> = | |
| 8 | //! | <object> | |
| 9 | //! | <array> | |
| 10 | //! | write | |
| 11 | ||
| 12 | //! | <writeRawStream> | |
| 13 | //! <object> = beginObject ( <field> <value> )* endObject | |
| 14 | //! <field> = objectField | objectFieldRaw | <objectFieldRawStream> | |
| 15 | //! <array> = beginArray ( <value> )* endArray | |
| 16 | //! <writeRawStream> = beginWriteRaw ( stream.writeAll )* endWriteRaw | |
| 17 | //! <objectFieldRawStream> = beginObjectFieldRaw ( stream.writeAll )* endObjectFieldRaw | |
| 18 | //! ``` | |
| 19 | ||
| 20 | const std = @import("../std.zig"); | |
| 21 | const assert = std.debug.assert; | |
| 22 | const Allocator = std.mem.Allocator; | |
| 23 | const ArrayList = std.ArrayList; | |
| 24 | const BitStack = std.BitStack; | |
| 25 | const Stringify = @This(); | |
| 26 | const Writer = std.io.Writer; | |
| 27 | ||
| 28 | const IndentationMode = enum(u1) { | |
| 29 | object = 0, | |
| 30 | array = 1, | |
| 31 | }; | |
| 32 | ||
| 33 | writer: *Writer, | |
| 34 | options: Options = .{}, | |
| 35 | indent_level: usize = 0, | |
| 36 | next_punctuation: enum { | |
| 37 | the_beginning, | |
| 38 | none, | |
| 39 | comma, | |
| 40 | colon, | |
| 41 | } = .the_beginning, | |
| 42 | ||
| 43 | nesting_stack: switch (safety_checks) { | |
| 44 | .checked_to_fixed_depth => |fixed_buffer_size| [(fixed_buffer_size + 7) >> 3]u8, | |
| 45 | .assumed_correct => void, | |
| 46 | } = switch (safety_checks) { | |
| 47 | .checked_to_fixed_depth => @splat(0), | |
| 48 | .assumed_correct => {}, | |
| 49 | }, | |
| 50 | ||
| 51 | raw_streaming_mode: if (build_mode_has_safety) | |
| 52 | enum { none, value, objectField } | |
| 53 | else | |
| 54 | void = if (build_mode_has_safety) .none else {}, | |
| 55 | ||
| 56 | const build_mode_has_safety = switch (@import("builtin").mode) { | |
| 57 | .Debug, .ReleaseSafe => true, | |
| 58 | .ReleaseFast, .ReleaseSmall => false, | |
| 59 | }; | |
| 60 | ||
| 61 | /// The `safety_checks_hint` parameter determines how much memory is used to enable assertions that the above grammar is being followed, | |
| 62 | /// e.g. tripping an assertion rather than allowing `endObject` to emit the final `}` in `[[[]]}`. | |
| 63 | /// "Depth" in this context means the depth of nested `[]` or `{}` expressions | |
| 64 | /// (or equivalently the amount of recursion on the `<value>` grammar expression above). | |
| 65 | /// For example, emitting the JSON `[[[]]]` requires a depth of 3. | |
| 66 | /// If `.checked_to_fixed_depth` is used, there is additionally an assertion that the nesting depth never exceeds the given limit. | |
| 67 | /// `.checked_to_fixed_depth` embeds the storage required in the `Stringify` struct. | |
| 68 | /// `.assumed_correct` requires no space and performs none of these assertions. | |
| 69 | /// In `ReleaseFast` and `ReleaseSmall` mode, the given `safety_checks_hint` is ignored and is always treated as `.assumed_correct`. | |
| 70 | const safety_checks_hint: union(enum) { | |
| 71 | /// Rounded up to the nearest multiple of 8. | |
| 72 | checked_to_fixed_depth: usize, | |
| 73 | assumed_correct, | |
| 74 | } = .{ .checked_to_fixed_depth = 256 }; | |
| 75 | ||
| 76 | const safety_checks: @TypeOf(safety_checks_hint) = if (build_mode_has_safety) | |
| 77 | safety_checks_hint | |
| 78 | else | |
| 79 | .assumed_correct; | |
| 80 | ||
| 81 | pub const Error = Writer.Error; | |
| 82 | ||
| 83 | pub fn beginArray(self: *Stringify) Error!void { | |
| 84 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); | |
| 85 | try self.valueStart(); | |
| 86 | try self.writer.writeByte('['); | |
| 87 | try self.pushIndentation(.array); | |
| 88 | self.next_punctuation = .none; | |
| 89 | } | |
| 90 | ||
| 91 | pub fn beginObject(self: *Stringify) Error!void { | |
| 92 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); | |
| 93 | try self.valueStart(); | |
| 94 | try self.writer.writeByte('{'); | |
| 95 | try self.pushIndentation(.object); | |
| 96 | self.next_punctuation = .none; | |
| 97 | } | |
| 98 | ||
| 99 | pub fn endArray(self: *Stringify) Error!void { | |
| 100 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); | |
| 101 | self.popIndentation(.array); | |
| 102 | switch (self.next_punctuation) { | |
| 103 | .none => {}, | |
| 104 | .comma => { | |
| 105 | try self.indent(); | |
| 106 | }, | |
| 107 | .the_beginning, .colon => unreachable, | |
| 108 | } | |
| 109 | try self.writer.writeByte(']'); | |
| 110 | self.valueDone(); | |
| 111 | } | |
| 112 | ||
| 113 | pub fn endObject(self: *Stringify) Error!void { | |
| 114 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); | |
| 115 | self.popIndentation(.object); | |
| 116 | switch (self.next_punctuation) { | |
| 117 | .none => {}, | |
| 118 | .comma => { | |
| 119 | try self.indent(); | |
| 120 | }, | |
| 121 | .the_beginning, .colon => unreachable, | |
| 122 | } | |
| 123 | try self.writer.writeByte('}'); | |
| 124 | self.valueDone(); | |
| 125 | } | |
| 126 | ||
| 127 | fn pushIndentation(self: *Stringify, mode: IndentationMode) !void { | |
| 128 | switch (safety_checks) { | |
| 129 | .checked_to_fixed_depth => { | |
| 130 | BitStack.pushWithStateAssumeCapacity(&self.nesting_stack, &self.indent_level, @intFromEnum(mode)); | |
| 131 | }, | |
| 132 | .assumed_correct => { | |
| 133 | self.indent_level += 1; | |
| 134 | }, | |
| 135 | } | |
| 136 | } | |
| 137 | fn popIndentation(self: *Stringify, expected_mode: IndentationMode) void { | |
| 138 | switch (safety_checks) { | |
| 139 | .checked_to_fixed_depth => { | |
| 140 | assert(BitStack.popWithState(&self.nesting_stack, &self.indent_level) == @intFromEnum(expected_mode)); | |
| 141 | }, | |
| 142 | .assumed_correct => { | |
| 143 | self.indent_level -= 1; | |
| 144 | }, | |
| 145 | } | |
| 146 | } | |
| 147 | ||
| 148 | fn indent(self: *Stringify) !void { | |
| 149 | var char: u8 = ' '; | |
| 150 | const n_chars = switch (self.options.whitespace) { | |
| 151 | .minified => return, | |
| 152 | .indent_1 => 1 * self.indent_level, | |
| 153 | .indent_2 => 2 * self.indent_level, | |
| 154 | .indent_3 => 3 * self.indent_level, | |
| 155 | .indent_4 => 4 * self.indent_level, | |
| 156 | .indent_8 => 8 * self.indent_level, | |
| 157 | .indent_tab => blk: { | |
| 158 | char = '\t'; | |
| 159 | break :blk self.indent_level; | |
| 160 | }, | |
| 161 | }; | |
| 162 | try self.writer.writeByte('\n'); | |
| 163 | try self.writer.splatByteAll(char, n_chars); | |
| 164 | } | |
| 165 | ||
| 166 | fn valueStart(self: *Stringify) !void { | |
| 167 | if (self.isObjectKeyExpected()) |is_it| assert(!is_it); // Call objectField*(), not write(), for object keys. | |
| 168 | return self.valueStartAssumeTypeOk(); | |
| 169 | } | |
| 170 | fn objectFieldStart(self: *Stringify) !void { | |
| 171 | if (self.isObjectKeyExpected()) |is_it| assert(is_it); // Expected write(), not objectField*(). | |
| 172 | return self.valueStartAssumeTypeOk(); | |
| 173 | } | |
| 174 | fn valueStartAssumeTypeOk(self: *Stringify) !void { | |
| 175 | assert(!self.isComplete()); // JSON document already complete. | |
| 176 | switch (self.next_punctuation) { | |
| 177 | .the_beginning => { | |
| 178 | // No indentation for the very beginning. | |
| 179 | }, | |
| 180 | .none => { | |
| 181 | // First item in a container. | |
| 182 | try self.indent(); | |
| 183 | }, | |
| 184 | .comma => { | |
| 185 | // Subsequent item in a container. | |
| 186 | try self.writer.writeByte(','); | |
| 187 | try self.indent(); | |
| 188 | }, | |
| 189 | .colon => { | |
| 190 | try self.writer.writeByte(':'); | |
| 191 | if (self.options.whitespace != .minified) { | |
| 192 | try self.writer.writeByte(' '); | |
| 193 | } | |
| 194 | }, | |
| 195 | } | |
| 196 | } | |
| 197 | fn valueDone(self: *Stringify) void { | |
| 198 | self.next_punctuation = .comma; | |
| 199 | } | |
| 200 | ||
| 201 | // Only when safety is enabled: | |
| 202 | fn isObjectKeyExpected(self: *const Stringify) ?bool { | |
| 203 | switch (safety_checks) { | |
| 204 | .checked_to_fixed_depth => return self.indent_level > 0 and | |
| 205 | BitStack.peekWithState(&self.nesting_stack, self.indent_level) == @intFromEnum(IndentationMode.object) and | |
| 206 | self.next_punctuation != .colon, | |
| 207 | .assumed_correct => return null, | |
| 208 | } | |
| 209 | } | |
| 210 | fn isComplete(self: *const Stringify) bool { | |
| 211 | return self.indent_level == 0 and self.next_punctuation == .comma; | |
| 212 | } | |
| 213 | ||
| 214 | /// An alternative to calling `write` that formats a value with `std.fmt`. | |
| 215 | /// This function does the usual punctuation and indentation formatting | |
| 216 | /// assuming the resulting formatted string represents a single complete value; | |
| 217 | /// e.g. `"1"`, `"[]"`, `"[1,2]"`, not `"1,2"`. | |
| 218 | /// This function may be useful for doing your own number formatting. | |
| 219 | pub fn print(self: *Stringify, comptime fmt: []const u8, args: anytype) Error!void { | |
| 220 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); | |
| 221 | try self.valueStart(); | |
| 222 | try self.writer.print(fmt, args); | |
| 223 | self.valueDone(); | |
| 224 | } | |
| 225 | ||
| 226 | test print { | |
| 227 | var out_buf: [1024]u8 = undefined; | |
| 228 | var out: Writer = .fixed(&out_buf); | |
| 229 | ||
| 230 | var w: Stringify = .{ .writer = &out, .options = .{ .whitespace = .indent_2 } }; | |
| 231 | ||
| 232 | try w.beginObject(); | |
| 233 | try w.objectField("a"); | |
| 234 | try w.print("[ ]", .{}); | |
| 235 | try w.objectField("b"); | |
| 236 | try w.beginArray(); | |
| 237 | try w.print("[{s}] ", .{"[]"}); | |
| 238 | try w.print(" {}", .{12345}); | |
| 239 | try w.endArray(); | |
| 240 | try w.endObject(); | |
| 241 | ||
| 242 | const expected = | |
| 243 | \\{ | |
| 244 | \\ "a": [ ], | |
| 245 | \\ "b": [ | |
| 246 | \\ [[]] , | |
| 247 | \\ 12345 | |
| 248 | \\ ] | |
| 249 | \\} | |
| 250 | ; | |
| 251 | try std.testing.expectEqualStrings(expected, out.buffered()); | |
| 252 | } | |
| 253 | ||
| 254 | /// An alternative to calling `write` that allows you to write directly to the `.writer` field, e.g. with `.writer.writeAll()`. | |
| 255 | /// Call `beginWriteRaw()`, then write a complete value (including any quotes if necessary) directly to the `.writer` field, | |
| 256 | /// then call `endWriteRaw()`. | |
| 257 | /// This can be useful for streaming very long strings into the output without needing it all buffered in memory. | |
| 258 | pub fn beginWriteRaw(self: *Stringify) !void { | |
| 259 | if (build_mode_has_safety) { | |
| 260 | assert(self.raw_streaming_mode == .none); | |
| 261 | self.raw_streaming_mode = .value; | |
| 262 | } | |
| 263 | try self.valueStart(); | |
| 264 | } | |
| 265 | ||
| 266 | /// See `beginWriteRaw`. | |
| 267 | pub fn endWriteRaw(self: *Stringify) void { | |
| 268 | if (build_mode_has_safety) { | |
| 269 | assert(self.raw_streaming_mode == .value); | |
| 270 | self.raw_streaming_mode = .none; | |
| 271 | } | |
| 272 | self.valueDone(); | |
| 273 | } | |
| 274 | ||
| 275 | /// See `Stringify` for when to call this method. | |
| 276 | /// `key` is the string content of the property name. | |
| 277 | /// Surrounding quotes will be added and any special characters will be escaped. | |
| 278 | /// See also `objectFieldRaw`. | |
| 279 | pub fn objectField(self: *Stringify, key: []const u8) Error!void { | |
| 280 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); | |
| 281 | try self.objectFieldStart(); | |
| 282 | try encodeJsonString(key, self.options, self.writer); | |
| 283 | self.next_punctuation = .colon; | |
| 284 | } | |
| 285 | /// See `Stringify` for when to call this method. | |
| 286 | /// `quoted_key` is the complete bytes of the key including quotes and any necessary escape sequences. | |
| 287 | /// A few assertions are performed on the given value to ensure that the caller of this function understands the API contract. | |
| 288 | /// See also `objectField`. | |
| 289 | pub fn objectFieldRaw(self: *Stringify, quoted_key: []const u8) Error!void { | |
| 290 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); | |
| 291 | assert(quoted_key.len >= 2 and quoted_key[0] == '"' and quoted_key[quoted_key.len - 1] == '"'); // quoted_key should be "quoted". | |
| 292 | try self.objectFieldStart(); | |
| 293 | try self.writer.writeAll(quoted_key); | |
| 294 | self.next_punctuation = .colon; | |
| 295 | } | |
| 296 | ||
| 297 | /// In the rare case that you need to write very long object field names, | |
| 298 | /// this is an alternative to `objectField` and `objectFieldRaw` that allows you to write directly to the `.writer` field | |
| 299 | /// similar to `beginWriteRaw`. | |
| 300 | /// Call `endObjectFieldRaw()` when you're done. | |
| 301 | pub fn beginObjectFieldRaw(self: *Stringify) !void { | |
| 302 | if (build_mode_has_safety) { | |
| 303 | assert(self.raw_streaming_mode == .none); | |
| 304 | self.raw_streaming_mode = .objectField; | |
| 305 | } | |
| 306 | try self.objectFieldStart(); | |
| 307 | } | |
| 308 | ||
| 309 | /// See `beginObjectFieldRaw`. | |
| 310 | pub fn endObjectFieldRaw(self: *Stringify) void { | |
| 311 | if (build_mode_has_safety) { | |
| 312 | assert(self.raw_streaming_mode == .objectField); | |
| 313 | self.raw_streaming_mode = .none; | |
| 314 | } | |
| 315 | self.next_punctuation = .colon; | |
| 316 | } | |
| 317 | ||
| 318 | /// Renders the given Zig value as JSON. | |
| 319 | /// | |
| 320 | /// Supported types: | |
| 321 | /// * Zig `bool` -> JSON `true` or `false`. | |
| 322 | /// * Zig `?T` -> `null` or the rendering of `T`. | |
| 323 | /// * Zig `i32`, `u64`, etc. -> JSON number or string. | |
| 324 | /// * When option `emit_nonportable_numbers_as_strings` is true, if the value is outside the range `+-1<<53` (the precise integer range of f64), it is rendered as a JSON string in base 10. Otherwise, it is rendered as JSON number. | |
| 325 | /// * Zig floats -> JSON number or string. | |
| 326 | /// * If the value cannot be precisely represented by an f64, it is rendered as a JSON string. Otherwise, it is rendered as JSON number. | |
| 327 | /// * TODO: Float rendering will likely change in the future, e.g. to remove the unnecessary "e+00". | |
| 328 | /// * Zig `[]const u8`, `[]u8`, `*[N]u8`, `@Vector(N, u8)`, and similar -> JSON string. | |
| 329 | /// * See `Options.emit_strings_as_arrays`. | |
| 330 | /// * If the content is not valid UTF-8, rendered as an array of numbers instead. | |
| 331 | /// * Zig `[]T`, `[N]T`, `*[N]T`, `@Vector(N, T)`, and similar -> JSON array of the rendering of each item. | |
| 332 | /// * Zig tuple -> JSON array of the rendering of each item. | |
| 333 | /// * Zig `struct` -> JSON object with each field in declaration order. | |
| 334 | /// * If the struct declares a method `pub fn jsonStringify(self: *@This(), jw: anytype) !void`, it is called to do the serialization instead of the default behavior. The given `jw` is a pointer to this `Stringify`. See `std.json.Value` for an example. | |
| 335 | /// * See `Options.emit_null_optional_fields`. | |
| 336 | /// * Zig `union(enum)` -> JSON object with one field named for the active tag and a value representing the payload. | |
| 337 | /// * If the payload is `void`, then the emitted value is `{}`. | |
| 338 | /// * If the union declares a method `pub fn jsonStringify(self: *@This(), jw: anytype) !void`, it is called to do the serialization instead of the default behavior. The given `jw` is a pointer to this `Stringify`. | |
| 339 | /// * Zig `enum` -> JSON string naming the active tag. | |
| 340 | /// * If the enum declares a method `pub fn jsonStringify(self: *@This(), jw: anytype) !void`, it is called to do the serialization instead of the default behavior. The given `jw` is a pointer to this `Stringify`. | |
| 341 | /// * If the enum is non-exhaustive, unnamed values are rendered as integers. | |
| 342 | /// * Zig untyped enum literal -> JSON string naming the active tag. | |
| 343 | /// * Zig error -> JSON string naming the error. | |
| 344 | /// * Zig `*T` -> the rendering of `T`. Note there is no guard against circular-reference infinite recursion. | |
| 345 | /// | |
| 346 | /// See also alternative functions `print` and `beginWriteRaw`. | |
| 347 | /// For writing object field names, use `objectField` instead. | |
| 348 | pub fn write(self: *Stringify, v: anytype) Error!void { | |
| 349 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); | |
| 350 | const T = @TypeOf(v); | |
| 351 | switch (@typeInfo(T)) { | |
| 352 | .int => { | |
| 353 | try self.valueStart(); | |
| 354 | if (self.options.emit_nonportable_numbers_as_strings and | |
| 355 | (v <= -(1 << 53) or v >= (1 << 53))) | |
| 356 | { | |
| 357 | try self.writer.print("\"{}\"", .{v}); | |
| 358 | } else { | |
| 359 | try self.writer.print("{}", .{v}); | |
| 360 | } | |
| 361 | self.valueDone(); | |
| 362 | return; | |
| 363 | }, | |
| 364 | .comptime_int => { | |
| 365 | return self.write(@as(std.math.IntFittingRange(v, v), v)); | |
| 366 | }, | |
| 367 | .float, .comptime_float => { | |
| 368 | if (@as(f64, @floatCast(v)) == v) { | |
| 369 | try self.valueStart(); | |
| 370 | try self.writer.print("{}", .{@as(f64, @floatCast(v))}); | |
| 371 | self.valueDone(); | |
| 372 | return; | |
| 373 | } | |
| 374 | try self.valueStart(); | |
| 375 | try self.writer.print("\"{}\"", .{v}); | |
| 376 | self.valueDone(); | |
| 377 | return; | |
| 378 | }, | |
| 379 | ||
| 380 | .bool => { | |
| 381 | try self.valueStart(); | |
| 382 | try self.writer.writeAll(if (v) "true" else "false"); | |
| 383 | self.valueDone(); | |
| 384 | return; | |
| 385 | }, | |
| 386 | .null => { | |
| 387 | try self.valueStart(); | |
| 388 | try self.writer.writeAll("null"); | |
| 389 | self.valueDone(); | |
| 390 | return; | |
| 391 | }, | |
| 392 | .optional => { | |
| 393 | if (v) |payload| { | |
| 394 | return try self.write(payload); | |
| 395 | } else { | |
| 396 | return try self.write(null); | |
| 397 | } | |
| 398 | }, | |
| 399 | .@"enum" => |enum_info| { | |
| 400 | if (std.meta.hasFn(T, "jsonStringify")) { | |
| 401 | return v.jsonStringify(self); | |
| 402 | } | |
| 403 | ||
| 404 | if (!enum_info.is_exhaustive) { | |
| 405 | inline for (enum_info.fields) |field| { | |
| 406 | if (v == @field(T, field.name)) { | |
| 407 | break; | |
| 408 | } | |
| 409 | } else { | |
| 410 | return self.write(@intFromEnum(v)); | |
| 411 | } | |
| 412 | } | |
| 413 | ||
| 414 | return self.stringValue(@tagName(v)); | |
| 415 | }, | |
| 416 | .enum_literal => { | |
| 417 | return self.stringValue(@tagName(v)); | |
| 418 | }, | |
| 419 | .@"union" => { | |
| 420 | if (std.meta.hasFn(T, "jsonStringify")) { | |
| 421 | return v.jsonStringify(self); | |
| 422 | } | |
| 423 | ||
| 424 | const info = @typeInfo(T).@"union"; | |
| 425 | if (info.tag_type) |UnionTagType| { | |
| 426 | try self.beginObject(); | |
| 427 | inline for (info.fields) |u_field| { | |
| 428 | if (v == @field(UnionTagType, u_field.name)) { | |
| 429 | try self.objectField(u_field.name); | |
| 430 | if (u_field.type == void) { | |
| 431 | // void v is {} | |
| 432 | try self.beginObject(); | |
| 433 | try self.endObject(); | |
| 434 | } else { | |
| 435 | try self.write(@field(v, u_field.name)); | |
| 436 | } | |
| 437 | break; | |
| 438 | } | |
| 439 | } else { | |
| 440 | unreachable; // No active tag? | |
| 441 | } | |
| 442 | try self.endObject(); | |
| 443 | return; | |
| 444 | } else { | |
| 445 | @compileError("Unable to stringify untagged union '" ++ @typeName(T) ++ "'"); | |
| 446 | } | |
| 447 | }, | |
| 448 | .@"struct" => |S| { | |
| 449 | if (std.meta.hasFn(T, "jsonStringify")) { | |
| 450 | return v.jsonStringify(self); | |
| 451 | } | |
| 452 | ||
| 453 | if (S.is_tuple) { | |
| 454 | try self.beginArray(); | |
| 455 | } else { | |
| 456 | try self.beginObject(); | |
| 457 | } | |
| 458 | inline for (S.fields) |Field| { | |
| 459 | // don't include void fields | |
| 460 | if (Field.type == void) continue; | |
| 461 | ||
| 462 | var emit_field = true; | |
| 463 | ||
| 464 | // don't include optional fields that are null when emit_null_optional_fields is set to false | |
| 465 | if (@typeInfo(Field.type) == .optional) { | |
| 466 | if (self.options.emit_null_optional_fields == false) { | |
| 467 | if (@field(v, Field.name) == null) { | |
| 468 | emit_field = false; | |
| 469 | } | |
| 470 | } | |
| 471 | } | |
| 472 | ||
| 473 | if (emit_field) { | |
| 474 | if (!S.is_tuple) { | |
| 475 | try self.objectField(Field.name); | |
| 476 | } | |
| 477 | try self.write(@field(v, Field.name)); | |
| 478 | } | |
| 479 | } | |
| 480 | if (S.is_tuple) { | |
| 481 | try self.endArray(); | |
| 482 | } else { | |
| 483 | try self.endObject(); | |
| 484 | } | |
| 485 | return; | |
| 486 | }, | |
| 487 | .error_set => return self.stringValue(@errorName(v)), | |
| 488 | .pointer => |ptr_info| switch (ptr_info.size) { | |
| 489 | .one => switch (@typeInfo(ptr_info.child)) { | |
| 490 | .array => { | |
| 491 | // Coerce `*[N]T` to `[]const T`. | |
| 492 | const Slice = []const std.meta.Elem(ptr_info.child); | |
| 493 | return self.write(@as(Slice, v)); | |
| 494 | }, | |
| 495 | else => { | |
| 496 | return self.write(v.*); | |
| 497 | }, | |
| 498 | }, | |
| 499 | .many, .slice => { | |
| 500 | if (ptr_info.size == .many and ptr_info.sentinel() == null) | |
| 501 | @compileError("unable to stringify type '" ++ @typeName(T) ++ "' without sentinel"); | |
| 502 | const slice = if (ptr_info.size == .many) std.mem.span(v) else v; | |
| 503 | ||
| 504 | if (ptr_info.child == u8) { | |
| 505 | // This is a []const u8, or some similar Zig string. | |
| 506 | if (!self.options.emit_strings_as_arrays and std.unicode.utf8ValidateSlice(slice)) { | |
| 507 | return self.stringValue(slice); | |
| 508 | } | |
| 509 | } | |
| 510 | ||
| 511 | try self.beginArray(); | |
| 512 | for (slice) |x| { | |
| 513 | try self.write(x); | |
| 514 | } | |
| 515 | try self.endArray(); | |
| 516 | return; | |
| 517 | }, | |
| 518 | else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"), | |
| 519 | }, | |
| 520 | .array => { | |
| 521 | // Coerce `[N]T` to `*const [N]T` (and then to `[]const T`). | |
| 522 | return self.write(&v); | |
| 523 | }, | |
| 524 | .vector => |info| { | |
| 525 | const array: [info.len]info.child = v; | |
| 526 | return self.write(&array); | |
| 527 | }, | |
| 528 | else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"), | |
| 529 | } | |
| 530 | unreachable; | |
| 531 | } | |
| 532 | ||
| 533 | fn stringValue(self: *Stringify, s: []const u8) !void { | |
| 534 | try self.valueStart(); | |
| 535 | try encodeJsonString(s, self.options, self.writer); | |
| 536 | self.valueDone(); | |
| 537 | } | |
| 538 | ||
| 539 | pub const Options = struct { | |
| 540 | /// Controls the whitespace emitted. | |
| 541 | /// The default `.minified` is a compact encoding with no whitespace between tokens. | |
| 542 | /// Any setting other than `.minified` will use newlines, indentation, and a space after each ':'. | |
| 543 | /// `.indent_1` means 1 space for each indentation level, `.indent_2` means 2 spaces, etc. | |
| 544 | /// `.indent_tab` uses a tab for each indentation level. | |
| 545 | whitespace: enum { | |
| 546 | minified, | |
| 547 | indent_1, | |
| 548 | indent_2, | |
| 549 | indent_3, | |
| 550 | indent_4, | |
| 551 | indent_8, | |
| 552 | indent_tab, | |
| 553 | } = .minified, | |
| 554 | ||
| 555 | /// Should optional fields with null value be written? | |
| 556 | emit_null_optional_fields: bool = true, | |
| 557 | ||
| 558 | /// Arrays/slices of u8 are typically encoded as JSON strings. | |
| 559 | /// This option emits them as arrays of numbers instead. | |
| 560 | /// Does not affect calls to `objectField*()`. | |
| 561 | emit_strings_as_arrays: bool = false, | |
| 562 | ||
| 563 | /// Should unicode characters be escaped in strings? | |
| 564 | escape_unicode: bool = false, | |
| 565 | ||
| 566 | /// When true, renders numbers outside the range `+-1<<53` (the precise integer range of f64) as JSON strings in base 10. | |
| 567 | emit_nonportable_numbers_as_strings: bool = false, | |
| 568 | }; | |
| 569 | ||
| 570 | /// Writes the given value to the `Writer` writer. | |
| 571 | /// See `Stringify` for how the given value is serialized into JSON. | |
| 572 | /// The maximum nesting depth of the output JSON document is 256. | |
| 573 | pub fn value(v: anytype, options: Options, writer: *Writer) Error!void { | |
| 574 | var s: Stringify = .{ .writer = writer, .options = options }; | |
| 575 | try s.write(v); | |
| 576 | } | |
| 577 | ||
| 578 | test value { | |
| 579 | var out: std.io.Writer.Allocating = .init(std.testing.allocator); | |
| 580 | const writer = &out.writer; | |
| 581 | defer out.deinit(); | |
| 582 | ||
| 583 | const T = struct { a: i32, b: []const u8 }; | |
| 584 | try value(T{ .a = 123, .b = "xy" }, .{}, writer); | |
| 585 | try std.testing.expectEqualSlices(u8, "{\"a\":123,\"b\":\"xy\"}", out.getWritten()); | |
| 586 | ||
| 587 | try testStringify("9999999999999999", 9999999999999999, .{}); | |
| 588 | try testStringify("\"9999999999999999\"", 9999999999999999, .{ .emit_nonportable_numbers_as_strings = true }); | |
| 589 | ||
| 590 | try testStringify("[1,1]", @as(@Vector(2, u32), @splat(1)), .{}); | |
| 591 | try testStringify("\"AA\"", @as(@Vector(2, u8), @splat('A')), .{}); | |
| 592 | try testStringify("[65,65]", @as(@Vector(2, u8), @splat('A')), .{ .emit_strings_as_arrays = true }); | |
| 593 | ||
| 594 | // void field | |
| 595 | try testStringify("{\"foo\":42}", struct { | |
| 596 | foo: u32, | |
| 597 | bar: void = {}, | |
| 598 | }{ .foo = 42 }, .{}); | |
| 599 | ||
| 600 | const Tuple = struct { []const u8, usize }; | |
| 601 | try testStringify("[\"foo\",42]", Tuple{ "foo", 42 }, .{}); | |
| 602 | ||
| 603 | comptime { | |
| 604 | testStringify("false", false, .{}) catch unreachable; | |
| 605 | const MyStruct = struct { foo: u32 }; | |
| 606 | testStringify("[{\"foo\":42},{\"foo\":100},{\"foo\":1000}]", [_]MyStruct{ | |
| 607 | MyStruct{ .foo = 42 }, | |
| 608 | MyStruct{ .foo = 100 }, | |
| 609 | MyStruct{ .foo = 1000 }, | |
| 610 | }, .{}) catch unreachable; | |
| 611 | } | |
| 612 | } | |
| 613 | ||
| 614 | /// Calls `value` and stores the result in dynamically allocated memory instead | |
| 615 | /// of taking a writer. | |
| 616 | /// | |
| 617 | /// Caller owns returned memory. | |
| 618 | pub fn valueAlloc(gpa: Allocator, v: anytype, options: Options) error{OutOfMemory}![]u8 { | |
| 619 | var aw: std.io.Writer.Allocating = .init(gpa); | |
| 620 | defer aw.deinit(); | |
| 621 | value(v, options, &aw.writer) catch return error.OutOfMemory; | |
| 622 | return aw.toOwnedSlice(); | |
| 623 | } | |
| 624 | ||
| 625 | test valueAlloc { | |
| 626 | const allocator = std.testing.allocator; | |
| 627 | const expected = | |
| 628 | \\{"foo":"bar","answer":42,"my_friend":"sammy"} | |
| 629 | ; | |
| 630 | const actual = try valueAlloc(allocator, .{ .foo = "bar", .answer = 42, .my_friend = "sammy" }, .{}); | |
| 631 | defer allocator.free(actual); | |
| 632 | ||
| 633 | try std.testing.expectEqualStrings(expected, actual); | |
| 634 | } | |
| 635 | ||
| 636 | fn outputUnicodeEscape(codepoint: u21, w: *Writer) Error!void { | |
| 637 | if (codepoint <= 0xFFFF) { | |
| 638 | // If the character is in the Basic Multilingual Plane (U+0000 through U+FFFF), | |
| 639 | // then it may be represented as a six-character sequence: a reverse solidus, followed | |
| 640 | // by the lowercase letter u, followed by four hexadecimal digits that encode the character's code point. | |
| 641 | try w.writeAll("\\u"); | |
| 642 | try w.printInt(codepoint, 16, .lower, .{ .width = 4, .fill = '0' }); | |
| 643 | } else { | |
| 644 | assert(codepoint <= 0x10FFFF); | |
| 645 | // To escape an extended character that is not in the Basic Multilingual Plane, | |
| 646 | // the character is represented as a 12-character sequence, encoding the UTF-16 surrogate pair. | |
| 647 | const high = @as(u16, @intCast((codepoint - 0x10000) >> 10)) + 0xD800; | |
| 648 | const low = @as(u16, @intCast(codepoint & 0x3FF)) + 0xDC00; | |
| 649 | try w.writeAll("\\u"); | |
| 650 | try w.printInt(high, 16, .lower, .{ .width = 4, .fill = '0' }); | |
| 651 | try w.writeAll("\\u"); | |
| 652 | try w.printInt(low, 16, .lower, .{ .width = 4, .fill = '0' }); | |
| 653 | } | |
| 654 | } | |
| 655 | ||
| 656 | fn outputSpecialEscape(c: u8, writer: *Writer) Error!void { | |
| 657 | switch (c) { | |
| 658 | '\\' => try writer.writeAll("\\\\"), | |
| 659 | '\"' => try writer.writeAll("\\\""), | |
| 660 | 0x08 => try writer.writeAll("\\b"), | |
| 661 | 0x0C => try writer.writeAll("\\f"), | |
| 662 | '\n' => try writer.writeAll("\\n"), | |
| 663 | '\r' => try writer.writeAll("\\r"), | |
| 664 | '\t' => try writer.writeAll("\\t"), | |
| 665 | else => try outputUnicodeEscape(c, writer), | |
| 666 | } | |
| 667 | } | |
| 668 | ||
| 669 | /// Write `string` to `writer` as a JSON encoded string. | |
| 670 | pub fn encodeJsonString(string: []const u8, options: Options, writer: *Writer) Error!void { | |
| 671 | try writer.writeByte('\"'); | |
| 672 | try encodeJsonStringChars(string, options, writer); | |
| 673 | try writer.writeByte('\"'); | |
| 674 | } | |
| 675 | ||
| 676 | /// Write `chars` to `writer` as JSON encoded string characters. | |
| 677 | pub fn encodeJsonStringChars(chars: []const u8, options: Options, writer: *Writer) Error!void { | |
| 678 | var write_cursor: usize = 0; | |
| 679 | var i: usize = 0; | |
| 680 | if (options.escape_unicode) { | |
| 681 | while (i < chars.len) : (i += 1) { | |
| 682 | switch (chars[i]) { | |
| 683 | // normal ascii character | |
| 684 | 0x20...0x21, 0x23...0x5B, 0x5D...0x7E => {}, | |
| 685 | 0x00...0x1F, '\\', '\"' => { | |
| 686 | // Always must escape these. | |
| 687 | try writer.writeAll(chars[write_cursor..i]); | |
| 688 | try outputSpecialEscape(chars[i], writer); | |
| 689 | write_cursor = i + 1; | |
| 690 | }, | |
| 691 | 0x7F...0xFF => { | |
| 692 | try writer.writeAll(chars[write_cursor..i]); | |
| 693 | const ulen = std.unicode.utf8ByteSequenceLength(chars[i]) catch unreachable; | |
| 694 | const codepoint = std.unicode.utf8Decode(chars[i..][0..ulen]) catch unreachable; | |
| 695 | try outputUnicodeEscape(codepoint, writer); | |
| 696 | i += ulen - 1; | |
| 697 | write_cursor = i + 1; | |
| 698 | }, | |
| 699 | } | |
| 700 | } | |
| 701 | } else { | |
| 702 | while (i < chars.len) : (i += 1) { | |
| 703 | switch (chars[i]) { | |
| 704 | // normal bytes | |
| 705 | 0x20...0x21, 0x23...0x5B, 0x5D...0xFF => {}, | |
| 706 | 0x00...0x1F, '\\', '\"' => { | |
| 707 | // Always must escape these. | |
| 708 | try writer.writeAll(chars[write_cursor..i]); | |
| 709 | try outputSpecialEscape(chars[i], writer); | |
| 710 | write_cursor = i + 1; | |
| 711 | }, | |
| 712 | } | |
| 713 | } | |
| 714 | } | |
| 715 | try writer.writeAll(chars[write_cursor..chars.len]); | |
| 716 | } | |
| 717 | ||
| 718 | test "json write stream" { | |
| 719 | var out_buf: [1024]u8 = undefined; | |
| 720 | var out: Writer = .fixed(&out_buf); | |
| 721 | var w: Stringify = .{ .writer = &out, .options = .{ .whitespace = .indent_2 } }; | |
| 722 | try testBasicWriteStream(&w); | |
| 723 | } | |
| 724 | ||
| 725 | fn testBasicWriteStream(w: *Stringify) !void { | |
| 726 | w.writer.end = 0; | |
| 727 | ||
| 728 | try w.beginObject(); | |
| 729 | ||
| 730 | try w.objectField("object"); | |
| 731 | var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator); | |
| 732 | defer arena_allocator.deinit(); | |
| 733 | try w.write(try getJsonObject(arena_allocator.allocator())); | |
| 734 | ||
| 735 | try w.objectFieldRaw("\"string\""); | |
| 736 | try w.write("This is a string"); | |
| 737 | ||
| 738 | try w.objectField("array"); | |
| 739 | try w.beginArray(); | |
| 740 | try w.write("Another string"); | |
| 741 | try w.write(@as(i32, 1)); | |
| 742 | try w.write(@as(f32, 3.5)); | |
| 743 | try w.endArray(); | |
| 744 | ||
| 745 | try w.objectField("int"); | |
| 746 | try w.write(@as(i32, 10)); | |
| 747 | ||
| 748 | try w.objectField("float"); | |
| 749 | try w.write(@as(f32, 3.5)); | |
| 750 | ||
| 751 | try w.endObject(); | |
| 752 | ||
| 753 | const expected = | |
| 754 | \\{ | |
| 755 | \\ "object": { | |
| 756 | \\ "one": 1, | |
| 757 | \\ "two": 2 | |
| 758 | \\ }, | |
| 759 | \\ "string": "This is a string", | |
| 760 | \\ "array": [ | |
| 761 | \\ "Another string", | |
| 762 | \\ 1, | |
| 763 | \\ 3.5 | |
| 764 | \\ ], | |
| 765 | \\ "int": 10, | |
| 766 | \\ "float": 3.5 | |
| 767 | \\} | |
| 768 | ; | |
| 769 | try std.testing.expectEqualStrings(expected, w.writer.buffered()); | |
| 770 | } | |
| 771 | ||
| 772 | fn getJsonObject(allocator: std.mem.Allocator) !std.json.Value { | |
| 773 | var v: std.json.Value = .{ .object = std.json.ObjectMap.init(allocator) }; | |
| 774 | try v.object.put("one", std.json.Value{ .integer = @as(i64, @intCast(1)) }); | |
| 775 | try v.object.put("two", std.json.Value{ .float = 2.0 }); | |
| 776 | return v; | |
| 777 | } | |
| 778 | ||
| 779 | test "stringify null optional fields" { | |
| 780 | const MyStruct = struct { | |
| 781 | optional: ?[]const u8 = null, | |
| 782 | required: []const u8 = "something", | |
| 783 | another_optional: ?[]const u8 = null, | |
| 784 | another_required: []const u8 = "something else", | |
| 785 | }; | |
| 786 | try testStringify( | |
| 787 | \\{"optional":null,"required":"something","another_optional":null,"another_required":"something else"} | |
| 788 | , | |
| 789 | MyStruct{}, | |
| 790 | .{}, | |
| 791 | ); | |
| 792 | try testStringify( | |
| 793 | \\{"required":"something","another_required":"something else"} | |
| 794 | , | |
| 795 | MyStruct{}, | |
| 796 | .{ .emit_null_optional_fields = false }, | |
| 797 | ); | |
| 798 | } | |
| 799 | ||
| 800 | test "stringify basic types" { | |
| 801 | try testStringify("false", false, .{}); | |
| 802 | try testStringify("true", true, .{}); | |
| 803 | try testStringify("null", @as(?u8, null), .{}); | |
| 804 | try testStringify("null", @as(?*u32, null), .{}); | |
| 805 | try testStringify("42", 42, .{}); | |
| 806 | try testStringify("42", 42.0, .{}); | |
| 807 | try testStringify("42", @as(u8, 42), .{}); | |
| 808 | try testStringify("42", @as(u128, 42), .{}); | |
| 809 | try testStringify("9999999999999999", 9999999999999999, .{}); | |
| 810 | try testStringify("42", @as(f32, 42), .{}); | |
| 811 | try testStringify("42", @as(f64, 42), .{}); | |
| 812 | try testStringify("\"ItBroke\"", @as(anyerror, error.ItBroke), .{}); | |
| 813 | try testStringify("\"ItBroke\"", error.ItBroke, .{}); | |
| 814 | } | |
| 815 | ||
| 816 | test "stringify string" { | |
| 817 | try testStringify("\"hello\"", "hello", .{}); | |
| 818 | try testStringify("\"with\\nescapes\\r\"", "with\nescapes\r", .{}); | |
| 819 | try testStringify("\"with\\nescapes\\r\"", "with\nescapes\r", .{ .escape_unicode = true }); | |
| 820 | try testStringify("\"with unicode\\u0001\"", "with unicode\u{1}", .{}); | |
| 821 | try testStringify("\"with unicode\\u0001\"", "with unicode\u{1}", .{ .escape_unicode = true }); | |
| 822 | try testStringify("\"with unicode\u{80}\"", "with unicode\u{80}", .{}); | |
| 823 | try testStringify("\"with unicode\\u0080\"", "with unicode\u{80}", .{ .escape_unicode = true }); | |
| 824 | try testStringify("\"with unicode\u{FF}\"", "with unicode\u{FF}", .{}); | |
| 825 | try testStringify("\"with unicode\\u00ff\"", "with unicode\u{FF}", .{ .escape_unicode = true }); | |
| 826 | try testStringify("\"with unicode\u{100}\"", "with unicode\u{100}", .{}); | |
| 827 | try testStringify("\"with unicode\\u0100\"", "with unicode\u{100}", .{ .escape_unicode = true }); | |
| 828 | try testStringify("\"with unicode\u{800}\"", "with unicode\u{800}", .{}); | |
| 829 | try testStringify("\"with unicode\\u0800\"", "with unicode\u{800}", .{ .escape_unicode = true }); | |
| 830 | try testStringify("\"with unicode\u{8000}\"", "with unicode\u{8000}", .{}); | |
| 831 | try testStringify("\"with unicode\\u8000\"", "with unicode\u{8000}", .{ .escape_unicode = true }); | |
| 832 | try testStringify("\"with unicode\u{D799}\"", "with unicode\u{D799}", .{}); | |
| 833 | try testStringify("\"with unicode\\ud799\"", "with unicode\u{D799}", .{ .escape_unicode = true }); | |
| 834 | try testStringify("\"with unicode\u{10000}\"", "with unicode\u{10000}", .{}); | |
| 835 | try testStringify("\"with unicode\\ud800\\udc00\"", "with unicode\u{10000}", .{ .escape_unicode = true }); | |
| 836 | try testStringify("\"with unicode\u{10FFFF}\"", "with unicode\u{10FFFF}", .{}); | |
| 837 | try testStringify("\"with unicode\\udbff\\udfff\"", "with unicode\u{10FFFF}", .{ .escape_unicode = true }); | |
| 838 | } | |
| 839 | ||
| 840 | test "stringify many-item sentinel-terminated string" { | |
| 841 | try testStringify("\"hello\"", @as([*:0]const u8, "hello"), .{}); | |
| 842 | try testStringify("\"with\\nescapes\\r\"", @as([*:0]const u8, "with\nescapes\r"), .{ .escape_unicode = true }); | |
| 843 | try testStringify("\"with unicode\\u0001\"", @as([*:0]const u8, "with unicode\u{1}"), .{ .escape_unicode = true }); | |
| 844 | } | |
| 845 | ||
| 846 | test "stringify enums" { | |
| 847 | const E = enum { | |
| 848 | foo, | |
| 849 | bar, | |
| 850 | }; | |
| 851 | try testStringify("\"foo\"", E.foo, .{}); | |
| 852 | try testStringify("\"bar\"", E.bar, .{}); | |
| 853 | } | |
| 854 | ||
| 855 | test "stringify non-exhaustive enum" { | |
| 856 | const E = enum(u8) { | |
| 857 | foo = 0, | |
| 858 | _, | |
| 859 | }; | |
| 860 | try testStringify("\"foo\"", E.foo, .{}); | |
| 861 | try testStringify("1", @as(E, @enumFromInt(1)), .{}); | |
| 862 | } | |
| 863 | ||
| 864 | test "stringify enum literals" { | |
| 865 | try testStringify("\"foo\"", .foo, .{}); | |
| 866 | try testStringify("\"bar\"", .bar, .{}); | |
| 867 | } | |
| 868 | ||
| 869 | test "stringify tagged unions" { | |
| 870 | const T = union(enum) { | |
| 871 | nothing, | |
| 872 | foo: u32, | |
| 873 | bar: bool, | |
| 874 | }; | |
| 875 | try testStringify("{\"nothing\":{}}", T{ .nothing = {} }, .{}); | |
| 876 | try testStringify("{\"foo\":42}", T{ .foo = 42 }, .{}); | |
| 877 | try testStringify("{\"bar\":true}", T{ .bar = true }, .{}); | |
| 878 | } | |
| 879 | ||
| 880 | test "stringify struct" { | |
| 881 | try testStringify("{\"foo\":42}", struct { | |
| 882 | foo: u32, | |
| 883 | }{ .foo = 42 }, .{}); | |
| 884 | } | |
| 885 | ||
| 886 | test "emit_strings_as_arrays" { | |
| 887 | // Should only affect string values, not object keys. | |
| 888 | try testStringify("{\"foo\":\"bar\"}", .{ .foo = "bar" }, .{}); | |
| 889 | try testStringify("{\"foo\":[98,97,114]}", .{ .foo = "bar" }, .{ .emit_strings_as_arrays = true }); | |
| 890 | // Should *not* affect these types: | |
| 891 | try testStringify("\"foo\"", @as(enum { foo, bar }, .foo), .{ .emit_strings_as_arrays = true }); | |
| 892 | try testStringify("\"ItBroke\"", error.ItBroke, .{ .emit_strings_as_arrays = true }); | |
| 893 | // Should work on these: | |
| 894 | try testStringify("\"bar\"", @Vector(3, u8){ 'b', 'a', 'r' }, .{}); | |
| 895 | try testStringify("[98,97,114]", @Vector(3, u8){ 'b', 'a', 'r' }, .{ .emit_strings_as_arrays = true }); | |
| 896 | try testStringify("\"bar\"", [3]u8{ 'b', 'a', 'r' }, .{}); | |
| 897 | try testStringify("[98,97,114]", [3]u8{ 'b', 'a', 'r' }, .{ .emit_strings_as_arrays = true }); | |
| 898 | } | |
| 899 | ||
| 900 | test "stringify struct with indentation" { | |
| 901 | try testStringify( | |
| 902 | \\{ | |
| 903 | \\ "foo": 42, | |
| 904 | \\ "bar": [ | |
| 905 | \\ 1, | |
| 906 | \\ 2, | |
| 907 | \\ 3 | |
| 908 | \\ ] | |
| 909 | \\} | |
| 910 | , | |
| 911 | struct { | |
| 912 | foo: u32, | |
| 913 | bar: [3]u32, | |
| 914 | }{ | |
| 915 | .foo = 42, | |
| 916 | .bar = .{ 1, 2, 3 }, | |
| 917 | }, | |
| 918 | .{ .whitespace = .indent_4 }, | |
| 919 | ); | |
| 920 | try testStringify( | |
| 921 | "{\n\t\"foo\": 42,\n\t\"bar\": [\n\t\t1,\n\t\t2,\n\t\t3\n\t]\n}", | |
| 922 | struct { | |
| 923 | foo: u32, | |
| 924 | bar: [3]u32, | |
| 925 | }{ | |
| 926 | .foo = 42, | |
| 927 | .bar = .{ 1, 2, 3 }, | |
| 928 | }, | |
| 929 | .{ .whitespace = .indent_tab }, | |
| 930 | ); | |
| 931 | try testStringify( | |
| 932 | \\{"foo":42,"bar":[1,2,3]} | |
| 933 | , | |
| 934 | struct { | |
| 935 | foo: u32, | |
| 936 | bar: [3]u32, | |
| 937 | }{ | |
| 938 | .foo = 42, | |
| 939 | .bar = .{ 1, 2, 3 }, | |
| 940 | }, | |
| 941 | .{ .whitespace = .minified }, | |
| 942 | ); | |
| 943 | } | |
| 944 | ||
| 945 | test "stringify array of structs" { | |
| 946 | const MyStruct = struct { | |
| 947 | foo: u32, | |
| 948 | }; | |
| 949 | try testStringify("[{\"foo\":42},{\"foo\":100},{\"foo\":1000}]", [_]MyStruct{ | |
| 950 | MyStruct{ .foo = 42 }, | |
| 951 | MyStruct{ .foo = 100 }, | |
| 952 | MyStruct{ .foo = 1000 }, | |
| 953 | }, .{}); | |
| 954 | } | |
| 955 | ||
| 956 | test "stringify struct with custom stringifier" { | |
| 957 | try testStringify("[\"something special\",42]", struct { | |
| 958 | foo: u32, | |
| 959 | const Self = @This(); | |
| 960 | pub fn jsonStringify(v: @This(), jws: anytype) !void { | |
| 961 | _ = v; | |
| 962 | try jws.beginArray(); | |
| 963 | try jws.write("something special"); | |
| 964 | try jws.write(42); | |
| 965 | try jws.endArray(); | |
| 966 | } | |
| 967 | }{ .foo = 42 }, .{}); | |
| 968 | } | |
| 969 | ||
| 970 | fn testStringify(expected: []const u8, v: anytype, options: Options) !void { | |
| 971 | var buffer: [4096]u8 = undefined; | |
| 972 | var w: Writer = .fixed(&buffer); | |
| 973 | try value(v, options, &w); | |
| 974 | try std.testing.expectEqualStrings(expected, w.buffered()); | |
| 975 | } | |
| 976 | ||
| 977 | test "raw streaming" { | |
| 978 | var out_buf: [1024]u8 = undefined; | |
| 979 | var out: Writer = .fixed(&out_buf); | |
| 980 | ||
| 981 | var w: Stringify = .{ .writer = &out, .options = .{ .whitespace = .indent_2 } }; | |
| 982 | try w.beginObject(); | |
| 983 | try w.beginObjectFieldRaw(); | |
| 984 | try w.writer.writeAll("\"long"); | |
| 985 | try w.writer.writeAll(" key\""); | |
| 986 | w.endObjectFieldRaw(); | |
| 987 | try w.beginWriteRaw(); | |
| 988 | try w.writer.writeAll("\"long"); | |
| 989 | try w.writer.writeAll(" value\""); | |
| 990 | w.endWriteRaw(); | |
| 991 | try w.endObject(); | |
| 992 | ||
| 993 | const expected = | |
| 994 | \\{ | |
| 995 | \\ "long key": "long value" | |
| 996 | \\} | |
| 997 | ; | |
| 998 | try std.testing.expectEqualStrings(expected, w.writer.buffered()); | |
| 999 | } |
lib/std/json/dynamic.zig+6-12| ... | ... | @@ -4,17 +4,12 @@ const ArenaAllocator = std.heap.ArenaAllocator; |
| 4 | 4 | const ArrayList = std.ArrayList; |
| 5 | 5 | const StringArrayHashMap = std.StringArrayHashMap; |
| 6 | 6 | const Allocator = std.mem.Allocator; |
| 7 | ||
| 8 | const StringifyOptions = @import("./stringify.zig").StringifyOptions; | |
| 9 | const stringify = @import("./stringify.zig").stringify; | |
| 7 | const json = std.json; | |
| 10 | 8 | |
| 11 | 9 | const ParseOptions = @import("./static.zig").ParseOptions; |
| 12 | 10 | const ParseError = @import("./static.zig").ParseError; |
| 13 | 11 | |
| 14 | const JsonScanner = @import("./scanner.zig").Scanner; | |
| 15 | const AllocWhen = @import("./scanner.zig").AllocWhen; | |
| 16 | const Token = @import("./scanner.zig").Token; | |
| 17 | const isNumberFormattedLikeAnInteger = @import("./scanner.zig").isNumberFormattedLikeAnInteger; | |
| 12 | const isNumberFormattedLikeAnInteger = @import("Scanner.zig").isNumberFormattedLikeAnInteger; | |
| 18 | 13 | |
| 19 | 14 | pub const ObjectMap = StringArrayHashMap(Value); |
| 20 | 15 | pub const Array = ArrayList(Value); |
| ... | ... | @@ -52,12 +47,11 @@ pub const Value = union(enum) { |
| 52 | 47 | } |
| 53 | 48 | } |
| 54 | 49 | |
| 55 | pub fn dump(self: Value) void { | |
| 56 | std.debug.lockStdErr(); | |
| 57 | defer std.debug.unlockStdErr(); | |
| 50 | pub fn dump(v: Value) void { | |
| 51 | const w = std.debug.lockStderrWriter(&.{}); | |
| 52 | defer std.debug.unlockStderrWriter(); | |
| 58 | 53 | |
| 59 | const stderr = std.fs.File.stderr().deprecatedWriter(); | |
| 60 | stringify(self, .{}, stderr) catch return; | |
| 54 | json.Stringify.value(v, .{}, w) catch return; | |
| 61 | 55 | } |
| 62 | 56 | |
| 63 | 57 | pub fn jsonStringify(value: @This(), jws: anytype) !void { |
lib/std/json/dynamic_test.zig+18-22| ... | ... | @@ -1,8 +1,10 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const json = std.json; | |
| 2 | 3 | const mem = std.mem; |
| 3 | 4 | const testing = std.testing; |
| 4 | 5 | const ArenaAllocator = std.heap.ArenaAllocator; |
| 5 | 6 | const Allocator = std.mem.Allocator; |
| 7 | const Writer = std.io.Writer; | |
| 6 | 8 | |
| 7 | 9 | const ObjectMap = @import("dynamic.zig").ObjectMap; |
| 8 | 10 | const Array = @import("dynamic.zig").Array; |
| ... | ... | @@ -14,8 +16,7 @@ const parseFromTokenSource = @import("static.zig").parseFromTokenSource; |
| 14 | 16 | const parseFromValueLeaky = @import("static.zig").parseFromValueLeaky; |
| 15 | 17 | const ParseOptions = @import("static.zig").ParseOptions; |
| 16 | 18 | |
| 17 | const jsonReader = @import("scanner.zig").reader; | |
| 18 | const JsonReader = @import("scanner.zig").Reader; | |
| 19 | const Scanner = @import("Scanner.zig"); | |
| 19 | 20 | |
| 20 | 21 | test "json.parser.dynamic" { |
| 21 | 22 | const s = |
| ... | ... | @@ -70,14 +71,10 @@ test "json.parser.dynamic" { |
| 70 | 71 | try testing.expect(mem.eql(u8, large_int.number_string, "18446744073709551615")); |
| 71 | 72 | } |
| 72 | 73 | |
| 73 | const writeStream = @import("./stringify.zig").writeStream; | |
| 74 | 74 | test "write json then parse it" { |
| 75 | 75 | var out_buffer: [1000]u8 = undefined; |
| 76 | ||
| 77 | var fixed_buffer_stream = std.io.fixedBufferStream(&out_buffer); | |
| 78 | const out_stream = fixed_buffer_stream.writer(); | |
| 79 | var jw = writeStream(out_stream, .{}); | |
| 80 | defer jw.deinit(); | |
| 76 | var fixed_writer: Writer = .fixed(&out_buffer); | |
| 77 | var jw: json.Stringify = .{ .writer = &fixed_writer, .options = .{} }; | |
| 81 | 78 | |
| 82 | 79 | try jw.beginObject(); |
| 83 | 80 | |
| ... | ... | @@ -101,8 +98,8 @@ test "write json then parse it" { |
| 101 | 98 | |
| 102 | 99 | try jw.endObject(); |
| 103 | 100 | |
| 104 | fixed_buffer_stream = std.io.fixedBufferStream(fixed_buffer_stream.getWritten()); | |
| 105 | var json_reader = jsonReader(testing.allocator, fixed_buffer_stream.reader()); | |
| 101 | var fbs: std.Io.Reader = .fixed(fixed_writer.buffered()); | |
| 102 | var json_reader: Scanner.Reader = .init(testing.allocator, &fbs); | |
| 106 | 103 | defer json_reader.deinit(); |
| 107 | 104 | var parsed = try parseFromTokenSource(Value, testing.allocator, &json_reader, .{}); |
| 108 | 105 | defer parsed.deinit(); |
| ... | ... | @@ -242,10 +239,9 @@ test "Value.jsonStringify" { |
| 242 | 239 | .{ .object = obj }, |
| 243 | 240 | }; |
| 244 | 241 | var buffer: [0x1000]u8 = undefined; |
| 245 | var fbs = std.io.fixedBufferStream(&buffer); | |
| 242 | var fixed_writer: Writer = .fixed(&buffer); | |
| 246 | 243 | |
| 247 | var jw = writeStream(fbs.writer(), .{ .whitespace = .indent_1 }); | |
| 248 | defer jw.deinit(); | |
| 244 | var jw: json.Stringify = .{ .writer = &fixed_writer, .options = .{ .whitespace = .indent_1 } }; | |
| 249 | 245 | try jw.write(array); |
| 250 | 246 | |
| 251 | 247 | const expected = |
| ... | ... | @@ -266,7 +262,7 @@ test "Value.jsonStringify" { |
| 266 | 262 | \\ } |
| 267 | 263 | \\] |
| 268 | 264 | ; |
| 269 | try testing.expectEqualStrings(expected, fbs.getWritten()); | |
| 265 | try testing.expectEqualStrings(expected, fixed_writer.buffered()); | |
| 270 | 266 | } |
| 271 | 267 | |
| 272 | 268 | test "parseFromValue(std.json.Value,...)" { |
| ... | ... | @@ -334,8 +330,8 @@ test "polymorphic parsing" { |
| 334 | 330 | test "long object value" { |
| 335 | 331 | const value = "01234567890123456789"; |
| 336 | 332 | const doc = "{\"key\":\"" ++ value ++ "\"}"; |
| 337 | var fbs = std.io.fixedBufferStream(doc); | |
| 338 | var reader = smallBufferJsonReader(testing.allocator, fbs.reader()); | |
| 333 | var fbs: std.Io.Reader = .fixed(doc); | |
| 334 | var reader = smallBufferJsonReader(testing.allocator, &fbs); | |
| 339 | 335 | defer reader.deinit(); |
| 340 | 336 | var parsed = try parseFromTokenSource(Value, testing.allocator, &reader, .{}); |
| 341 | 337 | defer parsed.deinit(); |
| ... | ... | @@ -367,8 +363,8 @@ test "many object keys" { |
| 367 | 363 | \\ "k5": "v5" |
| 368 | 364 | \\} |
| 369 | 365 | ; |
| 370 | var fbs = std.io.fixedBufferStream(doc); | |
| 371 | var reader = smallBufferJsonReader(testing.allocator, fbs.reader()); | |
| 366 | var fbs: std.Io.Reader = .fixed(doc); | |
| 367 | var reader = smallBufferJsonReader(testing.allocator, &fbs); | |
| 372 | 368 | defer reader.deinit(); |
| 373 | 369 | var parsed = try parseFromTokenSource(Value, testing.allocator, &reader, .{}); |
| 374 | 370 | defer parsed.deinit(); |
| ... | ... | @@ -382,8 +378,8 @@ test "many object keys" { |
| 382 | 378 | |
| 383 | 379 | test "negative zero" { |
| 384 | 380 | const doc = "-0"; |
| 385 | var fbs = std.io.fixedBufferStream(doc); | |
| 386 | var reader = smallBufferJsonReader(testing.allocator, fbs.reader()); | |
| 381 | var fbs: std.Io.Reader = .fixed(doc); | |
| 382 | var reader = smallBufferJsonReader(testing.allocator, &fbs); | |
| 387 | 383 | defer reader.deinit(); |
| 388 | 384 | var parsed = try parseFromTokenSource(Value, testing.allocator, &reader, .{}); |
| 389 | 385 | defer parsed.deinit(); |
| ... | ... | @@ -391,6 +387,6 @@ test "negative zero" { |
| 391 | 387 | try testing.expect(std.math.isNegativeZero(parsed.value.float)); |
| 392 | 388 | } |
| 393 | 389 | |
| 394 | fn smallBufferJsonReader(allocator: Allocator, io_reader: anytype) JsonReader(16, @TypeOf(io_reader)) { | |
| 395 | return JsonReader(16, @TypeOf(io_reader)).init(allocator, io_reader); | |
| 390 | fn smallBufferJsonReader(allocator: Allocator, io_reader: anytype) Scanner.Reader { | |
| 391 | return .init(allocator, io_reader); | |
| 396 | 392 | } |
lib/std/json/fmt.zig deleted-40| ... | ... | @@ -1,40 +0,0 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const assert = std.debug.assert; | |
| 3 | ||
| 4 | const stringify = @import("stringify.zig").stringify; | |
| 5 | const StringifyOptions = @import("stringify.zig").StringifyOptions; | |
| 6 | ||
| 7 | /// Returns a formatter that formats the given value using stringify. | |
| 8 | pub fn fmt(value: anytype, options: StringifyOptions) Formatter(@TypeOf(value)) { | |
| 9 | return Formatter(@TypeOf(value)){ .value = value, .options = options }; | |
| 10 | } | |
| 11 | ||
| 12 | /// Formats the given value using stringify. | |
| 13 | pub fn Formatter(comptime T: type) type { | |
| 14 | return struct { | |
| 15 | value: T, | |
| 16 | options: StringifyOptions, | |
| 17 | ||
| 18 | pub fn format(self: @This(), writer: *std.io.Writer) std.io.Writer.Error!void { | |
| 19 | try stringify(self.value, self.options, writer); | |
| 20 | } | |
| 21 | }; | |
| 22 | } | |
| 23 | ||
| 24 | test fmt { | |
| 25 | const expectFmt = std.testing.expectFmt; | |
| 26 | try expectFmt("123", "{}", .{fmt(@as(u32, 123), .{})}); | |
| 27 | try expectFmt( | |
| 28 | \\{"num":927,"msg":"hello","sub":{"mybool":true}} | |
| 29 | , "{}", .{fmt(struct { | |
| 30 | num: u32, | |
| 31 | msg: []const u8, | |
| 32 | sub: struct { | |
| 33 | mybool: bool, | |
| 34 | }, | |
| 35 | }{ | |
| 36 | .num = 927, | |
| 37 | .msg = "hello", | |
| 38 | .sub = .{ .mybool = true }, | |
| 39 | }, .{})}); | |
| 40 | } |
lib/std/json/hashmap_test.zig+9-9| ... | ... | @@ -1,4 +1,5 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const json = std.json; | |
| 2 | 3 | const testing = std.testing; |
| 3 | 4 | |
| 4 | 5 | const ArrayHashMap = @import("hashmap.zig").ArrayHashMap; |
| ... | ... | @@ -7,10 +8,9 @@ const parseFromSlice = @import("static.zig").parseFromSlice; |
| 7 | 8 | const parseFromSliceLeaky = @import("static.zig").parseFromSliceLeaky; |
| 8 | 9 | const parseFromTokenSource = @import("static.zig").parseFromTokenSource; |
| 9 | 10 | const parseFromValue = @import("static.zig").parseFromValue; |
| 10 | const stringifyAlloc = @import("stringify.zig").stringifyAlloc; | |
| 11 | 11 | const Value = @import("dynamic.zig").Value; |
| 12 | 12 | |
| 13 | const jsonReader = @import("./scanner.zig").reader; | |
| 13 | const Scanner = @import("Scanner.zig"); | |
| 14 | 14 | |
| 15 | 15 | const T = struct { |
| 16 | 16 | i: i32, |
| ... | ... | @@ -39,8 +39,8 @@ test "parse json hashmap while streaming" { |
| 39 | 39 | \\ "xyz": {"i": 1, "s": "w"} |
| 40 | 40 | \\} |
| 41 | 41 | ; |
| 42 | var stream = std.io.fixedBufferStream(doc); | |
| 43 | var json_reader = jsonReader(testing.allocator, stream.reader()); | |
| 42 | var stream: std.Io.Reader = .fixed(doc); | |
| 43 | var json_reader: Scanner.Reader = .init(testing.allocator, &stream); | |
| 44 | 44 | |
| 45 | 45 | var parsed = try parseFromTokenSource( |
| 46 | 46 | ArrayHashMap(T), |
| ... | ... | @@ -89,7 +89,7 @@ test "stringify json hashmap" { |
| 89 | 89 | var value = ArrayHashMap(T){}; |
| 90 | 90 | defer value.deinit(testing.allocator); |
| 91 | 91 | { |
| 92 | const doc = try stringifyAlloc(testing.allocator, value, .{}); | |
| 92 | const doc = try json.Stringify.valueAlloc(testing.allocator, value, .{}); | |
| 93 | 93 | defer testing.allocator.free(doc); |
| 94 | 94 | try testing.expectEqualStrings("{}", doc); |
| 95 | 95 | } |
| ... | ... | @@ -98,7 +98,7 @@ test "stringify json hashmap" { |
| 98 | 98 | try value.map.put(testing.allocator, "xyz", .{ .i = 1, .s = "w" }); |
| 99 | 99 | |
| 100 | 100 | { |
| 101 | const doc = try stringifyAlloc(testing.allocator, value, .{}); | |
| 101 | const doc = try json.Stringify.valueAlloc(testing.allocator, value, .{}); | |
| 102 | 102 | defer testing.allocator.free(doc); |
| 103 | 103 | try testing.expectEqualStrings( |
| 104 | 104 | \\{"abc":{"i":0,"s":"d"},"xyz":{"i":1,"s":"w"}} |
| ... | ... | @@ -107,7 +107,7 @@ test "stringify json hashmap" { |
| 107 | 107 | |
| 108 | 108 | try testing.expect(value.map.swapRemove("abc")); |
| 109 | 109 | { |
| 110 | const doc = try stringifyAlloc(testing.allocator, value, .{}); | |
| 110 | const doc = try json.Stringify.valueAlloc(testing.allocator, value, .{}); | |
| 111 | 111 | defer testing.allocator.free(doc); |
| 112 | 112 | try testing.expectEqualStrings( |
| 113 | 113 | \\{"xyz":{"i":1,"s":"w"}} |
| ... | ... | @@ -116,7 +116,7 @@ test "stringify json hashmap" { |
| 116 | 116 | |
| 117 | 117 | try testing.expect(value.map.swapRemove("xyz")); |
| 118 | 118 | { |
| 119 | const doc = try stringifyAlloc(testing.allocator, value, .{}); | |
| 119 | const doc = try json.Stringify.valueAlloc(testing.allocator, value, .{}); | |
| 120 | 120 | defer testing.allocator.free(doc); |
| 121 | 121 | try testing.expectEqualStrings("{}", doc); |
| 122 | 122 | } |
| ... | ... | @@ -129,7 +129,7 @@ test "stringify json hashmap whitespace" { |
| 129 | 129 | try value.map.put(testing.allocator, "xyz", .{ .i = 1, .s = "w" }); |
| 130 | 130 | |
| 131 | 131 | { |
| 132 | const doc = try stringifyAlloc(testing.allocator, value, .{ .whitespace = .indent_2 }); | |
| 132 | const doc = try json.Stringify.valueAlloc(testing.allocator, value, .{ .whitespace = .indent_2 }); | |
| 133 | 133 | defer testing.allocator.free(doc); |
| 134 | 134 | try testing.expectEqualStrings( |
| 135 | 135 | \\{ |
lib/std/json/scanner.zig deleted-1776| ... | ... | @@ -1,1776 +0,0 @@ |
| 1 | // Notes on standards compliance: https://datatracker.ietf.org/doc/html/rfc8259 | |
| 2 | // * RFC 8259 requires JSON documents be valid UTF-8, | |
| 3 | // but makes an allowance for systems that are "part of a closed ecosystem". | |
| 4 | // I have no idea what that's supposed to mean in the context of a standard specification. | |
| 5 | // This implementation requires inputs to be valid UTF-8. | |
| 6 | // * RFC 8259 contradicts itself regarding whether lowercase is allowed in \u hex digits, | |
| 7 | // but this is probably a bug in the spec, and it's clear that lowercase is meant to be allowed. | |
| 8 | // (RFC 5234 defines HEXDIG to only allow uppercase.) | |
| 9 | // * When RFC 8259 refers to a "character", I assume they really mean a "Unicode scalar value". | |
| 10 | // See http://www.unicode.org/glossary/#unicode_scalar_value . | |
| 11 | // * RFC 8259 doesn't explicitly disallow unpaired surrogate halves in \u escape sequences, | |
| 12 | // but vaguely implies that \u escapes are for encoding Unicode "characters" (i.e. Unicode scalar values?), | |
| 13 | // which would mean that unpaired surrogate halves are forbidden. | |
| 14 | // By contrast ECMA-404 (a competing(/compatible?) JSON standard, which JavaScript's JSON.parse() conforms to) | |
| 15 | // explicitly allows unpaired surrogate halves. | |
| 16 | // This implementation forbids unpaired surrogate halves in \u sequences. | |
| 17 | // If a high surrogate half appears in a \u sequence, | |
| 18 | // then a low surrogate half must immediately follow in \u notation. | |
| 19 | // * RFC 8259 allows implementations to "accept non-JSON forms or extensions". | |
| 20 | // This implementation does not accept any of that. | |
| 21 | // * RFC 8259 allows implementations to put limits on "the size of texts", | |
| 22 | // "the maximum depth of nesting", "the range and precision of numbers", | |
| 23 | // and "the length and character contents of strings". | |
| 24 | // This low-level implementation does not limit these, | |
| 25 | // except where noted above, and except that nesting depth requires memory allocation. | |
| 26 | // Note that this low-level API does not interpret numbers numerically, | |
| 27 | // but simply emits their source form for some higher level code to make sense of. | |
| 28 | // * This low-level implementation allows duplicate object keys, | |
| 29 | // and key/value pairs are emitted in the order they appear in the input. | |
| 30 | ||
| 31 | const std = @import("std"); | |
| 32 | ||
| 33 | const Allocator = std.mem.Allocator; | |
| 34 | const ArrayList = std.ArrayList; | |
| 35 | const assert = std.debug.assert; | |
| 36 | const BitStack = std.BitStack; | |
| 37 | ||
| 38 | /// Scan the input and check for malformed JSON. | |
| 39 | /// On `SyntaxError` or `UnexpectedEndOfInput`, returns `false`. | |
| 40 | /// Returns any errors from the allocator as-is, which is unlikely, | |
| 41 | /// but can be caused by extreme nesting depth in the input. | |
| 42 | pub fn validate(allocator: Allocator, s: []const u8) Allocator.Error!bool { | |
| 43 | var scanner = Scanner.initCompleteInput(allocator, s); | |
| 44 | defer scanner.deinit(); | |
| 45 | ||
| 46 | while (true) { | |
| 47 | const token = scanner.next() catch |err| switch (err) { | |
| 48 | error.SyntaxError, error.UnexpectedEndOfInput => return false, | |
| 49 | error.OutOfMemory => return error.OutOfMemory, | |
| 50 | error.BufferUnderrun => unreachable, | |
| 51 | }; | |
| 52 | if (token == .end_of_document) break; | |
| 53 | } | |
| 54 | ||
| 55 | return true; | |
| 56 | } | |
| 57 | ||
| 58 | /// The parsing errors are divided into two categories: | |
| 59 | /// * `SyntaxError` is for clearly malformed JSON documents, | |
| 60 | /// such as giving an input document that isn't JSON at all. | |
| 61 | /// * `UnexpectedEndOfInput` is for signaling that everything's been | |
| 62 | /// valid so far, but the input appears to be truncated for some reason. | |
| 63 | /// Note that a completely empty (or whitespace-only) input will give `UnexpectedEndOfInput`. | |
| 64 | pub const Error = error{ SyntaxError, UnexpectedEndOfInput }; | |
| 65 | ||
| 66 | /// Calls `std.json.Reader` with `std.json.default_buffer_size`. | |
| 67 | pub fn reader(allocator: Allocator, io_reader: anytype) Reader(default_buffer_size, @TypeOf(io_reader)) { | |
| 68 | return Reader(default_buffer_size, @TypeOf(io_reader)).init(allocator, io_reader); | |
| 69 | } | |
| 70 | /// Used by `json.reader`. | |
| 71 | pub const default_buffer_size = 0x1000; | |
| 72 | ||
| 73 | /// The tokens emitted by `std.json.Scanner` and `std.json.Reader` `.next*()` functions follow this grammar: | |
| 74 | /// ``` | |
| 75 | /// <document> = <value> .end_of_document | |
| 76 | /// <value> = | |
| 77 | /// | <object> | |
| 78 | /// | <array> | |
| 79 | /// | <number> | |
| 80 | /// | <string> | |
| 81 | /// | .true | |
| 82 | /// | .false | |
| 83 | /// | .null | |
| 84 | /// <object> = .object_begin ( <string> <value> )* .object_end | |
| 85 | /// <array> = .array_begin ( <value> )* .array_end | |
| 86 | /// <number> = <It depends. See below.> | |
| 87 | /// <string> = <It depends. See below.> | |
| 88 | /// ``` | |
| 89 | /// | |
| 90 | /// What you get for `<number>` and `<string>` values depends on which `next*()` method you call: | |
| 91 | /// | |
| 92 | /// ``` | |
| 93 | /// next(): | |
| 94 | /// <number> = ( .partial_number )* .number | |
| 95 | /// <string> = ( <partial_string> )* .string | |
| 96 | /// <partial_string> = | |
| 97 | /// | .partial_string | |
| 98 | /// | .partial_string_escaped_1 | |
| 99 | /// | .partial_string_escaped_2 | |
| 100 | /// | .partial_string_escaped_3 | |
| 101 | /// | .partial_string_escaped_4 | |
| 102 | /// | |
| 103 | /// nextAlloc*(..., .alloc_always): | |
| 104 | /// <number> = .allocated_number | |
| 105 | /// <string> = .allocated_string | |
| 106 | /// | |
| 107 | /// nextAlloc*(..., .alloc_if_needed): | |
| 108 | /// <number> = | |
| 109 | /// | .number | |
| 110 | /// | .allocated_number | |
| 111 | /// <string> = | |
| 112 | /// | .string | |
| 113 | /// | .allocated_string | |
| 114 | /// ``` | |
| 115 | /// | |
| 116 | /// For all tokens with a `[]const u8`, `[]u8`, or `[n]u8` payload, the payload represents the content of the value. | |
| 117 | /// For number values, this is the representation of the number exactly as it appears in the input. | |
| 118 | /// For strings, this is the content of the string after resolving escape sequences. | |
| 119 | /// | |
| 120 | /// For `.allocated_number` and `.allocated_string`, the `[]u8` payloads are allocations made with the given allocator. | |
| 121 | /// You are responsible for managing that memory. `json.Reader.deinit()` does *not* free those allocations. | |
| 122 | /// | |
| 123 | /// The `.partial_*` tokens indicate that a value spans multiple input buffers or that a string contains escape sequences. | |
| 124 | /// To get a complete value in memory, you need to concatenate the values yourself. | |
| 125 | /// Calling `nextAlloc*()` does this for you, and returns an `.allocated_*` token with the result. | |
| 126 | /// | |
| 127 | /// For tokens with a `[]const u8` payload, the payload is a slice into the current input buffer. | |
| 128 | /// The memory may become undefined during the next call to `json.Scanner.feedInput()` | |
| 129 | /// or any `json.Reader` method whose return error set includes `json.Error`. | |
| 130 | /// To keep the value persistently, it recommended to make a copy or to use `.alloc_always`, | |
| 131 | /// which makes a copy for you. | |
| 132 | /// | |
| 133 | /// Note that `.number` and `.string` tokens that follow `.partial_*` tokens may have `0` length to indicate that | |
| 134 | /// the previously partial value is completed with no additional bytes. | |
| 135 | /// (This can happen when the break between input buffers happens to land on the exact end of a value. E.g. `"[1234"`, `"]"`.) | |
| 136 | /// `.partial_*` tokens never have `0` length. | |
| 137 | /// | |
| 138 | /// The recommended strategy for using the different `next*()` methods is something like this: | |
| 139 | /// | |
| 140 | /// When you're expecting an object key, use `.alloc_if_needed`. | |
| 141 | /// You often don't need a copy of the key string to persist; you might just check which field it is. | |
| 142 | /// In the case that the key happens to require an allocation, free it immediately after checking it. | |
| 143 | /// | |
| 144 | /// When you're expecting a meaningful string value (such as on the right of a `:`), | |
| 145 | /// use `.alloc_always` in order to keep the value valid throughout parsing the rest of the document. | |
| 146 | /// | |
| 147 | /// When you're expecting a number value, use `.alloc_if_needed`. | |
| 148 | /// You're probably going to be parsing the string representation of the number into a numeric representation, | |
| 149 | /// so you need the complete string representation only temporarily. | |
| 150 | /// | |
| 151 | /// When you're skipping an unrecognized value, use `skipValue()`. | |
| 152 | pub const Token = union(enum) { | |
| 153 | object_begin, | |
| 154 | object_end, | |
| 155 | array_begin, | |
| 156 | array_end, | |
| 157 | ||
| 158 | true, | |
| 159 | false, | |
| 160 | null, | |
| 161 | ||
| 162 | number: []const u8, | |
| 163 | partial_number: []const u8, | |
| 164 | allocated_number: []u8, | |
| 165 | ||
| 166 | string: []const u8, | |
| 167 | partial_string: []const u8, | |
| 168 | partial_string_escaped_1: [1]u8, | |
| 169 | partial_string_escaped_2: [2]u8, | |
| 170 | partial_string_escaped_3: [3]u8, | |
| 171 | partial_string_escaped_4: [4]u8, | |
| 172 | allocated_string: []u8, | |
| 173 | ||
| 174 | end_of_document, | |
| 175 | }; | |
| 176 | ||
| 177 | /// This is only used in `peekNextTokenType()` and gives a categorization based on the first byte of the next token that will be emitted from a `next*()` call. | |
| 178 | pub const TokenType = enum { | |
| 179 | object_begin, | |
| 180 | object_end, | |
| 181 | array_begin, | |
| 182 | array_end, | |
| 183 | true, | |
| 184 | false, | |
| 185 | null, | |
| 186 | number, | |
| 187 | string, | |
| 188 | end_of_document, | |
| 189 | }; | |
| 190 | ||
| 191 | /// To enable diagnostics, declare `var diagnostics = Diagnostics{};` then call `source.enableDiagnostics(&diagnostics);` | |
| 192 | /// where `source` is either a `std.json.Reader` or a `std.json.Scanner` that has just been initialized. | |
| 193 | /// At any time, notably just after an error, call `getLine()`, `getColumn()`, and/or `getByteOffset()` | |
| 194 | /// to get meaningful information from this. | |
| 195 | pub const Diagnostics = struct { | |
| 196 | line_number: u64 = 1, | |
| 197 | line_start_cursor: usize = @as(usize, @bitCast(@as(isize, -1))), // Start just "before" the input buffer to get a 1-based column for line 1. | |
| 198 | total_bytes_before_current_input: u64 = 0, | |
| 199 | cursor_pointer: *const usize = undefined, | |
| 200 | ||
| 201 | /// Starts at 1. | |
| 202 | pub fn getLine(self: *const @This()) u64 { | |
| 203 | return self.line_number; | |
| 204 | } | |
| 205 | /// Starts at 1. | |
| 206 | pub fn getColumn(self: *const @This()) u64 { | |
| 207 | return self.cursor_pointer.* -% self.line_start_cursor; | |
| 208 | } | |
| 209 | /// Starts at 0. Measures the byte offset since the start of the input. | |
| 210 | pub fn getByteOffset(self: *const @This()) u64 { | |
| 211 | return self.total_bytes_before_current_input + self.cursor_pointer.*; | |
| 212 | } | |
| 213 | }; | |
| 214 | ||
| 215 | /// See the documentation for `std.json.Token`. | |
| 216 | pub const AllocWhen = enum { alloc_if_needed, alloc_always }; | |
| 217 | ||
| 218 | /// For security, the maximum size allocated to store a single string or number value is limited to 4MiB by default. | |
| 219 | /// This limit can be specified by calling `nextAllocMax()` instead of `nextAlloc()`. | |
| 220 | pub const default_max_value_len = 4 * 1024 * 1024; | |
| 221 | ||
| 222 | /// Connects a `std.io.GenericReader` to a `std.json.Scanner`. | |
| 223 | /// All `next*()` methods here handle `error.BufferUnderrun` from `std.json.Scanner`, and then read from the reader. | |
| 224 | pub fn Reader(comptime buffer_size: usize, comptime ReaderType: type) type { | |
| 225 | return struct { | |
| 226 | scanner: Scanner, | |
| 227 | reader: ReaderType, | |
| 228 | ||
| 229 | buffer: [buffer_size]u8 = undefined, | |
| 230 | ||
| 231 | /// The allocator is only used to track `[]` and `{}` nesting levels. | |
| 232 | pub fn init(allocator: Allocator, io_reader: ReaderType) @This() { | |
| 233 | return .{ | |
| 234 | .scanner = Scanner.initStreaming(allocator), | |
| 235 | .reader = io_reader, | |
| 236 | }; | |
| 237 | } | |
| 238 | pub fn deinit(self: *@This()) void { | |
| 239 | self.scanner.deinit(); | |
| 240 | self.* = undefined; | |
| 241 | } | |
| 242 | ||
| 243 | /// Calls `std.json.Scanner.enableDiagnostics`. | |
| 244 | pub fn enableDiagnostics(self: *@This(), diagnostics: *Diagnostics) void { | |
| 245 | self.scanner.enableDiagnostics(diagnostics); | |
| 246 | } | |
| 247 | ||
| 248 | pub const NextError = ReaderType.Error || Error || Allocator.Error; | |
| 249 | pub const SkipError = NextError; | |
| 250 | pub const AllocError = NextError || error{ValueTooLong}; | |
| 251 | pub const PeekError = ReaderType.Error || Error; | |
| 252 | ||
| 253 | /// Equivalent to `nextAllocMax(allocator, when, default_max_value_len);` | |
| 254 | /// See also `std.json.Token` for documentation of `nextAlloc*()` function behavior. | |
| 255 | pub fn nextAlloc(self: *@This(), allocator: Allocator, when: AllocWhen) AllocError!Token { | |
| 256 | return self.nextAllocMax(allocator, when, default_max_value_len); | |
| 257 | } | |
| 258 | /// See also `std.json.Token` for documentation of `nextAlloc*()` function behavior. | |
| 259 | pub fn nextAllocMax(self: *@This(), allocator: Allocator, when: AllocWhen, max_value_len: usize) AllocError!Token { | |
| 260 | const token_type = try self.peekNextTokenType(); | |
| 261 | switch (token_type) { | |
| 262 | .number, .string => { | |
| 263 | var value_list = ArrayList(u8).init(allocator); | |
| 264 | errdefer { | |
| 265 | value_list.deinit(); | |
| 266 | } | |
| 267 | if (try self.allocNextIntoArrayListMax(&value_list, when, max_value_len)) |slice| { | |
| 268 | return if (token_type == .number) | |
| 269 | Token{ .number = slice } | |
| 270 | else | |
| 271 | Token{ .string = slice }; | |
| 272 | } else { | |
| 273 | return if (token_type == .number) | |
| 274 | Token{ .allocated_number = try value_list.toOwnedSlice() } | |
| 275 | else | |
| 276 | Token{ .allocated_string = try value_list.toOwnedSlice() }; | |
| 277 | } | |
| 278 | }, | |
| 279 | ||
| 280 | // Simple tokens never alloc. | |
| 281 | .object_begin, | |
| 282 | .object_end, | |
| 283 | .array_begin, | |
| 284 | .array_end, | |
| 285 | .true, | |
| 286 | .false, | |
| 287 | .null, | |
| 288 | .end_of_document, | |
| 289 | => return try self.next(), | |
| 290 | } | |
| 291 | } | |
| 292 | ||
| 293 | /// Equivalent to `allocNextIntoArrayListMax(value_list, when, default_max_value_len);` | |
| 294 | pub fn allocNextIntoArrayList(self: *@This(), value_list: *ArrayList(u8), when: AllocWhen) AllocError!?[]const u8 { | |
| 295 | return self.allocNextIntoArrayListMax(value_list, when, default_max_value_len); | |
| 296 | } | |
| 297 | /// Calls `std.json.Scanner.allocNextIntoArrayListMax` and handles `error.BufferUnderrun`. | |
| 298 | pub fn allocNextIntoArrayListMax(self: *@This(), value_list: *ArrayList(u8), when: AllocWhen, max_value_len: usize) AllocError!?[]const u8 { | |
| 299 | while (true) { | |
| 300 | return self.scanner.allocNextIntoArrayListMax(value_list, when, max_value_len) catch |err| switch (err) { | |
| 301 | error.BufferUnderrun => { | |
| 302 | try self.refillBuffer(); | |
| 303 | continue; | |
| 304 | }, | |
| 305 | else => |other_err| return other_err, | |
| 306 | }; | |
| 307 | } | |
| 308 | } | |
| 309 | ||
| 310 | /// Like `std.json.Scanner.skipValue`, but handles `error.BufferUnderrun`. | |
| 311 | pub fn skipValue(self: *@This()) SkipError!void { | |
| 312 | switch (try self.peekNextTokenType()) { | |
| 313 | .object_begin, .array_begin => { | |
| 314 | try self.skipUntilStackHeight(self.stackHeight()); | |
| 315 | }, | |
| 316 | .number, .string => { | |
| 317 | while (true) { | |
| 318 | switch (try self.next()) { | |
| 319 | .partial_number, | |
| 320 | .partial_string, | |
| 321 | .partial_string_escaped_1, | |
| 322 | .partial_string_escaped_2, | |
| 323 | .partial_string_escaped_3, | |
| 324 | .partial_string_escaped_4, | |
| 325 | => continue, | |
| 326 | ||
| 327 | .number, .string => break, | |
| 328 | ||
| 329 | else => unreachable, | |
| 330 | } | |
| 331 | } | |
| 332 | }, | |
| 333 | .true, .false, .null => { | |
| 334 | _ = try self.next(); | |
| 335 | }, | |
| 336 | ||
| 337 | .object_end, .array_end, .end_of_document => unreachable, // Attempt to skip a non-value token. | |
| 338 | } | |
| 339 | } | |
| 340 | /// Like `std.json.Scanner.skipUntilStackHeight()` but handles `error.BufferUnderrun`. | |
| 341 | pub fn skipUntilStackHeight(self: *@This(), terminal_stack_height: usize) NextError!void { | |
| 342 | while (true) { | |
| 343 | return self.scanner.skipUntilStackHeight(terminal_stack_height) catch |err| switch (err) { | |
| 344 | error.BufferUnderrun => { | |
| 345 | try self.refillBuffer(); | |
| 346 | continue; | |
| 347 | }, | |
| 348 | else => |other_err| return other_err, | |
| 349 | }; | |
| 350 | } | |
| 351 | } | |
| 352 | ||
| 353 | /// Calls `std.json.Scanner.stackHeight`. | |
| 354 | pub fn stackHeight(self: *const @This()) usize { | |
| 355 | return self.scanner.stackHeight(); | |
| 356 | } | |
| 357 | /// Calls `std.json.Scanner.ensureTotalStackCapacity`. | |
| 358 | pub fn ensureTotalStackCapacity(self: *@This(), height: usize) Allocator.Error!void { | |
| 359 | try self.scanner.ensureTotalStackCapacity(height); | |
| 360 | } | |
| 361 | ||
| 362 | /// See `std.json.Token` for documentation of this function. | |
| 363 | pub fn next(self: *@This()) NextError!Token { | |
| 364 | while (true) { | |
| 365 | return self.scanner.next() catch |err| switch (err) { | |
| 366 | error.BufferUnderrun => { | |
| 367 | try self.refillBuffer(); | |
| 368 | continue; | |
| 369 | }, | |
| 370 | else => |other_err| return other_err, | |
| 371 | }; | |
| 372 | } | |
| 373 | } | |
| 374 | ||
| 375 | /// See `std.json.Scanner.peekNextTokenType()`. | |
| 376 | pub fn peekNextTokenType(self: *@This()) PeekError!TokenType { | |
| 377 | while (true) { | |
| 378 | return self.scanner.peekNextTokenType() catch |err| switch (err) { | |
| 379 | error.BufferUnderrun => { | |
| 380 | try self.refillBuffer(); | |
| 381 | continue; | |
| 382 | }, | |
| 383 | else => |other_err| return other_err, | |
| 384 | }; | |
| 385 | } | |
| 386 | } | |
| 387 | ||
| 388 | fn refillBuffer(self: *@This()) ReaderType.Error!void { | |
| 389 | const input = self.buffer[0..try self.reader.read(self.buffer[0..])]; | |
| 390 | if (input.len > 0) { | |
| 391 | self.scanner.feedInput(input); | |
| 392 | } else { | |
| 393 | self.scanner.endInput(); | |
| 394 | } | |
| 395 | } | |
| 396 | }; | |
| 397 | } | |
| 398 | ||
| 399 | /// The lowest level parsing API in this package; | |
| 400 | /// supports streaming input with a low memory footprint. | |
| 401 | /// The memory requirement is `O(d)` where d is the nesting depth of `[]` or `{}` containers in the input. | |
| 402 | /// Specifically `d/8` bytes are required for this purpose, | |
| 403 | /// with some extra buffer according to the implementation of `std.ArrayList`. | |
| 404 | /// | |
| 405 | /// This scanner can emit partial tokens; see `std.json.Token`. | |
| 406 | /// The input to this class is a sequence of input buffers that you must supply one at a time. | |
| 407 | /// Call `feedInput()` with the first buffer, then call `next()` repeatedly until `error.BufferUnderrun` is returned. | |
| 408 | /// Then call `feedInput()` again and so forth. | |
| 409 | /// Call `endInput()` when the last input buffer has been given to `feedInput()`, either immediately after calling `feedInput()`, | |
| 410 | /// or when `error.BufferUnderrun` requests more data and there is no more. | |
| 411 | /// Be sure to call `next()` after calling `endInput()` until `Token.end_of_document` has been returned. | |
| 412 | pub const Scanner = struct { | |
| 413 | state: State = .value, | |
| 414 | string_is_object_key: bool = false, | |
| 415 | stack: BitStack, | |
| 416 | value_start: usize = undefined, | |
| 417 | utf16_code_units: [2]u16 = undefined, | |
| 418 | ||
| 419 | input: []const u8 = "", | |
| 420 | cursor: usize = 0, | |
| 421 | is_end_of_input: bool = false, | |
| 422 | diagnostics: ?*Diagnostics = null, | |
| 423 | ||
| 424 | /// The allocator is only used to track `[]` and `{}` nesting levels. | |
| 425 | pub fn initStreaming(allocator: Allocator) @This() { | |
| 426 | return .{ | |
| 427 | .stack = BitStack.init(allocator), | |
| 428 | }; | |
| 429 | } | |
| 430 | /// Use this if your input is a single slice. | |
| 431 | /// This is effectively equivalent to: | |
| 432 | /// ``` | |
| 433 | /// initStreaming(allocator); | |
| 434 | /// feedInput(complete_input); | |
| 435 | /// endInput(); | |
| 436 | /// ``` | |
| 437 | pub fn initCompleteInput(allocator: Allocator, complete_input: []const u8) @This() { | |
| 438 | return .{ | |
| 439 | .stack = BitStack.init(allocator), | |
| 440 | .input = complete_input, | |
| 441 | .is_end_of_input = true, | |
| 442 | }; | |
| 443 | } | |
| 444 | pub fn deinit(self: *@This()) void { | |
| 445 | self.stack.deinit(); | |
| 446 | self.* = undefined; | |
| 447 | } | |
| 448 | ||
| 449 | pub fn enableDiagnostics(self: *@This(), diagnostics: *Diagnostics) void { | |
| 450 | diagnostics.cursor_pointer = &self.cursor; | |
| 451 | self.diagnostics = diagnostics; | |
| 452 | } | |
| 453 | ||
| 454 | /// Call this whenever you get `error.BufferUnderrun` from `next()`. | |
| 455 | /// When there is no more input to provide, call `endInput()`. | |
| 456 | pub fn feedInput(self: *@This(), input: []const u8) void { | |
| 457 | assert(self.cursor == self.input.len); // Not done with the last input slice. | |
| 458 | if (self.diagnostics) |diag| { | |
| 459 | diag.total_bytes_before_current_input += self.input.len; | |
| 460 | // This usually goes "negative" to measure how far before the beginning | |
| 461 | // of the new buffer the current line started. | |
| 462 | diag.line_start_cursor -%= self.cursor; | |
| 463 | } | |
| 464 | self.input = input; | |
| 465 | self.cursor = 0; | |
| 466 | self.value_start = 0; | |
| 467 | } | |
| 468 | /// Call this when you will no longer call `feedInput()` anymore. | |
| 469 | /// This can be called either immediately after the last `feedInput()`, | |
| 470 | /// or at any time afterward, such as when getting `error.BufferUnderrun` from `next()`. | |
| 471 | /// Don't forget to call `next*()` after `endInput()` until you get `.end_of_document`. | |
| 472 | pub fn endInput(self: *@This()) void { | |
| 473 | self.is_end_of_input = true; | |
| 474 | } | |
| 475 | ||
| 476 | pub const NextError = Error || Allocator.Error || error{BufferUnderrun}; | |
| 477 | pub const AllocError = Error || Allocator.Error || error{ValueTooLong}; | |
| 478 | pub const PeekError = Error || error{BufferUnderrun}; | |
| 479 | pub const SkipError = Error || Allocator.Error; | |
| 480 | pub const AllocIntoArrayListError = AllocError || error{BufferUnderrun}; | |
| 481 | ||
| 482 | /// Equivalent to `nextAllocMax(allocator, when, default_max_value_len);` | |
| 483 | /// This function is only available after `endInput()` (or `initCompleteInput()`) has been called. | |
| 484 | /// See also `std.json.Token` for documentation of `nextAlloc*()` function behavior. | |
| 485 | pub fn nextAlloc(self: *@This(), allocator: Allocator, when: AllocWhen) AllocError!Token { | |
| 486 | return self.nextAllocMax(allocator, when, default_max_value_len); | |
| 487 | } | |
| 488 | ||
| 489 | /// This function is only available after `endInput()` (or `initCompleteInput()`) has been called. | |
| 490 | /// See also `std.json.Token` for documentation of `nextAlloc*()` function behavior. | |
| 491 | pub fn nextAllocMax(self: *@This(), allocator: Allocator, when: AllocWhen, max_value_len: usize) AllocError!Token { | |
| 492 | assert(self.is_end_of_input); // This function is not available in streaming mode. | |
| 493 | const token_type = self.peekNextTokenType() catch |e| switch (e) { | |
| 494 | error.BufferUnderrun => unreachable, | |
| 495 | else => |err| return err, | |
| 496 | }; | |
| 497 | switch (token_type) { | |
| 498 | .number, .string => { | |
| 499 | var value_list = ArrayList(u8).init(allocator); | |
| 500 | errdefer { | |
| 501 | value_list.deinit(); | |
| 502 | } | |
| 503 | if (self.allocNextIntoArrayListMax(&value_list, when, max_value_len) catch |e| switch (e) { | |
| 504 | error.BufferUnderrun => unreachable, | |
| 505 | else => |err| return err, | |
| 506 | }) |slice| { | |
| 507 | return if (token_type == .number) | |
| 508 | Token{ .number = slice } | |
| 509 | else | |
| 510 | Token{ .string = slice }; | |
| 511 | } else { | |
| 512 | return if (token_type == .number) | |
| 513 | Token{ .allocated_number = try value_list.toOwnedSlice() } | |
| 514 | else | |
| 515 | Token{ .allocated_string = try value_list.toOwnedSlice() }; | |
| 516 | } | |
| 517 | }, | |
| 518 | ||
| 519 | // Simple tokens never alloc. | |
| 520 | .object_begin, | |
| 521 | .object_end, | |
| 522 | .array_begin, | |
| 523 | .array_end, | |
| 524 | .true, | |
| 525 | .false, | |
| 526 | .null, | |
| 527 | .end_of_document, | |
| 528 | => return self.next() catch |e| switch (e) { | |
| 529 | error.BufferUnderrun => unreachable, | |
| 530 | else => |err| return err, | |
| 531 | }, | |
| 532 | } | |
| 533 | } | |
| 534 | ||
| 535 | /// Equivalent to `allocNextIntoArrayListMax(value_list, when, default_max_value_len);` | |
| 536 | pub fn allocNextIntoArrayList(self: *@This(), value_list: *ArrayList(u8), when: AllocWhen) AllocIntoArrayListError!?[]const u8 { | |
| 537 | return self.allocNextIntoArrayListMax(value_list, when, default_max_value_len); | |
| 538 | } | |
| 539 | /// The next token type must be either `.number` or `.string`. See `peekNextTokenType()`. | |
| 540 | /// When allocation is not necessary with `.alloc_if_needed`, | |
| 541 | /// this method returns the content slice from the input buffer, and `value_list` is not touched. | |
| 542 | /// When allocation is necessary or with `.alloc_always`, this method concatenates partial tokens into the given `value_list`, | |
| 543 | /// and returns `null` once the final `.number` or `.string` token has been written into it. | |
| 544 | /// In case of an `error.BufferUnderrun`, partial values will be left in the given value_list. | |
| 545 | /// The given `value_list` is never reset by this method, so an `error.BufferUnderrun` situation | |
| 546 | /// can be resumed by passing the same array list in again. | |
| 547 | /// This method does not indicate whether the token content being returned is for a `.number` or `.string` token type; | |
| 548 | /// the caller of this method is expected to know which type of token is being processed. | |
| 549 | pub fn allocNextIntoArrayListMax(self: *@This(), value_list: *ArrayList(u8), when: AllocWhen, max_value_len: usize) AllocIntoArrayListError!?[]const u8 { | |
| 550 | while (true) { | |
| 551 | const token = try self.next(); | |
| 552 | switch (token) { | |
| 553 | // Accumulate partial values. | |
| 554 | .partial_number, .partial_string => |slice| { | |
| 555 | try appendSlice(value_list, slice, max_value_len); | |
| 556 | }, | |
| 557 | .partial_string_escaped_1 => |buf| { | |
| 558 | try appendSlice(value_list, buf[0..], max_value_len); | |
| 559 | }, | |
| 560 | .partial_string_escaped_2 => |buf| { | |
| 561 | try appendSlice(value_list, buf[0..], max_value_len); | |
| 562 | }, | |
| 563 | .partial_string_escaped_3 => |buf| { | |
| 564 | try appendSlice(value_list, buf[0..], max_value_len); | |
| 565 | }, | |
| 566 | .partial_string_escaped_4 => |buf| { | |
| 567 | try appendSlice(value_list, buf[0..], max_value_len); | |
| 568 | }, | |
| 569 | ||
| 570 | // Return complete values. | |
| 571 | .number => |slice| { | |
| 572 | if (when == .alloc_if_needed and value_list.items.len == 0) { | |
| 573 | // No alloc necessary. | |
| 574 | return slice; | |
| 575 | } | |
| 576 | try appendSlice(value_list, slice, max_value_len); | |
| 577 | // The token is complete. | |
| 578 | return null; | |
| 579 | }, | |
| 580 | .string => |slice| { | |
| 581 | if (when == .alloc_if_needed and value_list.items.len == 0) { | |
| 582 | // No alloc necessary. | |
| 583 | return slice; | |
| 584 | } | |
| 585 | try appendSlice(value_list, slice, max_value_len); | |
| 586 | // The token is complete. | |
| 587 | return null; | |
| 588 | }, | |
| 589 | ||
| 590 | .object_begin, | |
| 591 | .object_end, | |
| 592 | .array_begin, | |
| 593 | .array_end, | |
| 594 | .true, | |
| 595 | .false, | |
| 596 | .null, | |
| 597 | .end_of_document, | |
| 598 | => unreachable, // Only .number and .string token types are allowed here. Check peekNextTokenType() before calling this. | |
| 599 | ||
| 600 | .allocated_number, .allocated_string => unreachable, | |
| 601 | } | |
| 602 | } | |
| 603 | } | |
| 604 | ||
| 605 | /// This function is only available after `endInput()` (or `initCompleteInput()`) has been called. | |
| 606 | /// If the next token type is `.object_begin` or `.array_begin`, | |
| 607 | /// this function calls `next()` repeatedly until the corresponding `.object_end` or `.array_end` is found. | |
| 608 | /// If the next token type is `.number` or `.string`, | |
| 609 | /// this function calls `next()` repeatedly until the (non `.partial_*`) `.number` or `.string` token is found. | |
| 610 | /// If the next token type is `.true`, `.false`, or `.null`, this function calls `next()` once. | |
| 611 | /// The next token type must not be `.object_end`, `.array_end`, or `.end_of_document`; | |
| 612 | /// see `peekNextTokenType()`. | |
| 613 | pub fn skipValue(self: *@This()) SkipError!void { | |
| 614 | assert(self.is_end_of_input); // This function is not available in streaming mode. | |
| 615 | switch (self.peekNextTokenType() catch |e| switch (e) { | |
| 616 | error.BufferUnderrun => unreachable, | |
| 617 | else => |err| return err, | |
| 618 | }) { | |
| 619 | .object_begin, .array_begin => { | |
| 620 | self.skipUntilStackHeight(self.stackHeight()) catch |e| switch (e) { | |
| 621 | error.BufferUnderrun => unreachable, | |
| 622 | else => |err| return err, | |
| 623 | }; | |
| 624 | }, | |
| 625 | .number, .string => { | |
| 626 | while (true) { | |
| 627 | switch (self.next() catch |e| switch (e) { | |
| 628 | error.BufferUnderrun => unreachable, | |
| 629 | else => |err| return err, | |
| 630 | }) { | |
| 631 | .partial_number, | |
| 632 | .partial_string, | |
| 633 | .partial_string_escaped_1, | |
| 634 | .partial_string_escaped_2, | |
| 635 | .partial_string_escaped_3, | |
| 636 | .partial_string_escaped_4, | |
| 637 | => continue, | |
| 638 | ||
| 639 | .number, .string => break, | |
| 640 | ||
| 641 | else => unreachable, | |
| 642 | } | |
| 643 | } | |
| 644 | }, | |
| 645 | .true, .false, .null => { | |
| 646 | _ = self.next() catch |e| switch (e) { | |
| 647 | error.BufferUnderrun => unreachable, | |
| 648 | else => |err| return err, | |
| 649 | }; | |
| 650 | }, | |
| 651 | ||
| 652 | .object_end, .array_end, .end_of_document => unreachable, // Attempt to skip a non-value token. | |
| 653 | } | |
| 654 | } | |
| 655 | ||
| 656 | /// Skip tokens until an `.object_end` or `.array_end` token results in a `stackHeight()` equal the given stack height. | |
| 657 | /// Unlike `skipValue()`, this function is available in streaming mode. | |
| 658 | pub fn skipUntilStackHeight(self: *@This(), terminal_stack_height: usize) NextError!void { | |
| 659 | while (true) { | |
| 660 | switch (try self.next()) { | |
| 661 | .object_end, .array_end => { | |
| 662 | if (self.stackHeight() == terminal_stack_height) break; | |
| 663 | }, | |
| 664 | .end_of_document => unreachable, | |
| 665 | else => continue, | |
| 666 | } | |
| 667 | } | |
| 668 | } | |
| 669 | ||
| 670 | /// The depth of `{}` or `[]` nesting levels at the current position. | |
| 671 | pub fn stackHeight(self: *const @This()) usize { | |
| 672 | return self.stack.bit_len; | |
| 673 | } | |
| 674 | ||
| 675 | /// Pre allocate memory to hold the given number of nesting levels. | |
| 676 | /// `stackHeight()` up to the given number will not cause allocations. | |
| 677 | pub fn ensureTotalStackCapacity(self: *@This(), height: usize) Allocator.Error!void { | |
| 678 | try self.stack.ensureTotalCapacity(height); | |
| 679 | } | |
| 680 | ||
| 681 | /// See `std.json.Token` for documentation of this function. | |
| 682 | pub fn next(self: *@This()) NextError!Token { | |
| 683 | state_loop: while (true) { | |
| 684 | switch (self.state) { | |
| 685 | .value => { | |
| 686 | switch (try self.skipWhitespaceExpectByte()) { | |
| 687 | // Object, Array | |
| 688 | '{' => { | |
| 689 | try self.stack.push(OBJECT_MODE); | |
| 690 | self.cursor += 1; | |
| 691 | self.state = .object_start; | |
| 692 | return .object_begin; | |
| 693 | }, | |
| 694 | '[' => { | |
| 695 | try self.stack.push(ARRAY_MODE); | |
| 696 | self.cursor += 1; | |
| 697 | self.state = .array_start; | |
| 698 | return .array_begin; | |
| 699 | }, | |
| 700 | ||
| 701 | // String | |
| 702 | '"' => { | |
| 703 | self.cursor += 1; | |
| 704 | self.value_start = self.cursor; | |
| 705 | self.state = .string; | |
| 706 | continue :state_loop; | |
| 707 | }, | |
| 708 | ||
| 709 | // Number | |
| 710 | '1'...'9' => { | |
| 711 | self.value_start = self.cursor; | |
| 712 | self.cursor += 1; | |
| 713 | self.state = .number_int; | |
| 714 | continue :state_loop; | |
| 715 | }, | |
| 716 | '0' => { | |
| 717 | self.value_start = self.cursor; | |
| 718 | self.cursor += 1; | |
| 719 | self.state = .number_leading_zero; | |
| 720 | continue :state_loop; | |
| 721 | }, | |
| 722 | '-' => { | |
| 723 | self.value_start = self.cursor; | |
| 724 | self.cursor += 1; | |
| 725 | self.state = .number_minus; | |
| 726 | continue :state_loop; | |
| 727 | }, | |
| 728 | ||
| 729 | // literal values | |
| 730 | 't' => { | |
| 731 | self.cursor += 1; | |
| 732 | self.state = .literal_t; | |
| 733 | continue :state_loop; | |
| 734 | }, | |
| 735 | 'f' => { | |
| 736 | self.cursor += 1; | |
| 737 | self.state = .literal_f; | |
| 738 | continue :state_loop; | |
| 739 | }, | |
| 740 | 'n' => { | |
| 741 | self.cursor += 1; | |
| 742 | self.state = .literal_n; | |
| 743 | continue :state_loop; | |
| 744 | }, | |
| 745 | ||
| 746 | else => return error.SyntaxError, | |
| 747 | } | |
| 748 | }, | |
| 749 | ||
| 750 | .post_value => { | |
| 751 | if (try self.skipWhitespaceCheckEnd()) return .end_of_document; | |
| 752 | ||
| 753 | const c = self.input[self.cursor]; | |
| 754 | if (self.string_is_object_key) { | |
| 755 | self.string_is_object_key = false; | |
| 756 | switch (c) { | |
| 757 | ':' => { | |
| 758 | self.cursor += 1; | |
| 759 | self.state = .value; | |
| 760 | continue :state_loop; | |
| 761 | }, | |
| 762 | else => return error.SyntaxError, | |
| 763 | } | |
| 764 | } | |
| 765 | ||
| 766 | switch (c) { | |
| 767 | '}' => { | |
| 768 | if (self.stack.pop() != OBJECT_MODE) return error.SyntaxError; | |
| 769 | self.cursor += 1; | |
| 770 | // stay in .post_value state. | |
| 771 | return .object_end; | |
| 772 | }, | |
| 773 | ']' => { | |
| 774 | if (self.stack.pop() != ARRAY_MODE) return error.SyntaxError; | |
| 775 | self.cursor += 1; | |
| 776 | // stay in .post_value state. | |
| 777 | return .array_end; | |
| 778 | }, | |
| 779 | ',' => { | |
| 780 | switch (self.stack.peek()) { | |
| 781 | OBJECT_MODE => { | |
| 782 | self.state = .object_post_comma; | |
| 783 | }, | |
| 784 | ARRAY_MODE => { | |
| 785 | self.state = .value; | |
| 786 | }, | |
| 787 | } | |
| 788 | self.cursor += 1; | |
| 789 | continue :state_loop; | |
| 790 | }, | |
| 791 | else => return error.SyntaxError, | |
| 792 | } | |
| 793 | }, | |
| 794 | ||
| 795 | .object_start => { | |
| 796 | switch (try self.skipWhitespaceExpectByte()) { | |
| 797 | '"' => { | |
| 798 | self.cursor += 1; | |
| 799 | self.value_start = self.cursor; | |
| 800 | self.state = .string; | |
| 801 | self.string_is_object_key = true; | |
| 802 | continue :state_loop; | |
| 803 | }, | |
| 804 | '}' => { | |
| 805 | self.cursor += 1; | |
| 806 | _ = self.stack.pop(); | |
| 807 | self.state = .post_value; | |
| 808 | return .object_end; | |
| 809 | }, | |
| 810 | else => return error.SyntaxError, | |
| 811 | } | |
| 812 | }, | |
| 813 | .object_post_comma => { | |
| 814 | switch (try self.skipWhitespaceExpectByte()) { | |
| 815 | '"' => { | |
| 816 | self.cursor += 1; | |
| 817 | self.value_start = self.cursor; | |
| 818 | self.state = .string; | |
| 819 | self.string_is_object_key = true; | |
| 820 | continue :state_loop; | |
| 821 | }, | |
| 822 | else => return error.SyntaxError, | |
| 823 | } | |
| 824 | }, | |
| 825 | ||
| 826 | .array_start => { | |
| 827 | switch (try self.skipWhitespaceExpectByte()) { | |
| 828 | ']' => { | |
| 829 | self.cursor += 1; | |
| 830 | _ = self.stack.pop(); | |
| 831 | self.state = .post_value; | |
| 832 | return .array_end; | |
| 833 | }, | |
| 834 | else => { | |
| 835 | self.state = .value; | |
| 836 | continue :state_loop; | |
| 837 | }, | |
| 838 | } | |
| 839 | }, | |
| 840 | ||
| 841 | .number_minus => { | |
| 842 | if (self.cursor >= self.input.len) return self.endOfBufferInNumber(false); | |
| 843 | switch (self.input[self.cursor]) { | |
| 844 | '0' => { | |
| 845 | self.cursor += 1; | |
| 846 | self.state = .number_leading_zero; | |
| 847 | continue :state_loop; | |
| 848 | }, | |
| 849 | '1'...'9' => { | |
| 850 | self.cursor += 1; | |
| 851 | self.state = .number_int; | |
| 852 | continue :state_loop; | |
| 853 | }, | |
| 854 | else => return error.SyntaxError, | |
| 855 | } | |
| 856 | }, | |
| 857 | .number_leading_zero => { | |
| 858 | if (self.cursor >= self.input.len) return self.endOfBufferInNumber(true); | |
| 859 | switch (self.input[self.cursor]) { | |
| 860 | '.' => { | |
| 861 | self.cursor += 1; | |
| 862 | self.state = .number_post_dot; | |
| 863 | continue :state_loop; | |
| 864 | }, | |
| 865 | 'e', 'E' => { | |
| 866 | self.cursor += 1; | |
| 867 | self.state = .number_post_e; | |
| 868 | continue :state_loop; | |
| 869 | }, | |
| 870 | else => { | |
| 871 | self.state = .post_value; | |
| 872 | return Token{ .number = self.takeValueSlice() }; | |
| 873 | }, | |
| 874 | } | |
| 875 | }, | |
| 876 | .number_int => { | |
| 877 | while (self.cursor < self.input.len) : (self.cursor += 1) { | |
| 878 | switch (self.input[self.cursor]) { | |
| 879 | '0'...'9' => continue, | |
| 880 | '.' => { | |
| 881 | self.cursor += 1; | |
| 882 | self.state = .number_post_dot; | |
| 883 | continue :state_loop; | |
| 884 | }, | |
| 885 | 'e', 'E' => { | |
| 886 | self.cursor += 1; | |
| 887 | self.state = .number_post_e; | |
| 888 | continue :state_loop; | |
| 889 | }, | |
| 890 | else => { | |
| 891 | self.state = .post_value; | |
| 892 | return Token{ .number = self.takeValueSlice() }; | |
| 893 | }, | |
| 894 | } | |
| 895 | } | |
| 896 | return self.endOfBufferInNumber(true); | |
| 897 | }, | |
| 898 | .number_post_dot => { | |
| 899 | if (self.cursor >= self.input.len) return self.endOfBufferInNumber(false); | |
| 900 | switch (self.input[self.cursor]) { | |
| 901 | '0'...'9' => { | |
| 902 | self.cursor += 1; | |
| 903 | self.state = .number_frac; | |
| 904 | continue :state_loop; | |
| 905 | }, | |
| 906 | else => return error.SyntaxError, | |
| 907 | } | |
| 908 | }, | |
| 909 | .number_frac => { | |
| 910 | while (self.cursor < self.input.len) : (self.cursor += 1) { | |
| 911 | switch (self.input[self.cursor]) { | |
| 912 | '0'...'9' => continue, | |
| 913 | 'e', 'E' => { | |
| 914 | self.cursor += 1; | |
| 915 | self.state = .number_post_e; | |
| 916 | continue :state_loop; | |
| 917 | }, | |
| 918 | else => { | |
| 919 | self.state = .post_value; | |
| 920 | return Token{ .number = self.takeValueSlice() }; | |
| 921 | }, | |
| 922 | } | |
| 923 | } | |
| 924 | return self.endOfBufferInNumber(true); | |
| 925 | }, | |
| 926 | .number_post_e => { | |
| 927 | if (self.cursor >= self.input.len) return self.endOfBufferInNumber(false); | |
| 928 | switch (self.input[self.cursor]) { | |
| 929 | '0'...'9' => { | |
| 930 | self.cursor += 1; | |
| 931 | self.state = .number_exp; | |
| 932 | continue :state_loop; | |
| 933 | }, | |
| 934 | '+', '-' => { | |
| 935 | self.cursor += 1; | |
| 936 | self.state = .number_post_e_sign; | |
| 937 | continue :state_loop; | |
| 938 | }, | |
| 939 | else => return error.SyntaxError, | |
| 940 | } | |
| 941 | }, | |
| 942 | .number_post_e_sign => { | |
| 943 | if (self.cursor >= self.input.len) return self.endOfBufferInNumber(false); | |
| 944 | switch (self.input[self.cursor]) { | |
| 945 | '0'...'9' => { | |
| 946 | self.cursor += 1; | |
| 947 | self.state = .number_exp; | |
| 948 | continue :state_loop; | |
| 949 | }, | |
| 950 | else => return error.SyntaxError, | |
| 951 | } | |
| 952 | }, | |
| 953 | .number_exp => { | |
| 954 | while (self.cursor < self.input.len) : (self.cursor += 1) { | |
| 955 | switch (self.input[self.cursor]) { | |
| 956 | '0'...'9' => continue, | |
| 957 | else => { | |
| 958 | self.state = .post_value; | |
| 959 | return Token{ .number = self.takeValueSlice() }; | |
| 960 | }, | |
| 961 | } | |
| 962 | } | |
| 963 | return self.endOfBufferInNumber(true); | |
| 964 | }, | |
| 965 | ||
| 966 | .string => { | |
| 967 | while (self.cursor < self.input.len) : (self.cursor += 1) { | |
| 968 | switch (self.input[self.cursor]) { | |
| 969 | 0...0x1f => return error.SyntaxError, // Bare ASCII control code in string. | |
| 970 | ||
| 971 | // ASCII plain text. | |
| 972 | 0x20...('"' - 1), ('"' + 1)...('\\' - 1), ('\\' + 1)...0x7F => continue, | |
| 973 | ||
| 974 | // Special characters. | |
| 975 | '"' => { | |
| 976 | const result = Token{ .string = self.takeValueSlice() }; | |
| 977 | self.cursor += 1; | |
| 978 | self.state = .post_value; | |
| 979 | return result; | |
| 980 | }, | |
| 981 | '\\' => { | |
| 982 | const slice = self.takeValueSlice(); | |
| 983 | self.cursor += 1; | |
| 984 | self.state = .string_backslash; | |
| 985 | if (slice.len > 0) return Token{ .partial_string = slice }; | |
| 986 | continue :state_loop; | |
| 987 | }, | |
| 988 | ||
| 989 | // UTF-8 validation. | |
| 990 | // See http://unicode.org/mail-arch/unicode-ml/y2003-m02/att-0467/01-The_Algorithm_to_Valide_an_UTF-8_String | |
| 991 | 0xC2...0xDF => { | |
| 992 | self.cursor += 1; | |
| 993 | self.state = .string_utf8_last_byte; | |
| 994 | continue :state_loop; | |
| 995 | }, | |
| 996 | 0xE0 => { | |
| 997 | self.cursor += 1; | |
| 998 | self.state = .string_utf8_second_to_last_byte_guard_against_overlong; | |
| 999 | continue :state_loop; | |
| 1000 | }, | |
| 1001 | 0xE1...0xEC, 0xEE...0xEF => { | |
| 1002 | self.cursor += 1; | |
| 1003 | self.state = .string_utf8_second_to_last_byte; | |
| 1004 | continue :state_loop; | |
| 1005 | }, | |
| 1006 | 0xED => { | |
| 1007 | self.cursor += 1; | |
| 1008 | self.state = .string_utf8_second_to_last_byte_guard_against_surrogate_half; | |
| 1009 | continue :state_loop; | |
| 1010 | }, | |
| 1011 | 0xF0 => { | |
| 1012 | self.cursor += 1; | |
| 1013 | self.state = .string_utf8_third_to_last_byte_guard_against_overlong; | |
| 1014 | continue :state_loop; | |
| 1015 | }, | |
| 1016 | 0xF1...0xF3 => { | |
| 1017 | self.cursor += 1; | |
| 1018 | self.state = .string_utf8_third_to_last_byte; | |
| 1019 | continue :state_loop; | |
| 1020 | }, | |
| 1021 | 0xF4 => { | |
| 1022 | self.cursor += 1; | |
| 1023 | self.state = .string_utf8_third_to_last_byte_guard_against_too_large; | |
| 1024 | continue :state_loop; | |
| 1025 | }, | |
| 1026 | 0x80...0xC1, 0xF5...0xFF => return error.SyntaxError, // Invalid UTF-8. | |
| 1027 | } | |
| 1028 | } | |
| 1029 | if (self.is_end_of_input) return error.UnexpectedEndOfInput; | |
| 1030 | const slice = self.takeValueSlice(); | |
| 1031 | if (slice.len > 0) return Token{ .partial_string = slice }; | |
| 1032 | return error.BufferUnderrun; | |
| 1033 | }, | |
| 1034 | .string_backslash => { | |
| 1035 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1036 | switch (self.input[self.cursor]) { | |
| 1037 | '"', '\\', '/' => { | |
| 1038 | // Since these characters now represent themselves literally, | |
| 1039 | // we can simply begin the next plaintext slice here. | |
| 1040 | self.value_start = self.cursor; | |
| 1041 | self.cursor += 1; | |
| 1042 | self.state = .string; | |
| 1043 | continue :state_loop; | |
| 1044 | }, | |
| 1045 | 'b' => { | |
| 1046 | self.cursor += 1; | |
| 1047 | self.value_start = self.cursor; | |
| 1048 | self.state = .string; | |
| 1049 | return Token{ .partial_string_escaped_1 = [_]u8{0x08} }; | |
| 1050 | }, | |
| 1051 | 'f' => { | |
| 1052 | self.cursor += 1; | |
| 1053 | self.value_start = self.cursor; | |
| 1054 | self.state = .string; | |
| 1055 | return Token{ .partial_string_escaped_1 = [_]u8{0x0c} }; | |
| 1056 | }, | |
| 1057 | 'n' => { | |
| 1058 | self.cursor += 1; | |
| 1059 | self.value_start = self.cursor; | |
| 1060 | self.state = .string; | |
| 1061 | return Token{ .partial_string_escaped_1 = [_]u8{'\n'} }; | |
| 1062 | }, | |
| 1063 | 'r' => { | |
| 1064 | self.cursor += 1; | |
| 1065 | self.value_start = self.cursor; | |
| 1066 | self.state = .string; | |
| 1067 | return Token{ .partial_string_escaped_1 = [_]u8{'\r'} }; | |
| 1068 | }, | |
| 1069 | 't' => { | |
| 1070 | self.cursor += 1; | |
| 1071 | self.value_start = self.cursor; | |
| 1072 | self.state = .string; | |
| 1073 | return Token{ .partial_string_escaped_1 = [_]u8{'\t'} }; | |
| 1074 | }, | |
| 1075 | 'u' => { | |
| 1076 | self.cursor += 1; | |
| 1077 | self.state = .string_backslash_u; | |
| 1078 | continue :state_loop; | |
| 1079 | }, | |
| 1080 | else => return error.SyntaxError, | |
| 1081 | } | |
| 1082 | }, | |
| 1083 | .string_backslash_u => { | |
| 1084 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1085 | const c = self.input[self.cursor]; | |
| 1086 | switch (c) { | |
| 1087 | '0'...'9' => { | |
| 1088 | self.utf16_code_units[0] = @as(u16, c - '0') << 12; | |
| 1089 | }, | |
| 1090 | 'A'...'F' => { | |
| 1091 | self.utf16_code_units[0] = @as(u16, c - 'A' + 10) << 12; | |
| 1092 | }, | |
| 1093 | 'a'...'f' => { | |
| 1094 | self.utf16_code_units[0] = @as(u16, c - 'a' + 10) << 12; | |
| 1095 | }, | |
| 1096 | else => return error.SyntaxError, | |
| 1097 | } | |
| 1098 | self.cursor += 1; | |
| 1099 | self.state = .string_backslash_u_1; | |
| 1100 | continue :state_loop; | |
| 1101 | }, | |
| 1102 | .string_backslash_u_1 => { | |
| 1103 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1104 | const c = self.input[self.cursor]; | |
| 1105 | switch (c) { | |
| 1106 | '0'...'9' => { | |
| 1107 | self.utf16_code_units[0] |= @as(u16, c - '0') << 8; | |
| 1108 | }, | |
| 1109 | 'A'...'F' => { | |
| 1110 | self.utf16_code_units[0] |= @as(u16, c - 'A' + 10) << 8; | |
| 1111 | }, | |
| 1112 | 'a'...'f' => { | |
| 1113 | self.utf16_code_units[0] |= @as(u16, c - 'a' + 10) << 8; | |
| 1114 | }, | |
| 1115 | else => return error.SyntaxError, | |
| 1116 | } | |
| 1117 | self.cursor += 1; | |
| 1118 | self.state = .string_backslash_u_2; | |
| 1119 | continue :state_loop; | |
| 1120 | }, | |
| 1121 | .string_backslash_u_2 => { | |
| 1122 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1123 | const c = self.input[self.cursor]; | |
| 1124 | switch (c) { | |
| 1125 | '0'...'9' => { | |
| 1126 | self.utf16_code_units[0] |= @as(u16, c - '0') << 4; | |
| 1127 | }, | |
| 1128 | 'A'...'F' => { | |
| 1129 | self.utf16_code_units[0] |= @as(u16, c - 'A' + 10) << 4; | |
| 1130 | }, | |
| 1131 | 'a'...'f' => { | |
| 1132 | self.utf16_code_units[0] |= @as(u16, c - 'a' + 10) << 4; | |
| 1133 | }, | |
| 1134 | else => return error.SyntaxError, | |
| 1135 | } | |
| 1136 | self.cursor += 1; | |
| 1137 | self.state = .string_backslash_u_3; | |
| 1138 | continue :state_loop; | |
| 1139 | }, | |
| 1140 | .string_backslash_u_3 => { | |
| 1141 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1142 | const c = self.input[self.cursor]; | |
| 1143 | switch (c) { | |
| 1144 | '0'...'9' => { | |
| 1145 | self.utf16_code_units[0] |= c - '0'; | |
| 1146 | }, | |
| 1147 | 'A'...'F' => { | |
| 1148 | self.utf16_code_units[0] |= c - 'A' + 10; | |
| 1149 | }, | |
| 1150 | 'a'...'f' => { | |
| 1151 | self.utf16_code_units[0] |= c - 'a' + 10; | |
| 1152 | }, | |
| 1153 | else => return error.SyntaxError, | |
| 1154 | } | |
| 1155 | self.cursor += 1; | |
| 1156 | if (std.unicode.utf16IsHighSurrogate(self.utf16_code_units[0])) { | |
| 1157 | self.state = .string_surrogate_half; | |
| 1158 | continue :state_loop; | |
| 1159 | } else if (std.unicode.utf16IsLowSurrogate(self.utf16_code_units[0])) { | |
| 1160 | return error.SyntaxError; // Unexpected low surrogate half. | |
| 1161 | } else { | |
| 1162 | self.value_start = self.cursor; | |
| 1163 | self.state = .string; | |
| 1164 | return partialStringCodepoint(self.utf16_code_units[0]); | |
| 1165 | } | |
| 1166 | }, | |
| 1167 | .string_surrogate_half => { | |
| 1168 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1169 | switch (self.input[self.cursor]) { | |
| 1170 | '\\' => { | |
| 1171 | self.cursor += 1; | |
| 1172 | self.state = .string_surrogate_half_backslash; | |
| 1173 | continue :state_loop; | |
| 1174 | }, | |
| 1175 | else => return error.SyntaxError, // Expected low surrogate half. | |
| 1176 | } | |
| 1177 | }, | |
| 1178 | .string_surrogate_half_backslash => { | |
| 1179 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1180 | switch (self.input[self.cursor]) { | |
| 1181 | 'u' => { | |
| 1182 | self.cursor += 1; | |
| 1183 | self.state = .string_surrogate_half_backslash_u; | |
| 1184 | continue :state_loop; | |
| 1185 | }, | |
| 1186 | else => return error.SyntaxError, // Expected low surrogate half. | |
| 1187 | } | |
| 1188 | }, | |
| 1189 | .string_surrogate_half_backslash_u => { | |
| 1190 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1191 | switch (self.input[self.cursor]) { | |
| 1192 | 'D', 'd' => { | |
| 1193 | self.cursor += 1; | |
| 1194 | self.utf16_code_units[1] = 0xD << 12; | |
| 1195 | self.state = .string_surrogate_half_backslash_u_1; | |
| 1196 | continue :state_loop; | |
| 1197 | }, | |
| 1198 | else => return error.SyntaxError, // Expected low surrogate half. | |
| 1199 | } | |
| 1200 | }, | |
| 1201 | .string_surrogate_half_backslash_u_1 => { | |
| 1202 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1203 | const c = self.input[self.cursor]; | |
| 1204 | switch (c) { | |
| 1205 | 'C'...'F' => { | |
| 1206 | self.cursor += 1; | |
| 1207 | self.utf16_code_units[1] |= @as(u16, c - 'A' + 10) << 8; | |
| 1208 | self.state = .string_surrogate_half_backslash_u_2; | |
| 1209 | continue :state_loop; | |
| 1210 | }, | |
| 1211 | 'c'...'f' => { | |
| 1212 | self.cursor += 1; | |
| 1213 | self.utf16_code_units[1] |= @as(u16, c - 'a' + 10) << 8; | |
| 1214 | self.state = .string_surrogate_half_backslash_u_2; | |
| 1215 | continue :state_loop; | |
| 1216 | }, | |
| 1217 | else => return error.SyntaxError, // Expected low surrogate half. | |
| 1218 | } | |
| 1219 | }, | |
| 1220 | .string_surrogate_half_backslash_u_2 => { | |
| 1221 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1222 | const c = self.input[self.cursor]; | |
| 1223 | switch (c) { | |
| 1224 | '0'...'9' => { | |
| 1225 | self.cursor += 1; | |
| 1226 | self.utf16_code_units[1] |= @as(u16, c - '0') << 4; | |
| 1227 | self.state = .string_surrogate_half_backslash_u_3; | |
| 1228 | continue :state_loop; | |
| 1229 | }, | |
| 1230 | 'A'...'F' => { | |
| 1231 | self.cursor += 1; | |
| 1232 | self.utf16_code_units[1] |= @as(u16, c - 'A' + 10) << 4; | |
| 1233 | self.state = .string_surrogate_half_backslash_u_3; | |
| 1234 | continue :state_loop; | |
| 1235 | }, | |
| 1236 | 'a'...'f' => { | |
| 1237 | self.cursor += 1; | |
| 1238 | self.utf16_code_units[1] |= @as(u16, c - 'a' + 10) << 4; | |
| 1239 | self.state = .string_surrogate_half_backslash_u_3; | |
| 1240 | continue :state_loop; | |
| 1241 | }, | |
| 1242 | else => return error.SyntaxError, | |
| 1243 | } | |
| 1244 | }, | |
| 1245 | .string_surrogate_half_backslash_u_3 => { | |
| 1246 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1247 | const c = self.input[self.cursor]; | |
| 1248 | switch (c) { | |
| 1249 | '0'...'9' => { | |
| 1250 | self.utf16_code_units[1] |= c - '0'; | |
| 1251 | }, | |
| 1252 | 'A'...'F' => { | |
| 1253 | self.utf16_code_units[1] |= c - 'A' + 10; | |
| 1254 | }, | |
| 1255 | 'a'...'f' => { | |
| 1256 | self.utf16_code_units[1] |= c - 'a' + 10; | |
| 1257 | }, | |
| 1258 | else => return error.SyntaxError, | |
| 1259 | } | |
| 1260 | self.cursor += 1; | |
| 1261 | self.value_start = self.cursor; | |
| 1262 | self.state = .string; | |
| 1263 | const code_point = std.unicode.utf16DecodeSurrogatePair(&self.utf16_code_units) catch unreachable; | |
| 1264 | return partialStringCodepoint(code_point); | |
| 1265 | }, | |
| 1266 | ||
| 1267 | .string_utf8_last_byte => { | |
| 1268 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1269 | switch (self.input[self.cursor]) { | |
| 1270 | 0x80...0xBF => { | |
| 1271 | self.cursor += 1; | |
| 1272 | self.state = .string; | |
| 1273 | continue :state_loop; | |
| 1274 | }, | |
| 1275 | else => return error.SyntaxError, // Invalid UTF-8. | |
| 1276 | } | |
| 1277 | }, | |
| 1278 | .string_utf8_second_to_last_byte => { | |
| 1279 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1280 | switch (self.input[self.cursor]) { | |
| 1281 | 0x80...0xBF => { | |
| 1282 | self.cursor += 1; | |
| 1283 | self.state = .string_utf8_last_byte; | |
| 1284 | continue :state_loop; | |
| 1285 | }, | |
| 1286 | else => return error.SyntaxError, // Invalid UTF-8. | |
| 1287 | } | |
| 1288 | }, | |
| 1289 | .string_utf8_second_to_last_byte_guard_against_overlong => { | |
| 1290 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1291 | switch (self.input[self.cursor]) { | |
| 1292 | 0xA0...0xBF => { | |
| 1293 | self.cursor += 1; | |
| 1294 | self.state = .string_utf8_last_byte; | |
| 1295 | continue :state_loop; | |
| 1296 | }, | |
| 1297 | else => return error.SyntaxError, // Invalid UTF-8. | |
| 1298 | } | |
| 1299 | }, | |
| 1300 | .string_utf8_second_to_last_byte_guard_against_surrogate_half => { | |
| 1301 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1302 | switch (self.input[self.cursor]) { | |
| 1303 | 0x80...0x9F => { | |
| 1304 | self.cursor += 1; | |
| 1305 | self.state = .string_utf8_last_byte; | |
| 1306 | continue :state_loop; | |
| 1307 | }, | |
| 1308 | else => return error.SyntaxError, // Invalid UTF-8. | |
| 1309 | } | |
| 1310 | }, | |
| 1311 | .string_utf8_third_to_last_byte => { | |
| 1312 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1313 | switch (self.input[self.cursor]) { | |
| 1314 | 0x80...0xBF => { | |
| 1315 | self.cursor += 1; | |
| 1316 | self.state = .string_utf8_second_to_last_byte; | |
| 1317 | continue :state_loop; | |
| 1318 | }, | |
| 1319 | else => return error.SyntaxError, // Invalid UTF-8. | |
| 1320 | } | |
| 1321 | }, | |
| 1322 | .string_utf8_third_to_last_byte_guard_against_overlong => { | |
| 1323 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1324 | switch (self.input[self.cursor]) { | |
| 1325 | 0x90...0xBF => { | |
| 1326 | self.cursor += 1; | |
| 1327 | self.state = .string_utf8_second_to_last_byte; | |
| 1328 | continue :state_loop; | |
| 1329 | }, | |
| 1330 | else => return error.SyntaxError, // Invalid UTF-8. | |
| 1331 | } | |
| 1332 | }, | |
| 1333 | .string_utf8_third_to_last_byte_guard_against_too_large => { | |
| 1334 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1335 | switch (self.input[self.cursor]) { | |
| 1336 | 0x80...0x8F => { | |
| 1337 | self.cursor += 1; | |
| 1338 | self.state = .string_utf8_second_to_last_byte; | |
| 1339 | continue :state_loop; | |
| 1340 | }, | |
| 1341 | else => return error.SyntaxError, // Invalid UTF-8. | |
| 1342 | } | |
| 1343 | }, | |
| 1344 | ||
| 1345 | .literal_t => { | |
| 1346 | switch (try self.expectByte()) { | |
| 1347 | 'r' => { | |
| 1348 | self.cursor += 1; | |
| 1349 | self.state = .literal_tr; | |
| 1350 | continue :state_loop; | |
| 1351 | }, | |
| 1352 | else => return error.SyntaxError, | |
| 1353 | } | |
| 1354 | }, | |
| 1355 | .literal_tr => { | |
| 1356 | switch (try self.expectByte()) { | |
| 1357 | 'u' => { | |
| 1358 | self.cursor += 1; | |
| 1359 | self.state = .literal_tru; | |
| 1360 | continue :state_loop; | |
| 1361 | }, | |
| 1362 | else => return error.SyntaxError, | |
| 1363 | } | |
| 1364 | }, | |
| 1365 | .literal_tru => { | |
| 1366 | switch (try self.expectByte()) { | |
| 1367 | 'e' => { | |
| 1368 | self.cursor += 1; | |
| 1369 | self.state = .post_value; | |
| 1370 | return .true; | |
| 1371 | }, | |
| 1372 | else => return error.SyntaxError, | |
| 1373 | } | |
| 1374 | }, | |
| 1375 | .literal_f => { | |
| 1376 | switch (try self.expectByte()) { | |
| 1377 | 'a' => { | |
| 1378 | self.cursor += 1; | |
| 1379 | self.state = .literal_fa; | |
| 1380 | continue :state_loop; | |
| 1381 | }, | |
| 1382 | else => return error.SyntaxError, | |
| 1383 | } | |
| 1384 | }, | |
| 1385 | .literal_fa => { | |
| 1386 | switch (try self.expectByte()) { | |
| 1387 | 'l' => { | |
| 1388 | self.cursor += 1; | |
| 1389 | self.state = .literal_fal; | |
| 1390 | continue :state_loop; | |
| 1391 | }, | |
| 1392 | else => return error.SyntaxError, | |
| 1393 | } | |
| 1394 | }, | |
| 1395 | .literal_fal => { | |
| 1396 | switch (try self.expectByte()) { | |
| 1397 | 's' => { | |
| 1398 | self.cursor += 1; | |
| 1399 | self.state = .literal_fals; | |
| 1400 | continue :state_loop; | |
| 1401 | }, | |
| 1402 | else => return error.SyntaxError, | |
| 1403 | } | |
| 1404 | }, | |
| 1405 | .literal_fals => { | |
| 1406 | switch (try self.expectByte()) { | |
| 1407 | 'e' => { | |
| 1408 | self.cursor += 1; | |
| 1409 | self.state = .post_value; | |
| 1410 | return .false; | |
| 1411 | }, | |
| 1412 | else => return error.SyntaxError, | |
| 1413 | } | |
| 1414 | }, | |
| 1415 | .literal_n => { | |
| 1416 | switch (try self.expectByte()) { | |
| 1417 | 'u' => { | |
| 1418 | self.cursor += 1; | |
| 1419 | self.state = .literal_nu; | |
| 1420 | continue :state_loop; | |
| 1421 | }, | |
| 1422 | else => return error.SyntaxError, | |
| 1423 | } | |
| 1424 | }, | |
| 1425 | .literal_nu => { | |
| 1426 | switch (try self.expectByte()) { | |
| 1427 | 'l' => { | |
| 1428 | self.cursor += 1; | |
| 1429 | self.state = .literal_nul; | |
| 1430 | continue :state_loop; | |
| 1431 | }, | |
| 1432 | else => return error.SyntaxError, | |
| 1433 | } | |
| 1434 | }, | |
| 1435 | .literal_nul => { | |
| 1436 | switch (try self.expectByte()) { | |
| 1437 | 'l' => { | |
| 1438 | self.cursor += 1; | |
| 1439 | self.state = .post_value; | |
| 1440 | return .null; | |
| 1441 | }, | |
| 1442 | else => return error.SyntaxError, | |
| 1443 | } | |
| 1444 | }, | |
| 1445 | } | |
| 1446 | unreachable; | |
| 1447 | } | |
| 1448 | } | |
| 1449 | ||
| 1450 | /// Seeks ahead in the input until the first byte of the next token (or the end of the input) | |
| 1451 | /// determines which type of token will be returned from the next `next*()` call. | |
| 1452 | /// This function is idempotent, only advancing past commas, colons, and inter-token whitespace. | |
| 1453 | pub fn peekNextTokenType(self: *@This()) PeekError!TokenType { | |
| 1454 | state_loop: while (true) { | |
| 1455 | switch (self.state) { | |
| 1456 | .value => { | |
| 1457 | switch (try self.skipWhitespaceExpectByte()) { | |
| 1458 | '{' => return .object_begin, | |
| 1459 | '[' => return .array_begin, | |
| 1460 | '"' => return .string, | |
| 1461 | '-', '0'...'9' => return .number, | |
| 1462 | 't' => return .true, | |
| 1463 | 'f' => return .false, | |
| 1464 | 'n' => return .null, | |
| 1465 | else => return error.SyntaxError, | |
| 1466 | } | |
| 1467 | }, | |
| 1468 | ||
| 1469 | .post_value => { | |
| 1470 | if (try self.skipWhitespaceCheckEnd()) return .end_of_document; | |
| 1471 | ||
| 1472 | const c = self.input[self.cursor]; | |
| 1473 | if (self.string_is_object_key) { | |
| 1474 | self.string_is_object_key = false; | |
| 1475 | switch (c) { | |
| 1476 | ':' => { | |
| 1477 | self.cursor += 1; | |
| 1478 | self.state = .value; | |
| 1479 | continue :state_loop; | |
| 1480 | }, | |
| 1481 | else => return error.SyntaxError, | |
| 1482 | } | |
| 1483 | } | |
| 1484 | ||
| 1485 | switch (c) { | |
| 1486 | '}' => return .object_end, | |
| 1487 | ']' => return .array_end, | |
| 1488 | ',' => { | |
| 1489 | switch (self.stack.peek()) { | |
| 1490 | OBJECT_MODE => { | |
| 1491 | self.state = .object_post_comma; | |
| 1492 | }, | |
| 1493 | ARRAY_MODE => { | |
| 1494 | self.state = .value; | |
| 1495 | }, | |
| 1496 | } | |
| 1497 | self.cursor += 1; | |
| 1498 | continue :state_loop; | |
| 1499 | }, | |
| 1500 | else => return error.SyntaxError, | |
| 1501 | } | |
| 1502 | }, | |
| 1503 | ||
| 1504 | .object_start => { | |
| 1505 | switch (try self.skipWhitespaceExpectByte()) { | |
| 1506 | '"' => return .string, | |
| 1507 | '}' => return .object_end, | |
| 1508 | else => return error.SyntaxError, | |
| 1509 | } | |
| 1510 | }, | |
| 1511 | .object_post_comma => { | |
| 1512 | switch (try self.skipWhitespaceExpectByte()) { | |
| 1513 | '"' => return .string, | |
| 1514 | else => return error.SyntaxError, | |
| 1515 | } | |
| 1516 | }, | |
| 1517 | ||
| 1518 | .array_start => { | |
| 1519 | switch (try self.skipWhitespaceExpectByte()) { | |
| 1520 | ']' => return .array_end, | |
| 1521 | else => { | |
| 1522 | self.state = .value; | |
| 1523 | continue :state_loop; | |
| 1524 | }, | |
| 1525 | } | |
| 1526 | }, | |
| 1527 | ||
| 1528 | .number_minus, | |
| 1529 | .number_leading_zero, | |
| 1530 | .number_int, | |
| 1531 | .number_post_dot, | |
| 1532 | .number_frac, | |
| 1533 | .number_post_e, | |
| 1534 | .number_post_e_sign, | |
| 1535 | .number_exp, | |
| 1536 | => return .number, | |
| 1537 | ||
| 1538 | .string, | |
| 1539 | .string_backslash, | |
| 1540 | .string_backslash_u, | |
| 1541 | .string_backslash_u_1, | |
| 1542 | .string_backslash_u_2, | |
| 1543 | .string_backslash_u_3, | |
| 1544 | .string_surrogate_half, | |
| 1545 | .string_surrogate_half_backslash, | |
| 1546 | .string_surrogate_half_backslash_u, | |
| 1547 | .string_surrogate_half_backslash_u_1, | |
| 1548 | .string_surrogate_half_backslash_u_2, | |
| 1549 | .string_surrogate_half_backslash_u_3, | |
| 1550 | => return .string, | |
| 1551 | ||
| 1552 | .string_utf8_last_byte, | |
| 1553 | .string_utf8_second_to_last_byte, | |
| 1554 | .string_utf8_second_to_last_byte_guard_against_overlong, | |
| 1555 | .string_utf8_second_to_last_byte_guard_against_surrogate_half, | |
| 1556 | .string_utf8_third_to_last_byte, | |
| 1557 | .string_utf8_third_to_last_byte_guard_against_overlong, | |
| 1558 | .string_utf8_third_to_last_byte_guard_against_too_large, | |
| 1559 | => return .string, | |
| 1560 | ||
| 1561 | .literal_t, | |
| 1562 | .literal_tr, | |
| 1563 | .literal_tru, | |
| 1564 | => return .true, | |
| 1565 | .literal_f, | |
| 1566 | .literal_fa, | |
| 1567 | .literal_fal, | |
| 1568 | .literal_fals, | |
| 1569 | => return .false, | |
| 1570 | .literal_n, | |
| 1571 | .literal_nu, | |
| 1572 | .literal_nul, | |
| 1573 | => return .null, | |
| 1574 | } | |
| 1575 | unreachable; | |
| 1576 | } | |
| 1577 | } | |
| 1578 | ||
| 1579 | const State = enum { | |
| 1580 | value, | |
| 1581 | post_value, | |
| 1582 | ||
| 1583 | object_start, | |
| 1584 | object_post_comma, | |
| 1585 | ||
| 1586 | array_start, | |
| 1587 | ||
| 1588 | number_minus, | |
| 1589 | number_leading_zero, | |
| 1590 | number_int, | |
| 1591 | number_post_dot, | |
| 1592 | number_frac, | |
| 1593 | number_post_e, | |
| 1594 | number_post_e_sign, | |
| 1595 | number_exp, | |
| 1596 | ||
| 1597 | string, | |
| 1598 | string_backslash, | |
| 1599 | string_backslash_u, | |
| 1600 | string_backslash_u_1, | |
| 1601 | string_backslash_u_2, | |
| 1602 | string_backslash_u_3, | |
| 1603 | string_surrogate_half, | |
| 1604 | string_surrogate_half_backslash, | |
| 1605 | string_surrogate_half_backslash_u, | |
| 1606 | string_surrogate_half_backslash_u_1, | |
| 1607 | string_surrogate_half_backslash_u_2, | |
| 1608 | string_surrogate_half_backslash_u_3, | |
| 1609 | ||
| 1610 | // From http://unicode.org/mail-arch/unicode-ml/y2003-m02/att-0467/01-The_Algorithm_to_Valide_an_UTF-8_String | |
| 1611 | string_utf8_last_byte, // State A | |
| 1612 | string_utf8_second_to_last_byte, // State B | |
| 1613 | string_utf8_second_to_last_byte_guard_against_overlong, // State C | |
| 1614 | string_utf8_second_to_last_byte_guard_against_surrogate_half, // State D | |
| 1615 | string_utf8_third_to_last_byte, // State E | |
| 1616 | string_utf8_third_to_last_byte_guard_against_overlong, // State F | |
| 1617 | string_utf8_third_to_last_byte_guard_against_too_large, // State G | |
| 1618 | ||
| 1619 | literal_t, | |
| 1620 | literal_tr, | |
| 1621 | literal_tru, | |
| 1622 | literal_f, | |
| 1623 | literal_fa, | |
| 1624 | literal_fal, | |
| 1625 | literal_fals, | |
| 1626 | literal_n, | |
| 1627 | literal_nu, | |
| 1628 | literal_nul, | |
| 1629 | }; | |
| 1630 | ||
| 1631 | fn expectByte(self: *const @This()) !u8 { | |
| 1632 | if (self.cursor < self.input.len) { | |
| 1633 | return self.input[self.cursor]; | |
| 1634 | } | |
| 1635 | // No byte. | |
| 1636 | if (self.is_end_of_input) return error.UnexpectedEndOfInput; | |
| 1637 | return error.BufferUnderrun; | |
| 1638 | } | |
| 1639 | ||
| 1640 | fn skipWhitespace(self: *@This()) void { | |
| 1641 | while (self.cursor < self.input.len) : (self.cursor += 1) { | |
| 1642 | switch (self.input[self.cursor]) { | |
| 1643 | // Whitespace | |
| 1644 | ' ', '\t', '\r' => continue, | |
| 1645 | '\n' => { | |
| 1646 | if (self.diagnostics) |diag| { | |
| 1647 | diag.line_number += 1; | |
| 1648 | // This will count the newline itself, | |
| 1649 | // which means a straight-forward subtraction will give a 1-based column number. | |
| 1650 | diag.line_start_cursor = self.cursor; | |
| 1651 | } | |
| 1652 | continue; | |
| 1653 | }, | |
| 1654 | else => return, | |
| 1655 | } | |
| 1656 | } | |
| 1657 | } | |
| 1658 | ||
| 1659 | fn skipWhitespaceExpectByte(self: *@This()) !u8 { | |
| 1660 | self.skipWhitespace(); | |
| 1661 | return self.expectByte(); | |
| 1662 | } | |
| 1663 | ||
| 1664 | fn skipWhitespaceCheckEnd(self: *@This()) !bool { | |
| 1665 | self.skipWhitespace(); | |
| 1666 | if (self.cursor >= self.input.len) { | |
| 1667 | // End of buffer. | |
| 1668 | if (self.is_end_of_input) { | |
| 1669 | // End of everything. | |
| 1670 | if (self.stackHeight() == 0) { | |
| 1671 | // We did it! | |
| 1672 | return true; | |
| 1673 | } | |
| 1674 | return error.UnexpectedEndOfInput; | |
| 1675 | } | |
| 1676 | return error.BufferUnderrun; | |
| 1677 | } | |
| 1678 | if (self.stackHeight() == 0) return error.SyntaxError; | |
| 1679 | return false; | |
| 1680 | } | |
| 1681 | ||
| 1682 | fn takeValueSlice(self: *@This()) []const u8 { | |
| 1683 | const slice = self.input[self.value_start..self.cursor]; | |
| 1684 | self.value_start = self.cursor; | |
| 1685 | return slice; | |
| 1686 | } | |
| 1687 | fn takeValueSliceMinusTrailingOffset(self: *@This(), trailing_negative_offset: usize) []const u8 { | |
| 1688 | // Check if the escape sequence started before the current input buffer. | |
| 1689 | // (The algebra here is awkward to avoid unsigned underflow, | |
| 1690 | // but it's just making sure the slice on the next line isn't UB.) | |
| 1691 | if (self.cursor <= self.value_start + trailing_negative_offset) return ""; | |
| 1692 | const slice = self.input[self.value_start .. self.cursor - trailing_negative_offset]; | |
| 1693 | // When trailing_negative_offset is non-zero, setting self.value_start doesn't matter, | |
| 1694 | // because we always set it again while emitting the .partial_string_escaped_*. | |
| 1695 | self.value_start = self.cursor; | |
| 1696 | return slice; | |
| 1697 | } | |
| 1698 | ||
| 1699 | fn endOfBufferInNumber(self: *@This(), allow_end: bool) !Token { | |
| 1700 | const slice = self.takeValueSlice(); | |
| 1701 | if (self.is_end_of_input) { | |
| 1702 | if (!allow_end) return error.UnexpectedEndOfInput; | |
| 1703 | self.state = .post_value; | |
| 1704 | return Token{ .number = slice }; | |
| 1705 | } | |
| 1706 | if (slice.len == 0) return error.BufferUnderrun; | |
| 1707 | return Token{ .partial_number = slice }; | |
| 1708 | } | |
| 1709 | ||
| 1710 | fn endOfBufferInString(self: *@This()) !Token { | |
| 1711 | if (self.is_end_of_input) return error.UnexpectedEndOfInput; | |
| 1712 | const slice = self.takeValueSliceMinusTrailingOffset(switch (self.state) { | |
| 1713 | // Don't include the escape sequence in the partial string. | |
| 1714 | .string_backslash => 1, | |
| 1715 | .string_backslash_u => 2, | |
| 1716 | .string_backslash_u_1 => 3, | |
| 1717 | .string_backslash_u_2 => 4, | |
| 1718 | .string_backslash_u_3 => 5, | |
| 1719 | .string_surrogate_half => 6, | |
| 1720 | .string_surrogate_half_backslash => 7, | |
| 1721 | .string_surrogate_half_backslash_u => 8, | |
| 1722 | .string_surrogate_half_backslash_u_1 => 9, | |
| 1723 | .string_surrogate_half_backslash_u_2 => 10, | |
| 1724 | .string_surrogate_half_backslash_u_3 => 11, | |
| 1725 | ||
| 1726 | // Include everything up to the cursor otherwise. | |
| 1727 | .string, | |
| 1728 | .string_utf8_last_byte, | |
| 1729 | .string_utf8_second_to_last_byte, | |
| 1730 | .string_utf8_second_to_last_byte_guard_against_overlong, | |
| 1731 | .string_utf8_second_to_last_byte_guard_against_surrogate_half, | |
| 1732 | .string_utf8_third_to_last_byte, | |
| 1733 | .string_utf8_third_to_last_byte_guard_against_overlong, | |
| 1734 | .string_utf8_third_to_last_byte_guard_against_too_large, | |
| 1735 | => 0, | |
| 1736 | ||
| 1737 | else => unreachable, | |
| 1738 | }); | |
| 1739 | if (slice.len == 0) return error.BufferUnderrun; | |
| 1740 | return Token{ .partial_string = slice }; | |
| 1741 | } | |
| 1742 | ||
| 1743 | fn partialStringCodepoint(code_point: u21) Token { | |
| 1744 | var buf: [4]u8 = undefined; | |
| 1745 | switch (std.unicode.utf8Encode(code_point, &buf) catch unreachable) { | |
| 1746 | 1 => return Token{ .partial_string_escaped_1 = buf[0..1].* }, | |
| 1747 | 2 => return Token{ .partial_string_escaped_2 = buf[0..2].* }, | |
| 1748 | 3 => return Token{ .partial_string_escaped_3 = buf[0..3].* }, | |
| 1749 | 4 => return Token{ .partial_string_escaped_4 = buf[0..4].* }, | |
| 1750 | else => unreachable, | |
| 1751 | } | |
| 1752 | } | |
| 1753 | }; | |
| 1754 | ||
| 1755 | const OBJECT_MODE = 0; | |
| 1756 | const ARRAY_MODE = 1; | |
| 1757 | ||
| 1758 | fn appendSlice(list: *std.ArrayList(u8), buf: []const u8, max_value_len: usize) !void { | |
| 1759 | const new_len = std.math.add(usize, list.items.len, buf.len) catch return error.ValueTooLong; | |
| 1760 | if (new_len > max_value_len) return error.ValueTooLong; | |
| 1761 | try list.appendSlice(buf); | |
| 1762 | } | |
| 1763 | ||
| 1764 | /// For the slice you get from a `Token.number` or `Token.allocated_number`, | |
| 1765 | /// this function returns true if the number doesn't contain any fraction or exponent components, and is not `-0`. | |
| 1766 | /// Note, the numeric value encoded by the value may still be an integer, such as `1.0`. | |
| 1767 | /// This function is meant to give a hint about whether integer parsing or float parsing should be used on the value. | |
| 1768 | /// This function will not give meaningful results on non-numeric input. | |
| 1769 | pub fn isNumberFormattedLikeAnInteger(value: []const u8) bool { | |
| 1770 | if (std.mem.eql(u8, value, "-0")) return false; | |
| 1771 | return std.mem.indexOfAny(u8, value, ".eE") == null; | |
| 1772 | } | |
| 1773 | ||
| 1774 | test { | |
| 1775 | _ = @import("./scanner_test.zig"); | |
| 1776 | } |
lib/std/json/scanner_test.zig+39-39| ... | ... | @@ -1,13 +1,11 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const JsonScanner = @import("./scanner.zig").Scanner; | |
| 3 | const jsonReader = @import("./scanner.zig").reader; | |
| 4 | const JsonReader = @import("./scanner.zig").Reader; | |
| 5 | const Token = @import("./scanner.zig").Token; | |
| 6 | const TokenType = @import("./scanner.zig").TokenType; | |
| 7 | const Diagnostics = @import("./scanner.zig").Diagnostics; | |
| 8 | const Error = @import("./scanner.zig").Error; | |
| 9 | const validate = @import("./scanner.zig").validate; | |
| 10 | const isNumberFormattedLikeAnInteger = @import("./scanner.zig").isNumberFormattedLikeAnInteger; | |
| 2 | const Scanner = @import("Scanner.zig"); | |
| 3 | const Token = Scanner.Token; | |
| 4 | const TokenType = Scanner.TokenType; | |
| 5 | const Diagnostics = Scanner.Diagnostics; | |
| 6 | const Error = Scanner.Error; | |
| 7 | const validate = Scanner.validate; | |
| 8 | const isNumberFormattedLikeAnInteger = Scanner.isNumberFormattedLikeAnInteger; | |
| 11 | 9 | |
| 12 | 10 | const example_document_str = |
| 13 | 11 | \\{ |
| ... | ... | @@ -36,7 +34,7 @@ fn expectPeekNext(scanner_or_reader: anytype, expected_token_type: TokenType, ex |
| 36 | 34 | } |
| 37 | 35 | |
| 38 | 36 | test "token" { |
| 39 | var scanner = JsonScanner.initCompleteInput(std.testing.allocator, example_document_str); | |
| 37 | var scanner = Scanner.initCompleteInput(std.testing.allocator, example_document_str); | |
| 40 | 38 | defer scanner.deinit(); |
| 41 | 39 | |
| 42 | 40 | try expectNext(&scanner, .object_begin); |
| ... | ... | @@ -138,23 +136,25 @@ fn testAllTypes(source: anytype, large_buffer: bool) !void { |
| 138 | 136 | } |
| 139 | 137 | |
| 140 | 138 | test "peek all types" { |
| 141 | var scanner = JsonScanner.initCompleteInput(std.testing.allocator, all_types_test_case); | |
| 139 | var scanner = Scanner.initCompleteInput(std.testing.allocator, all_types_test_case); | |
| 142 | 140 | defer scanner.deinit(); |
| 143 | 141 | try testAllTypes(&scanner, true); |
| 144 | 142 | |
| 145 | var stream = std.io.fixedBufferStream(all_types_test_case); | |
| 146 | var json_reader = jsonReader(std.testing.allocator, stream.reader()); | |
| 143 | var stream: std.Io.Reader = .fixed(all_types_test_case); | |
| 144 | var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream); | |
| 147 | 145 | defer json_reader.deinit(); |
| 148 | 146 | try testAllTypes(&json_reader, true); |
| 149 | 147 | |
| 150 | var tiny_stream = std.io.fixedBufferStream(all_types_test_case); | |
| 151 | var tiny_json_reader = JsonReader(1, @TypeOf(tiny_stream.reader())).init(std.testing.allocator, tiny_stream.reader()); | |
| 148 | var tiny_buffer: [1]u8 = undefined; | |
| 149 | var tiny_stream: std.testing.Reader = .init(&tiny_buffer, &.{.{ .buffer = all_types_test_case }}); | |
| 150 | tiny_stream.artificial_limit = .limited(1); | |
| 151 | var tiny_json_reader: Scanner.Reader = .init(std.testing.allocator, &tiny_stream.interface); | |
| 152 | 152 | defer tiny_json_reader.deinit(); |
| 153 | 153 | try testAllTypes(&tiny_json_reader, false); |
| 154 | 154 | } |
| 155 | 155 | |
| 156 | 156 | test "token mismatched close" { |
| 157 | var scanner = JsonScanner.initCompleteInput(std.testing.allocator, "[102, 111, 111 }"); | |
| 157 | var scanner = Scanner.initCompleteInput(std.testing.allocator, "[102, 111, 111 }"); | |
| 158 | 158 | defer scanner.deinit(); |
| 159 | 159 | try expectNext(&scanner, .array_begin); |
| 160 | 160 | try expectNext(&scanner, Token{ .number = "102" }); |
| ... | ... | @@ -164,15 +164,15 @@ test "token mismatched close" { |
| 164 | 164 | } |
| 165 | 165 | |
| 166 | 166 | test "token premature object close" { |
| 167 | var scanner = JsonScanner.initCompleteInput(std.testing.allocator, "{ \"key\": }"); | |
| 167 | var scanner = Scanner.initCompleteInput(std.testing.allocator, "{ \"key\": }"); | |
| 168 | 168 | defer scanner.deinit(); |
| 169 | 169 | try expectNext(&scanner, .object_begin); |
| 170 | 170 | try expectNext(&scanner, Token{ .string = "key" }); |
| 171 | 171 | try std.testing.expectError(error.SyntaxError, scanner.next()); |
| 172 | 172 | } |
| 173 | 173 | |
| 174 | test "JsonScanner basic" { | |
| 175 | var scanner = JsonScanner.initCompleteInput(std.testing.allocator, example_document_str); | |
| 174 | test "Scanner basic" { | |
| 175 | var scanner = Scanner.initCompleteInput(std.testing.allocator, example_document_str); | |
| 176 | 176 | defer scanner.deinit(); |
| 177 | 177 | |
| 178 | 178 | while (true) { |
| ... | ... | @@ -181,10 +181,10 @@ test "JsonScanner basic" { |
| 181 | 181 | } |
| 182 | 182 | } |
| 183 | 183 | |
| 184 | test "JsonReader basic" { | |
| 185 | var stream = std.io.fixedBufferStream(example_document_str); | |
| 184 | test "Scanner.Reader basic" { | |
| 185 | var stream: std.Io.Reader = .fixed(example_document_str); | |
| 186 | 186 | |
| 187 | var json_reader = jsonReader(std.testing.allocator, stream.reader()); | |
| 187 | var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream); | |
| 188 | 188 | defer json_reader.deinit(); |
| 189 | 189 | |
| 190 | 190 | while (true) { |
| ... | ... | @@ -215,7 +215,7 @@ const number_test_items = blk: { |
| 215 | 215 | |
| 216 | 216 | test "numbers" { |
| 217 | 217 | for (number_test_items) |number_str| { |
| 218 | var scanner = JsonScanner.initCompleteInput(std.testing.allocator, number_str); | |
| 218 | var scanner = Scanner.initCompleteInput(std.testing.allocator, number_str); | |
| 219 | 219 | defer scanner.deinit(); |
| 220 | 220 | |
| 221 | 221 | const token = try scanner.next(); |
| ... | ... | @@ -243,10 +243,10 @@ const string_test_cases = .{ |
| 243 | 243 | |
| 244 | 244 | test "strings" { |
| 245 | 245 | inline for (string_test_cases) |tuple| { |
| 246 | var stream = std.io.fixedBufferStream("\"" ++ tuple[0] ++ "\""); | |
| 246 | var stream: std.Io.Reader = .fixed("\"" ++ tuple[0] ++ "\""); | |
| 247 | 247 | var arena = std.heap.ArenaAllocator.init(std.testing.allocator); |
| 248 | 248 | defer arena.deinit(); |
| 249 | var json_reader = jsonReader(std.testing.allocator, stream.reader()); | |
| 249 | var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream); | |
| 250 | 250 | defer json_reader.deinit(); |
| 251 | 251 | |
| 252 | 252 | const token = try json_reader.nextAlloc(arena.allocator(), .alloc_if_needed); |
| ... | ... | @@ -289,7 +289,7 @@ test "nesting" { |
| 289 | 289 | } |
| 290 | 290 | |
| 291 | 291 | fn expectMaybeError(document_str: []const u8, maybe_error: ?Error) !void { |
| 292 | var scanner = JsonScanner.initCompleteInput(std.testing.allocator, document_str); | |
| 292 | var scanner = Scanner.initCompleteInput(std.testing.allocator, document_str); | |
| 293 | 293 | defer scanner.deinit(); |
| 294 | 294 | |
| 295 | 295 | while (true) { |
| ... | ... | @@ -352,12 +352,12 @@ fn expectEqualTokens(expected_token: Token, actual_token: Token) !void { |
| 352 | 352 | } |
| 353 | 353 | |
| 354 | 354 | fn testTinyBufferSize(document_str: []const u8) !void { |
| 355 | var tiny_stream = std.io.fixedBufferStream(document_str); | |
| 356 | var normal_stream = std.io.fixedBufferStream(document_str); | |
| 355 | var tiny_stream: std.Io.Reader = .fixed(document_str); | |
| 356 | var normal_stream: std.Io.Reader = .fixed(document_str); | |
| 357 | 357 | |
| 358 | var tiny_json_reader = JsonReader(1, @TypeOf(tiny_stream.reader())).init(std.testing.allocator, tiny_stream.reader()); | |
| 358 | var tiny_json_reader: Scanner.Reader = .init(std.testing.allocator, &tiny_stream); | |
| 359 | 359 | defer tiny_json_reader.deinit(); |
| 360 | var normal_json_reader = JsonReader(0x1000, @TypeOf(normal_stream.reader())).init(std.testing.allocator, normal_stream.reader()); | |
| 360 | var normal_json_reader: Scanner.Reader = .init(std.testing.allocator, &normal_stream); | |
| 361 | 361 | defer normal_json_reader.deinit(); |
| 362 | 362 | |
| 363 | 363 | expectEqualStreamOfTokens(&normal_json_reader, &tiny_json_reader) catch |err| { |
| ... | ... | @@ -397,13 +397,13 @@ test "validate" { |
| 397 | 397 | } |
| 398 | 398 | |
| 399 | 399 | fn testSkipValue(s: []const u8) !void { |
| 400 | var scanner = JsonScanner.initCompleteInput(std.testing.allocator, s); | |
| 400 | var scanner = Scanner.initCompleteInput(std.testing.allocator, s); | |
| 401 | 401 | defer scanner.deinit(); |
| 402 | 402 | try scanner.skipValue(); |
| 403 | 403 | try expectEqualTokens(.end_of_document, try scanner.next()); |
| 404 | 404 | |
| 405 | var stream = std.io.fixedBufferStream(s); | |
| 406 | var json_reader = jsonReader(std.testing.allocator, stream.reader()); | |
| 405 | var stream: std.Io.Reader = .fixed(s); | |
| 406 | var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream); | |
| 407 | 407 | defer json_reader.deinit(); |
| 408 | 408 | try json_reader.skipValue(); |
| 409 | 409 | try expectEqualTokens(.end_of_document, try json_reader.next()); |
| ... | ... | @@ -441,7 +441,7 @@ fn testEnsureStackCapacity(do_ensure: bool) !void { |
| 441 | 441 | try input_string.appendNTimes(std.testing.allocator, ']', nestings); |
| 442 | 442 | defer input_string.deinit(std.testing.allocator); |
| 443 | 443 | |
| 444 | var scanner = JsonScanner.initCompleteInput(failing_allocator, input_string.items); | |
| 444 | var scanner = Scanner.initCompleteInput(failing_allocator, input_string.items); | |
| 445 | 445 | defer scanner.deinit(); |
| 446 | 446 | |
| 447 | 447 | if (do_ensure) { |
| ... | ... | @@ -473,17 +473,17 @@ fn testDiagnosticsFromSource(expected_error: ?anyerror, line: u64, col: u64, byt |
| 473 | 473 | try std.testing.expectEqual(byte_offset, diagnostics.getByteOffset()); |
| 474 | 474 | } |
| 475 | 475 | fn testDiagnostics(expected_error: ?anyerror, line: u64, col: u64, byte_offset: u64, s: []const u8) !void { |
| 476 | var scanner = JsonScanner.initCompleteInput(std.testing.allocator, s); | |
| 476 | var scanner = Scanner.initCompleteInput(std.testing.allocator, s); | |
| 477 | 477 | defer scanner.deinit(); |
| 478 | 478 | try testDiagnosticsFromSource(expected_error, line, col, byte_offset, &scanner); |
| 479 | 479 | |
| 480 | var tiny_stream = std.io.fixedBufferStream(s); | |
| 481 | var tiny_json_reader = JsonReader(1, @TypeOf(tiny_stream.reader())).init(std.testing.allocator, tiny_stream.reader()); | |
| 480 | var tiny_stream: std.Io.Reader = .fixed(s); | |
| 481 | var tiny_json_reader: Scanner.Reader = .init(std.testing.allocator, &tiny_stream); | |
| 482 | 482 | defer tiny_json_reader.deinit(); |
| 483 | 483 | try testDiagnosticsFromSource(expected_error, line, col, byte_offset, &tiny_json_reader); |
| 484 | 484 | |
| 485 | var medium_stream = std.io.fixedBufferStream(s); | |
| 486 | var medium_json_reader = JsonReader(5, @TypeOf(medium_stream.reader())).init(std.testing.allocator, medium_stream.reader()); | |
| 485 | var medium_stream: std.Io.Reader = .fixed(s); | |
| 486 | var medium_json_reader: Scanner.Reader = .init(std.testing.allocator, &medium_stream); | |
| 487 | 487 | defer medium_json_reader.deinit(); |
| 488 | 488 | try testDiagnosticsFromSource(expected_error, line, col, byte_offset, &medium_json_reader); |
| 489 | 489 | } |
lib/std/json/static.zig+5-5| ... | ... | @@ -4,11 +4,11 @@ const Allocator = std.mem.Allocator; |
| 4 | 4 | const ArenaAllocator = std.heap.ArenaAllocator; |
| 5 | 5 | const ArrayList = std.ArrayList; |
| 6 | 6 | |
| 7 | const Scanner = @import("./scanner.zig").Scanner; | |
| 8 | const Token = @import("./scanner.zig").Token; | |
| 9 | const AllocWhen = @import("./scanner.zig").AllocWhen; | |
| 10 | const default_max_value_len = @import("./scanner.zig").default_max_value_len; | |
| 11 | const isNumberFormattedLikeAnInteger = @import("./scanner.zig").isNumberFormattedLikeAnInteger; | |
| 7 | const Scanner = @import("Scanner.zig"); | |
| 8 | const Token = Scanner.Token; | |
| 9 | const AllocWhen = Scanner.AllocWhen; | |
| 10 | const default_max_value_len = Scanner.default_max_value_len; | |
| 11 | const isNumberFormattedLikeAnInteger = Scanner.isNumberFormattedLikeAnInteger; | |
| 12 | 12 | |
| 13 | 13 | const Value = @import("./dynamic.zig").Value; |
| 14 | 14 | const Array = @import("./dynamic.zig").Array; |
lib/std/json/static_test.zig+14-16| ... | ... | @@ -12,9 +12,7 @@ const parseFromValue = @import("./static.zig").parseFromValue; |
| 12 | 12 | const parseFromValueLeaky = @import("./static.zig").parseFromValueLeaky; |
| 13 | 13 | const ParseOptions = @import("./static.zig").ParseOptions; |
| 14 | 14 | |
| 15 | const JsonScanner = @import("./scanner.zig").Scanner; | |
| 16 | const jsonReader = @import("./scanner.zig").reader; | |
| 17 | const Diagnostics = @import("./scanner.zig").Diagnostics; | |
| 15 | const Scanner = @import("Scanner.zig"); | |
| 18 | 16 | |
| 19 | 17 | const Value = @import("./dynamic.zig").Value; |
| 20 | 18 | |
| ... | ... | @@ -300,9 +298,9 @@ const subnamespaces_0_doc = |
| 300 | 298 | fn testAllParseFunctions(comptime T: type, expected: T, doc: []const u8) !void { |
| 301 | 299 | // First do the one with the debug info in case we get a SyntaxError or something. |
| 302 | 300 | { |
| 303 | var scanner = JsonScanner.initCompleteInput(testing.allocator, doc); | |
| 301 | var scanner = Scanner.initCompleteInput(testing.allocator, doc); | |
| 304 | 302 | defer scanner.deinit(); |
| 305 | var diagnostics = Diagnostics{}; | |
| 303 | var diagnostics = Scanner.Diagnostics{}; | |
| 306 | 304 | scanner.enableDiagnostics(&diagnostics); |
| 307 | 305 | var parsed = parseFromTokenSource(T, testing.allocator, &scanner, .{}) catch |e| { |
| 308 | 306 | std.debug.print("at line,col: {}:{}\n", .{ diagnostics.getLine(), diagnostics.getColumn() }); |
| ... | ... | @@ -317,8 +315,8 @@ fn testAllParseFunctions(comptime T: type, expected: T, doc: []const u8) !void { |
| 317 | 315 | try testing.expectEqualDeep(expected, parsed.value); |
| 318 | 316 | } |
| 319 | 317 | { |
| 320 | var stream = std.io.fixedBufferStream(doc); | |
| 321 | var json_reader = jsonReader(std.testing.allocator, stream.reader()); | |
| 318 | var stream: std.Io.Reader = .fixed(doc); | |
| 319 | var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream); | |
| 322 | 320 | defer json_reader.deinit(); |
| 323 | 321 | var parsed = try parseFromTokenSource(T, testing.allocator, &json_reader, .{}); |
| 324 | 322 | defer parsed.deinit(); |
| ... | ... | @@ -331,13 +329,13 @@ fn testAllParseFunctions(comptime T: type, expected: T, doc: []const u8) !void { |
| 331 | 329 | try testing.expectEqualDeep(expected, try parseFromSliceLeaky(T, arena.allocator(), doc, .{})); |
| 332 | 330 | } |
| 333 | 331 | { |
| 334 | var scanner = JsonScanner.initCompleteInput(testing.allocator, doc); | |
| 332 | var scanner = Scanner.initCompleteInput(testing.allocator, doc); | |
| 335 | 333 | defer scanner.deinit(); |
| 336 | 334 | try testing.expectEqualDeep(expected, try parseFromTokenSourceLeaky(T, arena.allocator(), &scanner, .{})); |
| 337 | 335 | } |
| 338 | 336 | { |
| 339 | var stream = std.io.fixedBufferStream(doc); | |
| 340 | var json_reader = jsonReader(std.testing.allocator, stream.reader()); | |
| 337 | var stream: std.Io.Reader = .fixed(doc); | |
| 338 | var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream); | |
| 341 | 339 | defer json_reader.deinit(); |
| 342 | 340 | try testing.expectEqualDeep(expected, try parseFromTokenSourceLeaky(T, arena.allocator(), &json_reader, .{})); |
| 343 | 341 | } |
| ... | ... | @@ -763,7 +761,7 @@ test "parse exponential into int" { |
| 763 | 761 | |
| 764 | 762 | test "parseFromTokenSource" { |
| 765 | 763 | { |
| 766 | var scanner = JsonScanner.initCompleteInput(testing.allocator, "123"); | |
| 764 | var scanner = Scanner.initCompleteInput(testing.allocator, "123"); | |
| 767 | 765 | defer scanner.deinit(); |
| 768 | 766 | var parsed = try parseFromTokenSource(u32, testing.allocator, &scanner, .{}); |
| 769 | 767 | defer parsed.deinit(); |
| ... | ... | @@ -771,8 +769,8 @@ test "parseFromTokenSource" { |
| 771 | 769 | } |
| 772 | 770 | |
| 773 | 771 | { |
| 774 | var stream = std.io.fixedBufferStream("123"); | |
| 775 | var json_reader = jsonReader(std.testing.allocator, stream.reader()); | |
| 772 | var stream: std.Io.Reader = .fixed("123"); | |
| 773 | var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream); | |
| 776 | 774 | defer json_reader.deinit(); |
| 777 | 775 | var parsed = try parseFromTokenSource(u32, testing.allocator, &json_reader, .{}); |
| 778 | 776 | defer parsed.deinit(); |
| ... | ... | @@ -836,7 +834,7 @@ test "json parse partial" { |
| 836 | 834 | \\} |
| 837 | 835 | ; |
| 838 | 836 | const allocator = testing.allocator; |
| 839 | var scanner = JsonScanner.initCompleteInput(allocator, str); | |
| 837 | var scanner = Scanner.initCompleteInput(allocator, str); | |
| 840 | 838 | defer scanner.deinit(); |
| 841 | 839 | |
| 842 | 840 | var arena = ArenaAllocator.init(allocator); |
| ... | ... | @@ -886,8 +884,8 @@ test "json parse allocate when streaming" { |
| 886 | 884 | var arena = ArenaAllocator.init(allocator); |
| 887 | 885 | defer arena.deinit(); |
| 888 | 886 | |
| 889 | var stream = std.io.fixedBufferStream(str); | |
| 890 | var json_reader = jsonReader(std.testing.allocator, stream.reader()); | |
| 887 | var stream: std.Io.Reader = .fixed(str); | |
| 888 | var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream); | |
| 891 | 889 | |
| 892 | 890 | const parsed = parseFromTokenSourceLeaky(T, arena.allocator(), &json_reader, .{}) catch |err| { |
| 893 | 891 | json_reader.deinit(); |
lib/std/json/stringify.zig deleted-772| ... | ... | @@ -1,772 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | const assert = std.debug.assert; | |
| 3 | const Allocator = std.mem.Allocator; | |
| 4 | const ArrayList = std.ArrayList; | |
| 5 | const BitStack = std.BitStack; | |
| 6 | ||
| 7 | const OBJECT_MODE = 0; | |
| 8 | const ARRAY_MODE = 1; | |
| 9 | ||
| 10 | pub const StringifyOptions = struct { | |
| 11 | /// Controls the whitespace emitted. | |
| 12 | /// The default `.minified` is a compact encoding with no whitespace between tokens. | |
| 13 | /// Any setting other than `.minified` will use newlines, indentation, and a space after each ':'. | |
| 14 | /// `.indent_1` means 1 space for each indentation level, `.indent_2` means 2 spaces, etc. | |
| 15 | /// `.indent_tab` uses a tab for each indentation level. | |
| 16 | whitespace: enum { | |
| 17 | minified, | |
| 18 | indent_1, | |
| 19 | indent_2, | |
| 20 | indent_3, | |
| 21 | indent_4, | |
| 22 | indent_8, | |
| 23 | indent_tab, | |
| 24 | } = .minified, | |
| 25 | ||
| 26 | /// Should optional fields with null value be written? | |
| 27 | emit_null_optional_fields: bool = true, | |
| 28 | ||
| 29 | /// Arrays/slices of u8 are typically encoded as JSON strings. | |
| 30 | /// This option emits them as arrays of numbers instead. | |
| 31 | /// Does not affect calls to `objectField*()`. | |
| 32 | emit_strings_as_arrays: bool = false, | |
| 33 | ||
| 34 | /// Should unicode characters be escaped in strings? | |
| 35 | escape_unicode: bool = false, | |
| 36 | ||
| 37 | /// When true, renders numbers outside the range `+-1<<53` (the precise integer range of f64) as JSON strings in base 10. | |
| 38 | emit_nonportable_numbers_as_strings: bool = false, | |
| 39 | }; | |
| 40 | ||
| 41 | /// Writes the given value to the `std.io.GenericWriter` stream. | |
| 42 | /// See `WriteStream` for how the given value is serialized into JSON. | |
| 43 | /// The maximum nesting depth of the output JSON document is 256. | |
| 44 | /// See also `stringifyMaxDepth` and `stringifyArbitraryDepth`. | |
| 45 | pub fn stringify( | |
| 46 | value: anytype, | |
| 47 | options: StringifyOptions, | |
| 48 | out_stream: anytype, | |
| 49 | ) @TypeOf(out_stream).Error!void { | |
| 50 | var jw = writeStream(out_stream, options); | |
| 51 | defer jw.deinit(); | |
| 52 | try jw.write(value); | |
| 53 | } | |
| 54 | ||
| 55 | /// Like `stringify` with configurable nesting depth. | |
| 56 | /// `max_depth` is rounded up to the nearest multiple of 8. | |
| 57 | /// Give `null` for `max_depth` to disable some safety checks and allow arbitrary nesting depth. | |
| 58 | /// See `writeStreamMaxDepth` for more info. | |
| 59 | pub fn stringifyMaxDepth( | |
| 60 | value: anytype, | |
| 61 | options: StringifyOptions, | |
| 62 | out_stream: anytype, | |
| 63 | comptime max_depth: ?usize, | |
| 64 | ) @TypeOf(out_stream).Error!void { | |
| 65 | var jw = writeStreamMaxDepth(out_stream, options, max_depth); | |
| 66 | try jw.write(value); | |
| 67 | } | |
| 68 | ||
| 69 | /// Like `stringify` but takes an allocator to facilitate safety checks while allowing arbitrary nesting depth. | |
| 70 | /// These safety checks can be helpful when debugging custom `jsonStringify` implementations; | |
| 71 | /// See `WriteStream`. | |
| 72 | pub fn stringifyArbitraryDepth( | |
| 73 | allocator: Allocator, | |
| 74 | value: anytype, | |
| 75 | options: StringifyOptions, | |
| 76 | out_stream: anytype, | |
| 77 | ) WriteStream(@TypeOf(out_stream), .checked_to_arbitrary_depth).Error!void { | |
| 78 | var jw = writeStreamArbitraryDepth(allocator, out_stream, options); | |
| 79 | defer jw.deinit(); | |
| 80 | try jw.write(value); | |
| 81 | } | |
| 82 | ||
| 83 | /// Calls `stringifyArbitraryDepth` and stores the result in dynamically allocated memory | |
| 84 | /// instead of taking a `std.io.GenericWriter`. | |
| 85 | /// | |
| 86 | /// Caller owns returned memory. | |
| 87 | pub fn stringifyAlloc( | |
| 88 | allocator: Allocator, | |
| 89 | value: anytype, | |
| 90 | options: StringifyOptions, | |
| 91 | ) error{OutOfMemory}![]u8 { | |
| 92 | var list = std.ArrayList(u8).init(allocator); | |
| 93 | errdefer list.deinit(); | |
| 94 | try stringifyArbitraryDepth(allocator, value, options, list.writer()); | |
| 95 | return list.toOwnedSlice(); | |
| 96 | } | |
| 97 | ||
| 98 | /// See `WriteStream` for documentation. | |
| 99 | /// Equivalent to calling `writeStreamMaxDepth` with a depth of `256`. | |
| 100 | /// | |
| 101 | /// The caller does *not* need to call `deinit()` on the returned object. | |
| 102 | pub fn writeStream( | |
| 103 | out_stream: anytype, | |
| 104 | options: StringifyOptions, | |
| 105 | ) WriteStream(@TypeOf(out_stream), .{ .checked_to_fixed_depth = 256 }) { | |
| 106 | return writeStreamMaxDepth(out_stream, options, 256); | |
| 107 | } | |
| 108 | ||
| 109 | /// See `WriteStream` for documentation. | |
| 110 | /// The returned object includes 1 bit of size per `max_depth` to enable safety checks on the order of method calls; | |
| 111 | /// see the grammar in the `WriteStream` documentation. | |
| 112 | /// `max_depth` is rounded up to the nearest multiple of 8. | |
| 113 | /// If the nesting depth exceeds `max_depth`, it is detectable illegal behavior. | |
| 114 | /// Give `null` for `max_depth` to disable safety checks for the grammar and allow arbitrary nesting depth. | |
| 115 | /// In `ReleaseFast` and `ReleaseSmall`, `max_depth` is ignored, effectively equivalent to passing `null`. | |
| 116 | /// Alternatively, see `writeStreamArbitraryDepth` to do safety checks to arbitrary depth. | |
| 117 | /// | |
| 118 | /// The caller does *not* need to call `deinit()` on the returned object. | |
| 119 | pub fn writeStreamMaxDepth( | |
| 120 | out_stream: anytype, | |
| 121 | options: StringifyOptions, | |
| 122 | comptime max_depth: ?usize, | |
| 123 | ) WriteStream( | |
| 124 | @TypeOf(out_stream), | |
| 125 | if (max_depth) |d| .{ .checked_to_fixed_depth = d } else .assumed_correct, | |
| 126 | ) { | |
| 127 | return WriteStream( | |
| 128 | @TypeOf(out_stream), | |
| 129 | if (max_depth) |d| .{ .checked_to_fixed_depth = d } else .assumed_correct, | |
| 130 | ).init(undefined, out_stream, options); | |
| 131 | } | |
| 132 | ||
| 133 | /// See `WriteStream` for documentation. | |
| 134 | /// This version of the write stream enables safety checks to arbitrarily deep nesting levels | |
| 135 | /// by using the given allocator. | |
| 136 | /// The caller should call `deinit()` on the returned object to free allocated memory. | |
| 137 | /// | |
| 138 | /// In `ReleaseFast` and `ReleaseSmall` mode, this function is effectively equivalent to calling `writeStreamMaxDepth(..., null)`; | |
| 139 | /// in those build modes, the allocator is *not used*. | |
| 140 | pub fn writeStreamArbitraryDepth( | |
| 141 | allocator: Allocator, | |
| 142 | out_stream: anytype, | |
| 143 | options: StringifyOptions, | |
| 144 | ) WriteStream(@TypeOf(out_stream), .checked_to_arbitrary_depth) { | |
| 145 | return WriteStream(@TypeOf(out_stream), .checked_to_arbitrary_depth).init(allocator, out_stream, options); | |
| 146 | } | |
| 147 | ||
| 148 | /// Writes JSON ([RFC8259](https://tools.ietf.org/html/rfc8259)) formatted data | |
| 149 | /// to a stream. | |
| 150 | /// | |
| 151 | /// The sequence of method calls to write JSON content must follow this grammar: | |
| 152 | /// ``` | |
| 153 | /// <once> = <value> | |
| 154 | /// <value> = | |
| 155 | /// | <object> | |
| 156 | /// | <array> | |
| 157 | /// | write | |
| 158 | ||
| 159 | /// | <writeRawStream> | |
| 160 | /// <object> = beginObject ( <field> <value> )* endObject | |
| 161 | /// <field> = objectField | objectFieldRaw | <objectFieldRawStream> | |
| 162 | /// <array> = beginArray ( <value> )* endArray | |
| 163 | /// <writeRawStream> = beginWriteRaw ( stream.writeAll )* endWriteRaw | |
| 164 | /// <objectFieldRawStream> = beginObjectFieldRaw ( stream.writeAll )* endObjectFieldRaw | |
| 165 | /// ``` | |
| 166 | /// | |
| 167 | /// The `safety_checks_hint` parameter determines how much memory is used to enable assertions that the above grammar is being followed, | |
| 168 | /// e.g. tripping an assertion rather than allowing `endObject` to emit the final `}` in `[[[]]}`. | |
| 169 | /// "Depth" in this context means the depth of nested `[]` or `{}` expressions | |
| 170 | /// (or equivalently the amount of recursion on the `<value>` grammar expression above). | |
| 171 | /// For example, emitting the JSON `[[[]]]` requires a depth of 3. | |
| 172 | /// If `.checked_to_fixed_depth` is used, there is additionally an assertion that the nesting depth never exceeds the given limit. | |
| 173 | /// `.checked_to_arbitrary_depth` requires a runtime allocator for the memory. | |
| 174 | /// `.checked_to_fixed_depth` embeds the storage required in the `WriteStream` struct. | |
| 175 | /// `.assumed_correct` requires no space and performs none of these assertions. | |
| 176 | /// In `ReleaseFast` and `ReleaseSmall` mode, the given `safety_checks_hint` is ignored and is always treated as `.assumed_correct`. | |
| 177 | pub fn WriteStream( | |
| 178 | comptime OutStream: type, | |
| 179 | comptime safety_checks_hint: union(enum) { | |
| 180 | checked_to_arbitrary_depth, | |
| 181 | checked_to_fixed_depth: usize, // Rounded up to the nearest multiple of 8. | |
| 182 | assumed_correct, | |
| 183 | }, | |
| 184 | ) type { | |
| 185 | return struct { | |
| 186 | const Self = @This(); | |
| 187 | const build_mode_has_safety = switch (@import("builtin").mode) { | |
| 188 | .Debug, .ReleaseSafe => true, | |
| 189 | .ReleaseFast, .ReleaseSmall => false, | |
| 190 | }; | |
| 191 | const safety_checks: @TypeOf(safety_checks_hint) = if (build_mode_has_safety) | |
| 192 | safety_checks_hint | |
| 193 | else | |
| 194 | .assumed_correct; | |
| 195 | ||
| 196 | pub const Stream = OutStream; | |
| 197 | pub const Error = switch (safety_checks) { | |
| 198 | .checked_to_arbitrary_depth => Stream.Error || error{OutOfMemory}, | |
| 199 | .checked_to_fixed_depth, .assumed_correct => Stream.Error, | |
| 200 | }; | |
| 201 | ||
| 202 | options: StringifyOptions, | |
| 203 | ||
| 204 | stream: OutStream, | |
| 205 | indent_level: usize = 0, | |
| 206 | next_punctuation: enum { | |
| 207 | the_beginning, | |
| 208 | none, | |
| 209 | comma, | |
| 210 | colon, | |
| 211 | } = .the_beginning, | |
| 212 | ||
| 213 | nesting_stack: switch (safety_checks) { | |
| 214 | .checked_to_arbitrary_depth => BitStack, | |
| 215 | .checked_to_fixed_depth => |fixed_buffer_size| [(fixed_buffer_size + 7) >> 3]u8, | |
| 216 | .assumed_correct => void, | |
| 217 | }, | |
| 218 | ||
| 219 | raw_streaming_mode: if (build_mode_has_safety) | |
| 220 | enum { none, value, objectField } | |
| 221 | else | |
| 222 | void = if (build_mode_has_safety) .none else {}, | |
| 223 | ||
| 224 | pub fn init(safety_allocator: Allocator, stream: OutStream, options: StringifyOptions) Self { | |
| 225 | return .{ | |
| 226 | .options = options, | |
| 227 | .stream = stream, | |
| 228 | .nesting_stack = switch (safety_checks) { | |
| 229 | .checked_to_arbitrary_depth => BitStack.init(safety_allocator), | |
| 230 | .checked_to_fixed_depth => |fixed_buffer_size| [_]u8{0} ** ((fixed_buffer_size + 7) >> 3), | |
| 231 | .assumed_correct => {}, | |
| 232 | }, | |
| 233 | }; | |
| 234 | } | |
| 235 | ||
| 236 | /// Only necessary with .checked_to_arbitrary_depth. | |
| 237 | pub fn deinit(self: *Self) void { | |
| 238 | switch (safety_checks) { | |
| 239 | .checked_to_arbitrary_depth => self.nesting_stack.deinit(), | |
| 240 | .checked_to_fixed_depth, .assumed_correct => {}, | |
| 241 | } | |
| 242 | self.* = undefined; | |
| 243 | } | |
| 244 | ||
| 245 | pub fn beginArray(self: *Self) Error!void { | |
| 246 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); | |
| 247 | try self.valueStart(); | |
| 248 | try self.stream.writeByte('['); | |
| 249 | try self.pushIndentation(ARRAY_MODE); | |
| 250 | self.next_punctuation = .none; | |
| 251 | } | |
| 252 | ||
| 253 | pub fn beginObject(self: *Self) Error!void { | |
| 254 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); | |
| 255 | try self.valueStart(); | |
| 256 | try self.stream.writeByte('{'); | |
| 257 | try self.pushIndentation(OBJECT_MODE); | |
| 258 | self.next_punctuation = .none; | |
| 259 | } | |
| 260 | ||
| 261 | pub fn endArray(self: *Self) Error!void { | |
| 262 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); | |
| 263 | self.popIndentation(ARRAY_MODE); | |
| 264 | switch (self.next_punctuation) { | |
| 265 | .none => {}, | |
| 266 | .comma => { | |
| 267 | try self.indent(); | |
| 268 | }, | |
| 269 | .the_beginning, .colon => unreachable, | |
| 270 | } | |
| 271 | try self.stream.writeByte(']'); | |
| 272 | self.valueDone(); | |
| 273 | } | |
| 274 | ||
| 275 | pub fn endObject(self: *Self) Error!void { | |
| 276 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); | |
| 277 | self.popIndentation(OBJECT_MODE); | |
| 278 | switch (self.next_punctuation) { | |
| 279 | .none => {}, | |
| 280 | .comma => { | |
| 281 | try self.indent(); | |
| 282 | }, | |
| 283 | .the_beginning, .colon => unreachable, | |
| 284 | } | |
| 285 | try self.stream.writeByte('}'); | |
| 286 | self.valueDone(); | |
| 287 | } | |
| 288 | ||
| 289 | fn pushIndentation(self: *Self, mode: u1) !void { | |
| 290 | switch (safety_checks) { | |
| 291 | .checked_to_arbitrary_depth => { | |
| 292 | try self.nesting_stack.push(mode); | |
| 293 | self.indent_level += 1; | |
| 294 | }, | |
| 295 | .checked_to_fixed_depth => { | |
| 296 | BitStack.pushWithStateAssumeCapacity(&self.nesting_stack, &self.indent_level, mode); | |
| 297 | }, | |
| 298 | .assumed_correct => { | |
| 299 | self.indent_level += 1; | |
| 300 | }, | |
| 301 | } | |
| 302 | } | |
| 303 | fn popIndentation(self: *Self, assert_its_this_one: u1) void { | |
| 304 | switch (safety_checks) { | |
| 305 | .checked_to_arbitrary_depth => { | |
| 306 | assert(self.nesting_stack.pop() == assert_its_this_one); | |
| 307 | self.indent_level -= 1; | |
| 308 | }, | |
| 309 | .checked_to_fixed_depth => { | |
| 310 | assert(BitStack.popWithState(&self.nesting_stack, &self.indent_level) == assert_its_this_one); | |
| 311 | }, | |
| 312 | .assumed_correct => { | |
| 313 | self.indent_level -= 1; | |
| 314 | }, | |
| 315 | } | |
| 316 | } | |
| 317 | ||
| 318 | fn indent(self: *Self) !void { | |
| 319 | var char: u8 = ' '; | |
| 320 | const n_chars = switch (self.options.whitespace) { | |
| 321 | .minified => return, | |
| 322 | .indent_1 => 1 * self.indent_level, | |
| 323 | .indent_2 => 2 * self.indent_level, | |
| 324 | .indent_3 => 3 * self.indent_level, | |
| 325 | .indent_4 => 4 * self.indent_level, | |
| 326 | .indent_8 => 8 * self.indent_level, | |
| 327 | .indent_tab => blk: { | |
| 328 | char = '\t'; | |
| 329 | break :blk self.indent_level; | |
| 330 | }, | |
| 331 | }; | |
| 332 | try self.stream.writeByte('\n'); | |
| 333 | try self.stream.writeByteNTimes(char, n_chars); | |
| 334 | } | |
| 335 | ||
| 336 | fn valueStart(self: *Self) !void { | |
| 337 | if (self.isObjectKeyExpected()) |is_it| assert(!is_it); // Call objectField*(), not write(), for object keys. | |
| 338 | return self.valueStartAssumeTypeOk(); | |
| 339 | } | |
| 340 | fn objectFieldStart(self: *Self) !void { | |
| 341 | if (self.isObjectKeyExpected()) |is_it| assert(is_it); // Expected write(), not objectField*(). | |
| 342 | return self.valueStartAssumeTypeOk(); | |
| 343 | } | |
| 344 | fn valueStartAssumeTypeOk(self: *Self) !void { | |
| 345 | assert(!self.isComplete()); // JSON document already complete. | |
| 346 | switch (self.next_punctuation) { | |
| 347 | .the_beginning => { | |
| 348 | // No indentation for the very beginning. | |
| 349 | }, | |
| 350 | .none => { | |
| 351 | // First item in a container. | |
| 352 | try self.indent(); | |
| 353 | }, | |
| 354 | .comma => { | |
| 355 | // Subsequent item in a container. | |
| 356 | try self.stream.writeByte(','); | |
| 357 | try self.indent(); | |
| 358 | }, | |
| 359 | .colon => { | |
| 360 | try self.stream.writeByte(':'); | |
| 361 | if (self.options.whitespace != .minified) { | |
| 362 | try self.stream.writeByte(' '); | |
| 363 | } | |
| 364 | }, | |
| 365 | } | |
| 366 | } | |
| 367 | fn valueDone(self: *Self) void { | |
| 368 | self.next_punctuation = .comma; | |
| 369 | } | |
| 370 | ||
| 371 | // Only when safety is enabled: | |
| 372 | fn isObjectKeyExpected(self: *const Self) ?bool { | |
| 373 | switch (safety_checks) { | |
| 374 | .checked_to_arbitrary_depth => return self.indent_level > 0 and | |
| 375 | self.nesting_stack.peek() == OBJECT_MODE and | |
| 376 | self.next_punctuation != .colon, | |
| 377 | .checked_to_fixed_depth => return self.indent_level > 0 and | |
| 378 | BitStack.peekWithState(&self.nesting_stack, self.indent_level) == OBJECT_MODE and | |
| 379 | self.next_punctuation != .colon, | |
| 380 | .assumed_correct => return null, | |
| 381 | } | |
| 382 | } | |
| 383 | fn isComplete(self: *const Self) bool { | |
| 384 | return self.indent_level == 0 and self.next_punctuation == .comma; | |
| 385 | } | |
| 386 | ||
| 387 | /// An alternative to calling `write` that formats a value with `std.fmt`. | |
| 388 | /// This function does the usual punctuation and indentation formatting | |
| 389 | /// assuming the resulting formatted string represents a single complete value; | |
| 390 | /// e.g. `"1"`, `"[]"`, `"[1,2]"`, not `"1,2"`. | |
| 391 | /// This function may be useful for doing your own number formatting. | |
| 392 | pub fn print(self: *Self, comptime fmt: []const u8, args: anytype) Error!void { | |
| 393 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); | |
| 394 | try self.valueStart(); | |
| 395 | try self.stream.print(fmt, args); | |
| 396 | self.valueDone(); | |
| 397 | } | |
| 398 | ||
| 399 | /// An alternative to calling `write` that allows you to write directly to the `.stream` field, e.g. with `.stream.writeAll()`. | |
| 400 | /// Call `beginWriteRaw()`, then write a complete value (including any quotes if necessary) directly to the `.stream` field, | |
| 401 | /// then call `endWriteRaw()`. | |
| 402 | /// This can be useful for streaming very long strings into the output without needing it all buffered in memory. | |
| 403 | pub fn beginWriteRaw(self: *Self) !void { | |
| 404 | if (build_mode_has_safety) { | |
| 405 | assert(self.raw_streaming_mode == .none); | |
| 406 | self.raw_streaming_mode = .value; | |
| 407 | } | |
| 408 | try self.valueStart(); | |
| 409 | } | |
| 410 | ||
| 411 | /// See `beginWriteRaw`. | |
| 412 | pub fn endWriteRaw(self: *Self) void { | |
| 413 | if (build_mode_has_safety) { | |
| 414 | assert(self.raw_streaming_mode == .value); | |
| 415 | self.raw_streaming_mode = .none; | |
| 416 | } | |
| 417 | self.valueDone(); | |
| 418 | } | |
| 419 | ||
| 420 | /// See `WriteStream` for when to call this method. | |
| 421 | /// `key` is the string content of the property name. | |
| 422 | /// Surrounding quotes will be added and any special characters will be escaped. | |
| 423 | /// See also `objectFieldRaw`. | |
| 424 | pub fn objectField(self: *Self, key: []const u8) Error!void { | |
| 425 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); | |
| 426 | try self.objectFieldStart(); | |
| 427 | try encodeJsonString(key, self.options, self.stream); | |
| 428 | self.next_punctuation = .colon; | |
| 429 | } | |
| 430 | /// See `WriteStream` for when to call this method. | |
| 431 | /// `quoted_key` is the complete bytes of the key including quotes and any necessary escape sequences. | |
| 432 | /// A few assertions are performed on the given value to ensure that the caller of this function understands the API contract. | |
| 433 | /// See also `objectField`. | |
| 434 | pub fn objectFieldRaw(self: *Self, quoted_key: []const u8) Error!void { | |
| 435 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); | |
| 436 | assert(quoted_key.len >= 2 and quoted_key[0] == '"' and quoted_key[quoted_key.len - 1] == '"'); // quoted_key should be "quoted". | |
| 437 | try self.objectFieldStart(); | |
| 438 | try self.stream.writeAll(quoted_key); | |
| 439 | self.next_punctuation = .colon; | |
| 440 | } | |
| 441 | ||
| 442 | /// In the rare case that you need to write very long object field names, | |
| 443 | /// this is an alternative to `objectField` and `objectFieldRaw` that allows you to write directly to the `.stream` field | |
| 444 | /// similar to `beginWriteRaw`. | |
| 445 | /// Call `endObjectFieldRaw()` when you're done. | |
| 446 | pub fn beginObjectFieldRaw(self: *Self) !void { | |
| 447 | if (build_mode_has_safety) { | |
| 448 | assert(self.raw_streaming_mode == .none); | |
| 449 | self.raw_streaming_mode = .objectField; | |
| 450 | } | |
| 451 | try self.objectFieldStart(); | |
| 452 | } | |
| 453 | ||
| 454 | /// See `beginObjectFieldRaw`. | |
| 455 | pub fn endObjectFieldRaw(self: *Self) void { | |
| 456 | if (build_mode_has_safety) { | |
| 457 | assert(self.raw_streaming_mode == .objectField); | |
| 458 | self.raw_streaming_mode = .none; | |
| 459 | } | |
| 460 | self.next_punctuation = .colon; | |
| 461 | } | |
| 462 | ||
| 463 | /// Renders the given Zig value as JSON. | |
| 464 | /// | |
| 465 | /// Supported types: | |
| 466 | /// * Zig `bool` -> JSON `true` or `false`. | |
| 467 | /// * Zig `?T` -> `null` or the rendering of `T`. | |
| 468 | /// * Zig `i32`, `u64`, etc. -> JSON number or string. | |
| 469 | /// * When option `emit_nonportable_numbers_as_strings` is true, if the value is outside the range `+-1<<53` (the precise integer range of f64), it is rendered as a JSON string in base 10. Otherwise, it is rendered as JSON number. | |
| 470 | /// * Zig floats -> JSON number or string. | |
| 471 | /// * If the value cannot be precisely represented by an f64, it is rendered as a JSON string. Otherwise, it is rendered as JSON number. | |
| 472 | /// * Zig `[]const u8`, `[]u8`, `*[N]u8`, `@Vector(N, u8)`, and similar -> JSON string. | |
| 473 | /// * See `StringifyOptions.emit_strings_as_arrays`. | |
| 474 | /// * If the content is not valid UTF-8, rendered as an array of numbers instead. | |
| 475 | /// * Zig `[]T`, `[N]T`, `*[N]T`, `@Vector(N, T)`, and similar -> JSON array of the rendering of each item. | |
| 476 | /// * Zig tuple -> JSON array of the rendering of each item. | |
| 477 | /// * Zig `struct` -> JSON object with each field in declaration order. | |
| 478 | /// * If the struct declares a method `pub fn jsonStringify(self: *@This(), jw: anytype) !void`, it is called to do the serialization instead of the default behavior. The given `jw` is a pointer to this `WriteStream`. See `std.json.Value` for an example. | |
| 479 | /// * See `StringifyOptions.emit_null_optional_fields`. | |
| 480 | /// * Zig `union(enum)` -> JSON object with one field named for the active tag and a value representing the payload. | |
| 481 | /// * If the payload is `void`, then the emitted value is `{}`. | |
| 482 | /// * If the union declares a method `pub fn jsonStringify(self: *@This(), jw: anytype) !void`, it is called to do the serialization instead of the default behavior. The given `jw` is a pointer to this `WriteStream`. | |
| 483 | /// * Zig `enum` -> JSON string naming the active tag. | |
| 484 | /// * If the enum declares a method `pub fn jsonStringify(self: *@This(), jw: anytype) !void`, it is called to do the serialization instead of the default behavior. The given `jw` is a pointer to this `WriteStream`. | |
| 485 | /// * If the enum is non-exhaustive, unnamed values are rendered as integers. | |
| 486 | /// * Zig untyped enum literal -> JSON string naming the active tag. | |
| 487 | /// * Zig error -> JSON string naming the error. | |
| 488 | /// * Zig `*T` -> the rendering of `T`. Note there is no guard against circular-reference infinite recursion. | |
| 489 | /// | |
| 490 | /// See also alternative functions `print` and `beginWriteRaw`. | |
| 491 | /// For writing object field names, use `objectField` instead. | |
| 492 | pub fn write(self: *Self, value: anytype) Error!void { | |
| 493 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); | |
| 494 | const T = @TypeOf(value); | |
| 495 | switch (@typeInfo(T)) { | |
| 496 | .int => { | |
| 497 | try self.valueStart(); | |
| 498 | if (self.options.emit_nonportable_numbers_as_strings and | |
| 499 | (value <= -(1 << 53) or value >= (1 << 53))) | |
| 500 | { | |
| 501 | try self.stream.print("\"{}\"", .{value}); | |
| 502 | } else { | |
| 503 | try self.stream.print("{}", .{value}); | |
| 504 | } | |
| 505 | self.valueDone(); | |
| 506 | return; | |
| 507 | }, | |
| 508 | .comptime_int => { | |
| 509 | return self.write(@as(std.math.IntFittingRange(value, value), value)); | |
| 510 | }, | |
| 511 | .float, .comptime_float => { | |
| 512 | if (@as(f64, @floatCast(value)) == value) { | |
| 513 | try self.valueStart(); | |
| 514 | try self.stream.print("{}", .{@as(f64, @floatCast(value))}); | |
| 515 | self.valueDone(); | |
| 516 | return; | |
| 517 | } | |
| 518 | try self.valueStart(); | |
| 519 | try self.stream.print("\"{}\"", .{value}); | |
| 520 | self.valueDone(); | |
| 521 | return; | |
| 522 | }, | |
| 523 | ||
| 524 | .bool => { | |
| 525 | try self.valueStart(); | |
| 526 | try self.stream.writeAll(if (value) "true" else "false"); | |
| 527 | self.valueDone(); | |
| 528 | return; | |
| 529 | }, | |
| 530 | .null => { | |
| 531 | try self.valueStart(); | |
| 532 | try self.stream.writeAll("null"); | |
| 533 | self.valueDone(); | |
| 534 | return; | |
| 535 | }, | |
| 536 | .optional => { | |
| 537 | if (value) |payload| { | |
| 538 | return try self.write(payload); | |
| 539 | } else { | |
| 540 | return try self.write(null); | |
| 541 | } | |
| 542 | }, | |
| 543 | .@"enum" => |enum_info| { | |
| 544 | if (std.meta.hasFn(T, "jsonStringify")) { | |
| 545 | return value.jsonStringify(self); | |
| 546 | } | |
| 547 | ||
| 548 | if (!enum_info.is_exhaustive) { | |
| 549 | inline for (enum_info.fields) |field| { | |
| 550 | if (value == @field(T, field.name)) { | |
| 551 | break; | |
| 552 | } | |
| 553 | } else { | |
| 554 | return self.write(@intFromEnum(value)); | |
| 555 | } | |
| 556 | } | |
| 557 | ||
| 558 | return self.stringValue(@tagName(value)); | |
| 559 | }, | |
| 560 | .enum_literal => { | |
| 561 | return self.stringValue(@tagName(value)); | |
| 562 | }, | |
| 563 | .@"union" => { | |
| 564 | if (std.meta.hasFn(T, "jsonStringify")) { | |
| 565 | return value.jsonStringify(self); | |
| 566 | } | |
| 567 | ||
| 568 | const info = @typeInfo(T).@"union"; | |
| 569 | if (info.tag_type) |UnionTagType| { | |
| 570 | try self.beginObject(); | |
| 571 | inline for (info.fields) |u_field| { | |
| 572 | if (value == @field(UnionTagType, u_field.name)) { | |
| 573 | try self.objectField(u_field.name); | |
| 574 | if (u_field.type == void) { | |
| 575 | // void value is {} | |
| 576 | try self.beginObject(); | |
| 577 | try self.endObject(); | |
| 578 | } else { | |
| 579 | try self.write(@field(value, u_field.name)); | |
| 580 | } | |
| 581 | break; | |
| 582 | } | |
| 583 | } else { | |
| 584 | unreachable; // No active tag? | |
| 585 | } | |
| 586 | try self.endObject(); | |
| 587 | return; | |
| 588 | } else { | |
| 589 | @compileError("Unable to stringify untagged union '" ++ @typeName(T) ++ "'"); | |
| 590 | } | |
| 591 | }, | |
| 592 | .@"struct" => |S| { | |
| 593 | if (std.meta.hasFn(T, "jsonStringify")) { | |
| 594 | return value.jsonStringify(self); | |
| 595 | } | |
| 596 | ||
| 597 | if (S.is_tuple) { | |
| 598 | try self.beginArray(); | |
| 599 | } else { | |
| 600 | try self.beginObject(); | |
| 601 | } | |
| 602 | inline for (S.fields) |Field| { | |
| 603 | // don't include void fields | |
| 604 | if (Field.type == void) continue; | |
| 605 | ||
| 606 | var emit_field = true; | |
| 607 | ||
| 608 | // don't include optional fields that are null when emit_null_optional_fields is set to false | |
| 609 | if (@typeInfo(Field.type) == .optional) { | |
| 610 | if (self.options.emit_null_optional_fields == false) { | |
| 611 | if (@field(value, Field.name) == null) { | |
| 612 | emit_field = false; | |
| 613 | } | |
| 614 | } | |
| 615 | } | |
| 616 | ||
| 617 | if (emit_field) { | |
| 618 | if (!S.is_tuple) { | |
| 619 | try self.objectField(Field.name); | |
| 620 | } | |
| 621 | try self.write(@field(value, Field.name)); | |
| 622 | } | |
| 623 | } | |
| 624 | if (S.is_tuple) { | |
| 625 | try self.endArray(); | |
| 626 | } else { | |
| 627 | try self.endObject(); | |
| 628 | } | |
| 629 | return; | |
| 630 | }, | |
| 631 | .error_set => return self.stringValue(@errorName(value)), | |
| 632 | .pointer => |ptr_info| switch (ptr_info.size) { | |
| 633 | .one => switch (@typeInfo(ptr_info.child)) { | |
| 634 | .array => { | |
| 635 | // Coerce `*[N]T` to `[]const T`. | |
| 636 | const Slice = []const std.meta.Elem(ptr_info.child); | |
| 637 | return self.write(@as(Slice, value)); | |
| 638 | }, | |
| 639 | else => { | |
| 640 | return self.write(value.*); | |
| 641 | }, | |
| 642 | }, | |
| 643 | .many, .slice => { | |
| 644 | if (ptr_info.size == .many and ptr_info.sentinel() == null) | |
| 645 | @compileError("unable to stringify type '" ++ @typeName(T) ++ "' without sentinel"); | |
| 646 | const slice = if (ptr_info.size == .many) std.mem.span(value) else value; | |
| 647 | ||
| 648 | if (ptr_info.child == u8) { | |
| 649 | // This is a []const u8, or some similar Zig string. | |
| 650 | if (!self.options.emit_strings_as_arrays and std.unicode.utf8ValidateSlice(slice)) { | |
| 651 | return self.stringValue(slice); | |
| 652 | } | |
| 653 | } | |
| 654 | ||
| 655 | try self.beginArray(); | |
| 656 | for (slice) |x| { | |
| 657 | try self.write(x); | |
| 658 | } | |
| 659 | try self.endArray(); | |
| 660 | return; | |
| 661 | }, | |
| 662 | else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"), | |
| 663 | }, | |
| 664 | .array => { | |
| 665 | // Coerce `[N]T` to `*const [N]T` (and then to `[]const T`). | |
| 666 | return self.write(&value); | |
| 667 | }, | |
| 668 | .vector => |info| { | |
| 669 | const array: [info.len]info.child = value; | |
| 670 | return self.write(&array); | |
| 671 | }, | |
| 672 | else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"), | |
| 673 | } | |
| 674 | unreachable; | |
| 675 | } | |
| 676 | ||
| 677 | fn stringValue(self: *Self, s: []const u8) !void { | |
| 678 | try self.valueStart(); | |
| 679 | try encodeJsonString(s, self.options, self.stream); | |
| 680 | self.valueDone(); | |
| 681 | } | |
| 682 | }; | |
| 683 | } | |
| 684 | ||
| 685 | fn outputUnicodeEscape(codepoint: u21, out_stream: anytype) !void { | |
| 686 | if (codepoint <= 0xFFFF) { | |
| 687 | // If the character is in the Basic Multilingual Plane (U+0000 through U+FFFF), | |
| 688 | // then it may be represented as a six-character sequence: a reverse solidus, followed | |
| 689 | // by the lowercase letter u, followed by four hexadecimal digits that encode the character's code point. | |
| 690 | try out_stream.writeAll("\\u"); | |
| 691 | //try w.printInt("x", .{ .width = 4, .fill = '0' }, codepoint); | |
| 692 | try std.fmt.format(out_stream, "{x:0>4}", .{codepoint}); | |
| 693 | } else { | |
| 694 | assert(codepoint <= 0x10FFFF); | |
| 695 | // To escape an extended character that is not in the Basic Multilingual Plane, | |
| 696 | // the character is represented as a 12-character sequence, encoding the UTF-16 surrogate pair. | |
| 697 | const high = @as(u16, @intCast((codepoint - 0x10000) >> 10)) + 0xD800; | |
| 698 | const low = @as(u16, @intCast(codepoint & 0x3FF)) + 0xDC00; | |
| 699 | try out_stream.writeAll("\\u"); | |
| 700 | //try w.printInt("x", .{ .width = 4, .fill = '0' }, high); | |
| 701 | try std.fmt.format(out_stream, "{x:0>4}", .{high}); | |
| 702 | try out_stream.writeAll("\\u"); | |
| 703 | //try w.printInt("x", .{ .width = 4, .fill = '0' }, low); | |
| 704 | try std.fmt.format(out_stream, "{x:0>4}", .{low}); | |
| 705 | } | |
| 706 | } | |
| 707 | ||
| 708 | fn outputSpecialEscape(c: u8, writer: anytype) !void { | |
| 709 | switch (c) { | |
| 710 | '\\' => try writer.writeAll("\\\\"), | |
| 711 | '\"' => try writer.writeAll("\\\""), | |
| 712 | 0x08 => try writer.writeAll("\\b"), | |
| 713 | 0x0C => try writer.writeAll("\\f"), | |
| 714 | '\n' => try writer.writeAll("\\n"), | |
| 715 | '\r' => try writer.writeAll("\\r"), | |
| 716 | '\t' => try writer.writeAll("\\t"), | |
| 717 | else => try outputUnicodeEscape(c, writer), | |
| 718 | } | |
| 719 | } | |
| 720 | ||
| 721 | /// Write `string` to `writer` as a JSON encoded string. | |
| 722 | pub fn encodeJsonString(string: []const u8, options: StringifyOptions, writer: anytype) !void { | |
| 723 | try writer.writeByte('\"'); | |
| 724 | try encodeJsonStringChars(string, options, writer); | |
| 725 | try writer.writeByte('\"'); | |
| 726 | } | |
| 727 | ||
| 728 | /// Write `chars` to `writer` as JSON encoded string characters. | |
| 729 | pub fn encodeJsonStringChars(chars: []const u8, options: StringifyOptions, writer: anytype) !void { | |
| 730 | var write_cursor: usize = 0; | |
| 731 | var i: usize = 0; | |
| 732 | if (options.escape_unicode) { | |
| 733 | while (i < chars.len) : (i += 1) { | |
| 734 | switch (chars[i]) { | |
| 735 | // normal ascii character | |
| 736 | 0x20...0x21, 0x23...0x5B, 0x5D...0x7E => {}, | |
| 737 | 0x00...0x1F, '\\', '\"' => { | |
| 738 | // Always must escape these. | |
| 739 | try writer.writeAll(chars[write_cursor..i]); | |
| 740 | try outputSpecialEscape(chars[i], writer); | |
| 741 | write_cursor = i + 1; | |
| 742 | }, | |
| 743 | 0x7F...0xFF => { | |
| 744 | try writer.writeAll(chars[write_cursor..i]); | |
| 745 | const ulen = std.unicode.utf8ByteSequenceLength(chars[i]) catch unreachable; | |
| 746 | const codepoint = std.unicode.utf8Decode(chars[i..][0..ulen]) catch unreachable; | |
| 747 | try outputUnicodeEscape(codepoint, writer); | |
| 748 | i += ulen - 1; | |
| 749 | write_cursor = i + 1; | |
| 750 | }, | |
| 751 | } | |
| 752 | } | |
| 753 | } else { | |
| 754 | while (i < chars.len) : (i += 1) { | |
| 755 | switch (chars[i]) { | |
| 756 | // normal bytes | |
| 757 | 0x20...0x21, 0x23...0x5B, 0x5D...0xFF => {}, | |
| 758 | 0x00...0x1F, '\\', '\"' => { | |
| 759 | // Always must escape these. | |
| 760 | try writer.writeAll(chars[write_cursor..i]); | |
| 761 | try outputSpecialEscape(chars[i], writer); | |
| 762 | write_cursor = i + 1; | |
| 763 | }, | |
| 764 | } | |
| 765 | } | |
| 766 | } | |
| 767 | try writer.writeAll(chars[write_cursor..chars.len]); | |
| 768 | } | |
| 769 | ||
| 770 | test { | |
| 771 | _ = @import("./stringify_test.zig"); | |
| 772 | } |
lib/std/json/stringify_test.zig deleted-504| ... | ... | @@ -1,504 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | const mem = std.mem; | |
| 3 | const testing = std.testing; | |
| 4 | ||
| 5 | const ObjectMap = @import("dynamic.zig").ObjectMap; | |
| 6 | const Value = @import("dynamic.zig").Value; | |
| 7 | ||
| 8 | const StringifyOptions = @import("stringify.zig").StringifyOptions; | |
| 9 | const stringify = @import("stringify.zig").stringify; | |
| 10 | const stringifyMaxDepth = @import("stringify.zig").stringifyMaxDepth; | |
| 11 | const stringifyArbitraryDepth = @import("stringify.zig").stringifyArbitraryDepth; | |
| 12 | const stringifyAlloc = @import("stringify.zig").stringifyAlloc; | |
| 13 | const writeStream = @import("stringify.zig").writeStream; | |
| 14 | const writeStreamMaxDepth = @import("stringify.zig").writeStreamMaxDepth; | |
| 15 | const writeStreamArbitraryDepth = @import("stringify.zig").writeStreamArbitraryDepth; | |
| 16 | ||
| 17 | test "json write stream" { | |
| 18 | var out_buf: [1024]u8 = undefined; | |
| 19 | var slice_stream = std.io.fixedBufferStream(&out_buf); | |
| 20 | const out = slice_stream.writer(); | |
| 21 | ||
| 22 | { | |
| 23 | var w = writeStream(out, .{ .whitespace = .indent_2 }); | |
| 24 | try testBasicWriteStream(&w, &slice_stream); | |
| 25 | } | |
| 26 | ||
| 27 | { | |
| 28 | var w = writeStreamMaxDepth(out, .{ .whitespace = .indent_2 }, 8); | |
| 29 | try testBasicWriteStream(&w, &slice_stream); | |
| 30 | } | |
| 31 | ||
| 32 | { | |
| 33 | var w = writeStreamMaxDepth(out, .{ .whitespace = .indent_2 }, null); | |
| 34 | try testBasicWriteStream(&w, &slice_stream); | |
| 35 | } | |
| 36 | ||
| 37 | { | |
| 38 | var w = writeStreamArbitraryDepth(testing.allocator, out, .{ .whitespace = .indent_2 }); | |
| 39 | defer w.deinit(); | |
| 40 | try testBasicWriteStream(&w, &slice_stream); | |
| 41 | } | |
| 42 | } | |
| 43 | ||
| 44 | fn testBasicWriteStream(w: anytype, slice_stream: anytype) !void { | |
| 45 | slice_stream.reset(); | |
| 46 | ||
| 47 | try w.beginObject(); | |
| 48 | ||
| 49 | try w.objectField("object"); | |
| 50 | var arena_allocator = std.heap.ArenaAllocator.init(testing.allocator); | |
| 51 | defer arena_allocator.deinit(); | |
| 52 | try w.write(try getJsonObject(arena_allocator.allocator())); | |
| 53 | ||
| 54 | try w.objectFieldRaw("\"string\""); | |
| 55 | try w.write("This is a string"); | |
| 56 | ||
| 57 | try w.objectField("array"); | |
| 58 | try w.beginArray(); | |
| 59 | try w.write("Another string"); | |
| 60 | try w.write(@as(i32, 1)); | |
| 61 | try w.write(@as(f32, 3.5)); | |
| 62 | try w.endArray(); | |
| 63 | ||
| 64 | try w.objectField("int"); | |
| 65 | try w.write(@as(i32, 10)); | |
| 66 | ||
| 67 | try w.objectField("float"); | |
| 68 | try w.write(@as(f32, 3.5)); | |
| 69 | ||
| 70 | try w.endObject(); | |
| 71 | ||
| 72 | const result = slice_stream.getWritten(); | |
| 73 | const expected = | |
| 74 | \\{ | |
| 75 | \\ "object": { | |
| 76 | \\ "one": 1, | |
| 77 | \\ "two": 2 | |
| 78 | \\ }, | |
| 79 | \\ "string": "This is a string", | |
| 80 | \\ "array": [ | |
| 81 | \\ "Another string", | |
| 82 | \\ 1, | |
| 83 | \\ 3.5 | |
| 84 | \\ ], | |
| 85 | \\ "int": 10, | |
| 86 | \\ "float": 3.5 | |
| 87 | \\} | |
| 88 | ; | |
| 89 | try std.testing.expectEqualStrings(expected, result); | |
| 90 | } | |
| 91 | ||
| 92 | fn getJsonObject(allocator: std.mem.Allocator) !Value { | |
| 93 | var value = Value{ .object = ObjectMap.init(allocator) }; | |
| 94 | try value.object.put("one", Value{ .integer = @as(i64, @intCast(1)) }); | |
| 95 | try value.object.put("two", Value{ .float = 2.0 }); | |
| 96 | return value; | |
| 97 | } | |
| 98 | ||
| 99 | test "stringify null optional fields" { | |
| 100 | const MyStruct = struct { | |
| 101 | optional: ?[]const u8 = null, | |
| 102 | required: []const u8 = "something", | |
| 103 | another_optional: ?[]const u8 = null, | |
| 104 | another_required: []const u8 = "something else", | |
| 105 | }; | |
| 106 | try testStringify( | |
| 107 | \\{"optional":null,"required":"something","another_optional":null,"another_required":"something else"} | |
| 108 | , | |
| 109 | MyStruct{}, | |
| 110 | .{}, | |
| 111 | ); | |
| 112 | try testStringify( | |
| 113 | \\{"required":"something","another_required":"something else"} | |
| 114 | , | |
| 115 | MyStruct{}, | |
| 116 | .{ .emit_null_optional_fields = false }, | |
| 117 | ); | |
| 118 | } | |
| 119 | ||
| 120 | test "stringify basic types" { | |
| 121 | try testStringify("false", false, .{}); | |
| 122 | try testStringify("true", true, .{}); | |
| 123 | try testStringify("null", @as(?u8, null), .{}); | |
| 124 | try testStringify("null", @as(?*u32, null), .{}); | |
| 125 | try testStringify("42", 42, .{}); | |
| 126 | try testStringify("42", 42.0, .{}); | |
| 127 | try testStringify("42", @as(u8, 42), .{}); | |
| 128 | try testStringify("42", @as(u128, 42), .{}); | |
| 129 | try testStringify("9999999999999999", 9999999999999999, .{}); | |
| 130 | try testStringify("42", @as(f32, 42), .{}); | |
| 131 | try testStringify("42", @as(f64, 42), .{}); | |
| 132 | try testStringify("\"ItBroke\"", @as(anyerror, error.ItBroke), .{}); | |
| 133 | try testStringify("\"ItBroke\"", error.ItBroke, .{}); | |
| 134 | } | |
| 135 | ||
| 136 | test "stringify string" { | |
| 137 | try testStringify("\"hello\"", "hello", .{}); | |
| 138 | try testStringify("\"with\\nescapes\\r\"", "with\nescapes\r", .{}); | |
| 139 | try testStringify("\"with\\nescapes\\r\"", "with\nescapes\r", .{ .escape_unicode = true }); | |
| 140 | try testStringify("\"with unicode\\u0001\"", "with unicode\u{1}", .{}); | |
| 141 | try testStringify("\"with unicode\\u0001\"", "with unicode\u{1}", .{ .escape_unicode = true }); | |
| 142 | try testStringify("\"with unicode\u{80}\"", "with unicode\u{80}", .{}); | |
| 143 | try testStringify("\"with unicode\\u0080\"", "with unicode\u{80}", .{ .escape_unicode = true }); | |
| 144 | try testStringify("\"with unicode\u{FF}\"", "with unicode\u{FF}", .{}); | |
| 145 | try testStringify("\"with unicode\\u00ff\"", "with unicode\u{FF}", .{ .escape_unicode = true }); | |
| 146 | try testStringify("\"with unicode\u{100}\"", "with unicode\u{100}", .{}); | |
| 147 | try testStringify("\"with unicode\\u0100\"", "with unicode\u{100}", .{ .escape_unicode = true }); | |
| 148 | try testStringify("\"with unicode\u{800}\"", "with unicode\u{800}", .{}); | |
| 149 | try testStringify("\"with unicode\\u0800\"", "with unicode\u{800}", .{ .escape_unicode = true }); | |
| 150 | try testStringify("\"with unicode\u{8000}\"", "with unicode\u{8000}", .{}); | |
| 151 | try testStringify("\"with unicode\\u8000\"", "with unicode\u{8000}", .{ .escape_unicode = true }); | |
| 152 | try testStringify("\"with unicode\u{D799}\"", "with unicode\u{D799}", .{}); | |
| 153 | try testStringify("\"with unicode\\ud799\"", "with unicode\u{D799}", .{ .escape_unicode = true }); | |
| 154 | try testStringify("\"with unicode\u{10000}\"", "with unicode\u{10000}", .{}); | |
| 155 | try testStringify("\"with unicode\\ud800\\udc00\"", "with unicode\u{10000}", .{ .escape_unicode = true }); | |
| 156 | try testStringify("\"with unicode\u{10FFFF}\"", "with unicode\u{10FFFF}", .{}); | |
| 157 | try testStringify("\"with unicode\\udbff\\udfff\"", "with unicode\u{10FFFF}", .{ .escape_unicode = true }); | |
| 158 | } | |
| 159 | ||
| 160 | test "stringify many-item sentinel-terminated string" { | |
| 161 | try testStringify("\"hello\"", @as([*:0]const u8, "hello"), .{}); | |
| 162 | try testStringify("\"with\\nescapes\\r\"", @as([*:0]const u8, "with\nescapes\r"), .{ .escape_unicode = true }); | |
| 163 | try testStringify("\"with unicode\\u0001\"", @as([*:0]const u8, "with unicode\u{1}"), .{ .escape_unicode = true }); | |
| 164 | } | |
| 165 | ||
| 166 | test "stringify enums" { | |
| 167 | const E = enum { | |
| 168 | foo, | |
| 169 | bar, | |
| 170 | }; | |
| 171 | try testStringify("\"foo\"", E.foo, .{}); | |
| 172 | try testStringify("\"bar\"", E.bar, .{}); | |
| 173 | } | |
| 174 | ||
| 175 | test "stringify non-exhaustive enum" { | |
| 176 | const E = enum(u8) { | |
| 177 | foo = 0, | |
| 178 | _, | |
| 179 | }; | |
| 180 | try testStringify("\"foo\"", E.foo, .{}); | |
| 181 | try testStringify("1", @as(E, @enumFromInt(1)), .{}); | |
| 182 | } | |
| 183 | ||
| 184 | test "stringify enum literals" { | |
| 185 | try testStringify("\"foo\"", .foo, .{}); | |
| 186 | try testStringify("\"bar\"", .bar, .{}); | |
| 187 | } | |
| 188 | ||
| 189 | test "stringify tagged unions" { | |
| 190 | const T = union(enum) { | |
| 191 | nothing, | |
| 192 | foo: u32, | |
| 193 | bar: bool, | |
| 194 | }; | |
| 195 | try testStringify("{\"nothing\":{}}", T{ .nothing = {} }, .{}); | |
| 196 | try testStringify("{\"foo\":42}", T{ .foo = 42 }, .{}); | |
| 197 | try testStringify("{\"bar\":true}", T{ .bar = true }, .{}); | |
| 198 | } | |
| 199 | ||
| 200 | test "stringify struct" { | |
| 201 | try testStringify("{\"foo\":42}", struct { | |
| 202 | foo: u32, | |
| 203 | }{ .foo = 42 }, .{}); | |
| 204 | } | |
| 205 | ||
| 206 | test "emit_strings_as_arrays" { | |
| 207 | // Should only affect string values, not object keys. | |
| 208 | try testStringify("{\"foo\":\"bar\"}", .{ .foo = "bar" }, .{}); | |
| 209 | try testStringify("{\"foo\":[98,97,114]}", .{ .foo = "bar" }, .{ .emit_strings_as_arrays = true }); | |
| 210 | // Should *not* affect these types: | |
| 211 | try testStringify("\"foo\"", @as(enum { foo, bar }, .foo), .{ .emit_strings_as_arrays = true }); | |
| 212 | try testStringify("\"ItBroke\"", error.ItBroke, .{ .emit_strings_as_arrays = true }); | |
| 213 | // Should work on these: | |
| 214 | try testStringify("\"bar\"", @Vector(3, u8){ 'b', 'a', 'r' }, .{}); | |
| 215 | try testStringify("[98,97,114]", @Vector(3, u8){ 'b', 'a', 'r' }, .{ .emit_strings_as_arrays = true }); | |
| 216 | try testStringify("\"bar\"", [3]u8{ 'b', 'a', 'r' }, .{}); | |
| 217 | try testStringify("[98,97,114]", [3]u8{ 'b', 'a', 'r' }, .{ .emit_strings_as_arrays = true }); | |
| 218 | } | |
| 219 | ||
| 220 | test "stringify struct with indentation" { | |
| 221 | try testStringify( | |
| 222 | \\{ | |
| 223 | \\ "foo": 42, | |
| 224 | \\ "bar": [ | |
| 225 | \\ 1, | |
| 226 | \\ 2, | |
| 227 | \\ 3 | |
| 228 | \\ ] | |
| 229 | \\} | |
| 230 | , | |
| 231 | struct { | |
| 232 | foo: u32, | |
| 233 | bar: [3]u32, | |
| 234 | }{ | |
| 235 | .foo = 42, | |
| 236 | .bar = .{ 1, 2, 3 }, | |
| 237 | }, | |
| 238 | .{ .whitespace = .indent_4 }, | |
| 239 | ); | |
| 240 | try testStringify( | |
| 241 | "{\n\t\"foo\": 42,\n\t\"bar\": [\n\t\t1,\n\t\t2,\n\t\t3\n\t]\n}", | |
| 242 | struct { | |
| 243 | foo: u32, | |
| 244 | bar: [3]u32, | |
| 245 | }{ | |
| 246 | .foo = 42, | |
| 247 | .bar = .{ 1, 2, 3 }, | |
| 248 | }, | |
| 249 | .{ .whitespace = .indent_tab }, | |
| 250 | ); | |
| 251 | try testStringify( | |
| 252 | \\{"foo":42,"bar":[1,2,3]} | |
| 253 | , | |
| 254 | struct { | |
| 255 | foo: u32, | |
| 256 | bar: [3]u32, | |
| 257 | }{ | |
| 258 | .foo = 42, | |
| 259 | .bar = .{ 1, 2, 3 }, | |
| 260 | }, | |
| 261 | .{ .whitespace = .minified }, | |
| 262 | ); | |
| 263 | } | |
| 264 | ||
| 265 | test "stringify struct with void field" { | |
| 266 | try testStringify("{\"foo\":42}", struct { | |
| 267 | foo: u32, | |
| 268 | bar: void = {}, | |
| 269 | }{ .foo = 42 }, .{}); | |
| 270 | } | |
| 271 | ||
| 272 | test "stringify array of structs" { | |
| 273 | const MyStruct = struct { | |
| 274 | foo: u32, | |
| 275 | }; | |
| 276 | try testStringify("[{\"foo\":42},{\"foo\":100},{\"foo\":1000}]", [_]MyStruct{ | |
| 277 | MyStruct{ .foo = 42 }, | |
| 278 | MyStruct{ .foo = 100 }, | |
| 279 | MyStruct{ .foo = 1000 }, | |
| 280 | }, .{}); | |
| 281 | } | |
| 282 | ||
| 283 | test "stringify struct with custom stringifier" { | |
| 284 | try testStringify("[\"something special\",42]", struct { | |
| 285 | foo: u32, | |
| 286 | const Self = @This(); | |
| 287 | pub fn jsonStringify(value: @This(), jws: anytype) !void { | |
| 288 | _ = value; | |
| 289 | try jws.beginArray(); | |
| 290 | try jws.write("something special"); | |
| 291 | try jws.write(42); | |
| 292 | try jws.endArray(); | |
| 293 | } | |
| 294 | }{ .foo = 42 }, .{}); | |
| 295 | } | |
| 296 | ||
| 297 | test "stringify vector" { | |
| 298 | try testStringify("[1,1]", @as(@Vector(2, u32), @splat(1)), .{}); | |
| 299 | try testStringify("\"AA\"", @as(@Vector(2, u8), @splat('A')), .{}); | |
| 300 | try testStringify("[65,65]", @as(@Vector(2, u8), @splat('A')), .{ .emit_strings_as_arrays = true }); | |
| 301 | } | |
| 302 | ||
| 303 | test "stringify tuple" { | |
| 304 | try testStringify("[\"foo\",42]", std.meta.Tuple(&.{ []const u8, usize }){ "foo", 42 }, .{}); | |
| 305 | } | |
| 306 | ||
| 307 | fn testStringify(expected: []const u8, value: anytype, options: StringifyOptions) !void { | |
| 308 | const ValidationWriter = struct { | |
| 309 | const Self = @This(); | |
| 310 | pub const Writer = std.io.GenericWriter(*Self, Error, write); | |
| 311 | pub const Error = error{ | |
| 312 | TooMuchData, | |
| 313 | DifferentData, | |
| 314 | }; | |
| 315 | ||
| 316 | expected_remaining: []const u8, | |
| 317 | ||
| 318 | fn init(exp: []const u8) Self { | |
| 319 | return .{ .expected_remaining = exp }; | |
| 320 | } | |
| 321 | ||
| 322 | pub fn writer(self: *Self) Writer { | |
| 323 | return .{ .context = self }; | |
| 324 | } | |
| 325 | ||
| 326 | fn write(self: *Self, bytes: []const u8) Error!usize { | |
| 327 | if (self.expected_remaining.len < bytes.len) { | |
| 328 | std.debug.print( | |
| 329 | \\====== expected this output: ========= | |
| 330 | \\{s} | |
| 331 | \\======== instead found this: ========= | |
| 332 | \\{s} | |
| 333 | \\====================================== | |
| 334 | , .{ | |
| 335 | self.expected_remaining, | |
| 336 | bytes, | |
| 337 | }); | |
| 338 | return error.TooMuchData; | |
| 339 | } | |
| 340 | if (!mem.eql(u8, self.expected_remaining[0..bytes.len], bytes)) { | |
| 341 | std.debug.print( | |
| 342 | \\====== expected this output: ========= | |
| 343 | \\{s} | |
| 344 | \\======== instead found this: ========= | |
| 345 | \\{s} | |
| 346 | \\====================================== | |
| 347 | , .{ | |
| 348 | self.expected_remaining[0..bytes.len], | |
| 349 | bytes, | |
| 350 | }); | |
| 351 | return error.DifferentData; | |
| 352 | } | |
| 353 | self.expected_remaining = self.expected_remaining[bytes.len..]; | |
| 354 | return bytes.len; | |
| 355 | } | |
| 356 | }; | |
| 357 | ||
| 358 | var vos = ValidationWriter.init(expected); | |
| 359 | try stringifyArbitraryDepth(testing.allocator, value, options, vos.writer()); | |
| 360 | if (vos.expected_remaining.len > 0) return error.NotEnoughData; | |
| 361 | ||
| 362 | // Also test with safety disabled. | |
| 363 | try testStringifyMaxDepth(expected, value, options, null); | |
| 364 | try testStringifyArbitraryDepth(expected, value, options); | |
| 365 | } | |
| 366 | ||
| 367 | fn testStringifyMaxDepth(expected: []const u8, value: anytype, options: StringifyOptions, comptime max_depth: ?usize) !void { | |
| 368 | var out_buf: [1024]u8 = undefined; | |
| 369 | var slice_stream = std.io.fixedBufferStream(&out_buf); | |
| 370 | const out = slice_stream.writer(); | |
| 371 | ||
| 372 | try stringifyMaxDepth(value, options, out, max_depth); | |
| 373 | const got = slice_stream.getWritten(); | |
| 374 | ||
| 375 | try testing.expectEqualStrings(expected, got); | |
| 376 | } | |
| 377 | ||
| 378 | fn testStringifyArbitraryDepth(expected: []const u8, value: anytype, options: StringifyOptions) !void { | |
| 379 | var out_buf: [1024]u8 = undefined; | |
| 380 | var slice_stream = std.io.fixedBufferStream(&out_buf); | |
| 381 | const out = slice_stream.writer(); | |
| 382 | ||
| 383 | try stringifyArbitraryDepth(testing.allocator, value, options, out); | |
| 384 | const got = slice_stream.getWritten(); | |
| 385 | ||
| 386 | try testing.expectEqualStrings(expected, got); | |
| 387 | } | |
| 388 | ||
| 389 | test "stringify alloc" { | |
| 390 | const allocator = std.testing.allocator; | |
| 391 | const expected = | |
| 392 | \\{"foo":"bar","answer":42,"my_friend":"sammy"} | |
| 393 | ; | |
| 394 | const actual = try stringifyAlloc(allocator, .{ .foo = "bar", .answer = 42, .my_friend = "sammy" }, .{}); | |
| 395 | defer allocator.free(actual); | |
| 396 | ||
| 397 | try std.testing.expectEqualStrings(expected, actual); | |
| 398 | } | |
| 399 | ||
| 400 | test "comptime stringify" { | |
| 401 | comptime testStringifyMaxDepth("false", false, .{}, null) catch unreachable; | |
| 402 | comptime testStringifyMaxDepth("false", false, .{}, 0) catch unreachable; | |
| 403 | comptime testStringifyArbitraryDepth("false", false, .{}) catch unreachable; | |
| 404 | ||
| 405 | const MyStruct = struct { | |
| 406 | foo: u32, | |
| 407 | }; | |
| 408 | comptime testStringifyMaxDepth("[{\"foo\":42},{\"foo\":100},{\"foo\":1000}]", [_]MyStruct{ | |
| 409 | MyStruct{ .foo = 42 }, | |
| 410 | MyStruct{ .foo = 100 }, | |
| 411 | MyStruct{ .foo = 1000 }, | |
| 412 | }, .{}, null) catch unreachable; | |
| 413 | comptime testStringifyMaxDepth("[{\"foo\":42},{\"foo\":100},{\"foo\":1000}]", [_]MyStruct{ | |
| 414 | MyStruct{ .foo = 42 }, | |
| 415 | MyStruct{ .foo = 100 }, | |
| 416 | MyStruct{ .foo = 1000 }, | |
| 417 | }, .{}, 8) catch unreachable; | |
| 418 | } | |
| 419 | ||
| 420 | test "print" { | |
| 421 | var out_buf: [1024]u8 = undefined; | |
| 422 | var slice_stream = std.io.fixedBufferStream(&out_buf); | |
| 423 | const out = slice_stream.writer(); | |
| 424 | ||
| 425 | var w = writeStream(out, .{ .whitespace = .indent_2 }); | |
| 426 | defer w.deinit(); | |
| 427 | ||
| 428 | try w.beginObject(); | |
| 429 | try w.objectField("a"); | |
| 430 | try w.print("[ ]", .{}); | |
| 431 | try w.objectField("b"); | |
| 432 | try w.beginArray(); | |
| 433 | try w.print("[{s}] ", .{"[]"}); | |
| 434 | try w.print(" {}", .{12345}); | |
| 435 | try w.endArray(); | |
| 436 | try w.endObject(); | |
| 437 | ||
| 438 | const result = slice_stream.getWritten(); | |
| 439 | const expected = | |
| 440 | \\{ | |
| 441 | \\ "a": [ ], | |
| 442 | \\ "b": [ | |
| 443 | \\ [[]] , | |
| 444 | \\ 12345 | |
| 445 | \\ ] | |
| 446 | \\} | |
| 447 | ; | |
| 448 | try std.testing.expectEqualStrings(expected, result); | |
| 449 | } | |
| 450 | ||
| 451 | test "nonportable numbers" { | |
| 452 | try testStringify("9999999999999999", 9999999999999999, .{}); | |
| 453 | try testStringify("\"9999999999999999\"", 9999999999999999, .{ .emit_nonportable_numbers_as_strings = true }); | |
| 454 | } | |
| 455 | ||
| 456 | test "stringify raw streaming" { | |
| 457 | var out_buf: [1024]u8 = undefined; | |
| 458 | var slice_stream = std.io.fixedBufferStream(&out_buf); | |
| 459 | const out = slice_stream.writer(); | |
| 460 | ||
| 461 | { | |
| 462 | var w = writeStream(out, .{ .whitespace = .indent_2 }); | |
| 463 | try testRawStreaming(&w, &slice_stream); | |
| 464 | } | |
| 465 | ||
| 466 | { | |
| 467 | var w = writeStreamMaxDepth(out, .{ .whitespace = .indent_2 }, 8); | |
| 468 | try testRawStreaming(&w, &slice_stream); | |
| 469 | } | |
| 470 | ||
| 471 | { | |
| 472 | var w = writeStreamMaxDepth(out, .{ .whitespace = .indent_2 }, null); | |
| 473 | try testRawStreaming(&w, &slice_stream); | |
| 474 | } | |
| 475 | ||
| 476 | { | |
| 477 | var w = writeStreamArbitraryDepth(testing.allocator, out, .{ .whitespace = .indent_2 }); | |
| 478 | defer w.deinit(); | |
| 479 | try testRawStreaming(&w, &slice_stream); | |
| 480 | } | |
| 481 | } | |
| 482 | ||
| 483 | fn testRawStreaming(w: anytype, slice_stream: anytype) !void { | |
| 484 | slice_stream.reset(); | |
| 485 | ||
| 486 | try w.beginObject(); | |
| 487 | try w.beginObjectFieldRaw(); | |
| 488 | try w.stream.writeAll("\"long"); | |
| 489 | try w.stream.writeAll(" key\""); | |
| 490 | w.endObjectFieldRaw(); | |
| 491 | try w.beginWriteRaw(); | |
| 492 | try w.stream.writeAll("\"long"); | |
| 493 | try w.stream.writeAll(" value\""); | |
| 494 | w.endWriteRaw(); | |
| 495 | try w.endObject(); | |
| 496 | ||
| 497 | const result = slice_stream.getWritten(); | |
| 498 | const expected = | |
| 499 | \\{ | |
| 500 | \\ "long key": "long value" | |
| 501 | \\} | |
| 502 | ; | |
| 503 | try std.testing.expectEqualStrings(expected, result); | |
| 504 | } |
lib/std/json/test.zig+5-6| ... | ... | @@ -1,10 +1,9 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const json = std.json; | |
| 2 | 3 | const testing = std.testing; |
| 3 | 4 | const parseFromSlice = @import("./static.zig").parseFromSlice; |
| 4 | const validate = @import("./scanner.zig").validate; | |
| 5 | const JsonScanner = @import("./scanner.zig").Scanner; | |
| 5 | const Scanner = @import("./Scanner.zig"); | |
| 6 | 6 | const Value = @import("./dynamic.zig").Value; |
| 7 | const stringifyAlloc = @import("./stringify.zig").stringifyAlloc; | |
| 8 | 7 | |
| 9 | 8 | // Support for JSONTestSuite.zig |
| 10 | 9 | pub fn ok(s: []const u8) !void { |
| ... | ... | @@ -20,7 +19,7 @@ pub fn any(s: []const u8) !void { |
| 20 | 19 | testHighLevelDynamicParser(s) catch {}; |
| 21 | 20 | } |
| 22 | 21 | fn testLowLevelScanner(s: []const u8) !void { |
| 23 | var scanner = JsonScanner.initCompleteInput(testing.allocator, s); | |
| 22 | var scanner = Scanner.initCompleteInput(testing.allocator, s); | |
| 24 | 23 | defer scanner.deinit(); |
| 25 | 24 | while (true) { |
| 26 | 25 | const token = try scanner.next(); |
| ... | ... | @@ -47,12 +46,12 @@ test "n_object_closed_missing_value" { |
| 47 | 46 | } |
| 48 | 47 | |
| 49 | 48 | fn roundTrip(s: []const u8) !void { |
| 50 | try testing.expect(try validate(testing.allocator, s)); | |
| 49 | try testing.expect(try Scanner.validate(testing.allocator, s)); | |
| 51 | 50 | |
| 52 | 51 | var parsed = try parseFromSlice(Value, testing.allocator, s, .{}); |
| 53 | 52 | defer parsed.deinit(); |
| 54 | 53 | |
| 55 | const rendered = try stringifyAlloc(testing.allocator, parsed.value, .{}); | |
| 54 | const rendered = try json.Stringify.valueAlloc(testing.allocator, parsed.value, .{}); | |
| 56 | 55 | defer testing.allocator.free(rendered); |
| 57 | 56 | |
| 58 | 57 | try testing.expectEqualStrings(s, rendered); |
lib/std/zig.zig+16-13| ... | ... | @@ -446,8 +446,8 @@ pub fn fmtString(bytes: []const u8) std.fmt.Formatter([]const u8, stringEscape) |
| 446 | 446 | } |
| 447 | 447 | |
| 448 | 448 | /// Return a formatter for escaping a single quoted Zig string. |
| 449 | pub fn fmtChar(bytes: []const u8) std.fmt.Formatter([]const u8, charEscape) { | |
| 450 | return .{ .data = bytes }; | |
| 449 | pub fn fmtChar(c: u21) std.fmt.Formatter(u21, charEscape) { | |
| 450 | return .{ .data = c }; | |
| 451 | 451 | } |
| 452 | 452 | |
| 453 | 453 | test fmtString { |
| ... | ... | @@ -458,9 +458,7 @@ test fmtString { |
| 458 | 458 | } |
| 459 | 459 | |
| 460 | 460 | test fmtChar { |
| 461 | try std.testing.expectFmt( | |
| 462 | \\" \\ hi \x07 \x11 " derp \'" | |
| 463 | , "\"{f}\"", .{fmtChar(" \\ hi \x07 \x11 \" derp '")}); | |
| 461 | try std.testing.expectFmt("c \\u{26a1}", "{f} {f}", .{ fmtChar('c'), fmtChar('⚡') }); | |
| 464 | 462 | } |
| 465 | 463 | |
| 466 | 464 | /// Print the string as escaped contents of a double quoted string. |
| ... | ... | @@ -480,21 +478,26 @@ pub fn stringEscape(bytes: []const u8, w: *Writer) Writer.Error!void { |
| 480 | 478 | }; |
| 481 | 479 | } |
| 482 | 480 | |
| 483 | /// Print the string as escaped contents of a single-quoted string. | |
| 484 | pub fn charEscape(bytes: []const u8, w: *Writer) Writer.Error!void { | |
| 485 | for (bytes) |byte| switch (byte) { | |
| 481 | /// Print as escaped contents of a single-quoted string. | |
| 482 | pub fn charEscape(codepoint: u21, w: *Writer) Writer.Error!void { | |
| 483 | switch (codepoint) { | |
| 486 | 484 | '\n' => try w.writeAll("\\n"), |
| 487 | 485 | '\r' => try w.writeAll("\\r"), |
| 488 | 486 | '\t' => try w.writeAll("\\t"), |
| 489 | 487 | '\\' => try w.writeAll("\\\\"), |
| 490 | '"' => try w.writeByte('"'), | |
| 491 | 488 | '\'' => try w.writeAll("\\'"), |
| 492 | ' ', '!', '#'...'&', '('...'[', ']'...'~' => try w.writeByte(byte), | |
| 489 | '"', ' ', '!', '#'...'&', '('...'[', ']'...'~' => try w.writeByte(@intCast(codepoint)), | |
| 493 | 490 | else => { |
| 494 | try w.writeAll("\\x"); | |
| 495 | try w.printInt(byte, 16, .lower, .{ .width = 2, .fill = '0' }); | |
| 491 | if (std.math.cast(u8, codepoint)) |byte| { | |
| 492 | try w.writeAll("\\x"); | |
| 493 | try w.printInt(byte, 16, .lower, .{ .width = 2, .fill = '0' }); | |
| 494 | } else { | |
| 495 | try w.writeAll("\\u{"); | |
| 496 | try w.printInt(codepoint, 16, .lower, .{}); | |
| 497 | try w.writeByte('}'); | |
| 498 | } | |
| 496 | 499 | }, |
| 497 | }; | |
| 500 | } | |
| 498 | 501 | } |
| 499 | 502 | |
| 500 | 503 | pub fn isValidId(bytes: []const u8) bool { |
lib/std/zig/Ast.zig+1-1| ... | ... | @@ -574,7 +574,7 @@ pub fn renderError(tree: Ast, parse_error: Error, w: *Writer) Writer.Error!void |
| 574 | 574 | '/' => "comment", |
| 575 | 575 | else => unreachable, |
| 576 | 576 | }, |
| 577 | std.zig.fmtChar(tok_slice[parse_error.extra.offset..][0..1]), | |
| 577 | std.zig.fmtChar(tok_slice[parse_error.extra.offset]), | |
| 578 | 578 | }); |
| 579 | 579 | }, |
| 580 | 580 |
lib/std/zon.zig+1| ... | ... | @@ -38,6 +38,7 @@ |
| 38 | 38 | |
| 39 | 39 | pub const parse = @import("zon/parse.zig"); |
| 40 | 40 | pub const stringify = @import("zon/stringify.zig"); |
| 41 | pub const Serializer = @import("zon/Serializer.zig"); | |
| 41 | 42 | |
| 42 | 43 | test { |
| 43 | 44 | _ = parse; |
lib/std/zon/Serializer.zig created+929| ... | ... | @@ -0,0 +1,929 @@ |
| 1 | //! Lower level control over serialization, you can create a new instance with `serializer`. | |
| 2 | //! | |
| 3 | //! Useful when you want control over which fields are serialized, how they're represented, | |
| 4 | //! or want to write a ZON object that does not exist in memory. | |
| 5 | //! | |
| 6 | //! You can serialize values with `value`. To serialize recursive types, the following are provided: | |
| 7 | //! * `valueMaxDepth` | |
| 8 | //! * `valueArbitraryDepth` | |
| 9 | //! | |
| 10 | //! You can also serialize values using specific notations: | |
| 11 | //! * `int` | |
| 12 | //! * `float` | |
| 13 | //! * `codePoint` | |
| 14 | //! * `tuple` | |
| 15 | //! * `tupleMaxDepth` | |
| 16 | //! * `tupleArbitraryDepth` | |
| 17 | //! * `string` | |
| 18 | //! * `multilineString` | |
| 19 | //! | |
| 20 | //! For manual serialization of containers, see: | |
| 21 | //! * `beginStruct` | |
| 22 | //! * `beginTuple` | |
| 23 | ||
| 24 | options: Options = .{}, | |
| 25 | indent_level: u8 = 0, | |
| 26 | writer: *Writer, | |
| 27 | ||
| 28 | const Serializer = @This(); | |
| 29 | const std = @import("std"); | |
| 30 | const assert = std.debug.assert; | |
| 31 | const Writer = std.Io.Writer; | |
| 32 | ||
| 33 | pub const Error = Writer.Error; | |
| 34 | pub const DepthError = Error || error{ExceededMaxDepth}; | |
| 35 | ||
| 36 | pub const Options = struct { | |
| 37 | /// If false, only syntactically necessary whitespace is emitted. | |
| 38 | whitespace: bool = true, | |
| 39 | }; | |
| 40 | ||
| 41 | /// Options for manual serialization of container types. | |
| 42 | pub const ContainerOptions = struct { | |
| 43 | /// The whitespace style that should be used for this container. Ignored if whitespace is off. | |
| 44 | whitespace_style: union(enum) { | |
| 45 | /// If true, wrap every field. If false do not. | |
| 46 | wrap: bool, | |
| 47 | /// Automatically decide whether to wrap or not based on the number of fields. Following | |
| 48 | /// the standard rule of thumb, containers with more than two fields are wrapped. | |
| 49 | fields: usize, | |
| 50 | } = .{ .wrap = true }, | |
| 51 | ||
| 52 | fn shouldWrap(self: ContainerOptions) bool { | |
| 53 | return switch (self.whitespace_style) { | |
| 54 | .wrap => |wrap| wrap, | |
| 55 | .fields => |fields| fields > 2, | |
| 56 | }; | |
| 57 | } | |
| 58 | }; | |
| 59 | ||
| 60 | /// Options for serialization of an individual value. | |
| 61 | /// | |
| 62 | /// See `SerializeOptions` for more information on these options. | |
| 63 | pub const ValueOptions = struct { | |
| 64 | emit_codepoint_literals: EmitCodepointLiterals = .never, | |
| 65 | emit_strings_as_containers: bool = false, | |
| 66 | emit_default_optional_fields: bool = true, | |
| 67 | }; | |
| 68 | ||
| 69 | /// Determines when to emit Unicode code point literals as opposed to integer literals. | |
| 70 | pub const EmitCodepointLiterals = enum { | |
| 71 | /// Never emit Unicode code point literals. | |
| 72 | never, | |
| 73 | /// Emit Unicode code point literals for any `u8` in the printable ASCII range. | |
| 74 | printable_ascii, | |
| 75 | /// Emit Unicode code point literals for any unsigned integer with 21 bits or fewer | |
| 76 | /// whose value is a valid non-surrogate code point. | |
| 77 | always, | |
| 78 | ||
| 79 | /// If the value should be emitted as a Unicode codepoint, return it as a u21. | |
| 80 | fn emitAsCodepoint(self: @This(), val: anytype) ?u21 { | |
| 81 | // Rule out incompatible integer types | |
| 82 | switch (@typeInfo(@TypeOf(val))) { | |
| 83 | .int => |int_info| if (int_info.signedness == .signed or int_info.bits > 21) { | |
| 84 | return null; | |
| 85 | }, | |
| 86 | .comptime_int => {}, | |
| 87 | else => comptime unreachable, | |
| 88 | } | |
| 89 | ||
| 90 | // Return null if the value shouldn't be printed as a Unicode codepoint, or the value casted | |
| 91 | // to a u21 if it should. | |
| 92 | switch (self) { | |
| 93 | .always => { | |
| 94 | const c = std.math.cast(u21, val) orelse return null; | |
| 95 | if (!std.unicode.utf8ValidCodepoint(c)) return null; | |
| 96 | return c; | |
| 97 | }, | |
| 98 | .printable_ascii => { | |
| 99 | const c = std.math.cast(u8, val) orelse return null; | |
| 100 | if (!std.ascii.isPrint(c)) return null; | |
| 101 | return c; | |
| 102 | }, | |
| 103 | .never => { | |
| 104 | return null; | |
| 105 | }, | |
| 106 | } | |
| 107 | } | |
| 108 | }; | |
| 109 | ||
| 110 | /// Serialize a value, similar to `serialize`. | |
| 111 | pub fn value(self: *Serializer, val: anytype, options: ValueOptions) Error!void { | |
| 112 | comptime assert(!typeIsRecursive(@TypeOf(val))); | |
| 113 | return self.valueArbitraryDepth(val, options); | |
| 114 | } | |
| 115 | ||
| 116 | /// Serialize a value, similar to `serializeMaxDepth`. | |
| 117 | /// Can return `error.ExceededMaxDepth`. | |
| 118 | pub fn valueMaxDepth(self: *Serializer, val: anytype, options: ValueOptions, depth: usize) DepthError!void { | |
| 119 | try checkValueDepth(val, depth); | |
| 120 | return self.valueArbitraryDepth(val, options); | |
| 121 | } | |
| 122 | ||
| 123 | /// Serialize a value, similar to `serializeArbitraryDepth`. | |
| 124 | pub fn valueArbitraryDepth(self: *Serializer, val: anytype, options: ValueOptions) Error!void { | |
| 125 | comptime assert(canSerializeType(@TypeOf(val))); | |
| 126 | switch (@typeInfo(@TypeOf(val))) { | |
| 127 | .int, .comptime_int => if (options.emit_codepoint_literals.emitAsCodepoint(val)) |c| { | |
| 128 | self.codePoint(c) catch |err| switch (err) { | |
| 129 | error.InvalidCodepoint => unreachable, // Already validated | |
| 130 | else => |e| return e, | |
| 131 | }; | |
| 132 | } else { | |
| 133 | try self.int(val); | |
| 134 | }, | |
| 135 | .float, .comptime_float => try self.float(val), | |
| 136 | .bool, .null => try self.writer.print("{}", .{val}), | |
| 137 | .enum_literal => try self.ident(@tagName(val)), | |
| 138 | .@"enum" => try self.ident(@tagName(val)), | |
| 139 | .pointer => |pointer| { | |
| 140 | // Try to serialize as a string | |
| 141 | const item: ?type = switch (@typeInfo(pointer.child)) { | |
| 142 | .array => |array| array.child, | |
| 143 | else => if (pointer.size == .slice) pointer.child else null, | |
| 144 | }; | |
| 145 | if (item == u8 and | |
| 146 | (pointer.sentinel() == null or pointer.sentinel() == 0) and | |
| 147 | !options.emit_strings_as_containers) | |
| 148 | { | |
| 149 | return try self.string(val); | |
| 150 | } | |
| 151 | ||
| 152 | // Serialize as either a tuple or as the child type | |
| 153 | switch (pointer.size) { | |
| 154 | .slice => try self.tupleImpl(val, options), | |
| 155 | .one => try self.valueArbitraryDepth(val.*, options), | |
| 156 | else => comptime unreachable, | |
| 157 | } | |
| 158 | }, | |
| 159 | .array => { | |
| 160 | var container = try self.beginTuple( | |
| 161 | .{ .whitespace_style = .{ .fields = val.len } }, | |
| 162 | ); | |
| 163 | for (val) |item_val| { | |
| 164 | try container.fieldArbitraryDepth(item_val, options); | |
| 165 | } | |
| 166 | try container.end(); | |
| 167 | }, | |
| 168 | .@"struct" => |@"struct"| if (@"struct".is_tuple) { | |
| 169 | var container = try self.beginTuple( | |
| 170 | .{ .whitespace_style = .{ .fields = @"struct".fields.len } }, | |
| 171 | ); | |
| 172 | inline for (val) |field_value| { | |
| 173 | try container.fieldArbitraryDepth(field_value, options); | |
| 174 | } | |
| 175 | try container.end(); | |
| 176 | } else { | |
| 177 | // Decide which fields to emit | |
| 178 | const fields, const skipped: [@"struct".fields.len]bool = if (options.emit_default_optional_fields) b: { | |
| 179 | break :b .{ @"struct".fields.len, @splat(false) }; | |
| 180 | } else b: { | |
| 181 | var fields = @"struct".fields.len; | |
| 182 | var skipped: [@"struct".fields.len]bool = @splat(false); | |
| 183 | inline for (@"struct".fields, &skipped) |field_info, *skip| { | |
| 184 | if (field_info.default_value_ptr) |ptr| { | |
| 185 | const default: *const field_info.type = @ptrCast(@alignCast(ptr)); | |
| 186 | const field_value = @field(val, field_info.name); | |
| 187 | if (std.meta.eql(field_value, default.*)) { | |
| 188 | skip.* = true; | |
| 189 | fields -= 1; | |
| 190 | } | |
| 191 | } | |
| 192 | } | |
| 193 | break :b .{ fields, skipped }; | |
| 194 | }; | |
| 195 | ||
| 196 | // Emit those fields | |
| 197 | var container = try self.beginStruct( | |
| 198 | .{ .whitespace_style = .{ .fields = fields } }, | |
| 199 | ); | |
| 200 | inline for (@"struct".fields, skipped) |field_info, skip| { | |
| 201 | if (!skip) { | |
| 202 | try container.fieldArbitraryDepth( | |
| 203 | field_info.name, | |
| 204 | @field(val, field_info.name), | |
| 205 | options, | |
| 206 | ); | |
| 207 | } | |
| 208 | } | |
| 209 | try container.end(); | |
| 210 | }, | |
| 211 | .@"union" => |@"union"| { | |
| 212 | comptime assert(@"union".tag_type != null); | |
| 213 | switch (val) { | |
| 214 | inline else => |pl, tag| if (@TypeOf(pl) == void) | |
| 215 | try self.writer.print(".{s}", .{@tagName(tag)}) | |
| 216 | else { | |
| 217 | var container = try self.beginStruct(.{ .whitespace_style = .{ .fields = 1 } }); | |
| 218 | ||
| 219 | try container.fieldArbitraryDepth( | |
| 220 | @tagName(tag), | |
| 221 | pl, | |
| 222 | options, | |
| 223 | ); | |
| 224 | ||
| 225 | try container.end(); | |
| 226 | }, | |
| 227 | } | |
| 228 | }, | |
| 229 | .optional => if (val) |inner| { | |
| 230 | try self.valueArbitraryDepth(inner, options); | |
| 231 | } else { | |
| 232 | try self.writer.writeAll("null"); | |
| 233 | }, | |
| 234 | .vector => |vector| { | |
| 235 | var container = try self.beginTuple( | |
| 236 | .{ .whitespace_style = .{ .fields = vector.len } }, | |
| 237 | ); | |
| 238 | for (0..vector.len) |i| { | |
| 239 | try container.fieldArbitraryDepth(val[i], options); | |
| 240 | } | |
| 241 | try container.end(); | |
| 242 | }, | |
| 243 | ||
| 244 | else => comptime unreachable, | |
| 245 | } | |
| 246 | } | |
| 247 | ||
| 248 | /// Serialize an integer. | |
| 249 | pub fn int(self: *Serializer, val: anytype) Error!void { | |
| 250 | try self.writer.printInt(val, 10, .lower, .{}); | |
| 251 | } | |
| 252 | ||
| 253 | /// Serialize a float. | |
| 254 | pub fn float(self: *Serializer, val: anytype) Error!void { | |
| 255 | switch (@typeInfo(@TypeOf(val))) { | |
| 256 | .float => if (std.math.isNan(val)) { | |
| 257 | return self.writer.writeAll("nan"); | |
| 258 | } else if (std.math.isPositiveInf(val)) { | |
| 259 | return self.writer.writeAll("inf"); | |
| 260 | } else if (std.math.isNegativeInf(val)) { | |
| 261 | return self.writer.writeAll("-inf"); | |
| 262 | } else if (std.math.isNegativeZero(val)) { | |
| 263 | return self.writer.writeAll("-0.0"); | |
| 264 | } else { | |
| 265 | try self.writer.print("{d}", .{val}); | |
| 266 | }, | |
| 267 | .comptime_float => if (val == 0) { | |
| 268 | return self.writer.writeAll("0"); | |
| 269 | } else { | |
| 270 | try self.writer.print("{d}", .{val}); | |
| 271 | }, | |
| 272 | else => comptime unreachable, | |
| 273 | } | |
| 274 | } | |
| 275 | ||
| 276 | /// Serialize `name` as an identifier prefixed with `.`. | |
| 277 | /// | |
| 278 | /// Escapes the identifier if necessary. | |
| 279 | pub fn ident(self: *Serializer, name: []const u8) Error!void { | |
| 280 | try self.writer.print(".{f}", .{std.zig.fmtIdPU(name)}); | |
| 281 | } | |
| 282 | ||
| 283 | pub const CodePointError = Error || error{InvalidCodepoint}; | |
| 284 | ||
| 285 | /// Serialize `val` as a Unicode codepoint. | |
| 286 | /// | |
| 287 | /// Returns `error.InvalidCodepoint` if `val` is not a valid Unicode codepoint. | |
| 288 | pub fn codePoint(self: *Serializer, val: u21) CodePointError!void { | |
| 289 | try self.writer.print("'{f}'", .{std.zig.fmtChar(val)}); | |
| 290 | } | |
| 291 | ||
| 292 | /// Like `value`, but always serializes `val` as a tuple. | |
| 293 | /// | |
| 294 | /// Will fail at comptime if `val` is not a tuple, array, pointer to an array, or slice. | |
| 295 | pub fn tuple(self: *Serializer, val: anytype, options: ValueOptions) Error!void { | |
| 296 | comptime assert(!typeIsRecursive(@TypeOf(val))); | |
| 297 | try self.tupleArbitraryDepth(val, options); | |
| 298 | } | |
| 299 | ||
| 300 | /// Like `tuple`, but recursive types are allowed. | |
| 301 | /// | |
| 302 | /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. | |
| 303 | pub fn tupleMaxDepth( | |
| 304 | self: *Serializer, | |
| 305 | val: anytype, | |
| 306 | options: ValueOptions, | |
| 307 | depth: usize, | |
| 308 | ) DepthError!void { | |
| 309 | try checkValueDepth(val, depth); | |
| 310 | try self.tupleArbitraryDepth(val, options); | |
| 311 | } | |
| 312 | ||
| 313 | /// Like `tuple`, but recursive types are allowed. | |
| 314 | /// | |
| 315 | /// It is the caller's responsibility to ensure that `val` does not contain cycles. | |
| 316 | pub fn tupleArbitraryDepth( | |
| 317 | self: *Serializer, | |
| 318 | val: anytype, | |
| 319 | options: ValueOptions, | |
| 320 | ) Error!void { | |
| 321 | try self.tupleImpl(val, options); | |
| 322 | } | |
| 323 | ||
| 324 | fn tupleImpl(self: *Serializer, val: anytype, options: ValueOptions) Error!void { | |
| 325 | comptime assert(canSerializeType(@TypeOf(val))); | |
| 326 | switch (@typeInfo(@TypeOf(val))) { | |
| 327 | .@"struct" => { | |
| 328 | var container = try self.beginTuple(.{ .whitespace_style = .{ .fields = val.len } }); | |
| 329 | inline for (val) |item_val| { | |
| 330 | try container.fieldArbitraryDepth(item_val, options); | |
| 331 | } | |
| 332 | try container.end(); | |
| 333 | }, | |
| 334 | .pointer, .array => { | |
| 335 | var container = try self.beginTuple(.{ .whitespace_style = .{ .fields = val.len } }); | |
| 336 | for (val) |item_val| { | |
| 337 | try container.fieldArbitraryDepth(item_val, options); | |
| 338 | } | |
| 339 | try container.end(); | |
| 340 | }, | |
| 341 | else => comptime unreachable, | |
| 342 | } | |
| 343 | } | |
| 344 | ||
| 345 | /// Like `value`, but always serializes `val` as a string. | |
| 346 | pub fn string(self: *Serializer, val: []const u8) Error!void { | |
| 347 | try self.writer.print("\"{f}\"", .{std.zig.fmtString(val)}); | |
| 348 | } | |
| 349 | ||
| 350 | /// Options for formatting multiline strings. | |
| 351 | pub const MultilineStringOptions = struct { | |
| 352 | /// If top level is true, whitespace before and after the multiline string is elided. | |
| 353 | /// If it is true, a newline is printed, then the value, followed by a newline, and if | |
| 354 | /// whitespace is true any necessary indentation follows. | |
| 355 | top_level: bool = false, | |
| 356 | }; | |
| 357 | ||
| 358 | pub const MultilineStringError = Error || error{InnerCarriageReturn}; | |
| 359 | ||
| 360 | /// Like `value`, but always serializes to a multiline string literal. | |
| 361 | /// | |
| 362 | /// Returns `error.InnerCarriageReturn` if `val` contains a CR not followed by a newline, | |
| 363 | /// since multiline strings cannot represent CR without a following newline. | |
| 364 | pub fn multilineString( | |
| 365 | self: *Serializer, | |
| 366 | val: []const u8, | |
| 367 | options: MultilineStringOptions, | |
| 368 | ) MultilineStringError!void { | |
| 369 | // Make sure the string does not contain any carriage returns not followed by a newline | |
| 370 | var i: usize = 0; | |
| 371 | while (i < val.len) : (i += 1) { | |
| 372 | if (val[i] == '\r') { | |
| 373 | if (i + 1 < val.len) { | |
| 374 | if (val[i + 1] == '\n') { | |
| 375 | i += 1; | |
| 376 | continue; | |
| 377 | } | |
| 378 | } | |
| 379 | return error.InnerCarriageReturn; | |
| 380 | } | |
| 381 | } | |
| 382 | ||
| 383 | if (!options.top_level) { | |
| 384 | try self.newline(); | |
| 385 | try self.indent(); | |
| 386 | } | |
| 387 | ||
| 388 | try self.writer.writeAll("\\\\"); | |
| 389 | for (val) |c| { | |
| 390 | if (c != '\r') { | |
| 391 | try self.writer.writeByte(c); // We write newlines here even if whitespace off | |
| 392 | if (c == '\n') { | |
| 393 | try self.indent(); | |
| 394 | try self.writer.writeAll("\\\\"); | |
| 395 | } | |
| 396 | } | |
| 397 | } | |
| 398 | ||
| 399 | if (!options.top_level) { | |
| 400 | try self.writer.writeByte('\n'); // Even if whitespace off | |
| 401 | try self.indent(); | |
| 402 | } | |
| 403 | } | |
| 404 | ||
| 405 | /// Create a `Struct` for writing ZON structs field by field. | |
| 406 | pub fn beginStruct(self: *Serializer, options: ContainerOptions) Error!Struct { | |
| 407 | return Struct.begin(self, options); | |
| 408 | } | |
| 409 | ||
| 410 | /// Creates a `Tuple` for writing ZON tuples field by field. | |
| 411 | pub fn beginTuple(self: *Serializer, options: ContainerOptions) Error!Tuple { | |
| 412 | return Tuple.begin(self, options); | |
| 413 | } | |
| 414 | ||
| 415 | fn indent(self: *Serializer) Error!void { | |
| 416 | if (self.options.whitespace) { | |
| 417 | try self.writer.splatByteAll(' ', 4 * self.indent_level); | |
| 418 | } | |
| 419 | } | |
| 420 | ||
| 421 | fn newline(self: *Serializer) Error!void { | |
| 422 | if (self.options.whitespace) { | |
| 423 | try self.writer.writeByte('\n'); | |
| 424 | } | |
| 425 | } | |
| 426 | ||
| 427 | fn newlineOrSpace(self: *Serializer, len: usize) Error!void { | |
| 428 | if (self.containerShouldWrap(len)) { | |
| 429 | try self.newline(); | |
| 430 | } else { | |
| 431 | try self.space(); | |
| 432 | } | |
| 433 | } | |
| 434 | ||
| 435 | fn space(self: *Serializer) Error!void { | |
| 436 | if (self.options.whitespace) { | |
| 437 | try self.writer.writeByte(' '); | |
| 438 | } | |
| 439 | } | |
| 440 | ||
| 441 | /// Writes ZON tuples field by field. | |
| 442 | pub const Tuple = struct { | |
| 443 | container: Container, | |
| 444 | ||
| 445 | fn begin(parent: *Serializer, options: ContainerOptions) Error!Tuple { | |
| 446 | return .{ | |
| 447 | .container = try Container.begin(parent, .anon, options), | |
| 448 | }; | |
| 449 | } | |
| 450 | ||
| 451 | /// Finishes serializing the tuple. | |
| 452 | /// | |
| 453 | /// Prints a trailing comma as configured when appropriate, and the closing bracket. | |
| 454 | pub fn end(self: *Tuple) Error!void { | |
| 455 | try self.container.end(); | |
| 456 | self.* = undefined; | |
| 457 | } | |
| 458 | ||
| 459 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`. | |
| 460 | pub fn field( | |
| 461 | self: *Tuple, | |
| 462 | val: anytype, | |
| 463 | options: ValueOptions, | |
| 464 | ) Error!void { | |
| 465 | try self.container.field(null, val, options); | |
| 466 | } | |
| 467 | ||
| 468 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`. | |
| 469 | /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. | |
| 470 | pub fn fieldMaxDepth( | |
| 471 | self: *Tuple, | |
| 472 | val: anytype, | |
| 473 | options: ValueOptions, | |
| 474 | depth: usize, | |
| 475 | ) DepthError!void { | |
| 476 | try self.container.fieldMaxDepth(null, val, options, depth); | |
| 477 | } | |
| 478 | ||
| 479 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by | |
| 480 | /// `valueArbitraryDepth`. | |
| 481 | pub fn fieldArbitraryDepth( | |
| 482 | self: *Tuple, | |
| 483 | val: anytype, | |
| 484 | options: ValueOptions, | |
| 485 | ) Error!void { | |
| 486 | try self.container.fieldArbitraryDepth(null, val, options); | |
| 487 | } | |
| 488 | ||
| 489 | /// Starts a field with a struct as a value. Returns the struct. | |
| 490 | pub fn beginStructField( | |
| 491 | self: *Tuple, | |
| 492 | options: ContainerOptions, | |
| 493 | ) Error!Struct { | |
| 494 | try self.fieldPrefix(); | |
| 495 | return self.container.serializer.beginStruct(options); | |
| 496 | } | |
| 497 | ||
| 498 | /// Starts a field with a tuple as a value. Returns the tuple. | |
| 499 | pub fn beginTupleField( | |
| 500 | self: *Tuple, | |
| 501 | options: ContainerOptions, | |
| 502 | ) Error!Tuple { | |
| 503 | try self.fieldPrefix(); | |
| 504 | return self.container.serializer.beginTuple(options); | |
| 505 | } | |
| 506 | ||
| 507 | /// Print a field prefix. This prints any necessary commas, and whitespace as | |
| 508 | /// configured. Useful if you want to serialize the field value yourself. | |
| 509 | pub fn fieldPrefix(self: *Tuple) Error!void { | |
| 510 | try self.container.fieldPrefix(null); | |
| 511 | } | |
| 512 | }; | |
| 513 | ||
| 514 | /// Writes ZON structs field by field. | |
| 515 | pub const Struct = struct { | |
| 516 | container: Container, | |
| 517 | ||
| 518 | fn begin(parent: *Serializer, options: ContainerOptions) Error!Struct { | |
| 519 | return .{ | |
| 520 | .container = try Container.begin(parent, .named, options), | |
| 521 | }; | |
| 522 | } | |
| 523 | ||
| 524 | /// Finishes serializing the struct. | |
| 525 | /// | |
| 526 | /// Prints a trailing comma as configured when appropriate, and the closing bracket. | |
| 527 | pub fn end(self: *Struct) Error!void { | |
| 528 | try self.container.end(); | |
| 529 | self.* = undefined; | |
| 530 | } | |
| 531 | ||
| 532 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`. | |
| 533 | pub fn field( | |
| 534 | self: *Struct, | |
| 535 | name: []const u8, | |
| 536 | val: anytype, | |
| 537 | options: ValueOptions, | |
| 538 | ) Error!void { | |
| 539 | try self.container.field(name, val, options); | |
| 540 | } | |
| 541 | ||
| 542 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`. | |
| 543 | /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. | |
| 544 | pub fn fieldMaxDepth( | |
| 545 | self: *Struct, | |
| 546 | name: []const u8, | |
| 547 | val: anytype, | |
| 548 | options: ValueOptions, | |
| 549 | depth: usize, | |
| 550 | ) DepthError!void { | |
| 551 | try self.container.fieldMaxDepth(name, val, options, depth); | |
| 552 | } | |
| 553 | ||
| 554 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by | |
| 555 | /// `valueArbitraryDepth`. | |
| 556 | pub fn fieldArbitraryDepth( | |
| 557 | self: *Struct, | |
| 558 | name: []const u8, | |
| 559 | val: anytype, | |
| 560 | options: ValueOptions, | |
| 561 | ) Error!void { | |
| 562 | try self.container.fieldArbitraryDepth(name, val, options); | |
| 563 | } | |
| 564 | ||
| 565 | /// Starts a field with a struct as a value. Returns the struct. | |
| 566 | pub fn beginStructField( | |
| 567 | self: *Struct, | |
| 568 | name: []const u8, | |
| 569 | options: ContainerOptions, | |
| 570 | ) Error!Struct { | |
| 571 | try self.fieldPrefix(name); | |
| 572 | return self.container.serializer.beginStruct(options); | |
| 573 | } | |
| 574 | ||
| 575 | /// Starts a field with a tuple as a value. Returns the tuple. | |
| 576 | pub fn beginTupleField( | |
| 577 | self: *Struct, | |
| 578 | name: []const u8, | |
| 579 | options: ContainerOptions, | |
| 580 | ) Error!Tuple { | |
| 581 | try self.fieldPrefix(name); | |
| 582 | return self.container.serializer.beginTuple(options); | |
| 583 | } | |
| 584 | ||
| 585 | /// Print a field prefix. This prints any necessary commas, the field name (escaped if | |
| 586 | /// necessary) and whitespace as configured. Useful if you want to serialize the field | |
| 587 | /// value yourself. | |
| 588 | pub fn fieldPrefix(self: *Struct, name: []const u8) Error!void { | |
| 589 | try self.container.fieldPrefix(name); | |
| 590 | } | |
| 591 | }; | |
| 592 | ||
| 593 | const Container = struct { | |
| 594 | const FieldStyle = enum { named, anon }; | |
| 595 | ||
| 596 | serializer: *Serializer, | |
| 597 | field_style: FieldStyle, | |
| 598 | options: ContainerOptions, | |
| 599 | empty: bool, | |
| 600 | ||
| 601 | fn begin( | |
| 602 | sz: *Serializer, | |
| 603 | field_style: FieldStyle, | |
| 604 | options: ContainerOptions, | |
| 605 | ) Error!Container { | |
| 606 | if (options.shouldWrap()) sz.indent_level +|= 1; | |
| 607 | try sz.writer.writeAll(".{"); | |
| 608 | return .{ | |
| 609 | .serializer = sz, | |
| 610 | .field_style = field_style, | |
| 611 | .options = options, | |
| 612 | .empty = true, | |
| 613 | }; | |
| 614 | } | |
| 615 | ||
| 616 | fn end(self: *Container) Error!void { | |
| 617 | if (self.options.shouldWrap()) self.serializer.indent_level -|= 1; | |
| 618 | if (!self.empty) { | |
| 619 | if (self.options.shouldWrap()) { | |
| 620 | if (self.serializer.options.whitespace) { | |
| 621 | try self.serializer.writer.writeByte(','); | |
| 622 | } | |
| 623 | try self.serializer.newline(); | |
| 624 | try self.serializer.indent(); | |
| 625 | } else if (!self.shouldElideSpaces()) { | |
| 626 | try self.serializer.space(); | |
| 627 | } | |
| 628 | } | |
| 629 | try self.serializer.writer.writeByte('}'); | |
| 630 | self.* = undefined; | |
| 631 | } | |
| 632 | ||
| 633 | fn fieldPrefix(self: *Container, name: ?[]const u8) Error!void { | |
| 634 | if (!self.empty) { | |
| 635 | try self.serializer.writer.writeByte(','); | |
| 636 | } | |
| 637 | self.empty = false; | |
| 638 | if (self.options.shouldWrap()) { | |
| 639 | try self.serializer.newline(); | |
| 640 | } else if (!self.shouldElideSpaces()) { | |
| 641 | try self.serializer.space(); | |
| 642 | } | |
| 643 | if (self.options.shouldWrap()) try self.serializer.indent(); | |
| 644 | if (name) |n| { | |
| 645 | try self.serializer.ident(n); | |
| 646 | try self.serializer.space(); | |
| 647 | try self.serializer.writer.writeByte('='); | |
| 648 | try self.serializer.space(); | |
| 649 | } | |
| 650 | } | |
| 651 | ||
| 652 | fn field( | |
| 653 | self: *Container, | |
| 654 | name: ?[]const u8, | |
| 655 | val: anytype, | |
| 656 | options: ValueOptions, | |
| 657 | ) Error!void { | |
| 658 | comptime assert(!typeIsRecursive(@TypeOf(val))); | |
| 659 | try self.fieldArbitraryDepth(name, val, options); | |
| 660 | } | |
| 661 | ||
| 662 | /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. | |
| 663 | fn fieldMaxDepth( | |
| 664 | self: *Container, | |
| 665 | name: ?[]const u8, | |
| 666 | val: anytype, | |
| 667 | options: ValueOptions, | |
| 668 | depth: usize, | |
| 669 | ) DepthError!void { | |
| 670 | try checkValueDepth(val, depth); | |
| 671 | try self.fieldArbitraryDepth(name, val, options); | |
| 672 | } | |
| 673 | ||
| 674 | fn fieldArbitraryDepth( | |
| 675 | self: *Container, | |
| 676 | name: ?[]const u8, | |
| 677 | val: anytype, | |
| 678 | options: ValueOptions, | |
| 679 | ) Error!void { | |
| 680 | try self.fieldPrefix(name); | |
| 681 | try self.serializer.valueArbitraryDepth(val, options); | |
| 682 | } | |
| 683 | ||
| 684 | fn shouldElideSpaces(self: *const Container) bool { | |
| 685 | return switch (self.options.whitespace_style) { | |
| 686 | .fields => |fields| self.field_style != .named and fields == 1, | |
| 687 | else => false, | |
| 688 | }; | |
| 689 | } | |
| 690 | }; | |
| 691 | ||
| 692 | test Serializer { | |
| 693 | var discarding: Writer.Discarding = .init(&.{}); | |
| 694 | var s: Serializer = .{ .writer = &discarding.writer }; | |
| 695 | var vec2 = try s.beginStruct(.{}); | |
| 696 | try vec2.field("x", 1.5, .{}); | |
| 697 | try vec2.fieldPrefix("prefix"); | |
| 698 | try s.value(2.5, .{}); | |
| 699 | try vec2.end(); | |
| 700 | } | |
| 701 | ||
| 702 | inline fn typeIsRecursive(comptime T: type) bool { | |
| 703 | return comptime typeIsRecursiveInner(T, &.{}); | |
| 704 | } | |
| 705 | ||
| 706 | fn typeIsRecursiveInner(comptime T: type, comptime prev_visited: []const type) bool { | |
| 707 | for (prev_visited) |V| { | |
| 708 | if (V == T) return true; | |
| 709 | } | |
| 710 | const visited = prev_visited ++ .{T}; | |
| 711 | ||
| 712 | return switch (@typeInfo(T)) { | |
| 713 | .pointer => |pointer| typeIsRecursiveInner(pointer.child, visited), | |
| 714 | .optional => |optional| typeIsRecursiveInner(optional.child, visited), | |
| 715 | .array => |array| typeIsRecursiveInner(array.child, visited), | |
| 716 | .vector => |vector| typeIsRecursiveInner(vector.child, visited), | |
| 717 | .@"struct" => |@"struct"| for (@"struct".fields) |field| { | |
| 718 | if (typeIsRecursiveInner(field.type, visited)) break true; | |
| 719 | } else false, | |
| 720 | .@"union" => |@"union"| inline for (@"union".fields) |field| { | |
| 721 | if (typeIsRecursiveInner(field.type, visited)) break true; | |
| 722 | } else false, | |
| 723 | else => false, | |
| 724 | }; | |
| 725 | } | |
| 726 | ||
| 727 | test typeIsRecursive { | |
| 728 | try std.testing.expect(!typeIsRecursive(bool)); | |
| 729 | try std.testing.expect(!typeIsRecursive(struct { x: i32, y: i32 })); | |
| 730 | try std.testing.expect(!typeIsRecursive(struct { i32, i32 })); | |
| 731 | try std.testing.expect(typeIsRecursive(struct { x: i32, y: i32, z: *@This() })); | |
| 732 | try std.testing.expect(typeIsRecursive(struct { | |
| 733 | a: struct { | |
| 734 | const A = @This(); | |
| 735 | b: struct { | |
| 736 | c: *struct { | |
| 737 | a: ?A, | |
| 738 | }, | |
| 739 | }, | |
| 740 | }, | |
| 741 | })); | |
| 742 | try std.testing.expect(typeIsRecursive(struct { | |
| 743 | a: [3]*@This(), | |
| 744 | })); | |
| 745 | try std.testing.expect(typeIsRecursive(struct { | |
| 746 | a: union { a: i32, b: *@This() }, | |
| 747 | })); | |
| 748 | } | |
| 749 | ||
| 750 | fn checkValueDepth(val: anytype, depth: usize) error{ExceededMaxDepth}!void { | |
| 751 | if (depth == 0) return error.ExceededMaxDepth; | |
| 752 | const child_depth = depth - 1; | |
| 753 | ||
| 754 | switch (@typeInfo(@TypeOf(val))) { | |
| 755 | .pointer => |pointer| switch (pointer.size) { | |
| 756 | .one => try checkValueDepth(val.*, child_depth), | |
| 757 | .slice => for (val) |item| { | |
| 758 | try checkValueDepth(item, child_depth); | |
| 759 | }, | |
| 760 | .c, .many => {}, | |
| 761 | }, | |
| 762 | .array => for (val) |item| { | |
| 763 | try checkValueDepth(item, child_depth); | |
| 764 | }, | |
| 765 | .@"struct" => |@"struct"| inline for (@"struct".fields) |field_info| { | |
| 766 | try checkValueDepth(@field(val, field_info.name), child_depth); | |
| 767 | }, | |
| 768 | .@"union" => |@"union"| if (@"union".tag_type == null) { | |
| 769 | return; | |
| 770 | } else switch (val) { | |
| 771 | inline else => |payload| { | |
| 772 | return checkValueDepth(payload, child_depth); | |
| 773 | }, | |
| 774 | }, | |
| 775 | .optional => if (val) |inner| try checkValueDepth(inner, child_depth), | |
| 776 | else => {}, | |
| 777 | } | |
| 778 | } | |
| 779 | ||
| 780 | fn expectValueDepthEquals(expected: usize, v: anytype) !void { | |
| 781 | try checkValueDepth(v, expected); | |
| 782 | try std.testing.expectError(error.ExceededMaxDepth, checkValueDepth(v, expected - 1)); | |
| 783 | } | |
| 784 | ||
| 785 | test checkValueDepth { | |
| 786 | try expectValueDepthEquals(1, 10); | |
| 787 | try expectValueDepthEquals(2, .{ .x = 1, .y = 2 }); | |
| 788 | try expectValueDepthEquals(2, .{ 1, 2 }); | |
| 789 | try expectValueDepthEquals(3, .{ 1, .{ 2, 3 } }); | |
| 790 | try expectValueDepthEquals(3, .{ .{ 1, 2 }, 3 }); | |
| 791 | try expectValueDepthEquals(3, .{ .x = 0, .y = 1, .z = .{ .x = 3 } }); | |
| 792 | try expectValueDepthEquals(3, .{ .x = 0, .y = .{ .x = 1 }, .z = 2 }); | |
| 793 | try expectValueDepthEquals(3, .{ .x = .{ .x = 0 }, .y = 1, .z = 2 }); | |
| 794 | try expectValueDepthEquals(2, @as(?u32, 1)); | |
| 795 | try expectValueDepthEquals(1, @as(?u32, null)); | |
| 796 | try expectValueDepthEquals(1, null); | |
| 797 | try expectValueDepthEquals(2, &1); | |
| 798 | try expectValueDepthEquals(3, &@as(?u32, 1)); | |
| 799 | ||
| 800 | const Union = union(enum) { | |
| 801 | x: u32, | |
| 802 | y: struct { x: u32 }, | |
| 803 | }; | |
| 804 | try expectValueDepthEquals(2, Union{ .x = 1 }); | |
| 805 | try expectValueDepthEquals(3, Union{ .y = .{ .x = 1 } }); | |
| 806 | ||
| 807 | const Recurse = struct { r: ?*const @This() }; | |
| 808 | try expectValueDepthEquals(2, Recurse{ .r = null }); | |
| 809 | try expectValueDepthEquals(5, Recurse{ .r = &Recurse{ .r = null } }); | |
| 810 | try expectValueDepthEquals(8, Recurse{ .r = &Recurse{ .r = &Recurse{ .r = null } } }); | |
| 811 | ||
| 812 | try expectValueDepthEquals(2, @as([]const u8, &.{ 1, 2, 3 })); | |
| 813 | try expectValueDepthEquals(3, @as([]const []const u8, &.{&.{ 1, 2, 3 }})); | |
| 814 | } | |
| 815 | ||
| 816 | inline fn canSerializeType(T: type) bool { | |
| 817 | comptime return canSerializeTypeInner(T, &.{}, false); | |
| 818 | } | |
| 819 | ||
| 820 | fn canSerializeTypeInner( | |
| 821 | T: type, | |
| 822 | /// Visited structs and unions, to avoid infinite recursion. | |
| 823 | /// Tracking more types is unnecessary, and a little complex due to optional nesting. | |
| 824 | visited: []const type, | |
| 825 | parent_is_optional: bool, | |
| 826 | ) bool { | |
| 827 | return switch (@typeInfo(T)) { | |
| 828 | .bool, | |
| 829 | .int, | |
| 830 | .float, | |
| 831 | .comptime_float, | |
| 832 | .comptime_int, | |
| 833 | .null, | |
| 834 | .enum_literal, | |
| 835 | => true, | |
| 836 | ||
| 837 | .noreturn, | |
| 838 | .void, | |
| 839 | .type, | |
| 840 | .undefined, | |
| 841 | .error_union, | |
| 842 | .error_set, | |
| 843 | .@"fn", | |
| 844 | .frame, | |
| 845 | .@"anyframe", | |
| 846 | .@"opaque", | |
| 847 | => false, | |
| 848 | ||
| 849 | .@"enum" => |@"enum"| @"enum".is_exhaustive, | |
| 850 | ||
| 851 | .pointer => |pointer| switch (pointer.size) { | |
| 852 | .one => canSerializeTypeInner(pointer.child, visited, parent_is_optional), | |
| 853 | .slice => canSerializeTypeInner(pointer.child, visited, false), | |
| 854 | .many, .c => false, | |
| 855 | }, | |
| 856 | ||
| 857 | .optional => |optional| if (parent_is_optional) | |
| 858 | false | |
| 859 | else | |
| 860 | canSerializeTypeInner(optional.child, visited, true), | |
| 861 | ||
| 862 | .array => |array| canSerializeTypeInner(array.child, visited, false), | |
| 863 | .vector => |vector| canSerializeTypeInner(vector.child, visited, false), | |
| 864 | ||
| 865 | .@"struct" => |@"struct"| { | |
| 866 | for (visited) |V| if (T == V) return true; | |
| 867 | const new_visited = visited ++ .{T}; | |
| 868 | for (@"struct".fields) |field| { | |
| 869 | if (!canSerializeTypeInner(field.type, new_visited, false)) return false; | |
| 870 | } | |
| 871 | return true; | |
| 872 | }, | |
| 873 | .@"union" => |@"union"| { | |
| 874 | for (visited) |V| if (T == V) return true; | |
| 875 | const new_visited = visited ++ .{T}; | |
| 876 | if (@"union".tag_type == null) return false; | |
| 877 | for (@"union".fields) |field| { | |
| 878 | if (field.type != void and !canSerializeTypeInner(field.type, new_visited, false)) { | |
| 879 | return false; | |
| 880 | } | |
| 881 | } | |
| 882 | return true; | |
| 883 | }, | |
| 884 | }; | |
| 885 | } | |
| 886 | ||
| 887 | test canSerializeType { | |
| 888 | try std.testing.expect(!comptime canSerializeType(void)); | |
| 889 | try std.testing.expect(!comptime canSerializeType(struct { f: [*]u8 })); | |
| 890 | try std.testing.expect(!comptime canSerializeType(struct { error{foo} })); | |
| 891 | try std.testing.expect(!comptime canSerializeType(union(enum) { a: void, f: [*c]u8 })); | |
| 892 | try std.testing.expect(!comptime canSerializeType(@Vector(0, [*c]u8))); | |
| 893 | try std.testing.expect(!comptime canSerializeType(*?[*c]u8)); | |
| 894 | try std.testing.expect(!comptime canSerializeType(enum(u8) { _ })); | |
| 895 | try std.testing.expect(!comptime canSerializeType(union { foo: void })); | |
| 896 | try std.testing.expect(comptime canSerializeType(union(enum) { foo: void })); | |
| 897 | try std.testing.expect(comptime canSerializeType(comptime_float)); | |
| 898 | try std.testing.expect(comptime canSerializeType(comptime_int)); | |
| 899 | try std.testing.expect(!comptime canSerializeType(struct { comptime foo: ??u8 = null })); | |
| 900 | try std.testing.expect(comptime canSerializeType(@TypeOf(.foo))); | |
| 901 | try std.testing.expect(comptime canSerializeType(?u8)); | |
| 902 | try std.testing.expect(comptime canSerializeType(*?*u8)); | |
| 903 | try std.testing.expect(comptime canSerializeType(?struct { | |
| 904 | foo: ?struct { | |
| 905 | ?union(enum) { | |
| 906 | a: ?@Vector(0, ?*u8), | |
| 907 | }, | |
| 908 | ?struct { | |
| 909 | f: ?[]?u8, | |
| 910 | }, | |
| 911 | }, | |
| 912 | })); | |
| 913 | try std.testing.expect(!comptime canSerializeType(??u8)); | |
| 914 | try std.testing.expect(!comptime canSerializeType(?*?u8)); | |
| 915 | try std.testing.expect(!comptime canSerializeType(*?*?*u8)); | |
| 916 | try std.testing.expect(comptime canSerializeType(struct { x: comptime_int = 2 })); | |
| 917 | try std.testing.expect(comptime canSerializeType(struct { x: comptime_float = 2 })); | |
| 918 | try std.testing.expect(comptime canSerializeType(struct { comptime_int })); | |
| 919 | try std.testing.expect(comptime canSerializeType(struct { comptime x: @TypeOf(.foo) = .foo })); | |
| 920 | const Recursive = struct { foo: ?*@This() }; | |
| 921 | try std.testing.expect(comptime canSerializeType(Recursive)); | |
| 922 | ||
| 923 | // Make sure we validate nested optional before we early out due to already having seen | |
| 924 | // a type recursion! | |
| 925 | try std.testing.expect(!comptime canSerializeType(struct { | |
| 926 | add_to_visited: ?u8, | |
| 927 | retrieve_from_visited: ??u8, | |
| 928 | })); | |
| 929 | } |
lib/std/zon/parse.zig+13-13| ... | ... | @@ -64,14 +64,14 @@ pub const Error = union(enum) { |
| 64 | 64 | } |
| 65 | 65 | }; |
| 66 | 66 | |
| 67 | fn formatMessage(self: []const u8, w: *std.io.Writer) std.io.Writer.Error!void { | |
| 67 | fn formatMessage(self: []const u8, w: *std.Io.Writer) std.Io.Writer.Error!void { | |
| 68 | 68 | // Just writes the string for now, but we're keeping this behind a formatter so we have |
| 69 | 69 | // the option to extend it in the future to print more advanced messages (like `Error` |
| 70 | 70 | // does) without breaking the API. |
| 71 | 71 | try w.writeAll(self); |
| 72 | 72 | } |
| 73 | 73 | |
| 74 | pub fn fmtMessage(self: Note, diag: *const Diagnostics) std.fmt.Formatter([]const u8, Note.formatMessage) { | |
| 74 | pub fn fmtMessage(self: Note, diag: *const Diagnostics) std.fmt.Alt([]const u8, Note.formatMessage) { | |
| 75 | 75 | return .{ .data = switch (self) { |
| 76 | 76 | .zoir => |note| note.msg.get(diag.zoir), |
| 77 | 77 | .type_check => |note| note.msg, |
| ... | ... | @@ -147,14 +147,14 @@ pub const Error = union(enum) { |
| 147 | 147 | diag: *const Diagnostics, |
| 148 | 148 | }; |
| 149 | 149 | |
| 150 | fn formatMessage(self: FormatMessage, w: *std.io.Writer) std.io.Writer.Error!void { | |
| 150 | fn formatMessage(self: FormatMessage, w: *std.Io.Writer) std.Io.Writer.Error!void { | |
| 151 | 151 | switch (self.err) { |
| 152 | 152 | .zoir => |err| try w.writeAll(err.msg.get(self.diag.zoir)), |
| 153 | 153 | .type_check => |tc| try w.writeAll(tc.message), |
| 154 | 154 | } |
| 155 | 155 | } |
| 156 | 156 | |
| 157 | pub fn fmtMessage(self: @This(), diag: *const Diagnostics) std.fmt.Formatter(FormatMessage, formatMessage) { | |
| 157 | pub fn fmtMessage(self: @This(), diag: *const Diagnostics) std.fmt.Alt(FormatMessage, formatMessage) { | |
| 158 | 158 | return .{ .data = .{ |
| 159 | 159 | .err = self, |
| 160 | 160 | .diag = diag, |
| ... | ... | @@ -226,7 +226,7 @@ pub const Diagnostics = struct { |
| 226 | 226 | return .{ .diag = self }; |
| 227 | 227 | } |
| 228 | 228 | |
| 229 | pub fn format(self: *const @This(), w: *std.io.Writer) std.io.Writer.Error!void { | |
| 229 | pub fn format(self: *const @This(), w: *std.Io.Writer) std.Io.Writer.Error!void { | |
| 230 | 230 | var errors = self.iterateErrors(); |
| 231 | 231 | while (errors.next()) |err| { |
| 232 | 232 | const loc = err.getLocation(self); |
| ... | ... | @@ -606,7 +606,7 @@ const Parser = struct { |
| 606 | 606 | } |
| 607 | 607 | } |
| 608 | 608 | |
| 609 | fn parseSlicePointer(self: *@This(), T: type, node: Zoir.Node.Index) !T { | |
| 609 | fn parseSlicePointer(self: *@This(), T: type, node: Zoir.Node.Index) ParseExprInnerError!T { | |
| 610 | 610 | switch (node.get(self.zoir)) { |
| 611 | 611 | .string_literal => return self.parseString(T, node), |
| 612 | 612 | .array_literal => |nodes| return self.parseSlice(T, nodes), |
| ... | ... | @@ -1048,6 +1048,7 @@ const Parser = struct { |
| 1048 | 1048 | name: []const u8, |
| 1049 | 1049 | ) error{ OutOfMemory, ParseZon } { |
| 1050 | 1050 | @branchHint(.cold); |
| 1051 | const gpa = self.gpa; | |
| 1051 | 1052 | const token = if (field) |f| b: { |
| 1052 | 1053 | var buf: [2]Ast.Node.Index = undefined; |
| 1053 | 1054 | const struct_init = self.ast.fullStructInit(&buf, node.getAstNode(self.zoir)).?; |
| ... | ... | @@ -1065,13 +1066,12 @@ const Parser = struct { |
| 1065 | 1066 | }; |
| 1066 | 1067 | } else b: { |
| 1067 | 1068 | const msg = "supported: "; |
| 1068 | var buf: std.ArrayListUnmanaged(u8) = try .initCapacity(self.gpa, 64); | |
| 1069 | defer buf.deinit(self.gpa); | |
| 1070 | const writer = buf.writer(self.gpa); | |
| 1071 | try writer.writeAll(msg); | |
| 1069 | var buf: std.ArrayListUnmanaged(u8) = try .initCapacity(gpa, 64); | |
| 1070 | defer buf.deinit(gpa); | |
| 1071 | try buf.appendSlice(gpa, msg); | |
| 1072 | 1072 | inline for (info.fields, 0..) |field_info, i| { |
| 1073 | if (i != 0) try writer.writeAll(", "); | |
| 1074 | try writer.print("'{f}'", .{std.zig.fmtIdFlags(field_info.name, .{ | |
| 1073 | if (i != 0) try buf.appendSlice(gpa, ", "); | |
| 1074 | try buf.print(gpa, "'{f}'", .{std.zig.fmtIdFlags(field_info.name, .{ | |
| 1075 | 1075 | .allow_primitive = true, |
| 1076 | 1076 | .allow_underscore = true, |
| 1077 | 1077 | })}); |
| ... | ... | @@ -1079,7 +1079,7 @@ const Parser = struct { |
| 1079 | 1079 | break :b .{ |
| 1080 | 1080 | .token = token, |
| 1081 | 1081 | .offset = 0, |
| 1082 | .msg = try buf.toOwnedSlice(self.gpa), | |
| 1082 | .msg = try buf.toOwnedSlice(gpa), | |
| 1083 | 1083 | .owned = true, |
| 1084 | 1084 | }; |
| 1085 | 1085 | }; |
lib/std/zon/stringify.zig+382-1333| ... | ... | @@ -22,13 +22,14 @@ |
| 22 | 22 | |
| 23 | 23 | const std = @import("std"); |
| 24 | 24 | const assert = std.debug.assert; |
| 25 | const Writer = std.Io.Writer; | |
| 26 | const Serializer = std.zon.Serializer; | |
| 25 | 27 | |
| 26 | /// Options for `serialize`. | |
| 27 | 28 | pub const SerializeOptions = struct { |
| 28 | 29 | /// If false, whitespace is omitted. Otherwise whitespace is emitted in standard Zig style. |
| 29 | 30 | whitespace: bool = true, |
| 30 | 31 | /// Determines when to emit Unicode code point literals as opposed to integer literals. |
| 31 | emit_codepoint_literals: EmitCodepointLiterals = .never, | |
| 32 | emit_codepoint_literals: Serializer.EmitCodepointLiterals = .never, | |
| 32 | 33 | /// If true, slices of `u8`s, and pointers to arrays of `u8` are serialized as containers. |
| 33 | 34 | /// Otherwise they are serialized as string literals. |
| 34 | 35 | emit_strings_as_containers: bool = false, |
| ... | ... | @@ -40,15 +41,12 @@ pub const SerializeOptions = struct { |
| 40 | 41 | /// Serialize the given value as ZON. |
| 41 | 42 | /// |
| 42 | 43 | /// It is asserted at comptime that `@TypeOf(val)` is not a recursive type. |
| 43 | pub fn serialize( | |
| 44 | val: anytype, | |
| 45 | options: SerializeOptions, | |
| 46 | writer: anytype, | |
| 47 | ) @TypeOf(writer).Error!void { | |
| 48 | var sz = serializer(writer, .{ | |
| 49 | .whitespace = options.whitespace, | |
| 50 | }); | |
| 51 | try sz.value(val, .{ | |
| 44 | pub fn serialize(val: anytype, options: SerializeOptions, writer: *Writer) Writer.Error!void { | |
| 45 | var s: Serializer = .{ | |
| 46 | .writer = writer, | |
| 47 | .options = .{ .whitespace = options.whitespace }, | |
| 48 | }; | |
| 49 | try s.value(val, .{ | |
| 52 | 50 | .emit_codepoint_literals = options.emit_codepoint_literals, |
| 53 | 51 | .emit_strings_as_containers = options.emit_strings_as_containers, |
| 54 | 52 | .emit_default_optional_fields = options.emit_default_optional_fields, |
| ... | ... | @@ -62,13 +60,14 @@ pub fn serialize( |
| 62 | 60 | pub fn serializeMaxDepth( |
| 63 | 61 | val: anytype, |
| 64 | 62 | options: SerializeOptions, |
| 65 | writer: anytype, | |
| 63 | writer: *Writer, | |
| 66 | 64 | depth: usize, |
| 67 | ) (@TypeOf(writer).Error || error{ExceededMaxDepth})!void { | |
| 68 | var sz = serializer(writer, .{ | |
| 69 | .whitespace = options.whitespace, | |
| 70 | }); | |
| 71 | try sz.valueMaxDepth(val, .{ | |
| 65 | ) Serializer.DepthError!void { | |
| 66 | var s: Serializer = .{ | |
| 67 | .writer = writer, | |
| 68 | .options = .{ .whitespace = options.whitespace }, | |
| 69 | }; | |
| 70 | try s.valueMaxDepth(val, .{ | |
| 72 | 71 | .emit_codepoint_literals = options.emit_codepoint_literals, |
| 73 | 72 | .emit_strings_as_containers = options.emit_strings_as_containers, |
| 74 | 73 | .emit_default_optional_fields = options.emit_default_optional_fields, |
| ... | ... | @@ -81,114 +80,19 @@ pub fn serializeMaxDepth( |
| 81 | 80 | pub fn serializeArbitraryDepth( |
| 82 | 81 | val: anytype, |
| 83 | 82 | options: SerializeOptions, |
| 84 | writer: anytype, | |
| 85 | ) @TypeOf(writer).Error!void { | |
| 86 | var sz = serializer(writer, .{ | |
| 87 | .whitespace = options.whitespace, | |
| 88 | }); | |
| 89 | try sz.valueArbitraryDepth(val, .{ | |
| 83 | writer: *Writer, | |
| 84 | ) Serializer.Error!void { | |
| 85 | var s: Serializer = .{ | |
| 86 | .writer = writer, | |
| 87 | .options = .{ .whitespace = options.whitespace }, | |
| 88 | }; | |
| 89 | try s.valueArbitraryDepth(val, .{ | |
| 90 | 90 | .emit_codepoint_literals = options.emit_codepoint_literals, |
| 91 | 91 | .emit_strings_as_containers = options.emit_strings_as_containers, |
| 92 | 92 | .emit_default_optional_fields = options.emit_default_optional_fields, |
| 93 | 93 | }); |
| 94 | 94 | } |
| 95 | 95 | |
| 96 | fn typeIsRecursive(comptime T: type) bool { | |
| 97 | return comptime typeIsRecursiveImpl(T, &.{}); | |
| 98 | } | |
| 99 | ||
| 100 | fn typeIsRecursiveImpl(comptime T: type, comptime prev_visited: []const type) bool { | |
| 101 | for (prev_visited) |V| { | |
| 102 | if (V == T) return true; | |
| 103 | } | |
| 104 | const visited = prev_visited ++ .{T}; | |
| 105 | ||
| 106 | return switch (@typeInfo(T)) { | |
| 107 | .pointer => |pointer| typeIsRecursiveImpl(pointer.child, visited), | |
| 108 | .optional => |optional| typeIsRecursiveImpl(optional.child, visited), | |
| 109 | .array => |array| typeIsRecursiveImpl(array.child, visited), | |
| 110 | .vector => |vector| typeIsRecursiveImpl(vector.child, visited), | |
| 111 | .@"struct" => |@"struct"| for (@"struct".fields) |field| { | |
| 112 | if (typeIsRecursiveImpl(field.type, visited)) break true; | |
| 113 | } else false, | |
| 114 | .@"union" => |@"union"| inline for (@"union".fields) |field| { | |
| 115 | if (typeIsRecursiveImpl(field.type, visited)) break true; | |
| 116 | } else false, | |
| 117 | else => false, | |
| 118 | }; | |
| 119 | } | |
| 120 | ||
| 121 | fn canSerializeType(T: type) bool { | |
| 122 | comptime return canSerializeTypeInner(T, &.{}, false); | |
| 123 | } | |
| 124 | ||
| 125 | fn canSerializeTypeInner( | |
| 126 | T: type, | |
| 127 | /// Visited structs and unions, to avoid infinite recursion. | |
| 128 | /// Tracking more types is unnecessary, and a little complex due to optional nesting. | |
| 129 | visited: []const type, | |
| 130 | parent_is_optional: bool, | |
| 131 | ) bool { | |
| 132 | return switch (@typeInfo(T)) { | |
| 133 | .bool, | |
| 134 | .int, | |
| 135 | .float, | |
| 136 | .comptime_float, | |
| 137 | .comptime_int, | |
| 138 | .null, | |
| 139 | .enum_literal, | |
| 140 | => true, | |
| 141 | ||
| 142 | .noreturn, | |
| 143 | .void, | |
| 144 | .type, | |
| 145 | .undefined, | |
| 146 | .error_union, | |
| 147 | .error_set, | |
| 148 | .@"fn", | |
| 149 | .frame, | |
| 150 | .@"anyframe", | |
| 151 | .@"opaque", | |
| 152 | => false, | |
| 153 | ||
| 154 | .@"enum" => |@"enum"| @"enum".is_exhaustive, | |
| 155 | ||
| 156 | .pointer => |pointer| switch (pointer.size) { | |
| 157 | .one => canSerializeTypeInner(pointer.child, visited, parent_is_optional), | |
| 158 | .slice => canSerializeTypeInner(pointer.child, visited, false), | |
| 159 | .many, .c => false, | |
| 160 | }, | |
| 161 | ||
| 162 | .optional => |optional| if (parent_is_optional) | |
| 163 | false | |
| 164 | else | |
| 165 | canSerializeTypeInner(optional.child, visited, true), | |
| 166 | ||
| 167 | .array => |array| canSerializeTypeInner(array.child, visited, false), | |
| 168 | .vector => |vector| canSerializeTypeInner(vector.child, visited, false), | |
| 169 | ||
| 170 | .@"struct" => |@"struct"| { | |
| 171 | for (visited) |V| if (T == V) return true; | |
| 172 | const new_visited = visited ++ .{T}; | |
| 173 | for (@"struct".fields) |field| { | |
| 174 | if (!canSerializeTypeInner(field.type, new_visited, false)) return false; | |
| 175 | } | |
| 176 | return true; | |
| 177 | }, | |
| 178 | .@"union" => |@"union"| { | |
| 179 | for (visited) |V| if (T == V) return true; | |
| 180 | const new_visited = visited ++ .{T}; | |
| 181 | if (@"union".tag_type == null) return false; | |
| 182 | for (@"union".fields) |field| { | |
| 183 | if (field.type != void and !canSerializeTypeInner(field.type, new_visited, false)) { | |
| 184 | return false; | |
| 185 | } | |
| 186 | } | |
| 187 | return true; | |
| 188 | }, | |
| 189 | }; | |
| 190 | } | |
| 191 | ||
| 192 | 96 | fn isNestedOptional(T: type) bool { |
| 193 | 97 | comptime switch (@typeInfo(T)) { |
| 194 | 98 | .optional => |optional| return isNestedOptionalInner(optional.child), |
| ... | ... | @@ -210,875 +114,17 @@ fn isNestedOptionalInner(T: type) bool { |
| 210 | 114 | } |
| 211 | 115 | } |
| 212 | 116 | |
| 213 | test "std.zon stringify canSerializeType" { | |
| 214 | try std.testing.expect(!comptime canSerializeType(void)); | |
| 215 | try std.testing.expect(!comptime canSerializeType(struct { f: [*]u8 })); | |
| 216 | try std.testing.expect(!comptime canSerializeType(struct { error{foo} })); | |
| 217 | try std.testing.expect(!comptime canSerializeType(union(enum) { a: void, f: [*c]u8 })); | |
| 218 | try std.testing.expect(!comptime canSerializeType(@Vector(0, [*c]u8))); | |
| 219 | try std.testing.expect(!comptime canSerializeType(*?[*c]u8)); | |
| 220 | try std.testing.expect(!comptime canSerializeType(enum(u8) { _ })); | |
| 221 | try std.testing.expect(!comptime canSerializeType(union { foo: void })); | |
| 222 | try std.testing.expect(comptime canSerializeType(union(enum) { foo: void })); | |
| 223 | try std.testing.expect(comptime canSerializeType(comptime_float)); | |
| 224 | try std.testing.expect(comptime canSerializeType(comptime_int)); | |
| 225 | try std.testing.expect(!comptime canSerializeType(struct { comptime foo: ??u8 = null })); | |
| 226 | try std.testing.expect(comptime canSerializeType(@TypeOf(.foo))); | |
| 227 | try std.testing.expect(comptime canSerializeType(?u8)); | |
| 228 | try std.testing.expect(comptime canSerializeType(*?*u8)); | |
| 229 | try std.testing.expect(comptime canSerializeType(?struct { | |
| 230 | foo: ?struct { | |
| 231 | ?union(enum) { | |
| 232 | a: ?@Vector(0, ?*u8), | |
| 233 | }, | |
| 234 | ?struct { | |
| 235 | f: ?[]?u8, | |
| 236 | }, | |
| 237 | }, | |
| 238 | })); | |
| 239 | try std.testing.expect(!comptime canSerializeType(??u8)); | |
| 240 | try std.testing.expect(!comptime canSerializeType(?*?u8)); | |
| 241 | try std.testing.expect(!comptime canSerializeType(*?*?*u8)); | |
| 242 | try std.testing.expect(comptime canSerializeType(struct { x: comptime_int = 2 })); | |
| 243 | try std.testing.expect(comptime canSerializeType(struct { x: comptime_float = 2 })); | |
| 244 | try std.testing.expect(comptime canSerializeType(struct { comptime_int })); | |
| 245 | try std.testing.expect(comptime canSerializeType(struct { comptime x: @TypeOf(.foo) = .foo })); | |
| 246 | const Recursive = struct { foo: ?*@This() }; | |
| 247 | try std.testing.expect(comptime canSerializeType(Recursive)); | |
| 248 | ||
| 249 | // Make sure we validate nested optional before we early out due to already having seen | |
| 250 | // a type recursion! | |
| 251 | try std.testing.expect(!comptime canSerializeType(struct { | |
| 252 | add_to_visited: ?u8, | |
| 253 | retrieve_from_visited: ??u8, | |
| 254 | })); | |
| 255 | } | |
| 256 | ||
| 257 | test "std.zon typeIsRecursive" { | |
| 258 | try std.testing.expect(!typeIsRecursive(bool)); | |
| 259 | try std.testing.expect(!typeIsRecursive(struct { x: i32, y: i32 })); | |
| 260 | try std.testing.expect(!typeIsRecursive(struct { i32, i32 })); | |
| 261 | try std.testing.expect(typeIsRecursive(struct { x: i32, y: i32, z: *@This() })); | |
| 262 | try std.testing.expect(typeIsRecursive(struct { | |
| 263 | a: struct { | |
| 264 | const A = @This(); | |
| 265 | b: struct { | |
| 266 | c: *struct { | |
| 267 | a: ?A, | |
| 268 | }, | |
| 269 | }, | |
| 270 | }, | |
| 271 | })); | |
| 272 | try std.testing.expect(typeIsRecursive(struct { | |
| 273 | a: [3]*@This(), | |
| 274 | })); | |
| 275 | try std.testing.expect(typeIsRecursive(struct { | |
| 276 | a: union { a: i32, b: *@This() }, | |
| 277 | })); | |
| 278 | } | |
| 279 | ||
| 280 | fn checkValueDepth(val: anytype, depth: usize) error{ExceededMaxDepth}!void { | |
| 281 | if (depth == 0) return error.ExceededMaxDepth; | |
| 282 | const child_depth = depth - 1; | |
| 283 | ||
| 284 | switch (@typeInfo(@TypeOf(val))) { | |
| 285 | .pointer => |pointer| switch (pointer.size) { | |
| 286 | .one => try checkValueDepth(val.*, child_depth), | |
| 287 | .slice => for (val) |item| { | |
| 288 | try checkValueDepth(item, child_depth); | |
| 289 | }, | |
| 290 | .c, .many => {}, | |
| 291 | }, | |
| 292 | .array => for (val) |item| { | |
| 293 | try checkValueDepth(item, child_depth); | |
| 294 | }, | |
| 295 | .@"struct" => |@"struct"| inline for (@"struct".fields) |field_info| { | |
| 296 | try checkValueDepth(@field(val, field_info.name), child_depth); | |
| 297 | }, | |
| 298 | .@"union" => |@"union"| if (@"union".tag_type == null) { | |
| 299 | return; | |
| 300 | } else switch (val) { | |
| 301 | inline else => |payload| { | |
| 302 | return checkValueDepth(payload, child_depth); | |
| 303 | }, | |
| 304 | }, | |
| 305 | .optional => if (val) |inner| try checkValueDepth(inner, child_depth), | |
| 306 | else => {}, | |
| 307 | } | |
| 308 | } | |
| 309 | ||
| 310 | fn expectValueDepthEquals(expected: usize, value: anytype) !void { | |
| 311 | try checkValueDepth(value, expected); | |
| 312 | try std.testing.expectError(error.ExceededMaxDepth, checkValueDepth(value, expected - 1)); | |
| 313 | } | |
| 314 | ||
| 315 | test "std.zon checkValueDepth" { | |
| 316 | try expectValueDepthEquals(1, 10); | |
| 317 | try expectValueDepthEquals(2, .{ .x = 1, .y = 2 }); | |
| 318 | try expectValueDepthEquals(2, .{ 1, 2 }); | |
| 319 | try expectValueDepthEquals(3, .{ 1, .{ 2, 3 } }); | |
| 320 | try expectValueDepthEquals(3, .{ .{ 1, 2 }, 3 }); | |
| 321 | try expectValueDepthEquals(3, .{ .x = 0, .y = 1, .z = .{ .x = 3 } }); | |
| 322 | try expectValueDepthEquals(3, .{ .x = 0, .y = .{ .x = 1 }, .z = 2 }); | |
| 323 | try expectValueDepthEquals(3, .{ .x = .{ .x = 0 }, .y = 1, .z = 2 }); | |
| 324 | try expectValueDepthEquals(2, @as(?u32, 1)); | |
| 325 | try expectValueDepthEquals(1, @as(?u32, null)); | |
| 326 | try expectValueDepthEquals(1, null); | |
| 327 | try expectValueDepthEquals(2, &1); | |
| 328 | try expectValueDepthEquals(3, &@as(?u32, 1)); | |
| 329 | ||
| 330 | const Union = union(enum) { | |
| 331 | x: u32, | |
| 332 | y: struct { x: u32 }, | |
| 333 | }; | |
| 334 | try expectValueDepthEquals(2, Union{ .x = 1 }); | |
| 335 | try expectValueDepthEquals(3, Union{ .y = .{ .x = 1 } }); | |
| 336 | ||
| 337 | const Recurse = struct { r: ?*const @This() }; | |
| 338 | try expectValueDepthEquals(2, Recurse{ .r = null }); | |
| 339 | try expectValueDepthEquals(5, Recurse{ .r = &Recurse{ .r = null } }); | |
| 340 | try expectValueDepthEquals(8, Recurse{ .r = &Recurse{ .r = &Recurse{ .r = null } } }); | |
| 341 | ||
| 342 | try expectValueDepthEquals(2, @as([]const u8, &.{ 1, 2, 3 })); | |
| 343 | try expectValueDepthEquals(3, @as([]const []const u8, &.{&.{ 1, 2, 3 }})); | |
| 344 | } | |
| 345 | ||
| 346 | /// Options for `Serializer`. | |
| 347 | pub const SerializerOptions = struct { | |
| 348 | /// If false, only syntactically necessary whitespace is emitted. | |
| 349 | whitespace: bool = true, | |
| 350 | }; | |
| 351 | ||
| 352 | /// Determines when to emit Unicode code point literals as opposed to integer literals. | |
| 353 | pub const EmitCodepointLiterals = enum { | |
| 354 | /// Never emit Unicode code point literals. | |
| 355 | never, | |
| 356 | /// Emit Unicode code point literals for any `u8` in the printable ASCII range. | |
| 357 | printable_ascii, | |
| 358 | /// Emit Unicode code point literals for any unsigned integer with 21 bits or fewer | |
| 359 | /// whose value is a valid non-surrogate code point. | |
| 360 | always, | |
| 361 | ||
| 362 | /// If the value should be emitted as a Unicode codepoint, return it as a u21. | |
| 363 | fn emitAsCodepoint(self: @This(), val: anytype) ?u21 { | |
| 364 | // Rule out incompatible integer types | |
| 365 | switch (@typeInfo(@TypeOf(val))) { | |
| 366 | .int => |int_info| if (int_info.signedness == .signed or int_info.bits > 21) { | |
| 367 | return null; | |
| 368 | }, | |
| 369 | .comptime_int => {}, | |
| 370 | else => comptime unreachable, | |
| 371 | } | |
| 372 | ||
| 373 | // Return null if the value shouldn't be printed as a Unicode codepoint, or the value casted | |
| 374 | // to a u21 if it should. | |
| 375 | switch (self) { | |
| 376 | .always => { | |
| 377 | const c = std.math.cast(u21, val) orelse return null; | |
| 378 | if (!std.unicode.utf8ValidCodepoint(c)) return null; | |
| 379 | return c; | |
| 380 | }, | |
| 381 | .printable_ascii => { | |
| 382 | const c = std.math.cast(u8, val) orelse return null; | |
| 383 | if (!std.ascii.isPrint(c)) return null; | |
| 384 | return c; | |
| 385 | }, | |
| 386 | .never => { | |
| 387 | return null; | |
| 388 | }, | |
| 389 | } | |
| 390 | } | |
| 391 | }; | |
| 392 | ||
| 393 | /// Options for serialization of an individual value. | |
| 394 | /// | |
| 395 | /// See `SerializeOptions` for more information on these options. | |
| 396 | pub const ValueOptions = struct { | |
| 397 | emit_codepoint_literals: EmitCodepointLiterals = .never, | |
| 398 | emit_strings_as_containers: bool = false, | |
| 399 | emit_default_optional_fields: bool = true, | |
| 400 | }; | |
| 401 | ||
| 402 | /// Options for manual serialization of container types. | |
| 403 | pub const SerializeContainerOptions = struct { | |
| 404 | /// The whitespace style that should be used for this container. Ignored if whitespace is off. | |
| 405 | whitespace_style: union(enum) { | |
| 406 | /// If true, wrap every field. If false do not. | |
| 407 | wrap: bool, | |
| 408 | /// Automatically decide whether to wrap or not based on the number of fields. Following | |
| 409 | /// the standard rule of thumb, containers with more than two fields are wrapped. | |
| 410 | fields: usize, | |
| 411 | } = .{ .wrap = true }, | |
| 412 | ||
| 413 | fn shouldWrap(self: SerializeContainerOptions) bool { | |
| 414 | return switch (self.whitespace_style) { | |
| 415 | .wrap => |wrap| wrap, | |
| 416 | .fields => |fields| fields > 2, | |
| 417 | }; | |
| 418 | } | |
| 419 | }; | |
| 420 | ||
| 421 | /// Lower level control over serialization, you can create a new instance with `serializer`. | |
| 422 | /// | |
| 423 | /// Useful when you want control over which fields are serialized, how they're represented, | |
| 424 | /// or want to write a ZON object that does not exist in memory. | |
| 425 | /// | |
| 426 | /// You can serialize values with `value`. To serialize recursive types, the following are provided: | |
| 427 | /// * `valueMaxDepth` | |
| 428 | /// * `valueArbitraryDepth` | |
| 429 | /// | |
| 430 | /// You can also serialize values using specific notations: | |
| 431 | /// * `int` | |
| 432 | /// * `float` | |
| 433 | /// * `codePoint` | |
| 434 | /// * `tuple` | |
| 435 | /// * `tupleMaxDepth` | |
| 436 | /// * `tupleArbitraryDepth` | |
| 437 | /// * `string` | |
| 438 | /// * `multilineString` | |
| 439 | /// | |
| 440 | /// For manual serialization of containers, see: | |
| 441 | /// * `beginStruct` | |
| 442 | /// * `beginTuple` | |
| 443 | /// | |
| 444 | /// # Example | |
| 445 | /// ```zig | |
| 446 | /// var sz = serializer(writer, .{}); | |
| 447 | /// var vec2 = try sz.beginStruct(.{}); | |
| 448 | /// try vec2.field("x", 1.5, .{}); | |
| 449 | /// try vec2.fieldPrefix(); | |
| 450 | /// try sz.value(2.5); | |
| 451 | /// try vec2.end(); | |
| 452 | /// ``` | |
| 453 | pub fn Serializer(Writer: type) type { | |
| 454 | return struct { | |
| 455 | const Self = @This(); | |
| 456 | ||
| 457 | options: SerializerOptions, | |
| 458 | indent_level: u8, | |
| 459 | writer: Writer, | |
| 460 | ||
| 461 | /// Initialize a serializer. | |
| 462 | fn init(writer: Writer, options: SerializerOptions) Self { | |
| 463 | return .{ | |
| 464 | .options = options, | |
| 465 | .writer = writer, | |
| 466 | .indent_level = 0, | |
| 467 | }; | |
| 468 | } | |
| 469 | ||
| 470 | /// Serialize a value, similar to `serialize`. | |
| 471 | pub fn value(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void { | |
| 472 | comptime assert(!typeIsRecursive(@TypeOf(val))); | |
| 473 | return self.valueArbitraryDepth(val, options); | |
| 474 | } | |
| 475 | ||
| 476 | /// Serialize a value, similar to `serializeMaxDepth`. | |
| 477 | pub fn valueMaxDepth( | |
| 478 | self: *Self, | |
| 479 | val: anytype, | |
| 480 | options: ValueOptions, | |
| 481 | depth: usize, | |
| 482 | ) (Writer.Error || error{ExceededMaxDepth})!void { | |
| 483 | try checkValueDepth(val, depth); | |
| 484 | return self.valueArbitraryDepth(val, options); | |
| 485 | } | |
| 486 | ||
| 487 | /// Serialize a value, similar to `serializeArbitraryDepth`. | |
| 488 | pub fn valueArbitraryDepth( | |
| 489 | self: *Self, | |
| 490 | val: anytype, | |
| 491 | options: ValueOptions, | |
| 492 | ) Writer.Error!void { | |
| 493 | comptime assert(canSerializeType(@TypeOf(val))); | |
| 494 | switch (@typeInfo(@TypeOf(val))) { | |
| 495 | .int, .comptime_int => if (options.emit_codepoint_literals.emitAsCodepoint(val)) |c| { | |
| 496 | self.codePoint(c) catch |err| switch (err) { | |
| 497 | error.InvalidCodepoint => unreachable, // Already validated | |
| 498 | else => |e| return e, | |
| 499 | }; | |
| 500 | } else { | |
| 501 | try self.int(val); | |
| 502 | }, | |
| 503 | .float, .comptime_float => try self.float(val), | |
| 504 | .bool, .null => try std.fmt.format(self.writer, "{}", .{val}), | |
| 505 | .enum_literal => try self.ident(@tagName(val)), | |
| 506 | .@"enum" => try self.ident(@tagName(val)), | |
| 507 | .pointer => |pointer| { | |
| 508 | // Try to serialize as a string | |
| 509 | const item: ?type = switch (@typeInfo(pointer.child)) { | |
| 510 | .array => |array| array.child, | |
| 511 | else => if (pointer.size == .slice) pointer.child else null, | |
| 512 | }; | |
| 513 | if (item == u8 and | |
| 514 | (pointer.sentinel() == null or pointer.sentinel() == 0) and | |
| 515 | !options.emit_strings_as_containers) | |
| 516 | { | |
| 517 | return try self.string(val); | |
| 518 | } | |
| 519 | ||
| 520 | // Serialize as either a tuple or as the child type | |
| 521 | switch (pointer.size) { | |
| 522 | .slice => try self.tupleImpl(val, options), | |
| 523 | .one => try self.valueArbitraryDepth(val.*, options), | |
| 524 | else => comptime unreachable, | |
| 525 | } | |
| 526 | }, | |
| 527 | .array => { | |
| 528 | var container = try self.beginTuple( | |
| 529 | .{ .whitespace_style = .{ .fields = val.len } }, | |
| 530 | ); | |
| 531 | for (val) |item_val| { | |
| 532 | try container.fieldArbitraryDepth(item_val, options); | |
| 533 | } | |
| 534 | try container.end(); | |
| 535 | }, | |
| 536 | .@"struct" => |@"struct"| if (@"struct".is_tuple) { | |
| 537 | var container = try self.beginTuple( | |
| 538 | .{ .whitespace_style = .{ .fields = @"struct".fields.len } }, | |
| 539 | ); | |
| 540 | inline for (val) |field_value| { | |
| 541 | try container.fieldArbitraryDepth(field_value, options); | |
| 542 | } | |
| 543 | try container.end(); | |
| 544 | } else { | |
| 545 | // Decide which fields to emit | |
| 546 | const fields, const skipped: [@"struct".fields.len]bool = if (options.emit_default_optional_fields) b: { | |
| 547 | break :b .{ @"struct".fields.len, @splat(false) }; | |
| 548 | } else b: { | |
| 549 | var fields = @"struct".fields.len; | |
| 550 | var skipped: [@"struct".fields.len]bool = @splat(false); | |
| 551 | inline for (@"struct".fields, &skipped) |field_info, *skip| { | |
| 552 | if (field_info.default_value_ptr) |ptr| { | |
| 553 | const default: *const field_info.type = @ptrCast(@alignCast(ptr)); | |
| 554 | const field_value = @field(val, field_info.name); | |
| 555 | if (std.meta.eql(field_value, default.*)) { | |
| 556 | skip.* = true; | |
| 557 | fields -= 1; | |
| 558 | } | |
| 559 | } | |
| 560 | } | |
| 561 | break :b .{ fields, skipped }; | |
| 562 | }; | |
| 563 | ||
| 564 | // Emit those fields | |
| 565 | var container = try self.beginStruct( | |
| 566 | .{ .whitespace_style = .{ .fields = fields } }, | |
| 567 | ); | |
| 568 | inline for (@"struct".fields, skipped) |field_info, skip| { | |
| 569 | if (!skip) { | |
| 570 | try container.fieldArbitraryDepth( | |
| 571 | field_info.name, | |
| 572 | @field(val, field_info.name), | |
| 573 | options, | |
| 574 | ); | |
| 575 | } | |
| 576 | } | |
| 577 | try container.end(); | |
| 578 | }, | |
| 579 | .@"union" => |@"union"| { | |
| 580 | comptime assert(@"union".tag_type != null); | |
| 581 | switch (val) { | |
| 582 | inline else => |pl, tag| if (@TypeOf(pl) == void) | |
| 583 | try self.writer.print(".{s}", .{@tagName(tag)}) | |
| 584 | else { | |
| 585 | var container = try self.beginStruct(.{ .whitespace_style = .{ .fields = 1 } }); | |
| 586 | ||
| 587 | try container.fieldArbitraryDepth( | |
| 588 | @tagName(tag), | |
| 589 | pl, | |
| 590 | options, | |
| 591 | ); | |
| 592 | ||
| 593 | try container.end(); | |
| 594 | }, | |
| 595 | } | |
| 596 | }, | |
| 597 | .optional => if (val) |inner| { | |
| 598 | try self.valueArbitraryDepth(inner, options); | |
| 599 | } else { | |
| 600 | try self.writer.writeAll("null"); | |
| 601 | }, | |
| 602 | .vector => |vector| { | |
| 603 | var container = try self.beginTuple( | |
| 604 | .{ .whitespace_style = .{ .fields = vector.len } }, | |
| 605 | ); | |
| 606 | for (0..vector.len) |i| { | |
| 607 | try container.fieldArbitraryDepth(val[i], options); | |
| 608 | } | |
| 609 | try container.end(); | |
| 610 | }, | |
| 611 | ||
| 612 | else => comptime unreachable, | |
| 613 | } | |
| 614 | } | |
| 615 | ||
| 616 | /// Serialize an integer. | |
| 617 | pub fn int(self: *Self, val: anytype) Writer.Error!void { | |
| 618 | //try self.writer.printInt(val, 10, .lower, .{}); | |
| 619 | try std.fmt.format(self.writer, "{d}", .{val}); | |
| 620 | } | |
| 621 | ||
| 622 | /// Serialize a float. | |
| 623 | pub fn float(self: *Self, val: anytype) Writer.Error!void { | |
| 624 | switch (@typeInfo(@TypeOf(val))) { | |
| 625 | .float => if (std.math.isNan(val)) { | |
| 626 | return self.writer.writeAll("nan"); | |
| 627 | } else if (std.math.isPositiveInf(val)) { | |
| 628 | return self.writer.writeAll("inf"); | |
| 629 | } else if (std.math.isNegativeInf(val)) { | |
| 630 | return self.writer.writeAll("-inf"); | |
| 631 | } else if (std.math.isNegativeZero(val)) { | |
| 632 | return self.writer.writeAll("-0.0"); | |
| 633 | } else { | |
| 634 | try std.fmt.format(self.writer, "{d}", .{val}); | |
| 635 | }, | |
| 636 | .comptime_float => if (val == 0) { | |
| 637 | return self.writer.writeAll("0"); | |
| 638 | } else { | |
| 639 | try std.fmt.format(self.writer, "{d}", .{val}); | |
| 640 | }, | |
| 641 | else => comptime unreachable, | |
| 642 | } | |
| 643 | } | |
| 644 | ||
| 645 | /// Serialize `name` as an identifier prefixed with `.`. | |
| 646 | /// | |
| 647 | /// Escapes the identifier if necessary. | |
| 648 | pub fn ident(self: *Self, name: []const u8) Writer.Error!void { | |
| 649 | try self.writer.print(".{f}", .{std.zig.fmtIdPU(name)}); | |
| 650 | } | |
| 651 | ||
| 652 | /// Serialize `val` as a Unicode codepoint. | |
| 653 | /// | |
| 654 | /// Returns `error.InvalidCodepoint` if `val` is not a valid Unicode codepoint. | |
| 655 | pub fn codePoint( | |
| 656 | self: *Self, | |
| 657 | val: u21, | |
| 658 | ) (Writer.Error || error{InvalidCodepoint})!void { | |
| 659 | var buf: [8]u8 = undefined; | |
| 660 | const len = std.unicode.utf8Encode(val, &buf) catch return error.InvalidCodepoint; | |
| 661 | const str = buf[0..len]; | |
| 662 | try std.fmt.format(self.writer, "'{f}'", .{std.zig.fmtChar(str)}); | |
| 663 | } | |
| 664 | ||
| 665 | /// Like `value`, but always serializes `val` as a tuple. | |
| 666 | /// | |
| 667 | /// Will fail at comptime if `val` is not a tuple, array, pointer to an array, or slice. | |
| 668 | pub fn tuple(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void { | |
| 669 | comptime assert(!typeIsRecursive(@TypeOf(val))); | |
| 670 | try self.tupleArbitraryDepth(val, options); | |
| 671 | } | |
| 672 | ||
| 673 | /// Like `tuple`, but recursive types are allowed. | |
| 674 | /// | |
| 675 | /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. | |
| 676 | pub fn tupleMaxDepth( | |
| 677 | self: *Self, | |
| 678 | val: anytype, | |
| 679 | options: ValueOptions, | |
| 680 | depth: usize, | |
| 681 | ) (Writer.Error || error{ExceededMaxDepth})!void { | |
| 682 | try checkValueDepth(val, depth); | |
| 683 | try self.tupleArbitraryDepth(val, options); | |
| 684 | } | |
| 685 | ||
| 686 | /// Like `tuple`, but recursive types are allowed. | |
| 687 | /// | |
| 688 | /// It is the caller's responsibility to ensure that `val` does not contain cycles. | |
| 689 | pub fn tupleArbitraryDepth( | |
| 690 | self: *Self, | |
| 691 | val: anytype, | |
| 692 | options: ValueOptions, | |
| 693 | ) Writer.Error!void { | |
| 694 | try self.tupleImpl(val, options); | |
| 695 | } | |
| 696 | ||
| 697 | fn tupleImpl(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void { | |
| 698 | comptime assert(canSerializeType(@TypeOf(val))); | |
| 699 | switch (@typeInfo(@TypeOf(val))) { | |
| 700 | .@"struct" => { | |
| 701 | var container = try self.beginTuple(.{ .whitespace_style = .{ .fields = val.len } }); | |
| 702 | inline for (val) |item_val| { | |
| 703 | try container.fieldArbitraryDepth(item_val, options); | |
| 704 | } | |
| 705 | try container.end(); | |
| 706 | }, | |
| 707 | .pointer, .array => { | |
| 708 | var container = try self.beginTuple(.{ .whitespace_style = .{ .fields = val.len } }); | |
| 709 | for (val) |item_val| { | |
| 710 | try container.fieldArbitraryDepth(item_val, options); | |
| 711 | } | |
| 712 | try container.end(); | |
| 713 | }, | |
| 714 | else => comptime unreachable, | |
| 715 | } | |
| 716 | } | |
| 717 | ||
| 718 | /// Like `value`, but always serializes `val` as a string. | |
| 719 | pub fn string(self: *Self, val: []const u8) Writer.Error!void { | |
| 720 | try std.fmt.format(self.writer, "\"{f}\"", .{std.zig.fmtString(val)}); | |
| 721 | } | |
| 722 | ||
| 723 | /// Options for formatting multiline strings. | |
| 724 | pub const MultilineStringOptions = struct { | |
| 725 | /// If top level is true, whitespace before and after the multiline string is elided. | |
| 726 | /// If it is true, a newline is printed, then the value, followed by a newline, and if | |
| 727 | /// whitespace is true any necessary indentation follows. | |
| 728 | top_level: bool = false, | |
| 729 | }; | |
| 730 | ||
| 731 | /// Like `value`, but always serializes to a multiline string literal. | |
| 732 | /// | |
| 733 | /// Returns `error.InnerCarriageReturn` if `val` contains a CR not followed by a newline, | |
| 734 | /// since multiline strings cannot represent CR without a following newline. | |
| 735 | pub fn multilineString( | |
| 736 | self: *Self, | |
| 737 | val: []const u8, | |
| 738 | options: MultilineStringOptions, | |
| 739 | ) (Writer.Error || error{InnerCarriageReturn})!void { | |
| 740 | // Make sure the string does not contain any carriage returns not followed by a newline | |
| 741 | var i: usize = 0; | |
| 742 | while (i < val.len) : (i += 1) { | |
| 743 | if (val[i] == '\r') { | |
| 744 | if (i + 1 < val.len) { | |
| 745 | if (val[i + 1] == '\n') { | |
| 746 | i += 1; | |
| 747 | continue; | |
| 748 | } | |
| 749 | } | |
| 750 | return error.InnerCarriageReturn; | |
| 751 | } | |
| 752 | } | |
| 753 | ||
| 754 | if (!options.top_level) { | |
| 755 | try self.newline(); | |
| 756 | try self.indent(); | |
| 757 | } | |
| 758 | ||
| 759 | try self.writer.writeAll("\\\\"); | |
| 760 | for (val) |c| { | |
| 761 | if (c != '\r') { | |
| 762 | try self.writer.writeByte(c); // We write newlines here even if whitespace off | |
| 763 | if (c == '\n') { | |
| 764 | try self.indent(); | |
| 765 | try self.writer.writeAll("\\\\"); | |
| 766 | } | |
| 767 | } | |
| 768 | } | |
| 769 | ||
| 770 | if (!options.top_level) { | |
| 771 | try self.writer.writeByte('\n'); // Even if whitespace off | |
| 772 | try self.indent(); | |
| 773 | } | |
| 774 | } | |
| 775 | ||
| 776 | /// Create a `Struct` for writing ZON structs field by field. | |
| 777 | pub fn beginStruct( | |
| 778 | self: *Self, | |
| 779 | options: SerializeContainerOptions, | |
| 780 | ) Writer.Error!Struct { | |
| 781 | return Struct.begin(self, options); | |
| 782 | } | |
| 783 | ||
| 784 | /// Creates a `Tuple` for writing ZON tuples field by field. | |
| 785 | pub fn beginTuple( | |
| 786 | self: *Self, | |
| 787 | options: SerializeContainerOptions, | |
| 788 | ) Writer.Error!Tuple { | |
| 789 | return Tuple.begin(self, options); | |
| 790 | } | |
| 791 | ||
| 792 | fn indent(self: *Self) Writer.Error!void { | |
| 793 | if (self.options.whitespace) { | |
| 794 | try self.writer.writeByteNTimes(' ', 4 * self.indent_level); | |
| 795 | } | |
| 796 | } | |
| 797 | ||
| 798 | fn newline(self: *Self) Writer.Error!void { | |
| 799 | if (self.options.whitespace) { | |
| 800 | try self.writer.writeByte('\n'); | |
| 801 | } | |
| 802 | } | |
| 803 | ||
| 804 | fn newlineOrSpace(self: *Self, len: usize) Writer.Error!void { | |
| 805 | if (self.containerShouldWrap(len)) { | |
| 806 | try self.newline(); | |
| 807 | } else { | |
| 808 | try self.space(); | |
| 809 | } | |
| 810 | } | |
| 811 | ||
| 812 | fn space(self: *Self) Writer.Error!void { | |
| 813 | if (self.options.whitespace) { | |
| 814 | try self.writer.writeByte(' '); | |
| 815 | } | |
| 816 | } | |
| 817 | ||
| 818 | /// Writes ZON tuples field by field. | |
| 819 | pub const Tuple = struct { | |
| 820 | container: Container, | |
| 821 | ||
| 822 | fn begin(parent: *Self, options: SerializeContainerOptions) Writer.Error!Tuple { | |
| 823 | return .{ | |
| 824 | .container = try Container.begin(parent, .anon, options), | |
| 825 | }; | |
| 826 | } | |
| 827 | ||
| 828 | /// Finishes serializing the tuple. | |
| 829 | /// | |
| 830 | /// Prints a trailing comma as configured when appropriate, and the closing bracket. | |
| 831 | pub fn end(self: *Tuple) Writer.Error!void { | |
| 832 | try self.container.end(); | |
| 833 | self.* = undefined; | |
| 834 | } | |
| 835 | ||
| 836 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`. | |
| 837 | pub fn field( | |
| 838 | self: *Tuple, | |
| 839 | val: anytype, | |
| 840 | options: ValueOptions, | |
| 841 | ) Writer.Error!void { | |
| 842 | try self.container.field(null, val, options); | |
| 843 | } | |
| 844 | ||
| 845 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`. | |
| 846 | pub fn fieldMaxDepth( | |
| 847 | self: *Tuple, | |
| 848 | val: anytype, | |
| 849 | options: ValueOptions, | |
| 850 | depth: usize, | |
| 851 | ) (Writer.Error || error{ExceededMaxDepth})!void { | |
| 852 | try self.container.fieldMaxDepth(null, val, options, depth); | |
| 853 | } | |
| 854 | ||
| 855 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by | |
| 856 | /// `valueArbitraryDepth`. | |
| 857 | pub fn fieldArbitraryDepth( | |
| 858 | self: *Tuple, | |
| 859 | val: anytype, | |
| 860 | options: ValueOptions, | |
| 861 | ) Writer.Error!void { | |
| 862 | try self.container.fieldArbitraryDepth(null, val, options); | |
| 863 | } | |
| 864 | ||
| 865 | /// Starts a field with a struct as a value. Returns the struct. | |
| 866 | pub fn beginStructField( | |
| 867 | self: *Tuple, | |
| 868 | options: SerializeContainerOptions, | |
| 869 | ) Writer.Error!Struct { | |
| 870 | try self.fieldPrefix(); | |
| 871 | return self.container.serializer.beginStruct(options); | |
| 872 | } | |
| 873 | ||
| 874 | /// Starts a field with a tuple as a value. Returns the tuple. | |
| 875 | pub fn beginTupleField( | |
| 876 | self: *Tuple, | |
| 877 | options: SerializeContainerOptions, | |
| 878 | ) Writer.Error!Tuple { | |
| 879 | try self.fieldPrefix(); | |
| 880 | return self.container.serializer.beginTuple(options); | |
| 881 | } | |
| 882 | ||
| 883 | /// Print a field prefix. This prints any necessary commas, and whitespace as | |
| 884 | /// configured. Useful if you want to serialize the field value yourself. | |
| 885 | pub fn fieldPrefix(self: *Tuple) Writer.Error!void { | |
| 886 | try self.container.fieldPrefix(null); | |
| 887 | } | |
| 888 | }; | |
| 889 | ||
| 890 | /// Writes ZON structs field by field. | |
| 891 | pub const Struct = struct { | |
| 892 | container: Container, | |
| 893 | ||
| 894 | fn begin(parent: *Self, options: SerializeContainerOptions) Writer.Error!Struct { | |
| 895 | return .{ | |
| 896 | .container = try Container.begin(parent, .named, options), | |
| 897 | }; | |
| 898 | } | |
| 899 | ||
| 900 | /// Finishes serializing the struct. | |
| 901 | /// | |
| 902 | /// Prints a trailing comma as configured when appropriate, and the closing bracket. | |
| 903 | pub fn end(self: *Struct) Writer.Error!void { | |
| 904 | try self.container.end(); | |
| 905 | self.* = undefined; | |
| 906 | } | |
| 907 | ||
| 908 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`. | |
| 909 | pub fn field( | |
| 910 | self: *Struct, | |
| 911 | name: []const u8, | |
| 912 | val: anytype, | |
| 913 | options: ValueOptions, | |
| 914 | ) Writer.Error!void { | |
| 915 | try self.container.field(name, val, options); | |
| 916 | } | |
| 917 | ||
| 918 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`. | |
| 919 | pub fn fieldMaxDepth( | |
| 920 | self: *Struct, | |
| 921 | name: []const u8, | |
| 922 | val: anytype, | |
| 923 | options: ValueOptions, | |
| 924 | depth: usize, | |
| 925 | ) (Writer.Error || error{ExceededMaxDepth})!void { | |
| 926 | try self.container.fieldMaxDepth(name, val, options, depth); | |
| 927 | } | |
| 928 | ||
| 929 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by | |
| 930 | /// `valueArbitraryDepth`. | |
| 931 | pub fn fieldArbitraryDepth( | |
| 932 | self: *Struct, | |
| 933 | name: []const u8, | |
| 934 | val: anytype, | |
| 935 | options: ValueOptions, | |
| 936 | ) Writer.Error!void { | |
| 937 | try self.container.fieldArbitraryDepth(name, val, options); | |
| 938 | } | |
| 939 | ||
| 940 | /// Starts a field with a struct as a value. Returns the struct. | |
| 941 | pub fn beginStructField( | |
| 942 | self: *Struct, | |
| 943 | name: []const u8, | |
| 944 | options: SerializeContainerOptions, | |
| 945 | ) Writer.Error!Struct { | |
| 946 | try self.fieldPrefix(name); | |
| 947 | return self.container.serializer.beginStruct(options); | |
| 948 | } | |
| 949 | ||
| 950 | /// Starts a field with a tuple as a value. Returns the tuple. | |
| 951 | pub fn beginTupleField( | |
| 952 | self: *Struct, | |
| 953 | name: []const u8, | |
| 954 | options: SerializeContainerOptions, | |
| 955 | ) Writer.Error!Tuple { | |
| 956 | try self.fieldPrefix(name); | |
| 957 | return self.container.serializer.beginTuple(options); | |
| 958 | } | |
| 959 | ||
| 960 | /// Print a field prefix. This prints any necessary commas, the field name (escaped if | |
| 961 | /// necessary) and whitespace as configured. Useful if you want to serialize the field | |
| 962 | /// value yourself. | |
| 963 | pub fn fieldPrefix(self: *Struct, name: []const u8) Writer.Error!void { | |
| 964 | try self.container.fieldPrefix(name); | |
| 965 | } | |
| 966 | }; | |
| 967 | ||
| 968 | const Container = struct { | |
| 969 | const FieldStyle = enum { named, anon }; | |
| 970 | ||
| 971 | serializer: *Self, | |
| 972 | field_style: FieldStyle, | |
| 973 | options: SerializeContainerOptions, | |
| 974 | empty: bool, | |
| 975 | ||
| 976 | fn begin( | |
| 977 | sz: *Self, | |
| 978 | field_style: FieldStyle, | |
| 979 | options: SerializeContainerOptions, | |
| 980 | ) Writer.Error!Container { | |
| 981 | if (options.shouldWrap()) sz.indent_level +|= 1; | |
| 982 | try sz.writer.writeAll(".{"); | |
| 983 | return .{ | |
| 984 | .serializer = sz, | |
| 985 | .field_style = field_style, | |
| 986 | .options = options, | |
| 987 | .empty = true, | |
| 988 | }; | |
| 989 | } | |
| 990 | ||
| 991 | fn end(self: *Container) Writer.Error!void { | |
| 992 | if (self.options.shouldWrap()) self.serializer.indent_level -|= 1; | |
| 993 | if (!self.empty) { | |
| 994 | if (self.options.shouldWrap()) { | |
| 995 | if (self.serializer.options.whitespace) { | |
| 996 | try self.serializer.writer.writeByte(','); | |
| 997 | } | |
| 998 | try self.serializer.newline(); | |
| 999 | try self.serializer.indent(); | |
| 1000 | } else if (!self.shouldElideSpaces()) { | |
| 1001 | try self.serializer.space(); | |
| 1002 | } | |
| 1003 | } | |
| 1004 | try self.serializer.writer.writeByte('}'); | |
| 1005 | self.* = undefined; | |
| 1006 | } | |
| 1007 | ||
| 1008 | fn fieldPrefix(self: *Container, name: ?[]const u8) Writer.Error!void { | |
| 1009 | if (!self.empty) { | |
| 1010 | try self.serializer.writer.writeByte(','); | |
| 1011 | } | |
| 1012 | self.empty = false; | |
| 1013 | if (self.options.shouldWrap()) { | |
| 1014 | try self.serializer.newline(); | |
| 1015 | } else if (!self.shouldElideSpaces()) { | |
| 1016 | try self.serializer.space(); | |
| 1017 | } | |
| 1018 | if (self.options.shouldWrap()) try self.serializer.indent(); | |
| 1019 | if (name) |n| { | |
| 1020 | try self.serializer.ident(n); | |
| 1021 | try self.serializer.space(); | |
| 1022 | try self.serializer.writer.writeByte('='); | |
| 1023 | try self.serializer.space(); | |
| 1024 | } | |
| 1025 | } | |
| 1026 | ||
| 1027 | fn field( | |
| 1028 | self: *Container, | |
| 1029 | name: ?[]const u8, | |
| 1030 | val: anytype, | |
| 1031 | options: ValueOptions, | |
| 1032 | ) Writer.Error!void { | |
| 1033 | comptime assert(!typeIsRecursive(@TypeOf(val))); | |
| 1034 | try self.fieldArbitraryDepth(name, val, options); | |
| 1035 | } | |
| 1036 | ||
| 1037 | fn fieldMaxDepth( | |
| 1038 | self: *Container, | |
| 1039 | name: ?[]const u8, | |
| 1040 | val: anytype, | |
| 1041 | options: ValueOptions, | |
| 1042 | depth: usize, | |
| 1043 | ) (Writer.Error || error{ExceededMaxDepth})!void { | |
| 1044 | try checkValueDepth(val, depth); | |
| 1045 | try self.fieldArbitraryDepth(name, val, options); | |
| 1046 | } | |
| 1047 | ||
| 1048 | fn fieldArbitraryDepth( | |
| 1049 | self: *Container, | |
| 1050 | name: ?[]const u8, | |
| 1051 | val: anytype, | |
| 1052 | options: ValueOptions, | |
| 1053 | ) Writer.Error!void { | |
| 1054 | try self.fieldPrefix(name); | |
| 1055 | try self.serializer.valueArbitraryDepth(val, options); | |
| 1056 | } | |
| 1057 | ||
| 1058 | fn shouldElideSpaces(self: *const Container) bool { | |
| 1059 | return switch (self.options.whitespace_style) { | |
| 1060 | .fields => |fields| self.field_style != .named and fields == 1, | |
| 1061 | else => false, | |
| 1062 | }; | |
| 1063 | } | |
| 1064 | }; | |
| 1065 | }; | |
| 1066 | } | |
| 1067 | ||
| 1068 | /// Creates a new `Serializer` with the given writer and options. | |
| 1069 | pub fn serializer(writer: anytype, options: SerializerOptions) Serializer(@TypeOf(writer)) { | |
| 1070 | return .init(writer, options); | |
| 1071 | } | |
| 1072 | ||
| 1073 | 117 | fn expectSerializeEqual( |
| 1074 | 118 | expected: []const u8, |
| 1075 | 119 | value: anytype, |
| 1076 | 120 | options: SerializeOptions, |
| 1077 | 121 | ) !void { |
| 1078 | var buf = std.ArrayList(u8).init(std.testing.allocator); | |
| 1079 | defer buf.deinit(); | |
| 1080 | try serialize(value, options, buf.writer()); | |
| 1081 | try std.testing.expectEqualStrings(expected, buf.items); | |
| 122 | var aw: Writer.Allocating = .init(std.testing.allocator); | |
| 123 | const bw = &aw.writer; | |
| 124 | defer aw.deinit(); | |
| 125 | ||
| 126 | try serialize(value, options, bw); | |
| 127 | try std.testing.expectEqualStrings(expected, aw.getWritten()); | |
| 1082 | 128 | } |
| 1083 | 129 | |
| 1084 | 130 | test "std.zon stringify whitespace, high level API" { |
| ... | ... | @@ -1175,59 +221,59 @@ test "std.zon stringify whitespace, high level API" { |
| 1175 | 221 | } |
| 1176 | 222 | |
| 1177 | 223 | test "std.zon stringify whitespace, low level API" { |
| 1178 | var buf = std.ArrayList(u8).init(std.testing.allocator); | |
| 1179 | defer buf.deinit(); | |
| 1180 | var sz = serializer(buf.writer(), .{}); | |
| 224 | var aw: Writer.Allocating = .init(std.testing.allocator); | |
| 225 | var s: Serializer = .{ .writer = &aw.writer }; | |
| 226 | defer aw.deinit(); | |
| 1181 | 227 | |
| 1182 | inline for (.{ true, false }) |whitespace| { | |
| 1183 | sz.options = .{ .whitespace = whitespace }; | |
| 228 | for ([2]bool{ true, false }) |whitespace| { | |
| 229 | s.options = .{ .whitespace = whitespace }; | |
| 1184 | 230 | |
| 1185 | 231 | // Empty containers |
| 1186 | 232 | { |
| 1187 | var container = try sz.beginStruct(.{}); | |
| 233 | var container = try s.beginStruct(.{}); | |
| 1188 | 234 | try container.end(); |
| 1189 | try std.testing.expectEqualStrings(".{}", buf.items); | |
| 1190 | buf.clearRetainingCapacity(); | |
| 235 | try std.testing.expectEqualStrings(".{}", aw.getWritten()); | |
| 236 | aw.clearRetainingCapacity(); | |
| 1191 | 237 | } |
| 1192 | 238 | |
| 1193 | 239 | { |
| 1194 | var container = try sz.beginTuple(.{}); | |
| 240 | var container = try s.beginTuple(.{}); | |
| 1195 | 241 | try container.end(); |
| 1196 | try std.testing.expectEqualStrings(".{}", buf.items); | |
| 1197 | buf.clearRetainingCapacity(); | |
| 242 | try std.testing.expectEqualStrings(".{}", aw.getWritten()); | |
| 243 | aw.clearRetainingCapacity(); | |
| 1198 | 244 | } |
| 1199 | 245 | |
| 1200 | 246 | { |
| 1201 | var container = try sz.beginStruct(.{ .whitespace_style = .{ .wrap = false } }); | |
| 247 | var container = try s.beginStruct(.{ .whitespace_style = .{ .wrap = false } }); | |
| 1202 | 248 | try container.end(); |
| 1203 | try std.testing.expectEqualStrings(".{}", buf.items); | |
| 1204 | buf.clearRetainingCapacity(); | |
| 249 | try std.testing.expectEqualStrings(".{}", aw.getWritten()); | |
| 250 | aw.clearRetainingCapacity(); | |
| 1205 | 251 | } |
| 1206 | 252 | |
| 1207 | 253 | { |
| 1208 | var container = try sz.beginTuple(.{ .whitespace_style = .{ .wrap = false } }); | |
| 254 | var container = try s.beginTuple(.{ .whitespace_style = .{ .wrap = false } }); | |
| 1209 | 255 | try container.end(); |
| 1210 | try std.testing.expectEqualStrings(".{}", buf.items); | |
| 1211 | buf.clearRetainingCapacity(); | |
| 256 | try std.testing.expectEqualStrings(".{}", aw.getWritten()); | |
| 257 | aw.clearRetainingCapacity(); | |
| 1212 | 258 | } |
| 1213 | 259 | |
| 1214 | 260 | { |
| 1215 | var container = try sz.beginStruct(.{ .whitespace_style = .{ .fields = 0 } }); | |
| 261 | var container = try s.beginStruct(.{ .whitespace_style = .{ .fields = 0 } }); | |
| 1216 | 262 | try container.end(); |
| 1217 | try std.testing.expectEqualStrings(".{}", buf.items); | |
| 1218 | buf.clearRetainingCapacity(); | |
| 263 | try std.testing.expectEqualStrings(".{}", aw.getWritten()); | |
| 264 | aw.clearRetainingCapacity(); | |
| 1219 | 265 | } |
| 1220 | 266 | |
| 1221 | 267 | { |
| 1222 | var container = try sz.beginTuple(.{ .whitespace_style = .{ .fields = 0 } }); | |
| 268 | var container = try s.beginTuple(.{ .whitespace_style = .{ .fields = 0 } }); | |
| 1223 | 269 | try container.end(); |
| 1224 | try std.testing.expectEqualStrings(".{}", buf.items); | |
| 1225 | buf.clearRetainingCapacity(); | |
| 270 | try std.testing.expectEqualStrings(".{}", aw.getWritten()); | |
| 271 | aw.clearRetainingCapacity(); | |
| 1226 | 272 | } |
| 1227 | 273 | |
| 1228 | 274 | // Size 1 |
| 1229 | 275 | { |
| 1230 | var container = try sz.beginStruct(.{}); | |
| 276 | var container = try s.beginStruct(.{}); | |
| 1231 | 277 | try container.field("a", 1, .{}); |
| 1232 | 278 | try container.end(); |
| 1233 | 279 | if (whitespace) { |
| ... | ... | @@ -1235,15 +281,15 @@ test "std.zon stringify whitespace, low level API" { |
| 1235 | 281 | \\.{ |
| 1236 | 282 | \\ .a = 1, |
| 1237 | 283 | \\} |
| 1238 | , buf.items); | |
| 284 | , aw.getWritten()); | |
| 1239 | 285 | } else { |
| 1240 | try std.testing.expectEqualStrings(".{.a=1}", buf.items); | |
| 286 | try std.testing.expectEqualStrings(".{.a=1}", aw.getWritten()); | |
| 1241 | 287 | } |
| 1242 | buf.clearRetainingCapacity(); | |
| 288 | aw.clearRetainingCapacity(); | |
| 1243 | 289 | } |
| 1244 | 290 | |
| 1245 | 291 | { |
| 1246 | var container = try sz.beginTuple(.{}); | |
| 292 | var container = try s.beginTuple(.{}); | |
| 1247 | 293 | try container.field(1, .{}); |
| 1248 | 294 | try container.end(); |
| 1249 | 295 | if (whitespace) { |
| ... | ... | @@ -1251,62 +297,62 @@ test "std.zon stringify whitespace, low level API" { |
| 1251 | 297 | \\.{ |
| 1252 | 298 | \\ 1, |
| 1253 | 299 | \\} |
| 1254 | , buf.items); | |
| 300 | , aw.getWritten()); | |
| 1255 | 301 | } else { |
| 1256 | try std.testing.expectEqualStrings(".{1}", buf.items); | |
| 302 | try std.testing.expectEqualStrings(".{1}", aw.getWritten()); | |
| 1257 | 303 | } |
| 1258 | buf.clearRetainingCapacity(); | |
| 304 | aw.clearRetainingCapacity(); | |
| 1259 | 305 | } |
| 1260 | 306 | |
| 1261 | 307 | { |
| 1262 | var container = try sz.beginStruct(.{ .whitespace_style = .{ .wrap = false } }); | |
| 308 | var container = try s.beginStruct(.{ .whitespace_style = .{ .wrap = false } }); | |
| 1263 | 309 | try container.field("a", 1, .{}); |
| 1264 | 310 | try container.end(); |
| 1265 | 311 | if (whitespace) { |
| 1266 | try std.testing.expectEqualStrings(".{ .a = 1 }", buf.items); | |
| 312 | try std.testing.expectEqualStrings(".{ .a = 1 }", aw.getWritten()); | |
| 1267 | 313 | } else { |
| 1268 | try std.testing.expectEqualStrings(".{.a=1}", buf.items); | |
| 314 | try std.testing.expectEqualStrings(".{.a=1}", aw.getWritten()); | |
| 1269 | 315 | } |
| 1270 | buf.clearRetainingCapacity(); | |
| 316 | aw.clearRetainingCapacity(); | |
| 1271 | 317 | } |
| 1272 | 318 | |
| 1273 | 319 | { |
| 1274 | 320 | // We get extra spaces here, since we didn't know up front that there would only be one |
| 1275 | 321 | // field. |
| 1276 | var container = try sz.beginTuple(.{ .whitespace_style = .{ .wrap = false } }); | |
| 322 | var container = try s.beginTuple(.{ .whitespace_style = .{ .wrap = false } }); | |
| 1277 | 323 | try container.field(1, .{}); |
| 1278 | 324 | try container.end(); |
| 1279 | 325 | if (whitespace) { |
| 1280 | try std.testing.expectEqualStrings(".{ 1 }", buf.items); | |
| 326 | try std.testing.expectEqualStrings(".{ 1 }", aw.getWritten()); | |
| 1281 | 327 | } else { |
| 1282 | try std.testing.expectEqualStrings(".{1}", buf.items); | |
| 328 | try std.testing.expectEqualStrings(".{1}", aw.getWritten()); | |
| 1283 | 329 | } |
| 1284 | buf.clearRetainingCapacity(); | |
| 330 | aw.clearRetainingCapacity(); | |
| 1285 | 331 | } |
| 1286 | 332 | |
| 1287 | 333 | { |
| 1288 | var container = try sz.beginStruct(.{ .whitespace_style = .{ .fields = 1 } }); | |
| 334 | var container = try s.beginStruct(.{ .whitespace_style = .{ .fields = 1 } }); | |
| 1289 | 335 | try container.field("a", 1, .{}); |
| 1290 | 336 | try container.end(); |
| 1291 | 337 | if (whitespace) { |
| 1292 | try std.testing.expectEqualStrings(".{ .a = 1 }", buf.items); | |
| 338 | try std.testing.expectEqualStrings(".{ .a = 1 }", aw.getWritten()); | |
| 1293 | 339 | } else { |
| 1294 | try std.testing.expectEqualStrings(".{.a=1}", buf.items); | |
| 340 | try std.testing.expectEqualStrings(".{.a=1}", aw.getWritten()); | |
| 1295 | 341 | } |
| 1296 | buf.clearRetainingCapacity(); | |
| 342 | aw.clearRetainingCapacity(); | |
| 1297 | 343 | } |
| 1298 | 344 | |
| 1299 | 345 | { |
| 1300 | var container = try sz.beginTuple(.{ .whitespace_style = .{ .fields = 1 } }); | |
| 346 | var container = try s.beginTuple(.{ .whitespace_style = .{ .fields = 1 } }); | |
| 1301 | 347 | try container.field(1, .{}); |
| 1302 | 348 | try container.end(); |
| 1303 | try std.testing.expectEqualStrings(".{1}", buf.items); | |
| 1304 | buf.clearRetainingCapacity(); | |
| 349 | try std.testing.expectEqualStrings(".{1}", aw.getWritten()); | |
| 350 | aw.clearRetainingCapacity(); | |
| 1305 | 351 | } |
| 1306 | 352 | |
| 1307 | 353 | // Size 2 |
| 1308 | 354 | { |
| 1309 | var container = try sz.beginStruct(.{}); | |
| 355 | var container = try s.beginStruct(.{}); | |
| 1310 | 356 | try container.field("a", 1, .{}); |
| 1311 | 357 | try container.field("b", 2, .{}); |
| 1312 | 358 | try container.end(); |
| ... | ... | @@ -1316,15 +362,15 @@ test "std.zon stringify whitespace, low level API" { |
| 1316 | 362 | \\ .a = 1, |
| 1317 | 363 | \\ .b = 2, |
| 1318 | 364 | \\} |
| 1319 | , buf.items); | |
| 365 | , aw.getWritten()); | |
| 1320 | 366 | } else { |
| 1321 | try std.testing.expectEqualStrings(".{.a=1,.b=2}", buf.items); | |
| 367 | try std.testing.expectEqualStrings(".{.a=1,.b=2}", aw.getWritten()); | |
| 1322 | 368 | } |
| 1323 | buf.clearRetainingCapacity(); | |
| 369 | aw.clearRetainingCapacity(); | |
| 1324 | 370 | } |
| 1325 | 371 | |
| 1326 | 372 | { |
| 1327 | var container = try sz.beginTuple(.{}); | |
| 373 | var container = try s.beginTuple(.{}); | |
| 1328 | 374 | try container.field(1, .{}); |
| 1329 | 375 | try container.field(2, .{}); |
| 1330 | 376 | try container.end(); |
| ... | ... | @@ -1334,68 +380,68 @@ test "std.zon stringify whitespace, low level API" { |
| 1334 | 380 | \\ 1, |
| 1335 | 381 | \\ 2, |
| 1336 | 382 | \\} |
| 1337 | , buf.items); | |
| 383 | , aw.getWritten()); | |
| 1338 | 384 | } else { |
| 1339 | try std.testing.expectEqualStrings(".{1,2}", buf.items); | |
| 385 | try std.testing.expectEqualStrings(".{1,2}", aw.getWritten()); | |
| 1340 | 386 | } |
| 1341 | buf.clearRetainingCapacity(); | |
| 387 | aw.clearRetainingCapacity(); | |
| 1342 | 388 | } |
| 1343 | 389 | |
| 1344 | 390 | { |
| 1345 | var container = try sz.beginStruct(.{ .whitespace_style = .{ .wrap = false } }); | |
| 391 | var container = try s.beginStruct(.{ .whitespace_style = .{ .wrap = false } }); | |
| 1346 | 392 | try container.field("a", 1, .{}); |
| 1347 | 393 | try container.field("b", 2, .{}); |
| 1348 | 394 | try container.end(); |
| 1349 | 395 | if (whitespace) { |
| 1350 | try std.testing.expectEqualStrings(".{ .a = 1, .b = 2 }", buf.items); | |
| 396 | try std.testing.expectEqualStrings(".{ .a = 1, .b = 2 }", aw.getWritten()); | |
| 1351 | 397 | } else { |
| 1352 | try std.testing.expectEqualStrings(".{.a=1,.b=2}", buf.items); | |
| 398 | try std.testing.expectEqualStrings(".{.a=1,.b=2}", aw.getWritten()); | |
| 1353 | 399 | } |
| 1354 | buf.clearRetainingCapacity(); | |
| 400 | aw.clearRetainingCapacity(); | |
| 1355 | 401 | } |
| 1356 | 402 | |
| 1357 | 403 | { |
| 1358 | var container = try sz.beginTuple(.{ .whitespace_style = .{ .wrap = false } }); | |
| 404 | var container = try s.beginTuple(.{ .whitespace_style = .{ .wrap = false } }); | |
| 1359 | 405 | try container.field(1, .{}); |
| 1360 | 406 | try container.field(2, .{}); |
| 1361 | 407 | try container.end(); |
| 1362 | 408 | if (whitespace) { |
| 1363 | try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items); | |
| 409 | try std.testing.expectEqualStrings(".{ 1, 2 }", aw.getWritten()); | |
| 1364 | 410 | } else { |
| 1365 | try std.testing.expectEqualStrings(".{1,2}", buf.items); | |
| 411 | try std.testing.expectEqualStrings(".{1,2}", aw.getWritten()); | |
| 1366 | 412 | } |
| 1367 | buf.clearRetainingCapacity(); | |
| 413 | aw.clearRetainingCapacity(); | |
| 1368 | 414 | } |
| 1369 | 415 | |
| 1370 | 416 | { |
| 1371 | var container = try sz.beginStruct(.{ .whitespace_style = .{ .fields = 2 } }); | |
| 417 | var container = try s.beginStruct(.{ .whitespace_style = .{ .fields = 2 } }); | |
| 1372 | 418 | try container.field("a", 1, .{}); |
| 1373 | 419 | try container.field("b", 2, .{}); |
| 1374 | 420 | try container.end(); |
| 1375 | 421 | if (whitespace) { |
| 1376 | try std.testing.expectEqualStrings(".{ .a = 1, .b = 2 }", buf.items); | |
| 422 | try std.testing.expectEqualStrings(".{ .a = 1, .b = 2 }", aw.getWritten()); | |
| 1377 | 423 | } else { |
| 1378 | try std.testing.expectEqualStrings(".{.a=1,.b=2}", buf.items); | |
| 424 | try std.testing.expectEqualStrings(".{.a=1,.b=2}", aw.getWritten()); | |
| 1379 | 425 | } |
| 1380 | buf.clearRetainingCapacity(); | |
| 426 | aw.clearRetainingCapacity(); | |
| 1381 | 427 | } |
| 1382 | 428 | |
| 1383 | 429 | { |
| 1384 | var container = try sz.beginTuple(.{ .whitespace_style = .{ .fields = 2 } }); | |
| 430 | var container = try s.beginTuple(.{ .whitespace_style = .{ .fields = 2 } }); | |
| 1385 | 431 | try container.field(1, .{}); |
| 1386 | 432 | try container.field(2, .{}); |
| 1387 | 433 | try container.end(); |
| 1388 | 434 | if (whitespace) { |
| 1389 | try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items); | |
| 435 | try std.testing.expectEqualStrings(".{ 1, 2 }", aw.getWritten()); | |
| 1390 | 436 | } else { |
| 1391 | try std.testing.expectEqualStrings(".{1,2}", buf.items); | |
| 437 | try std.testing.expectEqualStrings(".{1,2}", aw.getWritten()); | |
| 1392 | 438 | } |
| 1393 | buf.clearRetainingCapacity(); | |
| 439 | aw.clearRetainingCapacity(); | |
| 1394 | 440 | } |
| 1395 | 441 | |
| 1396 | 442 | // Size 3 |
| 1397 | 443 | { |
| 1398 | var container = try sz.beginStruct(.{}); | |
| 444 | var container = try s.beginStruct(.{}); | |
| 1399 | 445 | try container.field("a", 1, .{}); |
| 1400 | 446 | try container.field("b", 2, .{}); |
| 1401 | 447 | try container.field("c", 3, .{}); |
| ... | ... | @@ -1407,15 +453,15 @@ test "std.zon stringify whitespace, low level API" { |
| 1407 | 453 | \\ .b = 2, |
| 1408 | 454 | \\ .c = 3, |
| 1409 | 455 | \\} |
| 1410 | , buf.items); | |
| 456 | , aw.getWritten()); | |
| 1411 | 457 | } else { |
| 1412 | try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", buf.items); | |
| 458 | try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", aw.getWritten()); | |
| 1413 | 459 | } |
| 1414 | buf.clearRetainingCapacity(); | |
| 460 | aw.clearRetainingCapacity(); | |
| 1415 | 461 | } |
| 1416 | 462 | |
| 1417 | 463 | { |
| 1418 | var container = try sz.beginTuple(.{}); | |
| 464 | var container = try s.beginTuple(.{}); | |
| 1419 | 465 | try container.field(1, .{}); |
| 1420 | 466 | try container.field(2, .{}); |
| 1421 | 467 | try container.field(3, .{}); |
| ... | ... | @@ -1427,43 +473,43 @@ test "std.zon stringify whitespace, low level API" { |
| 1427 | 473 | \\ 2, |
| 1428 | 474 | \\ 3, |
| 1429 | 475 | \\} |
| 1430 | , buf.items); | |
| 476 | , aw.getWritten()); | |
| 1431 | 477 | } else { |
| 1432 | try std.testing.expectEqualStrings(".{1,2,3}", buf.items); | |
| 478 | try std.testing.expectEqualStrings(".{1,2,3}", aw.getWritten()); | |
| 1433 | 479 | } |
| 1434 | buf.clearRetainingCapacity(); | |
| 480 | aw.clearRetainingCapacity(); | |
| 1435 | 481 | } |
| 1436 | 482 | |
| 1437 | 483 | { |
| 1438 | var container = try sz.beginStruct(.{ .whitespace_style = .{ .wrap = false } }); | |
| 484 | var container = try s.beginStruct(.{ .whitespace_style = .{ .wrap = false } }); | |
| 1439 | 485 | try container.field("a", 1, .{}); |
| 1440 | 486 | try container.field("b", 2, .{}); |
| 1441 | 487 | try container.field("c", 3, .{}); |
| 1442 | 488 | try container.end(); |
| 1443 | 489 | if (whitespace) { |
| 1444 | try std.testing.expectEqualStrings(".{ .a = 1, .b = 2, .c = 3 }", buf.items); | |
| 490 | try std.testing.expectEqualStrings(".{ .a = 1, .b = 2, .c = 3 }", aw.getWritten()); | |
| 1445 | 491 | } else { |
| 1446 | try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", buf.items); | |
| 492 | try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", aw.getWritten()); | |
| 1447 | 493 | } |
| 1448 | buf.clearRetainingCapacity(); | |
| 494 | aw.clearRetainingCapacity(); | |
| 1449 | 495 | } |
| 1450 | 496 | |
| 1451 | 497 | { |
| 1452 | var container = try sz.beginTuple(.{ .whitespace_style = .{ .wrap = false } }); | |
| 498 | var container = try s.beginTuple(.{ .whitespace_style = .{ .wrap = false } }); | |
| 1453 | 499 | try container.field(1, .{}); |
| 1454 | 500 | try container.field(2, .{}); |
| 1455 | 501 | try container.field(3, .{}); |
| 1456 | 502 | try container.end(); |
| 1457 | 503 | if (whitespace) { |
| 1458 | try std.testing.expectEqualStrings(".{ 1, 2, 3 }", buf.items); | |
| 504 | try std.testing.expectEqualStrings(".{ 1, 2, 3 }", aw.getWritten()); | |
| 1459 | 505 | } else { |
| 1460 | try std.testing.expectEqualStrings(".{1,2,3}", buf.items); | |
| 506 | try std.testing.expectEqualStrings(".{1,2,3}", aw.getWritten()); | |
| 1461 | 507 | } |
| 1462 | buf.clearRetainingCapacity(); | |
| 508 | aw.clearRetainingCapacity(); | |
| 1463 | 509 | } |
| 1464 | 510 | |
| 1465 | 511 | { |
| 1466 | var container = try sz.beginStruct(.{ .whitespace_style = .{ .fields = 3 } }); | |
| 512 | var container = try s.beginStruct(.{ .whitespace_style = .{ .fields = 3 } }); | |
| 1467 | 513 | try container.field("a", 1, .{}); |
| 1468 | 514 | try container.field("b", 2, .{}); |
| 1469 | 515 | try container.field("c", 3, .{}); |
| ... | ... | @@ -1475,15 +521,15 @@ test "std.zon stringify whitespace, low level API" { |
| 1475 | 521 | \\ .b = 2, |
| 1476 | 522 | \\ .c = 3, |
| 1477 | 523 | \\} |
| 1478 | , buf.items); | |
| 524 | , aw.getWritten()); | |
| 1479 | 525 | } else { |
| 1480 | try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", buf.items); | |
| 526 | try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", aw.getWritten()); | |
| 1481 | 527 | } |
| 1482 | buf.clearRetainingCapacity(); | |
| 528 | aw.clearRetainingCapacity(); | |
| 1483 | 529 | } |
| 1484 | 530 | |
| 1485 | 531 | { |
| 1486 | var container = try sz.beginTuple(.{ .whitespace_style = .{ .fields = 3 } }); | |
| 532 | var container = try s.beginTuple(.{ .whitespace_style = .{ .fields = 3 } }); | |
| 1487 | 533 | try container.field(1, .{}); |
| 1488 | 534 | try container.field(2, .{}); |
| 1489 | 535 | try container.field(3, .{}); |
| ... | ... | @@ -1495,16 +541,16 @@ test "std.zon stringify whitespace, low level API" { |
| 1495 | 541 | \\ 2, |
| 1496 | 542 | \\ 3, |
| 1497 | 543 | \\} |
| 1498 | , buf.items); | |
| 544 | , aw.getWritten()); | |
| 1499 | 545 | } else { |
| 1500 | try std.testing.expectEqualStrings(".{1,2,3}", buf.items); | |
| 546 | try std.testing.expectEqualStrings(".{1,2,3}", aw.getWritten()); | |
| 1501 | 547 | } |
| 1502 | buf.clearRetainingCapacity(); | |
| 548 | aw.clearRetainingCapacity(); | |
| 1503 | 549 | } |
| 1504 | 550 | |
| 1505 | 551 | // Nested objects where the outer container doesn't wrap but the inner containers do |
| 1506 | 552 | { |
| 1507 | var container = try sz.beginStruct(.{ .whitespace_style = .{ .wrap = false } }); | |
| 553 | var container = try s.beginStruct(.{ .whitespace_style = .{ .wrap = false } }); | |
| 1508 | 554 | try container.field("first", .{ 1, 2, 3 }, .{}); |
| 1509 | 555 | try container.field("second", .{ 4, 5, 6 }, .{}); |
| 1510 | 556 | try container.end(); |
| ... | ... | @@ -1519,139 +565,141 @@ test "std.zon stringify whitespace, low level API" { |
| 1519 | 565 | \\ 5, |
| 1520 | 566 | \\ 6, |
| 1521 | 567 | \\} } |
| 1522 | , buf.items); | |
| 568 | , aw.getWritten()); | |
| 1523 | 569 | } else { |
| 1524 | 570 | try std.testing.expectEqualStrings( |
| 1525 | 571 | ".{.first=.{1,2,3},.second=.{4,5,6}}", |
| 1526 | buf.items, | |
| 572 | aw.getWritten(), | |
| 1527 | 573 | ); |
| 1528 | 574 | } |
| 1529 | buf.clearRetainingCapacity(); | |
| 575 | aw.clearRetainingCapacity(); | |
| 1530 | 576 | } |
| 1531 | 577 | } |
| 1532 | 578 | } |
| 1533 | 579 | |
| 1534 | 580 | test "std.zon stringify utf8 codepoints" { |
| 1535 | var buf = std.ArrayList(u8).init(std.testing.allocator); | |
| 1536 | defer buf.deinit(); | |
| 1537 | var sz = serializer(buf.writer(), .{}); | |
| 581 | var aw: Writer.Allocating = .init(std.testing.allocator); | |
| 582 | var s: Serializer = .{ .writer = &aw.writer }; | |
| 583 | defer aw.deinit(); | |
| 1538 | 584 | |
| 1539 | 585 | // Printable ASCII |
| 1540 | try sz.int('a'); | |
| 1541 | try std.testing.expectEqualStrings("97", buf.items); | |
| 1542 | buf.clearRetainingCapacity(); | |
| 586 | try s.int('a'); | |
| 587 | try std.testing.expectEqualStrings("97", aw.getWritten()); | |
| 588 | aw.clearRetainingCapacity(); | |
| 1543 | 589 | |
| 1544 | try sz.codePoint('a'); | |
| 1545 | try std.testing.expectEqualStrings("'a'", buf.items); | |
| 1546 | buf.clearRetainingCapacity(); | |
| 590 | try s.codePoint('a'); | |
| 591 | try std.testing.expectEqualStrings("'a'", aw.getWritten()); | |
| 592 | aw.clearRetainingCapacity(); | |
| 1547 | 593 | |
| 1548 | try sz.value('a', .{ .emit_codepoint_literals = .always }); | |
| 1549 | try std.testing.expectEqualStrings("'a'", buf.items); | |
| 1550 | buf.clearRetainingCapacity(); | |
| 594 | try s.value('a', .{ .emit_codepoint_literals = .always }); | |
| 595 | try std.testing.expectEqualStrings("'a'", aw.getWritten()); | |
| 596 | aw.clearRetainingCapacity(); | |
| 1551 | 597 | |
| 1552 | try sz.value('a', .{ .emit_codepoint_literals = .printable_ascii }); | |
| 1553 | try std.testing.expectEqualStrings("'a'", buf.items); | |
| 1554 | buf.clearRetainingCapacity(); | |
| 598 | try s.value('a', .{ .emit_codepoint_literals = .printable_ascii }); | |
| 599 | try std.testing.expectEqualStrings("'a'", aw.getWritten()); | |
| 600 | aw.clearRetainingCapacity(); | |
| 1555 | 601 | |
| 1556 | try sz.value('a', .{ .emit_codepoint_literals = .never }); | |
| 1557 | try std.testing.expectEqualStrings("97", buf.items); | |
| 1558 | buf.clearRetainingCapacity(); | |
| 602 | try s.value('a', .{ .emit_codepoint_literals = .never }); | |
| 603 | try std.testing.expectEqualStrings("97", aw.getWritten()); | |
| 604 | aw.clearRetainingCapacity(); | |
| 1559 | 605 | |
| 1560 | 606 | // Short escaped codepoint |
| 1561 | try sz.int('\n'); | |
| 1562 | try std.testing.expectEqualStrings("10", buf.items); | |
| 1563 | buf.clearRetainingCapacity(); | |
| 607 | try s.int('\n'); | |
| 608 | try std.testing.expectEqualStrings("10", aw.getWritten()); | |
| 609 | aw.clearRetainingCapacity(); | |
| 1564 | 610 | |
| 1565 | try sz.codePoint('\n'); | |
| 1566 | try std.testing.expectEqualStrings("'\\n'", buf.items); | |
| 1567 | buf.clearRetainingCapacity(); | |
| 611 | try s.codePoint('\n'); | |
| 612 | try std.testing.expectEqualStrings("'\\n'", aw.getWritten()); | |
| 613 | aw.clearRetainingCapacity(); | |
| 1568 | 614 | |
| 1569 | try sz.value('\n', .{ .emit_codepoint_literals = .always }); | |
| 1570 | try std.testing.expectEqualStrings("'\\n'", buf.items); | |
| 1571 | buf.clearRetainingCapacity(); | |
| 615 | try s.value('\n', .{ .emit_codepoint_literals = .always }); | |
| 616 | try std.testing.expectEqualStrings("'\\n'", aw.getWritten()); | |
| 617 | aw.clearRetainingCapacity(); | |
| 1572 | 618 | |
| 1573 | try sz.value('\n', .{ .emit_codepoint_literals = .printable_ascii }); | |
| 1574 | try std.testing.expectEqualStrings("10", buf.items); | |
| 1575 | buf.clearRetainingCapacity(); | |
| 619 | try s.value('\n', .{ .emit_codepoint_literals = .printable_ascii }); | |
| 620 | try std.testing.expectEqualStrings("10", aw.getWritten()); | |
| 621 | aw.clearRetainingCapacity(); | |
| 1576 | 622 | |
| 1577 | try sz.value('\n', .{ .emit_codepoint_literals = .never }); | |
| 1578 | try std.testing.expectEqualStrings("10", buf.items); | |
| 1579 | buf.clearRetainingCapacity(); | |
| 623 | try s.value('\n', .{ .emit_codepoint_literals = .never }); | |
| 624 | try std.testing.expectEqualStrings("10", aw.getWritten()); | |
| 625 | aw.clearRetainingCapacity(); | |
| 1580 | 626 | |
| 1581 | 627 | // Large codepoint |
| 1582 | try sz.int('⚡'); | |
| 1583 | try std.testing.expectEqualStrings("9889", buf.items); | |
| 1584 | buf.clearRetainingCapacity(); | |
| 628 | try s.int('⚡'); | |
| 629 | try std.testing.expectEqualStrings("9889", aw.getWritten()); | |
| 630 | aw.clearRetainingCapacity(); | |
| 1585 | 631 | |
| 1586 | try sz.codePoint('⚡'); | |
| 1587 | try std.testing.expectEqualStrings("'\\xe2\\x9a\\xa1'", buf.items); | |
| 1588 | buf.clearRetainingCapacity(); | |
| 632 | try s.codePoint('⚡'); | |
| 633 | try std.testing.expectEqualStrings("'\\u{26a1}'", aw.getWritten()); | |
| 634 | aw.clearRetainingCapacity(); | |
| 1589 | 635 | |
| 1590 | try sz.value('⚡', .{ .emit_codepoint_literals = .always }); | |
| 1591 | try std.testing.expectEqualStrings("'\\xe2\\x9a\\xa1'", buf.items); | |
| 1592 | buf.clearRetainingCapacity(); | |
| 636 | try s.value('⚡', .{ .emit_codepoint_literals = .always }); | |
| 637 | try std.testing.expectEqualStrings("'\\u{26a1}'", aw.getWritten()); | |
| 638 | aw.clearRetainingCapacity(); | |
| 1593 | 639 | |
| 1594 | try sz.value('⚡', .{ .emit_codepoint_literals = .printable_ascii }); | |
| 1595 | try std.testing.expectEqualStrings("9889", buf.items); | |
| 1596 | buf.clearRetainingCapacity(); | |
| 640 | try s.value('⚡', .{ .emit_codepoint_literals = .printable_ascii }); | |
| 641 | try std.testing.expectEqualStrings("9889", aw.getWritten()); | |
| 642 | aw.clearRetainingCapacity(); | |
| 1597 | 643 | |
| 1598 | try sz.value('⚡', .{ .emit_codepoint_literals = .never }); | |
| 1599 | try std.testing.expectEqualStrings("9889", buf.items); | |
| 1600 | buf.clearRetainingCapacity(); | |
| 644 | try s.value('⚡', .{ .emit_codepoint_literals = .never }); | |
| 645 | try std.testing.expectEqualStrings("9889", aw.getWritten()); | |
| 646 | aw.clearRetainingCapacity(); | |
| 1601 | 647 | |
| 1602 | 648 | // Invalid codepoint |
| 1603 | try std.testing.expectError(error.InvalidCodepoint, sz.codePoint(0x110000 + 1)); | |
| 649 | try s.codePoint(0x110000 + 1); | |
| 650 | try std.testing.expectEqualStrings("'\\u{110001}'", aw.getWritten()); | |
| 651 | aw.clearRetainingCapacity(); | |
| 1604 | 652 | |
| 1605 | try sz.int(0x110000 + 1); | |
| 1606 | try std.testing.expectEqualStrings("1114113", buf.items); | |
| 1607 | buf.clearRetainingCapacity(); | |
| 653 | try s.int(0x110000 + 1); | |
| 654 | try std.testing.expectEqualStrings("1114113", aw.getWritten()); | |
| 655 | aw.clearRetainingCapacity(); | |
| 1608 | 656 | |
| 1609 | try sz.value(0x110000 + 1, .{ .emit_codepoint_literals = .always }); | |
| 1610 | try std.testing.expectEqualStrings("1114113", buf.items); | |
| 1611 | buf.clearRetainingCapacity(); | |
| 657 | try s.value(0x110000 + 1, .{ .emit_codepoint_literals = .always }); | |
| 658 | try std.testing.expectEqualStrings("1114113", aw.getWritten()); | |
| 659 | aw.clearRetainingCapacity(); | |
| 1612 | 660 | |
| 1613 | try sz.value(0x110000 + 1, .{ .emit_codepoint_literals = .printable_ascii }); | |
| 1614 | try std.testing.expectEqualStrings("1114113", buf.items); | |
| 1615 | buf.clearRetainingCapacity(); | |
| 661 | try s.value(0x110000 + 1, .{ .emit_codepoint_literals = .printable_ascii }); | |
| 662 | try std.testing.expectEqualStrings("1114113", aw.getWritten()); | |
| 663 | aw.clearRetainingCapacity(); | |
| 1616 | 664 | |
| 1617 | try sz.value(0x110000 + 1, .{ .emit_codepoint_literals = .never }); | |
| 1618 | try std.testing.expectEqualStrings("1114113", buf.items); | |
| 1619 | buf.clearRetainingCapacity(); | |
| 665 | try s.value(0x110000 + 1, .{ .emit_codepoint_literals = .never }); | |
| 666 | try std.testing.expectEqualStrings("1114113", aw.getWritten()); | |
| 667 | aw.clearRetainingCapacity(); | |
| 1620 | 668 | |
| 1621 | 669 | // Valid codepoint, not a codepoint type |
| 1622 | try sz.value(@as(u22, 'a'), .{ .emit_codepoint_literals = .always }); | |
| 1623 | try std.testing.expectEqualStrings("97", buf.items); | |
| 1624 | buf.clearRetainingCapacity(); | |
| 670 | try s.value(@as(u22, 'a'), .{ .emit_codepoint_literals = .always }); | |
| 671 | try std.testing.expectEqualStrings("97", aw.getWritten()); | |
| 672 | aw.clearRetainingCapacity(); | |
| 1625 | 673 | |
| 1626 | try sz.value(@as(u22, 'a'), .{ .emit_codepoint_literals = .printable_ascii }); | |
| 1627 | try std.testing.expectEqualStrings("97", buf.items); | |
| 1628 | buf.clearRetainingCapacity(); | |
| 674 | try s.value(@as(u22, 'a'), .{ .emit_codepoint_literals = .printable_ascii }); | |
| 675 | try std.testing.expectEqualStrings("97", aw.getWritten()); | |
| 676 | aw.clearRetainingCapacity(); | |
| 1629 | 677 | |
| 1630 | try sz.value(@as(i32, 'a'), .{ .emit_codepoint_literals = .never }); | |
| 1631 | try std.testing.expectEqualStrings("97", buf.items); | |
| 1632 | buf.clearRetainingCapacity(); | |
| 678 | try s.value(@as(i32, 'a'), .{ .emit_codepoint_literals = .never }); | |
| 679 | try std.testing.expectEqualStrings("97", aw.getWritten()); | |
| 680 | aw.clearRetainingCapacity(); | |
| 1633 | 681 | |
| 1634 | 682 | // Make sure value options are passed to children |
| 1635 | try sz.value(.{ .c = '⚡' }, .{ .emit_codepoint_literals = .always }); | |
| 1636 | try std.testing.expectEqualStrings(".{ .c = '\\xe2\\x9a\\xa1' }", buf.items); | |
| 1637 | buf.clearRetainingCapacity(); | |
| 683 | try s.value(.{ .c = '⚡' }, .{ .emit_codepoint_literals = .always }); | |
| 684 | try std.testing.expectEqualStrings(".{ .c = '\\u{26a1}' }", aw.getWritten()); | |
| 685 | aw.clearRetainingCapacity(); | |
| 1638 | 686 | |
| 1639 | try sz.value(.{ .c = '⚡' }, .{ .emit_codepoint_literals = .never }); | |
| 1640 | try std.testing.expectEqualStrings(".{ .c = 9889 }", buf.items); | |
| 1641 | buf.clearRetainingCapacity(); | |
| 687 | try s.value(.{ .c = '⚡' }, .{ .emit_codepoint_literals = .never }); | |
| 688 | try std.testing.expectEqualStrings(".{ .c = 9889 }", aw.getWritten()); | |
| 689 | aw.clearRetainingCapacity(); | |
| 1642 | 690 | } |
| 1643 | 691 | |
| 1644 | 692 | test "std.zon stringify strings" { |
| 1645 | var buf = std.ArrayList(u8).init(std.testing.allocator); | |
| 1646 | defer buf.deinit(); | |
| 1647 | var sz = serializer(buf.writer(), .{}); | |
| 693 | var aw: Writer.Allocating = .init(std.testing.allocator); | |
| 694 | var s: Serializer = .{ .writer = &aw.writer }; | |
| 695 | defer aw.deinit(); | |
| 1648 | 696 | |
| 1649 | 697 | // Minimal case |
| 1650 | try sz.string("abc⚡\n"); | |
| 1651 | try std.testing.expectEqualStrings("\"abc\\xe2\\x9a\\xa1\\n\"", buf.items); | |
| 1652 | buf.clearRetainingCapacity(); | |
| 698 | try s.string("abc⚡\n"); | |
| 699 | try std.testing.expectEqualStrings("\"abc\\xe2\\x9a\\xa1\\n\"", aw.getWritten()); | |
| 700 | aw.clearRetainingCapacity(); | |
| 1653 | 701 | |
| 1654 | try sz.tuple("abc⚡\n", .{}); | |
| 702 | try s.tuple("abc⚡\n", .{}); | |
| 1655 | 703 | try std.testing.expectEqualStrings( |
| 1656 | 704 | \\.{ |
| 1657 | 705 | \\ 97, |
| ... | ... | @@ -1662,14 +710,14 @@ test "std.zon stringify strings" { |
| 1662 | 710 | \\ 161, |
| 1663 | 711 | \\ 10, |
| 1664 | 712 | \\} |
| 1665 | , buf.items); | |
| 1666 | buf.clearRetainingCapacity(); | |
| 713 | , aw.getWritten()); | |
| 714 | aw.clearRetainingCapacity(); | |
| 1667 | 715 | |
| 1668 | try sz.value("abc⚡\n", .{}); | |
| 1669 | try std.testing.expectEqualStrings("\"abc\\xe2\\x9a\\xa1\\n\"", buf.items); | |
| 1670 | buf.clearRetainingCapacity(); | |
| 716 | try s.value("abc⚡\n", .{}); | |
| 717 | try std.testing.expectEqualStrings("\"abc\\xe2\\x9a\\xa1\\n\"", aw.getWritten()); | |
| 718 | aw.clearRetainingCapacity(); | |
| 1671 | 719 | |
| 1672 | try sz.value("abc⚡\n", .{ .emit_strings_as_containers = true }); | |
| 720 | try s.value("abc⚡\n", .{ .emit_strings_as_containers = true }); | |
| 1673 | 721 | try std.testing.expectEqualStrings( |
| 1674 | 722 | \\.{ |
| 1675 | 723 | \\ 97, |
| ... | ... | @@ -1680,113 +728,113 @@ test "std.zon stringify strings" { |
| 1680 | 728 | \\ 161, |
| 1681 | 729 | \\ 10, |
| 1682 | 730 | \\} |
| 1683 | , buf.items); | |
| 1684 | buf.clearRetainingCapacity(); | |
| 731 | , aw.getWritten()); | |
| 732 | aw.clearRetainingCapacity(); | |
| 1685 | 733 | |
| 1686 | 734 | // Value options are inherited by children |
| 1687 | try sz.value(.{ .str = "abc" }, .{}); | |
| 1688 | try std.testing.expectEqualStrings(".{ .str = \"abc\" }", buf.items); | |
| 1689 | buf.clearRetainingCapacity(); | |
| 735 | try s.value(.{ .str = "abc" }, .{}); | |
| 736 | try std.testing.expectEqualStrings(".{ .str = \"abc\" }", aw.getWritten()); | |
| 737 | aw.clearRetainingCapacity(); | |
| 1690 | 738 | |
| 1691 | try sz.value(.{ .str = "abc" }, .{ .emit_strings_as_containers = true }); | |
| 739 | try s.value(.{ .str = "abc" }, .{ .emit_strings_as_containers = true }); | |
| 1692 | 740 | try std.testing.expectEqualStrings( |
| 1693 | 741 | \\.{ .str = .{ |
| 1694 | 742 | \\ 97, |
| 1695 | 743 | \\ 98, |
| 1696 | 744 | \\ 99, |
| 1697 | 745 | \\} } |
| 1698 | , buf.items); | |
| 1699 | buf.clearRetainingCapacity(); | |
| 746 | , aw.getWritten()); | |
| 747 | aw.clearRetainingCapacity(); | |
| 1700 | 748 | |
| 1701 | 749 | // Arrays (rather than pointers to arrays) of u8s are not considered strings, so that data can |
| 1702 | 750 | // round trip correctly. |
| 1703 | try sz.value("abc".*, .{}); | |
| 751 | try s.value("abc".*, .{}); | |
| 1704 | 752 | try std.testing.expectEqualStrings( |
| 1705 | 753 | \\.{ |
| 1706 | 754 | \\ 97, |
| 1707 | 755 | \\ 98, |
| 1708 | 756 | \\ 99, |
| 1709 | 757 | \\} |
| 1710 | , buf.items); | |
| 1711 | buf.clearRetainingCapacity(); | |
| 758 | , aw.getWritten()); | |
| 759 | aw.clearRetainingCapacity(); | |
| 1712 | 760 | } |
| 1713 | 761 | |
| 1714 | 762 | test "std.zon stringify multiline strings" { |
| 1715 | var buf = std.ArrayList(u8).init(std.testing.allocator); | |
| 1716 | defer buf.deinit(); | |
| 1717 | var sz = serializer(buf.writer(), .{}); | |
| 763 | var aw: Writer.Allocating = .init(std.testing.allocator); | |
| 764 | var s: Serializer = .{ .writer = &aw.writer }; | |
| 765 | defer aw.deinit(); | |
| 1718 | 766 | |
| 1719 | 767 | inline for (.{ true, false }) |whitespace| { |
| 1720 | sz.options.whitespace = whitespace; | |
| 768 | s.options.whitespace = whitespace; | |
| 1721 | 769 | |
| 1722 | 770 | { |
| 1723 | try sz.multilineString("", .{ .top_level = true }); | |
| 1724 | try std.testing.expectEqualStrings("\\\\", buf.items); | |
| 1725 | buf.clearRetainingCapacity(); | |
| 771 | try s.multilineString("", .{ .top_level = true }); | |
| 772 | try std.testing.expectEqualStrings("\\\\", aw.getWritten()); | |
| 773 | aw.clearRetainingCapacity(); | |
| 1726 | 774 | } |
| 1727 | 775 | |
| 1728 | 776 | { |
| 1729 | try sz.multilineString("abc⚡", .{ .top_level = true }); | |
| 1730 | try std.testing.expectEqualStrings("\\\\abc⚡", buf.items); | |
| 1731 | buf.clearRetainingCapacity(); | |
| 777 | try s.multilineString("abc⚡", .{ .top_level = true }); | |
| 778 | try std.testing.expectEqualStrings("\\\\abc⚡", aw.getWritten()); | |
| 779 | aw.clearRetainingCapacity(); | |
| 1732 | 780 | } |
| 1733 | 781 | |
| 1734 | 782 | { |
| 1735 | try sz.multilineString("abc⚡\ndef", .{ .top_level = true }); | |
| 1736 | try std.testing.expectEqualStrings("\\\\abc⚡\n\\\\def", buf.items); | |
| 1737 | buf.clearRetainingCapacity(); | |
| 783 | try s.multilineString("abc⚡\ndef", .{ .top_level = true }); | |
| 784 | try std.testing.expectEqualStrings("\\\\abc⚡\n\\\\def", aw.getWritten()); | |
| 785 | aw.clearRetainingCapacity(); | |
| 1738 | 786 | } |
| 1739 | 787 | |
| 1740 | 788 | { |
| 1741 | try sz.multilineString("abc⚡\r\ndef", .{ .top_level = true }); | |
| 1742 | try std.testing.expectEqualStrings("\\\\abc⚡\n\\\\def", buf.items); | |
| 1743 | buf.clearRetainingCapacity(); | |
| 789 | try s.multilineString("abc⚡\r\ndef", .{ .top_level = true }); | |
| 790 | try std.testing.expectEqualStrings("\\\\abc⚡\n\\\\def", aw.getWritten()); | |
| 791 | aw.clearRetainingCapacity(); | |
| 1744 | 792 | } |
| 1745 | 793 | |
| 1746 | 794 | { |
| 1747 | try sz.multilineString("\nabc⚡", .{ .top_level = true }); | |
| 1748 | try std.testing.expectEqualStrings("\\\\\n\\\\abc⚡", buf.items); | |
| 1749 | buf.clearRetainingCapacity(); | |
| 795 | try s.multilineString("\nabc⚡", .{ .top_level = true }); | |
| 796 | try std.testing.expectEqualStrings("\\\\\n\\\\abc⚡", aw.getWritten()); | |
| 797 | aw.clearRetainingCapacity(); | |
| 1750 | 798 | } |
| 1751 | 799 | |
| 1752 | 800 | { |
| 1753 | try sz.multilineString("\r\nabc⚡", .{ .top_level = true }); | |
| 1754 | try std.testing.expectEqualStrings("\\\\\n\\\\abc⚡", buf.items); | |
| 1755 | buf.clearRetainingCapacity(); | |
| 801 | try s.multilineString("\r\nabc⚡", .{ .top_level = true }); | |
| 802 | try std.testing.expectEqualStrings("\\\\\n\\\\abc⚡", aw.getWritten()); | |
| 803 | aw.clearRetainingCapacity(); | |
| 1756 | 804 | } |
| 1757 | 805 | |
| 1758 | 806 | { |
| 1759 | try sz.multilineString("abc\ndef", .{}); | |
| 807 | try s.multilineString("abc\ndef", .{}); | |
| 1760 | 808 | if (whitespace) { |
| 1761 | try std.testing.expectEqualStrings("\n\\\\abc\n\\\\def\n", buf.items); | |
| 809 | try std.testing.expectEqualStrings("\n\\\\abc\n\\\\def\n", aw.getWritten()); | |
| 1762 | 810 | } else { |
| 1763 | try std.testing.expectEqualStrings("\\\\abc\n\\\\def\n", buf.items); | |
| 811 | try std.testing.expectEqualStrings("\\\\abc\n\\\\def\n", aw.getWritten()); | |
| 1764 | 812 | } |
| 1765 | buf.clearRetainingCapacity(); | |
| 813 | aw.clearRetainingCapacity(); | |
| 1766 | 814 | } |
| 1767 | 815 | |
| 1768 | 816 | { |
| 1769 | 817 | const str: []const u8 = &.{ 'a', '\r', 'c' }; |
| 1770 | try sz.string(str); | |
| 1771 | try std.testing.expectEqualStrings("\"a\\rc\"", buf.items); | |
| 1772 | buf.clearRetainingCapacity(); | |
| 818 | try s.string(str); | |
| 819 | try std.testing.expectEqualStrings("\"a\\rc\"", aw.getWritten()); | |
| 820 | aw.clearRetainingCapacity(); | |
| 1773 | 821 | } |
| 1774 | 822 | |
| 1775 | 823 | { |
| 1776 | 824 | try std.testing.expectError( |
| 1777 | 825 | error.InnerCarriageReturn, |
| 1778 | sz.multilineString(@as([]const u8, &.{ 'a', '\r', 'c' }), .{}), | |
| 826 | s.multilineString(@as([]const u8, &.{ 'a', '\r', 'c' }), .{}), | |
| 1779 | 827 | ); |
| 1780 | 828 | try std.testing.expectError( |
| 1781 | 829 | error.InnerCarriageReturn, |
| 1782 | sz.multilineString(@as([]const u8, &.{ 'a', '\r', 'c', '\n' }), .{}), | |
| 830 | s.multilineString(@as([]const u8, &.{ 'a', '\r', 'c', '\n' }), .{}), | |
| 1783 | 831 | ); |
| 1784 | 832 | try std.testing.expectError( |
| 1785 | 833 | error.InnerCarriageReturn, |
| 1786 | sz.multilineString(@as([]const u8, &.{ 'a', '\r', 'c', '\r', '\n' }), .{}), | |
| 834 | s.multilineString(@as([]const u8, &.{ 'a', '\r', 'c', '\r', '\n' }), .{}), | |
| 1787 | 835 | ); |
| 1788 | try std.testing.expectEqualStrings("", buf.items); | |
| 1789 | buf.clearRetainingCapacity(); | |
| 836 | try std.testing.expectEqualStrings("", aw.getWritten()); | |
| 837 | aw.clearRetainingCapacity(); | |
| 1790 | 838 | } |
| 1791 | 839 | } |
| 1792 | 840 | } |
| ... | ... | @@ -1932,42 +980,43 @@ test "std.zon stringify skip default fields" { |
| 1932 | 980 | } |
| 1933 | 981 | |
| 1934 | 982 | test "std.zon depth limits" { |
| 1935 | var buf = std.ArrayList(u8).init(std.testing.allocator); | |
| 1936 | defer buf.deinit(); | |
| 983 | var aw: Writer.Allocating = .init(std.testing.allocator); | |
| 984 | const bw = &aw.writer; | |
| 985 | defer aw.deinit(); | |
| 1937 | 986 | |
| 1938 | 987 | const Recurse = struct { r: []const @This() }; |
| 1939 | 988 | |
| 1940 | 989 | // Normal operation |
| 1941 | try serializeMaxDepth(.{ 1, .{ 2, 3 } }, .{}, buf.writer(), 16); | |
| 1942 | try std.testing.expectEqualStrings(".{ 1, .{ 2, 3 } }", buf.items); | |
| 1943 | buf.clearRetainingCapacity(); | |
| 990 | try serializeMaxDepth(.{ 1, .{ 2, 3 } }, .{}, bw, 16); | |
| 991 | try std.testing.expectEqualStrings(".{ 1, .{ 2, 3 } }", aw.getWritten()); | |
| 992 | aw.clearRetainingCapacity(); | |
| 1944 | 993 | |
| 1945 | try serializeArbitraryDepth(.{ 1, .{ 2, 3 } }, .{}, buf.writer()); | |
| 1946 | try std.testing.expectEqualStrings(".{ 1, .{ 2, 3 } }", buf.items); | |
| 1947 | buf.clearRetainingCapacity(); | |
| 994 | try serializeArbitraryDepth(.{ 1, .{ 2, 3 } }, .{}, bw); | |
| 995 | try std.testing.expectEqualStrings(".{ 1, .{ 2, 3 } }", aw.getWritten()); | |
| 996 | aw.clearRetainingCapacity(); | |
| 1948 | 997 | |
| 1949 | 998 | // Max depth failing on non recursive type |
| 1950 | 999 | try std.testing.expectError( |
| 1951 | 1000 | error.ExceededMaxDepth, |
| 1952 | serializeMaxDepth(.{ 1, .{ 2, .{ 3, 4 } } }, .{}, buf.writer(), 3), | |
| 1001 | serializeMaxDepth(.{ 1, .{ 2, .{ 3, 4 } } }, .{}, bw, 3), | |
| 1953 | 1002 | ); |
| 1954 | try std.testing.expectEqualStrings("", buf.items); | |
| 1955 | buf.clearRetainingCapacity(); | |
| 1003 | try std.testing.expectEqualStrings("", aw.getWritten()); | |
| 1004 | aw.clearRetainingCapacity(); | |
| 1956 | 1005 | |
| 1957 | 1006 | // Max depth passing on recursive type |
| 1958 | 1007 | { |
| 1959 | 1008 | const maybe_recurse = Recurse{ .r = &.{} }; |
| 1960 | try serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 2); | |
| 1961 | try std.testing.expectEqualStrings(".{ .r = .{} }", buf.items); | |
| 1962 | buf.clearRetainingCapacity(); | |
| 1009 | try serializeMaxDepth(maybe_recurse, .{}, bw, 2); | |
| 1010 | try std.testing.expectEqualStrings(".{ .r = .{} }", aw.getWritten()); | |
| 1011 | aw.clearRetainingCapacity(); | |
| 1963 | 1012 | } |
| 1964 | 1013 | |
| 1965 | 1014 | // Unchecked passing on recursive type |
| 1966 | 1015 | { |
| 1967 | 1016 | const maybe_recurse = Recurse{ .r = &.{} }; |
| 1968 | try serializeArbitraryDepth(maybe_recurse, .{}, buf.writer()); | |
| 1969 | try std.testing.expectEqualStrings(".{ .r = .{} }", buf.items); | |
| 1970 | buf.clearRetainingCapacity(); | |
| 1017 | try serializeArbitraryDepth(maybe_recurse, .{}, bw); | |
| 1018 | try std.testing.expectEqualStrings(".{ .r = .{} }", aw.getWritten()); | |
| 1019 | aw.clearRetainingCapacity(); | |
| 1971 | 1020 | } |
| 1972 | 1021 | |
| 1973 | 1022 | // Max depth failing on recursive type due to depth |
| ... | ... | @@ -1976,10 +1025,10 @@ test "std.zon depth limits" { |
| 1976 | 1025 | maybe_recurse.r = &.{.{ .r = &.{} }}; |
| 1977 | 1026 | try std.testing.expectError( |
| 1978 | 1027 | error.ExceededMaxDepth, |
| 1979 | serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 2), | |
| 1028 | serializeMaxDepth(maybe_recurse, .{}, bw, 2), | |
| 1980 | 1029 | ); |
| 1981 | try std.testing.expectEqualStrings("", buf.items); | |
| 1982 | buf.clearRetainingCapacity(); | |
| 1030 | try std.testing.expectEqualStrings("", aw.getWritten()); | |
| 1031 | aw.clearRetainingCapacity(); | |
| 1983 | 1032 | } |
| 1984 | 1033 | |
| 1985 | 1034 | // Same but for a slice |
| ... | ... | @@ -1989,23 +1038,23 @@ test "std.zon depth limits" { |
| 1989 | 1038 | |
| 1990 | 1039 | try std.testing.expectError( |
| 1991 | 1040 | error.ExceededMaxDepth, |
| 1992 | serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 2), | |
| 1041 | serializeMaxDepth(maybe_recurse, .{}, bw, 2), | |
| 1993 | 1042 | ); |
| 1994 | try std.testing.expectEqualStrings("", buf.items); | |
| 1995 | buf.clearRetainingCapacity(); | |
| 1043 | try std.testing.expectEqualStrings("", aw.getWritten()); | |
| 1044 | aw.clearRetainingCapacity(); | |
| 1996 | 1045 | |
| 1997 | var sz = serializer(buf.writer(), .{}); | |
| 1046 | var s: Serializer = .{ .writer = bw }; | |
| 1998 | 1047 | |
| 1999 | 1048 | try std.testing.expectError( |
| 2000 | 1049 | error.ExceededMaxDepth, |
| 2001 | sz.tupleMaxDepth(maybe_recurse, .{}, 2), | |
| 1050 | s.tupleMaxDepth(maybe_recurse, .{}, 2), | |
| 2002 | 1051 | ); |
| 2003 | try std.testing.expectEqualStrings("", buf.items); | |
| 2004 | buf.clearRetainingCapacity(); | |
| 1052 | try std.testing.expectEqualStrings("", aw.getWritten()); | |
| 1053 | aw.clearRetainingCapacity(); | |
| 2005 | 1054 | |
| 2006 | try sz.tupleArbitraryDepth(maybe_recurse, .{}); | |
| 2007 | try std.testing.expectEqualStrings(".{.{ .r = .{} }}", buf.items); | |
| 2008 | buf.clearRetainingCapacity(); | |
| 1055 | try s.tupleArbitraryDepth(maybe_recurse, .{}); | |
| 1056 | try std.testing.expectEqualStrings(".{.{ .r = .{} }}", aw.getWritten()); | |
| 1057 | aw.clearRetainingCapacity(); | |
| 2009 | 1058 | } |
| 2010 | 1059 | |
| 2011 | 1060 | // A slice succeeding |
| ... | ... | @@ -2013,19 +1062,19 @@ test "std.zon depth limits" { |
| 2013 | 1062 | var temp: [1]Recurse = .{.{ .r = &.{} }}; |
| 2014 | 1063 | const maybe_recurse: []const Recurse = &temp; |
| 2015 | 1064 | |
| 2016 | try serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 3); | |
| 2017 | try std.testing.expectEqualStrings(".{.{ .r = .{} }}", buf.items); | |
| 2018 | buf.clearRetainingCapacity(); | |
| 1065 | try serializeMaxDepth(maybe_recurse, .{}, bw, 3); | |
| 1066 | try std.testing.expectEqualStrings(".{.{ .r = .{} }}", aw.getWritten()); | |
| 1067 | aw.clearRetainingCapacity(); | |
| 2019 | 1068 | |
| 2020 | var sz = serializer(buf.writer(), .{}); | |
| 1069 | var s: Serializer = .{ .writer = bw }; | |
| 2021 | 1070 | |
| 2022 | try sz.tupleMaxDepth(maybe_recurse, .{}, 3); | |
| 2023 | try std.testing.expectEqualStrings(".{.{ .r = .{} }}", buf.items); | |
| 2024 | buf.clearRetainingCapacity(); | |
| 1071 | try s.tupleMaxDepth(maybe_recurse, .{}, 3); | |
| 1072 | try std.testing.expectEqualStrings(".{.{ .r = .{} }}", aw.getWritten()); | |
| 1073 | aw.clearRetainingCapacity(); | |
| 2025 | 1074 | |
| 2026 | try sz.tupleArbitraryDepth(maybe_recurse, .{}); | |
| 2027 | try std.testing.expectEqualStrings(".{.{ .r = .{} }}", buf.items); | |
| 2028 | buf.clearRetainingCapacity(); | |
| 1075 | try s.tupleArbitraryDepth(maybe_recurse, .{}); | |
| 1076 | try std.testing.expectEqualStrings(".{.{ .r = .{} }}", aw.getWritten()); | |
| 1077 | aw.clearRetainingCapacity(); | |
| 2029 | 1078 | } |
| 2030 | 1079 | |
| 2031 | 1080 | // Max depth failing on recursive type due to recursion |
| ... | ... | @@ -2036,46 +1085,46 @@ test "std.zon depth limits" { |
| 2036 | 1085 | |
| 2037 | 1086 | try std.testing.expectError( |
| 2038 | 1087 | error.ExceededMaxDepth, |
| 2039 | serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 128), | |
| 1088 | serializeMaxDepth(maybe_recurse, .{}, bw, 128), | |
| 2040 | 1089 | ); |
| 2041 | try std.testing.expectEqualStrings("", buf.items); | |
| 2042 | buf.clearRetainingCapacity(); | |
| 1090 | try std.testing.expectEqualStrings("", aw.getWritten()); | |
| 1091 | aw.clearRetainingCapacity(); | |
| 2043 | 1092 | |
| 2044 | var sz = serializer(buf.writer(), .{}); | |
| 1093 | var s: Serializer = .{ .writer = bw }; | |
| 2045 | 1094 | try std.testing.expectError( |
| 2046 | 1095 | error.ExceededMaxDepth, |
| 2047 | sz.tupleMaxDepth(maybe_recurse, .{}, 128), | |
| 1096 | s.tupleMaxDepth(maybe_recurse, .{}, 128), | |
| 2048 | 1097 | ); |
| 2049 | try std.testing.expectEqualStrings("", buf.items); | |
| 2050 | buf.clearRetainingCapacity(); | |
| 1098 | try std.testing.expectEqualStrings("", aw.getWritten()); | |
| 1099 | aw.clearRetainingCapacity(); | |
| 2051 | 1100 | } |
| 2052 | 1101 | |
| 2053 | 1102 | // Max depth on other parts of the lower level API |
| 2054 | 1103 | { |
| 2055 | var sz = serializer(buf.writer(), .{}); | |
| 1104 | var s: Serializer = .{ .writer = bw }; | |
| 2056 | 1105 | |
| 2057 | 1106 | const maybe_recurse: []const Recurse = &.{}; |
| 2058 | 1107 | |
| 2059 | try std.testing.expectError(error.ExceededMaxDepth, sz.valueMaxDepth(1, .{}, 0)); | |
| 2060 | try sz.valueMaxDepth(2, .{}, 1); | |
| 2061 | try sz.value(3, .{}); | |
| 2062 | try sz.valueArbitraryDepth(maybe_recurse, .{}); | |
| 1108 | try std.testing.expectError(error.ExceededMaxDepth, s.valueMaxDepth(1, .{}, 0)); | |
| 1109 | try s.valueMaxDepth(2, .{}, 1); | |
| 1110 | try s.value(3, .{}); | |
| 1111 | try s.valueArbitraryDepth(maybe_recurse, .{}); | |
| 2063 | 1112 | |
| 2064 | var s = try sz.beginStruct(.{}); | |
| 2065 | try std.testing.expectError(error.ExceededMaxDepth, s.fieldMaxDepth("a", 1, .{}, 0)); | |
| 2066 | try s.fieldMaxDepth("b", 4, .{}, 1); | |
| 2067 | try s.field("c", 5, .{}); | |
| 2068 | try s.fieldArbitraryDepth("d", maybe_recurse, .{}); | |
| 2069 | try s.end(); | |
| 1113 | var wip_struct = try s.beginStruct(.{}); | |
| 1114 | try std.testing.expectError(error.ExceededMaxDepth, wip_struct.fieldMaxDepth("a", 1, .{}, 0)); | |
| 1115 | try wip_struct.fieldMaxDepth("b", 4, .{}, 1); | |
| 1116 | try wip_struct.field("c", 5, .{}); | |
| 1117 | try wip_struct.fieldArbitraryDepth("d", maybe_recurse, .{}); | |
| 1118 | try wip_struct.end(); | |
| 2070 | 1119 | |
| 2071 | var t = try sz.beginTuple(.{}); | |
| 1120 | var t = try s.beginTuple(.{}); | |
| 2072 | 1121 | try std.testing.expectError(error.ExceededMaxDepth, t.fieldMaxDepth(1, .{}, 0)); |
| 2073 | 1122 | try t.fieldMaxDepth(6, .{}, 1); |
| 2074 | 1123 | try t.field(7, .{}); |
| 2075 | 1124 | try t.fieldArbitraryDepth(maybe_recurse, .{}); |
| 2076 | 1125 | try t.end(); |
| 2077 | 1126 | |
| 2078 | var a = try sz.beginTuple(.{}); | |
| 1127 | var a = try s.beginTuple(.{}); | |
| 2079 | 1128 | try std.testing.expectError(error.ExceededMaxDepth, a.fieldMaxDepth(1, .{}, 0)); |
| 2080 | 1129 | try a.fieldMaxDepth(8, .{}, 1); |
| 2081 | 1130 | try a.field(9, .{}); |
| ... | ... | @@ -2096,7 +1145,7 @@ test "std.zon depth limits" { |
| 2096 | 1145 | \\ 9, |
| 2097 | 1146 | \\ .{}, |
| 2098 | 1147 | \\} |
| 2099 | , buf.items); | |
| 1148 | , aw.getWritten()); | |
| 2100 | 1149 | } |
| 2101 | 1150 | } |
| 2102 | 1151 | |
| ... | ... | @@ -2192,42 +1241,42 @@ test "std.zon stringify primitives" { |
| 2192 | 1241 | } |
| 2193 | 1242 | |
| 2194 | 1243 | test "std.zon stringify ident" { |
| 2195 | var buf = std.ArrayList(u8).init(std.testing.allocator); | |
| 2196 | defer buf.deinit(); | |
| 2197 | var sz = serializer(buf.writer(), .{}); | |
| 1244 | var aw: Writer.Allocating = .init(std.testing.allocator); | |
| 1245 | var s: Serializer = .{ .writer = &aw.writer }; | |
| 1246 | defer aw.deinit(); | |
| 2198 | 1247 | |
| 2199 | 1248 | try expectSerializeEqual(".{ .a = 0 }", .{ .a = 0 }, .{}); |
| 2200 | try sz.ident("a"); | |
| 2201 | try std.testing.expectEqualStrings(".a", buf.items); | |
| 2202 | buf.clearRetainingCapacity(); | |
| 1249 | try s.ident("a"); | |
| 1250 | try std.testing.expectEqualStrings(".a", aw.getWritten()); | |
| 1251 | aw.clearRetainingCapacity(); | |
| 2203 | 1252 | |
| 2204 | try sz.ident("foo_1"); | |
| 2205 | try std.testing.expectEqualStrings(".foo_1", buf.items); | |
| 2206 | buf.clearRetainingCapacity(); | |
| 1253 | try s.ident("foo_1"); | |
| 1254 | try std.testing.expectEqualStrings(".foo_1", aw.getWritten()); | |
| 1255 | aw.clearRetainingCapacity(); | |
| 2207 | 1256 | |
| 2208 | try sz.ident("_foo_1"); | |
| 2209 | try std.testing.expectEqualStrings("._foo_1", buf.items); | |
| 2210 | buf.clearRetainingCapacity(); | |
| 1257 | try s.ident("_foo_1"); | |
| 1258 | try std.testing.expectEqualStrings("._foo_1", aw.getWritten()); | |
| 1259 | aw.clearRetainingCapacity(); | |
| 2211 | 1260 | |
| 2212 | try sz.ident("foo bar"); | |
| 2213 | try std.testing.expectEqualStrings(".@\"foo bar\"", buf.items); | |
| 2214 | buf.clearRetainingCapacity(); | |
| 1261 | try s.ident("foo bar"); | |
| 1262 | try std.testing.expectEqualStrings(".@\"foo bar\"", aw.getWritten()); | |
| 1263 | aw.clearRetainingCapacity(); | |
| 2215 | 1264 | |
| 2216 | try sz.ident("1foo"); | |
| 2217 | try std.testing.expectEqualStrings(".@\"1foo\"", buf.items); | |
| 2218 | buf.clearRetainingCapacity(); | |
| 1265 | try s.ident("1foo"); | |
| 1266 | try std.testing.expectEqualStrings(".@\"1foo\"", aw.getWritten()); | |
| 1267 | aw.clearRetainingCapacity(); | |
| 2219 | 1268 | |
| 2220 | try sz.ident("var"); | |
| 2221 | try std.testing.expectEqualStrings(".@\"var\"", buf.items); | |
| 2222 | buf.clearRetainingCapacity(); | |
| 1269 | try s.ident("var"); | |
| 1270 | try std.testing.expectEqualStrings(".@\"var\"", aw.getWritten()); | |
| 1271 | aw.clearRetainingCapacity(); | |
| 2223 | 1272 | |
| 2224 | try sz.ident("true"); | |
| 2225 | try std.testing.expectEqualStrings(".true", buf.items); | |
| 2226 | buf.clearRetainingCapacity(); | |
| 1273 | try s.ident("true"); | |
| 1274 | try std.testing.expectEqualStrings(".true", aw.getWritten()); | |
| 1275 | aw.clearRetainingCapacity(); | |
| 2227 | 1276 | |
| 2228 | try sz.ident("_"); | |
| 2229 | try std.testing.expectEqualStrings("._", buf.items); | |
| 2230 | buf.clearRetainingCapacity(); | |
| 1277 | try s.ident("_"); | |
| 1278 | try std.testing.expectEqualStrings("._", aw.getWritten()); | |
| 1279 | aw.clearRetainingCapacity(); | |
| 2231 | 1280 | |
| 2232 | 1281 | const Enum = enum { |
| 2233 | 1282 | @"foo bar", |
| ... | ... | @@ -2239,40 +1288,40 @@ test "std.zon stringify ident" { |
| 2239 | 1288 | } |
| 2240 | 1289 | |
| 2241 | 1290 | test "std.zon stringify as tuple" { |
| 2242 | var buf = std.ArrayList(u8).init(std.testing.allocator); | |
| 2243 | defer buf.deinit(); | |
| 2244 | var sz = serializer(buf.writer(), .{}); | |
| 1291 | var aw: Writer.Allocating = .init(std.testing.allocator); | |
| 1292 | var s: Serializer = .{ .writer = &aw.writer }; | |
| 1293 | defer aw.deinit(); | |
| 2245 | 1294 | |
| 2246 | 1295 | // Tuples |
| 2247 | try sz.tuple(.{ 1, 2 }, .{}); | |
| 2248 | try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items); | |
| 2249 | buf.clearRetainingCapacity(); | |
| 1296 | try s.tuple(.{ 1, 2 }, .{}); | |
| 1297 | try std.testing.expectEqualStrings(".{ 1, 2 }", aw.getWritten()); | |
| 1298 | aw.clearRetainingCapacity(); | |
| 2250 | 1299 | |
| 2251 | 1300 | // Slice |
| 2252 | try sz.tuple(@as([]const u8, &.{ 1, 2 }), .{}); | |
| 2253 | try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items); | |
| 2254 | buf.clearRetainingCapacity(); | |
| 1301 | try s.tuple(@as([]const u8, &.{ 1, 2 }), .{}); | |
| 1302 | try std.testing.expectEqualStrings(".{ 1, 2 }", aw.getWritten()); | |
| 1303 | aw.clearRetainingCapacity(); | |
| 2255 | 1304 | |
| 2256 | 1305 | // Array |
| 2257 | try sz.tuple([2]u8{ 1, 2 }, .{}); | |
| 2258 | try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items); | |
| 2259 | buf.clearRetainingCapacity(); | |
| 1306 | try s.tuple([2]u8{ 1, 2 }, .{}); | |
| 1307 | try std.testing.expectEqualStrings(".{ 1, 2 }", aw.getWritten()); | |
| 1308 | aw.clearRetainingCapacity(); | |
| 2260 | 1309 | } |
| 2261 | 1310 | |
| 2262 | 1311 | test "std.zon stringify as float" { |
| 2263 | var buf = std.ArrayList(u8).init(std.testing.allocator); | |
| 2264 | defer buf.deinit(); | |
| 2265 | var sz = serializer(buf.writer(), .{}); | |
| 1312 | var aw: Writer.Allocating = .init(std.testing.allocator); | |
| 1313 | var s: Serializer = .{ .writer = &aw.writer }; | |
| 1314 | defer aw.deinit(); | |
| 2266 | 1315 | |
| 2267 | 1316 | // Comptime float |
| 2268 | try sz.float(2.5); | |
| 2269 | try std.testing.expectEqualStrings("2.5", buf.items); | |
| 2270 | buf.clearRetainingCapacity(); | |
| 1317 | try s.float(2.5); | |
| 1318 | try std.testing.expectEqualStrings("2.5", aw.getWritten()); | |
| 1319 | aw.clearRetainingCapacity(); | |
| 2271 | 1320 | |
| 2272 | 1321 | // Sized float |
| 2273 | try sz.float(@as(f32, 2.5)); | |
| 2274 | try std.testing.expectEqualStrings("2.5", buf.items); | |
| 2275 | buf.clearRetainingCapacity(); | |
| 1322 | try s.float(@as(f32, 2.5)); | |
| 1323 | try std.testing.expectEqualStrings("2.5", aw.getWritten()); | |
| 1324 | aw.clearRetainingCapacity(); | |
| 2276 | 1325 | } |
| 2277 | 1326 | |
| 2278 | 1327 | test "std.zon stringify vector" { |
| ... | ... | @@ -2364,13 +1413,13 @@ test "std.zon pointers" { |
| 2364 | 1413 | } |
| 2365 | 1414 | |
| 2366 | 1415 | test "std.zon tuple/struct field" { |
| 2367 | var buf = std.ArrayList(u8).init(std.testing.allocator); | |
| 2368 | defer buf.deinit(); | |
| 2369 | var sz = serializer(buf.writer(), .{}); | |
| 1416 | var aw: Writer.Allocating = .init(std.testing.allocator); | |
| 1417 | var s: Serializer = .{ .writer = &aw.writer }; | |
| 1418 | defer aw.deinit(); | |
| 2370 | 1419 | |
| 2371 | 1420 | // Test on structs |
| 2372 | 1421 | { |
| 2373 | var root = try sz.beginStruct(.{}); | |
| 1422 | var root = try s.beginStruct(.{}); | |
| 2374 | 1423 | { |
| 2375 | 1424 | var tuple = try root.beginTupleField("foo", .{}); |
| 2376 | 1425 | try tuple.field(0, .{}); |
| ... | ... | @@ -2396,13 +1445,13 @@ test "std.zon tuple/struct field" { |
| 2396 | 1445 | \\ .b = 1, |
| 2397 | 1446 | \\ }, |
| 2398 | 1447 | \\} |
| 2399 | , buf.items); | |
| 2400 | buf.clearRetainingCapacity(); | |
| 1448 | , aw.getWritten()); | |
| 1449 | aw.clearRetainingCapacity(); | |
| 2401 | 1450 | } |
| 2402 | 1451 | |
| 2403 | 1452 | // Test on tuples |
| 2404 | 1453 | { |
| 2405 | var root = try sz.beginTuple(.{}); | |
| 1454 | var root = try s.beginTuple(.{}); | |
| 2406 | 1455 | { |
| 2407 | 1456 | var tuple = try root.beginTupleField(.{}); |
| 2408 | 1457 | try tuple.field(0, .{}); |
| ... | ... | @@ -2428,7 +1477,7 @@ test "std.zon tuple/struct field" { |
| 2428 | 1477 | \\ .b = 1, |
| 2429 | 1478 | \\ }, |
| 2430 | 1479 | \\} |
| 2431 | , buf.items); | |
| 2432 | buf.clearRetainingCapacity(); | |
| 1480 | , aw.getWritten()); | |
| 1481 | aw.clearRetainingCapacity(); | |
| 2433 | 1482 | } |
| 2434 | 1483 | } |
src/main.zig+6-3| ... | ... | @@ -344,8 +344,9 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 344 | 344 | } else if (mem.eql(u8, cmd, "targets")) { |
| 345 | 345 | dev.check(.targets_command); |
| 346 | 346 | const host = std.zig.resolveTargetQueryOrFatal(.{}); |
| 347 | const stdout = fs.File.stdout().deprecatedWriter(); | |
| 348 | return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, &host); | |
| 347 | var stdout_writer = fs.File.stdout().writer(&stdio_buffer); | |
| 348 | try @import("print_targets.zig").cmdTargets(arena, cmd_args, &stdout_writer.interface, &host); | |
| 349 | return stdout_writer.interface.flush(); | |
| 349 | 350 | } else if (mem.eql(u8, cmd, "version")) { |
| 350 | 351 | dev.check(.version_command); |
| 351 | 352 | try fs.File.stdout().writeAll(build_options.version ++ "\n"); |
| ... | ... | @@ -356,7 +357,9 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 356 | 357 | } else if (mem.eql(u8, cmd, "env")) { |
| 357 | 358 | dev.check(.env_command); |
| 358 | 359 | verifyLibcxxCorrectlyLinked(); |
| 359 | return @import("print_env.zig").cmdEnv(arena, cmd_args); | |
| 360 | var stdout_writer = fs.File.stdout().writer(&stdio_buffer); | |
| 361 | try @import("print_env.zig").cmdEnv(arena, &stdout_writer.interface); | |
| 362 | return stdout_writer.interface.flush(); | |
| 360 | 363 | } else if (mem.eql(u8, cmd, "reduce")) { |
| 361 | 364 | return jitCmd(gpa, arena, cmd_args, .{ |
| 362 | 365 | .cmd_name = "reduce", |
src/print_env.zig+14-35| ... | ... | @@ -4,8 +4,7 @@ const introspect = @import("introspect.zig"); |
| 4 | 4 | const Allocator = std.mem.Allocator; |
| 5 | 5 | const fatal = std.process.fatal; |
| 6 | 6 | |
| 7 | pub fn cmdEnv(arena: Allocator, args: []const []const u8) !void { | |
| 8 | _ = args; | |
| 7 | pub fn cmdEnv(arena: Allocator, out: *std.Io.Writer) !void { | |
| 9 | 8 | const cwd_path = try introspect.getResolvedCwd(arena); |
| 10 | 9 | const self_exe_path = try std.fs.selfExePathAlloc(arena); |
| 11 | 10 | |
| ... | ... | @@ -21,41 +20,21 @@ pub fn cmdEnv(arena: Allocator, args: []const []const u8) !void { |
| 21 | 20 | const host = try std.zig.system.resolveTargetQuery(.{}); |
| 22 | 21 | const triple = try host.zigTriple(arena); |
| 23 | 22 | |
| 24 | var bw = std.io.bufferedWriter(std.fs.File.stdout().deprecatedWriter()); | |
| 25 | const w = bw.writer(); | |
| 23 | var serializer: std.zon.Serializer = .{ .writer = out }; | |
| 24 | var root = try serializer.beginStruct(.{}); | |
| 26 | 25 | |
| 27 | var jws = std.json.writeStream(w, .{ .whitespace = .indent_1 }); | |
| 28 | ||
| 29 | try jws.beginObject(); | |
| 30 | ||
| 31 | try jws.objectField("zig_exe"); | |
| 32 | try jws.write(self_exe_path); | |
| 33 | ||
| 34 | try jws.objectField("lib_dir"); | |
| 35 | try jws.write(zig_lib_directory.path.?); | |
| 36 | ||
| 37 | try jws.objectField("std_dir"); | |
| 38 | try jws.write(zig_std_dir); | |
| 39 | ||
| 40 | try jws.objectField("global_cache_dir"); | |
| 41 | try jws.write(global_cache_dir); | |
| 42 | ||
| 43 | try jws.objectField("version"); | |
| 44 | try jws.write(build_options.version); | |
| 45 | ||
| 46 | try jws.objectField("target"); | |
| 47 | try jws.write(triple); | |
| 48 | ||
| 49 | try jws.objectField("env"); | |
| 50 | try jws.beginObject(); | |
| 26 | try root.field("zig_exe", self_exe_path, .{}); | |
| 27 | try root.field("lib_dir", zig_lib_directory.path.?, .{}); | |
| 28 | try root.field("std_dir", zig_std_dir, .{}); | |
| 29 | try root.field("global_cache_dir", global_cache_dir, .{}); | |
| 30 | try root.field("version", build_options.version, .{}); | |
| 31 | try root.field("target", triple, .{}); | |
| 32 | var env = try root.beginStructField("env", .{}); | |
| 51 | 33 | inline for (@typeInfo(std.zig.EnvVar).@"enum".fields) |field| { |
| 52 | try jws.objectField(field.name); | |
| 53 | try jws.write(try @field(std.zig.EnvVar, field.name).get(arena)); | |
| 34 | try env.field(field.name, try @field(std.zig.EnvVar, field.name).get(arena), .{}); | |
| 54 | 35 | } |
| 55 | try jws.endObject(); | |
| 56 | ||
| 57 | try jws.endObject(); | |
| 58 | try w.writeByte('\n'); | |
| 36 | try env.end(); | |
| 37 | try root.end(); | |
| 59 | 38 | |
| 60 | try bw.flush(); | |
| 39 | try out.writeByte('\n'); | |
| 61 | 40 | } |
src/print_targets.zig+4-8| ... | ... | @@ -14,8 +14,7 @@ const introspect = @import("introspect.zig"); |
| 14 | 14 | pub fn cmdTargets( |
| 15 | 15 | allocator: Allocator, |
| 16 | 16 | args: []const []const u8, |
| 17 | /// Output stream | |
| 18 | stdout: anytype, | |
| 17 | out: *std.Io.Writer, | |
| 19 | 18 | native_target: *const Target, |
| 20 | 19 | ) !void { |
| 21 | 20 | _ = args; |
| ... | ... | @@ -38,12 +37,10 @@ pub fn cmdTargets( |
| 38 | 37 | const glibc_abi = try glibc.loadMetaData(allocator, abilists_contents); |
| 39 | 38 | defer glibc_abi.destroy(allocator); |
| 40 | 39 | |
| 41 | var bw = io.bufferedWriter(stdout); | |
| 42 | const w = bw.writer(); | |
| 43 | var sz = std.zon.stringify.serializer(w, .{}); | |
| 40 | var serializer: std.zon.Serializer = .{ .writer = out }; | |
| 44 | 41 | |
| 45 | 42 | { |
| 46 | var root_obj = try sz.beginStruct(.{}); | |
| 43 | var root_obj = try serializer.beginStruct(.{}); | |
| 47 | 44 | |
| 48 | 45 | try root_obj.field("arch", meta.fieldNames(Target.Cpu.Arch), .{}); |
| 49 | 46 | try root_obj.field("os", meta.fieldNames(Target.Os.Tag), .{}); |
| ... | ... | @@ -136,6 +133,5 @@ pub fn cmdTargets( |
| 136 | 133 | try root_obj.end(); |
| 137 | 134 | } |
| 138 | 135 | |
| 139 | try w.writeByte('\n'); | |
| 140 | return bw.flush(); | |
| 136 | try out.writeByte('\n'); | |
| 141 | 137 | } |
src/translate_c.zig+1-1| ... | ... | @@ -3338,7 +3338,7 @@ fn transPredefinedExpr(c: *Context, scope: *Scope, expr: *const clang.Predefined |
| 3338 | 3338 | |
| 3339 | 3339 | fn transCreateCharLitNode(c: *Context, narrow: bool, val: u32) TransError!Node { |
| 3340 | 3340 | return Tag.char_literal.create(c.arena, if (narrow) |
| 3341 | try std.fmt.allocPrint(c.arena, "'{f}'", .{std.zig.fmtChar(&.{@as(u8, @intCast(val))})}) | |
| 3341 | try std.fmt.allocPrint(c.arena, "'{f}'", .{std.zig.fmtChar(@intCast(val))}) | |
| 3342 | 3342 | else |
| 3343 | 3343 | try std.fmt.allocPrint(c.arena, "'\\u{{{x}}}'", .{val})); |
| 3344 | 3344 | } |