authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-18 19:32:42-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-19 16:05:01-07:00
logc30df072bde3794ea5f3794d93f69e13640c6b7a
tree832ab5f7947e9dcc1d8d4fb792ce240c773b0cd0
parenta288266f3310e9ba98456c5e968f8ce434be6cc7

std.json: update to new I/O API

also do a little bit of namespace cleanup

14 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.
1111
12const builtin = @import("builtin");12const builtin = @import("builtin");
13const testing = @import("std").testing;13const std = @import("std");
14const ArrayList = @import("std").ArrayList;14const testing = std.testing;
1515
16test Scanner {16test 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}
4343
44test writeStream {44test 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": 12357 \\ "foo": 123
56 \\}58 \\}
57 ;59 ;
58 try testing.expectEqualSlices(u8, expected, out.items);60 try testing.expectEqualSlices(u8, expected, out.getWritten());
59}
60
61test 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}
6962
70pub const ObjectMap = @import("json/dynamic.zig").ObjectMap;63pub 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;
7366
74pub const ArrayHashMap = @import("json/hashmap.zig").ArrayHashMap;67pub const ArrayHashMap = @import("json/hashmap.zig").ArrayHashMap;
7568
76pub const validate = @import("json/scanner.zig").validate;69pub const Scanner = @import("json/Scanner.zig");
77pub const Error = @import("json/scanner.zig").Error;70pub const validate = Scanner.validate;
78pub const reader = @import("json/scanner.zig").reader;71pub const Error = Scanner.Error;
79pub const default_buffer_size = @import("json/scanner.zig").default_buffer_size;72pub const reader = Scanner.reader;
80pub const Token = @import("json/scanner.zig").Token;73pub const default_buffer_size = Scanner.default_buffer_size;
81pub const TokenType = @import("json/scanner.zig").TokenType;74pub const Token = Scanner.Token;
82pub const Diagnostics = @import("json/scanner.zig").Diagnostics;75pub const TokenType = Scanner.TokenType;
83pub const AllocWhen = @import("json/scanner.zig").AllocWhen;76pub const Diagnostics = Scanner.Diagnostics;
84pub const default_max_value_len = @import("json/scanner.zig").default_max_value_len;77pub const AllocWhen = Scanner.AllocWhen;
85pub const Reader = @import("json/scanner.zig").Reader;78pub const default_max_value_len = Scanner.default_max_value_len;
86pub const Scanner = @import("json/scanner.zig").Scanner;79pub const Reader = Scanner.Reader;
87pub const isNumberFormattedLikeAnInteger = @import("json/scanner.zig").isNumberFormattedLikeAnInteger;80pub const isNumberFormattedLikeAnInteger = Scanner.isNumberFormattedLikeAnInteger;
8881
89pub const ParseOptions = @import("json/static.zig").ParseOptions;82pub const ParseOptions = @import("json/static.zig").ParseOptions;
90pub const Parsed = @import("json/static.zig").Parsed;83pub 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;
99pub const ParseError = @import("json/static.zig").ParseError;92pub const ParseError = @import("json/static.zig").ParseError;
100pub const ParseFromValueError = @import("json/static.zig").ParseFromValueError;93pub const ParseFromValueError = @import("json/static.zig").ParseFromValueError;
10194
102pub const StringifyOptions = @import("json/stringify.zig").StringifyOptions;95pub const Stringify = @import("json/Stringify.zig");
103pub const stringify = @import("json/stringify.zig").stringify;96
104pub const stringifyMaxDepth = @import("json/stringify.zig").stringifyMaxDepth;97/// Returns a formatter that formats the given value using stringify.
105pub const stringifyArbitraryDepth = @import("json/stringify.zig").stringifyArbitraryDepth;98pub fn fmt(value: anytype, options: Stringify.Options) Formatter(@TypeOf(value)) {
106pub const stringifyAlloc = @import("json/stringify.zig").stringifyAlloc;99 return Formatter(@TypeOf(value)){ .value = value, .options = options };
107pub const writeStream = @import("json/stringify.zig").writeStream;100}
108pub const writeStreamMaxDepth = @import("json/stringify.zig").writeStreamMaxDepth;101
109pub const writeStreamArbitraryDepth = @import("json/stringify.zig").writeStreamArbitraryDepth;102test fmt {
110pub const WriteStream = @import("json/stringify.zig").WriteStream;103 const expectFmt = std.testing.expectFmt;
111pub const encodeJsonString = @import("json/stringify.zig").encodeJsonString;104 try expectFmt("123", "{f}", .{fmt(@as(u32, 123), .{})});
112pub const encodeJsonStringChars = @import("json/stringify.zig").encodeJsonStringChars;105 try expectFmt(
113106 \\{"num":927,"msg":"hello","sub":{"mybool":true}}
114pub const Formatter = @import("json/fmt.zig").Formatter;107 , "{f}", .{fmt(struct {
115pub 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.
121pub 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}
116131
117test {132test {
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
45const Scanner = @This();
46const std = @import("std");
47
48const Allocator = std.mem.Allocator;
49const ArrayList = std.ArrayList;
50const assert = std.debug.assert;
51const BitStack = std.BitStack;
52
53state: State = .value,
54string_is_object_key: bool = false,
55stack: BitStack,
56value_start: usize = undefined,
57utf16_code_units: [2]u16 = undefined,
58
59input: []const u8 = "",
60cursor: usize = 0,
61is_end_of_input: bool = false,
62diagnostics: ?*Diagnostics = null,
63
64/// The allocator is only used to track `[]` and `{}` nesting levels.
65pub 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/// ```
77pub 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}
84pub fn deinit(self: *@This()) void {
85 self.stack.deinit();
86 self.* = undefined;
87}
88
89pub 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()`.
96pub 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`.
112pub fn endInput(self: *@This()) void {
113 self.is_end_of_input = true;
114}
115
116pub const NextError = Error || Allocator.Error || error{BufferUnderrun};
117pub const AllocError = Error || Allocator.Error || error{ValueTooLong};
118pub const PeekError = Error || error{BufferUnderrun};
119pub const SkipError = Error || Allocator.Error;
120pub 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.
125pub 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.
131pub 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);`
176pub 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.
189pub 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()`.
253pub 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.
298pub 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.
311pub 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.
317pub 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.
322pub 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.
1093pub 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
1219const 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
1271fn 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
1280fn 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
1299fn skipWhitespaceExpectByte(self: *@This()) !u8 {
1300 self.skipWhitespace();
1301 return self.expectByte();
1302}
1303
1304fn 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
1322fn 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}
1327fn 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
1339fn 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
1350fn 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
1383fn 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.
1398pub 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`.
1420pub const Error = error{ SyntaxError, UnexpectedEndOfInput };
1421
1422/// Used by `json.reader`.
1423pub 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()`.
1504pub 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.
1530pub 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.
1547pub 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`.
1568pub 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()`.
1572pub 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.
1575pub 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
1746const OBJECT_MODE = 0;
1747const ARRAY_MODE = 1;
1748
1749fn 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.
1760pub 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
1765test {
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//! | print
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
20const std = @import("../std.zig");
21const assert = std.debug.assert;
22const Allocator = std.mem.Allocator;
23const ArrayList = std.ArrayList;
24const BitStack = std.BitStack;
25const Stringify = @This();
26const Writer = std.io.Writer;
27
28const IndentationMode = enum(u1) {
29 object = 0,
30 array = 1,
31};
32
33writer: *Writer,
34options: Options = .{},
35indent_level: usize = 0,
36next_punctuation: enum {
37 the_beginning,
38 none,
39 comma,
40 colon,
41} = .the_beginning,
42
43nesting_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
51raw_streaming_mode: if (build_mode_has_safety)
52 enum { none, value, objectField }
53else
54 void = if (build_mode_has_safety) .none else {},
55
56const 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`.
70const 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
76const safety_checks: @TypeOf(safety_checks_hint) = if (build_mode_has_safety)
77 safety_checks_hint
78else
79 .assumed_correct;
80
81pub const Error = Writer.Error;
82
83pub 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
91pub 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
99pub 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
113pub 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
127fn 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}
137fn 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
148fn 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
166fn 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}
170fn objectFieldStart(self: *Stringify) !void {
171 if (self.isObjectKeyExpected()) |is_it| assert(is_it); // Expected write(), not objectField*().
172 return self.valueStartAssumeTypeOk();
173}
174fn 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}
197fn valueDone(self: *Stringify) void {
198 self.next_punctuation = .comma;
199}
200
201// Only when safety is enabled:
202fn 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}
210fn 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.
219pub 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
226test 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.
258pub 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`.
267pub 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`.
279pub 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`.
289pub 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.
301pub 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`.
310pub 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.
348pub 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
533fn stringValue(self: *Stringify, s: []const u8) !void {
534 try self.valueStart();
535 try encodeJsonString(s, self.options, self.writer);
536 self.valueDone();
537}
538
539pub 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.
573pub 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
578test 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.
618pub 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
625test 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
636fn 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
656fn 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.
670pub 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.
677pub 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
718test "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
725fn 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
772fn 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
779test "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
800test "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
816test "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
840test "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
846test "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
855test "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
864test "stringify enum literals" {
865 try testStringify("\"foo\"", .foo, .{});
866 try testStringify("\"bar\"", .bar, .{});
867}
868
869test "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
880test "stringify struct" {
881 try testStringify("{\"foo\":42}", struct {
882 foo: u32,
883 }{ .foo = 42 }, .{});
884}
885
886test "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
900test "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
945test "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
956test "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
970fn 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
977test "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;
4const ArrayList = std.ArrayList;4const ArrayList = std.ArrayList;
5const StringArrayHashMap = std.StringArrayHashMap;5const StringArrayHashMap = std.StringArrayHashMap;
6const Allocator = std.mem.Allocator;6const Allocator = std.mem.Allocator;
77const json = std.json;
8const StringifyOptions = @import("./stringify.zig").StringifyOptions;
9const stringify = @import("./stringify.zig").stringify;
108
11const ParseOptions = @import("./static.zig").ParseOptions;9const ParseOptions = @import("./static.zig").ParseOptions;
12const ParseError = @import("./static.zig").ParseError;10const ParseError = @import("./static.zig").ParseError;
1311
14const JsonScanner = @import("./scanner.zig").Scanner;12const isNumberFormattedLikeAnInteger = @import("Scanner.zig").isNumberFormattedLikeAnInteger;
15const AllocWhen = @import("./scanner.zig").AllocWhen;
16const Token = @import("./scanner.zig").Token;
17const isNumberFormattedLikeAnInteger = @import("./scanner.zig").isNumberFormattedLikeAnInteger;
1813
19pub const ObjectMap = StringArrayHashMap(Value);14pub const ObjectMap = StringArrayHashMap(Value);
20pub const Array = ArrayList(Value);15pub 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 }
5449
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();
5853
59 const stderr = std.fs.File.stderr().deprecatedWriter();54 json.Stringify.value(v, .{}, w) catch return;
60 stringify(self, .{}, stderr) catch return;
61 }55 }
6256
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 @@
1const std = @import("std");1const std = @import("std");
2const json = std.json;
2const mem = std.mem;3const mem = std.mem;
3const testing = std.testing;4const testing = std.testing;
4const ArenaAllocator = std.heap.ArenaAllocator;5const ArenaAllocator = std.heap.ArenaAllocator;
5const Allocator = std.mem.Allocator;6const Allocator = std.mem.Allocator;
7const Writer = std.io.Writer;
68
7const ObjectMap = @import("dynamic.zig").ObjectMap;9const ObjectMap = @import("dynamic.zig").ObjectMap;
8const Array = @import("dynamic.zig").Array;10const 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;
14const parseFromValueLeaky = @import("static.zig").parseFromValueLeaky;16const parseFromValueLeaky = @import("static.zig").parseFromValueLeaky;
15const ParseOptions = @import("static.zig").ParseOptions;17const ParseOptions = @import("static.zig").ParseOptions;
1618
17const jsonReader = @import("scanner.zig").reader;19const Scanner = @import("Scanner.zig");
18const JsonReader = @import("scanner.zig").Reader;
1920
20test "json.parser.dynamic" {21test "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}
7273
73const writeStream = @import("./stringify.zig").writeStream;
74test "write json then parse it" {74test "write json then parse it" {
75 var out_buffer: [1000]u8 = undefined;75 var out_buffer: [1000]u8 = undefined;
7676 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();
8178
82 try jw.beginObject();79 try jw.beginObject();
8380
...@@ -101,8 +98,8 @@ test "write json then parse it" {...@@ -101,8 +98,8 @@ test "write json then parse it" {
10198
102 try jw.endObject();99 try jw.endObject();
103100
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);
246243
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);
250246
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}
271267
272test "parseFromValue(std.json.Value,...)" {268test "parseFromValue(std.json.Value,...)" {
...@@ -334,8 +330,8 @@ test "polymorphic parsing" {...@@ -334,8 +330,8 @@ test "polymorphic parsing" {
334test "long object value" {330test "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" {
382378
383test "negative zero" {379test "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}
393389
394fn smallBufferJsonReader(allocator: Allocator, io_reader: anytype) JsonReader(16, @TypeOf(io_reader)) {390fn 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 @@
1const std = @import("../std.zig");
2const assert = std.debug.assert;
3
4const stringify = @import("stringify.zig").stringify;
5const StringifyOptions = @import("stringify.zig").StringifyOptions;
6
7/// Returns a formatter that formats the given value using stringify.
8pub 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.
13pub 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
24test 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 @@
1const std = @import("std");1const std = @import("std");
2const json = std.json;
2const testing = std.testing;3const testing = std.testing;
34
4const ArrayHashMap = @import("hashmap.zig").ArrayHashMap;5const 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;
7const parseFromSliceLeaky = @import("static.zig").parseFromSliceLeaky;8const parseFromSliceLeaky = @import("static.zig").parseFromSliceLeaky;
8const parseFromTokenSource = @import("static.zig").parseFromTokenSource;9const parseFromTokenSource = @import("static.zig").parseFromTokenSource;
9const parseFromValue = @import("static.zig").parseFromValue;10const parseFromValue = @import("static.zig").parseFromValue;
10const stringifyAlloc = @import("stringify.zig").stringifyAlloc;
11const Value = @import("dynamic.zig").Value;11const Value = @import("dynamic.zig").Value;
1212
13const jsonReader = @import("./scanner.zig").reader;13const Scanner = @import("Scanner.zig");
1414
15const T = struct {15const 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);
4444
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" });
9999
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" {
107107
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" {
116116
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" });
130130
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
31const std = @import("std");
32
33const Allocator = std.mem.Allocator;
34const ArrayList = std.ArrayList;
35const assert = std.debug.assert;
36const 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.
42pub 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`.
64pub const Error = error{ SyntaxError, UnexpectedEndOfInput };
65
66/// Calls `std.json.Reader` with `std.json.default_buffer_size`.
67pub 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`.
71pub 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()`.
152pub 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.
178pub 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.
195pub 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`.
216pub 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()`.
220pub 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.
224pub 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.
412pub 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
1755const OBJECT_MODE = 0;
1756const ARRAY_MODE = 1;
1757
1758fn 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.
1769pub 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
1774test {
1775 _ = @import("./scanner_test.zig");
1776}
lib/std/json/scanner_test.zig+39-39
...@@ -1,13 +1,11 @@...@@ -1,13 +1,11 @@
1const std = @import("std");1const std = @import("std");
2const JsonScanner = @import("./scanner.zig").Scanner;2const Scanner = @import("Scanner.zig");
3const jsonReader = @import("./scanner.zig").reader;3const Token = Scanner.Token;
4const JsonReader = @import("./scanner.zig").Reader;4const TokenType = Scanner.TokenType;
5const Token = @import("./scanner.zig").Token;5const Diagnostics = Scanner.Diagnostics;
6const TokenType = @import("./scanner.zig").TokenType;6const Error = Scanner.Error;
7const Diagnostics = @import("./scanner.zig").Diagnostics;7const validate = Scanner.validate;
8const Error = @import("./scanner.zig").Error;8const isNumberFormattedLikeAnInteger = Scanner.isNumberFormattedLikeAnInteger;
9const validate = @import("./scanner.zig").validate;
10const isNumberFormattedLikeAnInteger = @import("./scanner.zig").isNumberFormattedLikeAnInteger;
119
12const example_document_str =10const 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}
3735
38test "token" {36test "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();
4139
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}
139137
140test "peek all types" {138test "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);
144142
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);
149147
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}
155155
156test "token mismatched close" {156test "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}
165165
166test "token premature object close" {166test "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}
173173
174test "JsonScanner basic" {174test "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();
177177
178 while (true) {178 while (true) {
...@@ -181,10 +181,10 @@ test "JsonScanner basic" {...@@ -181,10 +181,10 @@ test "JsonScanner basic" {
181 }181 }
182}182}
183183
184test "JsonReader basic" {184test "Scanner.Reader basic" {
185 var stream = std.io.fixedBufferStream(example_document_str);185 var stream: std.Io.Reader = .fixed(example_document_str);
186186
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();
189189
190 while (true) {190 while (true) {
...@@ -215,7 +215,7 @@ const number_test_items = blk: {...@@ -215,7 +215,7 @@ const number_test_items = blk: {
215215
216test "numbers" {216test "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();
220220
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 = .{
243243
244test "strings" {244test "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();
251251
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}
290290
291fn expectMaybeError(document_str: []const u8, maybe_error: ?Error) !void {291fn 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();
294294
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}
353353
354fn testTinyBufferSize(document_str: []const u8) !void {354fn 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);
357357
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();
362362
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}
398398
399fn testSkipValue(s: []const u8) !void {399fn 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());
404404
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);
443443
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();
446446
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}
475fn testDiagnostics(expected_error: ?anyerror, line: u64, col: u64, byte_offset: u64, s: []const u8) !void {475fn 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);
479479
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);
484484
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;
4const ArenaAllocator = std.heap.ArenaAllocator;4const ArenaAllocator = std.heap.ArenaAllocator;
5const ArrayList = std.ArrayList;5const ArrayList = std.ArrayList;
66
7const Scanner = @import("./scanner.zig").Scanner;7const Scanner = @import("Scanner.zig");
8const Token = @import("./scanner.zig").Token;8const Token = Scanner.Token;
9const AllocWhen = @import("./scanner.zig").AllocWhen;9const AllocWhen = Scanner.AllocWhen;
10const default_max_value_len = @import("./scanner.zig").default_max_value_len;10const default_max_value_len = Scanner.default_max_value_len;
11const isNumberFormattedLikeAnInteger = @import("./scanner.zig").isNumberFormattedLikeAnInteger;11const isNumberFormattedLikeAnInteger = Scanner.isNumberFormattedLikeAnInteger;
1212
13const Value = @import("./dynamic.zig").Value;13const Value = @import("./dynamic.zig").Value;
14const Array = @import("./dynamic.zig").Array;14const 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;
12const parseFromValueLeaky = @import("./static.zig").parseFromValueLeaky;12const parseFromValueLeaky = @import("./static.zig").parseFromValueLeaky;
13const ParseOptions = @import("./static.zig").ParseOptions;13const ParseOptions = @import("./static.zig").ParseOptions;
1414
15const JsonScanner = @import("./scanner.zig").Scanner;15const Scanner = @import("Scanner.zig");
16const jsonReader = @import("./scanner.zig").reader;
17const Diagnostics = @import("./scanner.zig").Diagnostics;
1816
19const Value = @import("./dynamic.zig").Value;17const Value = @import("./dynamic.zig").Value;
2018
...@@ -300,9 +298,9 @@ const subnamespaces_0_doc =...@@ -300,9 +298,9 @@ const subnamespaces_0_doc =
300fn testAllParseFunctions(comptime T: type, expected: T, doc: []const u8) !void {298fn 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" {
763761
764test "parseFromTokenSource" {762test "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 }
772770
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();
841839
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();
888886
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);
891889
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 @@
1const std = @import("std");
2const assert = std.debug.assert;
3const Allocator = std.mem.Allocator;
4const ArrayList = std.ArrayList;
5const BitStack = std.BitStack;
6
7const OBJECT_MODE = 0;
8const ARRAY_MODE = 1;
9
10pub 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`.
45pub 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.
59pub 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`.
72pub 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.
87pub 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.
102pub 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.
119pub 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*.
140pub 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/// | print
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`.
177pub 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
685fn 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
708fn 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.
722pub 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.
729pub 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
770test {
771 _ = @import("./stringify_test.zig");
772}
lib/std/json/stringify_test.zig deleted-504
...@@ -1,504 +0,0 @@
1const std = @import("std");
2const mem = std.mem;
3const testing = std.testing;
4
5const ObjectMap = @import("dynamic.zig").ObjectMap;
6const Value = @import("dynamic.zig").Value;
7
8const StringifyOptions = @import("stringify.zig").StringifyOptions;
9const stringify = @import("stringify.zig").stringify;
10const stringifyMaxDepth = @import("stringify.zig").stringifyMaxDepth;
11const stringifyArbitraryDepth = @import("stringify.zig").stringifyArbitraryDepth;
12const stringifyAlloc = @import("stringify.zig").stringifyAlloc;
13const writeStream = @import("stringify.zig").writeStream;
14const writeStreamMaxDepth = @import("stringify.zig").writeStreamMaxDepth;
15const writeStreamArbitraryDepth = @import("stringify.zig").writeStreamArbitraryDepth;
16
17test "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
44fn 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
92fn 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
99test "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
120test "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
136test "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
160test "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
166test "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
175test "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
184test "stringify enum literals" {
185 try testStringify("\"foo\"", .foo, .{});
186 try testStringify("\"bar\"", .bar, .{});
187}
188
189test "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
200test "stringify struct" {
201 try testStringify("{\"foo\":42}", struct {
202 foo: u32,
203 }{ .foo = 42 }, .{});
204}
205
206test "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
220test "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
265test "stringify struct with void field" {
266 try testStringify("{\"foo\":42}", struct {
267 foo: u32,
268 bar: void = {},
269 }{ .foo = 42 }, .{});
270}
271
272test "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
283test "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
297test "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
303test "stringify tuple" {
304 try testStringify("[\"foo\",42]", std.meta.Tuple(&.{ []const u8, usize }){ "foo", 42 }, .{});
305}
306
307fn 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
367fn 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
378fn 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
389test "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
400test "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
420test "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
451test "nonportable numbers" {
452 try testStringify("9999999999999999", 9999999999999999, .{});
453 try testStringify("\"9999999999999999\"", 9999999999999999, .{ .emit_nonportable_numbers_as_strings = true });
454}
455
456test "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
483fn 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 @@
1const std = @import("std");1const std = @import("std");
2const json = std.json;
2const testing = std.testing;3const testing = std.testing;
3const parseFromSlice = @import("./static.zig").parseFromSlice;4const parseFromSlice = @import("./static.zig").parseFromSlice;
4const validate = @import("./scanner.zig").validate;5const Scanner = @import("./Scanner.zig");
5const JsonScanner = @import("./scanner.zig").Scanner;
6const Value = @import("./dynamic.zig").Value;6const Value = @import("./dynamic.zig").Value;
7const stringifyAlloc = @import("./stringify.zig").stringifyAlloc;
87
9// Support for JSONTestSuite.zig8// Support for JSONTestSuite.zig
10pub fn ok(s: []const u8) !void {9pub 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}
22fn testLowLevelScanner(s: []const u8) !void {21fn 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}
4847
49fn roundTrip(s: []const u8) !void {48fn roundTrip(s: []const u8) !void {
50 try testing.expect(try validate(testing.allocator, s));49 try testing.expect(try Scanner.validate(testing.allocator, s));
5150
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();
5453
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);
5756
58 try testing.expectEqualStrings(s, rendered);57 try testing.expectEqualStrings(s, rendered);