authorgravatar for quae@daurnimator.comdaurnimator <quae@daurnimator.com> 2020-02-25 20:30:57+11:00
committergravatar for quae@daurnimator.comdaurnimator <quae@daurnimator.com> 2020-04-01 00:12:59+11:00
logedf487b12645ced9e0745deb32b47c594f1f3072
tree32e092e0970263d8fb22384ecdf048530b08f2d7
parent17f5d04bedb7a3cf06e8b0a7a1e8d13a3f2635f5
signaturelock-open Commit is signed but in an unrecognized format.

std: add options to std.json.stringfy to control escaping


1 files changed, 65 insertions(+), 28 deletions(-)

lib/std/json.zig+65-28
...@@ -2357,10 +2357,38 @@ pub const StringifyOptions = struct {...@@ -2357,10 +2357,38 @@ pub const StringifyOptions = struct {
2357 /// Controls the whitespace emitted2357 /// Controls the whitespace emitted
2358 whitespace: ?Whitespace = null,2358 whitespace: ?Whitespace = null,
23592359
2360 // TODO: make escaping '/' in strings optional?2360 /// Should '/' be escaped in strings?
2361 escape_solidus: bool = false,
2362
2363 /// Should unicode characters be escaped in strings?
2364 escape_unicode: bool = false,
2365
2361 // TODO: allow picking if []u8 is string or array?2366 // TODO: allow picking if []u8 is string or array?
2362};2367};
23632368
2369fn outputUnicodeEscape(
2370 codepoint: u21,
2371 out_stream: var,
2372) !void {
2373 if (codepoint <= 0xFFFF) {
2374 // If the character is in the Basic Multilingual Plane (U+0000 through U+FFFF),
2375 // then it may be represented as a six-character sequence: a reverse solidus, followed
2376 // by the lowercase letter u, followed by four hexadecimal digits that encode the character's code point.
2377 try out_stream.writeAll("\\u");
2378 try std.fmt.formatIntValue(codepoint, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream);
2379 } else {
2380 assert(codepoint <= 0x10FFFF);
2381 // To escape an extended character that is not in the Basic Multilingual Plane,
2382 // the character is represented as a 12-character sequence, encoding the UTF-16 surrogate pair.
2383 const high = @intCast(u16, (codepoint - 0x10000) >> 10) + 0xD800;
2384 const low = @intCast(u16, codepoint & 0x3FF) + 0xDC00;
2385 try out_stream.writeAll("\\u");
2386 try std.fmt.formatIntValue(high, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream);
2387 try out_stream.writeAll("\\u");
2388 try std.fmt.formatIntValue(low, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream);
2389 }
2390}
2391
2364pub fn stringify(2392pub fn stringify(
2365 value: var,2393 value: var,
2366 options: StringifyOptions,2394 options: StringifyOptions,
...@@ -2467,12 +2495,21 @@ pub fn stringify(...@@ -2467,12 +2495,21 @@ pub fn stringify(
2467 var i: usize = 0;2495 var i: usize = 0;
2468 while (i < value.len) : (i += 1) {2496 while (i < value.len) : (i += 1) {
2469 switch (value[i]) {2497 switch (value[i]) {
2470 // normal ascii characters2498 // normal ascii character
2471 0x20...0x21, 0x23...0x2E, 0x30...0x5B, 0x5D...0x7F => try out_stream.writeAll(value[i .. i + 1]),2499 0x20...0x21, 0x23...0x2E, 0x30...0x5B, 0x5D...0x7F => |c| try out_stream.writeByte(c),
2472 // control characters with short escapes2500 // only 2 characters that *must* be escaped
2473 '\\' => try out_stream.writeAll("\\\\"),2501 '\\' => try out_stream.writeAll("\\\\"),
2474 '\"' => try out_stream.writeAll("\\\""),2502 '\"' => try out_stream.writeAll("\\\""),
2475 '/' => try out_stream.writeAll("\\/"),2503 // solidus is optional to escape
2504 '/' => {
2505 if (options.escape_solidus) {
2506 try out_stream.writeAll("\\/");
2507 } else {
2508 try out_stream.writeByte('\\');
2509 }
2510 },
2511 // control characters with short escapes
2512 // TODO: option to switch between unicode and 'short' forms?
2476 0x8 => try out_stream.writeAll("\\b"),2513 0x8 => try out_stream.writeAll("\\b"),
2477 0xC => try out_stream.writeAll("\\f"),2514 0xC => try out_stream.writeAll("\\f"),
2478 '\n' => try out_stream.writeAll("\\n"),2515 '\n' => try out_stream.writeAll("\\n"),
...@@ -2480,22 +2517,12 @@ pub fn stringify(...@@ -2480,22 +2517,12 @@ pub fn stringify(
2480 '\t' => try out_stream.writeAll("\\t"),2517 '\t' => try out_stream.writeAll("\\t"),
2481 else => {2518 else => {
2482 const ulen = std.unicode.utf8ByteSequenceLength(value[i]) catch unreachable;2519 const ulen = std.unicode.utf8ByteSequenceLength(value[i]) catch unreachable;
2483 const codepoint = std.unicode.utf8Decode(value[i .. i + ulen]) catch unreachable;2520 // control characters (only things left with 1 byte length) should always be printed as unicode escapes
2484 if (codepoint <= 0xFFFF) {2521 if (ulen == 1 or options.escape_unicode) {
2485 // If the character is in the Basic Multilingual Plane (U+0000 through U+FFFF),2522 const codepoint = std.unicode.utf8Decode(value[i .. i + ulen]) catch unreachable;
2486 // then it may be represented as a six-character sequence: a reverse solidus, followed2523 try outputUnicodeEscape(codepoint, out_stream);
2487 // by the lowercase letter u, followed by four hexadecimal digits that encode the character's code point.
2488 try out_stream.writeAll("\\u");
2489 try std.fmt.formatIntValue(codepoint, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream);
2490 } else {2524 } else {
2491 // To escape an extended character that is not in the Basic Multilingual Plane,2525 try out_stream.writeAll(value[i .. i + ulen]);
2492 // the character is represented as a 12-character sequence, encoding the UTF-16 surrogate pair.
2493 const high = @intCast(u16, (codepoint - 0x10000) >> 10) + 0xD800;
2494 const low = @intCast(u16, codepoint & 0x3FF) + 0xDC00;
2495 try out_stream.writeAll("\\u");
2496 try std.fmt.formatIntValue(high, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream);
2497 try out_stream.writeAll("\\u");
2498 try std.fmt.formatIntValue(low, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream);
2499 }2526 }
2500 i += ulen - 1;2527 i += ulen - 1;
2501 },2528 },
...@@ -2609,15 +2636,25 @@ test "stringify basic types" {...@@ -2609,15 +2636,25 @@ test "stringify basic types" {
2609test "stringify string" {2636test "stringify string" {
2610 try teststringify("\"hello\"", "hello", StringifyOptions{});2637 try teststringify("\"hello\"", "hello", StringifyOptions{});
2611 try teststringify("\"with\\nescapes\\r\"", "with\nescapes\r", StringifyOptions{});2638 try teststringify("\"with\\nescapes\\r\"", "with\nescapes\r", StringifyOptions{});
2639 try teststringify("\"with\\nescapes\\r\"", "with\nescapes\r", StringifyOptions{ .escape_unicode = true });
2612 try teststringify("\"with unicode\\u0001\"", "with unicode\u{1}", StringifyOptions{});2640 try teststringify("\"with unicode\\u0001\"", "with unicode\u{1}", StringifyOptions{});
2613 try teststringify("\"with unicode\\u0080\"", "with unicode\u{80}", StringifyOptions{});2641 try teststringify("\"with unicode\\u0001\"", "with unicode\u{1}", StringifyOptions{ .escape_unicode = true });
2614 try teststringify("\"with unicode\\u00ff\"", "with unicode\u{FF}", StringifyOptions{});2642 try teststringify("\"with unicode\u{80}\"", "with unicode\u{80}", StringifyOptions{});
2615 try teststringify("\"with unicode\\u0100\"", "with unicode\u{100}", StringifyOptions{});2643 try teststringify("\"with unicode\\u0080\"", "with unicode\u{80}", StringifyOptions{ .escape_unicode = true });
2616 try teststringify("\"with unicode\\u0800\"", "with unicode\u{800}", StringifyOptions{});2644 try teststringify("\"with unicode\u{FF}\"", "with unicode\u{FF}", StringifyOptions{});
2617 try teststringify("\"with unicode\\u8000\"", "with unicode\u{8000}", StringifyOptions{});2645 try teststringify("\"with unicode\\u00ff\"", "with unicode\u{FF}", StringifyOptions{ .escape_unicode = true });
2618 try teststringify("\"with unicode\\ud799\"", "with unicode\u{D799}", StringifyOptions{});2646 try teststringify("\"with unicode\u{100}\"", "with unicode\u{100}", StringifyOptions{});
2619 try teststringify("\"with unicode\\ud800\\udc00\"", "with unicode\u{10000}", StringifyOptions{});2647 try teststringify("\"with unicode\\u0100\"", "with unicode\u{100}", StringifyOptions{ .escape_unicode = true });
2620 try teststringify("\"with unicode\\udbff\\udfff\"", "with unicode\u{10FFFF}", StringifyOptions{});2648 try teststringify("\"with unicode\u{800}\"", "with unicode\u{800}", StringifyOptions{});
2649 try teststringify("\"with unicode\\u0800\"", "with unicode\u{800}", StringifyOptions{ .escape_unicode = true });
2650 try teststringify("\"with unicode\u{8000}\"", "with unicode\u{8000}", StringifyOptions{});
2651 try teststringify("\"with unicode\\u8000\"", "with unicode\u{8000}", StringifyOptions{ .escape_unicode = true });
2652 try teststringify("\"with unicode\u{D799}\"", "with unicode\u{D799}", StringifyOptions{});
2653 try teststringify("\"with unicode\\ud799\"", "with unicode\u{D799}", StringifyOptions{ .escape_unicode = true });
2654 try teststringify("\"with unicode\u{10000}\"", "with unicode\u{10000}", StringifyOptions{});
2655 try teststringify("\"with unicode\\ud800\\udc00\"", "with unicode\u{10000}", StringifyOptions{ .escape_unicode = true });
2656 try teststringify("\"with unicode\u{10FFFF}\"", "with unicode\u{10FFFF}", StringifyOptions{});
2657 try teststringify("\"with unicode\\udbff\\udfff\"", "with unicode\u{10FFFF}", StringifyOptions{ .escape_unicode = true });
2621}2658}
26222659
2623test "stringify tagged unions" {2660test "stringify tagged unions" {