| author | |
| committer | |
| log | c30df072bde3794ea5f3794d93f69e13640c6b7a |
| tree | 832ab5f7947e9dcc1d8d4fb792ce240c773b0cd0 |
| parent | a288266f3310e9ba98456c5e968f8ce434be6cc7 |
also do a little bit of namespace cleanup14 files changed, 2921 insertions(+), 3245 deletions(-)
lib/std/json.zig+59-44| ... | @@ -10,8 +10,8 @@ | ... | @@ -10,8 +10,8 @@ |
| 10 | //! The high-level `stringify` serializes a Zig or `Value` type into JSON. | 10 | //! The high-level `stringify` serializes a Zig or `Value` type into JSON. |
| 11 | 11 | ||
| 12 | const builtin = @import("builtin"); | 12 | const builtin = @import("builtin"); |
| 13 | const testing = @import("std").testing; | 13 | const std = @import("std"); |
| 14 | const ArrayList = @import("std").ArrayList; | 14 | const testing = std.testing; |
| 15 | 15 | ||
| 16 | test Scanner { | 16 | test Scanner { |
| 17 | var scanner = Scanner.initCompleteInput(testing.allocator, "{\"foo\": 123}\n"); | 17 | var scanner = Scanner.initCompleteInput(testing.allocator, "{\"foo\": 123}\n"); |
| ... | @@ -41,11 +41,13 @@ test Value { | ... | @@ -41,11 +41,13 @@ test Value { |
| 41 | try testing.expectEqualSlices(u8, "goes", parsed.value.object.get("anything").?.string); | 41 | try testing.expectEqualSlices(u8, "goes", parsed.value.object.get("anything").?.string); |
| 42 | } | 42 | } |
| 43 | 43 | ||
| 44 | test writeStream { | 44 | test Stringify { |
| 45 | var out = ArrayList(u8).init(testing.allocator); | 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 | defer out.deinit(); | 50 | defer out.deinit(); |
| 47 | var write_stream = writeStream(out.writer(), .{ .whitespace = .indent_2 }); | ||
| 48 | defer write_stream.deinit(); | ||
| 49 | try write_stream.beginObject(); | 51 | try write_stream.beginObject(); |
| 50 | try write_stream.objectField("foo"); | 52 | try write_stream.objectField("foo"); |
| 51 | try write_stream.write(123); | 53 | try write_stream.write(123); |
| ... | @@ -55,16 +57,7 @@ test writeStream { | ... | @@ -55,16 +57,7 @@ test writeStream { |
| 55 | \\ "foo": 123 | 57 | \\ "foo": 123 |
| 56 | \\} | 58 | \\} |
| 57 | ; | 59 | ; |
| 58 | try testing.expectEqualSlices(u8, expected, out.items); | 60 | try testing.expectEqualSlices(u8, expected, out.getWritten()); |
| 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); | ||
| 68 | } | 61 | } |
| 69 | 62 | ||
| 70 | pub const ObjectMap = @import("json/dynamic.zig").ObjectMap; | 63 | pub const ObjectMap = @import("json/dynamic.zig").ObjectMap; |
| ... | @@ -73,18 +66,18 @@ pub const Value = @import("json/dynamic.zig").Value; | ... | @@ -73,18 +66,18 @@ pub const Value = @import("json/dynamic.zig").Value; |
| 73 | 66 | ||
| 74 | pub const ArrayHashMap = @import("json/hashmap.zig").ArrayHashMap; | 67 | pub const ArrayHashMap = @import("json/hashmap.zig").ArrayHashMap; |
| 75 | 68 | ||
| 76 | pub const validate = @import("json/scanner.zig").validate; | 69 | pub const Scanner = @import("json/Scanner.zig"); |
| 77 | pub const Error = @import("json/scanner.zig").Error; | 70 | pub const validate = Scanner.validate; |
| 78 | pub const reader = @import("json/scanner.zig").reader; | 71 | pub const Error = Scanner.Error; |
| 79 | pub const default_buffer_size = @import("json/scanner.zig").default_buffer_size; | 72 | pub const reader = Scanner.reader; |
| 80 | pub const Token = @import("json/scanner.zig").Token; | 73 | pub const default_buffer_size = Scanner.default_buffer_size; |
| 81 | pub const TokenType = @import("json/scanner.zig").TokenType; | 74 | pub const Token = Scanner.Token; |
| 82 | pub const Diagnostics = @import("json/scanner.zig").Diagnostics; | 75 | pub const TokenType = Scanner.TokenType; |
| 83 | pub const AllocWhen = @import("json/scanner.zig").AllocWhen; | 76 | pub const Diagnostics = Scanner.Diagnostics; |
| 84 | pub const default_max_value_len = @import("json/scanner.zig").default_max_value_len; | 77 | pub const AllocWhen = Scanner.AllocWhen; |
| 85 | pub const Reader = @import("json/scanner.zig").Reader; | 78 | pub const default_max_value_len = Scanner.default_max_value_len; |
| 86 | pub const Scanner = @import("json/scanner.zig").Scanner; | 79 | pub const Reader = Scanner.Reader; |
| 87 | pub const isNumberFormattedLikeAnInteger = @import("json/scanner.zig").isNumberFormattedLikeAnInteger; | 80 | pub const isNumberFormattedLikeAnInteger = Scanner.isNumberFormattedLikeAnInteger; |
| 88 | 81 | ||
| 89 | pub const ParseOptions = @import("json/static.zig").ParseOptions; | 82 | pub const ParseOptions = @import("json/static.zig").ParseOptions; |
| 90 | pub const Parsed = @import("json/static.zig").Parsed; | 83 | pub const Parsed = @import("json/static.zig").Parsed; |
| ... | @@ -99,27 +92,49 @@ pub const innerParseFromValue = @import("json/static.zig").innerParseFromValue; | ... | @@ -99,27 +92,49 @@ pub const innerParseFromValue = @import("json/static.zig").innerParseFromValue; |
| 99 | pub const ParseError = @import("json/static.zig").ParseError; | 92 | pub const ParseError = @import("json/static.zig").ParseError; |
| 100 | pub const ParseFromValueError = @import("json/static.zig").ParseFromValueError; | 93 | pub const ParseFromValueError = @import("json/static.zig").ParseFromValueError; |
| 101 | 94 | ||
| 102 | pub const StringifyOptions = @import("json/stringify.zig").StringifyOptions; | 95 | pub const Stringify = @import("json/Stringify.zig"); |
| 103 | pub const stringify = @import("json/stringify.zig").stringify; | 96 | |
| 104 | pub const stringifyMaxDepth = @import("json/stringify.zig").stringifyMaxDepth; | 97 | /// Returns a formatter that formats the given value using stringify. |
| 105 | pub const stringifyArbitraryDepth = @import("json/stringify.zig").stringifyArbitraryDepth; | 98 | pub fn fmt(value: anytype, options: Stringify.Options) Formatter(@TypeOf(value)) { |
| 106 | pub const stringifyAlloc = @import("json/stringify.zig").stringifyAlloc; | 99 | return Formatter(@TypeOf(value)){ .value = value, .options = options }; |
| 107 | pub const writeStream = @import("json/stringify.zig").writeStream; | 100 | } |
| 108 | pub const writeStreamMaxDepth = @import("json/stringify.zig").writeStreamMaxDepth; | 101 | |
| 109 | pub const writeStreamArbitraryDepth = @import("json/stringify.zig").writeStreamArbitraryDepth; | 102 | test fmt { |
| 110 | pub const WriteStream = @import("json/stringify.zig").WriteStream; | 103 | const expectFmt = std.testing.expectFmt; |
| 111 | pub const encodeJsonString = @import("json/stringify.zig").encodeJsonString; | 104 | try expectFmt("123", "{f}", .{fmt(@as(u32, 123), .{})}); |
| 112 | pub const encodeJsonStringChars = @import("json/stringify.zig").encodeJsonStringChars; | 105 | try expectFmt( |
| 113 | 106 | \\{"num":927,"msg":"hello","sub":{"mybool":true}} | |
| 114 | pub const Formatter = @import("json/fmt.zig").Formatter; | 107 | , "{f}", .{fmt(struct { |
| 115 | pub const fmt = @import("json/fmt.zig").fmt; | 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 | test { | 132 | test { |
| 118 | _ = @import("json/test.zig"); | 133 | _ = @import("json/test.zig"); |
| 119 | _ = @import("json/scanner.zig"); | 134 | _ = Scanner; |
| 120 | _ = @import("json/dynamic.zig"); | 135 | _ = @import("json/dynamic.zig"); |
| 121 | _ = @import("json/hashmap.zig"); | 136 | _ = @import("json/hashmap.zig"); |
| 122 | _ = @import("json/static.zig"); | 137 | _ = @import("json/static.zig"); |
| 123 | _ = @import("json/stringify.zig"); | 138 | _ = Stringify; |
| 124 | _ = @import("json/JSONTestSuite_test.zig"); | 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,17 +4,12 @@ const ArenaAllocator = std.heap.ArenaAllocator; |
| 4 | const ArrayList = std.ArrayList; | 4 | const ArrayList = std.ArrayList; |
| 5 | const StringArrayHashMap = std.StringArrayHashMap; | 5 | const StringArrayHashMap = std.StringArrayHashMap; |
| 6 | const Allocator = std.mem.Allocator; | 6 | const Allocator = std.mem.Allocator; |
| 7 | 7 | const json = std.json; | |
| 8 | const StringifyOptions = @import("./stringify.zig").StringifyOptions; | ||
| 9 | const stringify = @import("./stringify.zig").stringify; | ||
| 10 | 8 | ||
| 11 | const ParseOptions = @import("./static.zig").ParseOptions; | 9 | const ParseOptions = @import("./static.zig").ParseOptions; |
| 12 | const ParseError = @import("./static.zig").ParseError; | 10 | const ParseError = @import("./static.zig").ParseError; |
| 13 | 11 | ||
| 14 | const JsonScanner = @import("./scanner.zig").Scanner; | 12 | const isNumberFormattedLikeAnInteger = @import("Scanner.zig").isNumberFormattedLikeAnInteger; |
| 15 | const AllocWhen = @import("./scanner.zig").AllocWhen; | ||
| 16 | const Token = @import("./scanner.zig").Token; | ||
| 17 | const isNumberFormattedLikeAnInteger = @import("./scanner.zig").isNumberFormattedLikeAnInteger; | ||
| 18 | 13 | ||
| 19 | pub const ObjectMap = StringArrayHashMap(Value); | 14 | pub const ObjectMap = StringArrayHashMap(Value); |
| 20 | pub const Array = ArrayList(Value); | 15 | pub const Array = ArrayList(Value); |
| ... | @@ -52,12 +47,11 @@ pub const Value = union(enum) { | ... | @@ -52,12 +47,11 @@ pub const Value = union(enum) { |
| 52 | } | 47 | } |
| 53 | } | 48 | } |
| 54 | 49 | ||
| 55 | pub fn dump(self: Value) void { | 50 | pub fn dump(v: Value) void { |
| 56 | std.debug.lockStdErr(); | 51 | const w = std.debug.lockStderrWriter(&.{}); |
| 57 | defer std.debug.unlockStdErr(); | 52 | defer std.debug.unlockStderrWriter(); |
| 58 | 53 | ||
| 59 | const stderr = std.fs.File.stderr().deprecatedWriter(); | 54 | json.Stringify.value(v, .{}, w) catch return; |
| 60 | stringify(self, .{}, stderr) catch return; | ||
| 61 | } | 55 | } |
| 62 | 56 | ||
| 63 | pub fn jsonStringify(value: @This(), jws: anytype) !void { | 57 | pub fn jsonStringify(value: @This(), jws: anytype) !void { |
lib/std/json/dynamic_test.zig+18-22| ... | @@ -1,8 +1,10 @@ | ... | @@ -1,8 +1,10 @@ |
| 1 | const std = @import("std"); | 1 | const std = @import("std"); |
| 2 | const json = std.json; | ||
| 2 | const mem = std.mem; | 3 | const mem = std.mem; |
| 3 | const testing = std.testing; | 4 | const testing = std.testing; |
| 4 | const ArenaAllocator = std.heap.ArenaAllocator; | 5 | const ArenaAllocator = std.heap.ArenaAllocator; |
| 5 | const Allocator = std.mem.Allocator; | 6 | const Allocator = std.mem.Allocator; |
| 7 | const Writer = std.io.Writer; | ||
| 6 | 8 | ||
| 7 | const ObjectMap = @import("dynamic.zig").ObjectMap; | 9 | const ObjectMap = @import("dynamic.zig").ObjectMap; |
| 8 | const Array = @import("dynamic.zig").Array; | 10 | const Array = @import("dynamic.zig").Array; |
| ... | @@ -14,8 +16,7 @@ const parseFromTokenSource = @import("static.zig").parseFromTokenSource; | ... | @@ -14,8 +16,7 @@ const parseFromTokenSource = @import("static.zig").parseFromTokenSource; |
| 14 | const parseFromValueLeaky = @import("static.zig").parseFromValueLeaky; | 16 | const parseFromValueLeaky = @import("static.zig").parseFromValueLeaky; |
| 15 | const ParseOptions = @import("static.zig").ParseOptions; | 17 | const ParseOptions = @import("static.zig").ParseOptions; |
| 16 | 18 | ||
| 17 | const jsonReader = @import("scanner.zig").reader; | 19 | const Scanner = @import("Scanner.zig"); |
| 18 | const JsonReader = @import("scanner.zig").Reader; | ||
| 19 | 20 | ||
| 20 | test "json.parser.dynamic" { | 21 | test "json.parser.dynamic" { |
| 21 | const s = | 22 | const s = |
| ... | @@ -70,14 +71,10 @@ test "json.parser.dynamic" { | ... | @@ -70,14 +71,10 @@ test "json.parser.dynamic" { |
| 70 | try testing.expect(mem.eql(u8, large_int.number_string, "18446744073709551615")); | 71 | try testing.expect(mem.eql(u8, large_int.number_string, "18446744073709551615")); |
| 71 | } | 72 | } |
| 72 | 73 | ||
| 73 | const writeStream = @import("./stringify.zig").writeStream; | ||
| 74 | test "write json then parse it" { | 74 | test "write json then parse it" { |
| 75 | var out_buffer: [1000]u8 = undefined; | 75 | var out_buffer: [1000]u8 = undefined; |
| 76 | 76 | var fixed_writer: Writer = .fixed(&out_buffer); | |
| 77 | var fixed_buffer_stream = std.io.fixedBufferStream(&out_buffer); | 77 | var jw: json.Stringify = .{ .writer = &fixed_writer, .options = .{} }; |
| 78 | const out_stream = fixed_buffer_stream.writer(); | ||
| 79 | var jw = writeStream(out_stream, .{}); | ||
| 80 | defer jw.deinit(); | ||
| 81 | 78 | ||
| 82 | try jw.beginObject(); | 79 | try jw.beginObject(); |
| 83 | 80 | ||
| ... | @@ -101,8 +98,8 @@ test "write json then parse it" { | ... | @@ -101,8 +98,8 @@ test "write json then parse it" { |
| 101 | 98 | ||
| 102 | try jw.endObject(); | 99 | try jw.endObject(); |
| 103 | 100 | ||
| 104 | fixed_buffer_stream = std.io.fixedBufferStream(fixed_buffer_stream.getWritten()); | 101 | var fbs: std.Io.Reader = .fixed(fixed_writer.buffered()); |
| 105 | var json_reader = jsonReader(testing.allocator, fixed_buffer_stream.reader()); | 102 | var json_reader: Scanner.Reader = .init(testing.allocator, &fbs); |
| 106 | defer json_reader.deinit(); | 103 | defer json_reader.deinit(); |
| 107 | var parsed = try parseFromTokenSource(Value, testing.allocator, &json_reader, .{}); | 104 | var parsed = try parseFromTokenSource(Value, testing.allocator, &json_reader, .{}); |
| 108 | defer parsed.deinit(); | 105 | defer parsed.deinit(); |
| ... | @@ -242,10 +239,9 @@ test "Value.jsonStringify" { | ... | @@ -242,10 +239,9 @@ test "Value.jsonStringify" { |
| 242 | .{ .object = obj }, | 239 | .{ .object = obj }, |
| 243 | }; | 240 | }; |
| 244 | var buffer: [0x1000]u8 = undefined; | 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 }); | 244 | var jw: json.Stringify = .{ .writer = &fixed_writer, .options = .{ .whitespace = .indent_1 } }; |
| 248 | defer jw.deinit(); | ||
| 249 | try jw.write(array); | 245 | try jw.write(array); |
| 250 | 246 | ||
| 251 | const expected = | 247 | const expected = |
| ... | @@ -266,7 +262,7 @@ test "Value.jsonStringify" { | ... | @@ -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 | test "parseFromValue(std.json.Value,...)" { | 268 | test "parseFromValue(std.json.Value,...)" { |
| ... | @@ -334,8 +330,8 @@ test "polymorphic parsing" { | ... | @@ -334,8 +330,8 @@ test "polymorphic parsing" { |
| 334 | test "long object value" { | 330 | test "long object value" { |
| 335 | const value = "01234567890123456789"; | 331 | const value = "01234567890123456789"; |
| 336 | const doc = "{\"key\":\"" ++ value ++ "\"}"; | 332 | const doc = "{\"key\":\"" ++ value ++ "\"}"; |
| 337 | var fbs = std.io.fixedBufferStream(doc); | 333 | var fbs: std.Io.Reader = .fixed(doc); |
| 338 | var reader = smallBufferJsonReader(testing.allocator, fbs.reader()); | 334 | var reader = smallBufferJsonReader(testing.allocator, &fbs); |
| 339 | defer reader.deinit(); | 335 | defer reader.deinit(); |
| 340 | var parsed = try parseFromTokenSource(Value, testing.allocator, &reader, .{}); | 336 | var parsed = try parseFromTokenSource(Value, testing.allocator, &reader, .{}); |
| 341 | defer parsed.deinit(); | 337 | defer parsed.deinit(); |
| ... | @@ -367,8 +363,8 @@ test "many object keys" { | ... | @@ -367,8 +363,8 @@ test "many object keys" { |
| 367 | \\ "k5": "v5" | 363 | \\ "k5": "v5" |
| 368 | \\} | 364 | \\} |
| 369 | ; | 365 | ; |
| 370 | var fbs = std.io.fixedBufferStream(doc); | 366 | var fbs: std.Io.Reader = .fixed(doc); |
| 371 | var reader = smallBufferJsonReader(testing.allocator, fbs.reader()); | 367 | var reader = smallBufferJsonReader(testing.allocator, &fbs); |
| 372 | defer reader.deinit(); | 368 | defer reader.deinit(); |
| 373 | var parsed = try parseFromTokenSource(Value, testing.allocator, &reader, .{}); | 369 | var parsed = try parseFromTokenSource(Value, testing.allocator, &reader, .{}); |
| 374 | defer parsed.deinit(); | 370 | defer parsed.deinit(); |
| ... | @@ -382,8 +378,8 @@ test "many object keys" { | ... | @@ -382,8 +378,8 @@ test "many object keys" { |
| 382 | 378 | ||
| 383 | test "negative zero" { | 379 | test "negative zero" { |
| 384 | const doc = "-0"; | 380 | const doc = "-0"; |
| 385 | var fbs = std.io.fixedBufferStream(doc); | 381 | var fbs: std.Io.Reader = .fixed(doc); |
| 386 | var reader = smallBufferJsonReader(testing.allocator, fbs.reader()); | 382 | var reader = smallBufferJsonReader(testing.allocator, &fbs); |
| 387 | defer reader.deinit(); | 383 | defer reader.deinit(); |
| 388 | var parsed = try parseFromTokenSource(Value, testing.allocator, &reader, .{}); | 384 | var parsed = try parseFromTokenSource(Value, testing.allocator, &reader, .{}); |
| 389 | defer parsed.deinit(); | 385 | defer parsed.deinit(); |
| ... | @@ -391,6 +387,6 @@ test "negative zero" { | ... | @@ -391,6 +387,6 @@ test "negative zero" { |
| 391 | try testing.expect(std.math.isNegativeZero(parsed.value.float)); | 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)) { | 390 | fn smallBufferJsonReader(allocator: Allocator, io_reader: anytype) Scanner.Reader { |
| 395 | return JsonReader(16, @TypeOf(io_reader)).init(allocator, io_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,4 +1,5 @@ |
| 1 | const std = @import("std"); | 1 | const std = @import("std"); |
| 2 | const json = std.json; | ||
| 2 | const testing = std.testing; | 3 | const testing = std.testing; |
| 3 | 4 | ||
| 4 | const ArrayHashMap = @import("hashmap.zig").ArrayHashMap; | 5 | const ArrayHashMap = @import("hashmap.zig").ArrayHashMap; |
| ... | @@ -7,10 +8,9 @@ const parseFromSlice = @import("static.zig").parseFromSlice; | ... | @@ -7,10 +8,9 @@ const parseFromSlice = @import("static.zig").parseFromSlice; |
| 7 | const parseFromSliceLeaky = @import("static.zig").parseFromSliceLeaky; | 8 | const parseFromSliceLeaky = @import("static.zig").parseFromSliceLeaky; |
| 8 | const parseFromTokenSource = @import("static.zig").parseFromTokenSource; | 9 | const parseFromTokenSource = @import("static.zig").parseFromTokenSource; |
| 9 | const parseFromValue = @import("static.zig").parseFromValue; | 10 | const parseFromValue = @import("static.zig").parseFromValue; |
| 10 | const stringifyAlloc = @import("stringify.zig").stringifyAlloc; | ||
| 11 | const Value = @import("dynamic.zig").Value; | 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 | const T = struct { | 15 | const T = struct { |
| 16 | i: i32, | 16 | i: i32, |
| ... | @@ -39,8 +39,8 @@ test "parse json hashmap while streaming" { | ... | @@ -39,8 +39,8 @@ test "parse json hashmap while streaming" { |
| 39 | \\ "xyz": {"i": 1, "s": "w"} | 39 | \\ "xyz": {"i": 1, "s": "w"} |
| 40 | \\} | 40 | \\} |
| 41 | ; | 41 | ; |
| 42 | var stream = std.io.fixedBufferStream(doc); | 42 | var stream: std.Io.Reader = .fixed(doc); |
| 43 | var json_reader = jsonReader(testing.allocator, stream.reader()); | 43 | var json_reader: Scanner.Reader = .init(testing.allocator, &stream); |
| 44 | 44 | ||
| 45 | var parsed = try parseFromTokenSource( | 45 | var parsed = try parseFromTokenSource( |
| 46 | ArrayHashMap(T), | 46 | ArrayHashMap(T), |
| ... | @@ -89,7 +89,7 @@ test "stringify json hashmap" { | ... | @@ -89,7 +89,7 @@ test "stringify json hashmap" { |
| 89 | var value = ArrayHashMap(T){}; | 89 | var value = ArrayHashMap(T){}; |
| 90 | defer value.deinit(testing.allocator); | 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 | defer testing.allocator.free(doc); | 93 | defer testing.allocator.free(doc); |
| 94 | try testing.expectEqualStrings("{}", doc); | 94 | try testing.expectEqualStrings("{}", doc); |
| 95 | } | 95 | } |
| ... | @@ -98,7 +98,7 @@ test "stringify json hashmap" { | ... | @@ -98,7 +98,7 @@ test "stringify json hashmap" { |
| 98 | try value.map.put(testing.allocator, "xyz", .{ .i = 1, .s = "w" }); | 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 | defer testing.allocator.free(doc); | 102 | defer testing.allocator.free(doc); |
| 103 | try testing.expectEqualStrings( | 103 | try testing.expectEqualStrings( |
| 104 | \\{"abc":{"i":0,"s":"d"},"xyz":{"i":1,"s":"w"}} | 104 | \\{"abc":{"i":0,"s":"d"},"xyz":{"i":1,"s":"w"}} |
| ... | @@ -107,7 +107,7 @@ test "stringify json hashmap" { | ... | @@ -107,7 +107,7 @@ test "stringify json hashmap" { |
| 107 | 107 | ||
| 108 | try testing.expect(value.map.swapRemove("abc")); | 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 | defer testing.allocator.free(doc); | 111 | defer testing.allocator.free(doc); |
| 112 | try testing.expectEqualStrings( | 112 | try testing.expectEqualStrings( |
| 113 | \\{"xyz":{"i":1,"s":"w"}} | 113 | \\{"xyz":{"i":1,"s":"w"}} |
| ... | @@ -116,7 +116,7 @@ test "stringify json hashmap" { | ... | @@ -116,7 +116,7 @@ test "stringify json hashmap" { |
| 116 | 116 | ||
| 117 | try testing.expect(value.map.swapRemove("xyz")); | 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 | defer testing.allocator.free(doc); | 120 | defer testing.allocator.free(doc); |
| 121 | try testing.expectEqualStrings("{}", doc); | 121 | try testing.expectEqualStrings("{}", doc); |
| 122 | } | 122 | } |
| ... | @@ -129,7 +129,7 @@ test "stringify json hashmap whitespace" { | ... | @@ -129,7 +129,7 @@ test "stringify json hashmap whitespace" { |
| 129 | try value.map.put(testing.allocator, "xyz", .{ .i = 1, .s = "w" }); | 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 | defer testing.allocator.free(doc); | 133 | defer testing.allocator.free(doc); |
| 134 | try testing.expectEqualStrings( | 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,13 +1,11 @@ |
| 1 | const std = @import("std"); | 1 | const std = @import("std"); |
| 2 | const JsonScanner = @import("./scanner.zig").Scanner; | 2 | const Scanner = @import("Scanner.zig"); |
| 3 | const jsonReader = @import("./scanner.zig").reader; | 3 | const Token = Scanner.Token; |
| 4 | const JsonReader = @import("./scanner.zig").Reader; | 4 | const TokenType = Scanner.TokenType; |
| 5 | const Token = @import("./scanner.zig").Token; | 5 | const Diagnostics = Scanner.Diagnostics; |
| 6 | const TokenType = @import("./scanner.zig").TokenType; | 6 | const Error = Scanner.Error; |
| 7 | const Diagnostics = @import("./scanner.zig").Diagnostics; | 7 | const validate = Scanner.validate; |
| 8 | const Error = @import("./scanner.zig").Error; | 8 | const isNumberFormattedLikeAnInteger = Scanner.isNumberFormattedLikeAnInteger; |
| 9 | const validate = @import("./scanner.zig").validate; | ||
| 10 | const isNumberFormattedLikeAnInteger = @import("./scanner.zig").isNumberFormattedLikeAnInteger; | ||
| 11 | 9 | ||
| 12 | const example_document_str = | 10 | const example_document_str = |
| 13 | \\{ | 11 | \\{ |
| ... | @@ -36,7 +34,7 @@ fn expectPeekNext(scanner_or_reader: anytype, expected_token_type: TokenType, ex | ... | @@ -36,7 +34,7 @@ fn expectPeekNext(scanner_or_reader: anytype, expected_token_type: TokenType, ex |
| 36 | } | 34 | } |
| 37 | 35 | ||
| 38 | test "token" { | 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 | defer scanner.deinit(); | 38 | defer scanner.deinit(); |
| 41 | 39 | ||
| 42 | try expectNext(&scanner, .object_begin); | 40 | try expectNext(&scanner, .object_begin); |
| ... | @@ -138,23 +136,25 @@ fn testAllTypes(source: anytype, large_buffer: bool) !void { | ... | @@ -138,23 +136,25 @@ fn testAllTypes(source: anytype, large_buffer: bool) !void { |
| 138 | } | 136 | } |
| 139 | 137 | ||
| 140 | test "peek all types" { | 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 | defer scanner.deinit(); | 140 | defer scanner.deinit(); |
| 143 | try testAllTypes(&scanner, true); | 141 | try testAllTypes(&scanner, true); |
| 144 | 142 | ||
| 145 | var stream = std.io.fixedBufferStream(all_types_test_case); | 143 | var stream: std.Io.Reader = .fixed(all_types_test_case); |
| 146 | var json_reader = jsonReader(std.testing.allocator, stream.reader()); | 144 | var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream); |
| 147 | defer json_reader.deinit(); | 145 | defer json_reader.deinit(); |
| 148 | try testAllTypes(&json_reader, true); | 146 | try testAllTypes(&json_reader, true); |
| 149 | 147 | ||
| 150 | var tiny_stream = std.io.fixedBufferStream(all_types_test_case); | 148 | var tiny_buffer: [1]u8 = undefined; |
| 151 | var tiny_json_reader = JsonReader(1, @TypeOf(tiny_stream.reader())).init(std.testing.allocator, tiny_stream.reader()); | 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 | defer tiny_json_reader.deinit(); | 152 | defer tiny_json_reader.deinit(); |
| 153 | try testAllTypes(&tiny_json_reader, false); | 153 | try testAllTypes(&tiny_json_reader, false); |
| 154 | } | 154 | } |
| 155 | 155 | ||
| 156 | test "token mismatched close" { | 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 | defer scanner.deinit(); | 158 | defer scanner.deinit(); |
| 159 | try expectNext(&scanner, .array_begin); | 159 | try expectNext(&scanner, .array_begin); |
| 160 | try expectNext(&scanner, Token{ .number = "102" }); | 160 | try expectNext(&scanner, Token{ .number = "102" }); |
| ... | @@ -164,15 +164,15 @@ test "token mismatched close" { | ... | @@ -164,15 +164,15 @@ test "token mismatched close" { |
| 164 | } | 164 | } |
| 165 | 165 | ||
| 166 | test "token premature object close" { | 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 | defer scanner.deinit(); | 168 | defer scanner.deinit(); |
| 169 | try expectNext(&scanner, .object_begin); | 169 | try expectNext(&scanner, .object_begin); |
| 170 | try expectNext(&scanner, Token{ .string = "key" }); | 170 | try expectNext(&scanner, Token{ .string = "key" }); |
| 171 | try std.testing.expectError(error.SyntaxError, scanner.next()); | 171 | try std.testing.expectError(error.SyntaxError, scanner.next()); |
| 172 | } | 172 | } |
| 173 | 173 | ||
| 174 | test "JsonScanner basic" { | 174 | test "Scanner basic" { |
| 175 | var scanner = JsonScanner.initCompleteInput(std.testing.allocator, example_document_str); | 175 | var scanner = Scanner.initCompleteInput(std.testing.allocator, example_document_str); |
| 176 | defer scanner.deinit(); | 176 | defer scanner.deinit(); |
| 177 | 177 | ||
| 178 | while (true) { | 178 | while (true) { |
| ... | @@ -181,10 +181,10 @@ test "JsonScanner basic" { | ... | @@ -181,10 +181,10 @@ test "JsonScanner basic" { |
| 181 | } | 181 | } |
| 182 | } | 182 | } |
| 183 | 183 | ||
| 184 | test "JsonReader basic" { | 184 | test "Scanner.Reader basic" { |
| 185 | var stream = std.io.fixedBufferStream(example_document_str); | 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 | defer json_reader.deinit(); | 188 | defer json_reader.deinit(); |
| 189 | 189 | ||
| 190 | while (true) { | 190 | while (true) { |
| ... | @@ -215,7 +215,7 @@ const number_test_items = blk: { | ... | @@ -215,7 +215,7 @@ const number_test_items = blk: { |
| 215 | 215 | ||
| 216 | test "numbers" { | 216 | test "numbers" { |
| 217 | for (number_test_items) |number_str| { | 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 | defer scanner.deinit(); | 219 | defer scanner.deinit(); |
| 220 | 220 | ||
| 221 | const token = try scanner.next(); | 221 | const token = try scanner.next(); |
| ... | @@ -243,10 +243,10 @@ const string_test_cases = .{ | ... | @@ -243,10 +243,10 @@ const string_test_cases = .{ |
| 243 | 243 | ||
| 244 | test "strings" { | 244 | test "strings" { |
| 245 | inline for (string_test_cases) |tuple| { | 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 | var arena = std.heap.ArenaAllocator.init(std.testing.allocator); | 247 | var arena = std.heap.ArenaAllocator.init(std.testing.allocator); |
| 248 | defer arena.deinit(); | 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 | defer json_reader.deinit(); | 250 | defer json_reader.deinit(); |
| 251 | 251 | ||
| 252 | const token = try json_reader.nextAlloc(arena.allocator(), .alloc_if_needed); | 252 | const token = try json_reader.nextAlloc(arena.allocator(), .alloc_if_needed); |
| ... | @@ -289,7 +289,7 @@ test "nesting" { | ... | @@ -289,7 +289,7 @@ test "nesting" { |
| 289 | } | 289 | } |
| 290 | 290 | ||
| 291 | fn expectMaybeError(document_str: []const u8, maybe_error: ?Error) !void { | 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 | defer scanner.deinit(); | 293 | defer scanner.deinit(); |
| 294 | 294 | ||
| 295 | while (true) { | 295 | while (true) { |
| ... | @@ -352,12 +352,12 @@ fn expectEqualTokens(expected_token: Token, actual_token: Token) !void { | ... | @@ -352,12 +352,12 @@ fn expectEqualTokens(expected_token: Token, actual_token: Token) !void { |
| 352 | } | 352 | } |
| 353 | 353 | ||
| 354 | fn testTinyBufferSize(document_str: []const u8) !void { | 354 | fn testTinyBufferSize(document_str: []const u8) !void { |
| 355 | var tiny_stream = std.io.fixedBufferStream(document_str); | 355 | var tiny_stream: std.Io.Reader = .fixed(document_str); |
| 356 | var normal_stream = std.io.fixedBufferStream(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 | defer tiny_json_reader.deinit(); | 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 | defer normal_json_reader.deinit(); | 361 | defer normal_json_reader.deinit(); |
| 362 | 362 | ||
| 363 | expectEqualStreamOfTokens(&normal_json_reader, &tiny_json_reader) catch |err| { | 363 | expectEqualStreamOfTokens(&normal_json_reader, &tiny_json_reader) catch |err| { |
| ... | @@ -397,13 +397,13 @@ test "validate" { | ... | @@ -397,13 +397,13 @@ test "validate" { |
| 397 | } | 397 | } |
| 398 | 398 | ||
| 399 | fn testSkipValue(s: []const u8) !void { | 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 | defer scanner.deinit(); | 401 | defer scanner.deinit(); |
| 402 | try scanner.skipValue(); | 402 | try scanner.skipValue(); |
| 403 | try expectEqualTokens(.end_of_document, try scanner.next()); | 403 | try expectEqualTokens(.end_of_document, try scanner.next()); |
| 404 | 404 | ||
| 405 | var stream = std.io.fixedBufferStream(s); | 405 | var stream: std.Io.Reader = .fixed(s); |
| 406 | var json_reader = jsonReader(std.testing.allocator, stream.reader()); | 406 | var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream); |
| 407 | defer json_reader.deinit(); | 407 | defer json_reader.deinit(); |
| 408 | try json_reader.skipValue(); | 408 | try json_reader.skipValue(); |
| 409 | try expectEqualTokens(.end_of_document, try json_reader.next()); | 409 | try expectEqualTokens(.end_of_document, try json_reader.next()); |
| ... | @@ -441,7 +441,7 @@ fn testEnsureStackCapacity(do_ensure: bool) !void { | ... | @@ -441,7 +441,7 @@ fn testEnsureStackCapacity(do_ensure: bool) !void { |
| 441 | try input_string.appendNTimes(std.testing.allocator, ']', nestings); | 441 | try input_string.appendNTimes(std.testing.allocator, ']', nestings); |
| 442 | defer input_string.deinit(std.testing.allocator); | 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 | defer scanner.deinit(); | 445 | defer scanner.deinit(); |
| 446 | 446 | ||
| 447 | if (do_ensure) { | 447 | if (do_ensure) { |
| ... | @@ -473,17 +473,17 @@ fn testDiagnosticsFromSource(expected_error: ?anyerror, line: u64, col: u64, byt | ... | @@ -473,17 +473,17 @@ fn testDiagnosticsFromSource(expected_error: ?anyerror, line: u64, col: u64, byt |
| 473 | try std.testing.expectEqual(byte_offset, diagnostics.getByteOffset()); | 473 | try std.testing.expectEqual(byte_offset, diagnostics.getByteOffset()); |
| 474 | } | 474 | } |
| 475 | fn testDiagnostics(expected_error: ?anyerror, line: u64, col: u64, byte_offset: u64, s: []const u8) !void { | 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 | defer scanner.deinit(); | 477 | defer scanner.deinit(); |
| 478 | try testDiagnosticsFromSource(expected_error, line, col, byte_offset, &scanner); | 478 | try testDiagnosticsFromSource(expected_error, line, col, byte_offset, &scanner); |
| 479 | 479 | ||
| 480 | var tiny_stream = std.io.fixedBufferStream(s); | 480 | var tiny_stream: std.Io.Reader = .fixed(s); |
| 481 | var tiny_json_reader = JsonReader(1, @TypeOf(tiny_stream.reader())).init(std.testing.allocator, tiny_stream.reader()); | 481 | var tiny_json_reader: Scanner.Reader = .init(std.testing.allocator, &tiny_stream); |
| 482 | defer tiny_json_reader.deinit(); | 482 | defer tiny_json_reader.deinit(); |
| 483 | try testDiagnosticsFromSource(expected_error, line, col, byte_offset, &tiny_json_reader); | 483 | try testDiagnosticsFromSource(expected_error, line, col, byte_offset, &tiny_json_reader); |
| 484 | 484 | ||
| 485 | var medium_stream = std.io.fixedBufferStream(s); | 485 | var medium_stream: std.Io.Reader = .fixed(s); |
| 486 | var medium_json_reader = JsonReader(5, @TypeOf(medium_stream.reader())).init(std.testing.allocator, medium_stream.reader()); | 486 | var medium_json_reader: Scanner.Reader = .init(std.testing.allocator, &medium_stream); |
| 487 | defer medium_json_reader.deinit(); | 487 | defer medium_json_reader.deinit(); |
| 488 | try testDiagnosticsFromSource(expected_error, line, col, byte_offset, &medium_json_reader); | 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,11 +4,11 @@ const Allocator = std.mem.Allocator; |
| 4 | const ArenaAllocator = std.heap.ArenaAllocator; | 4 | const ArenaAllocator = std.heap.ArenaAllocator; |
| 5 | const ArrayList = std.ArrayList; | 5 | const ArrayList = std.ArrayList; |
| 6 | 6 | ||
| 7 | const Scanner = @import("./scanner.zig").Scanner; | 7 | const Scanner = @import("Scanner.zig"); |
| 8 | const Token = @import("./scanner.zig").Token; | 8 | const Token = Scanner.Token; |
| 9 | const AllocWhen = @import("./scanner.zig").AllocWhen; | 9 | const AllocWhen = Scanner.AllocWhen; |
| 10 | const default_max_value_len = @import("./scanner.zig").default_max_value_len; | 10 | const default_max_value_len = Scanner.default_max_value_len; |
| 11 | const isNumberFormattedLikeAnInteger = @import("./scanner.zig").isNumberFormattedLikeAnInteger; | 11 | const isNumberFormattedLikeAnInteger = Scanner.isNumberFormattedLikeAnInteger; |
| 12 | 12 | ||
| 13 | const Value = @import("./dynamic.zig").Value; | 13 | const Value = @import("./dynamic.zig").Value; |
| 14 | const Array = @import("./dynamic.zig").Array; | 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,9 +12,7 @@ const parseFromValue = @import("./static.zig").parseFromValue; |
| 12 | const parseFromValueLeaky = @import("./static.zig").parseFromValueLeaky; | 12 | const parseFromValueLeaky = @import("./static.zig").parseFromValueLeaky; |
| 13 | const ParseOptions = @import("./static.zig").ParseOptions; | 13 | const ParseOptions = @import("./static.zig").ParseOptions; |
| 14 | 14 | ||
| 15 | const JsonScanner = @import("./scanner.zig").Scanner; | 15 | const Scanner = @import("Scanner.zig"); |
| 16 | const jsonReader = @import("./scanner.zig").reader; | ||
| 17 | const Diagnostics = @import("./scanner.zig").Diagnostics; | ||
| 18 | 16 | ||
| 19 | const Value = @import("./dynamic.zig").Value; | 17 | const Value = @import("./dynamic.zig").Value; |
| 20 | 18 | ||
| ... | @@ -300,9 +298,9 @@ const subnamespaces_0_doc = | ... | @@ -300,9 +298,9 @@ const subnamespaces_0_doc = |
| 300 | fn testAllParseFunctions(comptime T: type, expected: T, doc: []const u8) !void { | 298 | fn testAllParseFunctions(comptime T: type, expected: T, doc: []const u8) !void { |
| 301 | // First do the one with the debug info in case we get a SyntaxError or something. | 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 | defer scanner.deinit(); | 302 | defer scanner.deinit(); |
| 305 | var diagnostics = Diagnostics{}; | 303 | var diagnostics = Scanner.Diagnostics{}; |
| 306 | scanner.enableDiagnostics(&diagnostics); | 304 | scanner.enableDiagnostics(&diagnostics); |
| 307 | var parsed = parseFromTokenSource(T, testing.allocator, &scanner, .{}) catch |e| { | 305 | var parsed = parseFromTokenSource(T, testing.allocator, &scanner, .{}) catch |e| { |
| 308 | std.debug.print("at line,col: {}:{}\n", .{ diagnostics.getLine(), diagnostics.getColumn() }); | 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,8 +315,8 @@ fn testAllParseFunctions(comptime T: type, expected: T, doc: []const u8) !void { |
| 317 | try testing.expectEqualDeep(expected, parsed.value); | 315 | try testing.expectEqualDeep(expected, parsed.value); |
| 318 | } | 316 | } |
| 319 | { | 317 | { |
| 320 | var stream = std.io.fixedBufferStream(doc); | 318 | var stream: std.Io.Reader = .fixed(doc); |
| 321 | var json_reader = jsonReader(std.testing.allocator, stream.reader()); | 319 | var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream); |
| 322 | defer json_reader.deinit(); | 320 | defer json_reader.deinit(); |
| 323 | var parsed = try parseFromTokenSource(T, testing.allocator, &json_reader, .{}); | 321 | var parsed = try parseFromTokenSource(T, testing.allocator, &json_reader, .{}); |
| 324 | defer parsed.deinit(); | 322 | defer parsed.deinit(); |
| ... | @@ -331,13 +329,13 @@ fn testAllParseFunctions(comptime T: type, expected: T, doc: []const u8) !void { | ... | @@ -331,13 +329,13 @@ fn testAllParseFunctions(comptime T: type, expected: T, doc: []const u8) !void { |
| 331 | try testing.expectEqualDeep(expected, try parseFromSliceLeaky(T, arena.allocator(), doc, .{})); | 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 | defer scanner.deinit(); | 333 | defer scanner.deinit(); |
| 336 | try testing.expectEqualDeep(expected, try parseFromTokenSourceLeaky(T, arena.allocator(), &scanner, .{})); | 334 | try testing.expectEqualDeep(expected, try parseFromTokenSourceLeaky(T, arena.allocator(), &scanner, .{})); |
| 337 | } | 335 | } |
| 338 | { | 336 | { |
| 339 | var stream = std.io.fixedBufferStream(doc); | 337 | var stream: std.Io.Reader = .fixed(doc); |
| 340 | var json_reader = jsonReader(std.testing.allocator, stream.reader()); | 338 | var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream); |
| 341 | defer json_reader.deinit(); | 339 | defer json_reader.deinit(); |
| 342 | try testing.expectEqualDeep(expected, try parseFromTokenSourceLeaky(T, arena.allocator(), &json_reader, .{})); | 340 | try testing.expectEqualDeep(expected, try parseFromTokenSourceLeaky(T, arena.allocator(), &json_reader, .{})); |
| 343 | } | 341 | } |
| ... | @@ -763,7 +761,7 @@ test "parse exponential into int" { | ... | @@ -763,7 +761,7 @@ test "parse exponential into int" { |
| 763 | 761 | ||
| 764 | test "parseFromTokenSource" { | 762 | test "parseFromTokenSource" { |
| 765 | { | 763 | { |
| 766 | var scanner = JsonScanner.initCompleteInput(testing.allocator, "123"); | 764 | var scanner = Scanner.initCompleteInput(testing.allocator, "123"); |
| 767 | defer scanner.deinit(); | 765 | defer scanner.deinit(); |
| 768 | var parsed = try parseFromTokenSource(u32, testing.allocator, &scanner, .{}); | 766 | var parsed = try parseFromTokenSource(u32, testing.allocator, &scanner, .{}); |
| 769 | defer parsed.deinit(); | 767 | defer parsed.deinit(); |
| ... | @@ -771,8 +769,8 @@ test "parseFromTokenSource" { | ... | @@ -771,8 +769,8 @@ test "parseFromTokenSource" { |
| 771 | } | 769 | } |
| 772 | 770 | ||
| 773 | { | 771 | { |
| 774 | var stream = std.io.fixedBufferStream("123"); | 772 | var stream: std.Io.Reader = .fixed("123"); |
| 775 | var json_reader = jsonReader(std.testing.allocator, stream.reader()); | 773 | var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream); |
| 776 | defer json_reader.deinit(); | 774 | defer json_reader.deinit(); |
| 777 | var parsed = try parseFromTokenSource(u32, testing.allocator, &json_reader, .{}); | 775 | var parsed = try parseFromTokenSource(u32, testing.allocator, &json_reader, .{}); |
| 778 | defer parsed.deinit(); | 776 | defer parsed.deinit(); |
| ... | @@ -836,7 +834,7 @@ test "json parse partial" { | ... | @@ -836,7 +834,7 @@ test "json parse partial" { |
| 836 | \\} | 834 | \\} |
| 837 | ; | 835 | ; |
| 838 | const allocator = testing.allocator; | 836 | const allocator = testing.allocator; |
| 839 | var scanner = JsonScanner.initCompleteInput(allocator, str); | 837 | var scanner = Scanner.initCompleteInput(allocator, str); |
| 840 | defer scanner.deinit(); | 838 | defer scanner.deinit(); |
| 841 | 839 | ||
| 842 | var arena = ArenaAllocator.init(allocator); | 840 | var arena = ArenaAllocator.init(allocator); |
| ... | @@ -886,8 +884,8 @@ test "json parse allocate when streaming" { | ... | @@ -886,8 +884,8 @@ test "json parse allocate when streaming" { |
| 886 | var arena = ArenaAllocator.init(allocator); | 884 | var arena = ArenaAllocator.init(allocator); |
| 887 | defer arena.deinit(); | 885 | defer arena.deinit(); |
| 888 | 886 | ||
| 889 | var stream = std.io.fixedBufferStream(str); | 887 | var stream: std.Io.Reader = .fixed(str); |
| 890 | var json_reader = jsonReader(std.testing.allocator, stream.reader()); | 888 | var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream); |
| 891 | 889 | ||
| 892 | const parsed = parseFromTokenSourceLeaky(T, arena.allocator(), &json_reader, .{}) catch |err| { | 890 | const parsed = parseFromTokenSourceLeaky(T, arena.allocator(), &json_reader, .{}) catch |err| { |
| 893 | json_reader.deinit(); | 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,10 +1,9 @@ |
| 1 | const std = @import("std"); | 1 | const std = @import("std"); |
| 2 | const json = std.json; | ||
| 2 | const testing = std.testing; | 3 | const testing = std.testing; |
| 3 | const parseFromSlice = @import("./static.zig").parseFromSlice; | 4 | const parseFromSlice = @import("./static.zig").parseFromSlice; |
| 4 | const validate = @import("./scanner.zig").validate; | 5 | const Scanner = @import("./Scanner.zig"); |
| 5 | const JsonScanner = @import("./scanner.zig").Scanner; | ||
| 6 | const Value = @import("./dynamic.zig").Value; | 6 | const Value = @import("./dynamic.zig").Value; |
| 7 | const stringifyAlloc = @import("./stringify.zig").stringifyAlloc; | ||
| 8 | 7 | ||
| 9 | // Support for JSONTestSuite.zig | 8 | // Support for JSONTestSuite.zig |
| 10 | pub fn ok(s: []const u8) !void { | 9 | pub fn ok(s: []const u8) !void { |
| ... | @@ -20,7 +19,7 @@ pub fn any(s: []const u8) !void { | ... | @@ -20,7 +19,7 @@ pub fn any(s: []const u8) !void { |
| 20 | testHighLevelDynamicParser(s) catch {}; | 19 | testHighLevelDynamicParser(s) catch {}; |
| 21 | } | 20 | } |
| 22 | fn testLowLevelScanner(s: []const u8) !void { | 21 | fn testLowLevelScanner(s: []const u8) !void { |
| 23 | var scanner = JsonScanner.initCompleteInput(testing.allocator, s); | 22 | var scanner = Scanner.initCompleteInput(testing.allocator, s); |
| 24 | defer scanner.deinit(); | 23 | defer scanner.deinit(); |
| 25 | while (true) { | 24 | while (true) { |
| 26 | const token = try scanner.next(); | 25 | const token = try scanner.next(); |
| ... | @@ -47,12 +46,12 @@ test "n_object_closed_missing_value" { | ... | @@ -47,12 +46,12 @@ test "n_object_closed_missing_value" { |
| 47 | } | 46 | } |
| 48 | 47 | ||
| 49 | fn roundTrip(s: []const u8) !void { | 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 | var parsed = try parseFromSlice(Value, testing.allocator, s, .{}); | 51 | var parsed = try parseFromSlice(Value, testing.allocator, s, .{}); |
| 53 | defer parsed.deinit(); | 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 | defer testing.allocator.free(rendered); | 55 | defer testing.allocator.free(rendered); |
| 57 | 56 | ||
| 58 | try testing.expectEqualStrings(s, rendered); | 57 | try testing.expectEqualStrings(s, rendered); |