authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-20 09:48:25+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-07-20 09:48:25+02:00
loge43617e686f62af55d6663b695ec725e21bff210
tree36281b6b7607d8d7744749d4924acf6730b0ed2f
parentc58cce799932a5b9a735ac359794ec8bcde61633
parent0fb7a0a94bf6f9e329008d8b5b819a8d1d7124b0
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #24505 from ziglang/json

update std.json and std.zon to new I/O API

27 files changed, 4302 insertions(+), 4662 deletions(-)

lib/compiler/resinator/main.zig+7-5
......@@ -290,12 +290,14 @@ pub fn main() !void {
290290 };
291291 defer depfile.close();
292292
293 const depfile_writer = depfile.deprecatedWriter();
294 var depfile_buffered_writer = std.io.bufferedWriter(depfile_writer);
293 var depfile_buffer: [1024]u8 = undefined;
294 var depfile_writer = depfile.writer(&depfile_buffer);
295295 switch (options.depfile_fmt) {
296296 .json => {
297 var write_stream = std.json.writeStream(depfile_buffered_writer.writer(), .{ .whitespace = .indent_2 });
298 defer write_stream.deinit();
297 var write_stream: std.json.Stringify = .{
298 .writer = &depfile_writer.interface,
299 .options = .{ .whitespace = .indent_2 },
300 };
299301
300302 try write_stream.beginArray();
301303 for (dependencies_list.items) |dep_path| {
......@@ -304,7 +306,7 @@ pub fn main() !void {
304306 try write_stream.endArray();
305307 },
306308 }
307 try depfile_buffered_writer.flush();
309 try depfile_writer.interface.flush();
308310 }
309311 }
310312
lib/std/Build/Cache/Path.zig+5-3
......@@ -161,17 +161,19 @@ pub fn formatEscapeString(path: Path, writer: *std.io.Writer) std.io.Writer.Erro
161161 }
162162}
163163
164/// Deprecated, use double quoted escape to print paths.
164165pub fn fmtEscapeChar(path: Path) std.fmt.Formatter(Path, formatEscapeChar) {
165166 return .{ .data = path };
166167}
167168
169/// Deprecated, use double quoted escape to print paths.
168170pub fn formatEscapeChar(path: Path, writer: *std.io.Writer) std.io.Writer.Error!void {
169171 if (path.root_dir.path) |p| {
170 try std.zig.charEscape(p, writer);
171 if (path.sub_path.len > 0) try std.zig.charEscape(fs.path.sep_str, writer);
172 for (p) |byte| try std.zig.charEscape(byte, writer);
173 if (path.sub_path.len > 0) try writer.writeByte(fs.path.sep);
172174 }
173175 if (path.sub_path.len > 0) {
174 try std.zig.charEscape(path.sub_path, writer);
176 for (path.sub_path) |byte| try std.zig.charEscape(byte, writer);
175177 }
176178}
177179
lib/std/Io/Reader.zig+2-2
......@@ -990,9 +990,9 @@ pub fn discardDelimiterLimit(r: *Reader, delimiter: u8, limit: Limit) DiscardDel
990990/// Returns `error.EndOfStream` if and only if there are fewer than `n` bytes
991991/// remaining.
992992///
993/// Asserts buffer capacity is at least `n`.
993/// If the end of stream is not encountered, asserts buffer capacity is at
994/// least `n`.
994995pub fn fill(r: *Reader, n: usize) Error!void {
995 assert(n <= r.buffer.len);
996996 if (r.seek + n <= r.end) {
997997 @branchHint(.likely);
998998 return;
lib/std/json.zig+59-44
......@@ -10,8 +10,8 @@
1010//! The high-level `stringify` serializes a Zig or `Value` type into JSON.
1111
1212const builtin = @import("builtin");
13const testing = @import("std").testing;
14const ArrayList = @import("std").ArrayList;
13const std = @import("std");
14const testing = std.testing;
1515
1616test Scanner {
1717 var scanner = Scanner.initCompleteInput(testing.allocator, "{\"foo\": 123}\n");
......@@ -41,11 +41,13 @@ test Value {
4141 try testing.expectEqualSlices(u8, "goes", parsed.value.object.get("anything").?.string);
4242}
4343
44test writeStream {
45 var out = ArrayList(u8).init(testing.allocator);
44test Stringify {
45 var out: std.io.Writer.Allocating = .init(testing.allocator);
46 var write_stream: Stringify = .{
47 .writer = &out.writer,
48 .options = .{ .whitespace = .indent_2 },
49 };
4650 defer out.deinit();
47 var write_stream = writeStream(out.writer(), .{ .whitespace = .indent_2 });
48 defer write_stream.deinit();
4951 try write_stream.beginObject();
5052 try write_stream.objectField("foo");
5153 try write_stream.write(123);
......@@ -55,16 +57,7 @@ test writeStream {
5557 \\ "foo": 123
5658 \\}
5759 ;
58 try testing.expectEqualSlices(u8, expected, out.items);
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);
60 try testing.expectEqualSlices(u8, expected, out.getWritten());
6861}
6962
7063pub const ObjectMap = @import("json/dynamic.zig").ObjectMap;
......@@ -73,18 +66,18 @@ pub const Value = @import("json/dynamic.zig").Value;
7366
7467pub const ArrayHashMap = @import("json/hashmap.zig").ArrayHashMap;
7568
76pub const validate = @import("json/scanner.zig").validate;
77pub const Error = @import("json/scanner.zig").Error;
78pub const reader = @import("json/scanner.zig").reader;
79pub const default_buffer_size = @import("json/scanner.zig").default_buffer_size;
80pub const Token = @import("json/scanner.zig").Token;
81pub const TokenType = @import("json/scanner.zig").TokenType;
82pub const Diagnostics = @import("json/scanner.zig").Diagnostics;
83pub const AllocWhen = @import("json/scanner.zig").AllocWhen;
84pub const default_max_value_len = @import("json/scanner.zig").default_max_value_len;
85pub const Reader = @import("json/scanner.zig").Reader;
86pub const Scanner = @import("json/scanner.zig").Scanner;
87pub const isNumberFormattedLikeAnInteger = @import("json/scanner.zig").isNumberFormattedLikeAnInteger;
69pub const Scanner = @import("json/Scanner.zig");
70pub const validate = Scanner.validate;
71pub const Error = Scanner.Error;
72pub const reader = Scanner.reader;
73pub const default_buffer_size = Scanner.default_buffer_size;
74pub const Token = Scanner.Token;
75pub const TokenType = Scanner.TokenType;
76pub const Diagnostics = Scanner.Diagnostics;
77pub const AllocWhen = Scanner.AllocWhen;
78pub const default_max_value_len = Scanner.default_max_value_len;
79pub const Reader = Scanner.Reader;
80pub const isNumberFormattedLikeAnInteger = Scanner.isNumberFormattedLikeAnInteger;
8881
8982pub const ParseOptions = @import("json/static.zig").ParseOptions;
9083pub const Parsed = @import("json/static.zig").Parsed;
......@@ -99,27 +92,49 @@ pub const innerParseFromValue = @import("json/static.zig").innerParseFromValue;
9992pub const ParseError = @import("json/static.zig").ParseError;
10093pub const ParseFromValueError = @import("json/static.zig").ParseFromValueError;
10194
102pub const StringifyOptions = @import("json/stringify.zig").StringifyOptions;
103pub const stringify = @import("json/stringify.zig").stringify;
104pub const stringifyMaxDepth = @import("json/stringify.zig").stringifyMaxDepth;
105pub const stringifyArbitraryDepth = @import("json/stringify.zig").stringifyArbitraryDepth;
106pub const stringifyAlloc = @import("json/stringify.zig").stringifyAlloc;
107pub const writeStream = @import("json/stringify.zig").writeStream;
108pub const writeStreamMaxDepth = @import("json/stringify.zig").writeStreamMaxDepth;
109pub const writeStreamArbitraryDepth = @import("json/stringify.zig").writeStreamArbitraryDepth;
110pub const WriteStream = @import("json/stringify.zig").WriteStream;
111pub const encodeJsonString = @import("json/stringify.zig").encodeJsonString;
112pub const encodeJsonStringChars = @import("json/stringify.zig").encodeJsonStringChars;
113
114pub const Formatter = @import("json/fmt.zig").Formatter;
115pub const fmt = @import("json/fmt.zig").fmt;
95pub const Stringify = @import("json/Stringify.zig");
96
97/// Returns a formatter that formats the given value using stringify.
98pub fn fmt(value: anytype, options: Stringify.Options) Formatter(@TypeOf(value)) {
99 return Formatter(@TypeOf(value)){ .value = value, .options = options };
100}
101
102test fmt {
103 const expectFmt = std.testing.expectFmt;
104 try expectFmt("123", "{f}", .{fmt(@as(u32, 123), .{})});
105 try expectFmt(
106 \\{"num":927,"msg":"hello","sub":{"mybool":true}}
107 , "{f}", .{fmt(struct {
108 num: u32,
109 msg: []const u8,
110 sub: struct {
111 mybool: bool,
112 },
113 }{
114 .num = 927,
115 .msg = "hello",
116 .sub = .{ .mybool = true },
117 }, .{})});
118}
119
120/// Formats the given value using stringify.
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
117132test {
118133 _ = @import("json/test.zig");
119 _ = @import("json/scanner.zig");
134 _ = Scanner;
120135 _ = @import("json/dynamic.zig");
121136 _ = @import("json/hashmap.zig");
122137 _ = @import("json/static.zig");
123 _ = @import("json/stringify.zig");
138 _ = Stringify;
124139 _ = @import("json/JSONTestSuite_test.zig");
125140}
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;
44const ArrayList = std.ArrayList;
55const StringArrayHashMap = std.StringArrayHashMap;
66const Allocator = std.mem.Allocator;
7
8const StringifyOptions = @import("./stringify.zig").StringifyOptions;
9const stringify = @import("./stringify.zig").stringify;
7const json = std.json;
108
119const ParseOptions = @import("./static.zig").ParseOptions;
1210const ParseError = @import("./static.zig").ParseError;
1311
14const JsonScanner = @import("./scanner.zig").Scanner;
15const AllocWhen = @import("./scanner.zig").AllocWhen;
16const Token = @import("./scanner.zig").Token;
17const isNumberFormattedLikeAnInteger = @import("./scanner.zig").isNumberFormattedLikeAnInteger;
12const isNumberFormattedLikeAnInteger = @import("Scanner.zig").isNumberFormattedLikeAnInteger;
1813
1914pub const ObjectMap = StringArrayHashMap(Value);
2015pub const Array = ArrayList(Value);
......@@ -52,12 +47,11 @@ pub const Value = union(enum) {
5247 }
5348 }
5449
55 pub fn dump(self: Value) void {
56 std.debug.lockStdErr();
57 defer std.debug.unlockStdErr();
50 pub fn dump(v: Value) void {
51 const w = std.debug.lockStderrWriter(&.{});
52 defer std.debug.unlockStderrWriter();
5853
59 const stderr = std.fs.File.stderr().deprecatedWriter();
60 stringify(self, .{}, stderr) catch return;
54 json.Stringify.value(v, .{}, w) catch return;
6155 }
6256
6357 pub fn jsonStringify(value: @This(), jws: anytype) !void {
lib/std/json/dynamic_test.zig+18-22
......@@ -1,8 +1,10 @@
11const std = @import("std");
2const json = std.json;
23const mem = std.mem;
34const testing = std.testing;
45const ArenaAllocator = std.heap.ArenaAllocator;
56const Allocator = std.mem.Allocator;
7const Writer = std.io.Writer;
68
79const ObjectMap = @import("dynamic.zig").ObjectMap;
810const Array = @import("dynamic.zig").Array;
......@@ -14,8 +16,7 @@ const parseFromTokenSource = @import("static.zig").parseFromTokenSource;
1416const parseFromValueLeaky = @import("static.zig").parseFromValueLeaky;
1517const ParseOptions = @import("static.zig").ParseOptions;
1618
17const jsonReader = @import("scanner.zig").reader;
18const JsonReader = @import("scanner.zig").Reader;
19const Scanner = @import("Scanner.zig");
1920
2021test "json.parser.dynamic" {
2122 const s =
......@@ -70,14 +71,10 @@ test "json.parser.dynamic" {
7071 try testing.expect(mem.eql(u8, large_int.number_string, "18446744073709551615"));
7172}
7273
73const writeStream = @import("./stringify.zig").writeStream;
7474test "write json then parse it" {
7575 var out_buffer: [1000]u8 = undefined;
76
77 var fixed_buffer_stream = std.io.fixedBufferStream(&out_buffer);
78 const out_stream = fixed_buffer_stream.writer();
79 var jw = writeStream(out_stream, .{});
80 defer jw.deinit();
76 var fixed_writer: Writer = .fixed(&out_buffer);
77 var jw: json.Stringify = .{ .writer = &fixed_writer, .options = .{} };
8178
8279 try jw.beginObject();
8380
......@@ -101,8 +98,8 @@ test "write json then parse it" {
10198
10299 try jw.endObject();
103100
104 fixed_buffer_stream = std.io.fixedBufferStream(fixed_buffer_stream.getWritten());
105 var json_reader = jsonReader(testing.allocator, fixed_buffer_stream.reader());
101 var fbs: std.Io.Reader = .fixed(fixed_writer.buffered());
102 var json_reader: Scanner.Reader = .init(testing.allocator, &fbs);
106103 defer json_reader.deinit();
107104 var parsed = try parseFromTokenSource(Value, testing.allocator, &json_reader, .{});
108105 defer parsed.deinit();
......@@ -242,10 +239,9 @@ test "Value.jsonStringify" {
242239 .{ .object = obj },
243240 };
244241 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 });
248 defer jw.deinit();
244 var jw: json.Stringify = .{ .writer = &fixed_writer, .options = .{ .whitespace = .indent_1 } };
249245 try jw.write(array);
250246
251247 const expected =
......@@ -266,7 +262,7 @@ test "Value.jsonStringify" {
266262 \\ }
267263 \\]
268264 ;
269 try testing.expectEqualStrings(expected, fbs.getWritten());
265 try testing.expectEqualStrings(expected, fixed_writer.buffered());
270266}
271267
272268test "parseFromValue(std.json.Value,...)" {
......@@ -334,8 +330,8 @@ test "polymorphic parsing" {
334330test "long object value" {
335331 const value = "01234567890123456789";
336332 const doc = "{\"key\":\"" ++ value ++ "\"}";
337 var fbs = std.io.fixedBufferStream(doc);
338 var reader = smallBufferJsonReader(testing.allocator, fbs.reader());
333 var fbs: std.Io.Reader = .fixed(doc);
334 var reader = smallBufferJsonReader(testing.allocator, &fbs);
339335 defer reader.deinit();
340336 var parsed = try parseFromTokenSource(Value, testing.allocator, &reader, .{});
341337 defer parsed.deinit();
......@@ -367,8 +363,8 @@ test "many object keys" {
367363 \\ "k5": "v5"
368364 \\}
369365 ;
370 var fbs = std.io.fixedBufferStream(doc);
371 var reader = smallBufferJsonReader(testing.allocator, fbs.reader());
366 var fbs: std.Io.Reader = .fixed(doc);
367 var reader = smallBufferJsonReader(testing.allocator, &fbs);
372368 defer reader.deinit();
373369 var parsed = try parseFromTokenSource(Value, testing.allocator, &reader, .{});
374370 defer parsed.deinit();
......@@ -382,8 +378,8 @@ test "many object keys" {
382378
383379test "negative zero" {
384380 const doc = "-0";
385 var fbs = std.io.fixedBufferStream(doc);
386 var reader = smallBufferJsonReader(testing.allocator, fbs.reader());
381 var fbs: std.Io.Reader = .fixed(doc);
382 var reader = smallBufferJsonReader(testing.allocator, &fbs);
387383 defer reader.deinit();
388384 var parsed = try parseFromTokenSource(Value, testing.allocator, &reader, .{});
389385 defer parsed.deinit();
......@@ -391,6 +387,6 @@ test "negative zero" {
391387 try testing.expect(std.math.isNegativeZero(parsed.value.float));
392388}
393389
394fn smallBufferJsonReader(allocator: Allocator, io_reader: anytype) JsonReader(16, @TypeOf(io_reader)) {
395 return JsonReader(16, @TypeOf(io_reader)).init(allocator, io_reader);
390fn smallBufferJsonReader(allocator: Allocator, io_reader: anytype) Scanner.Reader {
391 return .init(allocator, io_reader);
396392}
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 @@
11const std = @import("std");
2const json = std.json;
23const testing = std.testing;
34
45const ArrayHashMap = @import("hashmap.zig").ArrayHashMap;
......@@ -7,10 +8,9 @@ const parseFromSlice = @import("static.zig").parseFromSlice;
78const parseFromSliceLeaky = @import("static.zig").parseFromSliceLeaky;
89const parseFromTokenSource = @import("static.zig").parseFromTokenSource;
910const parseFromValue = @import("static.zig").parseFromValue;
10const stringifyAlloc = @import("stringify.zig").stringifyAlloc;
1111const Value = @import("dynamic.zig").Value;
1212
13const jsonReader = @import("./scanner.zig").reader;
13const Scanner = @import("Scanner.zig");
1414
1515const T = struct {
1616 i: i32,
......@@ -39,8 +39,8 @@ test "parse json hashmap while streaming" {
3939 \\ "xyz": {"i": 1, "s": "w"}
4040 \\}
4141 ;
42 var stream = std.io.fixedBufferStream(doc);
43 var json_reader = jsonReader(testing.allocator, stream.reader());
42 var stream: std.Io.Reader = .fixed(doc);
43 var json_reader: Scanner.Reader = .init(testing.allocator, &stream);
4444
4545 var parsed = try parseFromTokenSource(
4646 ArrayHashMap(T),
......@@ -89,7 +89,7 @@ test "stringify json hashmap" {
8989 var value = ArrayHashMap(T){};
9090 defer value.deinit(testing.allocator);
9191 {
92 const doc = try stringifyAlloc(testing.allocator, value, .{});
92 const doc = try json.Stringify.valueAlloc(testing.allocator, value, .{});
9393 defer testing.allocator.free(doc);
9494 try testing.expectEqualStrings("{}", doc);
9595 }
......@@ -98,7 +98,7 @@ test "stringify json hashmap" {
9898 try value.map.put(testing.allocator, "xyz", .{ .i = 1, .s = "w" });
9999
100100 {
101 const doc = try stringifyAlloc(testing.allocator, value, .{});
101 const doc = try json.Stringify.valueAlloc(testing.allocator, value, .{});
102102 defer testing.allocator.free(doc);
103103 try testing.expectEqualStrings(
104104 \\{"abc":{"i":0,"s":"d"},"xyz":{"i":1,"s":"w"}}
......@@ -107,7 +107,7 @@ test "stringify json hashmap" {
107107
108108 try testing.expect(value.map.swapRemove("abc"));
109109 {
110 const doc = try stringifyAlloc(testing.allocator, value, .{});
110 const doc = try json.Stringify.valueAlloc(testing.allocator, value, .{});
111111 defer testing.allocator.free(doc);
112112 try testing.expectEqualStrings(
113113 \\{"xyz":{"i":1,"s":"w"}}
......@@ -116,7 +116,7 @@ test "stringify json hashmap" {
116116
117117 try testing.expect(value.map.swapRemove("xyz"));
118118 {
119 const doc = try stringifyAlloc(testing.allocator, value, .{});
119 const doc = try json.Stringify.valueAlloc(testing.allocator, value, .{});
120120 defer testing.allocator.free(doc);
121121 try testing.expectEqualStrings("{}", doc);
122122 }
......@@ -129,7 +129,7 @@ test "stringify json hashmap whitespace" {
129129 try value.map.put(testing.allocator, "xyz", .{ .i = 1, .s = "w" });
130130
131131 {
132 const doc = try stringifyAlloc(testing.allocator, value, .{ .whitespace = .indent_2 });
132 const doc = try json.Stringify.valueAlloc(testing.allocator, value, .{ .whitespace = .indent_2 });
133133 defer testing.allocator.free(doc);
134134 try testing.expectEqualStrings(
135135 \\{
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 @@
11const std = @import("std");
2const JsonScanner = @import("./scanner.zig").Scanner;
3const jsonReader = @import("./scanner.zig").reader;
4const JsonReader = @import("./scanner.zig").Reader;
5const Token = @import("./scanner.zig").Token;
6const TokenType = @import("./scanner.zig").TokenType;
7const Diagnostics = @import("./scanner.zig").Diagnostics;
8const Error = @import("./scanner.zig").Error;
9const validate = @import("./scanner.zig").validate;
10const isNumberFormattedLikeAnInteger = @import("./scanner.zig").isNumberFormattedLikeAnInteger;
2const Scanner = @import("Scanner.zig");
3const Token = Scanner.Token;
4const TokenType = Scanner.TokenType;
5const Diagnostics = Scanner.Diagnostics;
6const Error = Scanner.Error;
7const validate = Scanner.validate;
8const isNumberFormattedLikeAnInteger = Scanner.isNumberFormattedLikeAnInteger;
119
1210const example_document_str =
1311 \\{
......@@ -36,7 +34,7 @@ fn expectPeekNext(scanner_or_reader: anytype, expected_token_type: TokenType, ex
3634}
3735
3836test "token" {
39 var scanner = JsonScanner.initCompleteInput(std.testing.allocator, example_document_str);
37 var scanner = Scanner.initCompleteInput(std.testing.allocator, example_document_str);
4038 defer scanner.deinit();
4139
4240 try expectNext(&scanner, .object_begin);
......@@ -138,23 +136,25 @@ fn testAllTypes(source: anytype, large_buffer: bool) !void {
138136}
139137
140138test "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);
142140 defer scanner.deinit();
143141 try testAllTypes(&scanner, true);
144142
145 var stream = std.io.fixedBufferStream(all_types_test_case);
146 var json_reader = jsonReader(std.testing.allocator, stream.reader());
143 var stream: std.Io.Reader = .fixed(all_types_test_case);
144 var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream);
147145 defer json_reader.deinit();
148146 try testAllTypes(&json_reader, true);
149147
150 var tiny_stream = std.io.fixedBufferStream(all_types_test_case);
151 var tiny_json_reader = JsonReader(1, @TypeOf(tiny_stream.reader())).init(std.testing.allocator, tiny_stream.reader());
148 var tiny_buffer: [1]u8 = undefined;
149 var tiny_stream: std.testing.Reader = .init(&tiny_buffer, &.{.{ .buffer = all_types_test_case }});
150 tiny_stream.artificial_limit = .limited(1);
151 var tiny_json_reader: Scanner.Reader = .init(std.testing.allocator, &tiny_stream.interface);
152152 defer tiny_json_reader.deinit();
153153 try testAllTypes(&tiny_json_reader, false);
154154}
155155
156156test "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 }");
158158 defer scanner.deinit();
159159 try expectNext(&scanner, .array_begin);
160160 try expectNext(&scanner, Token{ .number = "102" });
......@@ -164,15 +164,15 @@ test "token mismatched close" {
164164}
165165
166166test "token premature object close" {
167 var scanner = JsonScanner.initCompleteInput(std.testing.allocator, "{ \"key\": }");
167 var scanner = Scanner.initCompleteInput(std.testing.allocator, "{ \"key\": }");
168168 defer scanner.deinit();
169169 try expectNext(&scanner, .object_begin);
170170 try expectNext(&scanner, Token{ .string = "key" });
171171 try std.testing.expectError(error.SyntaxError, scanner.next());
172172}
173173
174test "JsonScanner basic" {
175 var scanner = JsonScanner.initCompleteInput(std.testing.allocator, example_document_str);
174test "Scanner basic" {
175 var scanner = Scanner.initCompleteInput(std.testing.allocator, example_document_str);
176176 defer scanner.deinit();
177177
178178 while (true) {
......@@ -181,10 +181,10 @@ test "JsonScanner basic" {
181181 }
182182}
183183
184test "JsonReader basic" {
185 var stream = std.io.fixedBufferStream(example_document_str);
184test "Scanner.Reader basic" {
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);
188188 defer json_reader.deinit();
189189
190190 while (true) {
......@@ -215,7 +215,7 @@ const number_test_items = blk: {
215215
216216test "numbers" {
217217 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);
219219 defer scanner.deinit();
220220
221221 const token = try scanner.next();
......@@ -243,10 +243,10 @@ const string_test_cases = .{
243243
244244test "strings" {
245245 inline for (string_test_cases) |tuple| {
246 var stream = std.io.fixedBufferStream("\"" ++ tuple[0] ++ "\"");
246 var stream: std.Io.Reader = .fixed("\"" ++ tuple[0] ++ "\"");
247247 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
248248 defer arena.deinit();
249 var json_reader = jsonReader(std.testing.allocator, stream.reader());
249 var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream);
250250 defer json_reader.deinit();
251251
252252 const token = try json_reader.nextAlloc(arena.allocator(), .alloc_if_needed);
......@@ -289,7 +289,7 @@ test "nesting" {
289289}
290290
291291fn 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);
293293 defer scanner.deinit();
294294
295295 while (true) {
......@@ -352,12 +352,12 @@ fn expectEqualTokens(expected_token: Token, actual_token: Token) !void {
352352}
353353
354354fn testTinyBufferSize(document_str: []const u8) !void {
355 var tiny_stream = std.io.fixedBufferStream(document_str);
356 var normal_stream = std.io.fixedBufferStream(document_str);
355 var tiny_stream: std.Io.Reader = .fixed(document_str);
356 var normal_stream: std.Io.Reader = .fixed(document_str);
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);
359359 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);
361361 defer normal_json_reader.deinit();
362362
363363 expectEqualStreamOfTokens(&normal_json_reader, &tiny_json_reader) catch |err| {
......@@ -397,13 +397,13 @@ test "validate" {
397397}
398398
399399fn testSkipValue(s: []const u8) !void {
400 var scanner = JsonScanner.initCompleteInput(std.testing.allocator, s);
400 var scanner = Scanner.initCompleteInput(std.testing.allocator, s);
401401 defer scanner.deinit();
402402 try scanner.skipValue();
403403 try expectEqualTokens(.end_of_document, try scanner.next());
404404
405 var stream = std.io.fixedBufferStream(s);
406 var json_reader = jsonReader(std.testing.allocator, stream.reader());
405 var stream: std.Io.Reader = .fixed(s);
406 var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream);
407407 defer json_reader.deinit();
408408 try json_reader.skipValue();
409409 try expectEqualTokens(.end_of_document, try json_reader.next());
......@@ -441,7 +441,7 @@ fn testEnsureStackCapacity(do_ensure: bool) !void {
441441 try input_string.appendNTimes(std.testing.allocator, ']', nestings);
442442 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);
445445 defer scanner.deinit();
446446
447447 if (do_ensure) {
......@@ -473,17 +473,17 @@ fn testDiagnosticsFromSource(expected_error: ?anyerror, line: u64, col: u64, byt
473473 try std.testing.expectEqual(byte_offset, diagnostics.getByteOffset());
474474}
475475fn 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);
477477 defer scanner.deinit();
478478 try testDiagnosticsFromSource(expected_error, line, col, byte_offset, &scanner);
479479
480 var tiny_stream = std.io.fixedBufferStream(s);
481 var tiny_json_reader = JsonReader(1, @TypeOf(tiny_stream.reader())).init(std.testing.allocator, tiny_stream.reader());
480 var tiny_stream: std.Io.Reader = .fixed(s);
481 var tiny_json_reader: Scanner.Reader = .init(std.testing.allocator, &tiny_stream);
482482 defer tiny_json_reader.deinit();
483483 try testDiagnosticsFromSource(expected_error, line, col, byte_offset, &tiny_json_reader);
484484
485 var medium_stream = std.io.fixedBufferStream(s);
486 var medium_json_reader = JsonReader(5, @TypeOf(medium_stream.reader())).init(std.testing.allocator, medium_stream.reader());
485 var medium_stream: std.Io.Reader = .fixed(s);
486 var medium_json_reader: Scanner.Reader = .init(std.testing.allocator, &medium_stream);
487487 defer medium_json_reader.deinit();
488488 try testDiagnosticsFromSource(expected_error, line, col, byte_offset, &medium_json_reader);
489489}
lib/std/json/static.zig+5-5
......@@ -4,11 +4,11 @@ const Allocator = std.mem.Allocator;
44const ArenaAllocator = std.heap.ArenaAllocator;
55const ArrayList = std.ArrayList;
66
7const Scanner = @import("./scanner.zig").Scanner;
8const Token = @import("./scanner.zig").Token;
9const AllocWhen = @import("./scanner.zig").AllocWhen;
10const default_max_value_len = @import("./scanner.zig").default_max_value_len;
11const isNumberFormattedLikeAnInteger = @import("./scanner.zig").isNumberFormattedLikeAnInteger;
7const Scanner = @import("Scanner.zig");
8const Token = Scanner.Token;
9const AllocWhen = Scanner.AllocWhen;
10const default_max_value_len = Scanner.default_max_value_len;
11const isNumberFormattedLikeAnInteger = Scanner.isNumberFormattedLikeAnInteger;
1212
1313const Value = @import("./dynamic.zig").Value;
1414const Array = @import("./dynamic.zig").Array;
lib/std/json/static_test.zig+14-16
......@@ -12,9 +12,7 @@ const parseFromValue = @import("./static.zig").parseFromValue;
1212const parseFromValueLeaky = @import("./static.zig").parseFromValueLeaky;
1313const ParseOptions = @import("./static.zig").ParseOptions;
1414
15const JsonScanner = @import("./scanner.zig").Scanner;
16const jsonReader = @import("./scanner.zig").reader;
17const Diagnostics = @import("./scanner.zig").Diagnostics;
15const Scanner = @import("Scanner.zig");
1816
1917const Value = @import("./dynamic.zig").Value;
2018
......@@ -300,9 +298,9 @@ const subnamespaces_0_doc =
300298fn testAllParseFunctions(comptime T: type, expected: T, doc: []const u8) !void {
301299 // First do the one with the debug info in case we get a SyntaxError or something.
302300 {
303 var scanner = JsonScanner.initCompleteInput(testing.allocator, doc);
301 var scanner = Scanner.initCompleteInput(testing.allocator, doc);
304302 defer scanner.deinit();
305 var diagnostics = Diagnostics{};
303 var diagnostics = Scanner.Diagnostics{};
306304 scanner.enableDiagnostics(&diagnostics);
307305 var parsed = parseFromTokenSource(T, testing.allocator, &scanner, .{}) catch |e| {
308306 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 {
317315 try testing.expectEqualDeep(expected, parsed.value);
318316 }
319317 {
320 var stream = std.io.fixedBufferStream(doc);
321 var json_reader = jsonReader(std.testing.allocator, stream.reader());
318 var stream: std.Io.Reader = .fixed(doc);
319 var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream);
322320 defer json_reader.deinit();
323321 var parsed = try parseFromTokenSource(T, testing.allocator, &json_reader, .{});
324322 defer parsed.deinit();
......@@ -331,13 +329,13 @@ fn testAllParseFunctions(comptime T: type, expected: T, doc: []const u8) !void {
331329 try testing.expectEqualDeep(expected, try parseFromSliceLeaky(T, arena.allocator(), doc, .{}));
332330 }
333331 {
334 var scanner = JsonScanner.initCompleteInput(testing.allocator, doc);
332 var scanner = Scanner.initCompleteInput(testing.allocator, doc);
335333 defer scanner.deinit();
336334 try testing.expectEqualDeep(expected, try parseFromTokenSourceLeaky(T, arena.allocator(), &scanner, .{}));
337335 }
338336 {
339 var stream = std.io.fixedBufferStream(doc);
340 var json_reader = jsonReader(std.testing.allocator, stream.reader());
337 var stream: std.Io.Reader = .fixed(doc);
338 var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream);
341339 defer json_reader.deinit();
342340 try testing.expectEqualDeep(expected, try parseFromTokenSourceLeaky(T, arena.allocator(), &json_reader, .{}));
343341 }
......@@ -763,7 +761,7 @@ test "parse exponential into int" {
763761
764762test "parseFromTokenSource" {
765763 {
766 var scanner = JsonScanner.initCompleteInput(testing.allocator, "123");
764 var scanner = Scanner.initCompleteInput(testing.allocator, "123");
767765 defer scanner.deinit();
768766 var parsed = try parseFromTokenSource(u32, testing.allocator, &scanner, .{});
769767 defer parsed.deinit();
......@@ -771,8 +769,8 @@ test "parseFromTokenSource" {
771769 }
772770
773771 {
774 var stream = std.io.fixedBufferStream("123");
775 var json_reader = jsonReader(std.testing.allocator, stream.reader());
772 var stream: std.Io.Reader = .fixed("123");
773 var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream);
776774 defer json_reader.deinit();
777775 var parsed = try parseFromTokenSource(u32, testing.allocator, &json_reader, .{});
778776 defer parsed.deinit();
......@@ -836,7 +834,7 @@ test "json parse partial" {
836834 \\}
837835 ;
838836 const allocator = testing.allocator;
839 var scanner = JsonScanner.initCompleteInput(allocator, str);
837 var scanner = Scanner.initCompleteInput(allocator, str);
840838 defer scanner.deinit();
841839
842840 var arena = ArenaAllocator.init(allocator);
......@@ -886,8 +884,8 @@ test "json parse allocate when streaming" {
886884 var arena = ArenaAllocator.init(allocator);
887885 defer arena.deinit();
888886
889 var stream = std.io.fixedBufferStream(str);
890 var json_reader = jsonReader(std.testing.allocator, stream.reader());
887 var stream: std.Io.Reader = .fixed(str);
888 var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream);
891889
892890 const parsed = parseFromTokenSourceLeaky(T, arena.allocator(), &json_reader, .{}) catch |err| {
893891 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 @@
11const std = @import("std");
2const json = std.json;
23const testing = std.testing;
34const parseFromSlice = @import("./static.zig").parseFromSlice;
4const validate = @import("./scanner.zig").validate;
5const JsonScanner = @import("./scanner.zig").Scanner;
5const Scanner = @import("./Scanner.zig");
66const Value = @import("./dynamic.zig").Value;
7const stringifyAlloc = @import("./stringify.zig").stringifyAlloc;
87
98// Support for JSONTestSuite.zig
109pub fn ok(s: []const u8) !void {
......@@ -20,7 +19,7 @@ pub fn any(s: []const u8) !void {
2019 testHighLevelDynamicParser(s) catch {};
2120}
2221fn testLowLevelScanner(s: []const u8) !void {
23 var scanner = JsonScanner.initCompleteInput(testing.allocator, s);
22 var scanner = Scanner.initCompleteInput(testing.allocator, s);
2423 defer scanner.deinit();
2524 while (true) {
2625 const token = try scanner.next();
......@@ -47,12 +46,12 @@ test "n_object_closed_missing_value" {
4746}
4847
4948fn 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
5251 var parsed = try parseFromSlice(Value, testing.allocator, s, .{});
5352 defer parsed.deinit();
5453
55 const rendered = try stringifyAlloc(testing.allocator, parsed.value, .{});
54 const rendered = try json.Stringify.valueAlloc(testing.allocator, parsed.value, .{});
5655 defer testing.allocator.free(rendered);
5756
5857 try testing.expectEqualStrings(s, rendered);
lib/std/zig.zig+16-13
......@@ -446,8 +446,8 @@ pub fn fmtString(bytes: []const u8) std.fmt.Formatter([]const u8, stringEscape)
446446}
447447
448448/// Return a formatter for escaping a single quoted Zig string.
449pub fn fmtChar(bytes: []const u8) std.fmt.Formatter([]const u8, charEscape) {
450 return .{ .data = bytes };
449pub fn fmtChar(c: u21) std.fmt.Formatter(u21, charEscape) {
450 return .{ .data = c };
451451}
452452
453453test fmtString {
......@@ -458,9 +458,7 @@ test fmtString {
458458}
459459
460460test fmtChar {
461 try std.testing.expectFmt(
462 \\" \\ hi \x07 \x11 " derp \'"
463 , "\"{f}\"", .{fmtChar(" \\ hi \x07 \x11 \" derp '")});
461 try std.testing.expectFmt("c \\u{26a1}", "{f} {f}", .{ fmtChar('c'), fmtChar('⚡') });
464462}
465463
466464/// Print the string as escaped contents of a double quoted string.
......@@ -480,21 +478,26 @@ pub fn stringEscape(bytes: []const u8, w: *Writer) Writer.Error!void {
480478 };
481479}
482480
483/// Print the string as escaped contents of a single-quoted string.
484pub fn charEscape(bytes: []const u8, w: *Writer) Writer.Error!void {
485 for (bytes) |byte| switch (byte) {
481/// Print as escaped contents of a single-quoted string.
482pub fn charEscape(codepoint: u21, w: *Writer) Writer.Error!void {
483 switch (codepoint) {
486484 '\n' => try w.writeAll("\\n"),
487485 '\r' => try w.writeAll("\\r"),
488486 '\t' => try w.writeAll("\\t"),
489487 '\\' => try w.writeAll("\\\\"),
490 '"' => try w.writeByte('"'),
491488 '\'' => try w.writeAll("\\'"),
492 ' ', '!', '#'...'&', '('...'[', ']'...'~' => try w.writeByte(byte),
489 '"', ' ', '!', '#'...'&', '('...'[', ']'...'~' => try w.writeByte(@intCast(codepoint)),
493490 else => {
494 try w.writeAll("\\x");
495 try w.printInt(byte, 16, .lower, .{ .width = 2, .fill = '0' });
491 if (std.math.cast(u8, codepoint)) |byte| {
492 try w.writeAll("\\x");
493 try w.printInt(byte, 16, .lower, .{ .width = 2, .fill = '0' });
494 } else {
495 try w.writeAll("\\u{");
496 try w.printInt(codepoint, 16, .lower, .{});
497 try w.writeByte('}');
498 }
496499 },
497 };
500 }
498501}
499502
500503pub fn isValidId(bytes: []const u8) bool {
lib/std/zig/Ast.zig+1-1
......@@ -574,7 +574,7 @@ pub fn renderError(tree: Ast, parse_error: Error, w: *Writer) Writer.Error!void
574574 '/' => "comment",
575575 else => unreachable,
576576 },
577 std.zig.fmtChar(tok_slice[parse_error.extra.offset..][0..1]),
577 std.zig.fmtChar(tok_slice[parse_error.extra.offset]),
578578 });
579579 },
580580
lib/std/zon.zig+1
......@@ -38,6 +38,7 @@
3838
3939pub const parse = @import("zon/parse.zig");
4040pub const stringify = @import("zon/stringify.zig");
41pub const Serializer = @import("zon/Serializer.zig");
4142
4243test {
4344 _ = parse;
lib/std/zon/Serializer.zig created+929
......@@ -0,0 +1,929 @@
1//! Lower level control over serialization, you can create a new instance with `serializer`.
2//!
3//! Useful when you want control over which fields are serialized, how they're represented,
4//! or want to write a ZON object that does not exist in memory.
5//!
6//! You can serialize values with `value`. To serialize recursive types, the following are provided:
7//! * `valueMaxDepth`
8//! * `valueArbitraryDepth`
9//!
10//! You can also serialize values using specific notations:
11//! * `int`
12//! * `float`
13//! * `codePoint`
14//! * `tuple`
15//! * `tupleMaxDepth`
16//! * `tupleArbitraryDepth`
17//! * `string`
18//! * `multilineString`
19//!
20//! For manual serialization of containers, see:
21//! * `beginStruct`
22//! * `beginTuple`
23
24options: Options = .{},
25indent_level: u8 = 0,
26writer: *Writer,
27
28const Serializer = @This();
29const std = @import("std");
30const assert = std.debug.assert;
31const Writer = std.Io.Writer;
32
33pub const Error = Writer.Error;
34pub const DepthError = Error || error{ExceededMaxDepth};
35
36pub const Options = struct {
37 /// If false, only syntactically necessary whitespace is emitted.
38 whitespace: bool = true,
39};
40
41/// Options for manual serialization of container types.
42pub const ContainerOptions = struct {
43 /// The whitespace style that should be used for this container. Ignored if whitespace is off.
44 whitespace_style: union(enum) {
45 /// If true, wrap every field. If false do not.
46 wrap: bool,
47 /// Automatically decide whether to wrap or not based on the number of fields. Following
48 /// the standard rule of thumb, containers with more than two fields are wrapped.
49 fields: usize,
50 } = .{ .wrap = true },
51
52 fn shouldWrap(self: ContainerOptions) bool {
53 return switch (self.whitespace_style) {
54 .wrap => |wrap| wrap,
55 .fields => |fields| fields > 2,
56 };
57 }
58};
59
60/// Options for serialization of an individual value.
61///
62/// See `SerializeOptions` for more information on these options.
63pub const ValueOptions = struct {
64 emit_codepoint_literals: EmitCodepointLiterals = .never,
65 emit_strings_as_containers: bool = false,
66 emit_default_optional_fields: bool = true,
67};
68
69/// Determines when to emit Unicode code point literals as opposed to integer literals.
70pub const EmitCodepointLiterals = enum {
71 /// Never emit Unicode code point literals.
72 never,
73 /// Emit Unicode code point literals for any `u8` in the printable ASCII range.
74 printable_ascii,
75 /// Emit Unicode code point literals for any unsigned integer with 21 bits or fewer
76 /// whose value is a valid non-surrogate code point.
77 always,
78
79 /// If the value should be emitted as a Unicode codepoint, return it as a u21.
80 fn emitAsCodepoint(self: @This(), val: anytype) ?u21 {
81 // Rule out incompatible integer types
82 switch (@typeInfo(@TypeOf(val))) {
83 .int => |int_info| if (int_info.signedness == .signed or int_info.bits > 21) {
84 return null;
85 },
86 .comptime_int => {},
87 else => comptime unreachable,
88 }
89
90 // Return null if the value shouldn't be printed as a Unicode codepoint, or the value casted
91 // to a u21 if it should.
92 switch (self) {
93 .always => {
94 const c = std.math.cast(u21, val) orelse return null;
95 if (!std.unicode.utf8ValidCodepoint(c)) return null;
96 return c;
97 },
98 .printable_ascii => {
99 const c = std.math.cast(u8, val) orelse return null;
100 if (!std.ascii.isPrint(c)) return null;
101 return c;
102 },
103 .never => {
104 return null;
105 },
106 }
107 }
108};
109
110/// Serialize a value, similar to `serialize`.
111pub fn value(self: *Serializer, val: anytype, options: ValueOptions) Error!void {
112 comptime assert(!typeIsRecursive(@TypeOf(val)));
113 return self.valueArbitraryDepth(val, options);
114}
115
116/// Serialize a value, similar to `serializeMaxDepth`.
117/// Can return `error.ExceededMaxDepth`.
118pub fn valueMaxDepth(self: *Serializer, val: anytype, options: ValueOptions, depth: usize) DepthError!void {
119 try checkValueDepth(val, depth);
120 return self.valueArbitraryDepth(val, options);
121}
122
123/// Serialize a value, similar to `serializeArbitraryDepth`.
124pub fn valueArbitraryDepth(self: *Serializer, val: anytype, options: ValueOptions) Error!void {
125 comptime assert(canSerializeType(@TypeOf(val)));
126 switch (@typeInfo(@TypeOf(val))) {
127 .int, .comptime_int => if (options.emit_codepoint_literals.emitAsCodepoint(val)) |c| {
128 self.codePoint(c) catch |err| switch (err) {
129 error.InvalidCodepoint => unreachable, // Already validated
130 else => |e| return e,
131 };
132 } else {
133 try self.int(val);
134 },
135 .float, .comptime_float => try self.float(val),
136 .bool, .null => try self.writer.print("{}", .{val}),
137 .enum_literal => try self.ident(@tagName(val)),
138 .@"enum" => try self.ident(@tagName(val)),
139 .pointer => |pointer| {
140 // Try to serialize as a string
141 const item: ?type = switch (@typeInfo(pointer.child)) {
142 .array => |array| array.child,
143 else => if (pointer.size == .slice) pointer.child else null,
144 };
145 if (item == u8 and
146 (pointer.sentinel() == null or pointer.sentinel() == 0) and
147 !options.emit_strings_as_containers)
148 {
149 return try self.string(val);
150 }
151
152 // Serialize as either a tuple or as the child type
153 switch (pointer.size) {
154 .slice => try self.tupleImpl(val, options),
155 .one => try self.valueArbitraryDepth(val.*, options),
156 else => comptime unreachable,
157 }
158 },
159 .array => {
160 var container = try self.beginTuple(
161 .{ .whitespace_style = .{ .fields = val.len } },
162 );
163 for (val) |item_val| {
164 try container.fieldArbitraryDepth(item_val, options);
165 }
166 try container.end();
167 },
168 .@"struct" => |@"struct"| if (@"struct".is_tuple) {
169 var container = try self.beginTuple(
170 .{ .whitespace_style = .{ .fields = @"struct".fields.len } },
171 );
172 inline for (val) |field_value| {
173 try container.fieldArbitraryDepth(field_value, options);
174 }
175 try container.end();
176 } else {
177 // Decide which fields to emit
178 const fields, const skipped: [@"struct".fields.len]bool = if (options.emit_default_optional_fields) b: {
179 break :b .{ @"struct".fields.len, @splat(false) };
180 } else b: {
181 var fields = @"struct".fields.len;
182 var skipped: [@"struct".fields.len]bool = @splat(false);
183 inline for (@"struct".fields, &skipped) |field_info, *skip| {
184 if (field_info.default_value_ptr) |ptr| {
185 const default: *const field_info.type = @ptrCast(@alignCast(ptr));
186 const field_value = @field(val, field_info.name);
187 if (std.meta.eql(field_value, default.*)) {
188 skip.* = true;
189 fields -= 1;
190 }
191 }
192 }
193 break :b .{ fields, skipped };
194 };
195
196 // Emit those fields
197 var container = try self.beginStruct(
198 .{ .whitespace_style = .{ .fields = fields } },
199 );
200 inline for (@"struct".fields, skipped) |field_info, skip| {
201 if (!skip) {
202 try container.fieldArbitraryDepth(
203 field_info.name,
204 @field(val, field_info.name),
205 options,
206 );
207 }
208 }
209 try container.end();
210 },
211 .@"union" => |@"union"| {
212 comptime assert(@"union".tag_type != null);
213 switch (val) {
214 inline else => |pl, tag| if (@TypeOf(pl) == void)
215 try self.writer.print(".{s}", .{@tagName(tag)})
216 else {
217 var container = try self.beginStruct(.{ .whitespace_style = .{ .fields = 1 } });
218
219 try container.fieldArbitraryDepth(
220 @tagName(tag),
221 pl,
222 options,
223 );
224
225 try container.end();
226 },
227 }
228 },
229 .optional => if (val) |inner| {
230 try self.valueArbitraryDepth(inner, options);
231 } else {
232 try self.writer.writeAll("null");
233 },
234 .vector => |vector| {
235 var container = try self.beginTuple(
236 .{ .whitespace_style = .{ .fields = vector.len } },
237 );
238 for (0..vector.len) |i| {
239 try container.fieldArbitraryDepth(val[i], options);
240 }
241 try container.end();
242 },
243
244 else => comptime unreachable,
245 }
246}
247
248/// Serialize an integer.
249pub fn int(self: *Serializer, val: anytype) Error!void {
250 try self.writer.printInt(val, 10, .lower, .{});
251}
252
253/// Serialize a float.
254pub fn float(self: *Serializer, val: anytype) Error!void {
255 switch (@typeInfo(@TypeOf(val))) {
256 .float => if (std.math.isNan(val)) {
257 return self.writer.writeAll("nan");
258 } else if (std.math.isPositiveInf(val)) {
259 return self.writer.writeAll("inf");
260 } else if (std.math.isNegativeInf(val)) {
261 return self.writer.writeAll("-inf");
262 } else if (std.math.isNegativeZero(val)) {
263 return self.writer.writeAll("-0.0");
264 } else {
265 try self.writer.print("{d}", .{val});
266 },
267 .comptime_float => if (val == 0) {
268 return self.writer.writeAll("0");
269 } else {
270 try self.writer.print("{d}", .{val});
271 },
272 else => comptime unreachable,
273 }
274}
275
276/// Serialize `name` as an identifier prefixed with `.`.
277///
278/// Escapes the identifier if necessary.
279pub fn ident(self: *Serializer, name: []const u8) Error!void {
280 try self.writer.print(".{f}", .{std.zig.fmtIdPU(name)});
281}
282
283pub const CodePointError = Error || error{InvalidCodepoint};
284
285/// Serialize `val` as a Unicode codepoint.
286///
287/// Returns `error.InvalidCodepoint` if `val` is not a valid Unicode codepoint.
288pub fn codePoint(self: *Serializer, val: u21) CodePointError!void {
289 try self.writer.print("'{f}'", .{std.zig.fmtChar(val)});
290}
291
292/// Like `value`, but always serializes `val` as a tuple.
293///
294/// Will fail at comptime if `val` is not a tuple, array, pointer to an array, or slice.
295pub fn tuple(self: *Serializer, val: anytype, options: ValueOptions) Error!void {
296 comptime assert(!typeIsRecursive(@TypeOf(val)));
297 try self.tupleArbitraryDepth(val, options);
298}
299
300/// Like `tuple`, but recursive types are allowed.
301///
302/// Returns `error.ExceededMaxDepth` if `depth` is exceeded.
303pub fn tupleMaxDepth(
304 self: *Serializer,
305 val: anytype,
306 options: ValueOptions,
307 depth: usize,
308) DepthError!void {
309 try checkValueDepth(val, depth);
310 try self.tupleArbitraryDepth(val, options);
311}
312
313/// Like `tuple`, but recursive types are allowed.
314///
315/// It is the caller's responsibility to ensure that `val` does not contain cycles.
316pub fn tupleArbitraryDepth(
317 self: *Serializer,
318 val: anytype,
319 options: ValueOptions,
320) Error!void {
321 try self.tupleImpl(val, options);
322}
323
324fn tupleImpl(self: *Serializer, val: anytype, options: ValueOptions) Error!void {
325 comptime assert(canSerializeType(@TypeOf(val)));
326 switch (@typeInfo(@TypeOf(val))) {
327 .@"struct" => {
328 var container = try self.beginTuple(.{ .whitespace_style = .{ .fields = val.len } });
329 inline for (val) |item_val| {
330 try container.fieldArbitraryDepth(item_val, options);
331 }
332 try container.end();
333 },
334 .pointer, .array => {
335 var container = try self.beginTuple(.{ .whitespace_style = .{ .fields = val.len } });
336 for (val) |item_val| {
337 try container.fieldArbitraryDepth(item_val, options);
338 }
339 try container.end();
340 },
341 else => comptime unreachable,
342 }
343}
344
345/// Like `value`, but always serializes `val` as a string.
346pub fn string(self: *Serializer, val: []const u8) Error!void {
347 try self.writer.print("\"{f}\"", .{std.zig.fmtString(val)});
348}
349
350/// Options for formatting multiline strings.
351pub const MultilineStringOptions = struct {
352 /// If top level is true, whitespace before and after the multiline string is elided.
353 /// If it is true, a newline is printed, then the value, followed by a newline, and if
354 /// whitespace is true any necessary indentation follows.
355 top_level: bool = false,
356};
357
358pub const MultilineStringError = Error || error{InnerCarriageReturn};
359
360/// Like `value`, but always serializes to a multiline string literal.
361///
362/// Returns `error.InnerCarriageReturn` if `val` contains a CR not followed by a newline,
363/// since multiline strings cannot represent CR without a following newline.
364pub fn multilineString(
365 self: *Serializer,
366 val: []const u8,
367 options: MultilineStringOptions,
368) MultilineStringError!void {
369 // Make sure the string does not contain any carriage returns not followed by a newline
370 var i: usize = 0;
371 while (i < val.len) : (i += 1) {
372 if (val[i] == '\r') {
373 if (i + 1 < val.len) {
374 if (val[i + 1] == '\n') {
375 i += 1;
376 continue;
377 }
378 }
379 return error.InnerCarriageReturn;
380 }
381 }
382
383 if (!options.top_level) {
384 try self.newline();
385 try self.indent();
386 }
387
388 try self.writer.writeAll("\\\\");
389 for (val) |c| {
390 if (c != '\r') {
391 try self.writer.writeByte(c); // We write newlines here even if whitespace off
392 if (c == '\n') {
393 try self.indent();
394 try self.writer.writeAll("\\\\");
395 }
396 }
397 }
398
399 if (!options.top_level) {
400 try self.writer.writeByte('\n'); // Even if whitespace off
401 try self.indent();
402 }
403}
404
405/// Create a `Struct` for writing ZON structs field by field.
406pub fn beginStruct(self: *Serializer, options: ContainerOptions) Error!Struct {
407 return Struct.begin(self, options);
408}
409
410/// Creates a `Tuple` for writing ZON tuples field by field.
411pub fn beginTuple(self: *Serializer, options: ContainerOptions) Error!Tuple {
412 return Tuple.begin(self, options);
413}
414
415fn indent(self: *Serializer) Error!void {
416 if (self.options.whitespace) {
417 try self.writer.splatByteAll(' ', 4 * self.indent_level);
418 }
419}
420
421fn newline(self: *Serializer) Error!void {
422 if (self.options.whitespace) {
423 try self.writer.writeByte('\n');
424 }
425}
426
427fn newlineOrSpace(self: *Serializer, len: usize) Error!void {
428 if (self.containerShouldWrap(len)) {
429 try self.newline();
430 } else {
431 try self.space();
432 }
433}
434
435fn space(self: *Serializer) Error!void {
436 if (self.options.whitespace) {
437 try self.writer.writeByte(' ');
438 }
439}
440
441/// Writes ZON tuples field by field.
442pub const Tuple = struct {
443 container: Container,
444
445 fn begin(parent: *Serializer, options: ContainerOptions) Error!Tuple {
446 return .{
447 .container = try Container.begin(parent, .anon, options),
448 };
449 }
450
451 /// Finishes serializing the tuple.
452 ///
453 /// Prints a trailing comma as configured when appropriate, and the closing bracket.
454 pub fn end(self: *Tuple) Error!void {
455 try self.container.end();
456 self.* = undefined;
457 }
458
459 /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`.
460 pub fn field(
461 self: *Tuple,
462 val: anytype,
463 options: ValueOptions,
464 ) Error!void {
465 try self.container.field(null, val, options);
466 }
467
468 /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`.
469 /// Returns `error.ExceededMaxDepth` if `depth` is exceeded.
470 pub fn fieldMaxDepth(
471 self: *Tuple,
472 val: anytype,
473 options: ValueOptions,
474 depth: usize,
475 ) DepthError!void {
476 try self.container.fieldMaxDepth(null, val, options, depth);
477 }
478
479 /// Serialize a field. Equivalent to calling `fieldPrefix` followed by
480 /// `valueArbitraryDepth`.
481 pub fn fieldArbitraryDepth(
482 self: *Tuple,
483 val: anytype,
484 options: ValueOptions,
485 ) Error!void {
486 try self.container.fieldArbitraryDepth(null, val, options);
487 }
488
489 /// Starts a field with a struct as a value. Returns the struct.
490 pub fn beginStructField(
491 self: *Tuple,
492 options: ContainerOptions,
493 ) Error!Struct {
494 try self.fieldPrefix();
495 return self.container.serializer.beginStruct(options);
496 }
497
498 /// Starts a field with a tuple as a value. Returns the tuple.
499 pub fn beginTupleField(
500 self: *Tuple,
501 options: ContainerOptions,
502 ) Error!Tuple {
503 try self.fieldPrefix();
504 return self.container.serializer.beginTuple(options);
505 }
506
507 /// Print a field prefix. This prints any necessary commas, and whitespace as
508 /// configured. Useful if you want to serialize the field value yourself.
509 pub fn fieldPrefix(self: *Tuple) Error!void {
510 try self.container.fieldPrefix(null);
511 }
512};
513
514/// Writes ZON structs field by field.
515pub const Struct = struct {
516 container: Container,
517
518 fn begin(parent: *Serializer, options: ContainerOptions) Error!Struct {
519 return .{
520 .container = try Container.begin(parent, .named, options),
521 };
522 }
523
524 /// Finishes serializing the struct.
525 ///
526 /// Prints a trailing comma as configured when appropriate, and the closing bracket.
527 pub fn end(self: *Struct) Error!void {
528 try self.container.end();
529 self.* = undefined;
530 }
531
532 /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`.
533 pub fn field(
534 self: *Struct,
535 name: []const u8,
536 val: anytype,
537 options: ValueOptions,
538 ) Error!void {
539 try self.container.field(name, val, options);
540 }
541
542 /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`.
543 /// Returns `error.ExceededMaxDepth` if `depth` is exceeded.
544 pub fn fieldMaxDepth(
545 self: *Struct,
546 name: []const u8,
547 val: anytype,
548 options: ValueOptions,
549 depth: usize,
550 ) DepthError!void {
551 try self.container.fieldMaxDepth(name, val, options, depth);
552 }
553
554 /// Serialize a field. Equivalent to calling `fieldPrefix` followed by
555 /// `valueArbitraryDepth`.
556 pub fn fieldArbitraryDepth(
557 self: *Struct,
558 name: []const u8,
559 val: anytype,
560 options: ValueOptions,
561 ) Error!void {
562 try self.container.fieldArbitraryDepth(name, val, options);
563 }
564
565 /// Starts a field with a struct as a value. Returns the struct.
566 pub fn beginStructField(
567 self: *Struct,
568 name: []const u8,
569 options: ContainerOptions,
570 ) Error!Struct {
571 try self.fieldPrefix(name);
572 return self.container.serializer.beginStruct(options);
573 }
574
575 /// Starts a field with a tuple as a value. Returns the tuple.
576 pub fn beginTupleField(
577 self: *Struct,
578 name: []const u8,
579 options: ContainerOptions,
580 ) Error!Tuple {
581 try self.fieldPrefix(name);
582 return self.container.serializer.beginTuple(options);
583 }
584
585 /// Print a field prefix. This prints any necessary commas, the field name (escaped if
586 /// necessary) and whitespace as configured. Useful if you want to serialize the field
587 /// value yourself.
588 pub fn fieldPrefix(self: *Struct, name: []const u8) Error!void {
589 try self.container.fieldPrefix(name);
590 }
591};
592
593const Container = struct {
594 const FieldStyle = enum { named, anon };
595
596 serializer: *Serializer,
597 field_style: FieldStyle,
598 options: ContainerOptions,
599 empty: bool,
600
601 fn begin(
602 sz: *Serializer,
603 field_style: FieldStyle,
604 options: ContainerOptions,
605 ) Error!Container {
606 if (options.shouldWrap()) sz.indent_level +|= 1;
607 try sz.writer.writeAll(".{");
608 return .{
609 .serializer = sz,
610 .field_style = field_style,
611 .options = options,
612 .empty = true,
613 };
614 }
615
616 fn end(self: *Container) Error!void {
617 if (self.options.shouldWrap()) self.serializer.indent_level -|= 1;
618 if (!self.empty) {
619 if (self.options.shouldWrap()) {
620 if (self.serializer.options.whitespace) {
621 try self.serializer.writer.writeByte(',');
622 }
623 try self.serializer.newline();
624 try self.serializer.indent();
625 } else if (!self.shouldElideSpaces()) {
626 try self.serializer.space();
627 }
628 }
629 try self.serializer.writer.writeByte('}');
630 self.* = undefined;
631 }
632
633 fn fieldPrefix(self: *Container, name: ?[]const u8) Error!void {
634 if (!self.empty) {
635 try self.serializer.writer.writeByte(',');
636 }
637 self.empty = false;
638 if (self.options.shouldWrap()) {
639 try self.serializer.newline();
640 } else if (!self.shouldElideSpaces()) {
641 try self.serializer.space();
642 }
643 if (self.options.shouldWrap()) try self.serializer.indent();
644 if (name) |n| {
645 try self.serializer.ident(n);
646 try self.serializer.space();
647 try self.serializer.writer.writeByte('=');
648 try self.serializer.space();
649 }
650 }
651
652 fn field(
653 self: *Container,
654 name: ?[]const u8,
655 val: anytype,
656 options: ValueOptions,
657 ) Error!void {
658 comptime assert(!typeIsRecursive(@TypeOf(val)));
659 try self.fieldArbitraryDepth(name, val, options);
660 }
661
662 /// Returns `error.ExceededMaxDepth` if `depth` is exceeded.
663 fn fieldMaxDepth(
664 self: *Container,
665 name: ?[]const u8,
666 val: anytype,
667 options: ValueOptions,
668 depth: usize,
669 ) DepthError!void {
670 try checkValueDepth(val, depth);
671 try self.fieldArbitraryDepth(name, val, options);
672 }
673
674 fn fieldArbitraryDepth(
675 self: *Container,
676 name: ?[]const u8,
677 val: anytype,
678 options: ValueOptions,
679 ) Error!void {
680 try self.fieldPrefix(name);
681 try self.serializer.valueArbitraryDepth(val, options);
682 }
683
684 fn shouldElideSpaces(self: *const Container) bool {
685 return switch (self.options.whitespace_style) {
686 .fields => |fields| self.field_style != .named and fields == 1,
687 else => false,
688 };
689 }
690};
691
692test Serializer {
693 var discarding: Writer.Discarding = .init(&.{});
694 var s: Serializer = .{ .writer = &discarding.writer };
695 var vec2 = try s.beginStruct(.{});
696 try vec2.field("x", 1.5, .{});
697 try vec2.fieldPrefix("prefix");
698 try s.value(2.5, .{});
699 try vec2.end();
700}
701
702inline fn typeIsRecursive(comptime T: type) bool {
703 return comptime typeIsRecursiveInner(T, &.{});
704}
705
706fn typeIsRecursiveInner(comptime T: type, comptime prev_visited: []const type) bool {
707 for (prev_visited) |V| {
708 if (V == T) return true;
709 }
710 const visited = prev_visited ++ .{T};
711
712 return switch (@typeInfo(T)) {
713 .pointer => |pointer| typeIsRecursiveInner(pointer.child, visited),
714 .optional => |optional| typeIsRecursiveInner(optional.child, visited),
715 .array => |array| typeIsRecursiveInner(array.child, visited),
716 .vector => |vector| typeIsRecursiveInner(vector.child, visited),
717 .@"struct" => |@"struct"| for (@"struct".fields) |field| {
718 if (typeIsRecursiveInner(field.type, visited)) break true;
719 } else false,
720 .@"union" => |@"union"| inline for (@"union".fields) |field| {
721 if (typeIsRecursiveInner(field.type, visited)) break true;
722 } else false,
723 else => false,
724 };
725}
726
727test typeIsRecursive {
728 try std.testing.expect(!typeIsRecursive(bool));
729 try std.testing.expect(!typeIsRecursive(struct { x: i32, y: i32 }));
730 try std.testing.expect(!typeIsRecursive(struct { i32, i32 }));
731 try std.testing.expect(typeIsRecursive(struct { x: i32, y: i32, z: *@This() }));
732 try std.testing.expect(typeIsRecursive(struct {
733 a: struct {
734 const A = @This();
735 b: struct {
736 c: *struct {
737 a: ?A,
738 },
739 },
740 },
741 }));
742 try std.testing.expect(typeIsRecursive(struct {
743 a: [3]*@This(),
744 }));
745 try std.testing.expect(typeIsRecursive(struct {
746 a: union { a: i32, b: *@This() },
747 }));
748}
749
750fn checkValueDepth(val: anytype, depth: usize) error{ExceededMaxDepth}!void {
751 if (depth == 0) return error.ExceededMaxDepth;
752 const child_depth = depth - 1;
753
754 switch (@typeInfo(@TypeOf(val))) {
755 .pointer => |pointer| switch (pointer.size) {
756 .one => try checkValueDepth(val.*, child_depth),
757 .slice => for (val) |item| {
758 try checkValueDepth(item, child_depth);
759 },
760 .c, .many => {},
761 },
762 .array => for (val) |item| {
763 try checkValueDepth(item, child_depth);
764 },
765 .@"struct" => |@"struct"| inline for (@"struct".fields) |field_info| {
766 try checkValueDepth(@field(val, field_info.name), child_depth);
767 },
768 .@"union" => |@"union"| if (@"union".tag_type == null) {
769 return;
770 } else switch (val) {
771 inline else => |payload| {
772 return checkValueDepth(payload, child_depth);
773 },
774 },
775 .optional => if (val) |inner| try checkValueDepth(inner, child_depth),
776 else => {},
777 }
778}
779
780fn expectValueDepthEquals(expected: usize, v: anytype) !void {
781 try checkValueDepth(v, expected);
782 try std.testing.expectError(error.ExceededMaxDepth, checkValueDepth(v, expected - 1));
783}
784
785test checkValueDepth {
786 try expectValueDepthEquals(1, 10);
787 try expectValueDepthEquals(2, .{ .x = 1, .y = 2 });
788 try expectValueDepthEquals(2, .{ 1, 2 });
789 try expectValueDepthEquals(3, .{ 1, .{ 2, 3 } });
790 try expectValueDepthEquals(3, .{ .{ 1, 2 }, 3 });
791 try expectValueDepthEquals(3, .{ .x = 0, .y = 1, .z = .{ .x = 3 } });
792 try expectValueDepthEquals(3, .{ .x = 0, .y = .{ .x = 1 }, .z = 2 });
793 try expectValueDepthEquals(3, .{ .x = .{ .x = 0 }, .y = 1, .z = 2 });
794 try expectValueDepthEquals(2, @as(?u32, 1));
795 try expectValueDepthEquals(1, @as(?u32, null));
796 try expectValueDepthEquals(1, null);
797 try expectValueDepthEquals(2, &1);
798 try expectValueDepthEquals(3, &@as(?u32, 1));
799
800 const Union = union(enum) {
801 x: u32,
802 y: struct { x: u32 },
803 };
804 try expectValueDepthEquals(2, Union{ .x = 1 });
805 try expectValueDepthEquals(3, Union{ .y = .{ .x = 1 } });
806
807 const Recurse = struct { r: ?*const @This() };
808 try expectValueDepthEquals(2, Recurse{ .r = null });
809 try expectValueDepthEquals(5, Recurse{ .r = &Recurse{ .r = null } });
810 try expectValueDepthEquals(8, Recurse{ .r = &Recurse{ .r = &Recurse{ .r = null } } });
811
812 try expectValueDepthEquals(2, @as([]const u8, &.{ 1, 2, 3 }));
813 try expectValueDepthEquals(3, @as([]const []const u8, &.{&.{ 1, 2, 3 }}));
814}
815
816inline fn canSerializeType(T: type) bool {
817 comptime return canSerializeTypeInner(T, &.{}, false);
818}
819
820fn canSerializeTypeInner(
821 T: type,
822 /// Visited structs and unions, to avoid infinite recursion.
823 /// Tracking more types is unnecessary, and a little complex due to optional nesting.
824 visited: []const type,
825 parent_is_optional: bool,
826) bool {
827 return switch (@typeInfo(T)) {
828 .bool,
829 .int,
830 .float,
831 .comptime_float,
832 .comptime_int,
833 .null,
834 .enum_literal,
835 => true,
836
837 .noreturn,
838 .void,
839 .type,
840 .undefined,
841 .error_union,
842 .error_set,
843 .@"fn",
844 .frame,
845 .@"anyframe",
846 .@"opaque",
847 => false,
848
849 .@"enum" => |@"enum"| @"enum".is_exhaustive,
850
851 .pointer => |pointer| switch (pointer.size) {
852 .one => canSerializeTypeInner(pointer.child, visited, parent_is_optional),
853 .slice => canSerializeTypeInner(pointer.child, visited, false),
854 .many, .c => false,
855 },
856
857 .optional => |optional| if (parent_is_optional)
858 false
859 else
860 canSerializeTypeInner(optional.child, visited, true),
861
862 .array => |array| canSerializeTypeInner(array.child, visited, false),
863 .vector => |vector| canSerializeTypeInner(vector.child, visited, false),
864
865 .@"struct" => |@"struct"| {
866 for (visited) |V| if (T == V) return true;
867 const new_visited = visited ++ .{T};
868 for (@"struct".fields) |field| {
869 if (!canSerializeTypeInner(field.type, new_visited, false)) return false;
870 }
871 return true;
872 },
873 .@"union" => |@"union"| {
874 for (visited) |V| if (T == V) return true;
875 const new_visited = visited ++ .{T};
876 if (@"union".tag_type == null) return false;
877 for (@"union".fields) |field| {
878 if (field.type != void and !canSerializeTypeInner(field.type, new_visited, false)) {
879 return false;
880 }
881 }
882 return true;
883 },
884 };
885}
886
887test canSerializeType {
888 try std.testing.expect(!comptime canSerializeType(void));
889 try std.testing.expect(!comptime canSerializeType(struct { f: [*]u8 }));
890 try std.testing.expect(!comptime canSerializeType(struct { error{foo} }));
891 try std.testing.expect(!comptime canSerializeType(union(enum) { a: void, f: [*c]u8 }));
892 try std.testing.expect(!comptime canSerializeType(@Vector(0, [*c]u8)));
893 try std.testing.expect(!comptime canSerializeType(*?[*c]u8));
894 try std.testing.expect(!comptime canSerializeType(enum(u8) { _ }));
895 try std.testing.expect(!comptime canSerializeType(union { foo: void }));
896 try std.testing.expect(comptime canSerializeType(union(enum) { foo: void }));
897 try std.testing.expect(comptime canSerializeType(comptime_float));
898 try std.testing.expect(comptime canSerializeType(comptime_int));
899 try std.testing.expect(!comptime canSerializeType(struct { comptime foo: ??u8 = null }));
900 try std.testing.expect(comptime canSerializeType(@TypeOf(.foo)));
901 try std.testing.expect(comptime canSerializeType(?u8));
902 try std.testing.expect(comptime canSerializeType(*?*u8));
903 try std.testing.expect(comptime canSerializeType(?struct {
904 foo: ?struct {
905 ?union(enum) {
906 a: ?@Vector(0, ?*u8),
907 },
908 ?struct {
909 f: ?[]?u8,
910 },
911 },
912 }));
913 try std.testing.expect(!comptime canSerializeType(??u8));
914 try std.testing.expect(!comptime canSerializeType(?*?u8));
915 try std.testing.expect(!comptime canSerializeType(*?*?*u8));
916 try std.testing.expect(comptime canSerializeType(struct { x: comptime_int = 2 }));
917 try std.testing.expect(comptime canSerializeType(struct { x: comptime_float = 2 }));
918 try std.testing.expect(comptime canSerializeType(struct { comptime_int }));
919 try std.testing.expect(comptime canSerializeType(struct { comptime x: @TypeOf(.foo) = .foo }));
920 const Recursive = struct { foo: ?*@This() };
921 try std.testing.expect(comptime canSerializeType(Recursive));
922
923 // Make sure we validate nested optional before we early out due to already having seen
924 // a type recursion!
925 try std.testing.expect(!comptime canSerializeType(struct {
926 add_to_visited: ?u8,
927 retrieve_from_visited: ??u8,
928 }));
929}
lib/std/zon/parse.zig+13-13
......@@ -64,14 +64,14 @@ pub const Error = union(enum) {
6464 }
6565 };
6666
67 fn formatMessage(self: []const u8, w: *std.io.Writer) std.io.Writer.Error!void {
67 fn formatMessage(self: []const u8, w: *std.Io.Writer) std.Io.Writer.Error!void {
6868 // Just writes the string for now, but we're keeping this behind a formatter so we have
6969 // the option to extend it in the future to print more advanced messages (like `Error`
7070 // does) without breaking the API.
7171 try w.writeAll(self);
7272 }
7373
74 pub fn fmtMessage(self: Note, diag: *const Diagnostics) std.fmt.Formatter([]const u8, Note.formatMessage) {
74 pub fn fmtMessage(self: Note, diag: *const Diagnostics) std.fmt.Alt([]const u8, Note.formatMessage) {
7575 return .{ .data = switch (self) {
7676 .zoir => |note| note.msg.get(diag.zoir),
7777 .type_check => |note| note.msg,
......@@ -147,14 +147,14 @@ pub const Error = union(enum) {
147147 diag: *const Diagnostics,
148148 };
149149
150 fn formatMessage(self: FormatMessage, w: *std.io.Writer) std.io.Writer.Error!void {
150 fn formatMessage(self: FormatMessage, w: *std.Io.Writer) std.Io.Writer.Error!void {
151151 switch (self.err) {
152152 .zoir => |err| try w.writeAll(err.msg.get(self.diag.zoir)),
153153 .type_check => |tc| try w.writeAll(tc.message),
154154 }
155155 }
156156
157 pub fn fmtMessage(self: @This(), diag: *const Diagnostics) std.fmt.Formatter(FormatMessage, formatMessage) {
157 pub fn fmtMessage(self: @This(), diag: *const Diagnostics) std.fmt.Alt(FormatMessage, formatMessage) {
158158 return .{ .data = .{
159159 .err = self,
160160 .diag = diag,
......@@ -226,7 +226,7 @@ pub const Diagnostics = struct {
226226 return .{ .diag = self };
227227 }
228228
229 pub fn format(self: *const @This(), w: *std.io.Writer) std.io.Writer.Error!void {
229 pub fn format(self: *const @This(), w: *std.Io.Writer) std.Io.Writer.Error!void {
230230 var errors = self.iterateErrors();
231231 while (errors.next()) |err| {
232232 const loc = err.getLocation(self);
......@@ -606,7 +606,7 @@ const Parser = struct {
606606 }
607607 }
608608
609 fn parseSlicePointer(self: *@This(), T: type, node: Zoir.Node.Index) !T {
609 fn parseSlicePointer(self: *@This(), T: type, node: Zoir.Node.Index) ParseExprInnerError!T {
610610 switch (node.get(self.zoir)) {
611611 .string_literal => return self.parseString(T, node),
612612 .array_literal => |nodes| return self.parseSlice(T, nodes),
......@@ -1048,6 +1048,7 @@ const Parser = struct {
10481048 name: []const u8,
10491049 ) error{ OutOfMemory, ParseZon } {
10501050 @branchHint(.cold);
1051 const gpa = self.gpa;
10511052 const token = if (field) |f| b: {
10521053 var buf: [2]Ast.Node.Index = undefined;
10531054 const struct_init = self.ast.fullStructInit(&buf, node.getAstNode(self.zoir)).?;
......@@ -1065,13 +1066,12 @@ const Parser = struct {
10651066 };
10661067 } else b: {
10671068 const msg = "supported: ";
1068 var buf: std.ArrayListUnmanaged(u8) = try .initCapacity(self.gpa, 64);
1069 defer buf.deinit(self.gpa);
1070 const writer = buf.writer(self.gpa);
1071 try writer.writeAll(msg);
1069 var buf: std.ArrayListUnmanaged(u8) = try .initCapacity(gpa, 64);
1070 defer buf.deinit(gpa);
1071 try buf.appendSlice(gpa, msg);
10721072 inline for (info.fields, 0..) |field_info, i| {
1073 if (i != 0) try writer.writeAll(", ");
1074 try writer.print("'{f}'", .{std.zig.fmtIdFlags(field_info.name, .{
1073 if (i != 0) try buf.appendSlice(gpa, ", ");
1074 try buf.print(gpa, "'{f}'", .{std.zig.fmtIdFlags(field_info.name, .{
10751075 .allow_primitive = true,
10761076 .allow_underscore = true,
10771077 })});
......@@ -1079,7 +1079,7 @@ const Parser = struct {
10791079 break :b .{
10801080 .token = token,
10811081 .offset = 0,
1082 .msg = try buf.toOwnedSlice(self.gpa),
1082 .msg = try buf.toOwnedSlice(gpa),
10831083 .owned = true,
10841084 };
10851085 };
lib/std/zon/stringify.zig+382-1333
......@@ -22,13 +22,14 @@
2222
2323const std = @import("std");
2424const assert = std.debug.assert;
25const Writer = std.Io.Writer;
26const Serializer = std.zon.Serializer;
2527
26/// Options for `serialize`.
2728pub const SerializeOptions = struct {
2829 /// If false, whitespace is omitted. Otherwise whitespace is emitted in standard Zig style.
2930 whitespace: bool = true,
3031 /// Determines when to emit Unicode code point literals as opposed to integer literals.
31 emit_codepoint_literals: EmitCodepointLiterals = .never,
32 emit_codepoint_literals: Serializer.EmitCodepointLiterals = .never,
3233 /// If true, slices of `u8`s, and pointers to arrays of `u8` are serialized as containers.
3334 /// Otherwise they are serialized as string literals.
3435 emit_strings_as_containers: bool = false,
......@@ -40,15 +41,12 @@ pub const SerializeOptions = struct {
4041/// Serialize the given value as ZON.
4142///
4243/// It is asserted at comptime that `@TypeOf(val)` is not a recursive type.
43pub fn serialize(
44 val: anytype,
45 options: SerializeOptions,
46 writer: anytype,
47) @TypeOf(writer).Error!void {
48 var sz = serializer(writer, .{
49 .whitespace = options.whitespace,
50 });
51 try sz.value(val, .{
44pub fn serialize(val: anytype, options: SerializeOptions, writer: *Writer) Writer.Error!void {
45 var s: Serializer = .{
46 .writer = writer,
47 .options = .{ .whitespace = options.whitespace },
48 };
49 try s.value(val, .{
5250 .emit_codepoint_literals = options.emit_codepoint_literals,
5351 .emit_strings_as_containers = options.emit_strings_as_containers,
5452 .emit_default_optional_fields = options.emit_default_optional_fields,
......@@ -62,13 +60,14 @@ pub fn serialize(
6260pub fn serializeMaxDepth(
6361 val: anytype,
6462 options: SerializeOptions,
65 writer: anytype,
63 writer: *Writer,
6664 depth: usize,
67) (@TypeOf(writer).Error || error{ExceededMaxDepth})!void {
68 var sz = serializer(writer, .{
69 .whitespace = options.whitespace,
70 });
71 try sz.valueMaxDepth(val, .{
65) Serializer.DepthError!void {
66 var s: Serializer = .{
67 .writer = writer,
68 .options = .{ .whitespace = options.whitespace },
69 };
70 try s.valueMaxDepth(val, .{
7271 .emit_codepoint_literals = options.emit_codepoint_literals,
7372 .emit_strings_as_containers = options.emit_strings_as_containers,
7473 .emit_default_optional_fields = options.emit_default_optional_fields,
......@@ -81,114 +80,19 @@ pub fn serializeMaxDepth(
8180pub fn serializeArbitraryDepth(
8281 val: anytype,
8382 options: SerializeOptions,
84 writer: anytype,
85) @TypeOf(writer).Error!void {
86 var sz = serializer(writer, .{
87 .whitespace = options.whitespace,
88 });
89 try sz.valueArbitraryDepth(val, .{
83 writer: *Writer,
84) Serializer.Error!void {
85 var s: Serializer = .{
86 .writer = writer,
87 .options = .{ .whitespace = options.whitespace },
88 };
89 try s.valueArbitraryDepth(val, .{
9090 .emit_codepoint_literals = options.emit_codepoint_literals,
9191 .emit_strings_as_containers = options.emit_strings_as_containers,
9292 .emit_default_optional_fields = options.emit_default_optional_fields,
9393 });
9494}
9595
96fn typeIsRecursive(comptime T: type) bool {
97 return comptime typeIsRecursiveImpl(T, &.{});
98}
99
100fn typeIsRecursiveImpl(comptime T: type, comptime prev_visited: []const type) bool {
101 for (prev_visited) |V| {
102 if (V == T) return true;
103 }
104 const visited = prev_visited ++ .{T};
105
106 return switch (@typeInfo(T)) {
107 .pointer => |pointer| typeIsRecursiveImpl(pointer.child, visited),
108 .optional => |optional| typeIsRecursiveImpl(optional.child, visited),
109 .array => |array| typeIsRecursiveImpl(array.child, visited),
110 .vector => |vector| typeIsRecursiveImpl(vector.child, visited),
111 .@"struct" => |@"struct"| for (@"struct".fields) |field| {
112 if (typeIsRecursiveImpl(field.type, visited)) break true;
113 } else false,
114 .@"union" => |@"union"| inline for (@"union".fields) |field| {
115 if (typeIsRecursiveImpl(field.type, visited)) break true;
116 } else false,
117 else => false,
118 };
119}
120
121fn canSerializeType(T: type) bool {
122 comptime return canSerializeTypeInner(T, &.{}, false);
123}
124
125fn canSerializeTypeInner(
126 T: type,
127 /// Visited structs and unions, to avoid infinite recursion.
128 /// Tracking more types is unnecessary, and a little complex due to optional nesting.
129 visited: []const type,
130 parent_is_optional: bool,
131) bool {
132 return switch (@typeInfo(T)) {
133 .bool,
134 .int,
135 .float,
136 .comptime_float,
137 .comptime_int,
138 .null,
139 .enum_literal,
140 => true,
141
142 .noreturn,
143 .void,
144 .type,
145 .undefined,
146 .error_union,
147 .error_set,
148 .@"fn",
149 .frame,
150 .@"anyframe",
151 .@"opaque",
152 => false,
153
154 .@"enum" => |@"enum"| @"enum".is_exhaustive,
155
156 .pointer => |pointer| switch (pointer.size) {
157 .one => canSerializeTypeInner(pointer.child, visited, parent_is_optional),
158 .slice => canSerializeTypeInner(pointer.child, visited, false),
159 .many, .c => false,
160 },
161
162 .optional => |optional| if (parent_is_optional)
163 false
164 else
165 canSerializeTypeInner(optional.child, visited, true),
166
167 .array => |array| canSerializeTypeInner(array.child, visited, false),
168 .vector => |vector| canSerializeTypeInner(vector.child, visited, false),
169
170 .@"struct" => |@"struct"| {
171 for (visited) |V| if (T == V) return true;
172 const new_visited = visited ++ .{T};
173 for (@"struct".fields) |field| {
174 if (!canSerializeTypeInner(field.type, new_visited, false)) return false;
175 }
176 return true;
177 },
178 .@"union" => |@"union"| {
179 for (visited) |V| if (T == V) return true;
180 const new_visited = visited ++ .{T};
181 if (@"union".tag_type == null) return false;
182 for (@"union".fields) |field| {
183 if (field.type != void and !canSerializeTypeInner(field.type, new_visited, false)) {
184 return false;
185 }
186 }
187 return true;
188 },
189 };
190}
191
19296fn isNestedOptional(T: type) bool {
19397 comptime switch (@typeInfo(T)) {
19498 .optional => |optional| return isNestedOptionalInner(optional.child),
......@@ -210,875 +114,17 @@ fn isNestedOptionalInner(T: type) bool {
210114 }
211115}
212116
213test "std.zon stringify canSerializeType" {
214 try std.testing.expect(!comptime canSerializeType(void));
215 try std.testing.expect(!comptime canSerializeType(struct { f: [*]u8 }));
216 try std.testing.expect(!comptime canSerializeType(struct { error{foo} }));
217 try std.testing.expect(!comptime canSerializeType(union(enum) { a: void, f: [*c]u8 }));
218 try std.testing.expect(!comptime canSerializeType(@Vector(0, [*c]u8)));
219 try std.testing.expect(!comptime canSerializeType(*?[*c]u8));
220 try std.testing.expect(!comptime canSerializeType(enum(u8) { _ }));
221 try std.testing.expect(!comptime canSerializeType(union { foo: void }));
222 try std.testing.expect(comptime canSerializeType(union(enum) { foo: void }));
223 try std.testing.expect(comptime canSerializeType(comptime_float));
224 try std.testing.expect(comptime canSerializeType(comptime_int));
225 try std.testing.expect(!comptime canSerializeType(struct { comptime foo: ??u8 = null }));
226 try std.testing.expect(comptime canSerializeType(@TypeOf(.foo)));
227 try std.testing.expect(comptime canSerializeType(?u8));
228 try std.testing.expect(comptime canSerializeType(*?*u8));
229 try std.testing.expect(comptime canSerializeType(?struct {
230 foo: ?struct {
231 ?union(enum) {
232 a: ?@Vector(0, ?*u8),
233 },
234 ?struct {
235 f: ?[]?u8,
236 },
237 },
238 }));
239 try std.testing.expect(!comptime canSerializeType(??u8));
240 try std.testing.expect(!comptime canSerializeType(?*?u8));
241 try std.testing.expect(!comptime canSerializeType(*?*?*u8));
242 try std.testing.expect(comptime canSerializeType(struct { x: comptime_int = 2 }));
243 try std.testing.expect(comptime canSerializeType(struct { x: comptime_float = 2 }));
244 try std.testing.expect(comptime canSerializeType(struct { comptime_int }));
245 try std.testing.expect(comptime canSerializeType(struct { comptime x: @TypeOf(.foo) = .foo }));
246 const Recursive = struct { foo: ?*@This() };
247 try std.testing.expect(comptime canSerializeType(Recursive));
248
249 // Make sure we validate nested optional before we early out due to already having seen
250 // a type recursion!
251 try std.testing.expect(!comptime canSerializeType(struct {
252 add_to_visited: ?u8,
253 retrieve_from_visited: ??u8,
254 }));
255}
256
257test "std.zon typeIsRecursive" {
258 try std.testing.expect(!typeIsRecursive(bool));
259 try std.testing.expect(!typeIsRecursive(struct { x: i32, y: i32 }));
260 try std.testing.expect(!typeIsRecursive(struct { i32, i32 }));
261 try std.testing.expect(typeIsRecursive(struct { x: i32, y: i32, z: *@This() }));
262 try std.testing.expect(typeIsRecursive(struct {
263 a: struct {
264 const A = @This();
265 b: struct {
266 c: *struct {
267 a: ?A,
268 },
269 },
270 },
271 }));
272 try std.testing.expect(typeIsRecursive(struct {
273 a: [3]*@This(),
274 }));
275 try std.testing.expect(typeIsRecursive(struct {
276 a: union { a: i32, b: *@This() },
277 }));
278}
279
280fn checkValueDepth(val: anytype, depth: usize) error{ExceededMaxDepth}!void {
281 if (depth == 0) return error.ExceededMaxDepth;
282 const child_depth = depth - 1;
283
284 switch (@typeInfo(@TypeOf(val))) {
285 .pointer => |pointer| switch (pointer.size) {
286 .one => try checkValueDepth(val.*, child_depth),
287 .slice => for (val) |item| {
288 try checkValueDepth(item, child_depth);
289 },
290 .c, .many => {},
291 },
292 .array => for (val) |item| {
293 try checkValueDepth(item, child_depth);
294 },
295 .@"struct" => |@"struct"| inline for (@"struct".fields) |field_info| {
296 try checkValueDepth(@field(val, field_info.name), child_depth);
297 },
298 .@"union" => |@"union"| if (@"union".tag_type == null) {
299 return;
300 } else switch (val) {
301 inline else => |payload| {
302 return checkValueDepth(payload, child_depth);
303 },
304 },
305 .optional => if (val) |inner| try checkValueDepth(inner, child_depth),
306 else => {},
307 }
308}
309
310fn expectValueDepthEquals(expected: usize, value: anytype) !void {
311 try checkValueDepth(value, expected);
312 try std.testing.expectError(error.ExceededMaxDepth, checkValueDepth(value, expected - 1));
313}
314
315test "std.zon checkValueDepth" {
316 try expectValueDepthEquals(1, 10);
317 try expectValueDepthEquals(2, .{ .x = 1, .y = 2 });
318 try expectValueDepthEquals(2, .{ 1, 2 });
319 try expectValueDepthEquals(3, .{ 1, .{ 2, 3 } });
320 try expectValueDepthEquals(3, .{ .{ 1, 2 }, 3 });
321 try expectValueDepthEquals(3, .{ .x = 0, .y = 1, .z = .{ .x = 3 } });
322 try expectValueDepthEquals(3, .{ .x = 0, .y = .{ .x = 1 }, .z = 2 });
323 try expectValueDepthEquals(3, .{ .x = .{ .x = 0 }, .y = 1, .z = 2 });
324 try expectValueDepthEquals(2, @as(?u32, 1));
325 try expectValueDepthEquals(1, @as(?u32, null));
326 try expectValueDepthEquals(1, null);
327 try expectValueDepthEquals(2, &1);
328 try expectValueDepthEquals(3, &@as(?u32, 1));
329
330 const Union = union(enum) {
331 x: u32,
332 y: struct { x: u32 },
333 };
334 try expectValueDepthEquals(2, Union{ .x = 1 });
335 try expectValueDepthEquals(3, Union{ .y = .{ .x = 1 } });
336
337 const Recurse = struct { r: ?*const @This() };
338 try expectValueDepthEquals(2, Recurse{ .r = null });
339 try expectValueDepthEquals(5, Recurse{ .r = &Recurse{ .r = null } });
340 try expectValueDepthEquals(8, Recurse{ .r = &Recurse{ .r = &Recurse{ .r = null } } });
341
342 try expectValueDepthEquals(2, @as([]const u8, &.{ 1, 2, 3 }));
343 try expectValueDepthEquals(3, @as([]const []const u8, &.{&.{ 1, 2, 3 }}));
344}
345
346/// Options for `Serializer`.
347pub const SerializerOptions = struct {
348 /// If false, only syntactically necessary whitespace is emitted.
349 whitespace: bool = true,
350};
351
352/// Determines when to emit Unicode code point literals as opposed to integer literals.
353pub const EmitCodepointLiterals = enum {
354 /// Never emit Unicode code point literals.
355 never,
356 /// Emit Unicode code point literals for any `u8` in the printable ASCII range.
357 printable_ascii,
358 /// Emit Unicode code point literals for any unsigned integer with 21 bits or fewer
359 /// whose value is a valid non-surrogate code point.
360 always,
361
362 /// If the value should be emitted as a Unicode codepoint, return it as a u21.
363 fn emitAsCodepoint(self: @This(), val: anytype) ?u21 {
364 // Rule out incompatible integer types
365 switch (@typeInfo(@TypeOf(val))) {
366 .int => |int_info| if (int_info.signedness == .signed or int_info.bits > 21) {
367 return null;
368 },
369 .comptime_int => {},
370 else => comptime unreachable,
371 }
372
373 // Return null if the value shouldn't be printed as a Unicode codepoint, or the value casted
374 // to a u21 if it should.
375 switch (self) {
376 .always => {
377 const c = std.math.cast(u21, val) orelse return null;
378 if (!std.unicode.utf8ValidCodepoint(c)) return null;
379 return c;
380 },
381 .printable_ascii => {
382 const c = std.math.cast(u8, val) orelse return null;
383 if (!std.ascii.isPrint(c)) return null;
384 return c;
385 },
386 .never => {
387 return null;
388 },
389 }
390 }
391};
392
393/// Options for serialization of an individual value.
394///
395/// See `SerializeOptions` for more information on these options.
396pub const ValueOptions = struct {
397 emit_codepoint_literals: EmitCodepointLiterals = .never,
398 emit_strings_as_containers: bool = false,
399 emit_default_optional_fields: bool = true,
400};
401
402/// Options for manual serialization of container types.
403pub const SerializeContainerOptions = struct {
404 /// The whitespace style that should be used for this container. Ignored if whitespace is off.
405 whitespace_style: union(enum) {
406 /// If true, wrap every field. If false do not.
407 wrap: bool,
408 /// Automatically decide whether to wrap or not based on the number of fields. Following
409 /// the standard rule of thumb, containers with more than two fields are wrapped.
410 fields: usize,
411 } = .{ .wrap = true },
412
413 fn shouldWrap(self: SerializeContainerOptions) bool {
414 return switch (self.whitespace_style) {
415 .wrap => |wrap| wrap,
416 .fields => |fields| fields > 2,
417 };
418 }
419};
420
421/// Lower level control over serialization, you can create a new instance with `serializer`.
422///
423/// Useful when you want control over which fields are serialized, how they're represented,
424/// or want to write a ZON object that does not exist in memory.
425///
426/// You can serialize values with `value`. To serialize recursive types, the following are provided:
427/// * `valueMaxDepth`
428/// * `valueArbitraryDepth`
429///
430/// You can also serialize values using specific notations:
431/// * `int`
432/// * `float`
433/// * `codePoint`
434/// * `tuple`
435/// * `tupleMaxDepth`
436/// * `tupleArbitraryDepth`
437/// * `string`
438/// * `multilineString`
439///
440/// For manual serialization of containers, see:
441/// * `beginStruct`
442/// * `beginTuple`
443///
444/// # Example
445/// ```zig
446/// var sz = serializer(writer, .{});
447/// var vec2 = try sz.beginStruct(.{});
448/// try vec2.field("x", 1.5, .{});
449/// try vec2.fieldPrefix();
450/// try sz.value(2.5);
451/// try vec2.end();
452/// ```
453pub fn Serializer(Writer: type) type {
454 return struct {
455 const Self = @This();
456
457 options: SerializerOptions,
458 indent_level: u8,
459 writer: Writer,
460
461 /// Initialize a serializer.
462 fn init(writer: Writer, options: SerializerOptions) Self {
463 return .{
464 .options = options,
465 .writer = writer,
466 .indent_level = 0,
467 };
468 }
469
470 /// Serialize a value, similar to `serialize`.
471 pub fn value(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void {
472 comptime assert(!typeIsRecursive(@TypeOf(val)));
473 return self.valueArbitraryDepth(val, options);
474 }
475
476 /// Serialize a value, similar to `serializeMaxDepth`.
477 pub fn valueMaxDepth(
478 self: *Self,
479 val: anytype,
480 options: ValueOptions,
481 depth: usize,
482 ) (Writer.Error || error{ExceededMaxDepth})!void {
483 try checkValueDepth(val, depth);
484 return self.valueArbitraryDepth(val, options);
485 }
486
487 /// Serialize a value, similar to `serializeArbitraryDepth`.
488 pub fn valueArbitraryDepth(
489 self: *Self,
490 val: anytype,
491 options: ValueOptions,
492 ) Writer.Error!void {
493 comptime assert(canSerializeType(@TypeOf(val)));
494 switch (@typeInfo(@TypeOf(val))) {
495 .int, .comptime_int => if (options.emit_codepoint_literals.emitAsCodepoint(val)) |c| {
496 self.codePoint(c) catch |err| switch (err) {
497 error.InvalidCodepoint => unreachable, // Already validated
498 else => |e| return e,
499 };
500 } else {
501 try self.int(val);
502 },
503 .float, .comptime_float => try self.float(val),
504 .bool, .null => try std.fmt.format(self.writer, "{}", .{val}),
505 .enum_literal => try self.ident(@tagName(val)),
506 .@"enum" => try self.ident(@tagName(val)),
507 .pointer => |pointer| {
508 // Try to serialize as a string
509 const item: ?type = switch (@typeInfo(pointer.child)) {
510 .array => |array| array.child,
511 else => if (pointer.size == .slice) pointer.child else null,
512 };
513 if (item == u8 and
514 (pointer.sentinel() == null or pointer.sentinel() == 0) and
515 !options.emit_strings_as_containers)
516 {
517 return try self.string(val);
518 }
519
520 // Serialize as either a tuple or as the child type
521 switch (pointer.size) {
522 .slice => try self.tupleImpl(val, options),
523 .one => try self.valueArbitraryDepth(val.*, options),
524 else => comptime unreachable,
525 }
526 },
527 .array => {
528 var container = try self.beginTuple(
529 .{ .whitespace_style = .{ .fields = val.len } },
530 );
531 for (val) |item_val| {
532 try container.fieldArbitraryDepth(item_val, options);
533 }
534 try container.end();
535 },
536 .@"struct" => |@"struct"| if (@"struct".is_tuple) {
537 var container = try self.beginTuple(
538 .{ .whitespace_style = .{ .fields = @"struct".fields.len } },
539 );
540 inline for (val) |field_value| {
541 try container.fieldArbitraryDepth(field_value, options);
542 }
543 try container.end();
544 } else {
545 // Decide which fields to emit
546 const fields, const skipped: [@"struct".fields.len]bool = if (options.emit_default_optional_fields) b: {
547 break :b .{ @"struct".fields.len, @splat(false) };
548 } else b: {
549 var fields = @"struct".fields.len;
550 var skipped: [@"struct".fields.len]bool = @splat(false);
551 inline for (@"struct".fields, &skipped) |field_info, *skip| {
552 if (field_info.default_value_ptr) |ptr| {
553 const default: *const field_info.type = @ptrCast(@alignCast(ptr));
554 const field_value = @field(val, field_info.name);
555 if (std.meta.eql(field_value, default.*)) {
556 skip.* = true;
557 fields -= 1;
558 }
559 }
560 }
561 break :b .{ fields, skipped };
562 };
563
564 // Emit those fields
565 var container = try self.beginStruct(
566 .{ .whitespace_style = .{ .fields = fields } },
567 );
568 inline for (@"struct".fields, skipped) |field_info, skip| {
569 if (!skip) {
570 try container.fieldArbitraryDepth(
571 field_info.name,
572 @field(val, field_info.name),
573 options,
574 );
575 }
576 }
577 try container.end();
578 },
579 .@"union" => |@"union"| {
580 comptime assert(@"union".tag_type != null);
581 switch (val) {
582 inline else => |pl, tag| if (@TypeOf(pl) == void)
583 try self.writer.print(".{s}", .{@tagName(tag)})
584 else {
585 var container = try self.beginStruct(.{ .whitespace_style = .{ .fields = 1 } });
586
587 try container.fieldArbitraryDepth(
588 @tagName(tag),
589 pl,
590 options,
591 );
592
593 try container.end();
594 },
595 }
596 },
597 .optional => if (val) |inner| {
598 try self.valueArbitraryDepth(inner, options);
599 } else {
600 try self.writer.writeAll("null");
601 },
602 .vector => |vector| {
603 var container = try self.beginTuple(
604 .{ .whitespace_style = .{ .fields = vector.len } },
605 );
606 for (0..vector.len) |i| {
607 try container.fieldArbitraryDepth(val[i], options);
608 }
609 try container.end();
610 },
611
612 else => comptime unreachable,
613 }
614 }
615
616 /// Serialize an integer.
617 pub fn int(self: *Self, val: anytype) Writer.Error!void {
618 //try self.writer.printInt(val, 10, .lower, .{});
619 try std.fmt.format(self.writer, "{d}", .{val});
620 }
621
622 /// Serialize a float.
623 pub fn float(self: *Self, val: anytype) Writer.Error!void {
624 switch (@typeInfo(@TypeOf(val))) {
625 .float => if (std.math.isNan(val)) {
626 return self.writer.writeAll("nan");
627 } else if (std.math.isPositiveInf(val)) {
628 return self.writer.writeAll("inf");
629 } else if (std.math.isNegativeInf(val)) {
630 return self.writer.writeAll("-inf");
631 } else if (std.math.isNegativeZero(val)) {
632 return self.writer.writeAll("-0.0");
633 } else {
634 try std.fmt.format(self.writer, "{d}", .{val});
635 },
636 .comptime_float => if (val == 0) {
637 return self.writer.writeAll("0");
638 } else {
639 try std.fmt.format(self.writer, "{d}", .{val});
640 },
641 else => comptime unreachable,
642 }
643 }
644
645 /// Serialize `name` as an identifier prefixed with `.`.
646 ///
647 /// Escapes the identifier if necessary.
648 pub fn ident(self: *Self, name: []const u8) Writer.Error!void {
649 try self.writer.print(".{f}", .{std.zig.fmtIdPU(name)});
650 }
651
652 /// Serialize `val` as a Unicode codepoint.
653 ///
654 /// Returns `error.InvalidCodepoint` if `val` is not a valid Unicode codepoint.
655 pub fn codePoint(
656 self: *Self,
657 val: u21,
658 ) (Writer.Error || error{InvalidCodepoint})!void {
659 var buf: [8]u8 = undefined;
660 const len = std.unicode.utf8Encode(val, &buf) catch return error.InvalidCodepoint;
661 const str = buf[0..len];
662 try std.fmt.format(self.writer, "'{f}'", .{std.zig.fmtChar(str)});
663 }
664
665 /// Like `value`, but always serializes `val` as a tuple.
666 ///
667 /// Will fail at comptime if `val` is not a tuple, array, pointer to an array, or slice.
668 pub fn tuple(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void {
669 comptime assert(!typeIsRecursive(@TypeOf(val)));
670 try self.tupleArbitraryDepth(val, options);
671 }
672
673 /// Like `tuple`, but recursive types are allowed.
674 ///
675 /// Returns `error.ExceededMaxDepth` if `depth` is exceeded.
676 pub fn tupleMaxDepth(
677 self: *Self,
678 val: anytype,
679 options: ValueOptions,
680 depth: usize,
681 ) (Writer.Error || error{ExceededMaxDepth})!void {
682 try checkValueDepth(val, depth);
683 try self.tupleArbitraryDepth(val, options);
684 }
685
686 /// Like `tuple`, but recursive types are allowed.
687 ///
688 /// It is the caller's responsibility to ensure that `val` does not contain cycles.
689 pub fn tupleArbitraryDepth(
690 self: *Self,
691 val: anytype,
692 options: ValueOptions,
693 ) Writer.Error!void {
694 try self.tupleImpl(val, options);
695 }
696
697 fn tupleImpl(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void {
698 comptime assert(canSerializeType(@TypeOf(val)));
699 switch (@typeInfo(@TypeOf(val))) {
700 .@"struct" => {
701 var container = try self.beginTuple(.{ .whitespace_style = .{ .fields = val.len } });
702 inline for (val) |item_val| {
703 try container.fieldArbitraryDepth(item_val, options);
704 }
705 try container.end();
706 },
707 .pointer, .array => {
708 var container = try self.beginTuple(.{ .whitespace_style = .{ .fields = val.len } });
709 for (val) |item_val| {
710 try container.fieldArbitraryDepth(item_val, options);
711 }
712 try container.end();
713 },
714 else => comptime unreachable,
715 }
716 }
717
718 /// Like `value`, but always serializes `val` as a string.
719 pub fn string(self: *Self, val: []const u8) Writer.Error!void {
720 try std.fmt.format(self.writer, "\"{f}\"", .{std.zig.fmtString(val)});
721 }
722
723 /// Options for formatting multiline strings.
724 pub const MultilineStringOptions = struct {
725 /// If top level is true, whitespace before and after the multiline string is elided.
726 /// If it is true, a newline is printed, then the value, followed by a newline, and if
727 /// whitespace is true any necessary indentation follows.
728 top_level: bool = false,
729 };
730
731 /// Like `value`, but always serializes to a multiline string literal.
732 ///
733 /// Returns `error.InnerCarriageReturn` if `val` contains a CR not followed by a newline,
734 /// since multiline strings cannot represent CR without a following newline.
735 pub fn multilineString(
736 self: *Self,
737 val: []const u8,
738 options: MultilineStringOptions,
739 ) (Writer.Error || error{InnerCarriageReturn})!void {
740 // Make sure the string does not contain any carriage returns not followed by a newline
741 var i: usize = 0;
742 while (i < val.len) : (i += 1) {
743 if (val[i] == '\r') {
744 if (i + 1 < val.len) {
745 if (val[i + 1] == '\n') {
746 i += 1;
747 continue;
748 }
749 }
750 return error.InnerCarriageReturn;
751 }
752 }
753
754 if (!options.top_level) {
755 try self.newline();
756 try self.indent();
757 }
758
759 try self.writer.writeAll("\\\\");
760 for (val) |c| {
761 if (c != '\r') {
762 try self.writer.writeByte(c); // We write newlines here even if whitespace off
763 if (c == '\n') {
764 try self.indent();
765 try self.writer.writeAll("\\\\");
766 }
767 }
768 }
769
770 if (!options.top_level) {
771 try self.writer.writeByte('\n'); // Even if whitespace off
772 try self.indent();
773 }
774 }
775
776 /// Create a `Struct` for writing ZON structs field by field.
777 pub fn beginStruct(
778 self: *Self,
779 options: SerializeContainerOptions,
780 ) Writer.Error!Struct {
781 return Struct.begin(self, options);
782 }
783
784 /// Creates a `Tuple` for writing ZON tuples field by field.
785 pub fn beginTuple(
786 self: *Self,
787 options: SerializeContainerOptions,
788 ) Writer.Error!Tuple {
789 return Tuple.begin(self, options);
790 }
791
792 fn indent(self: *Self) Writer.Error!void {
793 if (self.options.whitespace) {
794 try self.writer.writeByteNTimes(' ', 4 * self.indent_level);
795 }
796 }
797
798 fn newline(self: *Self) Writer.Error!void {
799 if (self.options.whitespace) {
800 try self.writer.writeByte('\n');
801 }
802 }
803
804 fn newlineOrSpace(self: *Self, len: usize) Writer.Error!void {
805 if (self.containerShouldWrap(len)) {
806 try self.newline();
807 } else {
808 try self.space();
809 }
810 }
811
812 fn space(self: *Self) Writer.Error!void {
813 if (self.options.whitespace) {
814 try self.writer.writeByte(' ');
815 }
816 }
817
818 /// Writes ZON tuples field by field.
819 pub const Tuple = struct {
820 container: Container,
821
822 fn begin(parent: *Self, options: SerializeContainerOptions) Writer.Error!Tuple {
823 return .{
824 .container = try Container.begin(parent, .anon, options),
825 };
826 }
827
828 /// Finishes serializing the tuple.
829 ///
830 /// Prints a trailing comma as configured when appropriate, and the closing bracket.
831 pub fn end(self: *Tuple) Writer.Error!void {
832 try self.container.end();
833 self.* = undefined;
834 }
835
836 /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`.
837 pub fn field(
838 self: *Tuple,
839 val: anytype,
840 options: ValueOptions,
841 ) Writer.Error!void {
842 try self.container.field(null, val, options);
843 }
844
845 /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`.
846 pub fn fieldMaxDepth(
847 self: *Tuple,
848 val: anytype,
849 options: ValueOptions,
850 depth: usize,
851 ) (Writer.Error || error{ExceededMaxDepth})!void {
852 try self.container.fieldMaxDepth(null, val, options, depth);
853 }
854
855 /// Serialize a field. Equivalent to calling `fieldPrefix` followed by
856 /// `valueArbitraryDepth`.
857 pub fn fieldArbitraryDepth(
858 self: *Tuple,
859 val: anytype,
860 options: ValueOptions,
861 ) Writer.Error!void {
862 try self.container.fieldArbitraryDepth(null, val, options);
863 }
864
865 /// Starts a field with a struct as a value. Returns the struct.
866 pub fn beginStructField(
867 self: *Tuple,
868 options: SerializeContainerOptions,
869 ) Writer.Error!Struct {
870 try self.fieldPrefix();
871 return self.container.serializer.beginStruct(options);
872 }
873
874 /// Starts a field with a tuple as a value. Returns the tuple.
875 pub fn beginTupleField(
876 self: *Tuple,
877 options: SerializeContainerOptions,
878 ) Writer.Error!Tuple {
879 try self.fieldPrefix();
880 return self.container.serializer.beginTuple(options);
881 }
882
883 /// Print a field prefix. This prints any necessary commas, and whitespace as
884 /// configured. Useful if you want to serialize the field value yourself.
885 pub fn fieldPrefix(self: *Tuple) Writer.Error!void {
886 try self.container.fieldPrefix(null);
887 }
888 };
889
890 /// Writes ZON structs field by field.
891 pub const Struct = struct {
892 container: Container,
893
894 fn begin(parent: *Self, options: SerializeContainerOptions) Writer.Error!Struct {
895 return .{
896 .container = try Container.begin(parent, .named, options),
897 };
898 }
899
900 /// Finishes serializing the struct.
901 ///
902 /// Prints a trailing comma as configured when appropriate, and the closing bracket.
903 pub fn end(self: *Struct) Writer.Error!void {
904 try self.container.end();
905 self.* = undefined;
906 }
907
908 /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`.
909 pub fn field(
910 self: *Struct,
911 name: []const u8,
912 val: anytype,
913 options: ValueOptions,
914 ) Writer.Error!void {
915 try self.container.field(name, val, options);
916 }
917
918 /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`.
919 pub fn fieldMaxDepth(
920 self: *Struct,
921 name: []const u8,
922 val: anytype,
923 options: ValueOptions,
924 depth: usize,
925 ) (Writer.Error || error{ExceededMaxDepth})!void {
926 try self.container.fieldMaxDepth(name, val, options, depth);
927 }
928
929 /// Serialize a field. Equivalent to calling `fieldPrefix` followed by
930 /// `valueArbitraryDepth`.
931 pub fn fieldArbitraryDepth(
932 self: *Struct,
933 name: []const u8,
934 val: anytype,
935 options: ValueOptions,
936 ) Writer.Error!void {
937 try self.container.fieldArbitraryDepth(name, val, options);
938 }
939
940 /// Starts a field with a struct as a value. Returns the struct.
941 pub fn beginStructField(
942 self: *Struct,
943 name: []const u8,
944 options: SerializeContainerOptions,
945 ) Writer.Error!Struct {
946 try self.fieldPrefix(name);
947 return self.container.serializer.beginStruct(options);
948 }
949
950 /// Starts a field with a tuple as a value. Returns the tuple.
951 pub fn beginTupleField(
952 self: *Struct,
953 name: []const u8,
954 options: SerializeContainerOptions,
955 ) Writer.Error!Tuple {
956 try self.fieldPrefix(name);
957 return self.container.serializer.beginTuple(options);
958 }
959
960 /// Print a field prefix. This prints any necessary commas, the field name (escaped if
961 /// necessary) and whitespace as configured. Useful if you want to serialize the field
962 /// value yourself.
963 pub fn fieldPrefix(self: *Struct, name: []const u8) Writer.Error!void {
964 try self.container.fieldPrefix(name);
965 }
966 };
967
968 const Container = struct {
969 const FieldStyle = enum { named, anon };
970
971 serializer: *Self,
972 field_style: FieldStyle,
973 options: SerializeContainerOptions,
974 empty: bool,
975
976 fn begin(
977 sz: *Self,
978 field_style: FieldStyle,
979 options: SerializeContainerOptions,
980 ) Writer.Error!Container {
981 if (options.shouldWrap()) sz.indent_level +|= 1;
982 try sz.writer.writeAll(".{");
983 return .{
984 .serializer = sz,
985 .field_style = field_style,
986 .options = options,
987 .empty = true,
988 };
989 }
990
991 fn end(self: *Container) Writer.Error!void {
992 if (self.options.shouldWrap()) self.serializer.indent_level -|= 1;
993 if (!self.empty) {
994 if (self.options.shouldWrap()) {
995 if (self.serializer.options.whitespace) {
996 try self.serializer.writer.writeByte(',');
997 }
998 try self.serializer.newline();
999 try self.serializer.indent();
1000 } else if (!self.shouldElideSpaces()) {
1001 try self.serializer.space();
1002 }
1003 }
1004 try self.serializer.writer.writeByte('}');
1005 self.* = undefined;
1006 }
1007
1008 fn fieldPrefix(self: *Container, name: ?[]const u8) Writer.Error!void {
1009 if (!self.empty) {
1010 try self.serializer.writer.writeByte(',');
1011 }
1012 self.empty = false;
1013 if (self.options.shouldWrap()) {
1014 try self.serializer.newline();
1015 } else if (!self.shouldElideSpaces()) {
1016 try self.serializer.space();
1017 }
1018 if (self.options.shouldWrap()) try self.serializer.indent();
1019 if (name) |n| {
1020 try self.serializer.ident(n);
1021 try self.serializer.space();
1022 try self.serializer.writer.writeByte('=');
1023 try self.serializer.space();
1024 }
1025 }
1026
1027 fn field(
1028 self: *Container,
1029 name: ?[]const u8,
1030 val: anytype,
1031 options: ValueOptions,
1032 ) Writer.Error!void {
1033 comptime assert(!typeIsRecursive(@TypeOf(val)));
1034 try self.fieldArbitraryDepth(name, val, options);
1035 }
1036
1037 fn fieldMaxDepth(
1038 self: *Container,
1039 name: ?[]const u8,
1040 val: anytype,
1041 options: ValueOptions,
1042 depth: usize,
1043 ) (Writer.Error || error{ExceededMaxDepth})!void {
1044 try checkValueDepth(val, depth);
1045 try self.fieldArbitraryDepth(name, val, options);
1046 }
1047
1048 fn fieldArbitraryDepth(
1049 self: *Container,
1050 name: ?[]const u8,
1051 val: anytype,
1052 options: ValueOptions,
1053 ) Writer.Error!void {
1054 try self.fieldPrefix(name);
1055 try self.serializer.valueArbitraryDepth(val, options);
1056 }
1057
1058 fn shouldElideSpaces(self: *const Container) bool {
1059 return switch (self.options.whitespace_style) {
1060 .fields => |fields| self.field_style != .named and fields == 1,
1061 else => false,
1062 };
1063 }
1064 };
1065 };
1066}
1067
1068/// Creates a new `Serializer` with the given writer and options.
1069pub fn serializer(writer: anytype, options: SerializerOptions) Serializer(@TypeOf(writer)) {
1070 return .init(writer, options);
1071}
1072
1073117fn expectSerializeEqual(
1074118 expected: []const u8,
1075119 value: anytype,
1076120 options: SerializeOptions,
1077121) !void {
1078 var buf = std.ArrayList(u8).init(std.testing.allocator);
1079 defer buf.deinit();
1080 try serialize(value, options, buf.writer());
1081 try std.testing.expectEqualStrings(expected, buf.items);
122 var aw: Writer.Allocating = .init(std.testing.allocator);
123 const bw = &aw.writer;
124 defer aw.deinit();
125
126 try serialize(value, options, bw);
127 try std.testing.expectEqualStrings(expected, aw.getWritten());
1082128}
1083129
1084130test "std.zon stringify whitespace, high level API" {
......@@ -1175,59 +221,59 @@ test "std.zon stringify whitespace, high level API" {
1175221}
1176222
1177223test "std.zon stringify whitespace, low level API" {
1178 var buf = std.ArrayList(u8).init(std.testing.allocator);
1179 defer buf.deinit();
1180 var sz = serializer(buf.writer(), .{});
224 var aw: Writer.Allocating = .init(std.testing.allocator);
225 var s: Serializer = .{ .writer = &aw.writer };
226 defer aw.deinit();
1181227
1182 inline for (.{ true, false }) |whitespace| {
1183 sz.options = .{ .whitespace = whitespace };
228 for ([2]bool{ true, false }) |whitespace| {
229 s.options = .{ .whitespace = whitespace };
1184230
1185231 // Empty containers
1186232 {
1187 var container = try sz.beginStruct(.{});
233 var container = try s.beginStruct(.{});
1188234 try container.end();
1189 try std.testing.expectEqualStrings(".{}", buf.items);
1190 buf.clearRetainingCapacity();
235 try std.testing.expectEqualStrings(".{}", aw.getWritten());
236 aw.clearRetainingCapacity();
1191237 }
1192238
1193239 {
1194 var container = try sz.beginTuple(.{});
240 var container = try s.beginTuple(.{});
1195241 try container.end();
1196 try std.testing.expectEqualStrings(".{}", buf.items);
1197 buf.clearRetainingCapacity();
242 try std.testing.expectEqualStrings(".{}", aw.getWritten());
243 aw.clearRetainingCapacity();
1198244 }
1199245
1200246 {
1201 var container = try sz.beginStruct(.{ .whitespace_style = .{ .wrap = false } });
247 var container = try s.beginStruct(.{ .whitespace_style = .{ .wrap = false } });
1202248 try container.end();
1203 try std.testing.expectEqualStrings(".{}", buf.items);
1204 buf.clearRetainingCapacity();
249 try std.testing.expectEqualStrings(".{}", aw.getWritten());
250 aw.clearRetainingCapacity();
1205251 }
1206252
1207253 {
1208 var container = try sz.beginTuple(.{ .whitespace_style = .{ .wrap = false } });
254 var container = try s.beginTuple(.{ .whitespace_style = .{ .wrap = false } });
1209255 try container.end();
1210 try std.testing.expectEqualStrings(".{}", buf.items);
1211 buf.clearRetainingCapacity();
256 try std.testing.expectEqualStrings(".{}", aw.getWritten());
257 aw.clearRetainingCapacity();
1212258 }
1213259
1214260 {
1215 var container = try sz.beginStruct(.{ .whitespace_style = .{ .fields = 0 } });
261 var container = try s.beginStruct(.{ .whitespace_style = .{ .fields = 0 } });
1216262 try container.end();
1217 try std.testing.expectEqualStrings(".{}", buf.items);
1218 buf.clearRetainingCapacity();
263 try std.testing.expectEqualStrings(".{}", aw.getWritten());
264 aw.clearRetainingCapacity();
1219265 }
1220266
1221267 {
1222 var container = try sz.beginTuple(.{ .whitespace_style = .{ .fields = 0 } });
268 var container = try s.beginTuple(.{ .whitespace_style = .{ .fields = 0 } });
1223269 try container.end();
1224 try std.testing.expectEqualStrings(".{}", buf.items);
1225 buf.clearRetainingCapacity();
270 try std.testing.expectEqualStrings(".{}", aw.getWritten());
271 aw.clearRetainingCapacity();
1226272 }
1227273
1228274 // Size 1
1229275 {
1230 var container = try sz.beginStruct(.{});
276 var container = try s.beginStruct(.{});
1231277 try container.field("a", 1, .{});
1232278 try container.end();
1233279 if (whitespace) {
......@@ -1235,15 +281,15 @@ test "std.zon stringify whitespace, low level API" {
1235281 \\.{
1236282 \\ .a = 1,
1237283 \\}
1238 , buf.items);
284 , aw.getWritten());
1239285 } else {
1240 try std.testing.expectEqualStrings(".{.a=1}", buf.items);
286 try std.testing.expectEqualStrings(".{.a=1}", aw.getWritten());
1241287 }
1242 buf.clearRetainingCapacity();
288 aw.clearRetainingCapacity();
1243289 }
1244290
1245291 {
1246 var container = try sz.beginTuple(.{});
292 var container = try s.beginTuple(.{});
1247293 try container.field(1, .{});
1248294 try container.end();
1249295 if (whitespace) {
......@@ -1251,62 +297,62 @@ test "std.zon stringify whitespace, low level API" {
1251297 \\.{
1252298 \\ 1,
1253299 \\}
1254 , buf.items);
300 , aw.getWritten());
1255301 } else {
1256 try std.testing.expectEqualStrings(".{1}", buf.items);
302 try std.testing.expectEqualStrings(".{1}", aw.getWritten());
1257303 }
1258 buf.clearRetainingCapacity();
304 aw.clearRetainingCapacity();
1259305 }
1260306
1261307 {
1262 var container = try sz.beginStruct(.{ .whitespace_style = .{ .wrap = false } });
308 var container = try s.beginStruct(.{ .whitespace_style = .{ .wrap = false } });
1263309 try container.field("a", 1, .{});
1264310 try container.end();
1265311 if (whitespace) {
1266 try std.testing.expectEqualStrings(".{ .a = 1 }", buf.items);
312 try std.testing.expectEqualStrings(".{ .a = 1 }", aw.getWritten());
1267313 } else {
1268 try std.testing.expectEqualStrings(".{.a=1}", buf.items);
314 try std.testing.expectEqualStrings(".{.a=1}", aw.getWritten());
1269315 }
1270 buf.clearRetainingCapacity();
316 aw.clearRetainingCapacity();
1271317 }
1272318
1273319 {
1274320 // We get extra spaces here, since we didn't know up front that there would only be one
1275321 // field.
1276 var container = try sz.beginTuple(.{ .whitespace_style = .{ .wrap = false } });
322 var container = try s.beginTuple(.{ .whitespace_style = .{ .wrap = false } });
1277323 try container.field(1, .{});
1278324 try container.end();
1279325 if (whitespace) {
1280 try std.testing.expectEqualStrings(".{ 1 }", buf.items);
326 try std.testing.expectEqualStrings(".{ 1 }", aw.getWritten());
1281327 } else {
1282 try std.testing.expectEqualStrings(".{1}", buf.items);
328 try std.testing.expectEqualStrings(".{1}", aw.getWritten());
1283329 }
1284 buf.clearRetainingCapacity();
330 aw.clearRetainingCapacity();
1285331 }
1286332
1287333 {
1288 var container = try sz.beginStruct(.{ .whitespace_style = .{ .fields = 1 } });
334 var container = try s.beginStruct(.{ .whitespace_style = .{ .fields = 1 } });
1289335 try container.field("a", 1, .{});
1290336 try container.end();
1291337 if (whitespace) {
1292 try std.testing.expectEqualStrings(".{ .a = 1 }", buf.items);
338 try std.testing.expectEqualStrings(".{ .a = 1 }", aw.getWritten());
1293339 } else {
1294 try std.testing.expectEqualStrings(".{.a=1}", buf.items);
340 try std.testing.expectEqualStrings(".{.a=1}", aw.getWritten());
1295341 }
1296 buf.clearRetainingCapacity();
342 aw.clearRetainingCapacity();
1297343 }
1298344
1299345 {
1300 var container = try sz.beginTuple(.{ .whitespace_style = .{ .fields = 1 } });
346 var container = try s.beginTuple(.{ .whitespace_style = .{ .fields = 1 } });
1301347 try container.field(1, .{});
1302348 try container.end();
1303 try std.testing.expectEqualStrings(".{1}", buf.items);
1304 buf.clearRetainingCapacity();
349 try std.testing.expectEqualStrings(".{1}", aw.getWritten());
350 aw.clearRetainingCapacity();
1305351 }
1306352
1307353 // Size 2
1308354 {
1309 var container = try sz.beginStruct(.{});
355 var container = try s.beginStruct(.{});
1310356 try container.field("a", 1, .{});
1311357 try container.field("b", 2, .{});
1312358 try container.end();
......@@ -1316,15 +362,15 @@ test "std.zon stringify whitespace, low level API" {
1316362 \\ .a = 1,
1317363 \\ .b = 2,
1318364 \\}
1319 , buf.items);
365 , aw.getWritten());
1320366 } else {
1321 try std.testing.expectEqualStrings(".{.a=1,.b=2}", buf.items);
367 try std.testing.expectEqualStrings(".{.a=1,.b=2}", aw.getWritten());
1322368 }
1323 buf.clearRetainingCapacity();
369 aw.clearRetainingCapacity();
1324370 }
1325371
1326372 {
1327 var container = try sz.beginTuple(.{});
373 var container = try s.beginTuple(.{});
1328374 try container.field(1, .{});
1329375 try container.field(2, .{});
1330376 try container.end();
......@@ -1334,68 +380,68 @@ test "std.zon stringify whitespace, low level API" {
1334380 \\ 1,
1335381 \\ 2,
1336382 \\}
1337 , buf.items);
383 , aw.getWritten());
1338384 } else {
1339 try std.testing.expectEqualStrings(".{1,2}", buf.items);
385 try std.testing.expectEqualStrings(".{1,2}", aw.getWritten());
1340386 }
1341 buf.clearRetainingCapacity();
387 aw.clearRetainingCapacity();
1342388 }
1343389
1344390 {
1345 var container = try sz.beginStruct(.{ .whitespace_style = .{ .wrap = false } });
391 var container = try s.beginStruct(.{ .whitespace_style = .{ .wrap = false } });
1346392 try container.field("a", 1, .{});
1347393 try container.field("b", 2, .{});
1348394 try container.end();
1349395 if (whitespace) {
1350 try std.testing.expectEqualStrings(".{ .a = 1, .b = 2 }", buf.items);
396 try std.testing.expectEqualStrings(".{ .a = 1, .b = 2 }", aw.getWritten());
1351397 } else {
1352 try std.testing.expectEqualStrings(".{.a=1,.b=2}", buf.items);
398 try std.testing.expectEqualStrings(".{.a=1,.b=2}", aw.getWritten());
1353399 }
1354 buf.clearRetainingCapacity();
400 aw.clearRetainingCapacity();
1355401 }
1356402
1357403 {
1358 var container = try sz.beginTuple(.{ .whitespace_style = .{ .wrap = false } });
404 var container = try s.beginTuple(.{ .whitespace_style = .{ .wrap = false } });
1359405 try container.field(1, .{});
1360406 try container.field(2, .{});
1361407 try container.end();
1362408 if (whitespace) {
1363 try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items);
409 try std.testing.expectEqualStrings(".{ 1, 2 }", aw.getWritten());
1364410 } else {
1365 try std.testing.expectEqualStrings(".{1,2}", buf.items);
411 try std.testing.expectEqualStrings(".{1,2}", aw.getWritten());
1366412 }
1367 buf.clearRetainingCapacity();
413 aw.clearRetainingCapacity();
1368414 }
1369415
1370416 {
1371 var container = try sz.beginStruct(.{ .whitespace_style = .{ .fields = 2 } });
417 var container = try s.beginStruct(.{ .whitespace_style = .{ .fields = 2 } });
1372418 try container.field("a", 1, .{});
1373419 try container.field("b", 2, .{});
1374420 try container.end();
1375421 if (whitespace) {
1376 try std.testing.expectEqualStrings(".{ .a = 1, .b = 2 }", buf.items);
422 try std.testing.expectEqualStrings(".{ .a = 1, .b = 2 }", aw.getWritten());
1377423 } else {
1378 try std.testing.expectEqualStrings(".{.a=1,.b=2}", buf.items);
424 try std.testing.expectEqualStrings(".{.a=1,.b=2}", aw.getWritten());
1379425 }
1380 buf.clearRetainingCapacity();
426 aw.clearRetainingCapacity();
1381427 }
1382428
1383429 {
1384 var container = try sz.beginTuple(.{ .whitespace_style = .{ .fields = 2 } });
430 var container = try s.beginTuple(.{ .whitespace_style = .{ .fields = 2 } });
1385431 try container.field(1, .{});
1386432 try container.field(2, .{});
1387433 try container.end();
1388434 if (whitespace) {
1389 try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items);
435 try std.testing.expectEqualStrings(".{ 1, 2 }", aw.getWritten());
1390436 } else {
1391 try std.testing.expectEqualStrings(".{1,2}", buf.items);
437 try std.testing.expectEqualStrings(".{1,2}", aw.getWritten());
1392438 }
1393 buf.clearRetainingCapacity();
439 aw.clearRetainingCapacity();
1394440 }
1395441
1396442 // Size 3
1397443 {
1398 var container = try sz.beginStruct(.{});
444 var container = try s.beginStruct(.{});
1399445 try container.field("a", 1, .{});
1400446 try container.field("b", 2, .{});
1401447 try container.field("c", 3, .{});
......@@ -1407,15 +453,15 @@ test "std.zon stringify whitespace, low level API" {
1407453 \\ .b = 2,
1408454 \\ .c = 3,
1409455 \\}
1410 , buf.items);
456 , aw.getWritten());
1411457 } else {
1412 try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", buf.items);
458 try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", aw.getWritten());
1413459 }
1414 buf.clearRetainingCapacity();
460 aw.clearRetainingCapacity();
1415461 }
1416462
1417463 {
1418 var container = try sz.beginTuple(.{});
464 var container = try s.beginTuple(.{});
1419465 try container.field(1, .{});
1420466 try container.field(2, .{});
1421467 try container.field(3, .{});
......@@ -1427,43 +473,43 @@ test "std.zon stringify whitespace, low level API" {
1427473 \\ 2,
1428474 \\ 3,
1429475 \\}
1430 , buf.items);
476 , aw.getWritten());
1431477 } else {
1432 try std.testing.expectEqualStrings(".{1,2,3}", buf.items);
478 try std.testing.expectEqualStrings(".{1,2,3}", aw.getWritten());
1433479 }
1434 buf.clearRetainingCapacity();
480 aw.clearRetainingCapacity();
1435481 }
1436482
1437483 {
1438 var container = try sz.beginStruct(.{ .whitespace_style = .{ .wrap = false } });
484 var container = try s.beginStruct(.{ .whitespace_style = .{ .wrap = false } });
1439485 try container.field("a", 1, .{});
1440486 try container.field("b", 2, .{});
1441487 try container.field("c", 3, .{});
1442488 try container.end();
1443489 if (whitespace) {
1444 try std.testing.expectEqualStrings(".{ .a = 1, .b = 2, .c = 3 }", buf.items);
490 try std.testing.expectEqualStrings(".{ .a = 1, .b = 2, .c = 3 }", aw.getWritten());
1445491 } else {
1446 try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", buf.items);
492 try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", aw.getWritten());
1447493 }
1448 buf.clearRetainingCapacity();
494 aw.clearRetainingCapacity();
1449495 }
1450496
1451497 {
1452 var container = try sz.beginTuple(.{ .whitespace_style = .{ .wrap = false } });
498 var container = try s.beginTuple(.{ .whitespace_style = .{ .wrap = false } });
1453499 try container.field(1, .{});
1454500 try container.field(2, .{});
1455501 try container.field(3, .{});
1456502 try container.end();
1457503 if (whitespace) {
1458 try std.testing.expectEqualStrings(".{ 1, 2, 3 }", buf.items);
504 try std.testing.expectEqualStrings(".{ 1, 2, 3 }", aw.getWritten());
1459505 } else {
1460 try std.testing.expectEqualStrings(".{1,2,3}", buf.items);
506 try std.testing.expectEqualStrings(".{1,2,3}", aw.getWritten());
1461507 }
1462 buf.clearRetainingCapacity();
508 aw.clearRetainingCapacity();
1463509 }
1464510
1465511 {
1466 var container = try sz.beginStruct(.{ .whitespace_style = .{ .fields = 3 } });
512 var container = try s.beginStruct(.{ .whitespace_style = .{ .fields = 3 } });
1467513 try container.field("a", 1, .{});
1468514 try container.field("b", 2, .{});
1469515 try container.field("c", 3, .{});
......@@ -1475,15 +521,15 @@ test "std.zon stringify whitespace, low level API" {
1475521 \\ .b = 2,
1476522 \\ .c = 3,
1477523 \\}
1478 , buf.items);
524 , aw.getWritten());
1479525 } else {
1480 try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", buf.items);
526 try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", aw.getWritten());
1481527 }
1482 buf.clearRetainingCapacity();
528 aw.clearRetainingCapacity();
1483529 }
1484530
1485531 {
1486 var container = try sz.beginTuple(.{ .whitespace_style = .{ .fields = 3 } });
532 var container = try s.beginTuple(.{ .whitespace_style = .{ .fields = 3 } });
1487533 try container.field(1, .{});
1488534 try container.field(2, .{});
1489535 try container.field(3, .{});
......@@ -1495,16 +541,16 @@ test "std.zon stringify whitespace, low level API" {
1495541 \\ 2,
1496542 \\ 3,
1497543 \\}
1498 , buf.items);
544 , aw.getWritten());
1499545 } else {
1500 try std.testing.expectEqualStrings(".{1,2,3}", buf.items);
546 try std.testing.expectEqualStrings(".{1,2,3}", aw.getWritten());
1501547 }
1502 buf.clearRetainingCapacity();
548 aw.clearRetainingCapacity();
1503549 }
1504550
1505551 // Nested objects where the outer container doesn't wrap but the inner containers do
1506552 {
1507 var container = try sz.beginStruct(.{ .whitespace_style = .{ .wrap = false } });
553 var container = try s.beginStruct(.{ .whitespace_style = .{ .wrap = false } });
1508554 try container.field("first", .{ 1, 2, 3 }, .{});
1509555 try container.field("second", .{ 4, 5, 6 }, .{});
1510556 try container.end();
......@@ -1519,139 +565,141 @@ test "std.zon stringify whitespace, low level API" {
1519565 \\ 5,
1520566 \\ 6,
1521567 \\} }
1522 , buf.items);
568 , aw.getWritten());
1523569 } else {
1524570 try std.testing.expectEqualStrings(
1525571 ".{.first=.{1,2,3},.second=.{4,5,6}}",
1526 buf.items,
572 aw.getWritten(),
1527573 );
1528574 }
1529 buf.clearRetainingCapacity();
575 aw.clearRetainingCapacity();
1530576 }
1531577 }
1532578}
1533579
1534580test "std.zon stringify utf8 codepoints" {
1535 var buf = std.ArrayList(u8).init(std.testing.allocator);
1536 defer buf.deinit();
1537 var sz = serializer(buf.writer(), .{});
581 var aw: Writer.Allocating = .init(std.testing.allocator);
582 var s: Serializer = .{ .writer = &aw.writer };
583 defer aw.deinit();
1538584
1539585 // Printable ASCII
1540 try sz.int('a');
1541 try std.testing.expectEqualStrings("97", buf.items);
1542 buf.clearRetainingCapacity();
586 try s.int('a');
587 try std.testing.expectEqualStrings("97", aw.getWritten());
588 aw.clearRetainingCapacity();
1543589
1544 try sz.codePoint('a');
1545 try std.testing.expectEqualStrings("'a'", buf.items);
1546 buf.clearRetainingCapacity();
590 try s.codePoint('a');
591 try std.testing.expectEqualStrings("'a'", aw.getWritten());
592 aw.clearRetainingCapacity();
1547593
1548 try sz.value('a', .{ .emit_codepoint_literals = .always });
1549 try std.testing.expectEqualStrings("'a'", buf.items);
1550 buf.clearRetainingCapacity();
594 try s.value('a', .{ .emit_codepoint_literals = .always });
595 try std.testing.expectEqualStrings("'a'", aw.getWritten());
596 aw.clearRetainingCapacity();
1551597
1552 try sz.value('a', .{ .emit_codepoint_literals = .printable_ascii });
1553 try std.testing.expectEqualStrings("'a'", buf.items);
1554 buf.clearRetainingCapacity();
598 try s.value('a', .{ .emit_codepoint_literals = .printable_ascii });
599 try std.testing.expectEqualStrings("'a'", aw.getWritten());
600 aw.clearRetainingCapacity();
1555601
1556 try sz.value('a', .{ .emit_codepoint_literals = .never });
1557 try std.testing.expectEqualStrings("97", buf.items);
1558 buf.clearRetainingCapacity();
602 try s.value('a', .{ .emit_codepoint_literals = .never });
603 try std.testing.expectEqualStrings("97", aw.getWritten());
604 aw.clearRetainingCapacity();
1559605
1560606 // Short escaped codepoint
1561 try sz.int('\n');
1562 try std.testing.expectEqualStrings("10", buf.items);
1563 buf.clearRetainingCapacity();
607 try s.int('\n');
608 try std.testing.expectEqualStrings("10", aw.getWritten());
609 aw.clearRetainingCapacity();
1564610
1565 try sz.codePoint('\n');
1566 try std.testing.expectEqualStrings("'\\n'", buf.items);
1567 buf.clearRetainingCapacity();
611 try s.codePoint('\n');
612 try std.testing.expectEqualStrings("'\\n'", aw.getWritten());
613 aw.clearRetainingCapacity();
1568614
1569 try sz.value('\n', .{ .emit_codepoint_literals = .always });
1570 try std.testing.expectEqualStrings("'\\n'", buf.items);
1571 buf.clearRetainingCapacity();
615 try s.value('\n', .{ .emit_codepoint_literals = .always });
616 try std.testing.expectEqualStrings("'\\n'", aw.getWritten());
617 aw.clearRetainingCapacity();
1572618
1573 try sz.value('\n', .{ .emit_codepoint_literals = .printable_ascii });
1574 try std.testing.expectEqualStrings("10", buf.items);
1575 buf.clearRetainingCapacity();
619 try s.value('\n', .{ .emit_codepoint_literals = .printable_ascii });
620 try std.testing.expectEqualStrings("10", aw.getWritten());
621 aw.clearRetainingCapacity();
1576622
1577 try sz.value('\n', .{ .emit_codepoint_literals = .never });
1578 try std.testing.expectEqualStrings("10", buf.items);
1579 buf.clearRetainingCapacity();
623 try s.value('\n', .{ .emit_codepoint_literals = .never });
624 try std.testing.expectEqualStrings("10", aw.getWritten());
625 aw.clearRetainingCapacity();
1580626
1581627 // Large codepoint
1582 try sz.int('⚡');
1583 try std.testing.expectEqualStrings("9889", buf.items);
1584 buf.clearRetainingCapacity();
628 try s.int('⚡');
629 try std.testing.expectEqualStrings("9889", aw.getWritten());
630 aw.clearRetainingCapacity();
1585631
1586 try sz.codePoint('⚡');
1587 try std.testing.expectEqualStrings("'\\xe2\\x9a\\xa1'", buf.items);
1588 buf.clearRetainingCapacity();
632 try s.codePoint('⚡');
633 try std.testing.expectEqualStrings("'\\u{26a1}'", aw.getWritten());
634 aw.clearRetainingCapacity();
1589635
1590 try sz.value('⚡', .{ .emit_codepoint_literals = .always });
1591 try std.testing.expectEqualStrings("'\\xe2\\x9a\\xa1'", buf.items);
1592 buf.clearRetainingCapacity();
636 try s.value('⚡', .{ .emit_codepoint_literals = .always });
637 try std.testing.expectEqualStrings("'\\u{26a1}'", aw.getWritten());
638 aw.clearRetainingCapacity();
1593639
1594 try sz.value('⚡', .{ .emit_codepoint_literals = .printable_ascii });
1595 try std.testing.expectEqualStrings("9889", buf.items);
1596 buf.clearRetainingCapacity();
640 try s.value('⚡', .{ .emit_codepoint_literals = .printable_ascii });
641 try std.testing.expectEqualStrings("9889", aw.getWritten());
642 aw.clearRetainingCapacity();
1597643
1598 try sz.value('⚡', .{ .emit_codepoint_literals = .never });
1599 try std.testing.expectEqualStrings("9889", buf.items);
1600 buf.clearRetainingCapacity();
644 try s.value('⚡', .{ .emit_codepoint_literals = .never });
645 try std.testing.expectEqualStrings("9889", aw.getWritten());
646 aw.clearRetainingCapacity();
1601647
1602648 // Invalid codepoint
1603 try std.testing.expectError(error.InvalidCodepoint, sz.codePoint(0x110000 + 1));
649 try s.codePoint(0x110000 + 1);
650 try std.testing.expectEqualStrings("'\\u{110001}'", aw.getWritten());
651 aw.clearRetainingCapacity();
1604652
1605 try sz.int(0x110000 + 1);
1606 try std.testing.expectEqualStrings("1114113", buf.items);
1607 buf.clearRetainingCapacity();
653 try s.int(0x110000 + 1);
654 try std.testing.expectEqualStrings("1114113", aw.getWritten());
655 aw.clearRetainingCapacity();
1608656
1609 try sz.value(0x110000 + 1, .{ .emit_codepoint_literals = .always });
1610 try std.testing.expectEqualStrings("1114113", buf.items);
1611 buf.clearRetainingCapacity();
657 try s.value(0x110000 + 1, .{ .emit_codepoint_literals = .always });
658 try std.testing.expectEqualStrings("1114113", aw.getWritten());
659 aw.clearRetainingCapacity();
1612660
1613 try sz.value(0x110000 + 1, .{ .emit_codepoint_literals = .printable_ascii });
1614 try std.testing.expectEqualStrings("1114113", buf.items);
1615 buf.clearRetainingCapacity();
661 try s.value(0x110000 + 1, .{ .emit_codepoint_literals = .printable_ascii });
662 try std.testing.expectEqualStrings("1114113", aw.getWritten());
663 aw.clearRetainingCapacity();
1616664
1617 try sz.value(0x110000 + 1, .{ .emit_codepoint_literals = .never });
1618 try std.testing.expectEqualStrings("1114113", buf.items);
1619 buf.clearRetainingCapacity();
665 try s.value(0x110000 + 1, .{ .emit_codepoint_literals = .never });
666 try std.testing.expectEqualStrings("1114113", aw.getWritten());
667 aw.clearRetainingCapacity();
1620668
1621669 // Valid codepoint, not a codepoint type
1622 try sz.value(@as(u22, 'a'), .{ .emit_codepoint_literals = .always });
1623 try std.testing.expectEqualStrings("97", buf.items);
1624 buf.clearRetainingCapacity();
670 try s.value(@as(u22, 'a'), .{ .emit_codepoint_literals = .always });
671 try std.testing.expectEqualStrings("97", aw.getWritten());
672 aw.clearRetainingCapacity();
1625673
1626 try sz.value(@as(u22, 'a'), .{ .emit_codepoint_literals = .printable_ascii });
1627 try std.testing.expectEqualStrings("97", buf.items);
1628 buf.clearRetainingCapacity();
674 try s.value(@as(u22, 'a'), .{ .emit_codepoint_literals = .printable_ascii });
675 try std.testing.expectEqualStrings("97", aw.getWritten());
676 aw.clearRetainingCapacity();
1629677
1630 try sz.value(@as(i32, 'a'), .{ .emit_codepoint_literals = .never });
1631 try std.testing.expectEqualStrings("97", buf.items);
1632 buf.clearRetainingCapacity();
678 try s.value(@as(i32, 'a'), .{ .emit_codepoint_literals = .never });
679 try std.testing.expectEqualStrings("97", aw.getWritten());
680 aw.clearRetainingCapacity();
1633681
1634682 // Make sure value options are passed to children
1635 try sz.value(.{ .c = '⚡' }, .{ .emit_codepoint_literals = .always });
1636 try std.testing.expectEqualStrings(".{ .c = '\\xe2\\x9a\\xa1' }", buf.items);
1637 buf.clearRetainingCapacity();
683 try s.value(.{ .c = '⚡' }, .{ .emit_codepoint_literals = .always });
684 try std.testing.expectEqualStrings(".{ .c = '\\u{26a1}' }", aw.getWritten());
685 aw.clearRetainingCapacity();
1638686
1639 try sz.value(.{ .c = '⚡' }, .{ .emit_codepoint_literals = .never });
1640 try std.testing.expectEqualStrings(".{ .c = 9889 }", buf.items);
1641 buf.clearRetainingCapacity();
687 try s.value(.{ .c = '⚡' }, .{ .emit_codepoint_literals = .never });
688 try std.testing.expectEqualStrings(".{ .c = 9889 }", aw.getWritten());
689 aw.clearRetainingCapacity();
1642690}
1643691
1644692test "std.zon stringify strings" {
1645 var buf = std.ArrayList(u8).init(std.testing.allocator);
1646 defer buf.deinit();
1647 var sz = serializer(buf.writer(), .{});
693 var aw: Writer.Allocating = .init(std.testing.allocator);
694 var s: Serializer = .{ .writer = &aw.writer };
695 defer aw.deinit();
1648696
1649697 // Minimal case
1650 try sz.string("abc⚡\n");
1651 try std.testing.expectEqualStrings("\"abc\\xe2\\x9a\\xa1\\n\"", buf.items);
1652 buf.clearRetainingCapacity();
698 try s.string("abc⚡\n");
699 try std.testing.expectEqualStrings("\"abc\\xe2\\x9a\\xa1\\n\"", aw.getWritten());
700 aw.clearRetainingCapacity();
1653701
1654 try sz.tuple("abc⚡\n", .{});
702 try s.tuple("abc⚡\n", .{});
1655703 try std.testing.expectEqualStrings(
1656704 \\.{
1657705 \\ 97,
......@@ -1662,14 +710,14 @@ test "std.zon stringify strings" {
1662710 \\ 161,
1663711 \\ 10,
1664712 \\}
1665 , buf.items);
1666 buf.clearRetainingCapacity();
713 , aw.getWritten());
714 aw.clearRetainingCapacity();
1667715
1668 try sz.value("abc⚡\n", .{});
1669 try std.testing.expectEqualStrings("\"abc\\xe2\\x9a\\xa1\\n\"", buf.items);
1670 buf.clearRetainingCapacity();
716 try s.value("abc⚡\n", .{});
717 try std.testing.expectEqualStrings("\"abc\\xe2\\x9a\\xa1\\n\"", aw.getWritten());
718 aw.clearRetainingCapacity();
1671719
1672 try sz.value("abc⚡\n", .{ .emit_strings_as_containers = true });
720 try s.value("abc⚡\n", .{ .emit_strings_as_containers = true });
1673721 try std.testing.expectEqualStrings(
1674722 \\.{
1675723 \\ 97,
......@@ -1680,113 +728,113 @@ test "std.zon stringify strings" {
1680728 \\ 161,
1681729 \\ 10,
1682730 \\}
1683 , buf.items);
1684 buf.clearRetainingCapacity();
731 , aw.getWritten());
732 aw.clearRetainingCapacity();
1685733
1686734 // Value options are inherited by children
1687 try sz.value(.{ .str = "abc" }, .{});
1688 try std.testing.expectEqualStrings(".{ .str = \"abc\" }", buf.items);
1689 buf.clearRetainingCapacity();
735 try s.value(.{ .str = "abc" }, .{});
736 try std.testing.expectEqualStrings(".{ .str = \"abc\" }", aw.getWritten());
737 aw.clearRetainingCapacity();
1690738
1691 try sz.value(.{ .str = "abc" }, .{ .emit_strings_as_containers = true });
739 try s.value(.{ .str = "abc" }, .{ .emit_strings_as_containers = true });
1692740 try std.testing.expectEqualStrings(
1693741 \\.{ .str = .{
1694742 \\ 97,
1695743 \\ 98,
1696744 \\ 99,
1697745 \\} }
1698 , buf.items);
1699 buf.clearRetainingCapacity();
746 , aw.getWritten());
747 aw.clearRetainingCapacity();
1700748
1701749 // Arrays (rather than pointers to arrays) of u8s are not considered strings, so that data can
1702750 // round trip correctly.
1703 try sz.value("abc".*, .{});
751 try s.value("abc".*, .{});
1704752 try std.testing.expectEqualStrings(
1705753 \\.{
1706754 \\ 97,
1707755 \\ 98,
1708756 \\ 99,
1709757 \\}
1710 , buf.items);
1711 buf.clearRetainingCapacity();
758 , aw.getWritten());
759 aw.clearRetainingCapacity();
1712760}
1713761
1714762test "std.zon stringify multiline strings" {
1715 var buf = std.ArrayList(u8).init(std.testing.allocator);
1716 defer buf.deinit();
1717 var sz = serializer(buf.writer(), .{});
763 var aw: Writer.Allocating = .init(std.testing.allocator);
764 var s: Serializer = .{ .writer = &aw.writer };
765 defer aw.deinit();
1718766
1719767 inline for (.{ true, false }) |whitespace| {
1720 sz.options.whitespace = whitespace;
768 s.options.whitespace = whitespace;
1721769
1722770 {
1723 try sz.multilineString("", .{ .top_level = true });
1724 try std.testing.expectEqualStrings("\\\\", buf.items);
1725 buf.clearRetainingCapacity();
771 try s.multilineString("", .{ .top_level = true });
772 try std.testing.expectEqualStrings("\\\\", aw.getWritten());
773 aw.clearRetainingCapacity();
1726774 }
1727775
1728776 {
1729 try sz.multilineString("abc⚡", .{ .top_level = true });
1730 try std.testing.expectEqualStrings("\\\\abc⚡", buf.items);
1731 buf.clearRetainingCapacity();
777 try s.multilineString("abc⚡", .{ .top_level = true });
778 try std.testing.expectEqualStrings("\\\\abc⚡", aw.getWritten());
779 aw.clearRetainingCapacity();
1732780 }
1733781
1734782 {
1735 try sz.multilineString("abc⚡\ndef", .{ .top_level = true });
1736 try std.testing.expectEqualStrings("\\\\abc⚡\n\\\\def", buf.items);
1737 buf.clearRetainingCapacity();
783 try s.multilineString("abc⚡\ndef", .{ .top_level = true });
784 try std.testing.expectEqualStrings("\\\\abc⚡\n\\\\def", aw.getWritten());
785 aw.clearRetainingCapacity();
1738786 }
1739787
1740788 {
1741 try sz.multilineString("abc⚡\r\ndef", .{ .top_level = true });
1742 try std.testing.expectEqualStrings("\\\\abc⚡\n\\\\def", buf.items);
1743 buf.clearRetainingCapacity();
789 try s.multilineString("abc⚡\r\ndef", .{ .top_level = true });
790 try std.testing.expectEqualStrings("\\\\abc⚡\n\\\\def", aw.getWritten());
791 aw.clearRetainingCapacity();
1744792 }
1745793
1746794 {
1747 try sz.multilineString("\nabc⚡", .{ .top_level = true });
1748 try std.testing.expectEqualStrings("\\\\\n\\\\abc⚡", buf.items);
1749 buf.clearRetainingCapacity();
795 try s.multilineString("\nabc⚡", .{ .top_level = true });
796 try std.testing.expectEqualStrings("\\\\\n\\\\abc⚡", aw.getWritten());
797 aw.clearRetainingCapacity();
1750798 }
1751799
1752800 {
1753 try sz.multilineString("\r\nabc⚡", .{ .top_level = true });
1754 try std.testing.expectEqualStrings("\\\\\n\\\\abc⚡", buf.items);
1755 buf.clearRetainingCapacity();
801 try s.multilineString("\r\nabc⚡", .{ .top_level = true });
802 try std.testing.expectEqualStrings("\\\\\n\\\\abc⚡", aw.getWritten());
803 aw.clearRetainingCapacity();
1756804 }
1757805
1758806 {
1759 try sz.multilineString("abc\ndef", .{});
807 try s.multilineString("abc\ndef", .{});
1760808 if (whitespace) {
1761 try std.testing.expectEqualStrings("\n\\\\abc\n\\\\def\n", buf.items);
809 try std.testing.expectEqualStrings("\n\\\\abc\n\\\\def\n", aw.getWritten());
1762810 } else {
1763 try std.testing.expectEqualStrings("\\\\abc\n\\\\def\n", buf.items);
811 try std.testing.expectEqualStrings("\\\\abc\n\\\\def\n", aw.getWritten());
1764812 }
1765 buf.clearRetainingCapacity();
813 aw.clearRetainingCapacity();
1766814 }
1767815
1768816 {
1769817 const str: []const u8 = &.{ 'a', '\r', 'c' };
1770 try sz.string(str);
1771 try std.testing.expectEqualStrings("\"a\\rc\"", buf.items);
1772 buf.clearRetainingCapacity();
818 try s.string(str);
819 try std.testing.expectEqualStrings("\"a\\rc\"", aw.getWritten());
820 aw.clearRetainingCapacity();
1773821 }
1774822
1775823 {
1776824 try std.testing.expectError(
1777825 error.InnerCarriageReturn,
1778 sz.multilineString(@as([]const u8, &.{ 'a', '\r', 'c' }), .{}),
826 s.multilineString(@as([]const u8, &.{ 'a', '\r', 'c' }), .{}),
1779827 );
1780828 try std.testing.expectError(
1781829 error.InnerCarriageReturn,
1782 sz.multilineString(@as([]const u8, &.{ 'a', '\r', 'c', '\n' }), .{}),
830 s.multilineString(@as([]const u8, &.{ 'a', '\r', 'c', '\n' }), .{}),
1783831 );
1784832 try std.testing.expectError(
1785833 error.InnerCarriageReturn,
1786 sz.multilineString(@as([]const u8, &.{ 'a', '\r', 'c', '\r', '\n' }), .{}),
834 s.multilineString(@as([]const u8, &.{ 'a', '\r', 'c', '\r', '\n' }), .{}),
1787835 );
1788 try std.testing.expectEqualStrings("", buf.items);
1789 buf.clearRetainingCapacity();
836 try std.testing.expectEqualStrings("", aw.getWritten());
837 aw.clearRetainingCapacity();
1790838 }
1791839 }
1792840}
......@@ -1932,42 +980,43 @@ test "std.zon stringify skip default fields" {
1932980}
1933981
1934982test "std.zon depth limits" {
1935 var buf = std.ArrayList(u8).init(std.testing.allocator);
1936 defer buf.deinit();
983 var aw: Writer.Allocating = .init(std.testing.allocator);
984 const bw = &aw.writer;
985 defer aw.deinit();
1937986
1938987 const Recurse = struct { r: []const @This() };
1939988
1940989 // Normal operation
1941 try serializeMaxDepth(.{ 1, .{ 2, 3 } }, .{}, buf.writer(), 16);
1942 try std.testing.expectEqualStrings(".{ 1, .{ 2, 3 } }", buf.items);
1943 buf.clearRetainingCapacity();
990 try serializeMaxDepth(.{ 1, .{ 2, 3 } }, .{}, bw, 16);
991 try std.testing.expectEqualStrings(".{ 1, .{ 2, 3 } }", aw.getWritten());
992 aw.clearRetainingCapacity();
1944993
1945 try serializeArbitraryDepth(.{ 1, .{ 2, 3 } }, .{}, buf.writer());
1946 try std.testing.expectEqualStrings(".{ 1, .{ 2, 3 } }", buf.items);
1947 buf.clearRetainingCapacity();
994 try serializeArbitraryDepth(.{ 1, .{ 2, 3 } }, .{}, bw);
995 try std.testing.expectEqualStrings(".{ 1, .{ 2, 3 } }", aw.getWritten());
996 aw.clearRetainingCapacity();
1948997
1949998 // Max depth failing on non recursive type
1950999 try std.testing.expectError(
19511000 error.ExceededMaxDepth,
1952 serializeMaxDepth(.{ 1, .{ 2, .{ 3, 4 } } }, .{}, buf.writer(), 3),
1001 serializeMaxDepth(.{ 1, .{ 2, .{ 3, 4 } } }, .{}, bw, 3),
19531002 );
1954 try std.testing.expectEqualStrings("", buf.items);
1955 buf.clearRetainingCapacity();
1003 try std.testing.expectEqualStrings("", aw.getWritten());
1004 aw.clearRetainingCapacity();
19561005
19571006 // Max depth passing on recursive type
19581007 {
19591008 const maybe_recurse = Recurse{ .r = &.{} };
1960 try serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 2);
1961 try std.testing.expectEqualStrings(".{ .r = .{} }", buf.items);
1962 buf.clearRetainingCapacity();
1009 try serializeMaxDepth(maybe_recurse, .{}, bw, 2);
1010 try std.testing.expectEqualStrings(".{ .r = .{} }", aw.getWritten());
1011 aw.clearRetainingCapacity();
19631012 }
19641013
19651014 // Unchecked passing on recursive type
19661015 {
19671016 const maybe_recurse = Recurse{ .r = &.{} };
1968 try serializeArbitraryDepth(maybe_recurse, .{}, buf.writer());
1969 try std.testing.expectEqualStrings(".{ .r = .{} }", buf.items);
1970 buf.clearRetainingCapacity();
1017 try serializeArbitraryDepth(maybe_recurse, .{}, bw);
1018 try std.testing.expectEqualStrings(".{ .r = .{} }", aw.getWritten());
1019 aw.clearRetainingCapacity();
19711020 }
19721021
19731022 // Max depth failing on recursive type due to depth
......@@ -1976,10 +1025,10 @@ test "std.zon depth limits" {
19761025 maybe_recurse.r = &.{.{ .r = &.{} }};
19771026 try std.testing.expectError(
19781027 error.ExceededMaxDepth,
1979 serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 2),
1028 serializeMaxDepth(maybe_recurse, .{}, bw, 2),
19801029 );
1981 try std.testing.expectEqualStrings("", buf.items);
1982 buf.clearRetainingCapacity();
1030 try std.testing.expectEqualStrings("", aw.getWritten());
1031 aw.clearRetainingCapacity();
19831032 }
19841033
19851034 // Same but for a slice
......@@ -1989,23 +1038,23 @@ test "std.zon depth limits" {
19891038
19901039 try std.testing.expectError(
19911040 error.ExceededMaxDepth,
1992 serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 2),
1041 serializeMaxDepth(maybe_recurse, .{}, bw, 2),
19931042 );
1994 try std.testing.expectEqualStrings("", buf.items);
1995 buf.clearRetainingCapacity();
1043 try std.testing.expectEqualStrings("", aw.getWritten());
1044 aw.clearRetainingCapacity();
19961045
1997 var sz = serializer(buf.writer(), .{});
1046 var s: Serializer = .{ .writer = bw };
19981047
19991048 try std.testing.expectError(
20001049 error.ExceededMaxDepth,
2001 sz.tupleMaxDepth(maybe_recurse, .{}, 2),
1050 s.tupleMaxDepth(maybe_recurse, .{}, 2),
20021051 );
2003 try std.testing.expectEqualStrings("", buf.items);
2004 buf.clearRetainingCapacity();
1052 try std.testing.expectEqualStrings("", aw.getWritten());
1053 aw.clearRetainingCapacity();
20051054
2006 try sz.tupleArbitraryDepth(maybe_recurse, .{});
2007 try std.testing.expectEqualStrings(".{.{ .r = .{} }}", buf.items);
2008 buf.clearRetainingCapacity();
1055 try s.tupleArbitraryDepth(maybe_recurse, .{});
1056 try std.testing.expectEqualStrings(".{.{ .r = .{} }}", aw.getWritten());
1057 aw.clearRetainingCapacity();
20091058 }
20101059
20111060 // A slice succeeding
......@@ -2013,19 +1062,19 @@ test "std.zon depth limits" {
20131062 var temp: [1]Recurse = .{.{ .r = &.{} }};
20141063 const maybe_recurse: []const Recurse = &temp;
20151064
2016 try serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 3);
2017 try std.testing.expectEqualStrings(".{.{ .r = .{} }}", buf.items);
2018 buf.clearRetainingCapacity();
1065 try serializeMaxDepth(maybe_recurse, .{}, bw, 3);
1066 try std.testing.expectEqualStrings(".{.{ .r = .{} }}", aw.getWritten());
1067 aw.clearRetainingCapacity();
20191068
2020 var sz = serializer(buf.writer(), .{});
1069 var s: Serializer = .{ .writer = bw };
20211070
2022 try sz.tupleMaxDepth(maybe_recurse, .{}, 3);
2023 try std.testing.expectEqualStrings(".{.{ .r = .{} }}", buf.items);
2024 buf.clearRetainingCapacity();
1071 try s.tupleMaxDepth(maybe_recurse, .{}, 3);
1072 try std.testing.expectEqualStrings(".{.{ .r = .{} }}", aw.getWritten());
1073 aw.clearRetainingCapacity();
20251074
2026 try sz.tupleArbitraryDepth(maybe_recurse, .{});
2027 try std.testing.expectEqualStrings(".{.{ .r = .{} }}", buf.items);
2028 buf.clearRetainingCapacity();
1075 try s.tupleArbitraryDepth(maybe_recurse, .{});
1076 try std.testing.expectEqualStrings(".{.{ .r = .{} }}", aw.getWritten());
1077 aw.clearRetainingCapacity();
20291078 }
20301079
20311080 // Max depth failing on recursive type due to recursion
......@@ -2036,46 +1085,46 @@ test "std.zon depth limits" {
20361085
20371086 try std.testing.expectError(
20381087 error.ExceededMaxDepth,
2039 serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 128),
1088 serializeMaxDepth(maybe_recurse, .{}, bw, 128),
20401089 );
2041 try std.testing.expectEqualStrings("", buf.items);
2042 buf.clearRetainingCapacity();
1090 try std.testing.expectEqualStrings("", aw.getWritten());
1091 aw.clearRetainingCapacity();
20431092
2044 var sz = serializer(buf.writer(), .{});
1093 var s: Serializer = .{ .writer = bw };
20451094 try std.testing.expectError(
20461095 error.ExceededMaxDepth,
2047 sz.tupleMaxDepth(maybe_recurse, .{}, 128),
1096 s.tupleMaxDepth(maybe_recurse, .{}, 128),
20481097 );
2049 try std.testing.expectEqualStrings("", buf.items);
2050 buf.clearRetainingCapacity();
1098 try std.testing.expectEqualStrings("", aw.getWritten());
1099 aw.clearRetainingCapacity();
20511100 }
20521101
20531102 // Max depth on other parts of the lower level API
20541103 {
2055 var sz = serializer(buf.writer(), .{});
1104 var s: Serializer = .{ .writer = bw };
20561105
20571106 const maybe_recurse: []const Recurse = &.{};
20581107
2059 try std.testing.expectError(error.ExceededMaxDepth, sz.valueMaxDepth(1, .{}, 0));
2060 try sz.valueMaxDepth(2, .{}, 1);
2061 try sz.value(3, .{});
2062 try sz.valueArbitraryDepth(maybe_recurse, .{});
1108 try std.testing.expectError(error.ExceededMaxDepth, s.valueMaxDepth(1, .{}, 0));
1109 try s.valueMaxDepth(2, .{}, 1);
1110 try s.value(3, .{});
1111 try s.valueArbitraryDepth(maybe_recurse, .{});
20631112
2064 var s = try sz.beginStruct(.{});
2065 try std.testing.expectError(error.ExceededMaxDepth, s.fieldMaxDepth("a", 1, .{}, 0));
2066 try s.fieldMaxDepth("b", 4, .{}, 1);
2067 try s.field("c", 5, .{});
2068 try s.fieldArbitraryDepth("d", maybe_recurse, .{});
2069 try s.end();
1113 var wip_struct = try s.beginStruct(.{});
1114 try std.testing.expectError(error.ExceededMaxDepth, wip_struct.fieldMaxDepth("a", 1, .{}, 0));
1115 try wip_struct.fieldMaxDepth("b", 4, .{}, 1);
1116 try wip_struct.field("c", 5, .{});
1117 try wip_struct.fieldArbitraryDepth("d", maybe_recurse, .{});
1118 try wip_struct.end();
20701119
2071 var t = try sz.beginTuple(.{});
1120 var t = try s.beginTuple(.{});
20721121 try std.testing.expectError(error.ExceededMaxDepth, t.fieldMaxDepth(1, .{}, 0));
20731122 try t.fieldMaxDepth(6, .{}, 1);
20741123 try t.field(7, .{});
20751124 try t.fieldArbitraryDepth(maybe_recurse, .{});
20761125 try t.end();
20771126
2078 var a = try sz.beginTuple(.{});
1127 var a = try s.beginTuple(.{});
20791128 try std.testing.expectError(error.ExceededMaxDepth, a.fieldMaxDepth(1, .{}, 0));
20801129 try a.fieldMaxDepth(8, .{}, 1);
20811130 try a.field(9, .{});
......@@ -2096,7 +1145,7 @@ test "std.zon depth limits" {
20961145 \\ 9,
20971146 \\ .{},
20981147 \\}
2099 , buf.items);
1148 , aw.getWritten());
21001149 }
21011150}
21021151
......@@ -2192,42 +1241,42 @@ test "std.zon stringify primitives" {
21921241}
21931242
21941243test "std.zon stringify ident" {
2195 var buf = std.ArrayList(u8).init(std.testing.allocator);
2196 defer buf.deinit();
2197 var sz = serializer(buf.writer(), .{});
1244 var aw: Writer.Allocating = .init(std.testing.allocator);
1245 var s: Serializer = .{ .writer = &aw.writer };
1246 defer aw.deinit();
21981247
21991248 try expectSerializeEqual(".{ .a = 0 }", .{ .a = 0 }, .{});
2200 try sz.ident("a");
2201 try std.testing.expectEqualStrings(".a", buf.items);
2202 buf.clearRetainingCapacity();
1249 try s.ident("a");
1250 try std.testing.expectEqualStrings(".a", aw.getWritten());
1251 aw.clearRetainingCapacity();
22031252
2204 try sz.ident("foo_1");
2205 try std.testing.expectEqualStrings(".foo_1", buf.items);
2206 buf.clearRetainingCapacity();
1253 try s.ident("foo_1");
1254 try std.testing.expectEqualStrings(".foo_1", aw.getWritten());
1255 aw.clearRetainingCapacity();
22071256
2208 try sz.ident("_foo_1");
2209 try std.testing.expectEqualStrings("._foo_1", buf.items);
2210 buf.clearRetainingCapacity();
1257 try s.ident("_foo_1");
1258 try std.testing.expectEqualStrings("._foo_1", aw.getWritten());
1259 aw.clearRetainingCapacity();
22111260
2212 try sz.ident("foo bar");
2213 try std.testing.expectEqualStrings(".@\"foo bar\"", buf.items);
2214 buf.clearRetainingCapacity();
1261 try s.ident("foo bar");
1262 try std.testing.expectEqualStrings(".@\"foo bar\"", aw.getWritten());
1263 aw.clearRetainingCapacity();
22151264
2216 try sz.ident("1foo");
2217 try std.testing.expectEqualStrings(".@\"1foo\"", buf.items);
2218 buf.clearRetainingCapacity();
1265 try s.ident("1foo");
1266 try std.testing.expectEqualStrings(".@\"1foo\"", aw.getWritten());
1267 aw.clearRetainingCapacity();
22191268
2220 try sz.ident("var");
2221 try std.testing.expectEqualStrings(".@\"var\"", buf.items);
2222 buf.clearRetainingCapacity();
1269 try s.ident("var");
1270 try std.testing.expectEqualStrings(".@\"var\"", aw.getWritten());
1271 aw.clearRetainingCapacity();
22231272
2224 try sz.ident("true");
2225 try std.testing.expectEqualStrings(".true", buf.items);
2226 buf.clearRetainingCapacity();
1273 try s.ident("true");
1274 try std.testing.expectEqualStrings(".true", aw.getWritten());
1275 aw.clearRetainingCapacity();
22271276
2228 try sz.ident("_");
2229 try std.testing.expectEqualStrings("._", buf.items);
2230 buf.clearRetainingCapacity();
1277 try s.ident("_");
1278 try std.testing.expectEqualStrings("._", aw.getWritten());
1279 aw.clearRetainingCapacity();
22311280
22321281 const Enum = enum {
22331282 @"foo bar",
......@@ -2239,40 +1288,40 @@ test "std.zon stringify ident" {
22391288}
22401289
22411290test "std.zon stringify as tuple" {
2242 var buf = std.ArrayList(u8).init(std.testing.allocator);
2243 defer buf.deinit();
2244 var sz = serializer(buf.writer(), .{});
1291 var aw: Writer.Allocating = .init(std.testing.allocator);
1292 var s: Serializer = .{ .writer = &aw.writer };
1293 defer aw.deinit();
22451294
22461295 // Tuples
2247 try sz.tuple(.{ 1, 2 }, .{});
2248 try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items);
2249 buf.clearRetainingCapacity();
1296 try s.tuple(.{ 1, 2 }, .{});
1297 try std.testing.expectEqualStrings(".{ 1, 2 }", aw.getWritten());
1298 aw.clearRetainingCapacity();
22501299
22511300 // Slice
2252 try sz.tuple(@as([]const u8, &.{ 1, 2 }), .{});
2253 try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items);
2254 buf.clearRetainingCapacity();
1301 try s.tuple(@as([]const u8, &.{ 1, 2 }), .{});
1302 try std.testing.expectEqualStrings(".{ 1, 2 }", aw.getWritten());
1303 aw.clearRetainingCapacity();
22551304
22561305 // Array
2257 try sz.tuple([2]u8{ 1, 2 }, .{});
2258 try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items);
2259 buf.clearRetainingCapacity();
1306 try s.tuple([2]u8{ 1, 2 }, .{});
1307 try std.testing.expectEqualStrings(".{ 1, 2 }", aw.getWritten());
1308 aw.clearRetainingCapacity();
22601309}
22611310
22621311test "std.zon stringify as float" {
2263 var buf = std.ArrayList(u8).init(std.testing.allocator);
2264 defer buf.deinit();
2265 var sz = serializer(buf.writer(), .{});
1312 var aw: Writer.Allocating = .init(std.testing.allocator);
1313 var s: Serializer = .{ .writer = &aw.writer };
1314 defer aw.deinit();
22661315
22671316 // Comptime float
2268 try sz.float(2.5);
2269 try std.testing.expectEqualStrings("2.5", buf.items);
2270 buf.clearRetainingCapacity();
1317 try s.float(2.5);
1318 try std.testing.expectEqualStrings("2.5", aw.getWritten());
1319 aw.clearRetainingCapacity();
22711320
22721321 // Sized float
2273 try sz.float(@as(f32, 2.5));
2274 try std.testing.expectEqualStrings("2.5", buf.items);
2275 buf.clearRetainingCapacity();
1322 try s.float(@as(f32, 2.5));
1323 try std.testing.expectEqualStrings("2.5", aw.getWritten());
1324 aw.clearRetainingCapacity();
22761325}
22771326
22781327test "std.zon stringify vector" {
......@@ -2364,13 +1413,13 @@ test "std.zon pointers" {
23641413}
23651414
23661415test "std.zon tuple/struct field" {
2367 var buf = std.ArrayList(u8).init(std.testing.allocator);
2368 defer buf.deinit();
2369 var sz = serializer(buf.writer(), .{});
1416 var aw: Writer.Allocating = .init(std.testing.allocator);
1417 var s: Serializer = .{ .writer = &aw.writer };
1418 defer aw.deinit();
23701419
23711420 // Test on structs
23721421 {
2373 var root = try sz.beginStruct(.{});
1422 var root = try s.beginStruct(.{});
23741423 {
23751424 var tuple = try root.beginTupleField("foo", .{});
23761425 try tuple.field(0, .{});
......@@ -2396,13 +1445,13 @@ test "std.zon tuple/struct field" {
23961445 \\ .b = 1,
23971446 \\ },
23981447 \\}
2399 , buf.items);
2400 buf.clearRetainingCapacity();
1448 , aw.getWritten());
1449 aw.clearRetainingCapacity();
24011450 }
24021451
24031452 // Test on tuples
24041453 {
2405 var root = try sz.beginTuple(.{});
1454 var root = try s.beginTuple(.{});
24061455 {
24071456 var tuple = try root.beginTupleField(.{});
24081457 try tuple.field(0, .{});
......@@ -2428,7 +1477,7 @@ test "std.zon tuple/struct field" {
24281477 \\ .b = 1,
24291478 \\ },
24301479 \\}
2431 , buf.items);
2432 buf.clearRetainingCapacity();
1480 , aw.getWritten());
1481 aw.clearRetainingCapacity();
24331482 }
24341483}
src/main.zig+6-3
......@@ -344,8 +344,9 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
344344 } else if (mem.eql(u8, cmd, "targets")) {
345345 dev.check(.targets_command);
346346 const host = std.zig.resolveTargetQueryOrFatal(.{});
347 const stdout = fs.File.stdout().deprecatedWriter();
348 return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, &host);
347 var stdout_writer = fs.File.stdout().writer(&stdio_buffer);
348 try @import("print_targets.zig").cmdTargets(arena, cmd_args, &stdout_writer.interface, &host);
349 return stdout_writer.interface.flush();
349350 } else if (mem.eql(u8, cmd, "version")) {
350351 dev.check(.version_command);
351352 try fs.File.stdout().writeAll(build_options.version ++ "\n");
......@@ -356,7 +357,9 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
356357 } else if (mem.eql(u8, cmd, "env")) {
357358 dev.check(.env_command);
358359 verifyLibcxxCorrectlyLinked();
359 return @import("print_env.zig").cmdEnv(arena, cmd_args);
360 var stdout_writer = fs.File.stdout().writer(&stdio_buffer);
361 try @import("print_env.zig").cmdEnv(arena, &stdout_writer.interface);
362 return stdout_writer.interface.flush();
360363 } else if (mem.eql(u8, cmd, "reduce")) {
361364 return jitCmd(gpa, arena, cmd_args, .{
362365 .cmd_name = "reduce",
src/print_env.zig+14-35
......@@ -4,8 +4,7 @@ const introspect = @import("introspect.zig");
44const Allocator = std.mem.Allocator;
55const fatal = std.process.fatal;
66
7pub fn cmdEnv(arena: Allocator, args: []const []const u8) !void {
8 _ = args;
7pub fn cmdEnv(arena: Allocator, out: *std.Io.Writer) !void {
98 const cwd_path = try introspect.getResolvedCwd(arena);
109 const self_exe_path = try std.fs.selfExePathAlloc(arena);
1110
......@@ -21,41 +20,21 @@ pub fn cmdEnv(arena: Allocator, args: []const []const u8) !void {
2120 const host = try std.zig.system.resolveTargetQuery(.{});
2221 const triple = try host.zigTriple(arena);
2322
24 var bw = std.io.bufferedWriter(std.fs.File.stdout().deprecatedWriter());
25 const w = bw.writer();
23 var serializer: std.zon.Serializer = .{ .writer = out };
24 var root = try serializer.beginStruct(.{});
2625
27 var jws = std.json.writeStream(w, .{ .whitespace = .indent_1 });
28
29 try jws.beginObject();
30
31 try jws.objectField("zig_exe");
32 try jws.write(self_exe_path);
33
34 try jws.objectField("lib_dir");
35 try jws.write(zig_lib_directory.path.?);
36
37 try jws.objectField("std_dir");
38 try jws.write(zig_std_dir);
39
40 try jws.objectField("global_cache_dir");
41 try jws.write(global_cache_dir);
42
43 try jws.objectField("version");
44 try jws.write(build_options.version);
45
46 try jws.objectField("target");
47 try jws.write(triple);
48
49 try jws.objectField("env");
50 try jws.beginObject();
26 try root.field("zig_exe", self_exe_path, .{});
27 try root.field("lib_dir", zig_lib_directory.path.?, .{});
28 try root.field("std_dir", zig_std_dir, .{});
29 try root.field("global_cache_dir", global_cache_dir, .{});
30 try root.field("version", build_options.version, .{});
31 try root.field("target", triple, .{});
32 var env = try root.beginStructField("env", .{});
5133 inline for (@typeInfo(std.zig.EnvVar).@"enum".fields) |field| {
52 try jws.objectField(field.name);
53 try jws.write(try @field(std.zig.EnvVar, field.name).get(arena));
34 try env.field(field.name, try @field(std.zig.EnvVar, field.name).get(arena), .{});
5435 }
55 try jws.endObject();
56
57 try jws.endObject();
58 try w.writeByte('\n');
36 try env.end();
37 try root.end();
5938
60 try bw.flush();
39 try out.writeByte('\n');
6140}
src/print_targets.zig+4-8
......@@ -14,8 +14,7 @@ const introspect = @import("introspect.zig");
1414pub fn cmdTargets(
1515 allocator: Allocator,
1616 args: []const []const u8,
17 /// Output stream
18 stdout: anytype,
17 out: *std.Io.Writer,
1918 native_target: *const Target,
2019) !void {
2120 _ = args;
......@@ -38,12 +37,10 @@ pub fn cmdTargets(
3837 const glibc_abi = try glibc.loadMetaData(allocator, abilists_contents);
3938 defer glibc_abi.destroy(allocator);
4039
41 var bw = io.bufferedWriter(stdout);
42 const w = bw.writer();
43 var sz = std.zon.stringify.serializer(w, .{});
40 var serializer: std.zon.Serializer = .{ .writer = out };
4441
4542 {
46 var root_obj = try sz.beginStruct(.{});
43 var root_obj = try serializer.beginStruct(.{});
4744
4845 try root_obj.field("arch", meta.fieldNames(Target.Cpu.Arch), .{});
4946 try root_obj.field("os", meta.fieldNames(Target.Os.Tag), .{});
......@@ -136,6 +133,5 @@ pub fn cmdTargets(
136133 try root_obj.end();
137134 }
138135
139 try w.writeByte('\n');
140 return bw.flush();
136 try out.writeByte('\n');
141137}
src/translate_c.zig+1-1
......@@ -3338,7 +3338,7 @@ fn transPredefinedExpr(c: *Context, scope: *Scope, expr: *const clang.Predefined
33383338
33393339fn transCreateCharLitNode(c: *Context, narrow: bool, val: u32) TransError!Node {
33403340 return Tag.char_literal.create(c.arena, if (narrow)
3341 try std.fmt.allocPrint(c.arena, "'{f}'", .{std.zig.fmtChar(&.{@as(u8, @intCast(val))})})
3341 try std.fmt.allocPrint(c.arena, "'{f}'", .{std.zig.fmtChar(@intCast(val))})
33423342 else
33433343 try std.fmt.allocPrint(c.arena, "'\\u{{{x}}}'", .{val}));
33443344}