| ... | ... | @@ -2252,45 +2252,43 @@ pub const StringifyOptions = struct { |
| 2252 | 2252 | pub fn stringify( |
| 2253 | 2253 | value: var, |
| 2254 | 2254 | options: StringifyOptions, |
| 2255 | | context: var, |
| 2256 | | comptime Errors: type, |
| 2257 | | comptime output: fn (@TypeOf(context), []const u8) Errors!void, |
| 2258 | | ) Errors!void { |
| 2255 | out_stream: var, |
| 2256 | ) !void { |
| 2259 | 2257 | const T = @TypeOf(value); |
| 2260 | 2258 | switch (@typeInfo(T)) { |
| 2261 | 2259 | .Float, .ComptimeFloat => { |
| 2262 | | return std.fmt.formatFloatScientific(value, std.fmt.FormatOptions{}, context, Errors, output); |
| 2260 | return std.fmtstream.formatFloatScientific(value, std.fmtstream.FormatOptions{}, out_stream); |
| 2263 | 2261 | }, |
| 2264 | 2262 | .Int, .ComptimeInt => { |
| 2265 | | return std.fmt.formatIntValue(value, "", std.fmt.FormatOptions{}, context, Errors, output); |
| 2263 | return std.fmtstream.formatIntValue(value, "", std.fmtstream.FormatOptions{}, out_stream); |
| 2266 | 2264 | }, |
| 2267 | 2265 | .Bool => { |
| 2268 | | return output(context, if (value) "true" else "false"); |
| 2266 | return out_stream.writeAll(if (value) "true" else "false"); |
| 2269 | 2267 | }, |
| 2270 | 2268 | .Optional => { |
| 2271 | 2269 | if (value) |payload| { |
| 2272 | | return try stringify(payload, options, context, Errors, output); |
| 2270 | return try stringify(payload, options, out_stream); |
| 2273 | 2271 | } else { |
| 2274 | | return output(context, "null"); |
| 2272 | return out_stream.writeAll("null"); |
| 2275 | 2273 | } |
| 2276 | 2274 | }, |
| 2277 | 2275 | .Enum => { |
| 2278 | 2276 | if (comptime std.meta.trait.hasFn("jsonStringify")(T)) { |
| 2279 | | return value.jsonStringify(options, context, Errors, output); |
| 2277 | return value.jsonStringify(options, out_stream); |
| 2280 | 2278 | } |
| 2281 | 2279 | |
| 2282 | 2280 | @compileError("Unable to stringify enum '" ++ @typeName(T) ++ "'"); |
| 2283 | 2281 | }, |
| 2284 | 2282 | .Union => { |
| 2285 | 2283 | if (comptime std.meta.trait.hasFn("jsonStringify")(T)) { |
| 2286 | | return value.jsonStringify(options, context, Errors, output); |
| 2284 | return value.jsonStringify(options, out_stream); |
| 2287 | 2285 | } |
| 2288 | 2286 | |
| 2289 | 2287 | const info = @typeInfo(T).Union; |
| 2290 | 2288 | if (info.tag_type) |UnionTagType| { |
| 2291 | 2289 | inline for (info.fields) |u_field| { |
| 2292 | 2290 | if (@enumToInt(@as(UnionTagType, value)) == u_field.enum_field.?.value) { |
| 2293 | | return try stringify(@field(value, u_field.name), options, context, Errors, output); |
| 2291 | return try stringify(@field(value, u_field.name), options, out_stream); |
| 2294 | 2292 | } |
| 2295 | 2293 | } |
| 2296 | 2294 | } else { |
| ... | ... | @@ -2299,10 +2297,10 @@ pub fn stringify( |
| 2299 | 2297 | }, |
| 2300 | 2298 | .Struct => |S| { |
| 2301 | 2299 | if (comptime std.meta.trait.hasFn("jsonStringify")(T)) { |
| 2302 | | return value.jsonStringify(options, context, Errors, output); |
| 2300 | return value.jsonStringify(options, out_stream); |
| 2303 | 2301 | } |
| 2304 | 2302 | |
| 2305 | | try output(context, "{"); |
| 2303 | try out_stream.writeAll("{"); |
| 2306 | 2304 | comptime var field_output = false; |
| 2307 | 2305 | inline for (S.fields) |Field, field_i| { |
| 2308 | 2306 | // don't include void fields |
| ... | ... | @@ -2311,39 +2309,39 @@ pub fn stringify( |
| 2311 | 2309 | if (!field_output) { |
| 2312 | 2310 | field_output = true; |
| 2313 | 2311 | } else { |
| 2314 | | try output(context, ","); |
| 2312 | try out_stream.writeAll(","); |
| 2315 | 2313 | } |
| 2316 | 2314 | |
| 2317 | | try stringify(Field.name, options, context, Errors, output); |
| 2318 | | try output(context, ":"); |
| 2319 | | try stringify(@field(value, Field.name), options, context, Errors, output); |
| 2315 | try stringify(Field.name, options, out_stream); |
| 2316 | try out_stream.writeAll(":"); |
| 2317 | try stringify(@field(value, Field.name), options, out_stream); |
| 2320 | 2318 | } |
| 2321 | | try output(context, "}"); |
| 2319 | try out_stream.writeAll("}"); |
| 2322 | 2320 | return; |
| 2323 | 2321 | }, |
| 2324 | 2322 | .Pointer => |ptr_info| switch (ptr_info.size) { |
| 2325 | 2323 | .One => { |
| 2326 | 2324 | // TODO: avoid loops? |
| 2327 | | return try stringify(value.*, options, context, Errors, output); |
| 2325 | return try stringify(value.*, options, out_stream); |
| 2328 | 2326 | }, |
| 2329 | 2327 | // TODO: .Many when there is a sentinel (waiting for https://github.com/ziglang/zig/pull/3972) |
| 2330 | 2328 | .Slice => { |
| 2331 | 2329 | if (ptr_info.child == u8 and std.unicode.utf8ValidateSlice(value)) { |
| 2332 | | try output(context, "\""); |
| 2330 | try out_stream.writeAll("\""); |
| 2333 | 2331 | var i: usize = 0; |
| 2334 | 2332 | while (i < value.len) : (i += 1) { |
| 2335 | 2333 | switch (value[i]) { |
| 2336 | 2334 | // normal ascii characters |
| 2337 | | 0x20...0x21, 0x23...0x2E, 0x30...0x5B, 0x5D...0x7F => try output(context, value[i .. i + 1]), |
| 2335 | 0x20...0x21, 0x23...0x2E, 0x30...0x5B, 0x5D...0x7F => try out_stream.writeAll(value[i .. i + 1]), |
| 2338 | 2336 | // control characters with short escapes |
| 2339 | | '\\' => try output(context, "\\\\"), |
| 2340 | | '\"' => try output(context, "\\\""), |
| 2341 | | '/' => try output(context, "\\/"), |
| 2342 | | 0x8 => try output(context, "\\b"), |
| 2343 | | 0xC => try output(context, "\\f"), |
| 2344 | | '\n' => try output(context, "\\n"), |
| 2345 | | '\r' => try output(context, "\\r"), |
| 2346 | | '\t' => try output(context, "\\t"), |
| 2337 | '\\' => try out_stream.writeAll("\\\\"), |
| 2338 | '\"' => try out_stream.writeAll("\\\""), |
| 2339 | '/' => try out_stream.writeAll("\\/"), |
| 2340 | 0x8 => try out_stream.writeAll("\\b"), |
| 2341 | 0xC => try out_stream.writeAll("\\f"), |
| 2342 | '\n' => try out_stream.writeAll("\\n"), |
| 2343 | '\r' => try out_stream.writeAll("\\r"), |
| 2344 | '\t' => try out_stream.writeAll("\\t"), |
| 2347 | 2345 | else => { |
| 2348 | 2346 | const ulen = std.unicode.utf8ByteSequenceLength(value[i]) catch unreachable; |
| 2349 | 2347 | const codepoint = std.unicode.utf8Decode(value[i .. i + ulen]) catch unreachable; |
| ... | ... | @@ -2351,40 +2349,40 @@ pub fn stringify( |
| 2351 | 2349 | // If the character is in the Basic Multilingual Plane (U+0000 through U+FFFF), |
| 2352 | 2350 | // then it may be represented as a six-character sequence: a reverse solidus, followed |
| 2353 | 2351 | // by the lowercase letter u, followed by four hexadecimal digits that encode the character's code point. |
| 2354 | | try output(context, "\\u"); |
| 2355 | | try std.fmt.formatIntValue(codepoint, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, context, Errors, output); |
| 2352 | try out_stream.writeAll("\\u"); |
| 2353 | try std.fmtstream.formatIntValue(codepoint, "x", std.fmtstream.FormatOptions{ .width = 4, .fill = '0' }, out_stream); |
| 2356 | 2354 | } else { |
| 2357 | 2355 | // To escape an extended character that is not in the Basic Multilingual Plane, |
| 2358 | 2356 | // the character is represented as a 12-character sequence, encoding the UTF-16 surrogate pair. |
| 2359 | 2357 | const high = @intCast(u16, (codepoint - 0x10000) >> 10) + 0xD800; |
| 2360 | 2358 | const low = @intCast(u16, codepoint & 0x3FF) + 0xDC00; |
| 2361 | | try output(context, "\\u"); |
| 2362 | | try std.fmt.formatIntValue(high, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, context, Errors, output); |
| 2363 | | try output(context, "\\u"); |
| 2364 | | try std.fmt.formatIntValue(low, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, context, Errors, output); |
| 2359 | try out_stream.writeAll("\\u"); |
| 2360 | try std.fmtstream.formatIntValue(high, "x", std.fmtstream.FormatOptions{ .width = 4, .fill = '0' }, out_stream); |
| 2361 | try out_stream.writeAll("\\u"); |
| 2362 | try std.fmtstream.formatIntValue(low, "x", std.fmtstream.FormatOptions{ .width = 4, .fill = '0' }, out_stream); |
| 2365 | 2363 | } |
| 2366 | 2364 | i += ulen - 1; |
| 2367 | 2365 | }, |
| 2368 | 2366 | } |
| 2369 | 2367 | } |
| 2370 | | try output(context, "\""); |
| 2368 | try out_stream.writeAll("\""); |
| 2371 | 2369 | return; |
| 2372 | 2370 | } |
| 2373 | 2371 | |
| 2374 | | try output(context, "["); |
| 2372 | try out_stream.writeAll("["); |
| 2375 | 2373 | for (value) |x, i| { |
| 2376 | 2374 | if (i != 0) { |
| 2377 | | try output(context, ","); |
| 2375 | try out_stream.writeAll(","); |
| 2378 | 2376 | } |
| 2379 | | try stringify(x, options, context, Errors, output); |
| 2377 | try stringify(x, options, out_stream); |
| 2380 | 2378 | } |
| 2381 | | try output(context, "]"); |
| 2379 | try out_stream.writeAll("]"); |
| 2382 | 2380 | return; |
| 2383 | 2381 | }, |
| 2384 | 2382 | else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"), |
| 2385 | 2383 | }, |
| 2386 | 2384 | .Array => |info| { |
| 2387 | | return try stringify(value[0..], options, context, Errors, output); |
| 2385 | return try stringify(value[0..], options, out_stream); |
| 2388 | 2386 | }, |
| 2389 | 2387 | else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"), |
| 2390 | 2388 | } |
| ... | ... | @@ -2392,10 +2390,26 @@ pub fn stringify( |
| 2392 | 2390 | } |
| 2393 | 2391 | |
| 2394 | 2392 | fn teststringify(expected: []const u8, value: var) !void { |
| 2395 | | const TestStringifyContext = struct { |
| 2393 | const ValidationOutStream = struct { |
| 2394 | const Self = @This(); |
| 2395 | pub const OutStream = std.io.OutStream(*Self, Error, write); |
| 2396 | pub const Error = error{ |
| 2397 | TooMuchData, |
| 2398 | DifferentData, |
| 2399 | }; |
| 2400 | |
| 2396 | 2401 | expected_remaining: []const u8, |
| 2397 | | fn testStringifyWrite(context: *@This(), bytes: []const u8) !void { |
| 2398 | | if (context.expected_remaining.len < bytes.len) { |
| 2402 | |
| 2403 | fn init(exp: []const u8) Self { |
| 2404 | return .{ .expected_remaining = exp }; |
| 2405 | } |
| 2406 | |
| 2407 | pub fn outStream(self: *Self) OutStream { |
| 2408 | return .{ .context = self }; |
| 2409 | } |
| 2410 | |
| 2411 | fn write(self: *Self, bytes: []const u8) Error!usize { |
| 2412 | if (self.expected_remaining.len < bytes.len) { |
| 2399 | 2413 | std.debug.warn( |
| 2400 | 2414 | \\====== expected this output: ========= |
| 2401 | 2415 | \\{} |
| ... | ... | @@ -2403,12 +2417,12 @@ fn teststringify(expected: []const u8, value: var) !void { |
| 2403 | 2417 | \\{} |
| 2404 | 2418 | \\====================================== |
| 2405 | 2419 | , .{ |
| 2406 | | context.expected_remaining, |
| 2420 | self.expected_remaining, |
| 2407 | 2421 | bytes, |
| 2408 | 2422 | }); |
| 2409 | 2423 | return error.TooMuchData; |
| 2410 | 2424 | } |
| 2411 | | if (!mem.eql(u8, context.expected_remaining[0..bytes.len], bytes)) { |
| 2425 | if (!mem.eql(u8, self.expected_remaining[0..bytes.len], bytes)) { |
| 2412 | 2426 | std.debug.warn( |
| 2413 | 2427 | \\====== expected this output: ========= |
| 2414 | 2428 | \\{} |
| ... | ... | @@ -2416,21 +2430,19 @@ fn teststringify(expected: []const u8, value: var) !void { |
| 2416 | 2430 | \\{} |
| 2417 | 2431 | \\====================================== |
| 2418 | 2432 | , .{ |
| 2419 | | context.expected_remaining[0..bytes.len], |
| 2433 | self.expected_remaining[0..bytes.len], |
| 2420 | 2434 | bytes, |
| 2421 | 2435 | }); |
| 2422 | 2436 | return error.DifferentData; |
| 2423 | 2437 | } |
| 2424 | | context.expected_remaining = context.expected_remaining[bytes.len..]; |
| 2438 | self.expected_remaining = self.expected_remaining[bytes.len..]; |
| 2439 | return bytes.len; |
| 2425 | 2440 | } |
| 2426 | 2441 | }; |
| 2427 | | var buf: [100]u8 = undefined; |
| 2428 | | var context = TestStringifyContext{ .expected_remaining = expected }; |
| 2429 | | try stringify(value, StringifyOptions{}, &context, error{ |
| 2430 | | TooMuchData, |
| 2431 | | DifferentData, |
| 2432 | | }, TestStringifyContext.testStringifyWrite); |
| 2433 | | if (context.expected_remaining.len > 0) return error.NotEnoughData; |
| 2442 | |
| 2443 | var vos = ValidationOutStream.init(expected); |
| 2444 | try stringify(value, StringifyOptions{}, vos.outStream()); |
| 2445 | if (vos.expected_remaining.len > 0) return error.NotEnoughData; |
| 2434 | 2446 | } |
| 2435 | 2447 | |
| 2436 | 2448 | test "stringify basic types" { |
| ... | ... | @@ -2498,13 +2510,11 @@ test "stringify struct with custom stringifier" { |
| 2498 | 2510 | pub fn jsonStringify( |
| 2499 | 2511 | value: Self, |
| 2500 | 2512 | options: StringifyOptions, |
| 2501 | | context: var, |
| 2502 | | comptime Errors: type, |
| 2503 | | comptime output: fn (@TypeOf(context), []const u8) Errors!void, |
| 2513 | out_stream: var, |
| 2504 | 2514 | ) !void { |
| 2505 | | try output(context, "[\"something special\","); |
| 2506 | | try stringify(42, options, context, Errors, output); |
| 2507 | | try output(context, "]"); |
| 2515 | try out_stream.writeAll("[\"something special\","); |
| 2516 | try stringify(42, options, out_stream); |
| 2517 | try out_stream.writeAll("]"); |
| 2508 | 2518 | } |
| 2509 | 2519 | }{ .foo = 42 }); |
| 2510 | 2520 | } |