Commit 2429fdd73b

Benjamin Feng <benjamin.feng@glassdoor.com>
2020-03-01 01:59:16
Convert JSON to fmtstream
1 parent 0fbccec
Changed files (1)
lib
lib/std/json.zig
@@ -2252,45 +2252,43 @@ pub const StringifyOptions = struct {
 pub fn stringify(
     value: var,
     options: StringifyOptions,
-    context: var,
-    comptime Errors: type,
-    comptime output: fn (@TypeOf(context), []const u8) Errors!void,
-) Errors!void {
+    out_stream: var,
+) !void {
     const T = @TypeOf(value);
     switch (@typeInfo(T)) {
         .Float, .ComptimeFloat => {
-            return std.fmt.formatFloatScientific(value, std.fmt.FormatOptions{}, context, Errors, output);
+            return std.fmtstream.formatFloatScientific(value, std.fmtstream.FormatOptions{}, out_stream);
         },
         .Int, .ComptimeInt => {
-            return std.fmt.formatIntValue(value, "", std.fmt.FormatOptions{}, context, Errors, output);
+            return std.fmtstream.formatIntValue(value, "", std.fmtstream.FormatOptions{}, out_stream);
         },
         .Bool => {
-            return output(context, if (value) "true" else "false");
+            return out_stream.writeAll(if (value) "true" else "false");
         },
         .Optional => {
             if (value) |payload| {
-                return try stringify(payload, options, context, Errors, output);
+                return try stringify(payload, options, out_stream);
             } else {
-                return output(context, "null");
+                return out_stream.writeAll("null");
             }
         },
         .Enum => {
             if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {
-                return value.jsonStringify(options, context, Errors, output);
+                return value.jsonStringify(options, out_stream);
             }
 
             @compileError("Unable to stringify enum '" ++ @typeName(T) ++ "'");
         },
         .Union => {
             if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {
-                return value.jsonStringify(options, context, Errors, output);
+                return value.jsonStringify(options, out_stream);
             }
 
             const info = @typeInfo(T).Union;
             if (info.tag_type) |UnionTagType| {
                 inline for (info.fields) |u_field| {
                     if (@enumToInt(@as(UnionTagType, value)) == u_field.enum_field.?.value) {
-                        return try stringify(@field(value, u_field.name), options, context, Errors, output);
+                        return try stringify(@field(value, u_field.name), options, out_stream);
                     }
                 }
             } else {
@@ -2299,10 +2297,10 @@ pub fn stringify(
         },
         .Struct => |S| {
             if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {
-                return value.jsonStringify(options, context, Errors, output);
+                return value.jsonStringify(options, out_stream);
             }
 
-            try output(context, "{");
+            try out_stream.writeAll("{");
             comptime var field_output = false;
             inline for (S.fields) |Field, field_i| {
                 // don't include void fields
@@ -2311,39 +2309,39 @@ pub fn stringify(
                 if (!field_output) {
                     field_output = true;
                 } else {
-                    try output(context, ",");
+                    try out_stream.writeAll(",");
                 }
 
-                try stringify(Field.name, options, context, Errors, output);
-                try output(context, ":");
-                try stringify(@field(value, Field.name), options, context, Errors, output);
+                try stringify(Field.name, options, out_stream);
+                try out_stream.writeAll(":");
+                try stringify(@field(value, Field.name), options, out_stream);
             }
-            try output(context, "}");
+            try out_stream.writeAll("}");
             return;
         },
         .Pointer => |ptr_info| switch (ptr_info.size) {
             .One => {
                 // TODO: avoid loops?
-                return try stringify(value.*, options, context, Errors, output);
+                return try stringify(value.*, options, out_stream);
             },
             // TODO: .Many when there is a sentinel (waiting for https://github.com/ziglang/zig/pull/3972)
             .Slice => {
                 if (ptr_info.child == u8 and std.unicode.utf8ValidateSlice(value)) {
-                    try output(context, "\"");
+                    try out_stream.writeAll("\"");
                     var i: usize = 0;
                     while (i < value.len) : (i += 1) {
                         switch (value[i]) {
                             // normal ascii characters
-                            0x20...0x21, 0x23...0x2E, 0x30...0x5B, 0x5D...0x7F => try output(context, value[i .. i + 1]),
+                            0x20...0x21, 0x23...0x2E, 0x30...0x5B, 0x5D...0x7F => try out_stream.writeAll(value[i .. i + 1]),
                             // control characters with short escapes
-                            '\\' => try output(context, "\\\\"),
-                            '\"' => try output(context, "\\\""),
-                            '/' => try output(context, "\\/"),
-                            0x8 => try output(context, "\\b"),
-                            0xC => try output(context, "\\f"),
-                            '\n' => try output(context, "\\n"),
-                            '\r' => try output(context, "\\r"),
-                            '\t' => try output(context, "\\t"),
+                            '\\' => try out_stream.writeAll("\\\\"),
+                            '\"' => try out_stream.writeAll("\\\""),
+                            '/' => try out_stream.writeAll("\\/"),
+                            0x8 => try out_stream.writeAll("\\b"),
+                            0xC => try out_stream.writeAll("\\f"),
+                            '\n' => try out_stream.writeAll("\\n"),
+                            '\r' => try out_stream.writeAll("\\r"),
+                            '\t' => try out_stream.writeAll("\\t"),
                             else => {
                                 const ulen = std.unicode.utf8ByteSequenceLength(value[i]) catch unreachable;
                                 const codepoint = std.unicode.utf8Decode(value[i .. i + ulen]) catch unreachable;
@@ -2351,40 +2349,40 @@ pub fn stringify(
                                     // 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
                                     // by the lowercase letter u, followed by four hexadecimal digits that encode the character's code point.
-                                    try output(context, "\\u");
-                                    try std.fmt.formatIntValue(codepoint, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, context, Errors, output);
+                                    try out_stream.writeAll("\\u");
+                                    try std.fmtstream.formatIntValue(codepoint, "x", std.fmtstream.FormatOptions{ .width = 4, .fill = '0' }, out_stream);
                                 } else {
                                     // To escape an extended character that is not in the Basic Multilingual Plane,
                                     // the character is represented as a 12-character sequence, encoding the UTF-16 surrogate pair.
                                     const high = @intCast(u16, (codepoint - 0x10000) >> 10) + 0xD800;
                                     const low = @intCast(u16, codepoint & 0x3FF) + 0xDC00;
-                                    try output(context, "\\u");
-                                    try std.fmt.formatIntValue(high, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, context, Errors, output);
-                                    try output(context, "\\u");
-                                    try std.fmt.formatIntValue(low, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, context, Errors, output);
+                                    try out_stream.writeAll("\\u");
+                                    try std.fmtstream.formatIntValue(high, "x", std.fmtstream.FormatOptions{ .width = 4, .fill = '0' }, out_stream);
+                                    try out_stream.writeAll("\\u");
+                                    try std.fmtstream.formatIntValue(low, "x", std.fmtstream.FormatOptions{ .width = 4, .fill = '0' }, out_stream);
                                 }
                                 i += ulen - 1;
                             },
                         }
                     }
-                    try output(context, "\"");
+                    try out_stream.writeAll("\"");
                     return;
                 }
 
-                try output(context, "[");
+                try out_stream.writeAll("[");
                 for (value) |x, i| {
                     if (i != 0) {
-                        try output(context, ",");
+                        try out_stream.writeAll(",");
                     }
-                    try stringify(x, options, context, Errors, output);
+                    try stringify(x, options, out_stream);
                 }
-                try output(context, "]");
+                try out_stream.writeAll("]");
                 return;
             },
             else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"),
         },
         .Array => |info| {
-            return try stringify(value[0..], options, context, Errors, output);
+            return try stringify(value[0..], options, out_stream);
         },
         else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"),
     }
@@ -2392,10 +2390,26 @@ pub fn stringify(
 }
 
 fn teststringify(expected: []const u8, value: var) !void {
-    const TestStringifyContext = struct {
+    const ValidationOutStream = struct {
+        const Self = @This();
+        pub const OutStream = std.io.OutStream(*Self, Error, write);
+        pub const Error = error{
+            TooMuchData,
+            DifferentData,
+        };
+
         expected_remaining: []const u8,
-        fn testStringifyWrite(context: *@This(), bytes: []const u8) !void {
-            if (context.expected_remaining.len < bytes.len) {
+
+        fn init(exp: []const u8) Self {
+            return .{ .expected_remaining = exp };
+        }
+
+        pub fn outStream(self: *Self) OutStream {
+            return .{ .context = self };
+        }
+
+        fn write(self: *Self, bytes: []const u8) Error!usize {
+            if (self.expected_remaining.len < bytes.len) {
                 std.debug.warn(
                     \\====== expected this output: =========
                     \\{}
@@ -2403,12 +2417,12 @@ fn teststringify(expected: []const u8, value: var) !void {
                     \\{}
                     \\======================================
                 , .{
-                    context.expected_remaining,
+                    self.expected_remaining,
                     bytes,
                 });
                 return error.TooMuchData;
             }
-            if (!mem.eql(u8, context.expected_remaining[0..bytes.len], bytes)) {
+            if (!mem.eql(u8, self.expected_remaining[0..bytes.len], bytes)) {
                 std.debug.warn(
                     \\====== expected this output: =========
                     \\{}
@@ -2416,21 +2430,19 @@ fn teststringify(expected: []const u8, value: var) !void {
                     \\{}
                     \\======================================
                 , .{
-                    context.expected_remaining[0..bytes.len],
+                    self.expected_remaining[0..bytes.len],
                     bytes,
                 });
                 return error.DifferentData;
             }
-            context.expected_remaining = context.expected_remaining[bytes.len..];
+            self.expected_remaining = self.expected_remaining[bytes.len..];
+            return bytes.len;
         }
     };
-    var buf: [100]u8 = undefined;
-    var context = TestStringifyContext{ .expected_remaining = expected };
-    try stringify(value, StringifyOptions{}, &context, error{
-        TooMuchData,
-        DifferentData,
-    }, TestStringifyContext.testStringifyWrite);
-    if (context.expected_remaining.len > 0) return error.NotEnoughData;
+
+    var vos = ValidationOutStream.init(expected);
+    try stringify(value, StringifyOptions{}, vos.outStream());
+    if (vos.expected_remaining.len > 0) return error.NotEnoughData;
 }
 
 test "stringify basic types" {
@@ -2498,13 +2510,11 @@ test "stringify struct with custom stringifier" {
         pub fn jsonStringify(
             value: Self,
             options: StringifyOptions,
-            context: var,
-            comptime Errors: type,
-            comptime output: fn (@TypeOf(context), []const u8) Errors!void,
+            out_stream: var,
         ) !void {
-            try output(context, "[\"something special\",");
-            try stringify(42, options, context, Errors, output);
-            try output(context, "]");
+            try out_stream.writeAll("[\"something special\",");
+            try stringify(42, options, out_stream);
+            try out_stream.writeAll("]");
         }
     }{ .foo = 42 });
 }