| ... | ... | @@ -1233,42 +1233,119 @@ pub const Value = union(enum) { |
| 1233 | 1233 | Array: Array, |
| 1234 | 1234 | Object: ObjectMap, |
| 1235 | 1235 | |
| 1236 | pub fn jsonStringify( |
| 1237 | value: @This(), |
| 1238 | options: StringifyOptions, |
| 1239 | out_stream: var, |
| 1240 | ) @TypeOf(out_stream).Error!void { |
| 1241 | switch (value) { |
| 1242 | .Null => try stringify(null, options, out_stream), |
| 1243 | .Bool => |inner| try stringify(inner, options, out_stream), |
| 1244 | .Integer => |inner| try stringify(inner, options, out_stream), |
| 1245 | .Float => |inner| try stringify(inner, options, out_stream), |
| 1246 | .String => |inner| try stringify(inner, options, out_stream), |
| 1247 | .Array => |inner| try stringify(inner.span(), options, out_stream), |
| 1248 | .Object => |inner| { |
| 1249 | try out_stream.writeByte('{'); |
| 1250 | var field_output = false; |
| 1251 | var child_options = options; |
| 1252 | if (child_options.whitespace) |*child_whitespace| { |
| 1253 | child_whitespace.indent_level += 1; |
| 1254 | } |
| 1255 | var it = inner.iterator(); |
| 1256 | while (it.next()) |entry| { |
| 1257 | if (!field_output) { |
| 1258 | field_output = true; |
| 1259 | } else { |
| 1260 | try out_stream.writeByte(','); |
| 1261 | } |
| 1262 | if (child_options.whitespace) |child_whitespace| { |
| 1263 | try out_stream.writeByte('\n'); |
| 1264 | try child_whitespace.outputIndent(out_stream); |
| 1265 | } |
| 1266 | |
| 1267 | try stringify(entry.key, options, out_stream); |
| 1268 | try out_stream.writeByte(':'); |
| 1269 | if (child_options.whitespace) |child_whitespace| { |
| 1270 | if (child_whitespace.separator) { |
| 1271 | try out_stream.writeByte(' '); |
| 1272 | } |
| 1273 | } |
| 1274 | try stringify(entry.value, child_options, out_stream); |
| 1275 | } |
| 1276 | if (field_output) { |
| 1277 | if (options.whitespace) |whitespace| { |
| 1278 | try out_stream.writeByte('\n'); |
| 1279 | try whitespace.outputIndent(out_stream); |
| 1280 | } |
| 1281 | } |
| 1282 | try out_stream.writeByte('}'); |
| 1283 | }, |
| 1284 | } |
| 1285 | } |
| 1286 | |
| 1236 | 1287 | pub fn dump(self: Value) void { |
| 1237 | 1288 | var held = std.debug.getStderrMutex().acquire(); |
| 1238 | 1289 | defer held.release(); |
| 1239 | 1290 | |
| 1240 | 1291 | const stderr = std.debug.getStderrStream(); |
| 1241 | | self.dumpStream(stderr, 1024) catch return; |
| 1292 | std.json.stringify(self, std.json.StringifyOptions{ .whitespace = null }, stderr) catch return; |
| 1242 | 1293 | } |
| 1294 | }; |
| 1243 | 1295 | |
| 1244 | | pub fn dumpIndent(self: Value, comptime indent: usize) void { |
| 1245 | | if (indent == 0) { |
| 1246 | | self.dump(); |
| 1247 | | } else { |
| 1248 | | var held = std.debug.getStderrMutex().acquire(); |
| 1249 | | defer held.release(); |
| 1250 | | |
| 1251 | | const stderr = std.debug.getStderrStream(); |
| 1252 | | self.dumpStreamIndent(indent, stderr, 1024) catch return; |
| 1253 | | } |
| 1296 | test "Value.jsonStringify" { |
| 1297 | { |
| 1298 | var buffer: [10]u8 = undefined; |
| 1299 | var fbs = std.io.fixedBufferStream(&buffer); |
| 1300 | try @as(Value, .Null).jsonStringify(.{}, fbs.outStream()); |
| 1301 | testing.expectEqualSlices(u8, fbs.getWritten(), "null"); |
| 1254 | 1302 | } |
| 1255 | | |
| 1256 | | pub fn dumpStream(self: @This(), stream: var, comptime max_depth: usize) !void { |
| 1257 | | var w = std.json.WriteStream(@TypeOf(stream).Child, max_depth).init(stream); |
| 1258 | | w.newline = ""; |
| 1259 | | w.one_indent = ""; |
| 1260 | | w.space = ""; |
| 1261 | | try w.emitJson(self); |
| 1303 | { |
| 1304 | var buffer: [10]u8 = undefined; |
| 1305 | var fbs = std.io.fixedBufferStream(&buffer); |
| 1306 | try (Value{ .Bool = true }).jsonStringify(.{}, fbs.outStream()); |
| 1307 | testing.expectEqualSlices(u8, fbs.getWritten(), "true"); |
| 1262 | 1308 | } |
| 1263 | | |
| 1264 | | pub fn dumpStreamIndent(self: @This(), comptime indent: usize, stream: var, comptime max_depth: usize) !void { |
| 1265 | | var one_indent = " " ** indent; |
| 1266 | | |
| 1267 | | var w = std.json.WriteStream(@TypeOf(stream).Child, max_depth).init(stream); |
| 1268 | | w.one_indent = one_indent; |
| 1269 | | try w.emitJson(self); |
| 1309 | { |
| 1310 | var buffer: [10]u8 = undefined; |
| 1311 | var fbs = std.io.fixedBufferStream(&buffer); |
| 1312 | try (Value{ .Integer = 42 }).jsonStringify(.{}, fbs.outStream()); |
| 1313 | testing.expectEqualSlices(u8, fbs.getWritten(), "42"); |
| 1270 | 1314 | } |
| 1271 | | }; |
| 1315 | { |
| 1316 | var buffer: [10]u8 = undefined; |
| 1317 | var fbs = std.io.fixedBufferStream(&buffer); |
| 1318 | try (Value{ .Float = 42 }).jsonStringify(.{}, fbs.outStream()); |
| 1319 | testing.expectEqualSlices(u8, fbs.getWritten(), "4.2e+01"); |
| 1320 | } |
| 1321 | { |
| 1322 | var buffer: [10]u8 = undefined; |
| 1323 | var fbs = std.io.fixedBufferStream(&buffer); |
| 1324 | try (Value{ .String = "weeee" }).jsonStringify(.{}, fbs.outStream()); |
| 1325 | testing.expectEqualSlices(u8, fbs.getWritten(), "\"weeee\""); |
| 1326 | } |
| 1327 | { |
| 1328 | var buffer: [10]u8 = undefined; |
| 1329 | var fbs = std.io.fixedBufferStream(&buffer); |
| 1330 | try (Value{ |
| 1331 | .Array = Array.fromOwnedSlice(undefined, &[_]Value{ |
| 1332 | .{ .Integer = 1 }, |
| 1333 | .{ .Integer = 2 }, |
| 1334 | .{ .Integer = 3 }, |
| 1335 | }), |
| 1336 | }).jsonStringify(.{}, fbs.outStream()); |
| 1337 | testing.expectEqualSlices(u8, fbs.getWritten(), "[1,2,3]"); |
| 1338 | } |
| 1339 | { |
| 1340 | var buffer: [10]u8 = undefined; |
| 1341 | var fbs = std.io.fixedBufferStream(&buffer); |
| 1342 | var obj = ObjectMap.init(testing.allocator); |
| 1343 | defer obj.deinit(); |
| 1344 | try obj.putNoClobber("a", .{ .String = "b" }); |
| 1345 | try (Value{ .Object = obj }).jsonStringify(.{}, fbs.outStream()); |
| 1346 | testing.expectEqualSlices(u8, fbs.getWritten(), "{\"a\":\"b\"}"); |
| 1347 | } |
| 1348 | } |
| 1272 | 1349 | |
| 1273 | 1350 | pub const ParseOptions = struct { |
| 1274 | 1351 | allocator: ?*Allocator = null, |
| ... | ... | @@ -2241,11 +2318,86 @@ test "string copy option" { |
| 2241 | 2318 | } |
| 2242 | 2319 | |
| 2243 | 2320 | pub const StringifyOptions = struct { |
| 2244 | | // TODO: indentation options? |
| 2245 | | // TODO: make escaping '/' in strings optional? |
| 2246 | | // TODO: allow picking if []u8 is string or array? |
| 2321 | pub const Whitespace = struct { |
| 2322 | /// How many indentation levels deep are we? |
| 2323 | indent_level: usize = 0, |
| 2324 | |
| 2325 | pub const Indentation = union(enum) { |
| 2326 | Space: u8, |
| 2327 | Tab: void, |
| 2328 | }; |
| 2329 | |
| 2330 | /// What character(s) should be used for indentation? |
| 2331 | indent: Indentation = Indentation{ .Space = 4 }, |
| 2332 | |
| 2333 | fn outputIndent( |
| 2334 | whitespace: @This(), |
| 2335 | out_stream: var, |
| 2336 | ) @TypeOf(out_stream).Error!void { |
| 2337 | var char: u8 = undefined; |
| 2338 | var n_chars: usize = undefined; |
| 2339 | switch (whitespace.indent) { |
| 2340 | .Space => |n_spaces| { |
| 2341 | char = ' '; |
| 2342 | n_chars = n_spaces; |
| 2343 | }, |
| 2344 | .Tab => { |
| 2345 | char = '\t'; |
| 2346 | n_chars = 1; |
| 2347 | }, |
| 2348 | } |
| 2349 | n_chars *= whitespace.indent_level; |
| 2350 | try out_stream.writeByteNTimes(char, n_chars); |
| 2351 | } |
| 2352 | |
| 2353 | /// After a colon, should whitespace be inserted? |
| 2354 | separator: bool = true, |
| 2355 | }; |
| 2356 | |
| 2357 | /// Controls the whitespace emitted |
| 2358 | whitespace: ?Whitespace = null, |
| 2359 | |
| 2360 | /// Should []u8 be serialised as a string? or an array? |
| 2361 | pub const StringOptions = union(enum) { |
| 2362 | Array, |
| 2363 | |
| 2364 | /// String output options |
| 2365 | const StringOutputOptions = struct { |
| 2366 | /// Should '/' be escaped in strings? |
| 2367 | escape_solidus: bool = false, |
| 2368 | |
| 2369 | /// Should unicode characters be escaped in strings? |
| 2370 | escape_unicode: bool = false, |
| 2371 | }; |
| 2372 | String: StringOutputOptions, |
| 2373 | }; |
| 2374 | |
| 2375 | string: StringOptions = StringOptions{ .String = .{} }, |
| 2247 | 2376 | }; |
| 2248 | 2377 | |
| 2378 | fn outputUnicodeEscape( |
| 2379 | codepoint: u21, |
| 2380 | out_stream: var, |
| 2381 | ) !void { |
| 2382 | if (codepoint <= 0xFFFF) { |
| 2383 | // If the character is in the Basic Multilingual Plane (U+0000 through U+FFFF), |
| 2384 | // then it may be represented as a six-character sequence: a reverse solidus, followed |
| 2385 | // by the lowercase letter u, followed by four hexadecimal digits that encode the character's code point. |
| 2386 | try out_stream.writeAll("\\u"); |
| 2387 | try std.fmt.formatIntValue(codepoint, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream); |
| 2388 | } else { |
| 2389 | assert(codepoint <= 0x10FFFF); |
| 2390 | // To escape an extended character that is not in the Basic Multilingual Plane, |
| 2391 | // the character is represented as a 12-character sequence, encoding the UTF-16 surrogate pair. |
| 2392 | const high = @intCast(u16, (codepoint - 0x10000) >> 10) + 0xD800; |
| 2393 | const low = @intCast(u16, codepoint & 0x3FF) + 0xDC00; |
| 2394 | try out_stream.writeAll("\\u"); |
| 2395 | try std.fmt.formatIntValue(high, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream); |
| 2396 | try out_stream.writeAll("\\u"); |
| 2397 | try std.fmt.formatIntValue(low, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream); |
| 2398 | } |
| 2399 | } |
| 2400 | |
| 2249 | 2401 | pub fn stringify( |
| 2250 | 2402 | value: var, |
| 2251 | 2403 | options: StringifyOptions, |
| ... | ... | @@ -2262,11 +2414,14 @@ pub fn stringify( |
| 2262 | 2414 | .Bool => { |
| 2263 | 2415 | return out_stream.writeAll(if (value) "true" else "false"); |
| 2264 | 2416 | }, |
| 2417 | .Null => { |
| 2418 | return out_stream.writeAll("null"); |
| 2419 | }, |
| 2265 | 2420 | .Optional => { |
| 2266 | 2421 | if (value) |payload| { |
| 2267 | 2422 | return try stringify(payload, options, out_stream); |
| 2268 | 2423 | } else { |
| 2269 | | return out_stream.writeAll("null"); |
| 2424 | return try stringify(null, options, out_stream); |
| 2270 | 2425 | } |
| 2271 | 2426 | }, |
| 2272 | 2427 | .Enum => { |
| ... | ... | @@ -2297,8 +2452,12 @@ pub fn stringify( |
| 2297 | 2452 | return value.jsonStringify(options, out_stream); |
| 2298 | 2453 | } |
| 2299 | 2454 | |
| 2300 | | try out_stream.writeAll("{"); |
| 2455 | try out_stream.writeByte('{'); |
| 2301 | 2456 | comptime var field_output = false; |
| 2457 | var child_options = options; |
| 2458 | if (child_options.whitespace) |*child_whitespace| { |
| 2459 | child_whitespace.indent_level += 1; |
| 2460 | } |
| 2302 | 2461 | inline for (S.fields) |Field, field_i| { |
| 2303 | 2462 | // don't include void fields |
| 2304 | 2463 | if (Field.field_type == void) continue; |
| ... | ... | @@ -2306,14 +2465,28 @@ pub fn stringify( |
| 2306 | 2465 | if (!field_output) { |
| 2307 | 2466 | field_output = true; |
| 2308 | 2467 | } else { |
| 2309 | | try out_stream.writeAll(","); |
| 2468 | try out_stream.writeByte(','); |
| 2469 | } |
| 2470 | if (child_options.whitespace) |child_whitespace| { |
| 2471 | try out_stream.writeByte('\n'); |
| 2472 | try child_whitespace.outputIndent(out_stream); |
| 2310 | 2473 | } |
| 2311 | | |
| 2312 | 2474 | try stringify(Field.name, options, out_stream); |
| 2313 | | try out_stream.writeAll(":"); |
| 2314 | | try stringify(@field(value, Field.name), options, out_stream); |
| 2475 | try out_stream.writeByte(':'); |
| 2476 | if (child_options.whitespace) |child_whitespace| { |
| 2477 | if (child_whitespace.separator) { |
| 2478 | try out_stream.writeByte(' '); |
| 2479 | } |
| 2480 | } |
| 2481 | try stringify(@field(value, Field.name), child_options, out_stream); |
| 2482 | } |
| 2483 | if (field_output) { |
| 2484 | if (options.whitespace) |whitespace| { |
| 2485 | try out_stream.writeByte('\n'); |
| 2486 | try whitespace.outputIndent(out_stream); |
| 2487 | } |
| 2315 | 2488 | } |
| 2316 | | try out_stream.writeAll("}"); |
| 2489 | try out_stream.writeByte('}'); |
| 2317 | 2490 | return; |
| 2318 | 2491 | }, |
| 2319 | 2492 | .Pointer => |ptr_info| switch (ptr_info.size) { |
| ... | ... | @@ -2329,17 +2502,26 @@ pub fn stringify( |
| 2329 | 2502 | }, |
| 2330 | 2503 | // TODO: .Many when there is a sentinel (waiting for https://github.com/ziglang/zig/pull/3972) |
| 2331 | 2504 | .Slice => { |
| 2332 | | if (ptr_info.child == u8 and std.unicode.utf8ValidateSlice(value)) { |
| 2333 | | try out_stream.writeAll("\""); |
| 2505 | if (ptr_info.child == u8 and options.string == .String and std.unicode.utf8ValidateSlice(value)) { |
| 2506 | try out_stream.writeByte('\"'); |
| 2334 | 2507 | var i: usize = 0; |
| 2335 | 2508 | while (i < value.len) : (i += 1) { |
| 2336 | 2509 | switch (value[i]) { |
| 2337 | | // normal ascii characters |
| 2338 | | 0x20...0x21, 0x23...0x2E, 0x30...0x5B, 0x5D...0x7F => try out_stream.writeAll(value[i .. i + 1]), |
| 2339 | | // control characters with short escapes |
| 2510 | // normal ascii character |
| 2511 | 0x20...0x21, 0x23...0x2E, 0x30...0x5B, 0x5D...0x7F => |c| try out_stream.writeByte(c), |
| 2512 | // only 2 characters that *must* be escaped |
| 2340 | 2513 | '\\' => try out_stream.writeAll("\\\\"), |
| 2341 | 2514 | '\"' => try out_stream.writeAll("\\\""), |
| 2342 | | '/' => try out_stream.writeAll("\\/"), |
| 2515 | // solidus is optional to escape |
| 2516 | '/' => { |
| 2517 | if (options.string.String.escape_solidus) { |
| 2518 | try out_stream.writeAll("\\/"); |
| 2519 | } else { |
| 2520 | try out_stream.writeByte('\\'); |
| 2521 | } |
| 2522 | }, |
| 2523 | // control characters with short escapes |
| 2524 | // TODO: option to switch between unicode and 'short' forms? |
| 2343 | 2525 | 0x8 => try out_stream.writeAll("\\b"), |
| 2344 | 2526 | 0xC => try out_stream.writeAll("\\f"), |
| 2345 | 2527 | '\n' => try out_stream.writeAll("\\n"), |
| ... | ... | @@ -2347,39 +2529,43 @@ pub fn stringify( |
| 2347 | 2529 | '\t' => try out_stream.writeAll("\\t"), |
| 2348 | 2530 | else => { |
| 2349 | 2531 | const ulen = std.unicode.utf8ByteSequenceLength(value[i]) catch unreachable; |
| 2350 | | const codepoint = std.unicode.utf8Decode(value[i .. i + ulen]) catch unreachable; |
| 2351 | | if (codepoint <= 0xFFFF) { |
| 2352 | | // If the character is in the Basic Multilingual Plane (U+0000 through U+FFFF), |
| 2353 | | // then it may be represented as a six-character sequence: a reverse solidus, followed |
| 2354 | | // by the lowercase letter u, followed by four hexadecimal digits that encode the character's code point. |
| 2355 | | try out_stream.writeAll("\\u"); |
| 2356 | | try std.fmt.formatIntValue(codepoint, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream); |
| 2532 | // control characters (only things left with 1 byte length) should always be printed as unicode escapes |
| 2533 | if (ulen == 1 or options.string.String.escape_unicode) { |
| 2534 | const codepoint = std.unicode.utf8Decode(value[i .. i + ulen]) catch unreachable; |
| 2535 | try outputUnicodeEscape(codepoint, out_stream); |
| 2357 | 2536 | } else { |
| 2358 | | // To escape an extended character that is not in the Basic Multilingual Plane, |
| 2359 | | // the character is represented as a 12-character sequence, encoding the UTF-16 surrogate pair. |
| 2360 | | const high = @intCast(u16, (codepoint - 0x10000) >> 10) + 0xD800; |
| 2361 | | const low = @intCast(u16, codepoint & 0x3FF) + 0xDC00; |
| 2362 | | try out_stream.writeAll("\\u"); |
| 2363 | | try std.fmt.formatIntValue(high, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream); |
| 2364 | | try out_stream.writeAll("\\u"); |
| 2365 | | try std.fmt.formatIntValue(low, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream); |
| 2537 | try out_stream.writeAll(value[i .. i + ulen]); |
| 2366 | 2538 | } |
| 2367 | 2539 | i += ulen - 1; |
| 2368 | 2540 | }, |
| 2369 | 2541 | } |
| 2370 | 2542 | } |
| 2371 | | try out_stream.writeAll("\""); |
| 2543 | try out_stream.writeByte('\"'); |
| 2372 | 2544 | return; |
| 2373 | 2545 | } |
| 2374 | 2546 | |
| 2375 | | try out_stream.writeAll("["); |
| 2547 | try out_stream.writeByte('['); |
| 2548 | var child_options = options; |
| 2549 | if (child_options.whitespace) |*whitespace| { |
| 2550 | whitespace.indent_level += 1; |
| 2551 | } |
| 2376 | 2552 | for (value) |x, i| { |
| 2377 | 2553 | if (i != 0) { |
| 2378 | | try out_stream.writeAll(","); |
| 2554 | try out_stream.writeByte(','); |
| 2555 | } |
| 2556 | if (child_options.whitespace) |child_whitespace| { |
| 2557 | try out_stream.writeByte('\n'); |
| 2558 | try child_whitespace.outputIndent(out_stream); |
| 2379 | 2559 | } |
| 2380 | | try stringify(x, options, out_stream); |
| 2560 | try stringify(x, child_options, out_stream); |
| 2381 | 2561 | } |
| 2382 | | try out_stream.writeAll("]"); |
| 2562 | if (value.len != 0) { |
| 2563 | if (options.whitespace) |whitespace| { |
| 2564 | try out_stream.writeByte('\n'); |
| 2565 | try whitespace.outputIndent(out_stream); |
| 2566 | } |
| 2567 | } |
| 2568 | try out_stream.writeByte(']'); |
| 2383 | 2569 | return; |
| 2384 | 2570 | }, |
| 2385 | 2571 | else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"), |
| ... | ... | @@ -2390,7 +2576,7 @@ pub fn stringify( |
| 2390 | 2576 | unreachable; |
| 2391 | 2577 | } |
| 2392 | 2578 | |
| 2393 | | fn teststringify(expected: []const u8, value: var) !void { |
| 2579 | fn teststringify(expected: []const u8, value: var, options: StringifyOptions) !void { |
| 2394 | 2580 | const ValidationOutStream = struct { |
| 2395 | 2581 | const Self = @This(); |
| 2396 | 2582 | pub const OutStream = std.io.OutStream(*Self, Error, write); |
| ... | ... | @@ -2442,55 +2628,105 @@ fn teststringify(expected: []const u8, value: var) !void { |
| 2442 | 2628 | }; |
| 2443 | 2629 | |
| 2444 | 2630 | var vos = ValidationOutStream.init(expected); |
| 2445 | | try stringify(value, StringifyOptions{}, vos.outStream()); |
| 2631 | try stringify(value, options, vos.outStream()); |
| 2446 | 2632 | if (vos.expected_remaining.len > 0) return error.NotEnoughData; |
| 2447 | 2633 | } |
| 2448 | 2634 | |
| 2449 | 2635 | test "stringify basic types" { |
| 2450 | | try teststringify("false", false); |
| 2451 | | try teststringify("true", true); |
| 2452 | | try teststringify("null", @as(?u8, null)); |
| 2453 | | try teststringify("null", @as(?*u32, null)); |
| 2454 | | try teststringify("42", 42); |
| 2455 | | try teststringify("4.2e+01", 42.0); |
| 2456 | | try teststringify("42", @as(u8, 42)); |
| 2457 | | try teststringify("42", @as(u128, 42)); |
| 2458 | | try teststringify("4.2e+01", @as(f32, 42)); |
| 2459 | | try teststringify("4.2e+01", @as(f64, 42)); |
| 2636 | try teststringify("false", false, StringifyOptions{}); |
| 2637 | try teststringify("true", true, StringifyOptions{}); |
| 2638 | try teststringify("null", @as(?u8, null), StringifyOptions{}); |
| 2639 | try teststringify("null", @as(?*u32, null), StringifyOptions{}); |
| 2640 | try teststringify("42", 42, StringifyOptions{}); |
| 2641 | try teststringify("4.2e+01", 42.0, StringifyOptions{}); |
| 2642 | try teststringify("42", @as(u8, 42), StringifyOptions{}); |
| 2643 | try teststringify("42", @as(u128, 42), StringifyOptions{}); |
| 2644 | try teststringify("4.2e+01", @as(f32, 42), StringifyOptions{}); |
| 2645 | try teststringify("4.2e+01", @as(f64, 42), StringifyOptions{}); |
| 2460 | 2646 | } |
| 2461 | 2647 | |
| 2462 | 2648 | test "stringify string" { |
| 2463 | | try teststringify("\"hello\"", "hello"); |
| 2464 | | try teststringify("\"with\\nescapes\\r\"", "with\nescapes\r"); |
| 2465 | | try teststringify("\"with unicode\\u0001\"", "with unicode\u{1}"); |
| 2466 | | try teststringify("\"with unicode\\u0080\"", "with unicode\u{80}"); |
| 2467 | | try teststringify("\"with unicode\\u00ff\"", "with unicode\u{FF}"); |
| 2468 | | try teststringify("\"with unicode\\u0100\"", "with unicode\u{100}"); |
| 2469 | | try teststringify("\"with unicode\\u0800\"", "with unicode\u{800}"); |
| 2470 | | try teststringify("\"with unicode\\u8000\"", "with unicode\u{8000}"); |
| 2471 | | try teststringify("\"with unicode\\ud799\"", "with unicode\u{D799}"); |
| 2472 | | try teststringify("\"with unicode\\ud800\\udc00\"", "with unicode\u{10000}"); |
| 2473 | | try teststringify("\"with unicode\\udbff\\udfff\"", "with unicode\u{10FFFF}"); |
| 2649 | try teststringify("\"hello\"", "hello", StringifyOptions{}); |
| 2650 | try teststringify("\"with\\nescapes\\r\"", "with\nescapes\r", StringifyOptions{}); |
| 2651 | try teststringify("\"with\\nescapes\\r\"", "with\nescapes\r", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); |
| 2652 | try teststringify("\"with unicode\\u0001\"", "with unicode\u{1}", StringifyOptions{}); |
| 2653 | try teststringify("\"with unicode\\u0001\"", "with unicode\u{1}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); |
| 2654 | try teststringify("\"with unicode\u{80}\"", "with unicode\u{80}", StringifyOptions{}); |
| 2655 | try teststringify("\"with unicode\\u0080\"", "with unicode\u{80}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); |
| 2656 | try teststringify("\"with unicode\u{FF}\"", "with unicode\u{FF}", StringifyOptions{}); |
| 2657 | try teststringify("\"with unicode\\u00ff\"", "with unicode\u{FF}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); |
| 2658 | try teststringify("\"with unicode\u{100}\"", "with unicode\u{100}", StringifyOptions{}); |
| 2659 | try teststringify("\"with unicode\\u0100\"", "with unicode\u{100}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); |
| 2660 | try teststringify("\"with unicode\u{800}\"", "with unicode\u{800}", StringifyOptions{}); |
| 2661 | try teststringify("\"with unicode\\u0800\"", "with unicode\u{800}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); |
| 2662 | try teststringify("\"with unicode\u{8000}\"", "with unicode\u{8000}", StringifyOptions{}); |
| 2663 | try teststringify("\"with unicode\\u8000\"", "with unicode\u{8000}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); |
| 2664 | try teststringify("\"with unicode\u{D799}\"", "with unicode\u{D799}", StringifyOptions{}); |
| 2665 | try teststringify("\"with unicode\\ud799\"", "with unicode\u{D799}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); |
| 2666 | try teststringify("\"with unicode\u{10000}\"", "with unicode\u{10000}", StringifyOptions{}); |
| 2667 | try teststringify("\"with unicode\\ud800\\udc00\"", "with unicode\u{10000}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); |
| 2668 | try teststringify("\"with unicode\u{10FFFF}\"", "with unicode\u{10FFFF}", StringifyOptions{}); |
| 2669 | try teststringify("\"with unicode\\udbff\\udfff\"", "with unicode\u{10FFFF}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); |
| 2474 | 2670 | } |
| 2475 | 2671 | |
| 2476 | 2672 | test "stringify tagged unions" { |
| 2477 | 2673 | try teststringify("42", union(enum) { |
| 2478 | 2674 | Foo: u32, |
| 2479 | 2675 | Bar: bool, |
| 2480 | | }{ .Foo = 42 }); |
| 2676 | }{ .Foo = 42 }, StringifyOptions{}); |
| 2481 | 2677 | } |
| 2482 | 2678 | |
| 2483 | 2679 | test "stringify struct" { |
| 2484 | 2680 | try teststringify("{\"foo\":42}", struct { |
| 2485 | 2681 | foo: u32, |
| 2486 | | }{ .foo = 42 }); |
| 2682 | }{ .foo = 42 }, StringifyOptions{}); |
| 2683 | } |
| 2684 | |
| 2685 | test "stringify struct with indentation" { |
| 2686 | try teststringify( |
| 2687 | \\{ |
| 2688 | \\ "foo": 42, |
| 2689 | \\ "bar": [ |
| 2690 | \\ 1, |
| 2691 | \\ 2, |
| 2692 | \\ 3 |
| 2693 | \\ ] |
| 2694 | \\} |
| 2695 | , |
| 2696 | struct { |
| 2697 | foo: u32, |
| 2698 | bar: [3]u32, |
| 2699 | }{ |
| 2700 | .foo = 42, |
| 2701 | .bar = .{ 1, 2, 3 }, |
| 2702 | }, |
| 2703 | StringifyOptions{ |
| 2704 | .whitespace = .{}, |
| 2705 | }, |
| 2706 | ); |
| 2707 | try teststringify( |
| 2708 | "{\n\t\"foo\":42,\n\t\"bar\":[\n\t\t1,\n\t\t2,\n\t\t3\n\t]\n}", |
| 2709 | struct { |
| 2710 | foo: u32, |
| 2711 | bar: [3]u32, |
| 2712 | }{ |
| 2713 | .foo = 42, |
| 2714 | .bar = .{ 1, 2, 3 }, |
| 2715 | }, |
| 2716 | StringifyOptions{ |
| 2717 | .whitespace = .{ |
| 2718 | .indent = .Tab, |
| 2719 | .separator = false, |
| 2720 | }, |
| 2721 | }, |
| 2722 | ); |
| 2487 | 2723 | } |
| 2488 | 2724 | |
| 2489 | 2725 | test "stringify struct with void field" { |
| 2490 | 2726 | try teststringify("{\"foo\":42}", struct { |
| 2491 | 2727 | foo: u32, |
| 2492 | 2728 | bar: void = {}, |
| 2493 | | }{ .foo = 42 }); |
| 2729 | }{ .foo = 42 }, StringifyOptions{}); |
| 2494 | 2730 | } |
| 2495 | 2731 | |
| 2496 | 2732 | test "stringify array of structs" { |
| ... | ... | @@ -2501,7 +2737,7 @@ test "stringify array of structs" { |
| 2501 | 2737 | MyStruct{ .foo = 42 }, |
| 2502 | 2738 | MyStruct{ .foo = 100 }, |
| 2503 | 2739 | MyStruct{ .foo = 1000 }, |
| 2504 | | }); |
| 2740 | }, StringifyOptions{}); |
| 2505 | 2741 | } |
| 2506 | 2742 | |
| 2507 | 2743 | test "stringify struct with custom stringifier" { |
| ... | ... | @@ -2515,7 +2751,7 @@ test "stringify struct with custom stringifier" { |
| 2515 | 2751 | ) !void { |
| 2516 | 2752 | try out_stream.writeAll("[\"something special\","); |
| 2517 | 2753 | try stringify(42, options, out_stream); |
| 2518 | | try out_stream.writeAll("]"); |
| 2754 | try out_stream.writeByte(']'); |
| 2519 | 2755 | } |
| 2520 | | }{ .foo = 42 }); |
| 2756 | }{ .foo = 42 }, StringifyOptions{}); |
| 2521 | 2757 | } |