mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
std.json: Unify stringify and writeStream (#16405)
This commit is contained in:
parent
a2d81c547c
commit
8924f81d8c
@ -205,6 +205,7 @@ set(ZIG_STAGE2_SOURCES
|
|||||||
"${CMAKE_SOURCE_DIR}/lib/std/atomic/queue.zig"
|
"${CMAKE_SOURCE_DIR}/lib/std/atomic/queue.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/std/atomic/stack.zig"
|
"${CMAKE_SOURCE_DIR}/lib/std/atomic/stack.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/std/base64.zig"
|
"${CMAKE_SOURCE_DIR}/lib/std/base64.zig"
|
||||||
|
"${CMAKE_SOURCE_DIR}/lib/std/BitStack.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/std/buf_map.zig"
|
"${CMAKE_SOURCE_DIR}/lib/std/buf_map.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/std/Build.zig"
|
"${CMAKE_SOURCE_DIR}/lib/std/Build.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/std/Build/Cache.zig"
|
"${CMAKE_SOURCE_DIR}/lib/std/Build/Cache.zig"
|
||||||
@ -260,7 +261,7 @@ set(ZIG_STAGE2_SOURCES
|
|||||||
"${CMAKE_SOURCE_DIR}/lib/std/io/seekable_stream.zig"
|
"${CMAKE_SOURCE_DIR}/lib/std/io/seekable_stream.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/std/io/writer.zig"
|
"${CMAKE_SOURCE_DIR}/lib/std/io/writer.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/std/json.zig"
|
"${CMAKE_SOURCE_DIR}/lib/std/json.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/std/json/write_stream.zig"
|
"${CMAKE_SOURCE_DIR}/lib/std/json/stringify.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/std/leb128.zig"
|
"${CMAKE_SOURCE_DIR}/lib/std/leb128.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/std/linked_list.zig"
|
"${CMAKE_SOURCE_DIR}/lib/std/linked_list.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/std/log.zig"
|
"${CMAKE_SOURCE_DIR}/lib/std/log.zig"
|
||||||
|
|||||||
86
lib/std/BitStack.zig
Normal file
86
lib/std/BitStack.zig
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
//! Effectively a stack of u1 values implemented using ArrayList(u8).
|
||||||
|
|
||||||
|
const BitStack = @This();
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const ArrayList = std.ArrayList;
|
||||||
|
|
||||||
|
bytes: std.ArrayList(u8),
|
||||||
|
bit_len: usize = 0,
|
||||||
|
|
||||||
|
pub fn init(allocator: Allocator) @This() {
|
||||||
|
return .{
|
||||||
|
.bytes = std.ArrayList(u8).init(allocator),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
self.bytes.deinit();
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ensureTotalCapacity(self: *@This(), bit_capcity: usize) Allocator.Error!void {
|
||||||
|
const byte_capacity = (bit_capcity + 7) >> 3;
|
||||||
|
try self.bytes.ensureTotalCapacity(byte_capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(self: *@This(), b: u1) Allocator.Error!void {
|
||||||
|
const byte_index = self.bit_len >> 3;
|
||||||
|
if (self.bytes.items.len <= byte_index) {
|
||||||
|
try self.bytes.append(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pushWithStateAssumeCapacity(self.bytes.items, &self.bit_len, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn peek(self: *const @This()) u1 {
|
||||||
|
return peekWithState(self.bytes.items, self.bit_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pop(self: *@This()) u1 {
|
||||||
|
return popWithState(self.bytes.items, &self.bit_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Standalone function for working with a fixed-size buffer.
|
||||||
|
pub fn pushWithStateAssumeCapacity(buf: []u8, bit_len: *usize, b: u1) void {
|
||||||
|
const byte_index = bit_len.* >> 3;
|
||||||
|
const bit_index = @as(u3, @intCast(bit_len.* & 7));
|
||||||
|
|
||||||
|
buf[byte_index] &= ~(@as(u8, 1) << bit_index);
|
||||||
|
buf[byte_index] |= @as(u8, b) << bit_index;
|
||||||
|
|
||||||
|
bit_len.* += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Standalone function for working with a fixed-size buffer.
|
||||||
|
pub fn peekWithState(buf: []const u8, bit_len: usize) u1 {
|
||||||
|
const byte_index = (bit_len - 1) >> 3;
|
||||||
|
const bit_index = @as(u3, @intCast((bit_len - 1) & 7));
|
||||||
|
return @as(u1, @intCast((buf[byte_index] >> bit_index) & 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Standalone function for working with a fixed-size buffer.
|
||||||
|
pub fn popWithState(buf: []const u8, bit_len: *usize) u1 {
|
||||||
|
const b = peekWithState(buf, bit_len.*);
|
||||||
|
bit_len.* -= 1;
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
const testing = std.testing;
|
||||||
|
test BitStack {
|
||||||
|
var stack = BitStack.init(testing.allocator);
|
||||||
|
defer stack.deinit();
|
||||||
|
|
||||||
|
try stack.push(1);
|
||||||
|
try stack.push(0);
|
||||||
|
try stack.push(0);
|
||||||
|
try stack.push(1);
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(u1, 1), stack.peek());
|
||||||
|
try testing.expectEqual(@as(u1, 1), stack.pop());
|
||||||
|
try testing.expectEqual(@as(u1, 0), stack.peek());
|
||||||
|
try testing.expectEqual(@as(u1, 0), stack.pop());
|
||||||
|
try testing.expectEqual(@as(u1, 0), stack.pop());
|
||||||
|
try testing.expectEqual(@as(u1, 1), stack.pop());
|
||||||
|
}
|
||||||
@ -43,14 +43,15 @@ test Value {
|
|||||||
test writeStream {
|
test writeStream {
|
||||||
var out = ArrayList(u8).init(testing.allocator);
|
var out = ArrayList(u8).init(testing.allocator);
|
||||||
defer out.deinit();
|
defer out.deinit();
|
||||||
var write_stream = writeStream(out.writer(), 99);
|
var write_stream = writeStream(out.writer(), .{ .whitespace = .indent_2 });
|
||||||
|
defer write_stream.deinit();
|
||||||
try write_stream.beginObject();
|
try write_stream.beginObject();
|
||||||
try write_stream.objectField("foo");
|
try write_stream.objectField("foo");
|
||||||
try write_stream.emitNumber(123);
|
try write_stream.write(123);
|
||||||
try write_stream.endObject();
|
try write_stream.endObject();
|
||||||
const expected =
|
const expected =
|
||||||
\\{
|
\\{
|
||||||
\\ "foo": 123
|
\\ "foo": 123
|
||||||
\\}
|
\\}
|
||||||
;
|
;
|
||||||
try testing.expectEqualSlices(u8, expected, out.items);
|
try testing.expectEqualSlices(u8, expected, out.items);
|
||||||
@ -98,13 +99,16 @@ pub const ParseError = @import("json/static.zig").ParseError;
|
|||||||
pub const ParseFromValueError = @import("json/static.zig").ParseFromValueError;
|
pub const ParseFromValueError = @import("json/static.zig").ParseFromValueError;
|
||||||
|
|
||||||
pub const StringifyOptions = @import("json/stringify.zig").StringifyOptions;
|
pub const StringifyOptions = @import("json/stringify.zig").StringifyOptions;
|
||||||
|
pub const stringify = @import("json/stringify.zig").stringify;
|
||||||
|
pub const stringifyMaxDepth = @import("json/stringify.zig").stringifyMaxDepth;
|
||||||
|
pub const stringifyArbitraryDepth = @import("json/stringify.zig").stringifyArbitraryDepth;
|
||||||
|
pub const stringifyAlloc = @import("json/stringify.zig").stringifyAlloc;
|
||||||
|
pub const writeStream = @import("json/stringify.zig").writeStream;
|
||||||
|
pub const writeStreamMaxDepth = @import("json/stringify.zig").writeStreamMaxDepth;
|
||||||
|
pub const writeStreamArbitraryDepth = @import("json/stringify.zig").writeStreamArbitraryDepth;
|
||||||
|
pub const WriteStream = @import("json/stringify.zig").WriteStream;
|
||||||
pub const encodeJsonString = @import("json/stringify.zig").encodeJsonString;
|
pub const encodeJsonString = @import("json/stringify.zig").encodeJsonString;
|
||||||
pub const encodeJsonStringChars = @import("json/stringify.zig").encodeJsonStringChars;
|
pub const encodeJsonStringChars = @import("json/stringify.zig").encodeJsonStringChars;
|
||||||
pub const stringify = @import("json/stringify.zig").stringify;
|
|
||||||
pub const stringifyAlloc = @import("json/stringify.zig").stringifyAlloc;
|
|
||||||
|
|
||||||
pub const WriteStream = @import("json/write_stream.zig").WriteStream;
|
|
||||||
pub const writeStream = @import("json/write_stream.zig").writeStream;
|
|
||||||
|
|
||||||
// Deprecations
|
// Deprecations
|
||||||
pub const parse = @compileError("Deprecated; use parseFromSlice() or parseFromTokenSource() instead.");
|
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
|
|||||||
test {
|
test {
|
||||||
_ = @import("json/test.zig");
|
_ = @import("json/test.zig");
|
||||||
_ = @import("json/scanner.zig");
|
_ = @import("json/scanner.zig");
|
||||||
_ = @import("json/write_stream.zig");
|
|
||||||
_ = @import("json/dynamic.zig");
|
_ = @import("json/dynamic.zig");
|
||||||
_ = @import("json/hashmap_test.zig");
|
_ = @import("json/hashmap.zig");
|
||||||
_ = @import("json/static.zig");
|
_ = @import("json/static.zig");
|
||||||
_ = @import("json/stringify.zig");
|
_ = @import("json/stringify.zig");
|
||||||
_ = @import("json/JSONTestSuite_test.zig");
|
_ = @import("json/JSONTestSuite_test.zig");
|
||||||
|
|||||||
@ -59,44 +59,23 @@ pub const Value = union(enum) {
|
|||||||
stringify(self, .{}, stderr) catch return;
|
stringify(self, .{}, stderr) catch return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn jsonStringify(
|
pub fn jsonStringify(value: @This(), jws: anytype) !void {
|
||||||
value: @This(),
|
|
||||||
options: StringifyOptions,
|
|
||||||
out_stream: anytype,
|
|
||||||
) @TypeOf(out_stream).Error!void {
|
|
||||||
switch (value) {
|
switch (value) {
|
||||||
.null => try stringify(null, options, out_stream),
|
.null => try jws.write(null),
|
||||||
.bool => |inner| try stringify(inner, options, out_stream),
|
.bool => |inner| try jws.write(inner),
|
||||||
.integer => |inner| try stringify(inner, options, out_stream),
|
.integer => |inner| try jws.write(inner),
|
||||||
.float => |inner| try stringify(inner, options, out_stream),
|
.float => |inner| try jws.write(inner),
|
||||||
.number_string => |inner| try out_stream.writeAll(inner),
|
.number_string => |inner| try jws.writePreformatted(inner),
|
||||||
.string => |inner| try stringify(inner, options, out_stream),
|
.string => |inner| try jws.write(inner),
|
||||||
.array => |inner| try stringify(inner.items, options, out_stream),
|
.array => |inner| try jws.write(inner.items),
|
||||||
.object => |inner| {
|
.object => |inner| {
|
||||||
try out_stream.writeByte('{');
|
try jws.beginObject();
|
||||||
var field_output = false;
|
|
||||||
var child_options = options;
|
|
||||||
child_options.whitespace.indent_level += 1;
|
|
||||||
var it = inner.iterator();
|
var it = inner.iterator();
|
||||||
while (it.next()) |entry| {
|
while (it.next()) |entry| {
|
||||||
if (!field_output) {
|
try jws.objectField(entry.key_ptr.*);
|
||||||
field_output = true;
|
try jws.write(entry.value_ptr.*);
|
||||||
} else {
|
|
||||||
try out_stream.writeByte(',');
|
|
||||||
}
|
|
||||||
try child_options.whitespace.outputIndent(out_stream);
|
|
||||||
|
|
||||||
try stringify(entry.key_ptr.*, options, out_stream);
|
|
||||||
try out_stream.writeByte(':');
|
|
||||||
if (child_options.whitespace.separator) {
|
|
||||||
try out_stream.writeByte(' ');
|
|
||||||
}
|
|
||||||
try stringify(entry.value_ptr.*, child_options, out_stream);
|
|
||||||
}
|
}
|
||||||
if (field_output) {
|
try jws.endObject();
|
||||||
try options.whitespace.outputIndent(out_stream);
|
|
||||||
}
|
|
||||||
try out_stream.writeByte('}');
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,38 +69,34 @@ test "json.parser.dynamic" {
|
|||||||
try testing.expect(mem.eql(u8, large_int.number_string, "18446744073709551615"));
|
try testing.expect(mem.eql(u8, large_int.number_string, "18446744073709551615"));
|
||||||
}
|
}
|
||||||
|
|
||||||
const writeStream = @import("./write_stream.zig").writeStream;
|
const writeStream = @import("./stringify.zig").writeStream;
|
||||||
test "write json then parse it" {
|
test "write json then parse it" {
|
||||||
var out_buffer: [1000]u8 = undefined;
|
var out_buffer: [1000]u8 = undefined;
|
||||||
|
|
||||||
var fixed_buffer_stream = std.io.fixedBufferStream(&out_buffer);
|
var fixed_buffer_stream = std.io.fixedBufferStream(&out_buffer);
|
||||||
const out_stream = fixed_buffer_stream.writer();
|
const out_stream = fixed_buffer_stream.writer();
|
||||||
var jw = writeStream(out_stream, 4);
|
var jw = writeStream(out_stream, .{});
|
||||||
|
defer jw.deinit();
|
||||||
|
|
||||||
try jw.beginObject();
|
try jw.beginObject();
|
||||||
|
|
||||||
try jw.objectField("f");
|
try jw.objectField("f");
|
||||||
try jw.emitBool(false);
|
try jw.write(false);
|
||||||
|
|
||||||
try jw.objectField("t");
|
try jw.objectField("t");
|
||||||
try jw.emitBool(true);
|
try jw.write(true);
|
||||||
|
|
||||||
try jw.objectField("int");
|
try jw.objectField("int");
|
||||||
try jw.emitNumber(1234);
|
try jw.write(1234);
|
||||||
|
|
||||||
try jw.objectField("array");
|
try jw.objectField("array");
|
||||||
try jw.beginArray();
|
try jw.beginArray();
|
||||||
|
try jw.write(null);
|
||||||
try jw.arrayElem();
|
try jw.write(12.34);
|
||||||
try jw.emitNull();
|
|
||||||
|
|
||||||
try jw.arrayElem();
|
|
||||||
try jw.emitNumber(12.34);
|
|
||||||
|
|
||||||
try jw.endArray();
|
try jw.endArray();
|
||||||
|
|
||||||
try jw.objectField("str");
|
try jw.objectField("str");
|
||||||
try jw.emitString("hello");
|
try jw.write("hello");
|
||||||
|
|
||||||
try jw.endObject();
|
try jw.endObject();
|
||||||
|
|
||||||
@ -185,64 +181,50 @@ test "escaped characters" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "Value.jsonStringify" {
|
test "Value.jsonStringify" {
|
||||||
{
|
var vals = [_]Value{
|
||||||
var buffer: [10]u8 = undefined;
|
.{ .integer = 1 },
|
||||||
var fbs = std.io.fixedBufferStream(&buffer);
|
.{ .integer = 2 },
|
||||||
try @as(Value, .null).jsonStringify(.{}, fbs.writer());
|
.{ .number_string = "3" },
|
||||||
try testing.expectEqualSlices(u8, fbs.getWritten(), "null");
|
};
|
||||||
}
|
var obj = ObjectMap.init(testing.allocator);
|
||||||
{
|
defer obj.deinit();
|
||||||
var buffer: [10]u8 = undefined;
|
try obj.putNoClobber("a", .{ .string = "b" });
|
||||||
var fbs = std.io.fixedBufferStream(&buffer);
|
var array = [_]Value{
|
||||||
try (Value{ .bool = true }).jsonStringify(.{}, fbs.writer());
|
Value.null,
|
||||||
try testing.expectEqualSlices(u8, fbs.getWritten(), "true");
|
Value{ .bool = true },
|
||||||
}
|
Value{ .integer = 42 },
|
||||||
{
|
Value{ .number_string = "43" },
|
||||||
var buffer: [10]u8 = undefined;
|
Value{ .float = 42 },
|
||||||
var fbs = std.io.fixedBufferStream(&buffer);
|
Value{ .string = "weeee" },
|
||||||
try (Value{ .integer = 42 }).jsonStringify(.{}, fbs.writer());
|
Value{ .array = Array.fromOwnedSlice(undefined, &vals) },
|
||||||
try testing.expectEqualSlices(u8, fbs.getWritten(), "42");
|
Value{ .object = obj },
|
||||||
}
|
};
|
||||||
{
|
var buffer: [0x1000]u8 = undefined;
|
||||||
var buffer: [10]u8 = undefined;
|
var fbs = std.io.fixedBufferStream(&buffer);
|
||||||
var fbs = std.io.fixedBufferStream(&buffer);
|
|
||||||
try (Value{ .number_string = "43" }).jsonStringify(.{}, fbs.writer());
|
var jw = writeStream(fbs.writer(), .{ .whitespace = .indent_1 });
|
||||||
try testing.expectEqualSlices(u8, fbs.getWritten(), "43");
|
defer jw.deinit();
|
||||||
}
|
try jw.write(array);
|
||||||
{
|
|
||||||
var buffer: [10]u8 = undefined;
|
const expected =
|
||||||
var fbs = std.io.fixedBufferStream(&buffer);
|
\\[
|
||||||
try (Value{ .float = 42 }).jsonStringify(.{}, fbs.writer());
|
\\ null,
|
||||||
try testing.expectEqualSlices(u8, fbs.getWritten(), "4.2e+01");
|
\\ true,
|
||||||
}
|
\\ 42,
|
||||||
{
|
\\ 43,
|
||||||
var buffer: [10]u8 = undefined;
|
\\ 4.2e+01,
|
||||||
var fbs = std.io.fixedBufferStream(&buffer);
|
\\ "weeee",
|
||||||
try (Value{ .string = "weeee" }).jsonStringify(.{}, fbs.writer());
|
\\ [
|
||||||
try testing.expectEqualSlices(u8, fbs.getWritten(), "\"weeee\"");
|
\\ 1,
|
||||||
}
|
\\ 2,
|
||||||
{
|
\\ 3
|
||||||
var buffer: [10]u8 = undefined;
|
\\ ],
|
||||||
var fbs = std.io.fixedBufferStream(&buffer);
|
\\ {
|
||||||
var vals = [_]Value{
|
\\ "a": "b"
|
||||||
.{ .integer = 1 },
|
\\ }
|
||||||
.{ .integer = 2 },
|
\\]
|
||||||
.{ .number_string = "3" },
|
;
|
||||||
};
|
try testing.expectEqualSlices(u8, expected, fbs.getWritten());
|
||||||
try (Value{
|
|
||||||
.array = Array.fromOwnedSlice(undefined, &vals),
|
|
||||||
}).jsonStringify(.{}, fbs.writer());
|
|
||||||
try testing.expectEqualSlices(u8, fbs.getWritten(), "[1,2,3]");
|
|
||||||
}
|
|
||||||
{
|
|
||||||
var buffer: [10]u8 = undefined;
|
|
||||||
var fbs = std.io.fixedBufferStream(&buffer);
|
|
||||||
var obj = ObjectMap.init(testing.allocator);
|
|
||||||
defer obj.deinit();
|
|
||||||
try obj.putNoClobber("a", .{ .string = "b" });
|
|
||||||
try (Value{ .object = obj }).jsonStringify(.{}, fbs.writer());
|
|
||||||
try testing.expectEqualSlices(u8, fbs.getWritten(), "{\"a\":\"b\"}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test "parseFromValue(std.json.Value,...)" {
|
test "parseFromValue(std.json.Value,...)" {
|
||||||
|
|||||||
@ -5,9 +5,6 @@ const ParseOptions = @import("static.zig").ParseOptions;
|
|||||||
const innerParse = @import("static.zig").innerParse;
|
const innerParse = @import("static.zig").innerParse;
|
||||||
const innerParseFromValue = @import("static.zig").innerParseFromValue;
|
const innerParseFromValue = @import("static.zig").innerParseFromValue;
|
||||||
const Value = @import("dynamic.zig").Value;
|
const Value = @import("dynamic.zig").Value;
|
||||||
const StringifyOptions = @import("stringify.zig").StringifyOptions;
|
|
||||||
const stringify = @import("stringify.zig").stringify;
|
|
||||||
const encodeJsonString = @import("stringify.zig").encodeJsonString;
|
|
||||||
|
|
||||||
/// A thin wrapper around `std.StringArrayHashMapUnmanaged` that implements
|
/// A thin wrapper around `std.StringArrayHashMapUnmanaged` that implements
|
||||||
/// `jsonParse`, `jsonParseFromValue`, and `jsonStringify`.
|
/// `jsonParse`, `jsonParseFromValue`, and `jsonStringify`.
|
||||||
@ -70,30 +67,14 @@ pub fn ArrayHashMap(comptime T: type) type {
|
|||||||
return .{ .map = map };
|
return .{ .map = map };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn jsonStringify(self: @This(), options: StringifyOptions, out_stream: anytype) !void {
|
pub fn jsonStringify(self: @This(), jws: anytype) !void {
|
||||||
try out_stream.writeByte('{');
|
try jws.beginObject();
|
||||||
var field_output = false;
|
|
||||||
var child_options = options;
|
|
||||||
child_options.whitespace.indent_level += 1;
|
|
||||||
var it = self.map.iterator();
|
var it = self.map.iterator();
|
||||||
while (it.next()) |kv| {
|
while (it.next()) |kv| {
|
||||||
if (!field_output) {
|
try jws.objectField(kv.key_ptr.*);
|
||||||
field_output = true;
|
try jws.write(kv.value_ptr.*);
|
||||||
} else {
|
|
||||||
try out_stream.writeByte(',');
|
|
||||||
}
|
|
||||||
try child_options.whitespace.outputIndent(out_stream);
|
|
||||||
try encodeJsonString(kv.key_ptr.*, options, out_stream);
|
|
||||||
try out_stream.writeByte(':');
|
|
||||||
if (child_options.whitespace.separator) {
|
|
||||||
try out_stream.writeByte(' ');
|
|
||||||
}
|
|
||||||
try stringify(kv.value_ptr.*, child_options, out_stream);
|
|
||||||
}
|
}
|
||||||
if (field_output) {
|
try jws.endObject();
|
||||||
try options.whitespace.outputIndent(out_stream);
|
|
||||||
}
|
|
||||||
try out_stream.writeByte('}');
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -101,11 +101,7 @@ test "stringify json hashmap whitespace" {
|
|||||||
try value.map.put(testing.allocator, "xyz", .{ .i = 1, .s = "w" });
|
try value.map.put(testing.allocator, "xyz", .{ .i = 1, .s = "w" });
|
||||||
|
|
||||||
{
|
{
|
||||||
const doc = try stringifyAlloc(testing.allocator, value, .{
|
const doc = try stringifyAlloc(testing.allocator, value, .{ .whitespace = .indent_2 });
|
||||||
.whitespace = .{
|
|
||||||
.indent = .{ .space = 2 },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
defer testing.allocator.free(doc);
|
defer testing.allocator.free(doc);
|
||||||
try testing.expectEqualStrings(
|
try testing.expectEqualStrings(
|
||||||
\\{
|
\\{
|
||||||
|
|||||||
@ -33,6 +33,7 @@ const std = @import("std");
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const ArrayList = std.ArrayList;
|
const ArrayList = std.ArrayList;
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
const BitStack = std.BitStack;
|
||||||
|
|
||||||
/// Scan the input and check for malformed JSON.
|
/// Scan the input and check for malformed JSON.
|
||||||
/// On `SyntaxError` or `UnexpectedEndOfInput`, returns `false`.
|
/// On `SyntaxError` or `UnexpectedEndOfInput`, returns `false`.
|
||||||
@ -337,7 +338,7 @@ pub fn Reader(comptime buffer_size: usize, comptime ReaderType: type) type {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Like `std.json.Scanner.skipUntilStackHeight()` but handles `error.BufferUnderrun`.
|
/// Like `std.json.Scanner.skipUntilStackHeight()` but handles `error.BufferUnderrun`.
|
||||||
pub fn skipUntilStackHeight(self: *@This(), terminal_stack_height: u32) NextError!void {
|
pub fn skipUntilStackHeight(self: *@This(), terminal_stack_height: usize) NextError!void {
|
||||||
while (true) {
|
while (true) {
|
||||||
return self.scanner.skipUntilStackHeight(terminal_stack_height) catch |err| switch (err) {
|
return self.scanner.skipUntilStackHeight(terminal_stack_height) catch |err| switch (err) {
|
||||||
error.BufferUnderrun => {
|
error.BufferUnderrun => {
|
||||||
@ -350,11 +351,11 @@ pub fn Reader(comptime buffer_size: usize, comptime ReaderType: type) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Calls `std.json.Scanner.stackHeight`.
|
/// Calls `std.json.Scanner.stackHeight`.
|
||||||
pub fn stackHeight(self: *const @This()) u32 {
|
pub fn stackHeight(self: *const @This()) usize {
|
||||||
return self.scanner.stackHeight();
|
return self.scanner.stackHeight();
|
||||||
}
|
}
|
||||||
/// Calls `std.json.Scanner.ensureTotalStackCapacity`.
|
/// Calls `std.json.Scanner.ensureTotalStackCapacity`.
|
||||||
pub fn ensureTotalStackCapacity(self: *@This(), height: u32) Allocator.Error!void {
|
pub fn ensureTotalStackCapacity(self: *@This(), height: usize) Allocator.Error!void {
|
||||||
try self.scanner.ensureTotalStackCapacity(height);
|
try self.scanner.ensureTotalStackCapacity(height);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -654,7 +655,7 @@ pub const Scanner = struct {
|
|||||||
|
|
||||||
/// Skip tokens until an `.object_end` or `.array_end` token results in a `stackHeight()` equal the given stack height.
|
/// Skip tokens until an `.object_end` or `.array_end` token results in a `stackHeight()` equal the given stack height.
|
||||||
/// Unlike `skipValue()`, this function is available in streaming mode.
|
/// Unlike `skipValue()`, this function is available in streaming mode.
|
||||||
pub fn skipUntilStackHeight(self: *@This(), terminal_stack_height: u32) NextError!void {
|
pub fn skipUntilStackHeight(self: *@This(), terminal_stack_height: usize) NextError!void {
|
||||||
while (true) {
|
while (true) {
|
||||||
switch (try self.next()) {
|
switch (try self.next()) {
|
||||||
.object_end, .array_end => {
|
.object_end, .array_end => {
|
||||||
@ -667,13 +668,13 @@ pub const Scanner = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The depth of `{}` or `[]` nesting levels at the current position.
|
/// The depth of `{}` or `[]` nesting levels at the current position.
|
||||||
pub fn stackHeight(self: *const @This()) u32 {
|
pub fn stackHeight(self: *const @This()) usize {
|
||||||
return self.stack.bit_len;
|
return self.stack.bit_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pre allocate memory to hold the given number of nesting levels.
|
/// Pre allocate memory to hold the given number of nesting levels.
|
||||||
/// `stackHeight()` up to the given number will not cause allocations.
|
/// `stackHeight()` up to the given number will not cause allocations.
|
||||||
pub fn ensureTotalStackCapacity(self: *@This(), height: u32) Allocator.Error!void {
|
pub fn ensureTotalStackCapacity(self: *@This(), height: usize) Allocator.Error!void {
|
||||||
try self.stack.ensureTotalCapacity(height);
|
try self.stack.ensureTotalCapacity(height);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1697,53 +1698,6 @@ pub const Scanner = struct {
|
|||||||
const OBJECT_MODE = 0;
|
const OBJECT_MODE = 0;
|
||||||
const ARRAY_MODE = 1;
|
const ARRAY_MODE = 1;
|
||||||
|
|
||||||
const BitStack = struct {
|
|
||||||
bytes: std.ArrayList(u8),
|
|
||||||
bit_len: u32 = 0,
|
|
||||||
|
|
||||||
pub fn init(allocator: Allocator) @This() {
|
|
||||||
return .{
|
|
||||||
.bytes = std.ArrayList(u8).init(allocator),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *@This()) void {
|
|
||||||
self.bytes.deinit();
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ensureTotalCapacity(self: *@This(), bit_capcity: u32) Allocator.Error!void {
|
|
||||||
const byte_capacity = (bit_capcity + 7) >> 3;
|
|
||||||
try self.bytes.ensureTotalCapacity(byte_capacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push(self: *@This(), b: u1) Allocator.Error!void {
|
|
||||||
const byte_index = self.bit_len >> 3;
|
|
||||||
const bit_index = @as(u3, @intCast(self.bit_len & 7));
|
|
||||||
|
|
||||||
if (self.bytes.items.len <= byte_index) {
|
|
||||||
try self.bytes.append(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.bytes.items[byte_index] &= ~(@as(u8, 1) << bit_index);
|
|
||||||
self.bytes.items[byte_index] |= @as(u8, b) << bit_index;
|
|
||||||
|
|
||||||
self.bit_len += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn peek(self: *const @This()) u1 {
|
|
||||||
const byte_index = (self.bit_len - 1) >> 3;
|
|
||||||
const bit_index = @as(u3, @intCast((self.bit_len - 1) & 7));
|
|
||||||
return @as(u1, @intCast((self.bytes.items[byte_index] >> bit_index) & 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pop(self: *@This()) u1 {
|
|
||||||
const b = self.peek();
|
|
||||||
self.bit_len -= 1;
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fn appendSlice(list: *std.ArrayList(u8), buf: []const u8, max_value_len: usize) !void {
|
fn appendSlice(list: *std.ArrayList(u8), buf: []const u8, max_value_len: usize) !void {
|
||||||
const new_len = std.math.add(usize, list.items.len, buf.len) catch return error.ValueTooLong;
|
const new_len = std.math.add(usize, list.items.len, buf.len) catch return error.ValueTooLong;
|
||||||
if (new_len > max_value_len) return error.ValueTooLong;
|
if (new_len > max_value_len) return error.ValueTooLong;
|
||||||
|
|||||||
@ -1,73 +1,583 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const mem = std.mem;
|
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const ArrayList = std.ArrayList;
|
||||||
|
const BitStack = std.BitStack;
|
||||||
|
|
||||||
|
const OBJECT_MODE = 0;
|
||||||
|
const ARRAY_MODE = 1;
|
||||||
|
|
||||||
pub const StringifyOptions = struct {
|
pub const StringifyOptions = struct {
|
||||||
pub const Whitespace = struct {
|
/// Controls the whitespace emitted.
|
||||||
/// How many indentation levels deep are we?
|
/// The default `.minified` is a compact encoding with no whitespace between tokens.
|
||||||
indent_level: usize = 0,
|
/// Any setting other than `.minified` will use newlines, indentation, and a space after each ':'.
|
||||||
|
/// `.indent_1` means 1 space for each indentation level, `.indent_2` means 2 spaces, etc.
|
||||||
/// What character(s) should be used for indentation?
|
/// `.indent_tab` uses a tab for each indentation level.
|
||||||
indent: union(enum) {
|
whitespace: enum {
|
||||||
space: u8,
|
minified,
|
||||||
tab: void,
|
indent_1,
|
||||||
none: void,
|
indent_2,
|
||||||
} = .{ .space = 4 },
|
indent_3,
|
||||||
|
indent_4,
|
||||||
/// After a colon, should whitespace be inserted?
|
indent_8,
|
||||||
separator: bool = true,
|
indent_tab,
|
||||||
|
} = .minified,
|
||||||
pub fn outputIndent(
|
|
||||||
whitespace: @This(),
|
|
||||||
out_stream: anytype,
|
|
||||||
) @TypeOf(out_stream).Error!void {
|
|
||||||
var char: u8 = undefined;
|
|
||||||
var n_chars: usize = undefined;
|
|
||||||
switch (whitespace.indent) {
|
|
||||||
.space => |n_spaces| {
|
|
||||||
char = ' ';
|
|
||||||
n_chars = n_spaces;
|
|
||||||
},
|
|
||||||
.tab => {
|
|
||||||
char = '\t';
|
|
||||||
n_chars = 1;
|
|
||||||
},
|
|
||||||
.none => return,
|
|
||||||
}
|
|
||||||
try out_stream.writeByte('\n');
|
|
||||||
n_chars *= whitespace.indent_level;
|
|
||||||
try out_stream.writeByteNTimes(char, n_chars);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Controls the whitespace emitted
|
|
||||||
whitespace: Whitespace = .{ .indent = .none, .separator = false },
|
|
||||||
|
|
||||||
/// Should optional fields with null value be written?
|
/// Should optional fields with null value be written?
|
||||||
emit_null_optional_fields: bool = true,
|
emit_null_optional_fields: bool = true,
|
||||||
|
|
||||||
string: StringOptions = StringOptions{ .String = .{} },
|
/// Arrays/slices of u8 are typically encoded as JSON strings.
|
||||||
|
/// This option emits them as arrays of numbers instead.
|
||||||
|
/// Does not affect calls to `objectField()`.
|
||||||
|
emit_strings_as_arrays: bool = false,
|
||||||
|
|
||||||
/// Should []u8 be serialised as a string? or an array?
|
/// Should unicode characters be escaped in strings?
|
||||||
pub const StringOptions = union(enum) {
|
escape_unicode: bool = false,
|
||||||
Array,
|
|
||||||
String: StringOutputOptions,
|
|
||||||
|
|
||||||
/// String output options
|
|
||||||
const StringOutputOptions = struct {
|
|
||||||
/// Should '/' be escaped in strings?
|
|
||||||
escape_solidus: bool = false,
|
|
||||||
|
|
||||||
/// Should unicode characters be escaped in strings?
|
|
||||||
escape_unicode: bool = false,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fn outputUnicodeEscape(
|
/// Writes the given value to the `std.io.Writer` stream.
|
||||||
codepoint: u21,
|
/// See `WriteStream` for how the given value is serialized into JSON.
|
||||||
|
/// The maximum nesting depth of the output JSON document is 256.
|
||||||
|
/// See also `stringifyMaxDepth` and `stringifyArbitraryDepth`.
|
||||||
|
pub fn stringify(
|
||||||
|
value: anytype,
|
||||||
|
options: StringifyOptions,
|
||||||
out_stream: anytype,
|
out_stream: anytype,
|
||||||
) !void {
|
) @TypeOf(out_stream).Error!void {
|
||||||
|
var jw = writeStream(out_stream, options);
|
||||||
|
defer jw.deinit();
|
||||||
|
try jw.write(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Like `stringify` with configurable nesting depth.
|
||||||
|
/// `max_depth` is rounded up to the nearest multiple of 8.
|
||||||
|
/// Give `null` for `max_depth` to disable some safety checks and allow arbitrary nesting depth.
|
||||||
|
/// See `writeStreamMaxDepth` for more info.
|
||||||
|
pub fn stringifyMaxDepth(
|
||||||
|
value: anytype,
|
||||||
|
options: StringifyOptions,
|
||||||
|
out_stream: anytype,
|
||||||
|
comptime max_depth: ?usize,
|
||||||
|
) @TypeOf(out_stream).Error!void {
|
||||||
|
var jw = writeStreamMaxDepth(out_stream, options, max_depth);
|
||||||
|
try jw.write(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Like `stringify` but takes an allocator to facilitate safety checks while allowing arbitrary nesting depth.
|
||||||
|
/// These safety checks can be helpful when debugging custom `jsonStringify` implementations;
|
||||||
|
/// See `WriteStream`.
|
||||||
|
pub fn stringifyArbitraryDepth(
|
||||||
|
allocator: Allocator,
|
||||||
|
value: anytype,
|
||||||
|
options: StringifyOptions,
|
||||||
|
out_stream: anytype,
|
||||||
|
) WriteStream(@TypeOf(out_stream), .checked_to_arbitrary_depth).Error!void {
|
||||||
|
var jw = writeStreamArbitraryDepth(allocator, out_stream, options);
|
||||||
|
defer jw.deinit();
|
||||||
|
try jw.write(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calls `stringifyArbitraryDepth` and stores the result in dynamically allocated memory
|
||||||
|
/// instead of taking a `std.io.Writer`.
|
||||||
|
///
|
||||||
|
/// Caller owns returned memory.
|
||||||
|
pub fn stringifyAlloc(
|
||||||
|
allocator: Allocator,
|
||||||
|
value: anytype,
|
||||||
|
options: StringifyOptions,
|
||||||
|
) error{OutOfMemory}![]const u8 {
|
||||||
|
var list = std.ArrayList(u8).init(allocator);
|
||||||
|
errdefer list.deinit();
|
||||||
|
try stringifyArbitraryDepth(allocator, value, options, list.writer());
|
||||||
|
return list.toOwnedSlice();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See `WriteStream` for documentation.
|
||||||
|
/// Equivalent to calling `writeStreamMaxDepth` with a depth of `256`.
|
||||||
|
///
|
||||||
|
/// The caller does *not* need to call `deinit()` on the returned object.
|
||||||
|
pub fn writeStream(
|
||||||
|
out_stream: anytype,
|
||||||
|
options: StringifyOptions,
|
||||||
|
) WriteStream(@TypeOf(out_stream), .{ .checked_to_fixed_depth = 256 }) {
|
||||||
|
return writeStreamMaxDepth(out_stream, options, 256);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See `WriteStream` for documentation.
|
||||||
|
/// The returned object includes 1 bit of size per `max_depth` to enable safety checks on the order of method calls;
|
||||||
|
/// see the grammar in the `WriteStream` documentation.
|
||||||
|
/// `max_depth` is rounded up to the nearest multiple of 8.
|
||||||
|
/// If the nesting depth exceeds `max_depth`, it is detectable illegal behavior.
|
||||||
|
/// Give `null` for `max_depth` to disable safety checks for the grammar and allow arbitrary nesting depth.
|
||||||
|
/// Alternatively, see `writeStreamArbitraryDepth` to do safety checks to arbitrary depth.
|
||||||
|
///
|
||||||
|
/// The caller does *not* need to call `deinit()` on the returned object.
|
||||||
|
pub fn writeStreamMaxDepth(
|
||||||
|
out_stream: anytype,
|
||||||
|
options: StringifyOptions,
|
||||||
|
comptime max_depth: ?usize,
|
||||||
|
) WriteStream(
|
||||||
|
@TypeOf(out_stream),
|
||||||
|
if (max_depth) |d| .{ .checked_to_fixed_depth = d } else .assumed_correct,
|
||||||
|
) {
|
||||||
|
return WriteStream(
|
||||||
|
@TypeOf(out_stream),
|
||||||
|
if (max_depth) |d| .{ .checked_to_fixed_depth = d } else .assumed_correct,
|
||||||
|
).init(undefined, out_stream, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See `WriteStream` for documentation.
|
||||||
|
/// This version of the write stream enables safety checks to arbitrarily deep nesting levels
|
||||||
|
/// by using the given allocator.
|
||||||
|
/// The caller should call `deinit()` on the returned object to free allocated memory.
|
||||||
|
pub fn writeStreamArbitraryDepth(
|
||||||
|
allocator: Allocator,
|
||||||
|
out_stream: anytype,
|
||||||
|
options: StringifyOptions,
|
||||||
|
) WriteStream(@TypeOf(out_stream), .checked_to_arbitrary_depth) {
|
||||||
|
return WriteStream(@TypeOf(out_stream), .checked_to_arbitrary_depth).init(allocator, out_stream, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes JSON ([RFC8259](https://tools.ietf.org/html/rfc8259)) formatted data
|
||||||
|
/// to a stream.
|
||||||
|
///
|
||||||
|
/// The seqeunce of method calls to write JSON content must follow this grammar:
|
||||||
|
/// ```
|
||||||
|
/// <once> = <value>
|
||||||
|
/// <value> =
|
||||||
|
/// | <object>
|
||||||
|
/// | <array>
|
||||||
|
/// | write
|
||||||
|
/// | writePreformatted
|
||||||
|
/// <object> = beginObject ( objectField <value> )* endObject
|
||||||
|
/// <array> = beginArray ( <value> )* endArray
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Supported types:
|
||||||
|
/// * Zig `bool` -> JSON `true` or `false`.
|
||||||
|
/// * Zig `?T` -> `null` or the rendering of `T`.
|
||||||
|
/// * Zig `i32`, `u64`, etc. -> JSON number or string.
|
||||||
|
/// * 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.
|
||||||
|
/// * Zig floats -> JSON number or string.
|
||||||
|
/// * If the value cannot be precisely represented by an f64, it is rendered as a JSON string. Otherwise, it is rendered as JSON number.
|
||||||
|
/// * TODO: Float rendering will likely change in the future, e.g. to remove the unnecessary "e+00".
|
||||||
|
/// * Zig `[]const u8`, `[]u8`, `*[N]u8`, `@Vector(N, u8)`, and similar -> JSON string.
|
||||||
|
/// * See `StringifyOptions.emit_strings_as_arrays`.
|
||||||
|
/// * If the content is not valid UTF-8, rendered as an array of numbers instead.
|
||||||
|
/// * Zig `[]T`, `[N]T`, `*[N]T`, `@Vector(N, T)`, and similar -> JSON array of the rendering of each item.
|
||||||
|
/// * Zig tuple -> JSON array of the rendering of each item.
|
||||||
|
/// * Zig `struct` -> JSON object with each field in declaration order.
|
||||||
|
/// * 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.
|
||||||
|
/// * See `StringifyOptions.emit_null_optional_fields`.
|
||||||
|
/// * Zig `union(enum)` -> JSON object with one field named for the active tag and a value representing the payload.
|
||||||
|
/// * If the payload is `void`, then the emitted value is `{}`.
|
||||||
|
/// * 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`.
|
||||||
|
/// * Zig `enum` -> JSON string naming the active tag.
|
||||||
|
/// * 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`.
|
||||||
|
/// * Zig error -> JSON string naming the error.
|
||||||
|
/// * Zig `*T` -> the rendering of `T`. Note there is no guard against circular-reference infinite recursion.
|
||||||
|
pub fn WriteStream(
|
||||||
|
comptime OutStream: type,
|
||||||
|
comptime safety_checks: union(enum) {
|
||||||
|
checked_to_arbitrary_depth,
|
||||||
|
checked_to_fixed_depth: usize, // Rounded up to the nearest multiple of 8.
|
||||||
|
assumed_correct,
|
||||||
|
},
|
||||||
|
) type {
|
||||||
|
return struct {
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub const Stream = OutStream;
|
||||||
|
pub const Error = switch (safety_checks) {
|
||||||
|
.checked_to_arbitrary_depth => Stream.Error || error{OutOfMemory},
|
||||||
|
.checked_to_fixed_depth, .assumed_correct => Stream.Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
options: StringifyOptions,
|
||||||
|
|
||||||
|
stream: OutStream,
|
||||||
|
indent_level: usize = 0,
|
||||||
|
next_punctuation: enum {
|
||||||
|
the_beginning,
|
||||||
|
none,
|
||||||
|
comma,
|
||||||
|
colon,
|
||||||
|
} = .the_beginning,
|
||||||
|
|
||||||
|
nesting_stack: switch (safety_checks) {
|
||||||
|
.checked_to_arbitrary_depth => BitStack,
|
||||||
|
.checked_to_fixed_depth => |fixed_buffer_size| [(fixed_buffer_size + 7) >> 3]u8,
|
||||||
|
.assumed_correct => void,
|
||||||
|
},
|
||||||
|
|
||||||
|
pub fn init(safety_allocator: Allocator, stream: OutStream, options: StringifyOptions) Self {
|
||||||
|
return .{
|
||||||
|
.options = options,
|
||||||
|
.stream = stream,
|
||||||
|
.nesting_stack = switch (safety_checks) {
|
||||||
|
.checked_to_arbitrary_depth => BitStack.init(safety_allocator),
|
||||||
|
.checked_to_fixed_depth => |fixed_buffer_size| [_]u8{0} ** ((fixed_buffer_size + 7) >> 3),
|
||||||
|
.assumed_correct => {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
switch (safety_checks) {
|
||||||
|
.checked_to_arbitrary_depth => self.nesting_stack.deinit(),
|
||||||
|
.checked_to_fixed_depth, .assumed_correct => {},
|
||||||
|
}
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn beginArray(self: *Self) Error!void {
|
||||||
|
try self.valueStart();
|
||||||
|
try self.stream.writeByte('[');
|
||||||
|
try self.pushIndentation(ARRAY_MODE);
|
||||||
|
self.next_punctuation = .none;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn beginObject(self: *Self) Error!void {
|
||||||
|
try self.valueStart();
|
||||||
|
try self.stream.writeByte('{');
|
||||||
|
try self.pushIndentation(OBJECT_MODE);
|
||||||
|
self.next_punctuation = .none;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn endArray(self: *Self) Error!void {
|
||||||
|
self.popIndentation(ARRAY_MODE);
|
||||||
|
switch (self.next_punctuation) {
|
||||||
|
.none => {},
|
||||||
|
.comma => {
|
||||||
|
try self.indent();
|
||||||
|
},
|
||||||
|
.the_beginning, .colon => unreachable,
|
||||||
|
}
|
||||||
|
try self.stream.writeByte(']');
|
||||||
|
self.valueDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn endObject(self: *Self) Error!void {
|
||||||
|
self.popIndentation(OBJECT_MODE);
|
||||||
|
switch (self.next_punctuation) {
|
||||||
|
.none => {},
|
||||||
|
.comma => {
|
||||||
|
try self.indent();
|
||||||
|
},
|
||||||
|
.the_beginning, .colon => unreachable,
|
||||||
|
}
|
||||||
|
try self.stream.writeByte('}');
|
||||||
|
self.valueDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pushIndentation(self: *Self, mode: u1) !void {
|
||||||
|
switch (safety_checks) {
|
||||||
|
.checked_to_arbitrary_depth => {
|
||||||
|
try self.nesting_stack.push(mode);
|
||||||
|
self.indent_level += 1;
|
||||||
|
},
|
||||||
|
.checked_to_fixed_depth => {
|
||||||
|
BitStack.pushWithStateAssumeCapacity(&self.nesting_stack, &self.indent_level, mode);
|
||||||
|
},
|
||||||
|
.assumed_correct => {
|
||||||
|
self.indent_level += 1;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn popIndentation(self: *Self, assert_its_this_one: u1) void {
|
||||||
|
switch (safety_checks) {
|
||||||
|
.checked_to_arbitrary_depth => {
|
||||||
|
assert(self.nesting_stack.pop() == assert_its_this_one);
|
||||||
|
self.indent_level -= 1;
|
||||||
|
},
|
||||||
|
.checked_to_fixed_depth => {
|
||||||
|
assert(BitStack.popWithState(&self.nesting_stack, &self.indent_level) == assert_its_this_one);
|
||||||
|
},
|
||||||
|
.assumed_correct => {
|
||||||
|
self.indent_level -= 1;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn indent(self: *Self) !void {
|
||||||
|
var char: u8 = ' ';
|
||||||
|
const n_chars = switch (self.options.whitespace) {
|
||||||
|
.minified => return,
|
||||||
|
.indent_1 => 1 * self.indent_level,
|
||||||
|
.indent_2 => 2 * self.indent_level,
|
||||||
|
.indent_3 => 3 * self.indent_level,
|
||||||
|
.indent_4 => 4 * self.indent_level,
|
||||||
|
.indent_8 => 8 * self.indent_level,
|
||||||
|
.indent_tab => blk: {
|
||||||
|
char = '\t';
|
||||||
|
break :blk self.indent_level;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
try self.stream.writeByte('\n');
|
||||||
|
try self.stream.writeByteNTimes(char, n_chars);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valueStart(self: *Self) !void {
|
||||||
|
if (self.isObjectKeyExpected()) |is_it| assert(!is_it); // Call objectField(), not write(), for object keys.
|
||||||
|
return self.valueStartAssumeTypeOk();
|
||||||
|
}
|
||||||
|
fn objectFieldStart(self: *Self) !void {
|
||||||
|
if (self.isObjectKeyExpected()) |is_it| assert(is_it); // Expected write(), not objectField().
|
||||||
|
return self.valueStartAssumeTypeOk();
|
||||||
|
}
|
||||||
|
fn valueStartAssumeTypeOk(self: *Self) !void {
|
||||||
|
assert(!self.isComplete()); // JSON document already complete.
|
||||||
|
switch (self.next_punctuation) {
|
||||||
|
.the_beginning => {
|
||||||
|
// No indentation for the very beginning.
|
||||||
|
},
|
||||||
|
.none => {
|
||||||
|
// First item in a container.
|
||||||
|
try self.indent();
|
||||||
|
},
|
||||||
|
.comma => {
|
||||||
|
// Subsequent item in a container.
|
||||||
|
try self.stream.writeByte(',');
|
||||||
|
try self.indent();
|
||||||
|
},
|
||||||
|
.colon => {
|
||||||
|
try self.stream.writeByte(':');
|
||||||
|
if (self.options.whitespace != .minified) {
|
||||||
|
try self.stream.writeByte(' ');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn valueDone(self: *Self) void {
|
||||||
|
self.next_punctuation = .comma;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only when safety is enabled:
|
||||||
|
fn isObjectKeyExpected(self: *const Self) ?bool {
|
||||||
|
switch (safety_checks) {
|
||||||
|
.checked_to_arbitrary_depth => return self.indent_level > 0 and
|
||||||
|
self.nesting_stack.peek() == OBJECT_MODE and
|
||||||
|
self.next_punctuation != .colon,
|
||||||
|
.checked_to_fixed_depth => return self.indent_level > 0 and
|
||||||
|
BitStack.peekWithState(&self.nesting_stack, self.indent_level) == OBJECT_MODE and
|
||||||
|
self.next_punctuation != .colon,
|
||||||
|
.assumed_correct => return null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn isComplete(self: *const Self) bool {
|
||||||
|
return self.indent_level == 0 and self.next_punctuation == .comma;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An alternative to calling `write` that outputs the given bytes verbatim.
|
||||||
|
/// This function does the usual punctuation and indentation formatting
|
||||||
|
/// assuming the given slice represents a single complete value;
|
||||||
|
/// e.g. `"1"`, `"[]"`, `"[1,2]"`, not `"1,2"`.
|
||||||
|
pub fn writePreformatted(self: *Self, value_slice: []const u8) Error!void {
|
||||||
|
try self.valueStart();
|
||||||
|
try self.stream.writeAll(value_slice);
|
||||||
|
self.valueDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn objectField(self: *Self, key: []const u8) Error!void {
|
||||||
|
try self.objectFieldStart();
|
||||||
|
try encodeJsonString(key, self.options, self.stream);
|
||||||
|
self.next_punctuation = .colon;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See `WriteStream`.
|
||||||
|
pub fn write(self: *Self, value: anytype) Error!void {
|
||||||
|
const T = @TypeOf(value);
|
||||||
|
switch (@typeInfo(T)) {
|
||||||
|
.Int => |info| {
|
||||||
|
if (info.bits < 53) {
|
||||||
|
try self.valueStart();
|
||||||
|
try self.stream.print("{}", .{value});
|
||||||
|
self.valueDone();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (value < 4503599627370496 and (info.signedness == .unsigned or value > -4503599627370496)) {
|
||||||
|
try self.valueStart();
|
||||||
|
try self.stream.print("{}", .{value});
|
||||||
|
self.valueDone();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try self.valueStart();
|
||||||
|
try self.stream.print("\"{}\"", .{value});
|
||||||
|
self.valueDone();
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
.ComptimeInt => {
|
||||||
|
return self.write(@as(std.math.IntFittingRange(value, value), value));
|
||||||
|
},
|
||||||
|
.Float, .ComptimeFloat => {
|
||||||
|
if (@as(f64, @floatCast(value)) == value) {
|
||||||
|
try self.valueStart();
|
||||||
|
try self.stream.print("{}", .{@as(f64, @floatCast(value))});
|
||||||
|
self.valueDone();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try self.valueStart();
|
||||||
|
try self.stream.print("\"{}\"", .{value});
|
||||||
|
self.valueDone();
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
|
||||||
|
.Bool => {
|
||||||
|
try self.valueStart();
|
||||||
|
try self.stream.writeAll(if (value) "true" else "false");
|
||||||
|
self.valueDone();
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
.Null => {
|
||||||
|
try self.valueStart();
|
||||||
|
try self.stream.writeAll("null");
|
||||||
|
self.valueDone();
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
.Optional => {
|
||||||
|
if (value) |payload| {
|
||||||
|
return try self.write(payload);
|
||||||
|
} else {
|
||||||
|
return try self.write(null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.Enum => {
|
||||||
|
if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {
|
||||||
|
return value.jsonStringify(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.stringValue(@tagName(value));
|
||||||
|
},
|
||||||
|
.Union => {
|
||||||
|
if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {
|
||||||
|
return value.jsonStringify(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
const info = @typeInfo(T).Union;
|
||||||
|
if (info.tag_type) |UnionTagType| {
|
||||||
|
try self.beginObject();
|
||||||
|
inline for (info.fields) |u_field| {
|
||||||
|
if (value == @field(UnionTagType, u_field.name)) {
|
||||||
|
try self.objectField(u_field.name);
|
||||||
|
if (u_field.type == void) {
|
||||||
|
// void value is {}
|
||||||
|
try self.beginObject();
|
||||||
|
try self.endObject();
|
||||||
|
} else {
|
||||||
|
try self.write(@field(value, u_field.name));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unreachable; // No active tag?
|
||||||
|
}
|
||||||
|
try self.endObject();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
@compileError("Unable to stringify untagged union '" ++ @typeName(T) ++ "'");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.Struct => |S| {
|
||||||
|
if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {
|
||||||
|
return value.jsonStringify(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (S.is_tuple) {
|
||||||
|
try self.beginArray();
|
||||||
|
} else {
|
||||||
|
try self.beginObject();
|
||||||
|
}
|
||||||
|
inline for (S.fields) |Field| {
|
||||||
|
// don't include void fields
|
||||||
|
if (Field.type == void) continue;
|
||||||
|
|
||||||
|
var emit_field = true;
|
||||||
|
|
||||||
|
// don't include optional fields that are null when emit_null_optional_fields is set to false
|
||||||
|
if (@typeInfo(Field.type) == .Optional) {
|
||||||
|
if (self.options.emit_null_optional_fields == false) {
|
||||||
|
if (@field(value, Field.name) == null) {
|
||||||
|
emit_field = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (emit_field) {
|
||||||
|
if (!S.is_tuple) {
|
||||||
|
try self.objectField(Field.name);
|
||||||
|
}
|
||||||
|
try self.write(@field(value, Field.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (S.is_tuple) {
|
||||||
|
try self.endArray();
|
||||||
|
} else {
|
||||||
|
try self.endObject();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
.ErrorSet => return self.stringValue(@errorName(value)),
|
||||||
|
.Pointer => |ptr_info| switch (ptr_info.size) {
|
||||||
|
.One => switch (@typeInfo(ptr_info.child)) {
|
||||||
|
.Array => {
|
||||||
|
// Coerce `*[N]T` to `[]const T`.
|
||||||
|
const Slice = []const std.meta.Elem(ptr_info.child);
|
||||||
|
return self.write(@as(Slice, value));
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
return self.write(value.*);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.Many, .Slice => {
|
||||||
|
if (ptr_info.size == .Many and ptr_info.sentinel == null)
|
||||||
|
@compileError("unable to stringify type '" ++ @typeName(T) ++ "' without sentinel");
|
||||||
|
const slice = if (ptr_info.size == .Many) std.mem.span(value) else value;
|
||||||
|
|
||||||
|
if (ptr_info.child == u8) {
|
||||||
|
// This is a []const u8, or some similar Zig string.
|
||||||
|
if (!self.options.emit_strings_as_arrays and std.unicode.utf8ValidateSlice(slice)) {
|
||||||
|
return self.stringValue(slice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.beginArray();
|
||||||
|
for (slice) |x| {
|
||||||
|
try self.write(x);
|
||||||
|
}
|
||||||
|
try self.endArray();
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"),
|
||||||
|
},
|
||||||
|
.Array => {
|
||||||
|
// Coerce `[N]T` to `*const [N]T` (and then to `[]const T`).
|
||||||
|
return self.write(&value);
|
||||||
|
},
|
||||||
|
.Vector => |info| {
|
||||||
|
const array: [info.len]info.child = value;
|
||||||
|
return self.write(&array);
|
||||||
|
},
|
||||||
|
else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"),
|
||||||
|
}
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stringValue(self: *Self, s: []const u8) !void {
|
||||||
|
try self.valueStart();
|
||||||
|
try encodeJsonString(s, self.options, self.stream);
|
||||||
|
self.valueDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const arrayElem = @compileError("Deprecated; You don't need to call this anymore.");
|
||||||
|
pub const emitNull = @compileError("Deprecated; Use .write(null) instead.");
|
||||||
|
pub const emitBool = @compileError("Deprecated; Use .write() instead.");
|
||||||
|
pub const emitNumber = @compileError("Deprecated; Use .write() instead.");
|
||||||
|
pub const emitString = @compileError("Deprecated; Use .write() instead.");
|
||||||
|
pub const emitJson = @compileError("Deprecated; Use .write() instead.");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn outputUnicodeEscape(codepoint: u21, out_stream: anytype) !void {
|
||||||
if (codepoint <= 0xFFFF) {
|
if (codepoint <= 0xFFFF) {
|
||||||
// If the character is in the Basic Multilingual Plane (U+0000 through U+FFFF),
|
// If the character is in the Basic Multilingual Plane (U+0000 through U+FFFF),
|
||||||
// then it may be represented as a six-character sequence: a reverse solidus, followed
|
// then it may be represented as a six-character sequence: a reverse solidus, followed
|
||||||
@ -87,6 +597,19 @@ fn outputUnicodeEscape(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn outputSpecialEscape(c: u8, writer: anytype) !void {
|
||||||
|
switch (c) {
|
||||||
|
'\\' => try writer.writeAll("\\\\"),
|
||||||
|
'\"' => try writer.writeAll("\\\""),
|
||||||
|
0x08 => try writer.writeAll("\\b"),
|
||||||
|
0x0C => try writer.writeAll("\\f"),
|
||||||
|
'\n' => try writer.writeAll("\\n"),
|
||||||
|
'\r' => try writer.writeAll("\\r"),
|
||||||
|
'\t' => try writer.writeAll("\\t"),
|
||||||
|
else => try outputUnicodeEscape(c, writer),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Write `string` to `writer` as a JSON encoded string.
|
/// Write `string` to `writer` as a JSON encoded string.
|
||||||
pub fn encodeJsonString(string: []const u8, options: StringifyOptions, writer: anytype) !void {
|
pub fn encodeJsonString(string: []const u8, options: StringifyOptions, writer: anytype) !void {
|
||||||
try writer.writeByte('\"');
|
try writer.writeByte('\"');
|
||||||
@ -96,218 +619,44 @@ pub fn encodeJsonString(string: []const u8, options: StringifyOptions, writer: a
|
|||||||
|
|
||||||
/// Write `chars` to `writer` as JSON encoded string characters.
|
/// Write `chars` to `writer` as JSON encoded string characters.
|
||||||
pub fn encodeJsonStringChars(chars: []const u8, options: StringifyOptions, writer: anytype) !void {
|
pub fn encodeJsonStringChars(chars: []const u8, options: StringifyOptions, writer: anytype) !void {
|
||||||
|
var write_cursor: usize = 0;
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
while (i < chars.len) : (i += 1) {
|
if (options.escape_unicode) {
|
||||||
switch (chars[i]) {
|
while (i < chars.len) : (i += 1) {
|
||||||
// normal ascii character
|
switch (chars[i]) {
|
||||||
0x20...0x21, 0x23...0x2E, 0x30...0x5B, 0x5D...0x7F => |c| try writer.writeByte(c),
|
// normal ascii character
|
||||||
// only 2 characters that *must* be escaped
|
0x20...0x21, 0x23...0x5B, 0x5D...0x7E => {},
|
||||||
'\\' => try writer.writeAll("\\\\"),
|
0x00...0x1F, '\\', '\"' => {
|
||||||
'\"' => try writer.writeAll("\\\""),
|
// Always must escape these.
|
||||||
// solidus is optional to escape
|
try writer.writeAll(chars[write_cursor..i]);
|
||||||
'/' => {
|
try outputSpecialEscape(chars[i], writer);
|
||||||
if (options.string.String.escape_solidus) {
|
write_cursor = i + 1;
|
||||||
try writer.writeAll("\\/");
|
},
|
||||||
} else {
|
0x7F...0xFF => {
|
||||||
try writer.writeByte('/');
|
try writer.writeAll(chars[write_cursor..i]);
|
||||||
}
|
const ulen = std.unicode.utf8ByteSequenceLength(chars[i]) catch unreachable;
|
||||||
},
|
|
||||||
// control characters with short escapes
|
|
||||||
// TODO: option to switch between unicode and 'short' forms?
|
|
||||||
0x8 => try writer.writeAll("\\b"),
|
|
||||||
0xC => try writer.writeAll("\\f"),
|
|
||||||
'\n' => try writer.writeAll("\\n"),
|
|
||||||
'\r' => try writer.writeAll("\\r"),
|
|
||||||
'\t' => try writer.writeAll("\\t"),
|
|
||||||
else => {
|
|
||||||
const ulen = std.unicode.utf8ByteSequenceLength(chars[i]) catch unreachable;
|
|
||||||
// control characters (only things left with 1 byte length) should always be printed as unicode escapes
|
|
||||||
if (ulen == 1 or options.string.String.escape_unicode) {
|
|
||||||
const codepoint = std.unicode.utf8Decode(chars[i..][0..ulen]) catch unreachable;
|
const codepoint = std.unicode.utf8Decode(chars[i..][0..ulen]) catch unreachable;
|
||||||
try outputUnicodeEscape(codepoint, writer);
|
try outputUnicodeEscape(codepoint, writer);
|
||||||
} else {
|
i += ulen - 1;
|
||||||
try writer.writeAll(chars[i..][0..ulen]);
|
write_cursor = i + 1;
|
||||||
}
|
},
|
||||||
i += ulen - 1;
|
}
|
||||||
},
|
}
|
||||||
|
} else {
|
||||||
|
while (i < chars.len) : (i += 1) {
|
||||||
|
switch (chars[i]) {
|
||||||
|
// normal bytes
|
||||||
|
0x20...0x21, 0x23...0x5B, 0x5D...0xFF => {},
|
||||||
|
0x00...0x1F, '\\', '\"' => {
|
||||||
|
// Always must escape these.
|
||||||
|
try writer.writeAll(chars[write_cursor..i]);
|
||||||
|
try outputSpecialEscape(chars[i], writer);
|
||||||
|
write_cursor = i + 1;
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
try writer.writeAll(chars[write_cursor..chars.len]);
|
||||||
|
|
||||||
/// If `value` has a method called `jsonStringify`, this will call that method instead of the
|
|
||||||
/// default implementation, passing it the `options` and `out_stream` parameters.
|
|
||||||
pub fn stringify(
|
|
||||||
value: anytype,
|
|
||||||
options: StringifyOptions,
|
|
||||||
out_stream: anytype,
|
|
||||||
) @TypeOf(out_stream).Error!void {
|
|
||||||
const T = @TypeOf(value);
|
|
||||||
switch (@typeInfo(T)) {
|
|
||||||
.Float, .ComptimeFloat => {
|
|
||||||
return std.fmt.formatFloatScientific(value, std.fmt.FormatOptions{}, out_stream);
|
|
||||||
},
|
|
||||||
.Int, .ComptimeInt => {
|
|
||||||
return std.fmt.formatIntValue(value, "", std.fmt.FormatOptions{}, out_stream);
|
|
||||||
},
|
|
||||||
.Bool => {
|
|
||||||
return out_stream.writeAll(if (value) "true" else "false");
|
|
||||||
},
|
|
||||||
.Null => {
|
|
||||||
return out_stream.writeAll("null");
|
|
||||||
},
|
|
||||||
.Optional => {
|
|
||||||
if (value) |payload| {
|
|
||||||
return try stringify(payload, options, out_stream);
|
|
||||||
} else {
|
|
||||||
return try stringify(null, options, out_stream);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.Enum => {
|
|
||||||
if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {
|
|
||||||
return value.jsonStringify(options, out_stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
return try encodeJsonString(@tagName(value), options, out_stream);
|
|
||||||
},
|
|
||||||
.Union => {
|
|
||||||
if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {
|
|
||||||
return value.jsonStringify(options, out_stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
const info = @typeInfo(T).Union;
|
|
||||||
if (info.tag_type) |UnionTagType| {
|
|
||||||
try out_stream.writeByte('{');
|
|
||||||
var child_options = options;
|
|
||||||
child_options.whitespace.indent_level += 1;
|
|
||||||
inline for (info.fields) |u_field| {
|
|
||||||
if (value == @field(UnionTagType, u_field.name)) {
|
|
||||||
try child_options.whitespace.outputIndent(out_stream);
|
|
||||||
try encodeJsonString(u_field.name, options, out_stream);
|
|
||||||
try out_stream.writeByte(':');
|
|
||||||
if (child_options.whitespace.separator) {
|
|
||||||
try out_stream.writeByte(' ');
|
|
||||||
}
|
|
||||||
if (u_field.type == void) {
|
|
||||||
try out_stream.writeAll("{}");
|
|
||||||
} else {
|
|
||||||
try stringify(@field(value, u_field.name), child_options, out_stream);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
unreachable; // No active tag?
|
|
||||||
}
|
|
||||||
try options.whitespace.outputIndent(out_stream);
|
|
||||||
try out_stream.writeByte('}');
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
@compileError("Unable to stringify untagged union '" ++ @typeName(T) ++ "'");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.Struct => |S| {
|
|
||||||
if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {
|
|
||||||
return value.jsonStringify(options, out_stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
try out_stream.writeByte(if (S.is_tuple) '[' else '{');
|
|
||||||
var field_output = false;
|
|
||||||
var child_options = options;
|
|
||||||
child_options.whitespace.indent_level += 1;
|
|
||||||
inline for (S.fields) |Field| {
|
|
||||||
// don't include void fields
|
|
||||||
if (Field.type == void) continue;
|
|
||||||
|
|
||||||
var emit_field = true;
|
|
||||||
|
|
||||||
// don't include optional fields that are null when emit_null_optional_fields is set to false
|
|
||||||
if (@typeInfo(Field.type) == .Optional) {
|
|
||||||
if (options.emit_null_optional_fields == false) {
|
|
||||||
if (@field(value, Field.name) == null) {
|
|
||||||
emit_field = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (emit_field) {
|
|
||||||
if (!field_output) {
|
|
||||||
field_output = true;
|
|
||||||
} else {
|
|
||||||
try out_stream.writeByte(',');
|
|
||||||
}
|
|
||||||
try child_options.whitespace.outputIndent(out_stream);
|
|
||||||
if (!S.is_tuple) {
|
|
||||||
try encodeJsonString(Field.name, options, out_stream);
|
|
||||||
try out_stream.writeByte(':');
|
|
||||||
if (child_options.whitespace.separator) {
|
|
||||||
try out_stream.writeByte(' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try stringify(@field(value, Field.name), child_options, out_stream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (field_output) {
|
|
||||||
try options.whitespace.outputIndent(out_stream);
|
|
||||||
}
|
|
||||||
try out_stream.writeByte(if (S.is_tuple) ']' else '}');
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
.ErrorSet => return stringify(@as([]const u8, @errorName(value)), options, out_stream),
|
|
||||||
.Pointer => |ptr_info| switch (ptr_info.size) {
|
|
||||||
.One => switch (@typeInfo(ptr_info.child)) {
|
|
||||||
.Array => {
|
|
||||||
const Slice = []const std.meta.Elem(ptr_info.child);
|
|
||||||
return stringify(@as(Slice, value), options, out_stream);
|
|
||||||
},
|
|
||||||
else => {
|
|
||||||
// TODO: avoid loops?
|
|
||||||
return stringify(value.*, options, out_stream);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
.Many, .Slice => {
|
|
||||||
if (ptr_info.size == .Many and ptr_info.sentinel == null)
|
|
||||||
@compileError("unable to stringify type '" ++ @typeName(T) ++ "' without sentinel");
|
|
||||||
const slice = if (ptr_info.size == .Many) mem.span(value) else value;
|
|
||||||
|
|
||||||
if (ptr_info.child == u8 and options.string == .String and std.unicode.utf8ValidateSlice(slice)) {
|
|
||||||
try encodeJsonString(slice, options, out_stream);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try out_stream.writeByte('[');
|
|
||||||
var child_options = options;
|
|
||||||
child_options.whitespace.indent_level += 1;
|
|
||||||
for (slice, 0..) |x, i| {
|
|
||||||
if (i != 0) {
|
|
||||||
try out_stream.writeByte(',');
|
|
||||||
}
|
|
||||||
try child_options.whitespace.outputIndent(out_stream);
|
|
||||||
try stringify(x, child_options, out_stream);
|
|
||||||
}
|
|
||||||
if (slice.len != 0) {
|
|
||||||
try options.whitespace.outputIndent(out_stream);
|
|
||||||
}
|
|
||||||
try out_stream.writeByte(']');
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"),
|
|
||||||
},
|
|
||||||
.Array => return stringify(&value, options, out_stream),
|
|
||||||
.Vector => |info| {
|
|
||||||
const array: [info.len]info.child = value;
|
|
||||||
return stringify(&array, options, out_stream);
|
|
||||||
},
|
|
||||||
else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"),
|
|
||||||
}
|
|
||||||
unreachable;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Same as `stringify` but accepts an Allocator and stores result in dynamically allocated memory instead of using a Writer.
|
|
||||||
// Caller owns returned memory.
|
|
||||||
pub fn stringifyAlloc(allocator: std.mem.Allocator, value: anytype, options: StringifyOptions) ![]const u8 {
|
|
||||||
var list = std.ArrayList(u8).init(allocator);
|
|
||||||
errdefer list.deinit();
|
|
||||||
try stringify(value, options, list.writer());
|
|
||||||
return list.toOwnedSlice();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
|
|||||||
@ -2,9 +2,99 @@ const std = @import("std");
|
|||||||
const mem = std.mem;
|
const mem = std.mem;
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
|
const ObjectMap = @import("dynamic.zig").ObjectMap;
|
||||||
|
const Value = @import("dynamic.zig").Value;
|
||||||
|
|
||||||
const StringifyOptions = @import("stringify.zig").StringifyOptions;
|
const StringifyOptions = @import("stringify.zig").StringifyOptions;
|
||||||
const stringify = @import("stringify.zig").stringify;
|
const stringify = @import("stringify.zig").stringify;
|
||||||
|
const stringifyMaxDepth = @import("stringify.zig").stringifyMaxDepth;
|
||||||
|
const stringifyArbitraryDepth = @import("stringify.zig").stringifyArbitraryDepth;
|
||||||
const stringifyAlloc = @import("stringify.zig").stringifyAlloc;
|
const stringifyAlloc = @import("stringify.zig").stringifyAlloc;
|
||||||
|
const writeStream = @import("stringify.zig").writeStream;
|
||||||
|
const writeStreamMaxDepth = @import("stringify.zig").writeStreamMaxDepth;
|
||||||
|
const writeStreamArbitraryDepth = @import("stringify.zig").writeStreamArbitraryDepth;
|
||||||
|
|
||||||
|
test "json write stream" {
|
||||||
|
var out_buf: [1024]u8 = undefined;
|
||||||
|
var slice_stream = std.io.fixedBufferStream(&out_buf);
|
||||||
|
const out = slice_stream.writer();
|
||||||
|
|
||||||
|
{
|
||||||
|
var w = writeStream(out, .{ .whitespace = .indent_2 });
|
||||||
|
try testBasicWriteStream(&w, &slice_stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var w = writeStreamMaxDepth(out, .{ .whitespace = .indent_2 }, 8);
|
||||||
|
try testBasicWriteStream(&w, &slice_stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var w = writeStreamMaxDepth(out, .{ .whitespace = .indent_2 }, null);
|
||||||
|
try testBasicWriteStream(&w, &slice_stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var w = writeStreamArbitraryDepth(testing.allocator, out, .{ .whitespace = .indent_2 });
|
||||||
|
defer w.deinit();
|
||||||
|
try testBasicWriteStream(&w, &slice_stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn testBasicWriteStream(w: anytype, slice_stream: anytype) !void {
|
||||||
|
slice_stream.reset();
|
||||||
|
|
||||||
|
try w.beginObject();
|
||||||
|
|
||||||
|
try w.objectField("object");
|
||||||
|
var arena_allocator = std.heap.ArenaAllocator.init(testing.allocator);
|
||||||
|
defer arena_allocator.deinit();
|
||||||
|
try w.write(try getJsonObject(arena_allocator.allocator()));
|
||||||
|
|
||||||
|
try w.objectField("string");
|
||||||
|
try w.write("This is a string");
|
||||||
|
|
||||||
|
try w.objectField("array");
|
||||||
|
try w.beginArray();
|
||||||
|
try w.write("Another string");
|
||||||
|
try w.write(@as(i32, 1));
|
||||||
|
try w.write(@as(f32, 3.5));
|
||||||
|
try w.endArray();
|
||||||
|
|
||||||
|
try w.objectField("int");
|
||||||
|
try w.write(@as(i32, 10));
|
||||||
|
|
||||||
|
try w.objectField("float");
|
||||||
|
try w.write(@as(f32, 3.5));
|
||||||
|
|
||||||
|
try w.endObject();
|
||||||
|
|
||||||
|
const result = slice_stream.getWritten();
|
||||||
|
const expected =
|
||||||
|
\\{
|
||||||
|
\\ "object": {
|
||||||
|
\\ "one": 1,
|
||||||
|
\\ "two": 2.0e+00
|
||||||
|
\\ },
|
||||||
|
\\ "string": "This is a string",
|
||||||
|
\\ "array": [
|
||||||
|
\\ "Another string",
|
||||||
|
\\ 1,
|
||||||
|
\\ 3.5e+00
|
||||||
|
\\ ],
|
||||||
|
\\ "int": 10,
|
||||||
|
\\ "float": 3.5e+00
|
||||||
|
\\}
|
||||||
|
;
|
||||||
|
try std.testing.expectEqualStrings(expected, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getJsonObject(allocator: std.mem.Allocator) !Value {
|
||||||
|
var value = Value{ .object = ObjectMap.init(allocator) };
|
||||||
|
try value.object.put("one", Value{ .integer = @as(i64, @intCast(1)) });
|
||||||
|
try value.object.put("two", Value{ .float = 2.0 });
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
test "stringify null optional fields" {
|
test "stringify null optional fields" {
|
||||||
const MyStruct = struct {
|
const MyStruct = struct {
|
||||||
@ -13,64 +103,63 @@ test "stringify null optional fields" {
|
|||||||
another_optional: ?[]const u8 = null,
|
another_optional: ?[]const u8 = null,
|
||||||
another_required: []const u8 = "something else",
|
another_required: []const u8 = "something else",
|
||||||
};
|
};
|
||||||
try teststringify(
|
try testStringify(
|
||||||
\\{"optional":null,"required":"something","another_optional":null,"another_required":"something else"}
|
\\{"optional":null,"required":"something","another_optional":null,"another_required":"something else"}
|
||||||
,
|
,
|
||||||
MyStruct{},
|
MyStruct{},
|
||||||
StringifyOptions{},
|
.{},
|
||||||
);
|
);
|
||||||
try teststringify(
|
try testStringify(
|
||||||
\\{"required":"something","another_required":"something else"}
|
\\{"required":"something","another_required":"something else"}
|
||||||
,
|
,
|
||||||
MyStruct{},
|
MyStruct{},
|
||||||
StringifyOptions{ .emit_null_optional_fields = false },
|
.{ .emit_null_optional_fields = false },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "stringify basic types" {
|
test "stringify basic types" {
|
||||||
try teststringify("false", false, StringifyOptions{});
|
try testStringify("false", false, .{});
|
||||||
try teststringify("true", true, StringifyOptions{});
|
try testStringify("true", true, .{});
|
||||||
try teststringify("null", @as(?u8, null), StringifyOptions{});
|
try testStringify("null", @as(?u8, null), .{});
|
||||||
try teststringify("null", @as(?*u32, null), StringifyOptions{});
|
try testStringify("null", @as(?*u32, null), .{});
|
||||||
try teststringify("42", 42, StringifyOptions{});
|
try testStringify("42", 42, .{});
|
||||||
try teststringify("4.2e+01", 42.0, StringifyOptions{});
|
try testStringify("4.2e+01", 42.0, .{});
|
||||||
try teststringify("42", @as(u8, 42), StringifyOptions{});
|
try testStringify("42", @as(u8, 42), .{});
|
||||||
try teststringify("42", @as(u128, 42), StringifyOptions{});
|
try testStringify("42", @as(u128, 42), .{});
|
||||||
try teststringify("4.2e+01", @as(f32, 42), StringifyOptions{});
|
try testStringify("4.2e+01", @as(f32, 42), .{});
|
||||||
try teststringify("4.2e+01", @as(f64, 42), StringifyOptions{});
|
try testStringify("4.2e+01", @as(f64, 42), .{});
|
||||||
try teststringify("\"ItBroke\"", @as(anyerror, error.ItBroke), StringifyOptions{});
|
try testStringify("\"ItBroke\"", @as(anyerror, error.ItBroke), .{});
|
||||||
|
try testStringify("\"ItBroke\"", error.ItBroke, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
test "stringify string" {
|
test "stringify string" {
|
||||||
try teststringify("\"hello\"", "hello", StringifyOptions{});
|
try testStringify("\"hello\"", "hello", .{});
|
||||||
try teststringify("\"with\\nescapes\\r\"", "with\nescapes\r", StringifyOptions{});
|
try testStringify("\"with\\nescapes\\r\"", "with\nescapes\r", .{});
|
||||||
try teststringify("\"with\\nescapes\\r\"", "with\nescapes\r", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
|
try testStringify("\"with\\nescapes\\r\"", "with\nescapes\r", .{ .escape_unicode = true });
|
||||||
try teststringify("\"with unicode\\u0001\"", "with unicode\u{1}", StringifyOptions{});
|
try testStringify("\"with unicode\\u0001\"", "with unicode\u{1}", .{});
|
||||||
try teststringify("\"with unicode\\u0001\"", "with unicode\u{1}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
|
try testStringify("\"with unicode\\u0001\"", "with unicode\u{1}", .{ .escape_unicode = true });
|
||||||
try teststringify("\"with unicode\u{80}\"", "with unicode\u{80}", StringifyOptions{});
|
try testStringify("\"with unicode\u{80}\"", "with unicode\u{80}", .{});
|
||||||
try teststringify("\"with unicode\\u0080\"", "with unicode\u{80}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
|
try testStringify("\"with unicode\\u0080\"", "with unicode\u{80}", .{ .escape_unicode = true });
|
||||||
try teststringify("\"with unicode\u{FF}\"", "with unicode\u{FF}", StringifyOptions{});
|
try testStringify("\"with unicode\u{FF}\"", "with unicode\u{FF}", .{});
|
||||||
try teststringify("\"with unicode\\u00ff\"", "with unicode\u{FF}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
|
try testStringify("\"with unicode\\u00ff\"", "with unicode\u{FF}", .{ .escape_unicode = true });
|
||||||
try teststringify("\"with unicode\u{100}\"", "with unicode\u{100}", StringifyOptions{});
|
try testStringify("\"with unicode\u{100}\"", "with unicode\u{100}", .{});
|
||||||
try teststringify("\"with unicode\\u0100\"", "with unicode\u{100}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
|
try testStringify("\"with unicode\\u0100\"", "with unicode\u{100}", .{ .escape_unicode = true });
|
||||||
try teststringify("\"with unicode\u{800}\"", "with unicode\u{800}", StringifyOptions{});
|
try testStringify("\"with unicode\u{800}\"", "with unicode\u{800}", .{});
|
||||||
try teststringify("\"with unicode\\u0800\"", "with unicode\u{800}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
|
try testStringify("\"with unicode\\u0800\"", "with unicode\u{800}", .{ .escape_unicode = true });
|
||||||
try teststringify("\"with unicode\u{8000}\"", "with unicode\u{8000}", StringifyOptions{});
|
try testStringify("\"with unicode\u{8000}\"", "with unicode\u{8000}", .{});
|
||||||
try teststringify("\"with unicode\\u8000\"", "with unicode\u{8000}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
|
try testStringify("\"with unicode\\u8000\"", "with unicode\u{8000}", .{ .escape_unicode = true });
|
||||||
try teststringify("\"with unicode\u{D799}\"", "with unicode\u{D799}", StringifyOptions{});
|
try testStringify("\"with unicode\u{D799}\"", "with unicode\u{D799}", .{});
|
||||||
try teststringify("\"with unicode\\ud799\"", "with unicode\u{D799}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
|
try testStringify("\"with unicode\\ud799\"", "with unicode\u{D799}", .{ .escape_unicode = true });
|
||||||
try teststringify("\"with unicode\u{10000}\"", "with unicode\u{10000}", StringifyOptions{});
|
try testStringify("\"with unicode\u{10000}\"", "with unicode\u{10000}", .{});
|
||||||
try teststringify("\"with unicode\\ud800\\udc00\"", "with unicode\u{10000}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
|
try testStringify("\"with unicode\\ud800\\udc00\"", "with unicode\u{10000}", .{ .escape_unicode = true });
|
||||||
try teststringify("\"with unicode\u{10FFFF}\"", "with unicode\u{10FFFF}", StringifyOptions{});
|
try testStringify("\"with unicode\u{10FFFF}\"", "with unicode\u{10FFFF}", .{});
|
||||||
try teststringify("\"with unicode\\udbff\\udfff\"", "with unicode\u{10FFFF}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
|
try testStringify("\"with unicode\\udbff\\udfff\"", "with unicode\u{10FFFF}", .{ .escape_unicode = true });
|
||||||
try teststringify("\"/\"", "/", StringifyOptions{});
|
|
||||||
try teststringify("\"\\/\"", "/", StringifyOptions{ .string = .{ .String = .{ .escape_solidus = true } } });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test "stringify many-item sentinel-terminated string" {
|
test "stringify many-item sentinel-terminated string" {
|
||||||
try teststringify("\"hello\"", @as([*:0]const u8, "hello"), StringifyOptions{});
|
try testStringify("\"hello\"", @as([*:0]const u8, "hello"), .{});
|
||||||
try teststringify("\"with\\nescapes\\r\"", @as([*:0]const u8, "with\nescapes\r"), StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
|
try testStringify("\"with\\nescapes\\r\"", @as([*:0]const u8, "with\nescapes\r"), .{ .escape_unicode = true });
|
||||||
try teststringify("\"with unicode\\u0001\"", @as([*:0]const u8, "with unicode\u{1}"), StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
|
try testStringify("\"with unicode\\u0001\"", @as([*:0]const u8, "with unicode\u{1}"), .{ .escape_unicode = true });
|
||||||
}
|
}
|
||||||
|
|
||||||
test "stringify enums" {
|
test "stringify enums" {
|
||||||
@ -78,8 +167,8 @@ test "stringify enums" {
|
|||||||
foo,
|
foo,
|
||||||
bar,
|
bar,
|
||||||
};
|
};
|
||||||
try teststringify("\"foo\"", E.foo, .{});
|
try testStringify("\"foo\"", E.foo, .{});
|
||||||
try teststringify("\"bar\"", E.bar, .{});
|
try testStringify("\"bar\"", E.bar, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
test "stringify tagged unions" {
|
test "stringify tagged unions" {
|
||||||
@ -88,24 +177,33 @@ test "stringify tagged unions" {
|
|||||||
foo: u32,
|
foo: u32,
|
||||||
bar: bool,
|
bar: bool,
|
||||||
};
|
};
|
||||||
try teststringify("{\"nothing\":{}}", T{ .nothing = {} }, StringifyOptions{});
|
try testStringify("{\"nothing\":{}}", T{ .nothing = {} }, .{});
|
||||||
try teststringify("{\"foo\":42}", T{ .foo = 42 }, StringifyOptions{});
|
try testStringify("{\"foo\":42}", T{ .foo = 42 }, .{});
|
||||||
try teststringify("{\"bar\":true}", T{ .bar = true }, StringifyOptions{});
|
try testStringify("{\"bar\":true}", T{ .bar = true }, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
test "stringify struct" {
|
test "stringify struct" {
|
||||||
try teststringify("{\"foo\":42}", struct {
|
try testStringify("{\"foo\":42}", struct {
|
||||||
foo: u32,
|
foo: u32,
|
||||||
}{ .foo = 42 }, StringifyOptions{});
|
}{ .foo = 42 }, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
test "stringify struct with string as array" {
|
test "emit_strings_as_arrays" {
|
||||||
try teststringify("{\"foo\":\"bar\"}", .{ .foo = "bar" }, StringifyOptions{});
|
// Should only affect string values, not object keys.
|
||||||
try teststringify("{\"foo\":[98,97,114]}", .{ .foo = "bar" }, StringifyOptions{ .string = .Array });
|
try testStringify("{\"foo\":\"bar\"}", .{ .foo = "bar" }, .{});
|
||||||
|
try testStringify("{\"foo\":[98,97,114]}", .{ .foo = "bar" }, .{ .emit_strings_as_arrays = true });
|
||||||
|
// Should *not* affect these types:
|
||||||
|
try testStringify("\"foo\"", @as(enum { foo, bar }, .foo), .{ .emit_strings_as_arrays = true });
|
||||||
|
try testStringify("\"ItBroke\"", error.ItBroke, .{ .emit_strings_as_arrays = true });
|
||||||
|
// Should work on these:
|
||||||
|
try testStringify("\"bar\"", @Vector(3, u8){ 'b', 'a', 'r' }, .{});
|
||||||
|
try testStringify("[98,97,114]", @Vector(3, u8){ 'b', 'a', 'r' }, .{ .emit_strings_as_arrays = true });
|
||||||
|
try testStringify("\"bar\"", [3]u8{ 'b', 'a', 'r' }, .{});
|
||||||
|
try testStringify("[98,97,114]", [3]u8{ 'b', 'a', 'r' }, .{ .emit_strings_as_arrays = true });
|
||||||
}
|
}
|
||||||
|
|
||||||
test "stringify struct with indentation" {
|
test "stringify struct with indentation" {
|
||||||
try teststringify(
|
try testStringify(
|
||||||
\\{
|
\\{
|
||||||
\\ "foo": 42,
|
\\ "foo": 42,
|
||||||
\\ "bar": [
|
\\ "bar": [
|
||||||
@ -122,12 +220,10 @@ test "stringify struct with indentation" {
|
|||||||
.foo = 42,
|
.foo = 42,
|
||||||
.bar = .{ 1, 2, 3 },
|
.bar = .{ 1, 2, 3 },
|
||||||
},
|
},
|
||||||
StringifyOptions{
|
.{ .whitespace = .indent_4 },
|
||||||
.whitespace = .{},
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
try teststringify(
|
try testStringify(
|
||||||
"{\n\t\"foo\":42,\n\t\"bar\":[\n\t\t1,\n\t\t2,\n\t\t3\n\t]\n}",
|
"{\n\t\"foo\": 42,\n\t\"bar\": [\n\t\t1,\n\t\t2,\n\t\t3\n\t]\n}",
|
||||||
struct {
|
struct {
|
||||||
foo: u32,
|
foo: u32,
|
||||||
bar: [3]u32,
|
bar: [3]u32,
|
||||||
@ -135,14 +231,9 @@ test "stringify struct with indentation" {
|
|||||||
.foo = 42,
|
.foo = 42,
|
||||||
.bar = .{ 1, 2, 3 },
|
.bar = .{ 1, 2, 3 },
|
||||||
},
|
},
|
||||||
StringifyOptions{
|
.{ .whitespace = .indent_tab },
|
||||||
.whitespace = .{
|
|
||||||
.indent = .tab,
|
|
||||||
.separator = false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
try teststringify(
|
try testStringify(
|
||||||
\\{"foo":42,"bar":[1,2,3]}
|
\\{"foo":42,"bar":[1,2,3]}
|
||||||
,
|
,
|
||||||
struct {
|
struct {
|
||||||
@ -152,59 +243,53 @@ test "stringify struct with indentation" {
|
|||||||
.foo = 42,
|
.foo = 42,
|
||||||
.bar = .{ 1, 2, 3 },
|
.bar = .{ 1, 2, 3 },
|
||||||
},
|
},
|
||||||
StringifyOptions{
|
.{ .whitespace = .minified },
|
||||||
.whitespace = .{
|
|
||||||
.indent = .none,
|
|
||||||
.separator = false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "stringify struct with void field" {
|
test "stringify struct with void field" {
|
||||||
try teststringify("{\"foo\":42}", struct {
|
try testStringify("{\"foo\":42}", struct {
|
||||||
foo: u32,
|
foo: u32,
|
||||||
bar: void = {},
|
bar: void = {},
|
||||||
}{ .foo = 42 }, StringifyOptions{});
|
}{ .foo = 42 }, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
test "stringify array of structs" {
|
test "stringify array of structs" {
|
||||||
const MyStruct = struct {
|
const MyStruct = struct {
|
||||||
foo: u32,
|
foo: u32,
|
||||||
};
|
};
|
||||||
try teststringify("[{\"foo\":42},{\"foo\":100},{\"foo\":1000}]", [_]MyStruct{
|
try testStringify("[{\"foo\":42},{\"foo\":100},{\"foo\":1000}]", [_]MyStruct{
|
||||||
MyStruct{ .foo = 42 },
|
MyStruct{ .foo = 42 },
|
||||||
MyStruct{ .foo = 100 },
|
MyStruct{ .foo = 100 },
|
||||||
MyStruct{ .foo = 1000 },
|
MyStruct{ .foo = 1000 },
|
||||||
}, StringifyOptions{});
|
}, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
test "stringify struct with custom stringifier" {
|
test "stringify struct with custom stringifier" {
|
||||||
try teststringify("[\"something special\",42]", struct {
|
try testStringify("[\"something special\",42]", struct {
|
||||||
foo: u32,
|
foo: u32,
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
pub fn jsonStringify(
|
pub fn jsonStringify(value: @This(), jws: anytype) !void {
|
||||||
value: Self,
|
|
||||||
options: StringifyOptions,
|
|
||||||
out_stream: anytype,
|
|
||||||
) !void {
|
|
||||||
_ = value;
|
_ = value;
|
||||||
try out_stream.writeAll("[\"something special\",");
|
try jws.beginArray();
|
||||||
try stringify(42, options, out_stream);
|
try jws.write("something special");
|
||||||
try out_stream.writeByte(']');
|
try jws.write(42);
|
||||||
|
try jws.endArray();
|
||||||
}
|
}
|
||||||
}{ .foo = 42 }, StringifyOptions{});
|
}{ .foo = 42 }, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
test "stringify vector" {
|
test "stringify vector" {
|
||||||
try teststringify("[1,1]", @as(@Vector(2, u32), @splat(1)), StringifyOptions{});
|
try testStringify("[1,1]", @as(@Vector(2, u32), @splat(1)), .{});
|
||||||
|
try testStringify("\"AA\"", @as(@Vector(2, u8), @splat('A')), .{});
|
||||||
|
try testStringify("[65,65]", @as(@Vector(2, u8), @splat('A')), .{ .emit_strings_as_arrays = true });
|
||||||
}
|
}
|
||||||
|
|
||||||
test "stringify tuple" {
|
test "stringify tuple" {
|
||||||
try teststringify("[\"foo\",42]", std.meta.Tuple(&.{ []const u8, usize }){ "foo", 42 }, StringifyOptions{});
|
try testStringify("[\"foo\",42]", std.meta.Tuple(&.{ []const u8, usize }){ "foo", 42 }, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn teststringify(expected: []const u8, value: anytype, options: StringifyOptions) !void {
|
fn testStringify(expected: []const u8, value: anytype, options: StringifyOptions) !void {
|
||||||
const ValidationWriter = struct {
|
const ValidationWriter = struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
pub const Writer = std.io.Writer(*Self, Error, write);
|
pub const Writer = std.io.Writer(*Self, Error, write);
|
||||||
@ -256,8 +341,34 @@ fn teststringify(expected: []const u8, value: anytype, options: StringifyOptions
|
|||||||
};
|
};
|
||||||
|
|
||||||
var vos = ValidationWriter.init(expected);
|
var vos = ValidationWriter.init(expected);
|
||||||
try stringify(value, options, vos.writer());
|
try stringifyArbitraryDepth(testing.allocator, value, options, vos.writer());
|
||||||
if (vos.expected_remaining.len > 0) return error.NotEnoughData;
|
if (vos.expected_remaining.len > 0) return error.NotEnoughData;
|
||||||
|
|
||||||
|
// Also test with safety disabled.
|
||||||
|
try testStringifyMaxDepth(expected, value, options, null);
|
||||||
|
try testStringifyArbitraryDepth(expected, value, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn testStringifyMaxDepth(expected: []const u8, value: anytype, options: StringifyOptions, comptime max_depth: ?usize) !void {
|
||||||
|
var out_buf: [1024]u8 = undefined;
|
||||||
|
var slice_stream = std.io.fixedBufferStream(&out_buf);
|
||||||
|
const out = slice_stream.writer();
|
||||||
|
|
||||||
|
try stringifyMaxDepth(value, options, out, max_depth);
|
||||||
|
const got = slice_stream.getWritten();
|
||||||
|
|
||||||
|
try testing.expectEqualStrings(expected, got);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn testStringifyArbitraryDepth(expected: []const u8, value: anytype, options: StringifyOptions) !void {
|
||||||
|
var out_buf: [1024]u8 = undefined;
|
||||||
|
var slice_stream = std.io.fixedBufferStream(&out_buf);
|
||||||
|
const out = slice_stream.writer();
|
||||||
|
|
||||||
|
try stringifyArbitraryDepth(testing.allocator, value, options, out);
|
||||||
|
const got = slice_stream.getWritten();
|
||||||
|
|
||||||
|
try testing.expectEqualStrings(expected, got);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "stringify alloc" {
|
test "stringify alloc" {
|
||||||
@ -270,3 +381,54 @@ test "stringify alloc" {
|
|||||||
|
|
||||||
try std.testing.expectEqualStrings(expected, actual);
|
try std.testing.expectEqualStrings(expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "comptime stringify" {
|
||||||
|
comptime testStringifyMaxDepth("false", false, .{}, null) catch unreachable;
|
||||||
|
comptime testStringifyMaxDepth("false", false, .{}, 0) catch unreachable;
|
||||||
|
comptime testStringifyArbitraryDepth("false", false, .{}) catch unreachable;
|
||||||
|
|
||||||
|
const MyStruct = struct {
|
||||||
|
foo: u32,
|
||||||
|
};
|
||||||
|
comptime testStringifyMaxDepth("[{\"foo\":42},{\"foo\":100},{\"foo\":1000}]", [_]MyStruct{
|
||||||
|
MyStruct{ .foo = 42 },
|
||||||
|
MyStruct{ .foo = 100 },
|
||||||
|
MyStruct{ .foo = 1000 },
|
||||||
|
}, .{}, null) catch unreachable;
|
||||||
|
comptime testStringifyMaxDepth("[{\"foo\":42},{\"foo\":100},{\"foo\":1000}]", [_]MyStruct{
|
||||||
|
MyStruct{ .foo = 42 },
|
||||||
|
MyStruct{ .foo = 100 },
|
||||||
|
MyStruct{ .foo = 1000 },
|
||||||
|
}, .{}, 8) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
test "writePreformatted" {
|
||||||
|
var out_buf: [1024]u8 = undefined;
|
||||||
|
var slice_stream = std.io.fixedBufferStream(&out_buf);
|
||||||
|
const out = slice_stream.writer();
|
||||||
|
|
||||||
|
var w = writeStream(out, .{ .whitespace = .indent_2 });
|
||||||
|
defer w.deinit();
|
||||||
|
|
||||||
|
try w.beginObject();
|
||||||
|
try w.objectField("a");
|
||||||
|
try w.writePreformatted("[ ]");
|
||||||
|
try w.objectField("b");
|
||||||
|
try w.beginArray();
|
||||||
|
try w.writePreformatted("[[]] ");
|
||||||
|
try w.writePreformatted(" {}");
|
||||||
|
try w.endArray();
|
||||||
|
try w.endObject();
|
||||||
|
|
||||||
|
const result = slice_stream.getWritten();
|
||||||
|
const expected =
|
||||||
|
\\{
|
||||||
|
\\ "a": [ ],
|
||||||
|
\\ "b": [
|
||||||
|
\\ [[]] ,
|
||||||
|
\\ {}
|
||||||
|
\\ ]
|
||||||
|
\\}
|
||||||
|
;
|
||||||
|
try std.testing.expectEqualStrings(expected, result);
|
||||||
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ const parseFromSlice = @import("./static.zig").parseFromSlice;
|
|||||||
const validate = @import("./scanner.zig").validate;
|
const validate = @import("./scanner.zig").validate;
|
||||||
const JsonScanner = @import("./scanner.zig").Scanner;
|
const JsonScanner = @import("./scanner.zig").Scanner;
|
||||||
const Value = @import("./dynamic.zig").Value;
|
const Value = @import("./dynamic.zig").Value;
|
||||||
|
const stringifyAlloc = @import("./stringify.zig").stringifyAlloc;
|
||||||
|
|
||||||
// Support for JSONTestSuite.zig
|
// Support for JSONTestSuite.zig
|
||||||
pub fn ok(s: []const u8) !void {
|
pub fn ok(s: []const u8) !void {
|
||||||
@ -49,11 +50,10 @@ fn roundTrip(s: []const u8) !void {
|
|||||||
var parsed = try parseFromSlice(Value, testing.allocator, s, .{});
|
var parsed = try parseFromSlice(Value, testing.allocator, s, .{});
|
||||||
defer parsed.deinit();
|
defer parsed.deinit();
|
||||||
|
|
||||||
var buf: [256]u8 = undefined;
|
const rendered = try stringifyAlloc(testing.allocator, parsed.value, .{});
|
||||||
var fbs = std.io.fixedBufferStream(&buf);
|
defer testing.allocator.free(rendered);
|
||||||
try parsed.value.jsonStringify(.{}, fbs.writer());
|
|
||||||
|
|
||||||
try testing.expectEqualStrings(s, fbs.getWritten());
|
try testing.expectEqualStrings(s, rendered);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "truncated UTF-8 sequence" {
|
test "truncated UTF-8 sequence" {
|
||||||
|
|||||||
@ -1,300 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const assert = std.debug.assert;
|
|
||||||
const maxInt = std.math.maxInt;
|
|
||||||
|
|
||||||
const StringifyOptions = @import("./stringify.zig").StringifyOptions;
|
|
||||||
const jsonStringify = @import("./stringify.zig").stringify;
|
|
||||||
|
|
||||||
const Value = @import("./dynamic.zig").Value;
|
|
||||||
|
|
||||||
const State = enum {
|
|
||||||
complete,
|
|
||||||
value,
|
|
||||||
array_start,
|
|
||||||
array,
|
|
||||||
object_start,
|
|
||||||
object,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Writes JSON ([RFC8259](https://tools.ietf.org/html/rfc8259)) formatted data
|
|
||||||
/// to a stream. `max_depth` is a comptime-known upper bound on the nesting depth.
|
|
||||||
/// TODO A future iteration of this API will allow passing `null` for this value,
|
|
||||||
/// and disable safety checks in release builds.
|
|
||||||
pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
|
|
||||||
return struct {
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
pub const Stream = OutStream;
|
|
||||||
|
|
||||||
whitespace: StringifyOptions.Whitespace = StringifyOptions.Whitespace{
|
|
||||||
.indent_level = 0,
|
|
||||||
.indent = .{ .space = 1 },
|
|
||||||
},
|
|
||||||
|
|
||||||
stream: OutStream,
|
|
||||||
state_index: usize,
|
|
||||||
state: [max_depth]State,
|
|
||||||
|
|
||||||
pub fn init(stream: OutStream) Self {
|
|
||||||
var self = Self{
|
|
||||||
.stream = stream,
|
|
||||||
.state_index = 1,
|
|
||||||
.state = undefined,
|
|
||||||
};
|
|
||||||
self.state[0] = .complete;
|
|
||||||
self.state[1] = .value;
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn beginArray(self: *Self) !void {
|
|
||||||
assert(self.state[self.state_index] == State.value); // need to call arrayElem or objectField
|
|
||||||
try self.stream.writeByte('[');
|
|
||||||
self.state[self.state_index] = State.array_start;
|
|
||||||
self.whitespace.indent_level += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn beginObject(self: *Self) !void {
|
|
||||||
assert(self.state[self.state_index] == State.value); // need to call arrayElem or objectField
|
|
||||||
try self.stream.writeByte('{');
|
|
||||||
self.state[self.state_index] = State.object_start;
|
|
||||||
self.whitespace.indent_level += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn arrayElem(self: *Self) !void {
|
|
||||||
const state = self.state[self.state_index];
|
|
||||||
switch (state) {
|
|
||||||
.complete => unreachable,
|
|
||||||
.value => unreachable,
|
|
||||||
.object_start => unreachable,
|
|
||||||
.object => unreachable,
|
|
||||||
.array, .array_start => {
|
|
||||||
if (state == .array) {
|
|
||||||
try self.stream.writeByte(',');
|
|
||||||
}
|
|
||||||
self.state[self.state_index] = .array;
|
|
||||||
self.pushState(.value);
|
|
||||||
try self.indent();
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn objectField(self: *Self, name: []const u8) !void {
|
|
||||||
const state = self.state[self.state_index];
|
|
||||||
switch (state) {
|
|
||||||
.complete => unreachable,
|
|
||||||
.value => unreachable,
|
|
||||||
.array_start => unreachable,
|
|
||||||
.array => unreachable,
|
|
||||||
.object, .object_start => {
|
|
||||||
if (state == .object) {
|
|
||||||
try self.stream.writeByte(',');
|
|
||||||
}
|
|
||||||
self.state[self.state_index] = .object;
|
|
||||||
self.pushState(.value);
|
|
||||||
try self.indent();
|
|
||||||
try self.writeEscapedString(name);
|
|
||||||
try self.stream.writeByte(':');
|
|
||||||
if (self.whitespace.separator) {
|
|
||||||
try self.stream.writeByte(' ');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn endArray(self: *Self) !void {
|
|
||||||
switch (self.state[self.state_index]) {
|
|
||||||
.complete => unreachable,
|
|
||||||
.value => unreachable,
|
|
||||||
.object_start => unreachable,
|
|
||||||
.object => unreachable,
|
|
||||||
.array_start => {
|
|
||||||
self.whitespace.indent_level -= 1;
|
|
||||||
try self.stream.writeByte(']');
|
|
||||||
self.popState();
|
|
||||||
},
|
|
||||||
.array => {
|
|
||||||
self.whitespace.indent_level -= 1;
|
|
||||||
try self.indent();
|
|
||||||
self.popState();
|
|
||||||
try self.stream.writeByte(']');
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn endObject(self: *Self) !void {
|
|
||||||
switch (self.state[self.state_index]) {
|
|
||||||
.complete => unreachable,
|
|
||||||
.value => unreachable,
|
|
||||||
.array_start => unreachable,
|
|
||||||
.array => unreachable,
|
|
||||||
.object_start => {
|
|
||||||
self.whitespace.indent_level -= 1;
|
|
||||||
try self.stream.writeByte('}');
|
|
||||||
self.popState();
|
|
||||||
},
|
|
||||||
.object => {
|
|
||||||
self.whitespace.indent_level -= 1;
|
|
||||||
try self.indent();
|
|
||||||
self.popState();
|
|
||||||
try self.stream.writeByte('}');
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn emitNull(self: *Self) !void {
|
|
||||||
assert(self.state[self.state_index] == State.value);
|
|
||||||
try self.stringify(null);
|
|
||||||
self.popState();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn emitBool(self: *Self, value: bool) !void {
|
|
||||||
assert(self.state[self.state_index] == State.value);
|
|
||||||
try self.stringify(value);
|
|
||||||
self.popState();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn emitNumber(
|
|
||||||
self: *Self,
|
|
||||||
/// An integer, float, or `std.math.BigInt`. Emitted as a bare number if it fits losslessly
|
|
||||||
/// in a IEEE 754 double float, otherwise emitted as a string to the full precision.
|
|
||||||
value: anytype,
|
|
||||||
) !void {
|
|
||||||
assert(self.state[self.state_index] == State.value);
|
|
||||||
switch (@typeInfo(@TypeOf(value))) {
|
|
||||||
.Int => |info| {
|
|
||||||
if (info.bits < 53) {
|
|
||||||
try self.stream.print("{}", .{value});
|
|
||||||
self.popState();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (value < 4503599627370496 and (info.signedness == .unsigned or value > -4503599627370496)) {
|
|
||||||
try self.stream.print("{}", .{value});
|
|
||||||
self.popState();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.ComptimeInt => {
|
|
||||||
return self.emitNumber(@as(std.math.IntFittingRange(value, value), value));
|
|
||||||
},
|
|
||||||
.Float, .ComptimeFloat => if (@as(f64, @floatCast(value)) == value) {
|
|
||||||
try self.stream.print("{}", .{@as(f64, @floatCast(value))});
|
|
||||||
self.popState();
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
try self.stream.print("\"{}\"", .{value});
|
|
||||||
self.popState();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn emitString(self: *Self, string: []const u8) !void {
|
|
||||||
assert(self.state[self.state_index] == State.value);
|
|
||||||
try self.writeEscapedString(string);
|
|
||||||
self.popState();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn writeEscapedString(self: *Self, string: []const u8) !void {
|
|
||||||
assert(std.unicode.utf8ValidateSlice(string));
|
|
||||||
try self.stringify(string);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Writes the complete json into the output stream
|
|
||||||
pub fn emitJson(self: *Self, value: Value) Stream.Error!void {
|
|
||||||
assert(self.state[self.state_index] == State.value);
|
|
||||||
try self.stringify(value);
|
|
||||||
self.popState();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn indent(self: *Self) !void {
|
|
||||||
assert(self.state_index >= 1);
|
|
||||||
try self.whitespace.outputIndent(self.stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pushState(self: *Self, state: State) void {
|
|
||||||
self.state_index += 1;
|
|
||||||
self.state[self.state_index] = state;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn popState(self: *Self) void {
|
|
||||||
self.state_index -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stringify(self: *Self, value: anytype) !void {
|
|
||||||
try jsonStringify(value, StringifyOptions{
|
|
||||||
.whitespace = self.whitespace,
|
|
||||||
}, self.stream);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn writeStream(
|
|
||||||
out_stream: anytype,
|
|
||||||
comptime max_depth: usize,
|
|
||||||
) WriteStream(@TypeOf(out_stream), max_depth) {
|
|
||||||
return WriteStream(@TypeOf(out_stream), max_depth).init(out_stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ObjectMap = @import("./dynamic.zig").ObjectMap;
|
|
||||||
|
|
||||||
test "json write stream" {
|
|
||||||
var out_buf: [1024]u8 = undefined;
|
|
||||||
var slice_stream = std.io.fixedBufferStream(&out_buf);
|
|
||||||
const out = slice_stream.writer();
|
|
||||||
|
|
||||||
var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
||||||
defer arena_allocator.deinit();
|
|
||||||
|
|
||||||
var w = writeStream(out, 10);
|
|
||||||
|
|
||||||
try w.beginObject();
|
|
||||||
|
|
||||||
try w.objectField("object");
|
|
||||||
try w.emitJson(try getJsonObject(arena_allocator.allocator()));
|
|
||||||
|
|
||||||
try w.objectField("string");
|
|
||||||
try w.emitString("This is a string");
|
|
||||||
|
|
||||||
try w.objectField("array");
|
|
||||||
try w.beginArray();
|
|
||||||
try w.arrayElem();
|
|
||||||
try w.emitString("Another string");
|
|
||||||
try w.arrayElem();
|
|
||||||
try w.emitNumber(@as(i32, 1));
|
|
||||||
try w.arrayElem();
|
|
||||||
try w.emitNumber(@as(f32, 3.5));
|
|
||||||
try w.endArray();
|
|
||||||
|
|
||||||
try w.objectField("int");
|
|
||||||
try w.emitNumber(@as(i32, 10));
|
|
||||||
|
|
||||||
try w.objectField("float");
|
|
||||||
try w.emitNumber(@as(f32, 3.5));
|
|
||||||
|
|
||||||
try w.endObject();
|
|
||||||
|
|
||||||
const result = slice_stream.getWritten();
|
|
||||||
const expected =
|
|
||||||
\\{
|
|
||||||
\\ "object": {
|
|
||||||
\\ "one": 1,
|
|
||||||
\\ "two": 2.0e+00
|
|
||||||
\\ },
|
|
||||||
\\ "string": "This is a string",
|
|
||||||
\\ "array": [
|
|
||||||
\\ "Another string",
|
|
||||||
\\ 1,
|
|
||||||
\\ 3.5e+00
|
|
||||||
\\ ],
|
|
||||||
\\ "int": 10,
|
|
||||||
\\ "float": 3.5e+00
|
|
||||||
\\}
|
|
||||||
;
|
|
||||||
try std.testing.expect(std.mem.eql(u8, expected, result));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn getJsonObject(allocator: std.mem.Allocator) !Value {
|
|
||||||
var value = Value{ .object = ObjectMap.init(allocator) };
|
|
||||||
try value.object.put("one", Value{ .integer = @as(i64, @intCast(1)) });
|
|
||||||
try value.object.put("two", Value{ .float = 2.0 });
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
@ -8,6 +8,7 @@ pub const AutoArrayHashMap = array_hash_map.AutoArrayHashMap;
|
|||||||
pub const AutoArrayHashMapUnmanaged = array_hash_map.AutoArrayHashMapUnmanaged;
|
pub const AutoArrayHashMapUnmanaged = array_hash_map.AutoArrayHashMapUnmanaged;
|
||||||
pub const AutoHashMap = hash_map.AutoHashMap;
|
pub const AutoHashMap = hash_map.AutoHashMap;
|
||||||
pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged;
|
pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged;
|
||||||
|
pub const BitStack = @import("BitStack.zig");
|
||||||
pub const BoundedArray = @import("bounded_array.zig").BoundedArray;
|
pub const BoundedArray = @import("bounded_array.zig").BoundedArray;
|
||||||
pub const BoundedArrayAligned = @import("bounded_array.zig").BoundedArrayAligned;
|
pub const BoundedArrayAligned = @import("bounded_array.zig").BoundedArrayAligned;
|
||||||
pub const Build = @import("Build.zig");
|
pub const Build = @import("Build.zig");
|
||||||
|
|||||||
129
src/Autodoc.zig
129
src/Autodoc.zig
@ -385,10 +385,11 @@ pub fn generateZirData(self: *Autodoc) !void {
|
|||||||
\\ /** @type {{DocData}} */
|
\\ /** @type {{DocData}} */
|
||||||
\\ var zigAnalysis=
|
\\ var zigAnalysis=
|
||||||
, .{});
|
, .{});
|
||||||
try std.json.stringify(
|
try std.json.stringifyArbitraryDepth(
|
||||||
|
arena_allocator.allocator(),
|
||||||
data,
|
data,
|
||||||
.{
|
.{
|
||||||
.whitespace = .{ .indent = .none, .separator = false },
|
.whitespace = .minified,
|
||||||
.emit_null_optional_fields = true,
|
.emit_null_optional_fields = true,
|
||||||
},
|
},
|
||||||
out,
|
out,
|
||||||
@ -532,28 +533,16 @@ const DocData = struct {
|
|||||||
ret: Expr,
|
ret: Expr,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn jsonStringify(
|
pub fn jsonStringify(self: DocData, jsw: anytype) !void {
|
||||||
self: DocData,
|
|
||||||
opts: std.json.StringifyOptions,
|
|
||||||
w: anytype,
|
|
||||||
) !void {
|
|
||||||
var jsw = std.json.writeStream(w, 15);
|
|
||||||
jsw.whitespace = opts.whitespace;
|
|
||||||
try jsw.beginObject();
|
try jsw.beginObject();
|
||||||
inline for (comptime std.meta.tags(std.meta.FieldEnum(DocData))) |f| {
|
inline for (comptime std.meta.tags(std.meta.FieldEnum(DocData))) |f| {
|
||||||
const f_name = @tagName(f);
|
const f_name = @tagName(f);
|
||||||
try jsw.objectField(f_name);
|
try jsw.objectField(f_name);
|
||||||
switch (f) {
|
switch (f) {
|
||||||
.files => try writeFileTableToJson(self.files, self.modules, &jsw),
|
.files => try writeFileTableToJson(self.files, self.modules, jsw),
|
||||||
.guide_sections => try writeGuidesToJson(self.guide_sections, &jsw),
|
.guide_sections => try writeGuidesToJson(self.guide_sections, jsw),
|
||||||
.modules => {
|
.modules => try jsw.write(self.modules.values()),
|
||||||
try std.json.stringify(self.modules.values(), opts, w);
|
else => try jsw.write(@field(self, f_name)),
|
||||||
jsw.state_index -= 1;
|
|
||||||
},
|
|
||||||
else => {
|
|
||||||
try std.json.stringify(@field(self, f_name), opts, w);
|
|
||||||
jsw.state_index -= 1;
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try jsw.endObject();
|
try jsw.endObject();
|
||||||
@ -583,24 +572,14 @@ const DocData = struct {
|
|||||||
value: usize,
|
value: usize,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn jsonStringify(
|
pub fn jsonStringify(self: DocModule, jsw: anytype) !void {
|
||||||
self: DocModule,
|
|
||||||
opts: std.json.StringifyOptions,
|
|
||||||
w: anytype,
|
|
||||||
) !void {
|
|
||||||
var jsw = std.json.writeStream(w, 15);
|
|
||||||
jsw.whitespace = opts.whitespace;
|
|
||||||
|
|
||||||
try jsw.beginObject();
|
try jsw.beginObject();
|
||||||
inline for (comptime std.meta.tags(std.meta.FieldEnum(DocModule))) |f| {
|
inline for (comptime std.meta.tags(std.meta.FieldEnum(DocModule))) |f| {
|
||||||
const f_name = @tagName(f);
|
const f_name = @tagName(f);
|
||||||
try jsw.objectField(f_name);
|
try jsw.objectField(f_name);
|
||||||
switch (f) {
|
switch (f) {
|
||||||
.table => try writeModuleTableToJson(self.table, &jsw),
|
.table => try writeModuleTableToJson(self.table, jsw),
|
||||||
else => {
|
else => try jsw.write(@field(self, f_name)),
|
||||||
try std.json.stringify(@field(self, f_name), opts, w);
|
|
||||||
jsw.state_index -= 1;
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try jsw.endObject();
|
try jsw.endObject();
|
||||||
@ -617,18 +596,10 @@ const DocData = struct {
|
|||||||
is_uns: bool = false, // usingnamespace
|
is_uns: bool = false, // usingnamespace
|
||||||
parent_container: ?usize, // index into `types`
|
parent_container: ?usize, // index into `types`
|
||||||
|
|
||||||
pub fn jsonStringify(
|
pub fn jsonStringify(self: Decl, jsw: anytype) !void {
|
||||||
self: Decl,
|
|
||||||
opts: std.json.StringifyOptions,
|
|
||||||
w: anytype,
|
|
||||||
) !void {
|
|
||||||
var jsw = std.json.writeStream(w, 15);
|
|
||||||
jsw.whitespace = opts.whitespace;
|
|
||||||
try jsw.beginArray();
|
try jsw.beginArray();
|
||||||
inline for (comptime std.meta.fields(Decl)) |f| {
|
inline for (comptime std.meta.fields(Decl)) |f| {
|
||||||
try jsw.arrayElem();
|
try jsw.write(@field(self, f.name));
|
||||||
try std.json.stringify(@field(self, f.name), opts, w);
|
|
||||||
jsw.state_index -= 1;
|
|
||||||
}
|
}
|
||||||
try jsw.endArray();
|
try jsw.endArray();
|
||||||
}
|
}
|
||||||
@ -644,18 +615,10 @@ const DocData = struct {
|
|||||||
fields: ?[]usize = null, // index into astNodes
|
fields: ?[]usize = null, // index into astNodes
|
||||||
@"comptime": bool = false,
|
@"comptime": bool = false,
|
||||||
|
|
||||||
pub fn jsonStringify(
|
pub fn jsonStringify(self: AstNode, jsw: anytype) !void {
|
||||||
self: AstNode,
|
|
||||||
opts: std.json.StringifyOptions,
|
|
||||||
w: anytype,
|
|
||||||
) !void {
|
|
||||||
var jsw = std.json.writeStream(w, 15);
|
|
||||||
jsw.whitespace = opts.whitespace;
|
|
||||||
try jsw.beginArray();
|
try jsw.beginArray();
|
||||||
inline for (comptime std.meta.fields(AstNode)) |f| {
|
inline for (comptime std.meta.fields(AstNode)) |f| {
|
||||||
try jsw.arrayElem();
|
try jsw.write(@field(self, f.name));
|
||||||
try std.json.stringify(@field(self, f.name), opts, w);
|
|
||||||
jsw.state_index -= 1;
|
|
||||||
}
|
}
|
||||||
try jsw.endArray();
|
try jsw.endArray();
|
||||||
}
|
}
|
||||||
@ -776,27 +739,18 @@ const DocData = struct {
|
|||||||
docs: []const u8,
|
docs: []const u8,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn jsonStringify(
|
pub fn jsonStringify(self: Type, jsw: anytype) !void {
|
||||||
self: Type,
|
|
||||||
opts: std.json.StringifyOptions,
|
|
||||||
w: anytype,
|
|
||||||
) !void {
|
|
||||||
const active_tag = std.meta.activeTag(self);
|
const active_tag = std.meta.activeTag(self);
|
||||||
var jsw = std.json.writeStream(w, 15);
|
|
||||||
jsw.whitespace = opts.whitespace;
|
|
||||||
try jsw.beginArray();
|
try jsw.beginArray();
|
||||||
try jsw.arrayElem();
|
try jsw.write(@intFromEnum(active_tag));
|
||||||
try jsw.emitNumber(@intFromEnum(active_tag));
|
|
||||||
inline for (comptime std.meta.fields(Type)) |case| {
|
inline for (comptime std.meta.fields(Type)) |case| {
|
||||||
if (@field(Type, case.name) == active_tag) {
|
if (@field(Type, case.name) == active_tag) {
|
||||||
const current_value = @field(self, case.name);
|
const current_value = @field(self, case.name);
|
||||||
inline for (comptime std.meta.fields(case.type)) |f| {
|
inline for (comptime std.meta.fields(case.type)) |f| {
|
||||||
try jsw.arrayElem();
|
|
||||||
if (f.type == std.builtin.Type.Pointer.Size) {
|
if (f.type == std.builtin.Type.Pointer.Size) {
|
||||||
try jsw.emitNumber(@intFromEnum(@field(current_value, f.name)));
|
try jsw.write(@intFromEnum(@field(current_value, f.name)));
|
||||||
} else {
|
} else {
|
||||||
try std.json.stringify(@field(current_value, f.name), opts, w);
|
try jsw.write(@field(current_value, f.name));
|
||||||
jsw.state_index -= 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -919,14 +873,8 @@ const DocData = struct {
|
|||||||
val: WalkResult,
|
val: WalkResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn jsonStringify(
|
pub fn jsonStringify(self: Expr, jsw: anytype) !void {
|
||||||
self: Expr,
|
|
||||||
opts: std.json.StringifyOptions,
|
|
||||||
w: anytype,
|
|
||||||
) @TypeOf(w).Error!void {
|
|
||||||
const active_tag = std.meta.activeTag(self);
|
const active_tag = std.meta.activeTag(self);
|
||||||
var jsw = std.json.writeStream(w, 15);
|
|
||||||
jsw.whitespace = opts.whitespace;
|
|
||||||
try jsw.beginObject();
|
try jsw.beginObject();
|
||||||
if (active_tag == .declIndex) {
|
if (active_tag == .declIndex) {
|
||||||
try jsw.objectField("declRef");
|
try jsw.objectField("declRef");
|
||||||
@ -935,14 +883,17 @@ const DocData = struct {
|
|||||||
}
|
}
|
||||||
switch (self) {
|
switch (self) {
|
||||||
.int => {
|
.int => {
|
||||||
if (self.int.negated) try w.writeAll("-");
|
if (self.int.negated) {
|
||||||
try jsw.emitNumber(self.int.value);
|
try jsw.write(-@as(i65, self.int.value));
|
||||||
|
} else {
|
||||||
|
try jsw.write(self.int.value);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
.builtinField => {
|
.builtinField => {
|
||||||
try jsw.emitString(@tagName(self.builtinField));
|
try jsw.write(@tagName(self.builtinField));
|
||||||
},
|
},
|
||||||
.declRef => {
|
.declRef => {
|
||||||
try jsw.emitNumber(self.declRef.Analyzed);
|
try jsw.write(self.declRef.Analyzed);
|
||||||
},
|
},
|
||||||
else => {
|
else => {
|
||||||
inline for (comptime std.meta.fields(Expr)) |case| {
|
inline for (comptime std.meta.fields(Expr)) |case| {
|
||||||
@ -952,14 +903,7 @@ const DocData = struct {
|
|||||||
if (comptime std.mem.eql(u8, case.name, "declRef"))
|
if (comptime std.mem.eql(u8, case.name, "declRef"))
|
||||||
continue;
|
continue;
|
||||||
if (@field(Expr, case.name) == active_tag) {
|
if (@field(Expr, case.name) == active_tag) {
|
||||||
try std.json.stringify(@field(self, case.name), opts, w);
|
try jsw.write(@field(self, case.name));
|
||||||
jsw.state_index -= 1;
|
|
||||||
// TODO: we should not reach into the state of the
|
|
||||||
// json writer, but alas, this is what's
|
|
||||||
// necessary with the current api.
|
|
||||||
// would be nice to have a proper integration
|
|
||||||
// between the json writer and the generic
|
|
||||||
// std.json.stringify implementation
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -5440,12 +5384,9 @@ fn writeFileTableToJson(
|
|||||||
try jsw.beginArray();
|
try jsw.beginArray();
|
||||||
var it = map.iterator();
|
var it = map.iterator();
|
||||||
while (it.next()) |entry| {
|
while (it.next()) |entry| {
|
||||||
try jsw.arrayElem();
|
|
||||||
try jsw.beginArray();
|
try jsw.beginArray();
|
||||||
try jsw.arrayElem();
|
try jsw.write(entry.key_ptr.*.sub_file_path);
|
||||||
try jsw.emitString(entry.key_ptr.*.sub_file_path);
|
try jsw.write(mods.getIndex(entry.key_ptr.*.pkg) orelse 0);
|
||||||
try jsw.arrayElem();
|
|
||||||
try jsw.emitNumber(mods.getIndex(entry.key_ptr.*.pkg) orelse 0);
|
|
||||||
try jsw.endArray();
|
try jsw.endArray();
|
||||||
}
|
}
|
||||||
try jsw.endArray();
|
try jsw.endArray();
|
||||||
@ -5462,21 +5403,19 @@ fn writeGuidesToJson(sections: std.ArrayListUnmanaged(Section), jsw: anytype) !v
|
|||||||
|
|
||||||
for (sections.items) |s| {
|
for (sections.items) |s| {
|
||||||
// section name
|
// section name
|
||||||
try jsw.arrayElem();
|
|
||||||
try jsw.beginObject();
|
try jsw.beginObject();
|
||||||
try jsw.objectField("name");
|
try jsw.objectField("name");
|
||||||
try jsw.emitString(s.name);
|
try jsw.write(s.name);
|
||||||
try jsw.objectField("guides");
|
try jsw.objectField("guides");
|
||||||
|
|
||||||
// section value
|
// section value
|
||||||
try jsw.beginArray();
|
try jsw.beginArray();
|
||||||
for (s.guides.items) |g| {
|
for (s.guides.items) |g| {
|
||||||
try jsw.arrayElem();
|
|
||||||
try jsw.beginObject();
|
try jsw.beginObject();
|
||||||
try jsw.objectField("name");
|
try jsw.objectField("name");
|
||||||
try jsw.emitString(g.name);
|
try jsw.write(g.name);
|
||||||
try jsw.objectField("body");
|
try jsw.objectField("body");
|
||||||
try jsw.emitString(g.body);
|
try jsw.write(g.body);
|
||||||
try jsw.endObject();
|
try jsw.endObject();
|
||||||
}
|
}
|
||||||
try jsw.endArray();
|
try jsw.endArray();
|
||||||
@ -5494,7 +5433,7 @@ fn writeModuleTableToJson(
|
|||||||
var it = map.valueIterator();
|
var it = map.valueIterator();
|
||||||
while (it.next()) |entry| {
|
while (it.next()) |entry| {
|
||||||
try jsw.objectField(entry.name);
|
try jsw.objectField(entry.name);
|
||||||
try jsw.emitNumber(entry.value);
|
try jsw.write(entry.value);
|
||||||
}
|
}
|
||||||
try jsw.endObject();
|
try jsw.endObject();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,26 +28,27 @@ pub fn cmdEnv(gpa: Allocator, args: []const []const u8, stdout: std.fs.File.Writ
|
|||||||
var bw = std.io.bufferedWriter(stdout);
|
var bw = std.io.bufferedWriter(stdout);
|
||||||
const w = bw.writer();
|
const w = bw.writer();
|
||||||
|
|
||||||
var jws = std.json.writeStream(w, 6);
|
var jws = std.json.writeStream(w, .{ .whitespace = .indent_1 });
|
||||||
|
|
||||||
try jws.beginObject();
|
try jws.beginObject();
|
||||||
|
|
||||||
try jws.objectField("zig_exe");
|
try jws.objectField("zig_exe");
|
||||||
try jws.emitString(self_exe_path);
|
try jws.write(self_exe_path);
|
||||||
|
|
||||||
try jws.objectField("lib_dir");
|
try jws.objectField("lib_dir");
|
||||||
try jws.emitString(zig_lib_directory.path.?);
|
try jws.write(zig_lib_directory.path.?);
|
||||||
|
|
||||||
try jws.objectField("std_dir");
|
try jws.objectField("std_dir");
|
||||||
try jws.emitString(zig_std_dir);
|
try jws.write(zig_std_dir);
|
||||||
|
|
||||||
try jws.objectField("global_cache_dir");
|
try jws.objectField("global_cache_dir");
|
||||||
try jws.emitString(global_cache_dir);
|
try jws.write(global_cache_dir);
|
||||||
|
|
||||||
try jws.objectField("version");
|
try jws.objectField("version");
|
||||||
try jws.emitString(build_options.version);
|
try jws.write(build_options.version);
|
||||||
|
|
||||||
try jws.objectField("target");
|
try jws.objectField("target");
|
||||||
try jws.emitString(triple);
|
try jws.write(triple);
|
||||||
|
|
||||||
try jws.endObject();
|
try jws.endObject();
|
||||||
try w.writeByte('\n');
|
try w.writeByte('\n');
|
||||||
|
|||||||
@ -40,31 +40,28 @@ pub fn cmdTargets(
|
|||||||
|
|
||||||
var bw = io.bufferedWriter(stdout);
|
var bw = io.bufferedWriter(stdout);
|
||||||
const w = bw.writer();
|
const w = bw.writer();
|
||||||
var jws = std.json.writeStream(w, 6);
|
var jws = std.json.writeStream(w, .{ .whitespace = .indent_1 });
|
||||||
|
|
||||||
try jws.beginObject();
|
try jws.beginObject();
|
||||||
|
|
||||||
try jws.objectField("arch");
|
try jws.objectField("arch");
|
||||||
try jws.beginArray();
|
try jws.beginArray();
|
||||||
for (meta.fieldNames(Target.Cpu.Arch)) |field| {
|
for (meta.fieldNames(Target.Cpu.Arch)) |field| {
|
||||||
try jws.arrayElem();
|
try jws.write(field);
|
||||||
try jws.emitString(field);
|
|
||||||
}
|
}
|
||||||
try jws.endArray();
|
try jws.endArray();
|
||||||
|
|
||||||
try jws.objectField("os");
|
try jws.objectField("os");
|
||||||
try jws.beginArray();
|
try jws.beginArray();
|
||||||
for (meta.fieldNames(Target.Os.Tag)) |field| {
|
for (meta.fieldNames(Target.Os.Tag)) |field| {
|
||||||
try jws.arrayElem();
|
try jws.write(field);
|
||||||
try jws.emitString(field);
|
|
||||||
}
|
}
|
||||||
try jws.endArray();
|
try jws.endArray();
|
||||||
|
|
||||||
try jws.objectField("abi");
|
try jws.objectField("abi");
|
||||||
try jws.beginArray();
|
try jws.beginArray();
|
||||||
for (meta.fieldNames(Target.Abi)) |field| {
|
for (meta.fieldNames(Target.Abi)) |field| {
|
||||||
try jws.arrayElem();
|
try jws.write(field);
|
||||||
try jws.emitString(field);
|
|
||||||
}
|
}
|
||||||
try jws.endArray();
|
try jws.endArray();
|
||||||
|
|
||||||
@ -75,19 +72,16 @@ pub fn cmdTargets(
|
|||||||
@tagName(libc.arch), @tagName(libc.os), @tagName(libc.abi),
|
@tagName(libc.arch), @tagName(libc.os), @tagName(libc.abi),
|
||||||
});
|
});
|
||||||
defer allocator.free(tmp);
|
defer allocator.free(tmp);
|
||||||
try jws.arrayElem();
|
try jws.write(tmp);
|
||||||
try jws.emitString(tmp);
|
|
||||||
}
|
}
|
||||||
try jws.endArray();
|
try jws.endArray();
|
||||||
|
|
||||||
try jws.objectField("glibc");
|
try jws.objectField("glibc");
|
||||||
try jws.beginArray();
|
try jws.beginArray();
|
||||||
for (glibc_abi.all_versions) |ver| {
|
for (glibc_abi.all_versions) |ver| {
|
||||||
try jws.arrayElem();
|
|
||||||
|
|
||||||
const tmp = try std.fmt.allocPrint(allocator, "{}", .{ver});
|
const tmp = try std.fmt.allocPrint(allocator, "{}", .{ver});
|
||||||
defer allocator.free(tmp);
|
defer allocator.free(tmp);
|
||||||
try jws.emitString(tmp);
|
try jws.write(tmp);
|
||||||
}
|
}
|
||||||
try jws.endArray();
|
try jws.endArray();
|
||||||
|
|
||||||
@ -102,8 +96,7 @@ pub fn cmdTargets(
|
|||||||
for (arch.allFeaturesList(), 0..) |feature, i_usize| {
|
for (arch.allFeaturesList(), 0..) |feature, i_usize| {
|
||||||
const index = @as(Target.Cpu.Feature.Set.Index, @intCast(i_usize));
|
const index = @as(Target.Cpu.Feature.Set.Index, @intCast(i_usize));
|
||||||
if (model.features.isEnabled(index)) {
|
if (model.features.isEnabled(index)) {
|
||||||
try jws.arrayElem();
|
try jws.write(feature.name);
|
||||||
try jws.emitString(feature.name);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try jws.endArray();
|
try jws.endArray();
|
||||||
@ -118,8 +111,7 @@ pub fn cmdTargets(
|
|||||||
try jws.objectField(@tagName(arch));
|
try jws.objectField(@tagName(arch));
|
||||||
try jws.beginArray();
|
try jws.beginArray();
|
||||||
for (arch.allFeaturesList()) |feature| {
|
for (arch.allFeaturesList()) |feature| {
|
||||||
try jws.arrayElem();
|
try jws.write(feature.name);
|
||||||
try jws.emitString(feature.name);
|
|
||||||
}
|
}
|
||||||
try jws.endArray();
|
try jws.endArray();
|
||||||
}
|
}
|
||||||
@ -131,17 +123,17 @@ pub fn cmdTargets(
|
|||||||
const triple = try native_target.zigTriple(allocator);
|
const triple = try native_target.zigTriple(allocator);
|
||||||
defer allocator.free(triple);
|
defer allocator.free(triple);
|
||||||
try jws.objectField("triple");
|
try jws.objectField("triple");
|
||||||
try jws.emitString(triple);
|
try jws.write(triple);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
try jws.objectField("cpu");
|
try jws.objectField("cpu");
|
||||||
try jws.beginObject();
|
try jws.beginObject();
|
||||||
try jws.objectField("arch");
|
try jws.objectField("arch");
|
||||||
try jws.emitString(@tagName(native_target.cpu.arch));
|
try jws.write(@tagName(native_target.cpu.arch));
|
||||||
|
|
||||||
try jws.objectField("name");
|
try jws.objectField("name");
|
||||||
const cpu = native_target.cpu;
|
const cpu = native_target.cpu;
|
||||||
try jws.emitString(cpu.model.name);
|
try jws.write(cpu.model.name);
|
||||||
|
|
||||||
{
|
{
|
||||||
try jws.objectField("features");
|
try jws.objectField("features");
|
||||||
@ -149,8 +141,7 @@ pub fn cmdTargets(
|
|||||||
for (native_target.cpu.arch.allFeaturesList(), 0..) |feature, i_usize| {
|
for (native_target.cpu.arch.allFeaturesList(), 0..) |feature, i_usize| {
|
||||||
const index = @as(Target.Cpu.Feature.Set.Index, @intCast(i_usize));
|
const index = @as(Target.Cpu.Feature.Set.Index, @intCast(i_usize));
|
||||||
if (cpu.features.isEnabled(index)) {
|
if (cpu.features.isEnabled(index)) {
|
||||||
try jws.arrayElem();
|
try jws.write(feature.name);
|
||||||
try jws.emitString(feature.name);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try jws.endArray();
|
try jws.endArray();
|
||||||
@ -158,9 +149,9 @@ pub fn cmdTargets(
|
|||||||
try jws.endObject();
|
try jws.endObject();
|
||||||
}
|
}
|
||||||
try jws.objectField("os");
|
try jws.objectField("os");
|
||||||
try jws.emitString(@tagName(native_target.os.tag));
|
try jws.write(@tagName(native_target.os.tag));
|
||||||
try jws.objectField("abi");
|
try jws.objectField("abi");
|
||||||
try jws.emitString(@tagName(native_target.abi));
|
try jws.write(@tagName(native_target.abi));
|
||||||
try jws.endObject();
|
try jws.endObject();
|
||||||
|
|
||||||
try jws.endObject();
|
try jws.endObject();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user