| ... | @@ -532,60 +532,181 @@ pub const Tokenizer = struct { | ... | @@ -532,60 +532,181 @@ pub const Tokenizer = struct { |
| 532 | | 532 | |
| 533 | fn checkLiteralCharacter(self: &Tokenizer) { | 533 | fn checkLiteralCharacter(self: &Tokenizer) { |
| 534 | if (self.pending_invalid_token != null) return; | 534 | if (self.pending_invalid_token != null) return; |
| | 535 | const invalid_length = self.getInvalidCharacterLength(); |
| | 536 | if (invalid_length == 0) return; |
| | 537 | self.pending_invalid_token = Token { |
| | 538 | .id = Token.Id.Invalid, |
| | 539 | .start = self.index, |
| | 540 | .end = self.index + invalid_length, |
| | 541 | }; |
| | 542 | } |
| | 543 | |
| | 544 | fn getInvalidCharacterLength(self: &Tokenizer) -> u3 { |
| 535 | const c0 = self.buffer[self.index]; | 545 | const c0 = self.buffer[self.index]; |
| 536 | if (c0 < 0x20 or c0 == 0x7f) { | 546 | if (c0 < 0x80) { |
| 537 | // ascii control codes are never allowed | 547 | if (c0 < 0x20 or c0 == 0x7f) { |
| 538 | // (note that \n was checked before we got here) | 548 | // ascii control codes are never allowed |
| 539 | self.pending_invalid_token = Token { | 549 | // (note that \n was checked before we got here) |
| 540 | .id = Token.Id.Invalid, | 550 | return 1; |
| 541 | .start = self.index, | 551 | } |
| 542 | .end = self.index + 1, | 552 | // looks fine to me. |
| 543 | }; | 553 | return 0; |
| 544 | return; | 554 | } else { |
| | 555 | // check utf8-encoded character. |
| | 556 | // remember that the last byte in the buffer is guaranteed to be '\n', |
| | 557 | // which means we really don't need to do bounds checks here, |
| | 558 | // as long as we check one byte at a time for being a continuation byte. |
| | 559 | var value: u32 = undefined; |
| | 560 | var length: u3 = undefined; |
| | 561 | if (c0 & 0b11100000 == 0b11000000) {value = c0 & 0b00011111; length = 2;} |
| | 562 | else if (c0 & 0b11110000 == 0b11100000) {value = c0 & 0b00001111; length = 3;} |
| | 563 | else if (c0 & 0b11111000 == 0b11110000) {value = c0 & 0b00000111; length = 4;} |
| | 564 | else return 1; // unexpected continuation or too many leading 1's |
| | 565 | |
| | 566 | const c1 = self.buffer[self.index + 1]; |
| | 567 | if (c1 & 0b11000000 != 0b10000000) return 1; // expected continuation |
| | 568 | value <<= 6; |
| | 569 | value |= c1 & 0b00111111; |
| | 570 | if (length == 2) { |
| | 571 | if (value < 0x80) return length; // overlong |
| | 572 | if (value == 0x85) return length; // U+0085 (NEL) |
| | 573 | self.index += length - 1; |
| | 574 | return 0; |
| | 575 | } |
| | 576 | const c2 = self.buffer[self.index + 2]; |
| | 577 | if (c2 & 0b11000000 != 0b10000000) return 2; // expected continuation |
| | 578 | value <<= 6; |
| | 579 | value |= c2 & 0b00111111; |
| | 580 | if (length == 3) { |
| | 581 | if (value < 0x800) return length; // overlong |
| | 582 | if (value == 0x2028) return length; // U+2028 (LS) |
| | 583 | if (value == 0x2029) return length; // U+2029 (PS) |
| | 584 | if (0xd800 <= value and value <= 0xdfff) return length; // surrogate halves not allowed in utf8 |
| | 585 | self.index += length - 1; |
| | 586 | return 0; |
| | 587 | } |
| | 588 | const c3 = self.buffer[self.index + 3]; |
| | 589 | if (c3 & 0b11000000 != 0b10000000) return 3; // expected continuation |
| | 590 | value <<= 6; |
| | 591 | value |= c3 & 0b00111111; |
| | 592 | if (length == 4) { |
| | 593 | if (value < 0x10000) return length; // overlong |
| | 594 | if (value > 0x10FFFF) return length; // out of bounds |
| | 595 | self.index += length - 1; |
| | 596 | return 0; |
| | 597 | } |
| | 598 | unreachable; |
| 545 | } | 599 | } |
| 546 | } | 600 | } |
| 547 | }; | 601 | }; |
| 548 | | 602 | |
| 549 | | 603 | |
| 550 | | 604 | |
| 551 | test "tokenizer" { | 605 | test "tokenizer - source must end with eol" { |
| 552 | // source must end with eol | 606 | testTokenizeWithEol("", []Token.Id { |
| 553 | testTokenize("", []Token.Id { | | |
| 554 | }, true); | 607 | }, true); |
| 555 | testTokenize("no newline", []Token.Id { | 608 | testTokenizeWithEol("no newline", []Token.Id { |
| 556 | }, false); | 609 | }, false); |
| 557 | testTokenize("test\n", []Token.Id { | 610 | testTokenizeWithEol("test\n", []Token.Id { |
| 558 | Token.Id.Keyword_test, | 611 | Token.Id.Keyword_test, |
| 559 | }, true); | 612 | }, true); |
| 560 | testTokenize("test\nno newline", []Token.Id { | 613 | testTokenizeWithEol("test\nno newline", []Token.Id { |
| 561 | Token.Id.Keyword_test, | 614 | Token.Id.Keyword_test, |
| 562 | }, false); | 615 | }, false); |
| | 616 | } |
| 563 | | 617 | |
| 564 | // invalid token characters | 618 | test "tokenizer - invalid token characters" { |
| 565 | testTokenize("#\n", []Token.Id { | 619 | testTokenize("#\n", []Token.Id{Token.Id.Invalid}); |
| 566 | Token.Id.Invalid, | 620 | testTokenize("`\n", []Token.Id{Token.Id.Invalid}); |
| 567 | }, true); | 621 | } |
| 568 | testTokenize("`\n", []Token.Id { | | |
| 569 | Token.Id.Invalid, | | |
| 570 | }, true); | | |
| 571 | | 622 | |
| 572 | // invalid literal/comment characters | 623 | test "tokenizer - invalid literal/comment characters" { |
| 573 | testTokenize("\"\x00\"\n", []Token.Id { | 624 | testTokenize("\"\x00\"\n", []Token.Id { |
| 574 | Token.Id { .StringLiteral = Token.StrLitKind.Normal }, | 625 | Token.Id { .StringLiteral = Token.StrLitKind.Normal }, |
| 575 | Token.Id.Invalid, | 626 | Token.Id.Invalid, |
| 576 | }, true); | 627 | }); |
| 577 | testTokenize("//\x00\n", []Token.Id { | 628 | testTokenize("//\x00\n", []Token.Id { |
| 578 | Token.Id.Invalid, | 629 | Token.Id.Invalid, |
| 579 | }, true); | 630 | }); |
| 580 | testTokenize("//\x1f\n", []Token.Id { | 631 | testTokenize("//\x1f\n", []Token.Id { |
| 581 | Token.Id.Invalid, | 632 | Token.Id.Invalid, |
| 582 | }, true); | 633 | }); |
| 583 | testTokenize("//\x7f\n", []Token.Id { | 634 | testTokenize("//\x7f\n", []Token.Id { |
| 584 | Token.Id.Invalid, | 635 | Token.Id.Invalid, |
| 585 | }, true); | 636 | }); |
| | 637 | } |
| | 638 | |
| | 639 | test "tokenizer - valid unicode" { |
| | 640 | testTokenize("//\xc2\x80\n", []Token.Id{}); |
| | 641 | testTokenize("//\xdf\xbf\n", []Token.Id{}); |
| | 642 | testTokenize("//\xe0\xa0\x80\n", []Token.Id{}); |
| | 643 | testTokenize("//\xe1\x80\x80\n", []Token.Id{}); |
| | 644 | testTokenize("//\xef\xbf\xbf\n", []Token.Id{}); |
| | 645 | testTokenize("//\xf0\x90\x80\x80\n", []Token.Id{}); |
| | 646 | testTokenize("//\xf1\x80\x80\x80\n", []Token.Id{}); |
| | 647 | testTokenize("//\xf3\xbf\xbf\xbf\n", []Token.Id{}); |
| | 648 | testTokenize("//\xf4\x8f\xbf\xbf\n", []Token.Id{}); |
| 586 | } | 649 | } |
| 587 | | 650 | |
| 588 | fn testTokenize(source: []const u8, expected_tokens: []const Token.Id, expected_eol_at_eof: bool) { | 651 | test "tokenizer - invalid unicode continuation bytes" { |
| | 652 | // unexpected continuation |
| | 653 | testTokenize("//\x80\n", []Token.Id{Token.Id.Invalid}); |
| | 654 | testTokenize("//\xbf\n", []Token.Id{Token.Id.Invalid}); |
| | 655 | // too many leading 1's |
| | 656 | testTokenize("//\xf8\n", []Token.Id{Token.Id.Invalid}); |
| | 657 | testTokenize("//\xff\n", []Token.Id{Token.Id.Invalid}); |
| | 658 | // expected continuation for 2 byte sequences |
| | 659 | testTokenize("//\xc2\x00\n", []Token.Id{Token.Id.Invalid}); |
| | 660 | testTokenize("//\xc2\xc0\n", []Token.Id{Token.Id.Invalid}); |
| | 661 | // expected continuation for 3 byte sequences |
| | 662 | testTokenize("//\xe0\x00\n", []Token.Id{Token.Id.Invalid}); |
| | 663 | testTokenize("//\xe0\xc0\n", []Token.Id{Token.Id.Invalid}); |
| | 664 | testTokenize("//\xe0\xa0\n", []Token.Id{Token.Id.Invalid}); |
| | 665 | testTokenize("//\xe0\xa0\x00\n", []Token.Id{Token.Id.Invalid}); |
| | 666 | testTokenize("//\xe0\xa0\xc0\n", []Token.Id{Token.Id.Invalid}); |
| | 667 | // expected continuation for 4 byte sequences |
| | 668 | testTokenize("//\xf0\x00\n", []Token.Id{Token.Id.Invalid}); |
| | 669 | testTokenize("//\xf0\xc0\n", []Token.Id{Token.Id.Invalid}); |
| | 670 | testTokenize("//\xf0\x90\x00\n", []Token.Id{Token.Id.Invalid}); |
| | 671 | testTokenize("//\xf0\x90\xc0\n", []Token.Id{Token.Id.Invalid}); |
| | 672 | testTokenize("//\xf0\x90\x80\x00\n", []Token.Id{Token.Id.Invalid}); |
| | 673 | testTokenize("//\xf0\x90\x80\xc0\n", []Token.Id{Token.Id.Invalid}); |
| | 674 | } |
| | 675 | |
| | 676 | test "tokenizer - overlong utf8 codepoint" { |
| | 677 | testTokenize("//\xc0\x80\n", []Token.Id{Token.Id.Invalid}); |
| | 678 | testTokenize("//\xc1\xbf\n", []Token.Id{Token.Id.Invalid}); |
| | 679 | testTokenize("//\xe0\x80\x80\n", []Token.Id{Token.Id.Invalid}); |
| | 680 | testTokenize("//\xe0\x9f\xbf\n", []Token.Id{Token.Id.Invalid}); |
| | 681 | testTokenize("//\xf0\x80\x80\x80\n", []Token.Id{Token.Id.Invalid}); |
| | 682 | testTokenize("//\xf0\x8f\xbf\xbf\n", []Token.Id{Token.Id.Invalid}); |
| | 683 | } |
| | 684 | |
| | 685 | test "tokenizer - misc invalid utf8" { |
| | 686 | // codepoint out of bounds |
| | 687 | testTokenize("//\xf4\x90\x80\x80\n", []Token.Id{Token.Id.Invalid}); |
| | 688 | testTokenize("//\xf7\xbf\xbf\xbf\n", []Token.Id{Token.Id.Invalid}); |
| | 689 | // unicode newline characters.U+0085, U+2028, U+2029 |
| | 690 | testTokenize("//\xc2\x84\n", []Token.Id{}); |
| | 691 | testTokenize("//\xc2\x85\n", []Token.Id{Token.Id.Invalid}); |
| | 692 | testTokenize("//\xc2\x86\n", []Token.Id{}); |
| | 693 | testTokenize("//\xe2\x80\xa7\n", []Token.Id{}); |
| | 694 | testTokenize("//\xe2\x80\xa8\n", []Token.Id{Token.Id.Invalid}); |
| | 695 | testTokenize("//\xe2\x80\xa9\n", []Token.Id{Token.Id.Invalid}); |
| | 696 | testTokenize("//\xe2\x80\xaa\n", []Token.Id{}); |
| | 697 | // surrogate halves |
| | 698 | testTokenize("//\xed\x9f\x80\n", []Token.Id{}); |
| | 699 | testTokenize("//\xed\xa0\x80\n", []Token.Id{Token.Id.Invalid}); |
| | 700 | testTokenize("//\xed\xbf\xbf\n", []Token.Id{Token.Id.Invalid}); |
| | 701 | testTokenize("//\xee\x80\x80\n", []Token.Id{}); |
| | 702 | // surrogate halves are invalid, even in surrogate pairs |
| | 703 | testTokenize("//\xed\xa0\xad\xed\xb2\xa9\n", []Token.Id{Token.Id.Invalid}); |
| | 704 | } |
| | 705 | |
| | 706 | fn testTokenize(source: []const u8, expected_tokens: []const Token.Id) { |
| | 707 | testTokenizeWithEol(source, expected_tokens, true); |
| | 708 | } |
| | 709 | fn testTokenizeWithEol(source: []const u8, expected_tokens: []const Token.Id, expected_eol_at_eof: bool) { |
| 589 | var tokenizer = Tokenizer.init(source); | 710 | var tokenizer = Tokenizer.init(source); |
| 590 | for (expected_tokens) |expected_token_id| { | 711 | for (expected_tokens) |expected_token_id| { |
| 591 | const token = tokenizer.next(); | 712 | const token = tokenizer.next(); |