| author | |
| committer | |
| log | 8924f81d8cd96f5a69a54d87119a748247079a09 |
| tree | 4dcb427921a41d3c75d3f3f4b79937d406c0a8a6 |
| parent | a2d81c547ccdb1130c4f55bb736d82f97902a3dd |
| signature |
16 files changed, 1096 insertions(+), 971 deletions(-)
CMakeLists.txt+2-1| ... | @@ -205,6 +205,7 @@ set(ZIG_STAGE2_SOURCES | ... | @@ -205,6 +205,7 @@ set(ZIG_STAGE2_SOURCES |
| 205 | "${CMAKE_SOURCE_DIR}/lib/std/atomic/queue.zig" | 205 | "${CMAKE_SOURCE_DIR}/lib/std/atomic/queue.zig" |
| 206 | "${CMAKE_SOURCE_DIR}/lib/std/atomic/stack.zig" | 206 | "${CMAKE_SOURCE_DIR}/lib/std/atomic/stack.zig" |
| 207 | "${CMAKE_SOURCE_DIR}/lib/std/base64.zig" | 207 | "${CMAKE_SOURCE_DIR}/lib/std/base64.zig" |
| 208 | "${CMAKE_SOURCE_DIR}/lib/std/BitStack.zig" | ||
| 208 | "${CMAKE_SOURCE_DIR}/lib/std/buf_map.zig" | 209 | "${CMAKE_SOURCE_DIR}/lib/std/buf_map.zig" |
| 209 | "${CMAKE_SOURCE_DIR}/lib/std/Build.zig" | 210 | "${CMAKE_SOURCE_DIR}/lib/std/Build.zig" |
| 210 | "${CMAKE_SOURCE_DIR}/lib/std/Build/Cache.zig" | 211 | "${CMAKE_SOURCE_DIR}/lib/std/Build/Cache.zig" |
| ... | @@ -260,7 +261,7 @@ set(ZIG_STAGE2_SOURCES | ... | @@ -260,7 +261,7 @@ set(ZIG_STAGE2_SOURCES |
| 260 | "${CMAKE_SOURCE_DIR}/lib/std/io/seekable_stream.zig" | 261 | "${CMAKE_SOURCE_DIR}/lib/std/io/seekable_stream.zig" |
| 261 | "${CMAKE_SOURCE_DIR}/lib/std/io/writer.zig" | 262 | "${CMAKE_SOURCE_DIR}/lib/std/io/writer.zig" |
| 262 | "${CMAKE_SOURCE_DIR}/lib/std/json.zig" | 263 | "${CMAKE_SOURCE_DIR}/lib/std/json.zig" |
| 263 | "${CMAKE_SOURCE_DIR}/lib/std/json/write_stream.zig" | 264 | "${CMAKE_SOURCE_DIR}/lib/std/json/stringify.zig" |
| 264 | "${CMAKE_SOURCE_DIR}/lib/std/leb128.zig" | 265 | "${CMAKE_SOURCE_DIR}/lib/std/leb128.zig" |
| 265 | "${CMAKE_SOURCE_DIR}/lib/std/linked_list.zig" | 266 | "${CMAKE_SOURCE_DIR}/lib/std/linked_list.zig" |
| 266 | "${CMAKE_SOURCE_DIR}/lib/std/log.zig" | 267 | "${CMAKE_SOURCE_DIR}/lib/std/log.zig" |
lib/std/BitStack.zig created+86| ... | @@ -0,0 +1,86 @@ | ||
| 1 | //! Effectively a stack of u1 values implemented using ArrayList(u8). | ||
| 2 | |||
| 3 | const BitStack = @This(); | ||
| 4 | |||
| 5 | const std = @import("std"); | ||
| 6 | const Allocator = std.mem.Allocator; | ||
| 7 | const ArrayList = std.ArrayList; | ||
| 8 | |||
| 9 | bytes: std.ArrayList(u8), | ||
| 10 | bit_len: usize = 0, | ||
| 11 | |||
| 12 | pub fn init(allocator: Allocator) @This() { | ||
| 13 | return .{ | ||
| 14 | .bytes = std.ArrayList(u8).init(allocator), | ||
| 15 | }; | ||
| 16 | } | ||
| 17 | |||
| 18 | pub fn deinit(self: *@This()) void { | ||
| 19 | self.bytes.deinit(); | ||
| 20 | self.* = undefined; | ||
| 21 | } | ||
| 22 | |||
| 23 | pub fn ensureTotalCapacity(self: *@This(), bit_capcity: usize) Allocator.Error!void { | ||
| 24 | const byte_capacity = (bit_capcity + 7) >> 3; | ||
| 25 | try self.bytes.ensureTotalCapacity(byte_capacity); | ||
| 26 | } | ||
| 27 | |||
| 28 | pub fn push(self: *@This(), b: u1) Allocator.Error!void { | ||
| 29 | const byte_index = self.bit_len >> 3; | ||
| 30 | if (self.bytes.items.len <= byte_index) { | ||
| 31 | try self.bytes.append(0); | ||
| 32 | } | ||
| 33 | |||
| 34 | pushWithStateAssumeCapacity(self.bytes.items, &self.bit_len, b); | ||
| 35 | } | ||
| 36 | |||
| 37 | pub fn peek(self: *const @This()) u1 { | ||
| 38 | return peekWithState(self.bytes.items, self.bit_len); | ||
| 39 | } | ||
| 40 | |||
| 41 | pub fn pop(self: *@This()) u1 { | ||
| 42 | return popWithState(self.bytes.items, &self.bit_len); | ||
| 43 | } | ||
| 44 | |||
| 45 | /// Standalone function for working with a fixed-size buffer. | ||
| 46 | pub fn pushWithStateAssumeCapacity(buf: []u8, bit_len: *usize, b: u1) void { | ||
| 47 | const byte_index = bit_len.* >> 3; | ||
| 48 | const bit_index = @as(u3, @intCast(bit_len.* & 7)); | ||
| 49 | |||
| 50 | buf[byte_index] &= ~(@as(u8, 1) << bit_index); | ||
| 51 | buf[byte_index] |= @as(u8, b) << bit_index; | ||
| 52 | |||
| 53 | bit_len.* += 1; | ||
| 54 | } | ||
| 55 | |||
| 56 | /// Standalone function for working with a fixed-size buffer. | ||
| 57 | pub fn peekWithState(buf: []const u8, bit_len: usize) u1 { | ||
| 58 | const byte_index = (bit_len - 1) >> 3; | ||
| 59 | const bit_index = @as(u3, @intCast((bit_len - 1) & 7)); | ||
| 60 | return @as(u1, @intCast((buf[byte_index] >> bit_index) & 1)); | ||
| 61 | } | ||
| 62 | |||
| 63 | /// Standalone function for working with a fixed-size buffer. | ||
| 64 | pub fn popWithState(buf: []const u8, bit_len: *usize) u1 { | ||
| 65 | const b = peekWithState(buf, bit_len.*); | ||
| 66 | bit_len.* -= 1; | ||
| 67 | return b; | ||
| 68 | } | ||
| 69 | |||
| 70 | const testing = std.testing; | ||
| 71 | test BitStack { | ||
| 72 | var stack = BitStack.init(testing.allocator); | ||
| 73 | defer stack.deinit(); | ||
| 74 | |||
| 75 | try stack.push(1); | ||
| 76 | try stack.push(0); | ||
| 77 | try stack.push(0); | ||
| 78 | try stack.push(1); | ||
| 79 | |||
| 80 | try testing.expectEqual(@as(u1, 1), stack.peek()); | ||
| 81 | try testing.expectEqual(@as(u1, 1), stack.pop()); | ||
| 82 | try testing.expectEqual(@as(u1, 0), stack.peek()); | ||
| 83 | try testing.expectEqual(@as(u1, 0), stack.pop()); | ||
| 84 | try testing.expectEqual(@as(u1, 0), stack.pop()); | ||
| 85 | try testing.expectEqual(@as(u1, 1), stack.pop()); | ||
| 86 | } | ||
lib/std/json.zig+13-10| ... | @@ -43,14 +43,15 @@ test Value { | ... | @@ -43,14 +43,15 @@ test Value { |
| 43 | test writeStream { | 43 | test writeStream { |
| 44 | var out = ArrayList(u8).init(testing.allocator); | 44 | var out = ArrayList(u8).init(testing.allocator); |
| 45 | defer out.deinit(); | 45 | defer out.deinit(); |
| 46 | var write_stream = writeStream(out.writer(), 99); | 46 | var write_stream = writeStream(out.writer(), .{ .whitespace = .indent_2 }); |
| 47 | defer write_stream.deinit(); | ||
| 47 | try write_stream.beginObject(); | 48 | try write_stream.beginObject(); |
| 48 | try write_stream.objectField("foo"); | 49 | try write_stream.objectField("foo"); |
| 49 | try write_stream.emitNumber(123); | 50 | try write_stream.write(123); |
| 50 | try write_stream.endObject(); | 51 | try write_stream.endObject(); |
| 51 | const expected = | 52 | const expected = |
| 52 | \\{ | 53 | \\{ |
| 53 | \\ "foo": 123 | 54 | \\ "foo": 123 |
| 54 | \\} | 55 | \\} |
| 55 | ; | 56 | ; |
| 56 | try testing.expectEqualSlices(u8, expected, out.items); | 57 | try testing.expectEqualSlices(u8, expected, out.items); |
| ... | @@ -98,13 +99,16 @@ pub const ParseError = @import("json/static.zig").ParseError; | ... | @@ -98,13 +99,16 @@ pub const ParseError = @import("json/static.zig").ParseError; |
| 98 | pub const ParseFromValueError = @import("json/static.zig").ParseFromValueError; | 99 | pub const ParseFromValueError = @import("json/static.zig").ParseFromValueError; |
| 99 | 100 | ||
| 100 | pub const StringifyOptions = @import("json/stringify.zig").StringifyOptions; | 101 | pub const StringifyOptions = @import("json/stringify.zig").StringifyOptions; |
| 101 | pub const encodeJsonString = @import("json/stringify.zig").encodeJsonString; | ||
| 102 | pub const encodeJsonStringChars = @import("json/stringify.zig").encodeJsonStringChars; | ||
| 103 | pub const stringify = @import("json/stringify.zig").stringify; | 102 | pub const stringify = @import("json/stringify.zig").stringify; |
| 103 | pub const stringifyMaxDepth = @import("json/stringify.zig").stringifyMaxDepth; | ||
| 104 | pub const stringifyArbitraryDepth = @import("json/stringify.zig").stringifyArbitraryDepth; | ||
| 104 | pub const stringifyAlloc = @import("json/stringify.zig").stringifyAlloc; | 105 | pub const stringifyAlloc = @import("json/stringify.zig").stringifyAlloc; |
| 105 | 106 | pub const writeStream = @import("json/stringify.zig").writeStream; | |
| 106 | pub const WriteStream = @import("json/write_stream.zig").WriteStream; | 107 | pub const writeStreamMaxDepth = @import("json/stringify.zig").writeStreamMaxDepth; |
| 107 | pub const writeStream = @import("json/write_stream.zig").writeStream; | 108 | pub const writeStreamArbitraryDepth = @import("json/stringify.zig").writeStreamArbitraryDepth; |
| 109 | pub const WriteStream = @import("json/stringify.zig").WriteStream; | ||
| 110 | pub const encodeJsonString = @import("json/stringify.zig").encodeJsonString; | ||
| 111 | pub const encodeJsonStringChars = @import("json/stringify.zig").encodeJsonStringChars; | ||
| 108 | 112 | ||
| 109 | // Deprecations | 113 | // Deprecations |
| 110 | pub const parse = @compileError("Deprecated; use parseFromSlice() or parseFromTokenSource() instead."); | 114 | pub const parse = @compileError("Deprecated; use parseFromSlice() or parseFromTokenSource() instead."); |
| ... | @@ -117,9 +121,8 @@ pub const TokenStream = @compileError("Deprecated; use json.Scanner or json.Read | ... | @@ -117,9 +121,8 @@ pub const TokenStream = @compileError("Deprecated; use json.Scanner or json.Read |
| 117 | test { | 121 | test { |
| 118 | _ = @import("json/test.zig"); | 122 | _ = @import("json/test.zig"); |
| 119 | _ = @import("json/scanner.zig"); | 123 | _ = @import("json/scanner.zig"); |
| 120 | _ = @import("json/write_stream.zig"); | ||
| 121 | _ = @import("json/dynamic.zig"); | 124 | _ = @import("json/dynamic.zig"); |
| 122 | _ = @import("json/hashmap_test.zig"); | 125 | _ = @import("json/hashmap.zig"); |
| 123 | _ = @import("json/static.zig"); | 126 | _ = @import("json/static.zig"); |
| 124 | _ = @import("json/stringify.zig"); | 127 | _ = @import("json/stringify.zig"); |
| 125 | _ = @import("json/JSONTestSuite_test.zig"); | 128 | _ = @import("json/JSONTestSuite_test.zig"); |
lib/std/json/dynamic.zig+12-33| ... | @@ -59,44 +59,23 @@ pub const Value = union(enum) { | ... | @@ -59,44 +59,23 @@ pub const Value = union(enum) { |
| 59 | stringify(self, .{}, stderr) catch return; | 59 | stringify(self, .{}, stderr) catch return; |
| 60 | } | 60 | } |
| 61 | 61 | ||
| 62 | pub fn jsonStringify( | 62 | pub fn jsonStringify(value: @This(), jws: anytype) !void { |
| 63 | value: @This(), | ||
| 64 | options: StringifyOptions, | ||
| 65 | out_stream: anytype, | ||
| 66 | ) @TypeOf(out_stream).Error!void { | ||
| 67 | switch (value) { | 63 | switch (value) { |
| 68 | .null => try stringify(null, options, out_stream), | 64 | .null => try jws.write(null), |
| 69 | .bool => |inner| try stringify(inner, options, out_stream), | 65 | .bool => |inner| try jws.write(inner), |
| 70 | .integer => |inner| try stringify(inner, options, out_stream), | 66 | .integer => |inner| try jws.write(inner), |
| 71 | .float => |inner| try stringify(inner, options, out_stream), | 67 | .float => |inner| try jws.write(inner), |
| 72 | .number_string => |inner| try out_stream.writeAll(inner), | 68 | .number_string => |inner| try jws.writePreformatted(inner), |
| 73 | .string => |inner| try stringify(inner, options, out_stream), | 69 | .string => |inner| try jws.write(inner), |
| 74 | .array => |inner| try stringify(inner.items, options, out_stream), | 70 | .array => |inner| try jws.write(inner.items), |
| 75 | .object => |inner| { | 71 | .object => |inner| { |
| 76 | try out_stream.writeByte('{'); | 72 | try jws.beginObject(); |
| 77 | var field_output = false; | ||
| 78 | var child_options = options; | ||
| 79 | child_options.whitespace.indent_level += 1; | ||
| 80 | var it = inner.iterator(); | 73 | var it = inner.iterator(); |
| 81 | while (it.next()) |entry| { | 74 | while (it.next()) |entry| { |
| 82 | if (!field_output) { | 75 | try jws.objectField(entry.key_ptr.*); |
| 83 | field_output = true; | 76 | try jws.write(entry.value_ptr.*); |
| 84 | } else { | ||
| 85 | try out_stream.writeByte(','); | ||
| 86 | } | ||
| 87 | try child_options.whitespace.outputIndent(out_stream); | ||
| 88 | |||
| 89 | try stringify(entry.key_ptr.*, options, out_stream); | ||
| 90 | try out_stream.writeByte(':'); | ||
| 91 | if (child_options.whitespace.separator) { | ||
| 92 | try out_stream.writeByte(' '); | ||
| 93 | } | ||
| 94 | try stringify(entry.value_ptr.*, child_options, out_stream); | ||
| 95 | } | ||
| 96 | if (field_output) { | ||
| 97 | try options.whitespace.outputIndent(out_stream); | ||
| 98 | } | 77 | } |
| 99 | try out_stream.writeByte('}'); | 78 | try jws.endObject(); |
| 100 | }, | 79 | }, |
| 101 | } | 80 | } |
| 102 | } | 81 | } |
lib/std/json/dynamic_test.zig+53-71| ... | @@ -69,38 +69,34 @@ test "json.parser.dynamic" { | ... | @@ -69,38 +69,34 @@ test "json.parser.dynamic" { |
| 69 | try testing.expect(mem.eql(u8, large_int.number_string, "18446744073709551615")); | 69 | try testing.expect(mem.eql(u8, large_int.number_string, "18446744073709551615")); |
| 70 | } | 70 | } |
| 71 | 71 | ||
| 72 | const writeStream = @import("./write_stream.zig").writeStream; | 72 | const writeStream = @import("./stringify.zig").writeStream; |
| 73 | test "write json then parse it" { | 73 | test "write json then parse it" { |
| 74 | var out_buffer: [1000]u8 = undefined; | 74 | var out_buffer: [1000]u8 = undefined; |
| 75 | 75 | ||
| 76 | var fixed_buffer_stream = std.io.fixedBufferStream(&out_buffer); | 76 | var fixed_buffer_stream = std.io.fixedBufferStream(&out_buffer); |
| 77 | const out_stream = fixed_buffer_stream.writer(); | 77 | const out_stream = fixed_buffer_stream.writer(); |
| 78 | var jw = writeStream(out_stream, 4); | 78 | var jw = writeStream(out_stream, .{}); |
| 79 | defer jw.deinit(); | ||
| 79 | 80 | ||
| 80 | try jw.beginObject(); | 81 | try jw.beginObject(); |
| 81 | 82 | ||
| 82 | try jw.objectField("f"); | 83 | try jw.objectField("f"); |
| 83 | try jw.emitBool(false); | 84 | try jw.write(false); |
| 84 | 85 | ||
| 85 | try jw.objectField("t"); | 86 | try jw.objectField("t"); |
| 86 | try jw.emitBool(true); | 87 | try jw.write(true); |
| 87 | 88 | ||
| 88 | try jw.objectField("int"); | 89 | try jw.objectField("int"); |
| 89 | try jw.emitNumber(1234); | 90 | try jw.write(1234); |
| 90 | 91 | ||
| 91 | try jw.objectField("array"); | 92 | try jw.objectField("array"); |
| 92 | try jw.beginArray(); | 93 | try jw.beginArray(); |
| 93 | 94 | try jw.write(null); | |
| 94 | try jw.arrayElem(); | 95 | try jw.write(12.34); |
| 95 | try jw.emitNull(); | ||
| 96 | |||
| 97 | try jw.arrayElem(); | ||
| 98 | try jw.emitNumber(12.34); | ||
| 99 | |||
| 100 | try jw.endArray(); | 96 | try jw.endArray(); |
| 101 | 97 | ||
| 102 | try jw.objectField("str"); | 98 | try jw.objectField("str"); |
| 103 | try jw.emitString("hello"); | 99 | try jw.write("hello"); |
| 104 | 100 | ||
| 105 | try jw.endObject(); | 101 | try jw.endObject(); |
| 106 | 102 | ||
| ... | @@ -185,64 +181,50 @@ test "escaped characters" { | ... | @@ -185,64 +181,50 @@ test "escaped characters" { |
| 185 | } | 181 | } |
| 186 | 182 | ||
| 187 | test "Value.jsonStringify" { | 183 | test "Value.jsonStringify" { |
| 188 | { | 184 | var vals = [_]Value{ |
| 189 | var buffer: [10]u8 = undefined; | 185 | .{ .integer = 1 }, |
| 190 | var fbs = std.io.fixedBufferStream(&buffer); | 186 | .{ .integer = 2 }, |
| 191 | try @as(Value, .null).jsonStringify(.{}, fbs.writer()); | 187 | .{ .number_string = "3" }, |
| 192 | try testing.expectEqualSlices(u8, fbs.getWritten(), "null"); | 188 | }; |
| 193 | } | 189 | var obj = ObjectMap.init(testing.allocator); |
| 194 | { | 190 | defer obj.deinit(); |
| 195 | var buffer: [10]u8 = undefined; | 191 | try obj.putNoClobber("a", .{ .string = "b" }); |
| 196 | var fbs = std.io.fixedBufferStream(&buffer); | 192 | var array = [_]Value{ |
| 197 | try (Value{ .bool = true }).jsonStringify(.{}, fbs.writer()); | 193 | Value.null, |
| 198 | try testing.expectEqualSlices(u8, fbs.getWritten(), "true"); | 194 | Value{ .bool = true }, |
| 199 | } | 195 | Value{ .integer = 42 }, |
| 200 | { | 196 | Value{ .number_string = "43" }, |
| 201 | var buffer: [10]u8 = undefined; | 197 | Value{ .float = 42 }, |
| 202 | var fbs = std.io.fixedBufferStream(&buffer); | 198 | Value{ .string = "weeee" }, |
| 203 | try (Value{ .integer = 42 }).jsonStringify(.{}, fbs.writer()); | 199 | Value{ .array = Array.fromOwnedSlice(undefined, &vals) }, |
| 204 | try testing.expectEqualSlices(u8, fbs.getWritten(), "42"); | 200 | Value{ .object = obj }, |
| 205 | } | 201 | }; |
| 206 | { | 202 | var buffer: [0x1000]u8 = undefined; |
| 207 | var buffer: [10]u8 = undefined; | 203 | var fbs = std.io.fixedBufferStream(&buffer); |
| 208 | var fbs = std.io.fixedBufferStream(&buffer); | 204 | |
| 209 | try (Value{ .number_string = "43" }).jsonStringify(.{}, fbs.writer()); | 205 | var jw = writeStream(fbs.writer(), .{ .whitespace = .indent_1 }); |
| 210 | try testing.expectEqualSlices(u8, fbs.getWritten(), "43"); | 206 | defer jw.deinit(); |
| 211 | } | 207 | try jw.write(array); |
| 212 | { | 208 | |
| 213 | var buffer: [10]u8 = undefined; | 209 | const expected = |
| 214 | var fbs = std.io.fixedBufferStream(&buffer); | 210 | \\[ |
| 215 | try (Value{ .float = 42 }).jsonStringify(.{}, fbs.writer()); | 211 | \\ null, |
| 216 | try testing.expectEqualSlices(u8, fbs.getWritten(), "4.2e+01"); | 212 | \\ true, |
| 217 | } | 213 | \\ 42, |
| 218 | { | 214 | \\ 43, |
| 219 | var buffer: [10]u8 = undefined; | 215 | \\ 4.2e+01, |
| 220 | var fbs = std.io.fixedBufferStream(&buffer); | 216 | \\ "weeee", |
| 221 | try (Value{ .string = "weeee" }).jsonStringify(.{}, fbs.writer()); | 217 | \\ [ |
| 222 | try testing.expectEqualSlices(u8, fbs.getWritten(), "\"weeee\""); | 218 | \\ 1, |
| 223 | } | 219 | \\ 2, |
| 224 | { | 220 | \\ 3 |
| 225 | var buffer: [10]u8 = undefined; | 221 | \\ ], |
| 226 | var fbs = std.io.fixedBufferStream(&buffer); | 222 | \\ { |
| 227 | var vals = [_]Value{ | 223 | \\ "a": "b" |
| 228 | .{ .integer = 1 }, | 224 | \\ } |
| 229 | .{ .integer = 2 }, | 225 | \\] |
| 230 | .{ .number_string = "3" }, | 226 | ; |
| 231 | }; | 227 | try testing.expectEqualSlices(u8, expected, fbs.getWritten()); |
| 232 | try (Value{ | ||
| 233 | .array = Array.fromOwnedSlice(undefined, &vals), | ||
| 234 | }).jsonStringify(.{}, fbs.writer()); | ||
| 235 | try testing.expectEqualSlices(u8, fbs.getWritten(), "[1,2,3]"); | ||
| 236 | } | ||
| 237 | { | ||
| 238 | var buffer: [10]u8 = undefined; | ||
| 239 | var fbs = std.io.fixedBufferStream(&buffer); | ||
| 240 | var obj = ObjectMap.init(testing.allocator); | ||
| 241 | defer obj.deinit(); | ||
| 242 | try obj.putNoClobber("a", .{ .string = "b" }); | ||
| 243 | try (Value{ .object = obj }).jsonStringify(.{}, fbs.writer()); | ||
| 244 | try testing.expectEqualSlices(u8, fbs.getWritten(), "{\"a\":\"b\"}"); | ||
| 245 | } | ||
| 246 | } | 228 | } |
| 247 | 229 | ||
| 248 | test "parseFromValue(std.json.Value,...)" { | 230 | test "parseFromValue(std.json.Value,...)" { |
lib/std/json/hashmap.zig+5-24| ... | @@ -5,9 +5,6 @@ const ParseOptions = @import("static.zig").ParseOptions; | ... | @@ -5,9 +5,6 @@ const ParseOptions = @import("static.zig").ParseOptions; |
| 5 | const innerParse = @import("static.zig").innerParse; | 5 | const innerParse = @import("static.zig").innerParse; |
| 6 | const innerParseFromValue = @import("static.zig").innerParseFromValue; | 6 | const innerParseFromValue = @import("static.zig").innerParseFromValue; |
| 7 | const Value = @import("dynamic.zig").Value; | 7 | const Value = @import("dynamic.zig").Value; |
| 8 | const StringifyOptions = @import("stringify.zig").StringifyOptions; | ||
| 9 | const stringify = @import("stringify.zig").stringify; | ||
| 10 | const encodeJsonString = @import("stringify.zig").encodeJsonString; | ||
| 11 | 8 | ||
| 12 | /// A thin wrapper around `std.StringArrayHashMapUnmanaged` that implements | 9 | /// A thin wrapper around `std.StringArrayHashMapUnmanaged` that implements |
| 13 | /// `jsonParse`, `jsonParseFromValue`, and `jsonStringify`. | 10 | /// `jsonParse`, `jsonParseFromValue`, and `jsonStringify`. |
| ... | @@ -70,30 +67,14 @@ pub fn ArrayHashMap(comptime T: type) type { | ... | @@ -70,30 +67,14 @@ pub fn ArrayHashMap(comptime T: type) type { |
| 70 | return .{ .map = map }; | 67 | return .{ .map = map }; |
| 71 | } | 68 | } |
| 72 | 69 | ||
| 73 | pub fn jsonStringify(self: @This(), options: StringifyOptions, out_stream: anytype) !void { | 70 | pub fn jsonStringify(self: @This(), jws: anytype) !void { |
| 74 | try out_stream.writeByte('{'); | 71 | try jws.beginObject(); |
| 75 | var field_output = false; | ||
| 76 | var child_options = options; | ||
| 77 | child_options.whitespace.indent_level += 1; | ||
| 78 | var it = self.map.iterator(); | 72 | var it = self.map.iterator(); |
| 79 | while (it.next()) |kv| { | 73 | while (it.next()) |kv| { |
| 80 | if (!field_output) { | 74 | try jws.objectField(kv.key_ptr.*); |
| 81 | field_output = true; | 75 | try jws.write(kv.value_ptr.*); |
| 82 | } else { | ||
| 83 | try out_stream.writeByte(','); | ||
| 84 | } | ||
| 85 | try child_options.whitespace.outputIndent(out_stream); | ||
| 86 | try encodeJsonString(kv.key_ptr.*, options, out_stream); | ||
| 87 | try out_stream.writeByte(':'); | ||
| 88 | if (child_options.whitespace.separator) { | ||
| 89 | try out_stream.writeByte(' '); | ||
| 90 | } | ||
| 91 | try stringify(kv.value_ptr.*, child_options, out_stream); | ||
| 92 | } | ||
| 93 | if (field_output) { | ||
| 94 | try options.whitespace.outputIndent(out_stream); | ||
| 95 | } | 76 | } |
| 96 | try out_stream.writeByte('}'); | 77 | try jws.endObject(); |
| 97 | } | 78 | } |
| 98 | }; | 79 | }; |
| 99 | } | 80 | } |
lib/std/json/hashmap_test.zig+1-5| ... | @@ -101,11 +101,7 @@ test "stringify json hashmap whitespace" { | ... | @@ -101,11 +101,7 @@ test "stringify json hashmap whitespace" { |
| 101 | try value.map.put(testing.allocator, "xyz", .{ .i = 1, .s = "w" }); | 101 | try value.map.put(testing.allocator, "xyz", .{ .i = 1, .s = "w" }); |
| 102 | 102 | ||
| 103 | { | 103 | { |
| 104 | const doc = try stringifyAlloc(testing.allocator, value, .{ | 104 | const doc = try stringifyAlloc(testing.allocator, value, .{ .whitespace = .indent_2 }); |
| 105 | .whitespace = .{ | ||
| 106 | .indent = .{ .space = 2 }, | ||
| 107 | }, | ||
| 108 | }); | ||
| 109 | defer testing.allocator.free(doc); | 105 | defer testing.allocator.free(doc); |
| 110 | try testing.expectEqualStrings( | 106 | try testing.expectEqualStrings( |
| 111 | \\{ | 107 | \\{ |
lib/std/json/scanner.zig+7-53| ... | @@ -33,6 +33,7 @@ const std = @import("std"); | ... | @@ -33,6 +33,7 @@ const std = @import("std"); |
| 33 | const Allocator = std.mem.Allocator; | 33 | const Allocator = std.mem.Allocator; |
| 34 | const ArrayList = std.ArrayList; | 34 | const ArrayList = std.ArrayList; |
| 35 | const assert = std.debug.assert; | 35 | const assert = std.debug.assert; |
| 36 | const BitStack = std.BitStack; | ||
| 36 | 37 | ||
| 37 | /// Scan the input and check for malformed JSON. | 38 | /// Scan the input and check for malformed JSON. |
| 38 | /// On `SyntaxError` or `UnexpectedEndOfInput`, returns `false`. | 39 | /// On `SyntaxError` or `UnexpectedEndOfInput`, returns `false`. |
| ... | @@ -337,7 +338,7 @@ pub fn Reader(comptime buffer_size: usize, comptime ReaderType: type) type { | ... | @@ -337,7 +338,7 @@ pub fn Reader(comptime buffer_size: usize, comptime ReaderType: type) type { |
| 337 | } | 338 | } |
| 338 | } | 339 | } |
| 339 | /// Like `std.json.Scanner.skipUntilStackHeight()` but handles `error.BufferUnderrun`. | 340 | /// Like `std.json.Scanner.skipUntilStackHeight()` but handles `error.BufferUnderrun`. |
| 340 | pub fn skipUntilStackHeight(self: *@This(), terminal_stack_height: u32) NextError!void { | 341 | pub fn skipUntilStackHeight(self: *@This(), terminal_stack_height: usize) NextError!void { |
| 341 | while (true) { | 342 | while (true) { |
| 342 | return self.scanner.skipUntilStackHeight(terminal_stack_height) catch |err| switch (err) { | 343 | return self.scanner.skipUntilStackHeight(terminal_stack_height) catch |err| switch (err) { |
| 343 | error.BufferUnderrun => { | 344 | error.BufferUnderrun => { |
| ... | @@ -350,11 +351,11 @@ pub fn Reader(comptime buffer_size: usize, comptime ReaderType: type) type { | ... | @@ -350,11 +351,11 @@ pub fn Reader(comptime buffer_size: usize, comptime ReaderType: type) type { |
| 350 | } | 351 | } |
| 351 | 352 | ||
| 352 | /// Calls `std.json.Scanner.stackHeight`. | 353 | /// Calls `std.json.Scanner.stackHeight`. |
| 353 | pub fn stackHeight(self: *const @This()) u32 { | 354 | pub fn stackHeight(self: *const @This()) usize { |
| 354 | return self.scanner.stackHeight(); | 355 | return self.scanner.stackHeight(); |
| 355 | } | 356 | } |
| 356 | /// Calls `std.json.Scanner.ensureTotalStackCapacity`. | 357 | /// Calls `std.json.Scanner.ensureTotalStackCapacity`. |
| 357 | pub fn ensureTotalStackCapacity(self: *@This(), height: u32) Allocator.Error!void { | 358 | pub fn ensureTotalStackCapacity(self: *@This(), height: usize) Allocator.Error!void { |
| 358 | try self.scanner.ensureTotalStackCapacity(height); | 359 | try self.scanner.ensureTotalStackCapacity(height); |
| 359 | } | 360 | } |
| 360 | 361 | ||
| ... | @@ -654,7 +655,7 @@ pub const Scanner = struct { | ... | @@ -654,7 +655,7 @@ pub const Scanner = struct { |
| 654 | 655 | ||
| 655 | /// Skip tokens until an `.object_end` or `.array_end` token results in a `stackHeight()` equal the given stack height. | 656 | /// Skip tokens until an `.object_end` or `.array_end` token results in a `stackHeight()` equal the given stack height. |
| 656 | /// Unlike `skipValue()`, this function is available in streaming mode. | 657 | /// Unlike `skipValue()`, this function is available in streaming mode. |
| 657 | pub fn skipUntilStackHeight(self: *@This(), terminal_stack_height: u32) NextError!void { | 658 | pub fn skipUntilStackHeight(self: *@This(), terminal_stack_height: usize) NextError!void { |
| 658 | while (true) { | 659 | while (true) { |
| 659 | switch (try self.next()) { | 660 | switch (try self.next()) { |
| 660 | .object_end, .array_end => { | 661 | .object_end, .array_end => { |
| ... | @@ -667,13 +668,13 @@ pub const Scanner = struct { | ... | @@ -667,13 +668,13 @@ pub const Scanner = struct { |
| 667 | } | 668 | } |
| 668 | 669 | ||
| 669 | /// The depth of `{}` or `[]` nesting levels at the current position. | 670 | /// The depth of `{}` or `[]` nesting levels at the current position. |
| 670 | pub fn stackHeight(self: *const @This()) u32 { | 671 | pub fn stackHeight(self: *const @This()) usize { |
| 671 | return self.stack.bit_len; | 672 | return self.stack.bit_len; |
| 672 | } | 673 | } |
| 673 | 674 | ||
| 674 | /// Pre allocate memory to hold the given number of nesting levels. | 675 | /// Pre allocate memory to hold the given number of nesting levels. |
| 675 | /// `stackHeight()` up to the given number will not cause allocations. | 676 | /// `stackHeight()` up to the given number will not cause allocations. |
| 676 | pub fn ensureTotalStackCapacity(self: *@This(), height: u32) Allocator.Error!void { | 677 | pub fn ensureTotalStackCapacity(self: *@This(), height: usize) Allocator.Error!void { |
| 677 | try self.stack.ensureTotalCapacity(height); | 678 | try self.stack.ensureTotalCapacity(height); |
| 678 | } | 679 | } |
| 679 | 680 | ||
| ... | @@ -1697,53 +1698,6 @@ pub const Scanner = struct { | ... | @@ -1697,53 +1698,6 @@ pub const Scanner = struct { |
| 1697 | const OBJECT_MODE = 0; | 1698 | const OBJECT_MODE = 0; |
| 1698 | const ARRAY_MODE = 1; | 1699 | const ARRAY_MODE = 1; |
| 1699 | 1700 | ||
| 1700 | const BitStack = struct { | ||
| 1701 | bytes: std.ArrayList(u8), | ||
| 1702 | bit_len: u32 = 0, | ||
| 1703 | |||
| 1704 | pub fn init(allocator: Allocator) @This() { | ||
| 1705 | return .{ | ||
| 1706 | .bytes = std.ArrayList(u8).init(allocator), | ||
| 1707 | }; | ||
| 1708 | } | ||
| 1709 | |||
| 1710 | pub fn deinit(self: *@This()) void { | ||
| 1711 | self.bytes.deinit(); | ||
| 1712 | self.* = undefined; | ||
| 1713 | } | ||
| 1714 | |||
| 1715 | pub fn ensureTotalCapacity(self: *@This(), bit_capcity: u32) Allocator.Error!void { | ||
| 1716 | const byte_capacity = (bit_capcity + 7) >> 3; | ||
| 1717 | try self.bytes.ensureTotalCapacity(byte_capacity); | ||
| 1718 | } | ||
| 1719 | |||
| 1720 | pub fn push(self: *@This(), b: u1) Allocator.Error!void { | ||
| 1721 | const byte_index = self.bit_len >> 3; | ||
| 1722 | const bit_index = @as(u3, @intCast(self.bit_len & 7)); | ||
| 1723 | |||
| 1724 | if (self.bytes.items.len <= byte_index) { | ||
| 1725 | try self.bytes.append(0); | ||
| 1726 | } | ||
| 1727 | |||
| 1728 | self.bytes.items[byte_index] &= ~(@as(u8, 1) << bit_index); | ||
| 1729 | self.bytes.items[byte_index] |= @as(u8, b) << bit_index; | ||
| 1730 | |||
| 1731 | self.bit_len += 1; | ||
| 1732 | } | ||
| 1733 | |||
| 1734 | pub fn peek(self: *const @This()) u1 { | ||
| 1735 | const byte_index = (self.bit_len - 1) >> 3; | ||
| 1736 | const bit_index = @as(u3, @intCast((self.bit_len - 1) & 7)); | ||
| 1737 | return @as(u1, @intCast((self.bytes.items[byte_index] >> bit_index) & 1)); | ||
| 1738 | } | ||
| 1739 | |||
| 1740 | pub fn pop(self: *@This()) u1 { | ||
| 1741 | const b = self.peek(); | ||
| 1742 | self.bit_len -= 1; | ||
| 1743 | return b; | ||
| 1744 | } | ||
| 1745 | }; | ||
| 1746 | |||
| 1747 | fn appendSlice(list: *std.ArrayList(u8), buf: []const u8, max_value_len: usize) !void { | 1701 | fn appendSlice(list: *std.ArrayList(u8), buf: []const u8, max_value_len: usize) !void { |
| 1748 | const new_len = std.math.add(usize, list.items.len, buf.len) catch return error.ValueTooLong; | 1702 | const new_len = std.math.add(usize, list.items.len, buf.len) catch return error.ValueTooLong; |
| 1749 | if (new_len > max_value_len) return error.ValueTooLong; | 1703 | if (new_len > max_value_len) return error.ValueTooLong; |
lib/std/json/stringify.zig+606-257| ... | @@ -1,73 +1,583 @@ | ... | @@ -1,73 +1,583 @@ |
| 1 | const std = @import("std"); | 1 | const std = @import("std"); |
| 2 | const mem = std.mem; | ||
| 3 | const assert = std.debug.assert; | 2 | const assert = std.debug.assert; |
| 3 | const Allocator = std.mem.Allocator; | ||
| 4 | const ArrayList = std.ArrayList; | ||
| 5 | const BitStack = std.BitStack; | ||
| 6 | |||
| 7 | const OBJECT_MODE = 0; | ||
| 8 | const ARRAY_MODE = 1; | ||
| 4 | 9 | ||
| 5 | pub const StringifyOptions = struct { | 10 | pub const StringifyOptions = struct { |
| 6 | pub const Whitespace = struct { | 11 | /// Controls the whitespace emitted. |
| 7 | /// How many indentation levels deep are we? | 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 | |||
| 38 | /// Writes the given value to the `std.io.Writer` stream. | ||
| 39 | /// See `WriteStream` for how the given value is serialized into JSON. | ||
| 40 | /// The maximum nesting depth of the output JSON document is 256. | ||
| 41 | /// See also `stringifyMaxDepth` and `stringifyArbitraryDepth`. | ||
| 42 | pub fn stringify( | ||
| 43 | value: anytype, | ||
| 44 | options: StringifyOptions, | ||
| 45 | out_stream: anytype, | ||
| 46 | ) @TypeOf(out_stream).Error!void { | ||
| 47 | var jw = writeStream(out_stream, options); | ||
| 48 | defer jw.deinit(); | ||
| 49 | try jw.write(value); | ||
| 50 | } | ||
| 51 | |||
| 52 | /// Like `stringify` with configurable nesting depth. | ||
| 53 | /// `max_depth` is rounded up to the nearest multiple of 8. | ||
| 54 | /// Give `null` for `max_depth` to disable some safety checks and allow arbitrary nesting depth. | ||
| 55 | /// See `writeStreamMaxDepth` for more info. | ||
| 56 | pub fn stringifyMaxDepth( | ||
| 57 | value: anytype, | ||
| 58 | options: StringifyOptions, | ||
| 59 | out_stream: anytype, | ||
| 60 | comptime max_depth: ?usize, | ||
| 61 | ) @TypeOf(out_stream).Error!void { | ||
| 62 | var jw = writeStreamMaxDepth(out_stream, options, max_depth); | ||
| 63 | try jw.write(value); | ||
| 64 | } | ||
| 65 | |||
| 66 | /// Like `stringify` but takes an allocator to facilitate safety checks while allowing arbitrary nesting depth. | ||
| 67 | /// These safety checks can be helpful when debugging custom `jsonStringify` implementations; | ||
| 68 | /// See `WriteStream`. | ||
| 69 | pub fn stringifyArbitraryDepth( | ||
| 70 | allocator: Allocator, | ||
| 71 | value: anytype, | ||
| 72 | options: StringifyOptions, | ||
| 73 | out_stream: anytype, | ||
| 74 | ) WriteStream(@TypeOf(out_stream), .checked_to_arbitrary_depth).Error!void { | ||
| 75 | var jw = writeStreamArbitraryDepth(allocator, out_stream, options); | ||
| 76 | defer jw.deinit(); | ||
| 77 | try jw.write(value); | ||
| 78 | } | ||
| 79 | |||
| 80 | /// Calls `stringifyArbitraryDepth` and stores the result in dynamically allocated memory | ||
| 81 | /// instead of taking a `std.io.Writer`. | ||
| 82 | /// | ||
| 83 | /// Caller owns returned memory. | ||
| 84 | pub fn stringifyAlloc( | ||
| 85 | allocator: Allocator, | ||
| 86 | value: anytype, | ||
| 87 | options: StringifyOptions, | ||
| 88 | ) error{OutOfMemory}![]const u8 { | ||
| 89 | var list = std.ArrayList(u8).init(allocator); | ||
| 90 | errdefer list.deinit(); | ||
| 91 | try stringifyArbitraryDepth(allocator, value, options, list.writer()); | ||
| 92 | return list.toOwnedSlice(); | ||
| 93 | } | ||
| 94 | |||
| 95 | /// See `WriteStream` for documentation. | ||
| 96 | /// Equivalent to calling `writeStreamMaxDepth` with a depth of `256`. | ||
| 97 | /// | ||
| 98 | /// The caller does *not* need to call `deinit()` on the returned object. | ||
| 99 | pub fn writeStream( | ||
| 100 | out_stream: anytype, | ||
| 101 | options: StringifyOptions, | ||
| 102 | ) WriteStream(@TypeOf(out_stream), .{ .checked_to_fixed_depth = 256 }) { | ||
| 103 | return writeStreamMaxDepth(out_stream, options, 256); | ||
| 104 | } | ||
| 105 | |||
| 106 | /// See `WriteStream` for documentation. | ||
| 107 | /// The returned object includes 1 bit of size per `max_depth` to enable safety checks on the order of method calls; | ||
| 108 | /// see the grammar in the `WriteStream` documentation. | ||
| 109 | /// `max_depth` is rounded up to the nearest multiple of 8. | ||
| 110 | /// If the nesting depth exceeds `max_depth`, it is detectable illegal behavior. | ||
| 111 | /// Give `null` for `max_depth` to disable safety checks for the grammar and allow arbitrary nesting depth. | ||
| 112 | /// Alternatively, see `writeStreamArbitraryDepth` to do safety checks to arbitrary depth. | ||
| 113 | /// | ||
| 114 | /// The caller does *not* need to call `deinit()` on the returned object. | ||
| 115 | pub fn writeStreamMaxDepth( | ||
| 116 | out_stream: anytype, | ||
| 117 | options: StringifyOptions, | ||
| 118 | comptime max_depth: ?usize, | ||
| 119 | ) WriteStream( | ||
| 120 | @TypeOf(out_stream), | ||
| 121 | if (max_depth) |d| .{ .checked_to_fixed_depth = d } else .assumed_correct, | ||
| 122 | ) { | ||
| 123 | return WriteStream( | ||
| 124 | @TypeOf(out_stream), | ||
| 125 | if (max_depth) |d| .{ .checked_to_fixed_depth = d } else .assumed_correct, | ||
| 126 | ).init(undefined, out_stream, options); | ||
| 127 | } | ||
| 128 | |||
| 129 | /// See `WriteStream` for documentation. | ||
| 130 | /// This version of the write stream enables safety checks to arbitrarily deep nesting levels | ||
| 131 | /// by using the given allocator. | ||
| 132 | /// The caller should call `deinit()` on the returned object to free allocated memory. | ||
| 133 | pub fn writeStreamArbitraryDepth( | ||
| 134 | allocator: Allocator, | ||
| 135 | out_stream: anytype, | ||
| 136 | options: StringifyOptions, | ||
| 137 | ) WriteStream(@TypeOf(out_stream), .checked_to_arbitrary_depth) { | ||
| 138 | return WriteStream(@TypeOf(out_stream), .checked_to_arbitrary_depth).init(allocator, out_stream, options); | ||
| 139 | } | ||
| 140 | |||
| 141 | /// Writes JSON ([RFC8259](https://tools.ietf.org/html/rfc8259)) formatted data | ||
| 142 | /// to a stream. | ||
| 143 | /// | ||
| 144 | /// The seqeunce of method calls to write JSON content must follow this grammar: | ||
| 145 | /// ``` | ||
| 146 | /// <once> = <value> | ||
| 147 | /// <value> = | ||
| 148 | /// | <object> | ||
| 149 | /// | <array> | ||
| 150 | /// | write | ||
| 151 | /// | writePreformatted | ||
| 152 | /// <object> = beginObject ( objectField <value> )* endObject | ||
| 153 | /// <array> = beginArray ( <value> )* endArray | ||
| 154 | /// ``` | ||
| 155 | /// | ||
| 156 | /// Supported types: | ||
| 157 | /// * Zig `bool` -> JSON `true` or `false`. | ||
| 158 | /// * Zig `?T` -> `null` or the rendering of `T`. | ||
| 159 | /// * Zig `i32`, `u64`, etc. -> JSON number or string. | ||
| 160 | /// * If the value is outside the range `±1<<53` (the precise integer rage of f64), it is rendered as a JSON string in base 10. Otherwise, it is rendered as JSON number. | ||
| 161 | /// * Zig floats -> JSON number or string. | ||
| 162 | /// * If the value cannot be precisely represented by an f64, it is rendered as a JSON string. Otherwise, it is rendered as JSON number. | ||
| 163 | /// * TODO: Float rendering will likely change in the future, e.g. to remove the unnecessary "e+00". | ||
| 164 | /// * Zig `[]const u8`, `[]u8`, `*[N]u8`, `@Vector(N, u8)`, and similar -> JSON string. | ||
| 165 | /// * See `StringifyOptions.emit_strings_as_arrays`. | ||
| 166 | /// * If the content is not valid UTF-8, rendered as an array of numbers instead. | ||
| 167 | /// * Zig `[]T`, `[N]T`, `*[N]T`, `@Vector(N, T)`, and similar -> JSON array of the rendering of each item. | ||
| 168 | /// * Zig tuple -> JSON array of the rendering of each item. | ||
| 169 | /// * Zig `struct` -> JSON object with each field in declaration order. | ||
| 170 | /// * 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. | ||
| 171 | /// * See `StringifyOptions.emit_null_optional_fields`. | ||
| 172 | /// * Zig `union(enum)` -> JSON object with one field named for the active tag and a value representing the payload. | ||
| 173 | /// * If the payload is `void`, then the emitted value is `{}`. | ||
| 174 | /// * 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`. | ||
| 175 | /// * Zig `enum` -> JSON string naming the active tag. | ||
| 176 | /// * 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`. | ||
| 177 | /// * Zig error -> JSON string naming the error. | ||
| 178 | /// * Zig `*T` -> the rendering of `T`. Note there is no guard against circular-reference infinite recursion. | ||
| 179 | pub fn WriteStream( | ||
| 180 | comptime OutStream: type, | ||
| 181 | comptime safety_checks: union(enum) { | ||
| 182 | checked_to_arbitrary_depth, | ||
| 183 | checked_to_fixed_depth: usize, // Rounded up to the nearest multiple of 8. | ||
| 184 | assumed_correct, | ||
| 185 | }, | ||
| 186 | ) type { | ||
| 187 | return struct { | ||
| 188 | const Self = @This(); | ||
| 189 | |||
| 190 | pub const Stream = OutStream; | ||
| 191 | pub const Error = switch (safety_checks) { | ||
| 192 | .checked_to_arbitrary_depth => Stream.Error || error{OutOfMemory}, | ||
| 193 | .checked_to_fixed_depth, .assumed_correct => Stream.Error, | ||
| 194 | }; | ||
| 195 | |||
| 196 | options: StringifyOptions, | ||
| 197 | |||
| 198 | stream: OutStream, | ||
| 8 | indent_level: usize = 0, | 199 | indent_level: usize = 0, |
| 200 | next_punctuation: enum { | ||
| 201 | the_beginning, | ||
| 202 | none, | ||
| 203 | comma, | ||
| 204 | colon, | ||
| 205 | } = .the_beginning, | ||
| 206 | |||
| 207 | nesting_stack: switch (safety_checks) { | ||
| 208 | .checked_to_arbitrary_depth => BitStack, | ||
| 209 | .checked_to_fixed_depth => |fixed_buffer_size| [(fixed_buffer_size + 7) >> 3]u8, | ||
| 210 | .assumed_correct => void, | ||
| 211 | }, | ||
| 9 | 212 | ||
| 10 | /// What character(s) should be used for indentation? | 213 | pub fn init(safety_allocator: Allocator, stream: OutStream, options: StringifyOptions) Self { |
| 11 | indent: union(enum) { | 214 | return .{ |
| 12 | space: u8, | 215 | .options = options, |
| 13 | tab: void, | 216 | .stream = stream, |
| 14 | none: void, | 217 | .nesting_stack = switch (safety_checks) { |
| 15 | } = .{ .space = 4 }, | 218 | .checked_to_arbitrary_depth => BitStack.init(safety_allocator), |
| 16 | 219 | .checked_to_fixed_depth => |fixed_buffer_size| [_]u8{0} ** ((fixed_buffer_size + 7) >> 3), | |
| 17 | /// After a colon, should whitespace be inserted? | 220 | .assumed_correct => {}, |
| 18 | separator: bool = true, | ||
| 19 | |||
| 20 | pub fn outputIndent( | ||
| 21 | whitespace: @This(), | ||
| 22 | out_stream: anytype, | ||
| 23 | ) @TypeOf(out_stream).Error!void { | ||
| 24 | var char: u8 = undefined; | ||
| 25 | var n_chars: usize = undefined; | ||
| 26 | switch (whitespace.indent) { | ||
| 27 | .space => |n_spaces| { | ||
| 28 | char = ' '; | ||
| 29 | n_chars = n_spaces; | ||
| 30 | }, | 221 | }, |
| 31 | .tab => { | 222 | }; |
| 223 | } | ||
| 224 | |||
| 225 | pub fn deinit(self: *Self) void { | ||
| 226 | switch (safety_checks) { | ||
| 227 | .checked_to_arbitrary_depth => self.nesting_stack.deinit(), | ||
| 228 | .checked_to_fixed_depth, .assumed_correct => {}, | ||
| 229 | } | ||
| 230 | self.* = undefined; | ||
| 231 | } | ||
| 232 | |||
| 233 | pub fn beginArray(self: *Self) Error!void { | ||
| 234 | try self.valueStart(); | ||
| 235 | try self.stream.writeByte('['); | ||
| 236 | try self.pushIndentation(ARRAY_MODE); | ||
| 237 | self.next_punctuation = .none; | ||
| 238 | } | ||
| 239 | |||
| 240 | pub fn beginObject(self: *Self) Error!void { | ||
| 241 | try self.valueStart(); | ||
| 242 | try self.stream.writeByte('{'); | ||
| 243 | try self.pushIndentation(OBJECT_MODE); | ||
| 244 | self.next_punctuation = .none; | ||
| 245 | } | ||
| 246 | |||
| 247 | pub fn endArray(self: *Self) Error!void { | ||
| 248 | self.popIndentation(ARRAY_MODE); | ||
| 249 | switch (self.next_punctuation) { | ||
| 250 | .none => {}, | ||
| 251 | .comma => { | ||
| 252 | try self.indent(); | ||
| 253 | }, | ||
| 254 | .the_beginning, .colon => unreachable, | ||
| 255 | } | ||
| 256 | try self.stream.writeByte(']'); | ||
| 257 | self.valueDone(); | ||
| 258 | } | ||
| 259 | |||
| 260 | pub fn endObject(self: *Self) Error!void { | ||
| 261 | self.popIndentation(OBJECT_MODE); | ||
| 262 | switch (self.next_punctuation) { | ||
| 263 | .none => {}, | ||
| 264 | .comma => { | ||
| 265 | try self.indent(); | ||
| 266 | }, | ||
| 267 | .the_beginning, .colon => unreachable, | ||
| 268 | } | ||
| 269 | try self.stream.writeByte('}'); | ||
| 270 | self.valueDone(); | ||
| 271 | } | ||
| 272 | |||
| 273 | fn pushIndentation(self: *Self, mode: u1) !void { | ||
| 274 | switch (safety_checks) { | ||
| 275 | .checked_to_arbitrary_depth => { | ||
| 276 | try self.nesting_stack.push(mode); | ||
| 277 | self.indent_level += 1; | ||
| 278 | }, | ||
| 279 | .checked_to_fixed_depth => { | ||
| 280 | BitStack.pushWithStateAssumeCapacity(&self.nesting_stack, &self.indent_level, mode); | ||
| 281 | }, | ||
| 282 | .assumed_correct => { | ||
| 283 | self.indent_level += 1; | ||
| 284 | }, | ||
| 285 | } | ||
| 286 | } | ||
| 287 | fn popIndentation(self: *Self, assert_its_this_one: u1) void { | ||
| 288 | switch (safety_checks) { | ||
| 289 | .checked_to_arbitrary_depth => { | ||
| 290 | assert(self.nesting_stack.pop() == assert_its_this_one); | ||
| 291 | self.indent_level -= 1; | ||
| 292 | }, | ||
| 293 | .checked_to_fixed_depth => { | ||
| 294 | assert(BitStack.popWithState(&self.nesting_stack, &self.indent_level) == assert_its_this_one); | ||
| 295 | }, | ||
| 296 | .assumed_correct => { | ||
| 297 | self.indent_level -= 1; | ||
| 298 | }, | ||
| 299 | } | ||
| 300 | } | ||
| 301 | |||
| 302 | fn indent(self: *Self) !void { | ||
| 303 | var char: u8 = ' '; | ||
| 304 | const n_chars = switch (self.options.whitespace) { | ||
| 305 | .minified => return, | ||
| 306 | .indent_1 => 1 * self.indent_level, | ||
| 307 | .indent_2 => 2 * self.indent_level, | ||
| 308 | .indent_3 => 3 * self.indent_level, | ||
| 309 | .indent_4 => 4 * self.indent_level, | ||
| 310 | .indent_8 => 8 * self.indent_level, | ||
| 311 | .indent_tab => blk: { | ||
| 32 | char = '\t'; | 312 | char = '\t'; |
| 33 | n_chars = 1; | 313 | break :blk self.indent_level; |
| 314 | }, | ||
| 315 | }; | ||
| 316 | try self.stream.writeByte('\n'); | ||
| 317 | try self.stream.writeByteNTimes(char, n_chars); | ||
| 318 | } | ||
| 319 | |||
| 320 | fn valueStart(self: *Self) !void { | ||
| 321 | if (self.isObjectKeyExpected()) |is_it| assert(!is_it); // Call objectField(), not write(), for object keys. | ||
| 322 | return self.valueStartAssumeTypeOk(); | ||
| 323 | } | ||
| 324 | fn objectFieldStart(self: *Self) !void { | ||
| 325 | if (self.isObjectKeyExpected()) |is_it| assert(is_it); // Expected write(), not objectField(). | ||
| 326 | return self.valueStartAssumeTypeOk(); | ||
| 327 | } | ||
| 328 | fn valueStartAssumeTypeOk(self: *Self) !void { | ||
| 329 | assert(!self.isComplete()); // JSON document already complete. | ||
| 330 | switch (self.next_punctuation) { | ||
| 331 | .the_beginning => { | ||
| 332 | // No indentation for the very beginning. | ||
| 333 | }, | ||
| 334 | .none => { | ||
| 335 | // First item in a container. | ||
| 336 | try self.indent(); | ||
| 337 | }, | ||
| 338 | .comma => { | ||
| 339 | // Subsequent item in a container. | ||
| 340 | try self.stream.writeByte(','); | ||
| 341 | try self.indent(); | ||
| 342 | }, | ||
| 343 | .colon => { | ||
| 344 | try self.stream.writeByte(':'); | ||
| 345 | if (self.options.whitespace != .minified) { | ||
| 346 | try self.stream.writeByte(' '); | ||
| 347 | } | ||
| 34 | }, | 348 | }, |
| 35 | .none => return, | ||
| 36 | } | 349 | } |
| 37 | try out_stream.writeByte('\n'); | ||
| 38 | n_chars *= whitespace.indent_level; | ||
| 39 | try out_stream.writeByteNTimes(char, n_chars); | ||
| 40 | } | 350 | } |
| 41 | }; | 351 | fn valueDone(self: *Self) void { |
| 352 | self.next_punctuation = .comma; | ||
| 353 | } | ||
| 42 | 354 | ||
| 43 | /// Controls the whitespace emitted | 355 | // Only when safety is enabled: |
| 44 | whitespace: Whitespace = .{ .indent = .none, .separator = false }, | 356 | fn isObjectKeyExpected(self: *const Self) ?bool { |
| 357 | switch (safety_checks) { | ||
| 358 | .checked_to_arbitrary_depth => return self.indent_level > 0 and | ||
| 359 | self.nesting_stack.peek() == OBJECT_MODE and | ||
| 360 | self.next_punctuation != .colon, | ||
| 361 | .checked_to_fixed_depth => return self.indent_level > 0 and | ||
| 362 | BitStack.peekWithState(&self.nesting_stack, self.indent_level) == OBJECT_MODE and | ||
| 363 | self.next_punctuation != .colon, | ||
| 364 | .assumed_correct => return null, | ||
| 365 | } | ||
| 366 | } | ||
| 367 | fn isComplete(self: *const Self) bool { | ||
| 368 | return self.indent_level == 0 and self.next_punctuation == .comma; | ||
| 369 | } | ||
| 45 | 370 | ||
| 46 | /// Should optional fields with null value be written? | 371 | /// An alternative to calling `write` that outputs the given bytes verbatim. |
| 47 | emit_null_optional_fields: bool = true, | 372 | /// This function does the usual punctuation and indentation formatting |
| 373 | /// assuming the given slice represents a single complete value; | ||
| 374 | /// e.g. `"1"`, `"[]"`, `"[1,2]"`, not `"1,2"`. | ||
| 375 | pub fn writePreformatted(self: *Self, value_slice: []const u8) Error!void { | ||
| 376 | try self.valueStart(); | ||
| 377 | try self.stream.writeAll(value_slice); | ||
| 378 | self.valueDone(); | ||
| 379 | } | ||
| 48 | 380 | ||
| 49 | string: StringOptions = StringOptions{ .String = .{} }, | 381 | pub fn objectField(self: *Self, key: []const u8) Error!void { |
| 382 | try self.objectFieldStart(); | ||
| 383 | try encodeJsonString(key, self.options, self.stream); | ||
| 384 | self.next_punctuation = .colon; | ||
| 385 | } | ||
| 50 | 386 | ||
| 51 | /// Should []u8 be serialised as a string? or an array? | 387 | /// See `WriteStream`. |
| 52 | pub const StringOptions = union(enum) { | 388 | pub fn write(self: *Self, value: anytype) Error!void { |
| 53 | Array, | 389 | const T = @TypeOf(value); |
| 54 | String: StringOutputOptions, | 390 | switch (@typeInfo(T)) { |
| 391 | .Int => |info| { | ||
| 392 | if (info.bits < 53) { | ||
| 393 | try self.valueStart(); | ||
| 394 | try self.stream.print("{}", .{value}); | ||
| 395 | self.valueDone(); | ||
| 396 | return; | ||
| 397 | } | ||
| 398 | if (value < 4503599627370496 and (info.signedness == .unsigned or value > -4503599627370496)) { | ||
| 399 | try self.valueStart(); | ||
| 400 | try self.stream.print("{}", .{value}); | ||
| 401 | self.valueDone(); | ||
| 402 | return; | ||
| 403 | } | ||
| 404 | try self.valueStart(); | ||
| 405 | try self.stream.print("\"{}\"", .{value}); | ||
| 406 | self.valueDone(); | ||
| 407 | return; | ||
| 408 | }, | ||
| 409 | .ComptimeInt => { | ||
| 410 | return self.write(@as(std.math.IntFittingRange(value, value), value)); | ||
| 411 | }, | ||
| 412 | .Float, .ComptimeFloat => { | ||
| 413 | if (@as(f64, @floatCast(value)) == value) { | ||
| 414 | try self.valueStart(); | ||
| 415 | try self.stream.print("{}", .{@as(f64, @floatCast(value))}); | ||
| 416 | self.valueDone(); | ||
| 417 | return; | ||
| 418 | } | ||
| 419 | try self.valueStart(); | ||
| 420 | try self.stream.print("\"{}\"", .{value}); | ||
| 421 | self.valueDone(); | ||
| 422 | return; | ||
| 423 | }, | ||
| 55 | 424 | ||
| 56 | /// String output options | 425 | .Bool => { |
| 57 | const StringOutputOptions = struct { | 426 | try self.valueStart(); |
| 58 | /// Should '/' be escaped in strings? | 427 | try self.stream.writeAll(if (value) "true" else "false"); |
| 59 | escape_solidus: bool = false, | 428 | self.valueDone(); |
| 429 | return; | ||
| 430 | }, | ||
| 431 | .Null => { | ||
| 432 | try self.valueStart(); | ||
| 433 | try self.stream.writeAll("null"); | ||
| 434 | self.valueDone(); | ||
| 435 | return; | ||
| 436 | }, | ||
| 437 | .Optional => { | ||
| 438 | if (value) |payload| { | ||
| 439 | return try self.write(payload); | ||
| 440 | } else { | ||
| 441 | return try self.write(null); | ||
| 442 | } | ||
| 443 | }, | ||
| 444 | .Enum => { | ||
| 445 | if (comptime std.meta.trait.hasFn("jsonStringify")(T)) { | ||
| 446 | return value.jsonStringify(self); | ||
| 447 | } | ||
| 60 | 448 | ||
| 61 | /// Should unicode characters be escaped in strings? | 449 | return self.stringValue(@tagName(value)); |
| 62 | escape_unicode: bool = false, | 450 | }, |
| 63 | }; | 451 | .Union => { |
| 452 | if (comptime std.meta.trait.hasFn("jsonStringify")(T)) { | ||
| 453 | return value.jsonStringify(self); | ||
| 454 | } | ||
| 455 | |||
| 456 | const info = @typeInfo(T).Union; | ||
| 457 | if (info.tag_type) |UnionTagType| { | ||
| 458 | try self.beginObject(); | ||
| 459 | inline for (info.fields) |u_field| { | ||
| 460 | if (value == @field(UnionTagType, u_field.name)) { | ||
| 461 | try self.objectField(u_field.name); | ||
| 462 | if (u_field.type == void) { | ||
| 463 | // void value is {} | ||
| 464 | try self.beginObject(); | ||
| 465 | try self.endObject(); | ||
| 466 | } else { | ||
| 467 | try self.write(@field(value, u_field.name)); | ||
| 468 | } | ||
| 469 | break; | ||
| 470 | } | ||
| 471 | } else { | ||
| 472 | unreachable; // No active tag? | ||
| 473 | } | ||
| 474 | try self.endObject(); | ||
| 475 | return; | ||
| 476 | } else { | ||
| 477 | @compileError("Unable to stringify untagged union '" ++ @typeName(T) ++ "'"); | ||
| 478 | } | ||
| 479 | }, | ||
| 480 | .Struct => |S| { | ||
| 481 | if (comptime std.meta.trait.hasFn("jsonStringify")(T)) { | ||
| 482 | return value.jsonStringify(self); | ||
| 483 | } | ||
| 484 | |||
| 485 | if (S.is_tuple) { | ||
| 486 | try self.beginArray(); | ||
| 487 | } else { | ||
| 488 | try self.beginObject(); | ||
| 489 | } | ||
| 490 | inline for (S.fields) |Field| { | ||
| 491 | // don't include void fields | ||
| 492 | if (Field.type == void) continue; | ||
| 493 | |||
| 494 | var emit_field = true; | ||
| 495 | |||
| 496 | // don't include optional fields that are null when emit_null_optional_fields is set to false | ||
| 497 | if (@typeInfo(Field.type) == .Optional) { | ||
| 498 | if (self.options.emit_null_optional_fields == false) { | ||
| 499 | if (@field(value, Field.name) == null) { | ||
| 500 | emit_field = false; | ||
| 501 | } | ||
| 502 | } | ||
| 503 | } | ||
| 504 | |||
| 505 | if (emit_field) { | ||
| 506 | if (!S.is_tuple) { | ||
| 507 | try self.objectField(Field.name); | ||
| 508 | } | ||
| 509 | try self.write(@field(value, Field.name)); | ||
| 510 | } | ||
| 511 | } | ||
| 512 | if (S.is_tuple) { | ||
| 513 | try self.endArray(); | ||
| 514 | } else { | ||
| 515 | try self.endObject(); | ||
| 516 | } | ||
| 517 | return; | ||
| 518 | }, | ||
| 519 | .ErrorSet => return self.stringValue(@errorName(value)), | ||
| 520 | .Pointer => |ptr_info| switch (ptr_info.size) { | ||
| 521 | .One => switch (@typeInfo(ptr_info.child)) { | ||
| 522 | .Array => { | ||
| 523 | // Coerce `*[N]T` to `[]const T`. | ||
| 524 | const Slice = []const std.meta.Elem(ptr_info.child); | ||
| 525 | return self.write(@as(Slice, value)); | ||
| 526 | }, | ||
| 527 | else => { | ||
| 528 | return self.write(value.*); | ||
| 529 | }, | ||
| 530 | }, | ||
| 531 | .Many, .Slice => { | ||
| 532 | if (ptr_info.size == .Many and ptr_info.sentinel == null) | ||
| 533 | @compileError("unable to stringify type '" ++ @typeName(T) ++ "' without sentinel"); | ||
| 534 | const slice = if (ptr_info.size == .Many) std.mem.span(value) else value; | ||
| 535 | |||
| 536 | if (ptr_info.child == u8) { | ||
| 537 | // This is a []const u8, or some similar Zig string. | ||
| 538 | if (!self.options.emit_strings_as_arrays and std.unicode.utf8ValidateSlice(slice)) { | ||
| 539 | return self.stringValue(slice); | ||
| 540 | } | ||
| 541 | } | ||
| 542 | |||
| 543 | try self.beginArray(); | ||
| 544 | for (slice) |x| { | ||
| 545 | try self.write(x); | ||
| 546 | } | ||
| 547 | try self.endArray(); | ||
| 548 | return; | ||
| 549 | }, | ||
| 550 | else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"), | ||
| 551 | }, | ||
| 552 | .Array => { | ||
| 553 | // Coerce `[N]T` to `*const [N]T` (and then to `[]const T`). | ||
| 554 | return self.write(&value); | ||
| 555 | }, | ||
| 556 | .Vector => |info| { | ||
| 557 | const array: [info.len]info.child = value; | ||
| 558 | return self.write(&array); | ||
| 559 | }, | ||
| 560 | else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"), | ||
| 561 | } | ||
| 562 | unreachable; | ||
| 563 | } | ||
| 564 | |||
| 565 | fn stringValue(self: *Self, s: []const u8) !void { | ||
| 566 | try self.valueStart(); | ||
| 567 | try encodeJsonString(s, self.options, self.stream); | ||
| 568 | self.valueDone(); | ||
| 569 | } | ||
| 570 | |||
| 571 | pub const arrayElem = @compileError("Deprecated; You don't need to call this anymore."); | ||
| 572 | pub const emitNull = @compileError("Deprecated; Use .write(null) instead."); | ||
| 573 | pub const emitBool = @compileError("Deprecated; Use .write() instead."); | ||
| 574 | pub const emitNumber = @compileError("Deprecated; Use .write() instead."); | ||
| 575 | pub const emitString = @compileError("Deprecated; Use .write() instead."); | ||
| 576 | pub const emitJson = @compileError("Deprecated; Use .write() instead."); | ||
| 64 | }; | 577 | }; |
| 65 | }; | 578 | } |
| 66 | 579 | ||
| 67 | fn outputUnicodeEscape( | 580 | fn outputUnicodeEscape(codepoint: u21, out_stream: anytype) !void { |
| 68 | codepoint: u21, | ||
| 69 | out_stream: anytype, | ||
| 70 | ) !void { | ||
| 71 | if (codepoint <= 0xFFFF) { | 581 | if (codepoint <= 0xFFFF) { |
| 72 | // If the character is in the Basic Multilingual Plane (U+0000 through U+FFFF), | 582 | // If the character is in the Basic Multilingual Plane (U+0000 through U+FFFF), |
| 73 | // then it may be represented as a six-character sequence: a reverse solidus, followed | 583 | // then it may be represented as a six-character sequence: a reverse solidus, followed |
| ... | @@ -87,6 +597,19 @@ fn outputUnicodeEscape( | ... | @@ -87,6 +597,19 @@ fn outputUnicodeEscape( |
| 87 | } | 597 | } |
| 88 | } | 598 | } |
| 89 | 599 | ||
| 600 | fn outputSpecialEscape(c: u8, writer: anytype) !void { | ||
| 601 | switch (c) { | ||
| 602 | '\\' => try writer.writeAll("\\\\"), | ||
| 603 | '\"' => try writer.writeAll("\\\""), | ||
| 604 | 0x08 => try writer.writeAll("\\b"), | ||
| 605 | 0x0C => try writer.writeAll("\\f"), | ||
| 606 | '\n' => try writer.writeAll("\\n"), | ||
| 607 | '\r' => try writer.writeAll("\\r"), | ||
| 608 | '\t' => try writer.writeAll("\\t"), | ||
| 609 | else => try outputUnicodeEscape(c, writer), | ||
| 610 | } | ||
| 611 | } | ||
| 612 | |||
| 90 | /// Write `string` to `writer` as a JSON encoded string. | 613 | /// Write `string` to `writer` as a JSON encoded string. |
| 91 | pub fn encodeJsonString(string: []const u8, options: StringifyOptions, writer: anytype) !void { | 614 | pub fn encodeJsonString(string: []const u8, options: StringifyOptions, writer: anytype) !void { |
| 92 | try writer.writeByte('\"'); | 615 | try writer.writeByte('\"'); |
| ... | @@ -96,218 +619,44 @@ pub fn encodeJsonString(string: []const u8, options: StringifyOptions, writer: a | ... | @@ -96,218 +619,44 @@ pub fn encodeJsonString(string: []const u8, options: StringifyOptions, writer: a |
| 96 | 619 | ||
| 97 | /// Write `chars` to `writer` as JSON encoded string characters. | 620 | /// Write `chars` to `writer` as JSON encoded string characters. |
| 98 | pub fn encodeJsonStringChars(chars: []const u8, options: StringifyOptions, writer: anytype) !void { | 621 | pub fn encodeJsonStringChars(chars: []const u8, options: StringifyOptions, writer: anytype) !void { |
| 622 | var write_cursor: usize = 0; | ||
| 99 | var i: usize = 0; | 623 | var i: usize = 0; |
| 100 | while (i < chars.len) : (i += 1) { | 624 | if (options.escape_unicode) { |
| 101 | switch (chars[i]) { | 625 | while (i < chars.len) : (i += 1) { |
| 102 | // normal ascii character | 626 | switch (chars[i]) { |
| 103 | 0x20...0x21, 0x23...0x2E, 0x30...0x5B, 0x5D...0x7F => |c| try writer.writeByte(c), | 627 | // normal ascii character |
| 104 | // only 2 characters that *must* be escaped | 628 | 0x20...0x21, 0x23...0x5B, 0x5D...0x7E => {}, |
| 105 | '\\' => try writer.writeAll("\\\\"), | 629 | 0x00...0x1F, '\\', '\"' => { |
| 106 | '\"' => try writer.writeAll("\\\""), | 630 | // Always must escape these. |
| 107 | // solidus is optional to escape | 631 | try writer.writeAll(chars[write_cursor..i]); |
| 108 | '/' => { | 632 | try outputSpecialEscape(chars[i], writer); |
| 109 | if (options.string.String.escape_solidus) { | 633 | write_cursor = i + 1; |
| 110 | try writer.writeAll("\\/"); | 634 | }, |
| 111 | } else { | 635 | 0x7F...0xFF => { |
| 112 | try writer.writeByte('/'); | 636 | try writer.writeAll(chars[write_cursor..i]); |
| 113 | } | 637 | const ulen = std.unicode.utf8ByteSequenceLength(chars[i]) catch unreachable; |
| 114 | }, | ||
| 115 | // control characters with short escapes | ||
| 116 | // TODO: option to switch between unicode and 'short' forms? | ||
| 117 | 0x8 => try writer.writeAll("\\b"), | ||
| 118 | 0xC => try writer.writeAll("\\f"), | ||
| 119 | '\n' => try writer.writeAll("\\n"), | ||
| 120 | '\r' => try writer.writeAll("\\r"), | ||
| 121 | '\t' => try writer.writeAll("\\t"), | ||
| 122 | else => { | ||
| 123 | const ulen = std.unicode.utf8ByteSequenceLength(chars[i]) catch unreachable; | ||
| 124 | // control characters (only things left with 1 byte length) should always be printed as unicode escapes | ||
| 125 | if (ulen == 1 or options.string.String.escape_unicode) { | ||
| 126 | const codepoint = std.unicode.utf8Decode(chars[i..][0..ulen]) catch unreachable; | 638 | const codepoint = std.unicode.utf8Decode(chars[i..][0..ulen]) catch unreachable; |
| 127 | try outputUnicodeEscape(codepoint, writer); | 639 | try outputUnicodeEscape(codepoint, writer); |
| 128 | } else { | 640 | i += ulen - 1; |
| 129 | try writer.writeAll(chars[i..][0..ulen]); | 641 | write_cursor = i + 1; |
| 130 | } | ||
| 131 | i += ulen - 1; | ||
| 132 | }, | ||
| 133 | } | ||
| 134 | } | ||
| 135 | } | ||
| 136 | |||
| 137 | /// If `value` has a method called `jsonStringify`, this will call that method instead of the | ||
| 138 | /// default implementation, passing it the `options` and `out_stream` parameters. | ||
| 139 | pub fn stringify( | ||
| 140 | value: anytype, | ||
| 141 | options: StringifyOptions, | ||
| 142 | out_stream: anytype, | ||
| 143 | ) @TypeOf(out_stream).Error!void { | ||
| 144 | const T = @TypeOf(value); | ||
| 145 | switch (@typeInfo(T)) { | ||
| 146 | .Float, .ComptimeFloat => { | ||
| 147 | return std.fmt.formatFloatScientific(value, std.fmt.FormatOptions{}, out_stream); | ||
| 148 | }, | ||
| 149 | .Int, .ComptimeInt => { | ||
| 150 | return std.fmt.formatIntValue(value, "", std.fmt.FormatOptions{}, out_stream); | ||
| 151 | }, | ||
| 152 | .Bool => { | ||
| 153 | return out_stream.writeAll(if (value) "true" else "false"); | ||
| 154 | }, | ||
| 155 | .Null => { | ||
| 156 | return out_stream.writeAll("null"); | ||
| 157 | }, | ||
| 158 | .Optional => { | ||
| 159 | if (value) |payload| { | ||
| 160 | return try stringify(payload, options, out_stream); | ||
| 161 | } else { | ||
| 162 | return try stringify(null, options, out_stream); | ||
| 163 | } | ||
| 164 | }, | ||
| 165 | .Enum => { | ||
| 166 | if (comptime std.meta.trait.hasFn("jsonStringify")(T)) { | ||
| 167 | return value.jsonStringify(options, out_stream); | ||
| 168 | } | ||
| 169 | |||
| 170 | return try encodeJsonString(@tagName(value), options, out_stream); | ||
| 171 | }, | ||
| 172 | .Union => { | ||
| 173 | if (comptime std.meta.trait.hasFn("jsonStringify")(T)) { | ||
| 174 | return value.jsonStringify(options, out_stream); | ||
| 175 | } | ||
| 176 | |||
| 177 | const info = @typeInfo(T).Union; | ||
| 178 | if (info.tag_type) |UnionTagType| { | ||
| 179 | try out_stream.writeByte('{'); | ||
| 180 | var child_options = options; | ||
| 181 | child_options.whitespace.indent_level += 1; | ||
| 182 | inline for (info.fields) |u_field| { | ||
| 183 | if (value == @field(UnionTagType, u_field.name)) { | ||
| 184 | try child_options.whitespace.outputIndent(out_stream); | ||
| 185 | try encodeJsonString(u_field.name, options, out_stream); | ||
| 186 | try out_stream.writeByte(':'); | ||
| 187 | if (child_options.whitespace.separator) { | ||
| 188 | try out_stream.writeByte(' '); | ||
| 189 | } | ||
| 190 | if (u_field.type == void) { | ||
| 191 | try out_stream.writeAll("{}"); | ||
| 192 | } else { | ||
| 193 | try stringify(@field(value, u_field.name), child_options, out_stream); | ||
| 194 | } | ||
| 195 | break; | ||
| 196 | } | ||
| 197 | } else { | ||
| 198 | unreachable; // No active tag? | ||
| 199 | } | ||
| 200 | try options.whitespace.outputIndent(out_stream); | ||
| 201 | try out_stream.writeByte('}'); | ||
| 202 | return; | ||
| 203 | } else { | ||
| 204 | @compileError("Unable to stringify untagged union '" ++ @typeName(T) ++ "'"); | ||
| 205 | } | ||
| 206 | }, | ||
| 207 | .Struct => |S| { | ||
| 208 | if (comptime std.meta.trait.hasFn("jsonStringify")(T)) { | ||
| 209 | return value.jsonStringify(options, out_stream); | ||
| 210 | } | ||
| 211 | |||
| 212 | try out_stream.writeByte(if (S.is_tuple) '[' else '{'); | ||
| 213 | var field_output = false; | ||
| 214 | var child_options = options; | ||
| 215 | child_options.whitespace.indent_level += 1; | ||
| 216 | inline for (S.fields) |Field| { | ||
| 217 | // don't include void fields | ||
| 218 | if (Field.type == void) continue; | ||
| 219 | |||
| 220 | var emit_field = true; | ||
| 221 | |||
| 222 | // don't include optional fields that are null when emit_null_optional_fields is set to false | ||
| 223 | if (@typeInfo(Field.type) == .Optional) { | ||
| 224 | if (options.emit_null_optional_fields == false) { | ||
| 225 | if (@field(value, Field.name) == null) { | ||
| 226 | emit_field = false; | ||
| 227 | } | ||
| 228 | } | ||
| 229 | } | ||
| 230 | |||
| 231 | if (emit_field) { | ||
| 232 | if (!field_output) { | ||
| 233 | field_output = true; | ||
| 234 | } else { | ||
| 235 | try out_stream.writeByte(','); | ||
| 236 | } | ||
| 237 | try child_options.whitespace.outputIndent(out_stream); | ||
| 238 | if (!S.is_tuple) { | ||
| 239 | try encodeJsonString(Field.name, options, out_stream); | ||
| 240 | try out_stream.writeByte(':'); | ||
| 241 | if (child_options.whitespace.separator) { | ||
| 242 | try out_stream.writeByte(' '); | ||
| 243 | } | ||
| 244 | } | ||
| 245 | try stringify(@field(value, Field.name), child_options, out_stream); | ||
| 246 | } | ||
| 247 | } | ||
| 248 | if (field_output) { | ||
| 249 | try options.whitespace.outputIndent(out_stream); | ||
| 250 | } | ||
| 251 | try out_stream.writeByte(if (S.is_tuple) ']' else '}'); | ||
| 252 | return; | ||
| 253 | }, | ||
| 254 | .ErrorSet => return stringify(@as([]const u8, @errorName(value)), options, out_stream), | ||
| 255 | .Pointer => |ptr_info| switch (ptr_info.size) { | ||
| 256 | .One => switch (@typeInfo(ptr_info.child)) { | ||
| 257 | .Array => { | ||
| 258 | const Slice = []const std.meta.Elem(ptr_info.child); | ||
| 259 | return stringify(@as(Slice, value), options, out_stream); | ||
| 260 | }, | 642 | }, |
| 261 | else => { | 643 | } |
| 262 | // TODO: avoid loops? | 644 | } |
| 263 | return stringify(value.*, options, out_stream); | 645 | } else { |
| 646 | while (i < chars.len) : (i += 1) { | ||
| 647 | switch (chars[i]) { | ||
| 648 | // normal bytes | ||
| 649 | 0x20...0x21, 0x23...0x5B, 0x5D...0xFF => {}, | ||
| 650 | 0x00...0x1F, '\\', '\"' => { | ||
| 651 | // Always must escape these. | ||
| 652 | try writer.writeAll(chars[write_cursor..i]); | ||
| 653 | try outputSpecialEscape(chars[i], writer); | ||
| 654 | write_cursor = i + 1; | ||
| 264 | }, | 655 | }, |
| 265 | }, | 656 | } |
| 266 | .Many, .Slice => { | 657 | } |
| 267 | if (ptr_info.size == .Many and ptr_info.sentinel == null) | ||
| 268 | @compileError("unable to stringify type '" ++ @typeName(T) ++ "' without sentinel"); | ||
| 269 | const slice = if (ptr_info.size == .Many) mem.span(value) else value; | ||
| 270 | |||
| 271 | if (ptr_info.child == u8 and options.string == .String and std.unicode.utf8ValidateSlice(slice)) { | ||
| 272 | try encodeJsonString(slice, options, out_stream); | ||
| 273 | return; | ||
| 274 | } | ||
| 275 | |||
| 276 | try out_stream.writeByte('['); | ||
| 277 | var child_options = options; | ||
| 278 | child_options.whitespace.indent_level += 1; | ||
| 279 | for (slice, 0..) |x, i| { | ||
| 280 | if (i != 0) { | ||
| 281 | try out_stream.writeByte(','); | ||
| 282 | } | ||
| 283 | try child_options.whitespace.outputIndent(out_stream); | ||
| 284 | try stringify(x, child_options, out_stream); | ||
| 285 | } | ||
| 286 | if (slice.len != 0) { | ||
| 287 | try options.whitespace.outputIndent(out_stream); | ||
| 288 | } | ||
| 289 | try out_stream.writeByte(']'); | ||
| 290 | return; | ||
| 291 | }, | ||
| 292 | else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"), | ||
| 293 | }, | ||
| 294 | .Array => return stringify(&value, options, out_stream), | ||
| 295 | .Vector => |info| { | ||
| 296 | const array: [info.len]info.child = value; | ||
| 297 | return stringify(&array, options, out_stream); | ||
| 298 | }, | ||
| 299 | else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"), | ||
| 300 | } | 658 | } |
| 301 | unreachable; | 659 | try writer.writeAll(chars[write_cursor..chars.len]); |
| 302 | } | ||
| 303 | |||
| 304 | // Same as `stringify` but accepts an Allocator and stores result in dynamically allocated memory instead of using a Writer. | ||
| 305 | // Caller owns returned memory. | ||
| 306 | pub fn stringifyAlloc(allocator: std.mem.Allocator, value: anytype, options: StringifyOptions) ![]const u8 { | ||
| 307 | var list = std.ArrayList(u8).init(allocator); | ||
| 308 | errdefer list.deinit(); | ||
| 309 | try stringify(value, options, list.writer()); | ||
| 310 | return list.toOwnedSlice(); | ||
| 311 | } | 660 | } |
| 312 | 661 | ||
| 313 | test { | 662 | test { |
lib/std/json/stringify_test.zig+250-88| ... | @@ -2,9 +2,99 @@ const std = @import("std"); | ... | @@ -2,9 +2,99 @@ const std = @import("std"); |
| 2 | const mem = std.mem; | 2 | const mem = std.mem; |
| 3 | const testing = std.testing; | 3 | const testing = std.testing; |
| 4 | 4 | ||
| 5 | const ObjectMap = @import("dynamic.zig").ObjectMap; | ||
| 6 | const Value = @import("dynamic.zig").Value; | ||
| 7 | |||
| 5 | const StringifyOptions = @import("stringify.zig").StringifyOptions; | 8 | const StringifyOptions = @import("stringify.zig").StringifyOptions; |
| 6 | const stringify = @import("stringify.zig").stringify; | 9 | const stringify = @import("stringify.zig").stringify; |
| 10 | const stringifyMaxDepth = @import("stringify.zig").stringifyMaxDepth; | ||
| 11 | const stringifyArbitraryDepth = @import("stringify.zig").stringifyArbitraryDepth; | ||
| 7 | const stringifyAlloc = @import("stringify.zig").stringifyAlloc; | 12 | const stringifyAlloc = @import("stringify.zig").stringifyAlloc; |
| 13 | const writeStream = @import("stringify.zig").writeStream; | ||
| 14 | const writeStreamMaxDepth = @import("stringify.zig").writeStreamMaxDepth; | ||
| 15 | const writeStreamArbitraryDepth = @import("stringify.zig").writeStreamArbitraryDepth; | ||
| 16 | |||
| 17 | test "json write stream" { | ||
| 18 | var out_buf: [1024]u8 = undefined; | ||
| 19 | var slice_stream = std.io.fixedBufferStream(&out_buf); | ||
| 20 | const out = slice_stream.writer(); | ||
| 21 | |||
| 22 | { | ||
| 23 | var w = writeStream(out, .{ .whitespace = .indent_2 }); | ||
| 24 | try testBasicWriteStream(&w, &slice_stream); | ||
| 25 | } | ||
| 26 | |||
| 27 | { | ||
| 28 | var w = writeStreamMaxDepth(out, .{ .whitespace = .indent_2 }, 8); | ||
| 29 | try testBasicWriteStream(&w, &slice_stream); | ||
| 30 | } | ||
| 31 | |||
| 32 | { | ||
| 33 | var w = writeStreamMaxDepth(out, .{ .whitespace = .indent_2 }, null); | ||
| 34 | try testBasicWriteStream(&w, &slice_stream); | ||
| 35 | } | ||
| 36 | |||
| 37 | { | ||
| 38 | var w = writeStreamArbitraryDepth(testing.allocator, out, .{ .whitespace = .indent_2 }); | ||
| 39 | defer w.deinit(); | ||
| 40 | try testBasicWriteStream(&w, &slice_stream); | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | fn testBasicWriteStream(w: anytype, slice_stream: anytype) !void { | ||
| 45 | slice_stream.reset(); | ||
| 46 | |||
| 47 | try w.beginObject(); | ||
| 48 | |||
| 49 | try w.objectField("object"); | ||
| 50 | var arena_allocator = std.heap.ArenaAllocator.init(testing.allocator); | ||
| 51 | defer arena_allocator.deinit(); | ||
| 52 | try w.write(try getJsonObject(arena_allocator.allocator())); | ||
| 53 | |||
| 54 | try w.objectField("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.0e+00 | ||
| 78 | \\ }, | ||
| 79 | \\ "string": "This is a string", | ||
| 80 | \\ "array": [ | ||
| 81 | \\ "Another string", | ||
| 82 | \\ 1, | ||
| 83 | \\ 3.5e+00 | ||
| 84 | \\ ], | ||
| 85 | \\ "int": 10, | ||
| 86 | \\ "float": 3.5e+00 | ||
| 87 | \\} | ||
| 88 | ; | ||
| 89 | try std.testing.expectEqualStrings(expected, result); | ||
| 90 | } | ||
| 91 | |||
| 92 | fn getJsonObject(allocator: std.mem.Allocator) !Value { | ||
| 93 | var value = Value{ .object = ObjectMap.init(allocator) }; | ||
| 94 | try value.object.put("one", Value{ .integer = @as(i64, @intCast(1)) }); | ||
| 95 | try value.object.put("two", Value{ .float = 2.0 }); | ||
| 96 | return value; | ||
| 97 | } | ||
| 8 | 98 | ||
| 9 | test "stringify null optional fields" { | 99 | test "stringify null optional fields" { |
| 10 | const MyStruct = struct { | 100 | const MyStruct = struct { |
| ... | @@ -13,64 +103,63 @@ test "stringify null optional fields" { | ... | @@ -13,64 +103,63 @@ test "stringify null optional fields" { |
| 13 | another_optional: ?[]const u8 = null, | 103 | another_optional: ?[]const u8 = null, |
| 14 | another_required: []const u8 = "something else", | 104 | another_required: []const u8 = "something else", |
| 15 | }; | 105 | }; |
| 16 | try teststringify( | 106 | try testStringify( |
| 17 | \\{"optional":null,"required":"something","another_optional":null,"another_required":"something else"} | 107 | \\{"optional":null,"required":"something","another_optional":null,"another_required":"something else"} |
| 18 | , | 108 | , |
| 19 | MyStruct{}, | 109 | MyStruct{}, |
| 20 | StringifyOptions{}, | 110 | .{}, |
| 21 | ); | 111 | ); |
| 22 | try teststringify( | 112 | try testStringify( |
| 23 | \\{"required":"something","another_required":"something else"} | 113 | \\{"required":"something","another_required":"something else"} |
| 24 | , | 114 | , |
| 25 | MyStruct{}, | 115 | MyStruct{}, |
| 26 | StringifyOptions{ .emit_null_optional_fields = false }, | 116 | .{ .emit_null_optional_fields = false }, |
| 27 | ); | 117 | ); |
| 28 | } | 118 | } |
| 29 | 119 | ||
| 30 | test "stringify basic types" { | 120 | test "stringify basic types" { |
| 31 | try teststringify("false", false, StringifyOptions{}); | 121 | try testStringify("false", false, .{}); |
| 32 | try teststringify("true", true, StringifyOptions{}); | 122 | try testStringify("true", true, .{}); |
| 33 | try teststringify("null", @as(?u8, null), StringifyOptions{}); | 123 | try testStringify("null", @as(?u8, null), .{}); |
| 34 | try teststringify("null", @as(?*u32, null), StringifyOptions{}); | 124 | try testStringify("null", @as(?*u32, null), .{}); |
| 35 | try teststringify("42", 42, StringifyOptions{}); | 125 | try testStringify("42", 42, .{}); |
| 36 | try teststringify("4.2e+01", 42.0, StringifyOptions{}); | 126 | try testStringify("4.2e+01", 42.0, .{}); |
| 37 | try teststringify("42", @as(u8, 42), StringifyOptions{}); | 127 | try testStringify("42", @as(u8, 42), .{}); |
| 38 | try teststringify("42", @as(u128, 42), StringifyOptions{}); | 128 | try testStringify("42", @as(u128, 42), .{}); |
| 39 | try teststringify("4.2e+01", @as(f32, 42), StringifyOptions{}); | 129 | try testStringify("4.2e+01", @as(f32, 42), .{}); |
| 40 | try teststringify("4.2e+01", @as(f64, 42), StringifyOptions{}); | 130 | try testStringify("4.2e+01", @as(f64, 42), .{}); |
| 41 | try teststringify("\"ItBroke\"", @as(anyerror, error.ItBroke), StringifyOptions{}); | 131 | try testStringify("\"ItBroke\"", @as(anyerror, error.ItBroke), .{}); |
| 132 | try testStringify("\"ItBroke\"", error.ItBroke, .{}); | ||
| 42 | } | 133 | } |
| 43 | 134 | ||
| 44 | test "stringify string" { | 135 | test "stringify string" { |
| 45 | try teststringify("\"hello\"", "hello", StringifyOptions{}); | 136 | try testStringify("\"hello\"", "hello", .{}); |
| 46 | try teststringify("\"with\\nescapes\\r\"", "with\nescapes\r", StringifyOptions{}); | 137 | try testStringify("\"with\\nescapes\\r\"", "with\nescapes\r", .{}); |
| 47 | try teststringify("\"with\\nescapes\\r\"", "with\nescapes\r", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); | 138 | try testStringify("\"with\\nescapes\\r\"", "with\nescapes\r", .{ .escape_unicode = true }); |
| 48 | try teststringify("\"with unicode\\u0001\"", "with unicode\u{1}", StringifyOptions{}); | 139 | try testStringify("\"with unicode\\u0001\"", "with unicode\u{1}", .{}); |
| 49 | try teststringify("\"with unicode\\u0001\"", "with unicode\u{1}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); | 140 | try testStringify("\"with unicode\\u0001\"", "with unicode\u{1}", .{ .escape_unicode = true }); |
| 50 | try teststringify("\"with unicode\u{80}\"", "with unicode\u{80}", StringifyOptions{}); | 141 | try testStringify("\"with unicode\u{80}\"", "with unicode\u{80}", .{}); |
| 51 | try teststringify("\"with unicode\\u0080\"", "with unicode\u{80}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); | 142 | try testStringify("\"with unicode\\u0080\"", "with unicode\u{80}", .{ .escape_unicode = true }); |
| 52 | try teststringify("\"with unicode\u{FF}\"", "with unicode\u{FF}", StringifyOptions{}); | 143 | try testStringify("\"with unicode\u{FF}\"", "with unicode\u{FF}", .{}); |
| 53 | try teststringify("\"with unicode\\u00ff\"", "with unicode\u{FF}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); | 144 | try testStringify("\"with unicode\\u00ff\"", "with unicode\u{FF}", .{ .escape_unicode = true }); |
| 54 | try teststringify("\"with unicode\u{100}\"", "with unicode\u{100}", StringifyOptions{}); | 145 | try testStringify("\"with unicode\u{100}\"", "with unicode\u{100}", .{}); |
| 55 | try teststringify("\"with unicode\\u0100\"", "with unicode\u{100}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); | 146 | try testStringify("\"with unicode\\u0100\"", "with unicode\u{100}", .{ .escape_unicode = true }); |
| 56 | try teststringify("\"with unicode\u{800}\"", "with unicode\u{800}", StringifyOptions{}); | 147 | try testStringify("\"with unicode\u{800}\"", "with unicode\u{800}", .{}); |
| 57 | try teststringify("\"with unicode\\u0800\"", "with unicode\u{800}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); | 148 | try testStringify("\"with unicode\\u0800\"", "with unicode\u{800}", .{ .escape_unicode = true }); |
| 58 | try teststringify("\"with unicode\u{8000}\"", "with unicode\u{8000}", StringifyOptions{}); | 149 | try testStringify("\"with unicode\u{8000}\"", "with unicode\u{8000}", .{}); |
| 59 | try teststringify("\"with unicode\\u8000\"", "with unicode\u{8000}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); | 150 | try testStringify("\"with unicode\\u8000\"", "with unicode\u{8000}", .{ .escape_unicode = true }); |
| 60 | try teststringify("\"with unicode\u{D799}\"", "with unicode\u{D799}", StringifyOptions{}); | 151 | try testStringify("\"with unicode\u{D799}\"", "with unicode\u{D799}", .{}); |
| 61 | try teststringify("\"with unicode\\ud799\"", "with unicode\u{D799}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); | 152 | try testStringify("\"with unicode\\ud799\"", "with unicode\u{D799}", .{ .escape_unicode = true }); |
| 62 | try teststringify("\"with unicode\u{10000}\"", "with unicode\u{10000}", StringifyOptions{}); | 153 | try testStringify("\"with unicode\u{10000}\"", "with unicode\u{10000}", .{}); |
| 63 | try teststringify("\"with unicode\\ud800\\udc00\"", "with unicode\u{10000}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); | 154 | try testStringify("\"with unicode\\ud800\\udc00\"", "with unicode\u{10000}", .{ .escape_unicode = true }); |
| 64 | try teststringify("\"with unicode\u{10FFFF}\"", "with unicode\u{10FFFF}", StringifyOptions{}); | 155 | try testStringify("\"with unicode\u{10FFFF}\"", "with unicode\u{10FFFF}", .{}); |
| 65 | try teststringify("\"with unicode\\udbff\\udfff\"", "with unicode\u{10FFFF}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); | 156 | try testStringify("\"with unicode\\udbff\\udfff\"", "with unicode\u{10FFFF}", .{ .escape_unicode = true }); |
| 66 | try teststringify("\"/\"", "/", StringifyOptions{}); | ||
| 67 | try teststringify("\"\\/\"", "/", StringifyOptions{ .string = .{ .String = .{ .escape_solidus = true } } }); | ||
| 68 | } | 157 | } |
| 69 | 158 | ||
| 70 | test "stringify many-item sentinel-terminated string" { | 159 | test "stringify many-item sentinel-terminated string" { |
| 71 | try teststringify("\"hello\"", @as([*:0]const u8, "hello"), StringifyOptions{}); | 160 | try testStringify("\"hello\"", @as([*:0]const u8, "hello"), .{}); |
| 72 | try teststringify("\"with\\nescapes\\r\"", @as([*:0]const u8, "with\nescapes\r"), StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); | 161 | try testStringify("\"with\\nescapes\\r\"", @as([*:0]const u8, "with\nescapes\r"), .{ .escape_unicode = true }); |
| 73 | try teststringify("\"with unicode\\u0001\"", @as([*:0]const u8, "with unicode\u{1}"), StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); | 162 | try testStringify("\"with unicode\\u0001\"", @as([*:0]const u8, "with unicode\u{1}"), .{ .escape_unicode = true }); |
| 74 | } | 163 | } |
| 75 | 164 | ||
| 76 | test "stringify enums" { | 165 | test "stringify enums" { |
| ... | @@ -78,8 +167,8 @@ test "stringify enums" { | ... | @@ -78,8 +167,8 @@ test "stringify enums" { |
| 78 | foo, | 167 | foo, |
| 79 | bar, | 168 | bar, |
| 80 | }; | 169 | }; |
| 81 | try teststringify("\"foo\"", E.foo, .{}); | 170 | try testStringify("\"foo\"", E.foo, .{}); |
| 82 | try teststringify("\"bar\"", E.bar, .{}); | 171 | try testStringify("\"bar\"", E.bar, .{}); |
| 83 | } | 172 | } |
| 84 | 173 | ||
| 85 | test "stringify tagged unions" { | 174 | test "stringify tagged unions" { |
| ... | @@ -88,24 +177,33 @@ test "stringify tagged unions" { | ... | @@ -88,24 +177,33 @@ test "stringify tagged unions" { |
| 88 | foo: u32, | 177 | foo: u32, |
| 89 | bar: bool, | 178 | bar: bool, |
| 90 | }; | 179 | }; |
| 91 | try teststringify("{\"nothing\":{}}", T{ .nothing = {} }, StringifyOptions{}); | 180 | try testStringify("{\"nothing\":{}}", T{ .nothing = {} }, .{}); |
| 92 | try teststringify("{\"foo\":42}", T{ .foo = 42 }, StringifyOptions{}); | 181 | try testStringify("{\"foo\":42}", T{ .foo = 42 }, .{}); |
| 93 | try teststringify("{\"bar\":true}", T{ .bar = true }, StringifyOptions{}); | 182 | try testStringify("{\"bar\":true}", T{ .bar = true }, .{}); |
| 94 | } | 183 | } |
| 95 | 184 | ||
| 96 | test "stringify struct" { | 185 | test "stringify struct" { |
| 97 | try teststringify("{\"foo\":42}", struct { | 186 | try testStringify("{\"foo\":42}", struct { |
| 98 | foo: u32, | 187 | foo: u32, |
| 99 | }{ .foo = 42 }, StringifyOptions{}); | 188 | }{ .foo = 42 }, .{}); |
| 100 | } | 189 | } |
| 101 | 190 | ||
| 102 | test "stringify struct with string as array" { | 191 | test "emit_strings_as_arrays" { |
| 103 | try teststringify("{\"foo\":\"bar\"}", .{ .foo = "bar" }, StringifyOptions{}); | 192 | // Should only affect string values, not object keys. |
| 104 | try teststringify("{\"foo\":[98,97,114]}", .{ .foo = "bar" }, StringifyOptions{ .string = .Array }); | 193 | try testStringify("{\"foo\":\"bar\"}", .{ .foo = "bar" }, .{}); |
| 194 | try testStringify("{\"foo\":[98,97,114]}", .{ .foo = "bar" }, .{ .emit_strings_as_arrays = true }); | ||
| 195 | // Should *not* affect these types: | ||
| 196 | try testStringify("\"foo\"", @as(enum { foo, bar }, .foo), .{ .emit_strings_as_arrays = true }); | ||
| 197 | try testStringify("\"ItBroke\"", error.ItBroke, .{ .emit_strings_as_arrays = true }); | ||
| 198 | // Should work on these: | ||
| 199 | try testStringify("\"bar\"", @Vector(3, u8){ 'b', 'a', 'r' }, .{}); | ||
| 200 | try testStringify("[98,97,114]", @Vector(3, u8){ 'b', 'a', 'r' }, .{ .emit_strings_as_arrays = true }); | ||
| 201 | try testStringify("\"bar\"", [3]u8{ 'b', 'a', 'r' }, .{}); | ||
| 202 | try testStringify("[98,97,114]", [3]u8{ 'b', 'a', 'r' }, .{ .emit_strings_as_arrays = true }); | ||
| 105 | } | 203 | } |
| 106 | 204 | ||
| 107 | test "stringify struct with indentation" { | 205 | test "stringify struct with indentation" { |
| 108 | try teststringify( | 206 | try testStringify( |
| 109 | \\{ | 207 | \\{ |
| 110 | \\ "foo": 42, | 208 | \\ "foo": 42, |
| 111 | \\ "bar": [ | 209 | \\ "bar": [ |
| ... | @@ -122,12 +220,10 @@ test "stringify struct with indentation" { | ... | @@ -122,12 +220,10 @@ test "stringify struct with indentation" { |
| 122 | .foo = 42, | 220 | .foo = 42, |
| 123 | .bar = .{ 1, 2, 3 }, | 221 | .bar = .{ 1, 2, 3 }, |
| 124 | }, | 222 | }, |
| 125 | StringifyOptions{ | 223 | .{ .whitespace = .indent_4 }, |
| 126 | .whitespace = .{}, | ||
| 127 | }, | ||
| 128 | ); | 224 | ); |
| 129 | try teststringify( | 225 | try testStringify( |
| 130 | "{\n\t\"foo\":42,\n\t\"bar\":[\n\t\t1,\n\t\t2,\n\t\t3\n\t]\n}", | 226 | "{\n\t\"foo\": 42,\n\t\"bar\": [\n\t\t1,\n\t\t2,\n\t\t3\n\t]\n}", |
| 131 | struct { | 227 | struct { |
| 132 | foo: u32, | 228 | foo: u32, |
| 133 | bar: [3]u32, | 229 | bar: [3]u32, |
| ... | @@ -135,14 +231,9 @@ test "stringify struct with indentation" { | ... | @@ -135,14 +231,9 @@ test "stringify struct with indentation" { |
| 135 | .foo = 42, | 231 | .foo = 42, |
| 136 | .bar = .{ 1, 2, 3 }, | 232 | .bar = .{ 1, 2, 3 }, |
| 137 | }, | 233 | }, |
| 138 | StringifyOptions{ | 234 | .{ .whitespace = .indent_tab }, |
| 139 | .whitespace = .{ | ||
| 140 | .indent = .tab, | ||
| 141 | .separator = false, | ||
| 142 | }, | ||
| 143 | }, | ||
| 144 | ); | 235 | ); |
| 145 | try teststringify( | 236 | try testStringify( |
| 146 | \\{"foo":42,"bar":[1,2,3]} | 237 | \\{"foo":42,"bar":[1,2,3]} |
| 147 | , | 238 | , |
| 148 | struct { | 239 | struct { |
| ... | @@ -152,59 +243,53 @@ test "stringify struct with indentation" { | ... | @@ -152,59 +243,53 @@ test "stringify struct with indentation" { |
| 152 | .foo = 42, | 243 | .foo = 42, |
| 153 | .bar = .{ 1, 2, 3 }, | 244 | .bar = .{ 1, 2, 3 }, |
| 154 | }, | 245 | }, |
| 155 | StringifyOptions{ | 246 | .{ .whitespace = .minified }, |
| 156 | .whitespace = .{ | ||
| 157 | .indent = .none, | ||
| 158 | .separator = false, | ||
| 159 | }, | ||
| 160 | }, | ||
| 161 | ); | 247 | ); |
| 162 | } | 248 | } |
| 163 | 249 | ||
| 164 | test "stringify struct with void field" { | 250 | test "stringify struct with void field" { |
| 165 | try teststringify("{\"foo\":42}", struct { | 251 | try testStringify("{\"foo\":42}", struct { |
| 166 | foo: u32, | 252 | foo: u32, |
| 167 | bar: void = {}, | 253 | bar: void = {}, |
| 168 | }{ .foo = 42 }, StringifyOptions{}); | 254 | }{ .foo = 42 }, .{}); |
| 169 | } | 255 | } |
| 170 | 256 | ||
| 171 | test "stringify array of structs" { | 257 | test "stringify array of structs" { |
| 172 | const MyStruct = struct { | 258 | const MyStruct = struct { |
| 173 | foo: u32, | 259 | foo: u32, |
| 174 | }; | 260 | }; |
| 175 | try teststringify("[{\"foo\":42},{\"foo\":100},{\"foo\":1000}]", [_]MyStruct{ | 261 | try testStringify("[{\"foo\":42},{\"foo\":100},{\"foo\":1000}]", [_]MyStruct{ |
| 176 | MyStruct{ .foo = 42 }, | 262 | MyStruct{ .foo = 42 }, |
| 177 | MyStruct{ .foo = 100 }, | 263 | MyStruct{ .foo = 100 }, |
| 178 | MyStruct{ .foo = 1000 }, | 264 | MyStruct{ .foo = 1000 }, |
| 179 | }, StringifyOptions{}); | 265 | }, .{}); |
| 180 | } | 266 | } |
| 181 | 267 | ||
| 182 | test "stringify struct with custom stringifier" { | 268 | test "stringify struct with custom stringifier" { |
| 183 | try teststringify("[\"something special\",42]", struct { | 269 | try testStringify("[\"something special\",42]", struct { |
| 184 | foo: u32, | 270 | foo: u32, |
| 185 | const Self = @This(); | 271 | const Self = @This(); |
| 186 | pub fn jsonStringify( | 272 | pub fn jsonStringify(value: @This(), jws: anytype) !void { |
| 187 | value: Self, | ||
| 188 | options: StringifyOptions, | ||
| 189 | out_stream: anytype, | ||
| 190 | ) !void { | ||
| 191 | _ = value; | 273 | _ = value; |
| 192 | try out_stream.writeAll("[\"something special\","); | 274 | try jws.beginArray(); |
| 193 | try stringify(42, options, out_stream); | 275 | try jws.write("something special"); |
| 194 | try out_stream.writeByte(']'); | 276 | try jws.write(42); |
| 277 | try jws.endArray(); | ||
| 195 | } | 278 | } |
| 196 | }{ .foo = 42 }, StringifyOptions{}); | 279 | }{ .foo = 42 }, .{}); |
| 197 | } | 280 | } |
| 198 | 281 | ||
| 199 | test "stringify vector" { | 282 | test "stringify vector" { |
| 200 | try teststringify("[1,1]", @as(@Vector(2, u32), @splat(1)), StringifyOptions{}); | 283 | try testStringify("[1,1]", @as(@Vector(2, u32), @splat(1)), .{}); |
| 284 | try testStringify("\"AA\"", @as(@Vector(2, u8), @splat('A')), .{}); | ||
| 285 | try testStringify("[65,65]", @as(@Vector(2, u8), @splat('A')), .{ .emit_strings_as_arrays = true }); | ||
| 201 | } | 286 | } |
| 202 | 287 | ||
| 203 | test "stringify tuple" { | 288 | test "stringify tuple" { |
| 204 | try teststringify("[\"foo\",42]", std.meta.Tuple(&.{ []const u8, usize }){ "foo", 42 }, StringifyOptions{}); | 289 | try testStringify("[\"foo\",42]", std.meta.Tuple(&.{ []const u8, usize }){ "foo", 42 }, .{}); |
| 205 | } | 290 | } |
| 206 | 291 | ||
| 207 | fn teststringify(expected: []const u8, value: anytype, options: StringifyOptions) !void { | 292 | fn testStringify(expected: []const u8, value: anytype, options: StringifyOptions) !void { |
| 208 | const ValidationWriter = struct { | 293 | const ValidationWriter = struct { |
| 209 | const Self = @This(); | 294 | const Self = @This(); |
| 210 | pub const Writer = std.io.Writer(*Self, Error, write); | 295 | pub const Writer = std.io.Writer(*Self, Error, write); |
| ... | @@ -256,8 +341,34 @@ fn teststringify(expected: []const u8, value: anytype, options: StringifyOptions | ... | @@ -256,8 +341,34 @@ fn teststringify(expected: []const u8, value: anytype, options: StringifyOptions |
| 256 | }; | 341 | }; |
| 257 | 342 | ||
| 258 | var vos = ValidationWriter.init(expected); | 343 | var vos = ValidationWriter.init(expected); |
| 259 | try stringify(value, options, vos.writer()); | 344 | try stringifyArbitraryDepth(testing.allocator, value, options, vos.writer()); |
| 260 | if (vos.expected_remaining.len > 0) return error.NotEnoughData; | 345 | if (vos.expected_remaining.len > 0) return error.NotEnoughData; |
| 346 | |||
| 347 | // Also test with safety disabled. | ||
| 348 | try testStringifyMaxDepth(expected, value, options, null); | ||
| 349 | try testStringifyArbitraryDepth(expected, value, options); | ||
| 350 | } | ||
| 351 | |||
| 352 | fn testStringifyMaxDepth(expected: []const u8, value: anytype, options: StringifyOptions, comptime max_depth: ?usize) !void { | ||
| 353 | var out_buf: [1024]u8 = undefined; | ||
| 354 | var slice_stream = std.io.fixedBufferStream(&out_buf); | ||
| 355 | const out = slice_stream.writer(); | ||
| 356 | |||
| 357 | try stringifyMaxDepth(value, options, out, max_depth); | ||
| 358 | const got = slice_stream.getWritten(); | ||
| 359 | |||
| 360 | try testing.expectEqualStrings(expected, got); | ||
| 361 | } | ||
| 362 | |||
| 363 | fn testStringifyArbitraryDepth(expected: []const u8, value: anytype, options: StringifyOptions) !void { | ||
| 364 | var out_buf: [1024]u8 = undefined; | ||
| 365 | var slice_stream = std.io.fixedBufferStream(&out_buf); | ||
| 366 | const out = slice_stream.writer(); | ||
| 367 | |||
| 368 | try stringifyArbitraryDepth(testing.allocator, value, options, out); | ||
| 369 | const got = slice_stream.getWritten(); | ||
| 370 | |||
| 371 | try testing.expectEqualStrings(expected, got); | ||
| 261 | } | 372 | } |
| 262 | 373 | ||
| 263 | test "stringify alloc" { | 374 | test "stringify alloc" { |
| ... | @@ -270,3 +381,54 @@ test "stringify alloc" { | ... | @@ -270,3 +381,54 @@ test "stringify alloc" { |
| 270 | 381 | ||
| 271 | try std.testing.expectEqualStrings(expected, actual); | 382 | try std.testing.expectEqualStrings(expected, actual); |
| 272 | } | 383 | } |
| 384 | |||
| 385 | test "comptime stringify" { | ||
| 386 | comptime testStringifyMaxDepth("false", false, .{}, null) catch unreachable; | ||
| 387 | comptime testStringifyMaxDepth("false", false, .{}, 0) catch unreachable; | ||
| 388 | comptime testStringifyArbitraryDepth("false", false, .{}) catch unreachable; | ||
| 389 | |||
| 390 | const MyStruct = struct { | ||
| 391 | foo: u32, | ||
| 392 | }; | ||
| 393 | comptime testStringifyMaxDepth("[{\"foo\":42},{\"foo\":100},{\"foo\":1000}]", [_]MyStruct{ | ||
| 394 | MyStruct{ .foo = 42 }, | ||
| 395 | MyStruct{ .foo = 100 }, | ||
| 396 | MyStruct{ .foo = 1000 }, | ||
| 397 | }, .{}, null) catch unreachable; | ||
| 398 | comptime testStringifyMaxDepth("[{\"foo\":42},{\"foo\":100},{\"foo\":1000}]", [_]MyStruct{ | ||
| 399 | MyStruct{ .foo = 42 }, | ||
| 400 | MyStruct{ .foo = 100 }, | ||
| 401 | MyStruct{ .foo = 1000 }, | ||
| 402 | }, .{}, 8) catch unreachable; | ||
| 403 | } | ||
| 404 | |||
| 405 | test "writePreformatted" { | ||
| 406 | var out_buf: [1024]u8 = undefined; | ||
| 407 | var slice_stream = std.io.fixedBufferStream(&out_buf); | ||
| 408 | const out = slice_stream.writer(); | ||
| 409 | |||
| 410 | var w = writeStream(out, .{ .whitespace = .indent_2 }); | ||
| 411 | defer w.deinit(); | ||
| 412 | |||
| 413 | try w.beginObject(); | ||
| 414 | try w.objectField("a"); | ||
| 415 | try w.writePreformatted("[ ]"); | ||
| 416 | try w.objectField("b"); | ||
| 417 | try w.beginArray(); | ||
| 418 | try w.writePreformatted("[[]] "); | ||
| 419 | try w.writePreformatted(" {}"); | ||
| 420 | try w.endArray(); | ||
| 421 | try w.endObject(); | ||
| 422 | |||
| 423 | const result = slice_stream.getWritten(); | ||
| 424 | const expected = | ||
| 425 | \\{ | ||
| 426 | \\ "a": [ ], | ||
| 427 | \\ "b": [ | ||
| 428 | \\ [[]] , | ||
| 429 | \\ {} | ||
| 430 | \\ ] | ||
| 431 | \\} | ||
| 432 | ; | ||
| 433 | try std.testing.expectEqualStrings(expected, result); | ||
| 434 | } |
lib/std/json/test.zig+4-4| ... | @@ -4,6 +4,7 @@ const parseFromSlice = @import("./static.zig").parseFromSlice; | ... | @@ -4,6 +4,7 @@ const parseFromSlice = @import("./static.zig").parseFromSlice; |
| 4 | const validate = @import("./scanner.zig").validate; | 4 | const validate = @import("./scanner.zig").validate; |
| 5 | const JsonScanner = @import("./scanner.zig").Scanner; | 5 | const JsonScanner = @import("./scanner.zig").Scanner; |
| 6 | const Value = @import("./dynamic.zig").Value; | 6 | const Value = @import("./dynamic.zig").Value; |
| 7 | const stringifyAlloc = @import("./stringify.zig").stringifyAlloc; | ||
| 7 | 8 | ||
| 8 | // Support for JSONTestSuite.zig | 9 | // Support for JSONTestSuite.zig |
| 9 | pub fn ok(s: []const u8) !void { | 10 | pub fn ok(s: []const u8) !void { |
| ... | @@ -49,11 +50,10 @@ fn roundTrip(s: []const u8) !void { | ... | @@ -49,11 +50,10 @@ fn roundTrip(s: []const u8) !void { |
| 49 | var parsed = try parseFromSlice(Value, testing.allocator, s, .{}); | 50 | var parsed = try parseFromSlice(Value, testing.allocator, s, .{}); |
| 50 | defer parsed.deinit(); | 51 | defer parsed.deinit(); |
| 51 | 52 | ||
| 52 | var buf: [256]u8 = undefined; | 53 | const rendered = try stringifyAlloc(testing.allocator, parsed.value, .{}); |
| 53 | var fbs = std.io.fixedBufferStream(&buf); | 54 | defer testing.allocator.free(rendered); |
| 54 | try parsed.value.jsonStringify(.{}, fbs.writer()); | ||
| 55 | 55 | ||
| 56 | try testing.expectEqualStrings(s, fbs.getWritten()); | 56 | try testing.expectEqualStrings(s, rendered); |
| 57 | } | 57 | } |
| 58 | 58 | ||
| 59 | test "truncated UTF-8 sequence" { | 59 | test "truncated UTF-8 sequence" { |
lib/std/json/write_stream.zig deleted-300| ... | @@ -1,300 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const assert = std.debug.assert; | ||
| 3 | const maxInt = std.math.maxInt; | ||
| 4 | |||
| 5 | const StringifyOptions = @import("./stringify.zig").StringifyOptions; | ||
| 6 | const jsonStringify = @import("./stringify.zig").stringify; | ||
| 7 | |||
| 8 | const Value = @import("./dynamic.zig").Value; | ||
| 9 | |||
| 10 | const State = enum { | ||
| 11 | complete, | ||
| 12 | value, | ||
| 13 | array_start, | ||
| 14 | array, | ||
| 15 | object_start, | ||
| 16 | object, | ||
| 17 | }; | ||
| 18 | |||
| 19 | /// Writes JSON ([RFC8259](https://tools.ietf.org/html/rfc8259)) formatted data | ||
| 20 | /// to a stream. `max_depth` is a comptime-known upper bound on the nesting depth. | ||
| 21 | /// TODO A future iteration of this API will allow passing `null` for this value, | ||
| 22 | /// and disable safety checks in release builds. | ||
| 23 | pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type { | ||
| 24 | return struct { | ||
| 25 | const Self = @This(); | ||
| 26 | |||
| 27 | pub const Stream = OutStream; | ||
| 28 | |||
| 29 | whitespace: StringifyOptions.Whitespace = StringifyOptions.Whitespace{ | ||
| 30 | .indent_level = 0, | ||
| 31 | .indent = .{ .space = 1 }, | ||
| 32 | }, | ||
| 33 | |||
| 34 | stream: OutStream, | ||
| 35 | state_index: usize, | ||
| 36 | state: [max_depth]State, | ||
| 37 | |||
| 38 | pub fn init(stream: OutStream) Self { | ||
| 39 | var self = Self{ | ||
| 40 | .stream = stream, | ||
| 41 | .state_index = 1, | ||
| 42 | .state = undefined, | ||
| 43 | }; | ||
| 44 | self.state[0] = .complete; | ||
| 45 | self.state[1] = .value; | ||
| 46 | return self; | ||
| 47 | } | ||
| 48 | |||
| 49 | pub fn beginArray(self: *Self) !void { | ||
| 50 | assert(self.state[self.state_index] == State.value); // need to call arrayElem or objectField | ||
| 51 | try self.stream.writeByte('['); | ||
| 52 | self.state[self.state_index] = State.array_start; | ||
| 53 | self.whitespace.indent_level += 1; | ||
| 54 | } | ||
| 55 | |||
| 56 | pub fn beginObject(self: *Self) !void { | ||
| 57 | assert(self.state[self.state_index] == State.value); // need to call arrayElem or objectField | ||
| 58 | try self.stream.writeByte('{'); | ||
| 59 | self.state[self.state_index] = State.object_start; | ||
| 60 | self.whitespace.indent_level += 1; | ||
| 61 | } | ||
| 62 | |||
| 63 | pub fn arrayElem(self: *Self) !void { | ||
| 64 | const state = self.state[self.state_index]; | ||
| 65 | switch (state) { | ||
| 66 | .complete => unreachable, | ||
| 67 | .value => unreachable, | ||
| 68 | .object_start => unreachable, | ||
| 69 | .object => unreachable, | ||
| 70 | .array, .array_start => { | ||
| 71 | if (state == .array) { | ||
| 72 | try self.stream.writeByte(','); | ||
| 73 | } | ||
| 74 | self.state[self.state_index] = .array; | ||
| 75 | self.pushState(.value); | ||
| 76 | try self.indent(); | ||
| 77 | }, | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | pub fn objectField(self: *Self, name: []const u8) !void { | ||
| 82 | const state = self.state[self.state_index]; | ||
| 83 | switch (state) { | ||
| 84 | .complete => unreachable, | ||
| 85 | .value => unreachable, | ||
| 86 | .array_start => unreachable, | ||
| 87 | .array => unreachable, | ||
| 88 | .object, .object_start => { | ||
| 89 | if (state == .object) { | ||
| 90 | try self.stream.writeByte(','); | ||
| 91 | } | ||
| 92 | self.state[self.state_index] = .object; | ||
| 93 | self.pushState(.value); | ||
| 94 | try self.indent(); | ||
| 95 | try self.writeEscapedString(name); | ||
| 96 | try self.stream.writeByte(':'); | ||
| 97 | if (self.whitespace.separator) { | ||
| 98 | try self.stream.writeByte(' '); | ||
| 99 | } | ||
| 100 | }, | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | pub fn endArray(self: *Self) !void { | ||
| 105 | switch (self.state[self.state_index]) { | ||
| 106 | .complete => unreachable, | ||
| 107 | .value => unreachable, | ||
| 108 | .object_start => unreachable, | ||
| 109 | .object => unreachable, | ||
| 110 | .array_start => { | ||
| 111 | self.whitespace.indent_level -= 1; | ||
| 112 | try self.stream.writeByte(']'); | ||
| 113 | self.popState(); | ||
| 114 | }, | ||
| 115 | .array => { | ||
| 116 | self.whitespace.indent_level -= 1; | ||
| 117 | try self.indent(); | ||
| 118 | self.popState(); | ||
| 119 | try self.stream.writeByte(']'); | ||
| 120 | }, | ||
| 121 | } | ||
| 122 | } | ||
| 123 | |||
| 124 | pub fn endObject(self: *Self) !void { | ||
| 125 | switch (self.state[self.state_index]) { | ||
| 126 | .complete => unreachable, | ||
| 127 | .value => unreachable, | ||
| 128 | .array_start => unreachable, | ||
| 129 | .array => unreachable, | ||
| 130 | .object_start => { | ||
| 131 | self.whitespace.indent_level -= 1; | ||
| 132 | try self.stream.writeByte('}'); | ||
| 133 | self.popState(); | ||
| 134 | }, | ||
| 135 | .object => { | ||
| 136 | self.whitespace.indent_level -= 1; | ||
| 137 | try self.indent(); | ||
| 138 | self.popState(); | ||
| 139 | try self.stream.writeByte('}'); | ||
| 140 | }, | ||
| 141 | } | ||
| 142 | } | ||
| 143 | |||
| 144 | pub fn emitNull(self: *Self) !void { | ||
| 145 | assert(self.state[self.state_index] == State.value); | ||
| 146 | try self.stringify(null); | ||
| 147 | self.popState(); | ||
| 148 | } | ||
| 149 | |||
| 150 | pub fn emitBool(self: *Self, value: bool) !void { | ||
| 151 | assert(self.state[self.state_index] == State.value); | ||
| 152 | try self.stringify(value); | ||
| 153 | self.popState(); | ||
| 154 | } | ||
| 155 | |||
| 156 | pub fn emitNumber( | ||
| 157 | self: *Self, | ||
| 158 | /// An integer, float, or `std.math.BigInt`. Emitted as a bare number if it fits losslessly | ||
| 159 | /// in a IEEE 754 double float, otherwise emitted as a string to the full precision. | ||
| 160 | value: anytype, | ||
| 161 | ) !void { | ||
| 162 | assert(self.state[self.state_index] == State.value); | ||
| 163 | switch (@typeInfo(@TypeOf(value))) { | ||
| 164 | .Int => |info| { | ||
| 165 | if (info.bits < 53) { | ||
| 166 | try self.stream.print("{}", .{value}); | ||
| 167 | self.popState(); | ||
| 168 | return; | ||
| 169 | } | ||
| 170 | if (value < 4503599627370496 and (info.signedness == .unsigned or value > -4503599627370496)) { | ||
| 171 | try self.stream.print("{}", .{value}); | ||
| 172 | self.popState(); | ||
| 173 | return; | ||
| 174 | } | ||
| 175 | }, | ||
| 176 | .ComptimeInt => { | ||
| 177 | return self.emitNumber(@as(std.math.IntFittingRange(value, value), value)); | ||
| 178 | }, | ||
| 179 | .Float, .ComptimeFloat => if (@as(f64, @floatCast(value)) == value) { | ||
| 180 | try self.stream.print("{}", .{@as(f64, @floatCast(value))}); | ||
| 181 | self.popState(); | ||
| 182 | return; | ||
| 183 | }, | ||
| 184 | else => {}, | ||
| 185 | } | ||
| 186 | try self.stream.print("\"{}\"", .{value}); | ||
| 187 | self.popState(); | ||
| 188 | } | ||
| 189 | |||
| 190 | pub fn emitString(self: *Self, string: []const u8) !void { | ||
| 191 | assert(self.state[self.state_index] == State.value); | ||
| 192 | try self.writeEscapedString(string); | ||
| 193 | self.popState(); | ||
| 194 | } | ||
| 195 | |||
| 196 | fn writeEscapedString(self: *Self, string: []const u8) !void { | ||
| 197 | assert(std.unicode.utf8ValidateSlice(string)); | ||
| 198 | try self.stringify(string); | ||
| 199 | } | ||
| 200 | |||
| 201 | /// Writes the complete json into the output stream | ||
| 202 | pub fn emitJson(self: *Self, value: Value) Stream.Error!void { | ||
| 203 | assert(self.state[self.state_index] == State.value); | ||
| 204 | try self.stringify(value); | ||
| 205 | self.popState(); | ||
| 206 | } | ||
| 207 | |||
| 208 | fn indent(self: *Self) !void { | ||
| 209 | assert(self.state_index >= 1); | ||
| 210 | try self.whitespace.outputIndent(self.stream); | ||
| 211 | } | ||
| 212 | |||
| 213 | fn pushState(self: *Self, state: State) void { | ||
| 214 | self.state_index += 1; | ||
| 215 | self.state[self.state_index] = state; | ||
| 216 | } | ||
| 217 | |||
| 218 | fn popState(self: *Self) void { | ||
| 219 | self.state_index -= 1; | ||
| 220 | } | ||
| 221 | |||
| 222 | fn stringify(self: *Self, value: anytype) !void { | ||
| 223 | try jsonStringify(value, StringifyOptions{ | ||
| 224 | .whitespace = self.whitespace, | ||
| 225 | }, self.stream); | ||
| 226 | } | ||
| 227 | }; | ||
| 228 | } | ||
| 229 | |||
| 230 | pub fn writeStream( | ||
| 231 | out_stream: anytype, | ||
| 232 | comptime max_depth: usize, | ||
| 233 | ) WriteStream(@TypeOf(out_stream), max_depth) { | ||
| 234 | return WriteStream(@TypeOf(out_stream), max_depth).init(out_stream); | ||
| 235 | } | ||
| 236 | |||
| 237 | const ObjectMap = @import("./dynamic.zig").ObjectMap; | ||
| 238 | |||
| 239 | test "json write stream" { | ||
| 240 | var out_buf: [1024]u8 = undefined; | ||
| 241 | var slice_stream = std.io.fixedBufferStream(&out_buf); | ||
| 242 | const out = slice_stream.writer(); | ||
| 243 | |||
| 244 | var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator); | ||
| 245 | defer arena_allocator.deinit(); | ||
| 246 | |||
| 247 | var w = writeStream(out, 10); | ||
| 248 | |||
| 249 | try w.beginObject(); | ||
| 250 | |||
| 251 | try w.objectField("object"); | ||
| 252 | try w.emitJson(try getJsonObject(arena_allocator.allocator())); | ||
| 253 | |||
| 254 | try w.objectField("string"); | ||
| 255 | try w.emitString("This is a string"); | ||
| 256 | |||
| 257 | try w.objectField("array"); | ||
| 258 | try w.beginArray(); | ||
| 259 | try w.arrayElem(); | ||
| 260 | try w.emitString("Another string"); | ||
| 261 | try w.arrayElem(); | ||
| 262 | try w.emitNumber(@as(i32, 1)); | ||
| 263 | try w.arrayElem(); | ||
| 264 | try w.emitNumber(@as(f32, 3.5)); | ||
| 265 | try w.endArray(); | ||
| 266 | |||
| 267 | try w.objectField("int"); | ||
| 268 | try w.emitNumber(@as(i32, 10)); | ||
| 269 | |||
| 270 | try w.objectField("float"); | ||
| 271 | try w.emitNumber(@as(f32, 3.5)); | ||
| 272 | |||
| 273 | try w.endObject(); | ||
| 274 | |||
| 275 | const result = slice_stream.getWritten(); | ||
| 276 | const expected = | ||
| 277 | \\{ | ||
| 278 | \\ "object": { | ||
| 279 | \\ "one": 1, | ||
| 280 | \\ "two": 2.0e+00 | ||
| 281 | \\ }, | ||
| 282 | \\ "string": "This is a string", | ||
| 283 | \\ "array": [ | ||
| 284 | \\ "Another string", | ||
| 285 | \\ 1, | ||
| 286 | \\ 3.5e+00 | ||
| 287 | \\ ], | ||
| 288 | \\ "int": 10, | ||
| 289 | \\ "float": 3.5e+00 | ||
| 290 | \\} | ||
| 291 | ; | ||
| 292 | try std.testing.expect(std.mem.eql(u8, expected, result)); | ||
| 293 | } | ||
| 294 | |||
| 295 | fn getJsonObject(allocator: std.mem.Allocator) !Value { | ||
| 296 | var value = Value{ .object = ObjectMap.init(allocator) }; | ||
| 297 | try value.object.put("one", Value{ .integer = @as(i64, @intCast(1)) }); | ||
| 298 | try value.object.put("two", Value{ .float = 2.0 }); | ||
| 299 | return value; | ||
| 300 | } | ||
lib/std/std.zig+1| ... | @@ -8,6 +8,7 @@ pub const AutoArrayHashMap = array_hash_map.AutoArrayHashMap; | ... | @@ -8,6 +8,7 @@ pub const AutoArrayHashMap = array_hash_map.AutoArrayHashMap; |
| 8 | pub const AutoArrayHashMapUnmanaged = array_hash_map.AutoArrayHashMapUnmanaged; | 8 | pub const AutoArrayHashMapUnmanaged = array_hash_map.AutoArrayHashMapUnmanaged; |
| 9 | pub const AutoHashMap = hash_map.AutoHashMap; | 9 | pub const AutoHashMap = hash_map.AutoHashMap; |
| 10 | pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged; | 10 | pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged; |
| 11 | pub const BitStack = @import("BitStack.zig"); | ||
| 11 | pub const BoundedArray = @import("bounded_array.zig").BoundedArray; | 12 | pub const BoundedArray = @import("bounded_array.zig").BoundedArray; |
| 12 | pub const BoundedArrayAligned = @import("bounded_array.zig").BoundedArrayAligned; | 13 | pub const BoundedArrayAligned = @import("bounded_array.zig").BoundedArrayAligned; |
| 13 | pub const Build = @import("Build.zig"); | 14 | pub const Build = @import("Build.zig"); |
src/Autodoc.zig+34-95| ... | @@ -385,10 +385,11 @@ pub fn generateZirData(self: *Autodoc) !void { | ... | @@ -385,10 +385,11 @@ pub fn generateZirData(self: *Autodoc) !void { |
| 385 | \\ /** @type {{DocData}} */ | 385 | \\ /** @type {{DocData}} */ |
| 386 | \\ var zigAnalysis= | 386 | \\ var zigAnalysis= |
| 387 | , .{}); | 387 | , .{}); |
| 388 | try std.json.stringify( | 388 | try std.json.stringifyArbitraryDepth( |
| 389 | arena_allocator.allocator(), | ||
| 389 | data, | 390 | data, |
| 390 | .{ | 391 | .{ |
| 391 | .whitespace = .{ .indent = .none, .separator = false }, | 392 | .whitespace = .minified, |
| 392 | .emit_null_optional_fields = true, | 393 | .emit_null_optional_fields = true, |
| 393 | }, | 394 | }, |
| 394 | out, | 395 | out, |
| ... | @@ -532,28 +533,16 @@ const DocData = struct { | ... | @@ -532,28 +533,16 @@ const DocData = struct { |
| 532 | ret: Expr, | 533 | ret: Expr, |
| 533 | }; | 534 | }; |
| 534 | 535 | ||
| 535 | pub fn jsonStringify( | 536 | pub fn jsonStringify(self: DocData, jsw: anytype) !void { |
| 536 | self: DocData, | ||
| 537 | opts: std.json.StringifyOptions, | ||
| 538 | w: anytype, | ||
| 539 | ) !void { | ||
| 540 | var jsw = std.json.writeStream(w, 15); | ||
| 541 | jsw.whitespace = opts.whitespace; | ||
| 542 | try jsw.beginObject(); | 537 | try jsw.beginObject(); |
| 543 | inline for (comptime std.meta.tags(std.meta.FieldEnum(DocData))) |f| { | 538 | inline for (comptime std.meta.tags(std.meta.FieldEnum(DocData))) |f| { |
| 544 | const f_name = @tagName(f); | 539 | const f_name = @tagName(f); |
| 545 | try jsw.objectField(f_name); | 540 | try jsw.objectField(f_name); |
| 546 | switch (f) { | 541 | switch (f) { |
| 547 | .files => try writeFileTableToJson(self.files, self.modules, &jsw), | 542 | .files => try writeFileTableToJson(self.files, self.modules, jsw), |
| 548 | .guide_sections => try writeGuidesToJson(self.guide_sections, &jsw), | 543 | .guide_sections => try writeGuidesToJson(self.guide_sections, jsw), |
| 549 | .modules => { | 544 | .modules => try jsw.write(self.modules.values()), |
| 550 | try std.json.stringify(self.modules.values(), opts, w); | 545 | else => try jsw.write(@field(self, f_name)), |
| 551 | jsw.state_index -= 1; | ||
| 552 | }, | ||
| 553 | else => { | ||
| 554 | try std.json.stringify(@field(self, f_name), opts, w); | ||
| 555 | jsw.state_index -= 1; | ||
| 556 | }, | ||
| 557 | } | 546 | } |
| 558 | } | 547 | } |
| 559 | try jsw.endObject(); | 548 | try jsw.endObject(); |
| ... | @@ -583,24 +572,14 @@ const DocData = struct { | ... | @@ -583,24 +572,14 @@ const DocData = struct { |
| 583 | value: usize, | 572 | value: usize, |
| 584 | }; | 573 | }; |
| 585 | 574 | ||
| 586 | pub fn jsonStringify( | 575 | pub fn jsonStringify(self: DocModule, jsw: anytype) !void { |
| 587 | self: DocModule, | ||
| 588 | opts: std.json.StringifyOptions, | ||
| 589 | w: anytype, | ||
| 590 | ) !void { | ||
| 591 | var jsw = std.json.writeStream(w, 15); | ||
| 592 | jsw.whitespace = opts.whitespace; | ||
| 593 | |||
| 594 | try jsw.beginObject(); | 576 | try jsw.beginObject(); |
| 595 | inline for (comptime std.meta.tags(std.meta.FieldEnum(DocModule))) |f| { | 577 | inline for (comptime std.meta.tags(std.meta.FieldEnum(DocModule))) |f| { |
| 596 | const f_name = @tagName(f); | 578 | const f_name = @tagName(f); |
| 597 | try jsw.objectField(f_name); | 579 | try jsw.objectField(f_name); |
| 598 | switch (f) { | 580 | switch (f) { |
| 599 | .table => try writeModuleTableToJson(self.table, &jsw), | 581 | .table => try writeModuleTableToJson(self.table, jsw), |
| 600 | else => { | 582 | else => try jsw.write(@field(self, f_name)), |
| 601 | try std.json.stringify(@field(self, f_name), opts, w); | ||
| 602 | jsw.state_index -= 1; | ||
| 603 | }, | ||
| 604 | } | 583 | } |
| 605 | } | 584 | } |
| 606 | try jsw.endObject(); | 585 | try jsw.endObject(); |
| ... | @@ -617,18 +596,10 @@ const DocData = struct { | ... | @@ -617,18 +596,10 @@ const DocData = struct { |
| 617 | is_uns: bool = false, // usingnamespace | 596 | is_uns: bool = false, // usingnamespace |
| 618 | parent_container: ?usize, // index into `types` | 597 | parent_container: ?usize, // index into `types` |
| 619 | 598 | ||
| 620 | pub fn jsonStringify( | 599 | pub fn jsonStringify(self: Decl, jsw: anytype) !void { |
| 621 | self: Decl, | ||
| 622 | opts: std.json.StringifyOptions, | ||
| 623 | w: anytype, | ||
| 624 | ) !void { | ||
| 625 | var jsw = std.json.writeStream(w, 15); | ||
| 626 | jsw.whitespace = opts.whitespace; | ||
| 627 | try jsw.beginArray(); | 600 | try jsw.beginArray(); |
| 628 | inline for (comptime std.meta.fields(Decl)) |f| { | 601 | inline for (comptime std.meta.fields(Decl)) |f| { |
| 629 | try jsw.arrayElem(); | 602 | try jsw.write(@field(self, f.name)); |
| 630 | try std.json.stringify(@field(self, f.name), opts, w); | ||
| 631 | jsw.state_index -= 1; | ||
| 632 | } | 603 | } |
| 633 | try jsw.endArray(); | 604 | try jsw.endArray(); |
| 634 | } | 605 | } |
| ... | @@ -644,18 +615,10 @@ const DocData = struct { | ... | @@ -644,18 +615,10 @@ const DocData = struct { |
| 644 | fields: ?[]usize = null, // index into astNodes | 615 | fields: ?[]usize = null, // index into astNodes |
| 645 | @"comptime": bool = false, | 616 | @"comptime": bool = false, |
| 646 | 617 | ||
| 647 | pub fn jsonStringify( | 618 | pub fn jsonStringify(self: AstNode, jsw: anytype) !void { |
| 648 | self: AstNode, | ||
| 649 | opts: std.json.StringifyOptions, | ||
| 650 | w: anytype, | ||
| 651 | ) !void { | ||
| 652 | var jsw = std.json.writeStream(w, 15); | ||
| 653 | jsw.whitespace = opts.whitespace; | ||
| 654 | try jsw.beginArray(); | 619 | try jsw.beginArray(); |
| 655 | inline for (comptime std.meta.fields(AstNode)) |f| { | 620 | inline for (comptime std.meta.fields(AstNode)) |f| { |
| 656 | try jsw.arrayElem(); | 621 | try jsw.write(@field(self, f.name)); |
| 657 | try std.json.stringify(@field(self, f.name), opts, w); | ||
| 658 | jsw.state_index -= 1; | ||
| 659 | } | 622 | } |
| 660 | try jsw.endArray(); | 623 | try jsw.endArray(); |
| 661 | } | 624 | } |
| ... | @@ -776,27 +739,18 @@ const DocData = struct { | ... | @@ -776,27 +739,18 @@ const DocData = struct { |
| 776 | docs: []const u8, | 739 | docs: []const u8, |
| 777 | }; | 740 | }; |
| 778 | 741 | ||
| 779 | pub fn jsonStringify( | 742 | pub fn jsonStringify(self: Type, jsw: anytype) !void { |
| 780 | self: Type, | ||
| 781 | opts: std.json.StringifyOptions, | ||
| 782 | w: anytype, | ||
| 783 | ) !void { | ||
| 784 | const active_tag = std.meta.activeTag(self); | 743 | const active_tag = std.meta.activeTag(self); |
| 785 | var jsw = std.json.writeStream(w, 15); | ||
| 786 | jsw.whitespace = opts.whitespace; | ||
| 787 | try jsw.beginArray(); | 744 | try jsw.beginArray(); |
| 788 | try jsw.arrayElem(); | 745 | try jsw.write(@intFromEnum(active_tag)); |
| 789 | try jsw.emitNumber(@intFromEnum(active_tag)); | ||
| 790 | inline for (comptime std.meta.fields(Type)) |case| { | 746 | inline for (comptime std.meta.fields(Type)) |case| { |
| 791 | if (@field(Type, case.name) == active_tag) { | 747 | if (@field(Type, case.name) == active_tag) { |
| 792 | const current_value = @field(self, case.name); | 748 | const current_value = @field(self, case.name); |
| 793 | inline for (comptime std.meta.fields(case.type)) |f| { | 749 | inline for (comptime std.meta.fields(case.type)) |f| { |
| 794 | try jsw.arrayElem(); | ||
| 795 | if (f.type == std.builtin.Type.Pointer.Size) { | 750 | if (f.type == std.builtin.Type.Pointer.Size) { |
| 796 | try jsw.emitNumber(@intFromEnum(@field(current_value, f.name))); | 751 | try jsw.write(@intFromEnum(@field(current_value, f.name))); |
| 797 | } else { | 752 | } else { |
| 798 | try std.json.stringify(@field(current_value, f.name), opts, w); | 753 | try jsw.write(@field(current_value, f.name)); |
| 799 | jsw.state_index -= 1; | ||
| 800 | } | 754 | } |
| 801 | } | 755 | } |
| 802 | } | 756 | } |
| ... | @@ -919,14 +873,8 @@ const DocData = struct { | ... | @@ -919,14 +873,8 @@ const DocData = struct { |
| 919 | val: WalkResult, | 873 | val: WalkResult, |
| 920 | }; | 874 | }; |
| 921 | 875 | ||
| 922 | pub fn jsonStringify( | 876 | pub fn jsonStringify(self: Expr, jsw: anytype) !void { |
| 923 | self: Expr, | ||
| 924 | opts: std.json.StringifyOptions, | ||
| 925 | w: anytype, | ||
| 926 | ) @TypeOf(w).Error!void { | ||
| 927 | const active_tag = std.meta.activeTag(self); | 877 | const active_tag = std.meta.activeTag(self); |
| 928 | var jsw = std.json.writeStream(w, 15); | ||
| 929 | jsw.whitespace = opts.whitespace; | ||
| 930 | try jsw.beginObject(); | 878 | try jsw.beginObject(); |
| 931 | if (active_tag == .declIndex) { | 879 | if (active_tag == .declIndex) { |
| 932 | try jsw.objectField("declRef"); | 880 | try jsw.objectField("declRef"); |
| ... | @@ -935,14 +883,17 @@ const DocData = struct { | ... | @@ -935,14 +883,17 @@ const DocData = struct { |
| 935 | } | 883 | } |
| 936 | switch (self) { | 884 | switch (self) { |
| 937 | .int => { | 885 | .int => { |
| 938 | if (self.int.negated) try w.writeAll("-"); | 886 | if (self.int.negated) { |
| 939 | try jsw.emitNumber(self.int.value); | 887 | try jsw.write(-@as(i65, self.int.value)); |
| 888 | } else { | ||
| 889 | try jsw.write(self.int.value); | ||
| 890 | } | ||
| 940 | }, | 891 | }, |
| 941 | .builtinField => { | 892 | .builtinField => { |
| 942 | try jsw.emitString(@tagName(self.builtinField)); | 893 | try jsw.write(@tagName(self.builtinField)); |
| 943 | }, | 894 | }, |
| 944 | .declRef => { | 895 | .declRef => { |
| 945 | try jsw.emitNumber(self.declRef.Analyzed); | 896 | try jsw.write(self.declRef.Analyzed); |
| 946 | }, | 897 | }, |
| 947 | else => { | 898 | else => { |
| 948 | inline for (comptime std.meta.fields(Expr)) |case| { | 899 | inline for (comptime std.meta.fields(Expr)) |case| { |
| ... | @@ -952,14 +903,7 @@ const DocData = struct { | ... | @@ -952,14 +903,7 @@ const DocData = struct { |
| 952 | if (comptime std.mem.eql(u8, case.name, "declRef")) | 903 | if (comptime std.mem.eql(u8, case.name, "declRef")) |
| 953 | continue; | 904 | continue; |
| 954 | if (@field(Expr, case.name) == active_tag) { | 905 | if (@field(Expr, case.name) == active_tag) { |
| 955 | try std.json.stringify(@field(self, case.name), opts, w); | 906 | try jsw.write(@field(self, case.name)); |
| 956 | jsw.state_index -= 1; | ||
| 957 | // TODO: we should not reach into the state of the | ||
| 958 | // json writer, but alas, this is what's | ||
| 959 | // necessary with the current api. | ||
| 960 | // would be nice to have a proper integration | ||
| 961 | // between the json writer and the generic | ||
| 962 | // std.json.stringify implementation | ||
| 963 | } | 907 | } |
| 964 | } | 908 | } |
| 965 | }, | 909 | }, |
| ... | @@ -5440,12 +5384,9 @@ fn writeFileTableToJson( | ... | @@ -5440,12 +5384,9 @@ fn writeFileTableToJson( |
| 5440 | try jsw.beginArray(); | 5384 | try jsw.beginArray(); |
| 5441 | var it = map.iterator(); | 5385 | var it = map.iterator(); |
| 5442 | while (it.next()) |entry| { | 5386 | while (it.next()) |entry| { |
| 5443 | try jsw.arrayElem(); | ||
| 5444 | try jsw.beginArray(); | 5387 | try jsw.beginArray(); |
| 5445 | try jsw.arrayElem(); | 5388 | try jsw.write(entry.key_ptr.*.sub_file_path); |
| 5446 | try jsw.emitString(entry.key_ptr.*.sub_file_path); | 5389 | try jsw.write(mods.getIndex(entry.key_ptr.*.pkg) orelse 0); |
| 5447 | try jsw.arrayElem(); | ||
| 5448 | try jsw.emitNumber(mods.getIndex(entry.key_ptr.*.pkg) orelse 0); | ||
| 5449 | try jsw.endArray(); | 5390 | try jsw.endArray(); |
| 5450 | } | 5391 | } |
| 5451 | try jsw.endArray(); | 5392 | try jsw.endArray(); |
| ... | @@ -5462,21 +5403,19 @@ fn writeGuidesToJson(sections: std.ArrayListUnmanaged(Section), jsw: anytype) !v | ... | @@ -5462,21 +5403,19 @@ fn writeGuidesToJson(sections: std.ArrayListUnmanaged(Section), jsw: anytype) !v |
| 5462 | 5403 | ||
| 5463 | for (sections.items) |s| { | 5404 | for (sections.items) |s| { |
| 5464 | // section name | 5405 | // section name |
| 5465 | try jsw.arrayElem(); | ||
| 5466 | try jsw.beginObject(); | 5406 | try jsw.beginObject(); |
| 5467 | try jsw.objectField("name"); | 5407 | try jsw.objectField("name"); |
| 5468 | try jsw.emitString(s.name); | 5408 | try jsw.write(s.name); |
| 5469 | try jsw.objectField("guides"); | 5409 | try jsw.objectField("guides"); |
| 5470 | 5410 | ||
| 5471 | // section value | 5411 | // section value |
| 5472 | try jsw.beginArray(); | 5412 | try jsw.beginArray(); |
| 5473 | for (s.guides.items) |g| { | 5413 | for (s.guides.items) |g| { |
| 5474 | try jsw.arrayElem(); | ||
| 5475 | try jsw.beginObject(); | 5414 | try jsw.beginObject(); |
| 5476 | try jsw.objectField("name"); | 5415 | try jsw.objectField("name"); |
| 5477 | try jsw.emitString(g.name); | 5416 | try jsw.write(g.name); |
| 5478 | try jsw.objectField("body"); | 5417 | try jsw.objectField("body"); |
| 5479 | try jsw.emitString(g.body); | 5418 | try jsw.write(g.body); |
| 5480 | try jsw.endObject(); | 5419 | try jsw.endObject(); |
| 5481 | } | 5420 | } |
| 5482 | try jsw.endArray(); | 5421 | try jsw.endArray(); |
| ... | @@ -5494,7 +5433,7 @@ fn writeModuleTableToJson( | ... | @@ -5494,7 +5433,7 @@ fn writeModuleTableToJson( |
| 5494 | var it = map.valueIterator(); | 5433 | var it = map.valueIterator(); |
| 5495 | while (it.next()) |entry| { | 5434 | while (it.next()) |entry| { |
| 5496 | try jsw.objectField(entry.name); | 5435 | try jsw.objectField(entry.name); |
| 5497 | try jsw.emitNumber(entry.value); | 5436 | try jsw.write(entry.value); |
| 5498 | } | 5437 | } |
| 5499 | try jsw.endObject(); | 5438 | try jsw.endObject(); |
| 5500 | } | 5439 | } |
src/print_env.zig+8-7| ... | @@ -28,26 +28,27 @@ pub fn cmdEnv(gpa: Allocator, args: []const []const u8, stdout: std.fs.File.Writ | ... | @@ -28,26 +28,27 @@ pub fn cmdEnv(gpa: Allocator, args: []const []const u8, stdout: std.fs.File.Writ |
| 28 | var bw = std.io.bufferedWriter(stdout); | 28 | var bw = std.io.bufferedWriter(stdout); |
| 29 | const w = bw.writer(); | 29 | const w = bw.writer(); |
| 30 | 30 | ||
| 31 | var jws = std.json.writeStream(w, 6); | 31 | var jws = std.json.writeStream(w, .{ .whitespace = .indent_1 }); |
| 32 | |||
| 32 | try jws.beginObject(); | 33 | try jws.beginObject(); |
| 33 | 34 | ||
| 34 | try jws.objectField("zig_exe"); | 35 | try jws.objectField("zig_exe"); |
| 35 | try jws.emitString(self_exe_path); | 36 | try jws.write(self_exe_path); |
| 36 | 37 | ||
| 37 | try jws.objectField("lib_dir"); | 38 | try jws.objectField("lib_dir"); |
| 38 | try jws.emitString(zig_lib_directory.path.?); | 39 | try jws.write(zig_lib_directory.path.?); |
| 39 | 40 | ||
| 40 | try jws.objectField("std_dir"); | 41 | try jws.objectField("std_dir"); |
| 41 | try jws.emitString(zig_std_dir); | 42 | try jws.write(zig_std_dir); |
| 42 | 43 | ||
| 43 | try jws.objectField("global_cache_dir"); | 44 | try jws.objectField("global_cache_dir"); |
| 44 | try jws.emitString(global_cache_dir); | 45 | try jws.write(global_cache_dir); |
| 45 | 46 | ||
| 46 | try jws.objectField("version"); | 47 | try jws.objectField("version"); |
| 47 | try jws.emitString(build_options.version); | 48 | try jws.write(build_options.version); |
| 48 | 49 | ||
| 49 | try jws.objectField("target"); | 50 | try jws.objectField("target"); |
| 50 | try jws.emitString(triple); | 51 | try jws.write(triple); |
| 51 | 52 | ||
| 52 | try jws.endObject(); | 53 | try jws.endObject(); |
| 53 | try w.writeByte('\n'); | 54 | try w.writeByte('\n'); |
src/print_targets.zig+14-23| ... | @@ -40,31 +40,28 @@ pub fn cmdTargets( | ... | @@ -40,31 +40,28 @@ pub fn cmdTargets( |
| 40 | 40 | ||
| 41 | var bw = io.bufferedWriter(stdout); | 41 | var bw = io.bufferedWriter(stdout); |
| 42 | const w = bw.writer(); | 42 | const w = bw.writer(); |
| 43 | var jws = std.json.writeStream(w, 6); | 43 | var jws = std.json.writeStream(w, .{ .whitespace = .indent_1 }); |
| 44 | 44 | ||
| 45 | try jws.beginObject(); | 45 | try jws.beginObject(); |
| 46 | 46 | ||
| 47 | try jws.objectField("arch"); | 47 | try jws.objectField("arch"); |
| 48 | try jws.beginArray(); | 48 | try jws.beginArray(); |
| 49 | for (meta.fieldNames(Target.Cpu.Arch)) |field| { | 49 | for (meta.fieldNames(Target.Cpu.Arch)) |field| { |
| 50 | try jws.arrayElem(); | 50 | try jws.write(field); |
| 51 | try jws.emitString(field); | ||
| 52 | } | 51 | } |
| 53 | try jws.endArray(); | 52 | try jws.endArray(); |
| 54 | 53 | ||
| 55 | try jws.objectField("os"); | 54 | try jws.objectField("os"); |
| 56 | try jws.beginArray(); | 55 | try jws.beginArray(); |
| 57 | for (meta.fieldNames(Target.Os.Tag)) |field| { | 56 | for (meta.fieldNames(Target.Os.Tag)) |field| { |
| 58 | try jws.arrayElem(); | 57 | try jws.write(field); |
| 59 | try jws.emitString(field); | ||
| 60 | } | 58 | } |
| 61 | try jws.endArray(); | 59 | try jws.endArray(); |
| 62 | 60 | ||
| 63 | try jws.objectField("abi"); | 61 | try jws.objectField("abi"); |
| 64 | try jws.beginArray(); | 62 | try jws.beginArray(); |
| 65 | for (meta.fieldNames(Target.Abi)) |field| { | 63 | for (meta.fieldNames(Target.Abi)) |field| { |
| 66 | try jws.arrayElem(); | 64 | try jws.write(field); |
| 67 | try jws.emitString(field); | ||
| 68 | } | 65 | } |
| 69 | try jws.endArray(); | 66 | try jws.endArray(); |
| 70 | 67 | ||
| ... | @@ -75,19 +72,16 @@ pub fn cmdTargets( | ... | @@ -75,19 +72,16 @@ pub fn cmdTargets( |
| 75 | @tagName(libc.arch), @tagName(libc.os), @tagName(libc.abi), | 72 | @tagName(libc.arch), @tagName(libc.os), @tagName(libc.abi), |
| 76 | }); | 73 | }); |
| 77 | defer allocator.free(tmp); | 74 | defer allocator.free(tmp); |
| 78 | try jws.arrayElem(); | 75 | try jws.write(tmp); |
| 79 | try jws.emitString(tmp); | ||
| 80 | } | 76 | } |
| 81 | try jws.endArray(); | 77 | try jws.endArray(); |
| 82 | 78 | ||
| 83 | try jws.objectField("glibc"); | 79 | try jws.objectField("glibc"); |
| 84 | try jws.beginArray(); | 80 | try jws.beginArray(); |
| 85 | for (glibc_abi.all_versions) |ver| { | 81 | for (glibc_abi.all_versions) |ver| { |
| 86 | try jws.arrayElem(); | ||
| 87 | |||
| 88 | const tmp = try std.fmt.allocPrint(allocator, "{}", .{ver}); | 82 | const tmp = try std.fmt.allocPrint(allocator, "{}", .{ver}); |
| 89 | defer allocator.free(tmp); | 83 | defer allocator.free(tmp); |
| 90 | try jws.emitString(tmp); | 84 | try jws.write(tmp); |
| 91 | } | 85 | } |
| 92 | try jws.endArray(); | 86 | try jws.endArray(); |
| 93 | 87 | ||
| ... | @@ -102,8 +96,7 @@ pub fn cmdTargets( | ... | @@ -102,8 +96,7 @@ pub fn cmdTargets( |
| 102 | for (arch.allFeaturesList(), 0..) |feature, i_usize| { | 96 | for (arch.allFeaturesList(), 0..) |feature, i_usize| { |
| 103 | const index = @as(Target.Cpu.Feature.Set.Index, @intCast(i_usize)); | 97 | const index = @as(Target.Cpu.Feature.Set.Index, @intCast(i_usize)); |
| 104 | if (model.features.isEnabled(index)) { | 98 | if (model.features.isEnabled(index)) { |
| 105 | try jws.arrayElem(); | 99 | try jws.write(feature.name); |
| 106 | try jws.emitString(feature.name); | ||
| 107 | } | 100 | } |
| 108 | } | 101 | } |
| 109 | try jws.endArray(); | 102 | try jws.endArray(); |
| ... | @@ -118,8 +111,7 @@ pub fn cmdTargets( | ... | @@ -118,8 +111,7 @@ pub fn cmdTargets( |
| 118 | try jws.objectField(@tagName(arch)); | 111 | try jws.objectField(@tagName(arch)); |
| 119 | try jws.beginArray(); | 112 | try jws.beginArray(); |
| 120 | for (arch.allFeaturesList()) |feature| { | 113 | for (arch.allFeaturesList()) |feature| { |
| 121 | try jws.arrayElem(); | 114 | try jws.write(feature.name); |
| 122 | try jws.emitString(feature.name); | ||
| 123 | } | 115 | } |
| 124 | try jws.endArray(); | 116 | try jws.endArray(); |
| 125 | } | 117 | } |
| ... | @@ -131,17 +123,17 @@ pub fn cmdTargets( | ... | @@ -131,17 +123,17 @@ pub fn cmdTargets( |
| 131 | const triple = try native_target.zigTriple(allocator); | 123 | const triple = try native_target.zigTriple(allocator); |
| 132 | defer allocator.free(triple); | 124 | defer allocator.free(triple); |
| 133 | try jws.objectField("triple"); | 125 | try jws.objectField("triple"); |
| 134 | try jws.emitString(triple); | 126 | try jws.write(triple); |
| 135 | } | 127 | } |
| 136 | { | 128 | { |
| 137 | try jws.objectField("cpu"); | 129 | try jws.objectField("cpu"); |
| 138 | try jws.beginObject(); | 130 | try jws.beginObject(); |
| 139 | try jws.objectField("arch"); | 131 | try jws.objectField("arch"); |
| 140 | try jws.emitString(@tagName(native_target.cpu.arch)); | 132 | try jws.write(@tagName(native_target.cpu.arch)); |
| 141 | 133 | ||
| 142 | try jws.objectField("name"); | 134 | try jws.objectField("name"); |
| 143 | const cpu = native_target.cpu; | 135 | const cpu = native_target.cpu; |
| 144 | try jws.emitString(cpu.model.name); | 136 | try jws.write(cpu.model.name); |
| 145 | 137 | ||
| 146 | { | 138 | { |
| 147 | try jws.objectField("features"); | 139 | try jws.objectField("features"); |
| ... | @@ -149,8 +141,7 @@ pub fn cmdTargets( | ... | @@ -149,8 +141,7 @@ pub fn cmdTargets( |
| 149 | for (native_target.cpu.arch.allFeaturesList(), 0..) |feature, i_usize| { | 141 | for (native_target.cpu.arch.allFeaturesList(), 0..) |feature, i_usize| { |
| 150 | const index = @as(Target.Cpu.Feature.Set.Index, @intCast(i_usize)); | 142 | const index = @as(Target.Cpu.Feature.Set.Index, @intCast(i_usize)); |
| 151 | if (cpu.features.isEnabled(index)) { | 143 | if (cpu.features.isEnabled(index)) { |
| 152 | try jws.arrayElem(); | 144 | try jws.write(feature.name); |
| 153 | try jws.emitString(feature.name); | ||
| 154 | } | 145 | } |
| 155 | } | 146 | } |
| 156 | try jws.endArray(); | 147 | try jws.endArray(); |
| ... | @@ -158,9 +149,9 @@ pub fn cmdTargets( | ... | @@ -158,9 +149,9 @@ pub fn cmdTargets( |
| 158 | try jws.endObject(); | 149 | try jws.endObject(); |
| 159 | } | 150 | } |
| 160 | try jws.objectField("os"); | 151 | try jws.objectField("os"); |
| 161 | try jws.emitString(@tagName(native_target.os.tag)); | 152 | try jws.write(@tagName(native_target.os.tag)); |
| 162 | try jws.objectField("abi"); | 153 | try jws.objectField("abi"); |
| 163 | try jws.emitString(@tagName(native_target.abi)); | 154 | try jws.write(@tagName(native_target.abi)); |
| 164 | try jws.endObject(); | 155 | try jws.endObject(); |
| 165 | 156 | ||
| 166 | try jws.endObject(); | 157 | try jws.endObject(); |