| ... | @@ -424,10 +424,7 @@ pub const Tokenizer = struct { | ... | @@ -424,10 +424,7 @@ pub const Tokenizer = struct { |
| 424 | }; | 424 | }; |
| 425 | state = .invalid; | 425 | state = .invalid; |
| 426 | }, | 426 | }, |
| 427 | '\r' => { | 427 | ' ', '\n', '\t', '\r' => { |
| 428 | state = .expect_newline; | | |
| 429 | }, | | |
| 430 | ' ', '\n', '\t' => { | | |
| 431 | result.loc.start = self.index + 1; | 428 | result.loc.start = self.index + 1; |
| 432 | }, | 429 | }, |
| 433 | '"' => { | 430 | '"' => { |
| ... | @@ -553,6 +550,13 @@ pub const Tokenizer = struct { | ... | @@ -553,6 +550,13 @@ pub const Tokenizer = struct { |
| 553 | }, | 550 | }, |
| 554 | | 551 | |
| 555 | .expect_newline => switch (c) { | 552 | .expect_newline => switch (c) { |
| | 553 | 0 => { |
| | 554 | if (self.index == self.buffer.len) { |
| | 555 | result.tag = .invalid; |
| | 556 | break; |
| | 557 | } |
| | 558 | state = .invalid; |
| | 559 | }, |
| 556 | '\n' => { | 560 | '\n' => { |
| 557 | result.loc.start = self.index + 1; | 561 | result.loc.start = self.index + 1; |
| 558 | state = .start; | 562 | state = .start; |
| ... | @@ -846,7 +850,15 @@ pub const Tokenizer = struct { | ... | @@ -846,7 +850,15 @@ pub const Tokenizer = struct { |
| 846 | self.index += 1; | 850 | self.index += 1; |
| 847 | break; | 851 | break; |
| 848 | }, | 852 | }, |
| 849 | 0x01...0x08, 0x0b...0x1f, 0x7f => { | 853 | '\r' => { |
| | 854 | if (self.buffer[self.index + 1] == '\n') { |
| | 855 | self.index += 2; |
| | 856 | break; |
| | 857 | } else { |
| | 858 | state = .invalid; |
| | 859 | } |
| | 860 | }, |
| | 861 | 0x01...0x09, 0x0b...0x0c, 0x0e...0x1f, 0x7f => { |
| 850 | state = .invalid; | 862 | state = .invalid; |
| 851 | }, | 863 | }, |
| 852 | else => continue, | 864 | else => continue, |
| ... | @@ -1091,7 +1103,7 @@ pub const Tokenizer = struct { | ... | @@ -1091,7 +1103,7 @@ pub const Tokenizer = struct { |
| 1091 | state = .start; | 1103 | state = .start; |
| 1092 | result.loc.start = self.index + 1; | 1104 | result.loc.start = self.index + 1; |
| 1093 | }, | 1105 | }, |
| 1094 | 0x01...0x08, 0x0b...0x0c, 0x0e...0x1f, 0x7f => { | 1106 | 0x01...0x09, 0x0b...0x0c, 0x0e...0x1f, 0x7f => { |
| 1095 | state = .invalid; | 1107 | state = .invalid; |
| 1096 | }, | 1108 | }, |
| 1097 | else => { | 1109 | else => { |
| ... | @@ -1099,14 +1111,23 @@ pub const Tokenizer = struct { | ... | @@ -1099,14 +1111,23 @@ pub const Tokenizer = struct { |
| 1099 | }, | 1111 | }, |
| 1100 | }, | 1112 | }, |
| 1101 | .doc_comment_start => switch (c) { | 1113 | .doc_comment_start => switch (c) { |
| 1102 | 0, '\n', '\r' => { | 1114 | 0, '\n' => { |
| 1103 | result.tag = .doc_comment; | 1115 | result.tag = .doc_comment; |
| 1104 | break; | 1116 | break; |
| 1105 | }, | 1117 | }, |
| | 1118 | '\r' => { |
| | 1119 | if (self.buffer[self.index + 1] == '\n') { |
| | 1120 | self.index += 1; |
| | 1121 | result.tag = .doc_comment; |
| | 1122 | break; |
| | 1123 | } else { |
| | 1124 | state = .invalid; |
| | 1125 | } |
| | 1126 | }, |
| 1106 | '/' => { | 1127 | '/' => { |
| 1107 | state = .line_comment; | 1128 | state = .line_comment; |
| 1108 | }, | 1129 | }, |
| 1109 | 0x01...0x08, 0x0b...0x0c, 0x0e...0x1f, 0x7f => { | 1130 | 0x01...0x09, 0x0b...0x0c, 0x0e...0x1f, 0x7f => { |
| 1110 | state = .invalid; | 1131 | state = .invalid; |
| 1111 | }, | 1132 | }, |
| 1112 | else => { | 1133 | else => { |
| ... | @@ -1135,16 +1156,24 @@ pub const Tokenizer = struct { | ... | @@ -1135,16 +1156,24 @@ pub const Tokenizer = struct { |
| 1135 | state = .start; | 1156 | state = .start; |
| 1136 | result.loc.start = self.index + 1; | 1157 | result.loc.start = self.index + 1; |
| 1137 | }, | 1158 | }, |
| 1138 | 0x01...0x08, 0x0b...0x0c, 0x0e...0x1f, 0x7f => { | 1159 | 0x01...0x09, 0x0b...0x0c, 0x0e...0x1f, 0x7f => { |
| 1139 | state = .invalid; | 1160 | state = .invalid; |
| 1140 | }, | 1161 | }, |
| 1141 | else => continue, | 1162 | else => continue, |
| 1142 | }, | 1163 | }, |
| 1143 | .doc_comment => switch (c) { | 1164 | .doc_comment => switch (c) { |
| 1144 | 0, '\n', '\r' => { | 1165 | 0, '\n' => { |
| 1145 | break; | 1166 | break; |
| 1146 | }, | 1167 | }, |
| 1147 | 0x01...0x08, 0x0b...0x0c, 0x0e...0x1f, 0x7f => { | 1168 | '\r' => { |
| | 1169 | if (self.buffer[self.index + 1] == '\n') { |
| | 1170 | self.index += 1; |
| | 1171 | break; |
| | 1172 | } else { |
| | 1173 | state = .invalid; |
| | 1174 | } |
| | 1175 | }, |
| | 1176 | 0x01...0x09, 0x0b...0x0c, 0x0e...0x1f, 0x7f => { |
| 1148 | state = .invalid; | 1177 | state = .invalid; |
| 1149 | }, | 1178 | }, |
| 1150 | else => continue, | 1179 | else => continue, |
| ... | @@ -1386,30 +1415,6 @@ test "string identifier and builtin fns" { | ... | @@ -1386,30 +1415,6 @@ test "string identifier and builtin fns" { |
| 1386 | }); | 1415 | }); |
| 1387 | } | 1416 | } |
| 1388 | | 1417 | |
| 1389 | test "multiline string literal with literal tab" { | | |
| 1390 | try testTokenize( | | |
| 1391 | \\\\foo	bar | | |
| 1392 | , &.{ | | |
| 1393 | .multiline_string_literal_line, | | |
| 1394 | }); | | |
| 1395 | } | | |
| 1396 | | | |
| 1397 | test "comments with literal tab" { | | |
| 1398 | try testTokenize( | | |
| 1399 | \\//foo	bar | | |
| 1400 | \\//!foo	bar | | |
| 1401 | \\///foo	bar | | |
| 1402 | \\//	foo | | |
| 1403 | \\///	foo | | |
| 1404 | \\///	/foo | | |
| 1405 | , &.{ | | |
| 1406 | .container_doc_comment, | | |
| 1407 | .doc_comment, | | |
| 1408 | .doc_comment, | | |
| 1409 | .doc_comment, | | |
| 1410 | }); | | |
| 1411 | } | | |
| 1412 | | | |
| 1413 | test "pipe and then invalid" { | 1418 | test "pipe and then invalid" { |
| 1414 | try testTokenize("||=", &.{ | 1419 | try testTokenize("||=", &.{ |
| 1415 | .pipe_pipe, | 1420 | .pipe_pipe, |
| ... | @@ -1767,6 +1772,60 @@ test "null byte before eof" { | ... | @@ -1767,6 +1772,60 @@ test "null byte before eof" { |
| 1767 | try testTokenize("/// NUL\x00\n", &.{ .doc_comment, .invalid }); | 1772 | try testTokenize("/// NUL\x00\n", &.{ .doc_comment, .invalid }); |
| 1768 | } | 1773 | } |
| 1769 | | 1774 | |
| | 1775 | test "invalid tabs and carriage returns" { |
| | 1776 | // "Inside Line Comments and Documentation Comments, Any TAB is rejected by |
| | 1777 | // the grammar since it is ambiguous how it should be rendered." |
| | 1778 | // https://github.com/ziglang/zig-spec/issues/38 |
| | 1779 | try testTokenize("//\t", &.{.invalid}); |
| | 1780 | try testTokenize("// \t", &.{.invalid}); |
| | 1781 | try testTokenize("///\t", &.{.invalid}); |
| | 1782 | try testTokenize("/// \t", &.{.invalid}); |
| | 1783 | try testTokenize("//!\t", &.{.invalid}); |
| | 1784 | try testTokenize("//! \t", &.{.invalid}); |
| | 1785 | |
| | 1786 | // "Inside Line Comments and Documentation Comments, CR directly preceding |
| | 1787 | // NL is unambiguously part of the newline sequence. It is accepted by the |
| | 1788 | // grammar and removed by zig fmt, leaving only NL. CR anywhere else is |
| | 1789 | // rejected by the grammar." |
| | 1790 | // https://github.com/ziglang/zig-spec/issues/38 |
| | 1791 | try testTokenize("//\r", &.{.invalid}); |
| | 1792 | try testTokenize("// \r", &.{.invalid}); |
| | 1793 | try testTokenize("///\r", &.{.invalid}); |
| | 1794 | try testTokenize("/// \r", &.{.invalid}); |
| | 1795 | try testTokenize("//\r ", &.{.invalid}); |
| | 1796 | try testTokenize("// \r ", &.{.invalid}); |
| | 1797 | try testTokenize("///\r ", &.{.invalid}); |
| | 1798 | try testTokenize("/// \r ", &.{.invalid}); |
| | 1799 | try testTokenize("//\r\n", &.{}); |
| | 1800 | try testTokenize("// \r\n", &.{}); |
| | 1801 | try testTokenize("///\r\n", &.{.doc_comment}); |
| | 1802 | try testTokenize("/// \r\n", &.{.doc_comment}); |
| | 1803 | try testTokenize("//!\r", &.{.invalid}); |
| | 1804 | try testTokenize("//! \r", &.{.invalid}); |
| | 1805 | try testTokenize("//!\r ", &.{.invalid}); |
| | 1806 | try testTokenize("//! \r ", &.{.invalid}); |
| | 1807 | try testTokenize("//!\r\n", &.{.container_doc_comment}); |
| | 1808 | try testTokenize("//! \r\n", &.{.container_doc_comment}); |
| | 1809 | |
| | 1810 | // The control characters TAB and CR are rejected by the grammar inside multi-line string literals, |
| | 1811 | // except if CR is directly before NL. |
| | 1812 | // https://github.com/ziglang/zig-spec/issues/38 |
| | 1813 | try testTokenize("\\\\\r", &.{.invalid}); |
| | 1814 | try testTokenize("\\\\\r ", &.{.invalid}); |
| | 1815 | try testTokenize("\\\\ \r", &.{.invalid}); |
| | 1816 | try testTokenize("\\\\\t", &.{.invalid}); |
| | 1817 | try testTokenize("\\\\\t ", &.{.invalid}); |
| | 1818 | try testTokenize("\\\\ \t", &.{.invalid}); |
| | 1819 | try testTokenize("\\\\\r\n", &.{.multiline_string_literal_line}); |
| | 1820 | |
| | 1821 | // "TAB used as whitespace is...accepted by the grammar. CR used as |
| | 1822 | // whitespace, whether directly preceding NL or stray, is...accepted by the |
| | 1823 | // grammar." |
| | 1824 | // https://github.com/ziglang/zig-spec/issues/38 |
| | 1825 | try testTokenize("\tpub\tswitch\t", &.{ .keyword_pub, .keyword_switch }); |
| | 1826 | try testTokenize("\rpub\rswitch\r", &.{ .keyword_pub, .keyword_switch }); |
| | 1827 | } |
| | 1828 | |
| 1770 | fn testTokenize(source: [:0]const u8, expected_token_tags: []const Token.Tag) !void { | 1829 | fn testTokenize(source: [:0]const u8, expected_token_tags: []const Token.Tag) !void { |
| 1771 | var tokenizer = Tokenizer.init(source); | 1830 | var tokenizer = Tokenizer.init(source); |
| 1772 | for (expected_token_tags) |expected_token_tag| { | 1831 | for (expected_token_tags) |expected_token_tag| { |