| ... | ... | @@ -4,12 +4,21 @@ |
| 4 | 4 | |
| 5 | 5 | const std = @import("std.zig"); |
| 6 | 6 | const debug = std.debug; |
| 7 | const assert = debug.assert; |
| 7 | 8 | const testing = std.testing; |
| 8 | 9 | const mem = std.mem; |
| 9 | 10 | const maxInt = std.math.maxInt; |
| 10 | 11 | |
| 11 | 12 | pub const WriteStream = @import("json/write_stream.zig").WriteStream; |
| 12 | 13 | |
| 14 | const StringEscapes = union(enum) { |
| 15 | None, |
| 16 | |
| 17 | Some: struct { |
| 18 | size_diff: isize, |
| 19 | }, |
| 20 | }; |
| 21 | |
| 13 | 22 | /// A single token slice into the parent string. |
| 14 | 23 | /// |
| 15 | 24 | /// Use `token.slice()` on the input at the current position to get the current slice. |
| ... | ... | @@ -23,7 +32,14 @@ pub const Token = union(enum) { |
| 23 | 32 | count: usize, |
| 24 | 33 | |
| 25 | 34 | /// Whether string contains an escape sequence and cannot be zero-copied |
| 26 | | has_escape: bool, |
| 35 | escapes: StringEscapes, |
| 36 | |
| 37 | pub fn decodedLength(self: @This()) usize { |
| 38 | return self.count +% switch (self.escapes) { |
| 39 | .None => 0, |
| 40 | .Some => |s| @bitCast(usize, s.size_diff), |
| 41 | }; |
| 42 | } |
| 27 | 43 | |
| 28 | 44 | /// Slice into the underlying input string. |
| 29 | 45 | pub fn slice(self: @This(), input: []const u8, i: usize) []const u8 { |
| ... | ... | @@ -66,7 +82,12 @@ pub const StreamingParser = struct { |
| 66 | 82 | // If we stopped now, would the complete parsed string to now be a valid json string |
| 67 | 83 | complete: bool, |
| 68 | 84 | // Current token flags to pass through to the next generated, see Token. |
| 69 | | string_has_escape: bool, |
| 85 | string_escapes: StringEscapes, |
| 86 | // When in .String states, was the previous character a high surrogate? |
| 87 | string_last_was_high_surrogate: bool, |
| 88 | // Used inside of StringEscapeHexUnicode* states |
| 89 | string_unicode_codepoint: u21, |
| 90 | // When in .Number states, is the number a (still) valid integer? |
| 70 | 91 | number_is_integer: bool, |
| 71 | 92 | |
| 72 | 93 | // Bit-stack for nested object/map literals (max 255 nestings). |
| ... | ... | @@ -92,8 +113,10 @@ pub const StreamingParser = struct { |
| 92 | 113 | p.stack = 0; |
| 93 | 114 | p.stack_used = 0; |
| 94 | 115 | p.complete = false; |
| 95 | | p.string_has_escape = false; |
| 96 | | p.number_is_integer = true; |
| 116 | p.string_escapes = undefined; |
| 117 | p.string_last_was_high_surrogate = undefined; |
| 118 | p.string_unicode_codepoint = undefined; |
| 119 | p.number_is_integer = undefined; |
| 97 | 120 | } |
| 98 | 121 | |
| 99 | 122 | pub const State = enum { |
| ... | ... | @@ -231,7 +254,8 @@ pub const StreamingParser = struct { |
| 231 | 254 | p.after_value_state = .TopLevelEnd; |
| 232 | 255 | // We don't actually need the following since after_value_state should override. |
| 233 | 256 | p.after_string_state = .ValueEnd; |
| 234 | | p.string_has_escape = false; |
| 257 | p.string_escapes = .None; |
| 258 | p.string_last_was_high_surrogate = false; |
| 235 | 259 | p.count = 0; |
| 236 | 260 | }, |
| 237 | 261 | 't' => { |
| ... | ... | @@ -367,6 +391,8 @@ pub const StreamingParser = struct { |
| 367 | 391 | }, |
| 368 | 392 | '"' => { |
| 369 | 393 | p.state = .String; |
| 394 | p.string_escapes = .None; |
| 395 | p.string_last_was_high_surrogate = false; |
| 370 | 396 | p.count = 0; |
| 371 | 397 | }, |
| 372 | 398 | 't' => { |
| ... | ... | @@ -436,6 +462,8 @@ pub const StreamingParser = struct { |
| 436 | 462 | }, |
| 437 | 463 | '"' => { |
| 438 | 464 | p.state = .String; |
| 465 | p.string_escapes = .None; |
| 466 | p.string_last_was_high_surrogate = false; |
| 439 | 467 | p.count = 0; |
| 440 | 468 | }, |
| 441 | 469 | 't' => { |
| ... | ... | @@ -534,15 +562,24 @@ pub const StreamingParser = struct { |
| 534 | 562 | token.* = .{ |
| 535 | 563 | .String = .{ |
| 536 | 564 | .count = p.count - 1, |
| 537 | | .has_escape = p.string_has_escape, |
| 565 | .escapes = p.string_escapes, |
| 538 | 566 | }, |
| 539 | 567 | }; |
| 568 | p.string_escapes = undefined; |
| 569 | p.string_last_was_high_surrogate = undefined; |
| 540 | 570 | }, |
| 541 | 571 | '\\' => { |
| 542 | 572 | p.state = .StringEscapeCharacter; |
| 573 | switch (p.string_escapes) { |
| 574 | .None => { |
| 575 | p.string_escapes = .{ .Some = .{ .size_diff = 0 } }; |
| 576 | }, |
| 577 | .Some => {}, |
| 578 | } |
| 543 | 579 | }, |
| 544 | 580 | 0x20, 0x21, 0x23...0x5B, 0x5D...0x7F => { |
| 545 | 581 | // non-control ascii |
| 582 | p.string_last_was_high_surrogate = false; |
| 546 | 583 | }, |
| 547 | 584 | 0xC0...0xDF => { |
| 548 | 585 | p.state = .StringUtf8Byte1; |
| ... | ... | @@ -569,7 +606,10 @@ pub const StreamingParser = struct { |
| 569 | 606 | }, |
| 570 | 607 | |
| 571 | 608 | .StringUtf8Byte1 => switch (c >> 6) { |
| 572 | | 0b10 => p.state = .String, |
| 609 | 0b10 => { |
| 610 | p.state = .String; |
| 611 | p.string_last_was_high_surrogate = false; |
| 612 | }, |
| 573 | 613 | else => return error.InvalidUtf8Byte, |
| 574 | 614 | }, |
| 575 | 615 | |
| ... | ... | @@ -583,11 +623,11 @@ pub const StreamingParser = struct { |
| 583 | 623 | // however, so we default to the status quo where both are accepted until this |
| 584 | 624 | // is further clarified. |
| 585 | 625 | '"', '\\', '/', 'b', 'f', 'n', 'r', 't' => { |
| 586 | | p.string_has_escape = true; |
| 626 | p.string_escapes.Some.size_diff -= 1; |
| 587 | 627 | p.state = .String; |
| 628 | p.string_last_was_high_surrogate = false; |
| 588 | 629 | }, |
| 589 | 630 | 'u' => { |
| 590 | | p.string_has_escape = true; |
| 591 | 631 | p.state = .StringEscapeHexUnicode4; |
| 592 | 632 | }, |
| 593 | 633 | else => { |
| ... | ... | @@ -595,32 +635,99 @@ pub const StreamingParser = struct { |
| 595 | 635 | }, |
| 596 | 636 | }, |
| 597 | 637 | |
| 598 | | .StringEscapeHexUnicode4 => switch (c) { |
| 599 | | '0'...'9', 'A'...'F', 'a'...'f' => { |
| 600 | | p.state = .StringEscapeHexUnicode3; |
| 601 | | }, |
| 602 | | else => return error.InvalidUnicodeHexSymbol, |
| 638 | .StringEscapeHexUnicode4 => { |
| 639 | var codepoint: u21 = undefined; |
| 640 | switch (c) { |
| 641 | else => return error.InvalidUnicodeHexSymbol, |
| 642 | '0'...'9' => { |
| 643 | codepoint = c - '0'; |
| 644 | }, |
| 645 | 'A'...'F' => { |
| 646 | codepoint = c - 'A' + 10; |
| 647 | }, |
| 648 | 'a'...'f' => { |
| 649 | codepoint = c - 'a' + 10; |
| 650 | }, |
| 651 | } |
| 652 | p.state = .StringEscapeHexUnicode3; |
| 653 | p.string_unicode_codepoint = codepoint << 12; |
| 603 | 654 | }, |
| 604 | 655 | |
| 605 | | .StringEscapeHexUnicode3 => switch (c) { |
| 606 | | '0'...'9', 'A'...'F', 'a'...'f' => { |
| 607 | | p.state = .StringEscapeHexUnicode2; |
| 608 | | }, |
| 609 | | else => return error.InvalidUnicodeHexSymbol, |
| 656 | .StringEscapeHexUnicode3 => { |
| 657 | var codepoint: u21 = undefined; |
| 658 | switch (c) { |
| 659 | else => return error.InvalidUnicodeHexSymbol, |
| 660 | '0'...'9' => { |
| 661 | codepoint = c - '0'; |
| 662 | }, |
| 663 | 'A'...'F' => { |
| 664 | codepoint = c - 'A' + 10; |
| 665 | }, |
| 666 | 'a'...'f' => { |
| 667 | codepoint = c - 'a' + 10; |
| 668 | }, |
| 669 | } |
| 670 | p.state = .StringEscapeHexUnicode2; |
| 671 | p.string_unicode_codepoint |= codepoint << 8; |
| 610 | 672 | }, |
| 611 | 673 | |
| 612 | | .StringEscapeHexUnicode2 => switch (c) { |
| 613 | | '0'...'9', 'A'...'F', 'a'...'f' => { |
| 614 | | p.state = .StringEscapeHexUnicode1; |
| 615 | | }, |
| 616 | | else => return error.InvalidUnicodeHexSymbol, |
| 674 | .StringEscapeHexUnicode2 => { |
| 675 | var codepoint: u21 = undefined; |
| 676 | switch (c) { |
| 677 | else => return error.InvalidUnicodeHexSymbol, |
| 678 | '0'...'9' => { |
| 679 | codepoint = c - '0'; |
| 680 | }, |
| 681 | 'A'...'F' => { |
| 682 | codepoint = c - 'A' + 10; |
| 683 | }, |
| 684 | 'a'...'f' => { |
| 685 | codepoint = c - 'a' + 10; |
| 686 | }, |
| 687 | } |
| 688 | p.state = .StringEscapeHexUnicode1; |
| 689 | p.string_unicode_codepoint |= codepoint << 4; |
| 617 | 690 | }, |
| 618 | 691 | |
| 619 | | .StringEscapeHexUnicode1 => switch (c) { |
| 620 | | '0'...'9', 'A'...'F', 'a'...'f' => { |
| 621 | | p.state = .String; |
| 622 | | }, |
| 623 | | else => return error.InvalidUnicodeHexSymbol, |
| 692 | .StringEscapeHexUnicode1 => { |
| 693 | var codepoint: u21 = undefined; |
| 694 | switch (c) { |
| 695 | else => return error.InvalidUnicodeHexSymbol, |
| 696 | '0'...'9' => { |
| 697 | codepoint = c - '0'; |
| 698 | }, |
| 699 | 'A'...'F' => { |
| 700 | codepoint = c - 'A' + 10; |
| 701 | }, |
| 702 | 'a'...'f' => { |
| 703 | codepoint = c - 'a' + 10; |
| 704 | }, |
| 705 | } |
| 706 | p.state = .String; |
| 707 | p.string_unicode_codepoint |= codepoint; |
| 708 | if (p.string_unicode_codepoint < 0xD800 or p.string_unicode_codepoint >= 0xE000) { |
| 709 | // not part of surrogate pair |
| 710 | p.string_escapes.Some.size_diff -= @as(isize, 6 - (std.unicode.utf8CodepointSequenceLength(p.string_unicode_codepoint) catch unreachable)); |
| 711 | p.string_last_was_high_surrogate = false; |
| 712 | } else if (p.string_unicode_codepoint < 0xDC00) { |
| 713 | // 'high' surrogate |
| 714 | // takes 3 bytes to encode a half surrogate pair into wtf8 |
| 715 | p.string_escapes.Some.size_diff -= 6 - 3; |
| 716 | p.string_last_was_high_surrogate = true; |
| 717 | } else { |
| 718 | // 'low' surrogate |
| 719 | p.string_escapes.Some.size_diff -= 6; |
| 720 | if (p.string_last_was_high_surrogate) { |
| 721 | // takes 4 bytes to encode a full surrogate pair into utf8 |
| 722 | // 3 bytes are already reserved by high surrogate |
| 723 | p.string_escapes.Some.size_diff -= -1; |
| 724 | } else { |
| 725 | // takes 3 bytes to encode a half surrogate pair into wtf8 |
| 726 | p.string_escapes.Some.size_diff -= -3; |
| 727 | } |
| 728 | p.string_last_was_high_surrogate = false; |
| 729 | } |
| 730 | p.string_unicode_codepoint = undefined; |
| 624 | 731 | }, |
| 625 | 732 | |
| 626 | 733 | .Number => { |
| ... | ... | @@ -657,6 +764,7 @@ pub const StreamingParser = struct { |
| 657 | 764 | .is_integer = p.number_is_integer, |
| 658 | 765 | }, |
| 659 | 766 | }; |
| 767 | p.number_is_integer = undefined; |
| 660 | 768 | return true; |
| 661 | 769 | }, |
| 662 | 770 | } |
| ... | ... | @@ -1271,7 +1379,10 @@ pub const Parser = struct { |
| 1271 | 1379 | // TODO: We don't strictly have to copy values which do not contain any escape |
| 1272 | 1380 | // characters if flagged with the option. |
| 1273 | 1381 | const slice = s.slice(input, i); |
| 1274 | | return Value{ .String = try unescapeStringAlloc(allocator, slice) }; |
| 1382 | return switch (s.escapes) { |
| 1383 | .None => Value{ .String = try mem.dupe(allocator, u8, slice) }, |
| 1384 | .Some => |some_escapes| Value{ .String = try unescapeStringAlloc(allocator, some_escapes, slice) }, |
| 1385 | }; |
| 1275 | 1386 | } |
| 1276 | 1387 | |
| 1277 | 1388 | fn parseNumber(p: *Parser, n: std.meta.TagPayloadType(Token, Token.Number), input: []const u8, i: usize) !Value { |
| ... | ... | @@ -1285,13 +1396,10 @@ pub const Parser = struct { |
| 1285 | 1396 | // Unescape a JSON string |
| 1286 | 1397 | // Only to be used on strings already validated by the parser |
| 1287 | 1398 | // (note the unreachable statements and lack of bounds checking) |
| 1288 | | // Optimized for arena allocators, uses Allocator.shrink |
| 1289 | | // |
| 1290 | | // Idea: count how many bytes we will need to allocate in the streaming parser and store it |
| 1291 | | // in the token to avoid allocating too much memory or iterating through the string again |
| 1292 | | // Downside: need to find how many bytes a unicode escape sequence will produce twice |
| 1293 | | fn unescapeStringAlloc(alloc: *Allocator, input: []const u8) ![]u8 { |
| 1294 | | const output = try alloc.alloc(u8, input.len); |
| 1399 | fn unescapeStringAlloc(alloc: *Allocator, escapes: std.meta.TagPayloadType(StringEscapes, StringEscapes.Some), input: []const u8) ![]u8 { |
| 1400 | const result_size = input.len +% @bitCast(usize, escapes.size_diff); |
| 1401 | |
| 1402 | const output = try alloc.alloc(u8, result_size); |
| 1295 | 1403 | errdefer alloc.free(output); |
| 1296 | 1404 | |
| 1297 | 1405 | var inIndex: usize = 0; |
| ... | ... | @@ -1347,8 +1455,9 @@ fn unescapeStringAlloc(alloc: *Allocator, input: []const u8) ![]u8 { |
| 1347 | 1455 | } |
| 1348 | 1456 | } |
| 1349 | 1457 | } |
| 1458 | assert(outIndex == result_size); |
| 1350 | 1459 | |
| 1351 | | return alloc.shrink(output, outIndex); |
| 1460 | return output; |
| 1352 | 1461 | } |
| 1353 | 1462 | |
| 1354 | 1463 | test "json.parser.dynamic" { |