| ... | @@ -625,11 +625,22 @@ pub const ArgIteratorWasi = struct { | ... | @@ -625,11 +625,22 @@ pub const ArgIteratorWasi = struct { |
| 625 | }; | 625 | }; |
| 626 | | 626 | |
| 627 | /// Iterator that implements the Windows command-line parsing algorithm. | 627 | /// Iterator that implements the Windows command-line parsing algorithm. |
| | 628 | /// The implementation is intended to be compatible with the post-2008 C runtime, |
| | 629 | /// but is *not* intended to be compatible with `CommandLineToArgvW` since |
| | 630 | /// `CommandLineToArgvW` uses the pre-2008 parsing rules. |
| 628 | /// | 631 | /// |
| 629 | /// This iterator faithfully implements the parsing behavior observed in `CommandLineToArgvW` with | 632 | /// This iterator faithfully implements the parsing behavior observed from the C runtime with |
| 630 | /// one exception: if the command-line string is empty, the iterator will immediately complete | 633 | /// one exception: if the command-line string is empty, the iterator will immediately complete |
| 631 | /// without returning any arguments (whereas `CommandLineArgvW` will return a single argument | 634 | /// without returning any arguments (whereas the C runtime will return a single argument |
| 632 | /// representing the name of the current executable). | 635 | /// representing the name of the current executable). |
| | 636 | /// |
| | 637 | /// The essential parts of the algorithm are described in Microsoft's documentation: |
| | 638 | /// |
| | 639 | /// - https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=msvc-170#parsing-c-command-line-arguments |
| | 640 | /// |
| | 641 | /// David Deley explains some additional undocumented quirks in great detail: |
| | 642 | /// |
| | 643 | /// - https://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULES |
| 633 | pub const ArgIteratorWindows = struct { | 644 | pub const ArgIteratorWindows = struct { |
| 634 | allocator: Allocator, | 645 | allocator: Allocator, |
| 635 | /// Owned by the iterator. | 646 | /// Owned by the iterator. |
| ... | @@ -686,6 +697,51 @@ pub const ArgIteratorWindows = struct { | ... | @@ -686,6 +697,51 @@ pub const ArgIteratorWindows = struct { |
| 686 | fn emitCharacter(self: *ArgIteratorWindows, char: u8) void { | 697 | fn emitCharacter(self: *ArgIteratorWindows, char: u8) void { |
| 687 | self.buffer[self.end] = char; | 698 | self.buffer[self.end] = char; |
| 688 | self.end += 1; | 699 | self.end += 1; |
| | 700 | |
| | 701 | // Because we are emitting WTF-8 byte-by-byte, we need to |
| | 702 | // check to see if we've emitted two consecutive surrogate |
| | 703 | // codepoints that form a valid surrogate pair in order |
| | 704 | // to ensure that we're always emitting well-formed WTF-8 |
| | 705 | // (https://simonsapin.github.io/wtf-8/#concatenating). |
| | 706 | // |
| | 707 | // If we do have a valid surrogate pair, we need to emit |
| | 708 | // the UTF-8 sequence for the codepoint that they encode |
| | 709 | // instead of the WTF-8 encoding for the two surrogate pairs |
| | 710 | // separately. |
| | 711 | // |
| | 712 | // This is relevant when dealing with a WTF-16 encoded |
| | 713 | // command line like this: |
| | 714 | // "<0xD801>"<0xDC37> |
| | 715 | // which would get converted to WTF-8 in `cmd_line` as: |
| | 716 | // "<0xED><0xA0><0x81>"<0xED><0xB0><0xB7> |
| | 717 | // and then after parsing it'd naively get emitted as: |
| | 718 | // <0xED><0xA0><0x81><0xED><0xB0><0xB7> |
| | 719 | // but instead, we need to recognize the surrogate pair |
| | 720 | // and emit the codepoint it encodes, which in this |
| | 721 | // example is U+10437 (𐐷), which is encoded in UTF-8 as: |
| | 722 | // <0xF0><0x90><0x90><0xB7> |
| | 723 | concatSurrogatePair(self); |
| | 724 | } |
| | 725 | |
| | 726 | fn concatSurrogatePair(self: *ArgIteratorWindows) void { |
| | 727 | // Surrogate codepoints are always encoded as 3 bytes, so there |
| | 728 | // must be 6 bytes for a surrogate pair to exist. |
| | 729 | if (self.end - self.start >= 6) { |
| | 730 | const window = self.buffer[self.end - 6 .. self.end]; |
| | 731 | const view = std.unicode.Wtf8View.init(window) catch return; |
| | 732 | var it = view.iterator(); |
| | 733 | var pair: [2]u16 = undefined; |
| | 734 | pair[0] = std.mem.nativeToLittle(u16, std.math.cast(u16, it.nextCodepoint().?) orelse return); |
| | 735 | if (!std.unicode.utf16IsHighSurrogate(std.mem.littleToNative(u16, pair[0]))) return; |
| | 736 | pair[1] = std.mem.nativeToLittle(u16, std.math.cast(u16, it.nextCodepoint().?) orelse return); |
| | 737 | if (!std.unicode.utf16IsLowSurrogate(std.mem.littleToNative(u16, pair[1]))) return; |
| | 738 | // We know we have a valid surrogate pair, so convert |
| | 739 | // it to UTF-8, overwriting the surrogate pair's bytes |
| | 740 | // and then chop off the extra bytes. |
| | 741 | const len = std.unicode.utf16LeToUtf8(window, &pair) catch unreachable; |
| | 742 | const delta = 6 - len; |
| | 743 | self.end -= delta; |
| | 744 | } |
| 689 | } | 745 | } |
| 690 | | 746 | |
| 691 | fn yieldArg(self: *ArgIteratorWindows) [:0]const u8 { | 747 | fn yieldArg(self: *ArgIteratorWindows) [:0]const u8 { |
| ... | @@ -711,69 +767,37 @@ pub const ArgIteratorWindows = struct { | ... | @@ -711,69 +767,37 @@ pub const ArgIteratorWindows = struct { |
| 711 | } | 767 | } |
| 712 | }; | 768 | }; |
| 713 | | 769 | |
| 714 | // The essential parts of the algorithm are described in Microsoft's documentation: | | |
| 715 | // | | |
| 716 | // - <https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=msvc-170#parsing-c-command-line-arguments> | | |
| 717 | // - <https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw> | | |
| 718 | // | | |
| 719 | // David Deley explains some additional undocumented quirks in great detail: | | |
| 720 | // | | |
| 721 | // - <https://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULES> | | |
| 722 | // | | |
| 723 | // Code points <= U+0020 terminating an unquoted first argument was discovered independently by | | |
| 724 | // testing and observing the behavior of 'CommandLineToArgvW' on Windows 10. | | |
| 725 | | | |
| 726 | fn nextWithStrategy(self: *ArgIteratorWindows, comptime strategy: type) strategy.T { | 770 | fn nextWithStrategy(self: *ArgIteratorWindows, comptime strategy: type) strategy.T { |
| 727 | // The first argument (the executable name) uses different parsing rules. | 771 | // The first argument (the executable name) uses different parsing rules. |
| 728 | if (self.index == 0) { | 772 | if (self.index == 0) { |
| 729 | var char = if (self.cmd_line.len != 0) self.cmd_line[0] else 0; | 773 | if (self.cmd_line.len == 0 or self.cmd_line[0] == 0) { |
| 730 | switch (char) { | 774 | // Immediately complete the iterator. |
| 731 | 0 => { | 775 | // The C runtime would return the name of the current executable here. |
| 732 | // Immediately complete the iterator. | 776 | return strategy.eof; |
| 733 | // 'CommandLineToArgvW' would return the name of the current executable here. | 777 | } |
| 734 | return strategy.eof; | 778 | |
| 735 | }, | 779 | var inside_quotes = false; |
| 736 | '"' => { | 780 | while (true) : (self.index += 1) { |
| 737 | // If the first character is a quote, read everything until the next quote (then | 781 | const char = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0; |
| 738 | // skip that quote), or until the end of the string. | 782 | switch (char) { |
| 739 | self.index += 1; | 783 | 0 => { |
| 740 | while (true) : (self.index += 1) { | 784 | return strategy.yieldArg(self); |
| 741 | char = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0; | 785 | }, |
| 742 | switch (char) { | 786 | '"' => { |
| 743 | 0 => { | 787 | inside_quotes = !inside_quotes; |
| 744 | return strategy.yieldArg(self); | 788 | }, |
| 745 | }, | 789 | ' ', '\t' => { |
| 746 | '"' => { | 790 | if (inside_quotes) |
| 747 | self.index += 1; | 791 | strategy.emitCharacter(self, char) |
| 748 | return strategy.yieldArg(self); | 792 | else { |
| 749 | }, | 793 | self.index += 1; |
| 750 | else => { | 794 | return strategy.yieldArg(self); |
| 751 | strategy.emitCharacter(self, char); | | |
| 752 | }, | | |
| 753 | } | | |
| 754 | } | | |
| 755 | }, | | |
| 756 | else => { | | |
| 757 | // Otherwise, read everything until the next space or ASCII control character | | |
| 758 | // (not including DEL) (then skip that character), or until the end of the | | |
| 759 | // string. This means that if the command-line string starts with one of these | | |
| 760 | // characters, the first returned argument will be the empty string. | | |
| 761 | while (true) : (self.index += 1) { | | |
| 762 | char = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0; | | |
| 763 | switch (char) { | | |
| 764 | 0 => { | | |
| 765 | return strategy.yieldArg(self); | | |
| 766 | }, | | |
| 767 | '\x01'...' ' => { | | |
| 768 | self.index += 1; | | |
| 769 | return strategy.yieldArg(self); | | |
| 770 | }, | | |
| 771 | else => { | | |
| 772 | strategy.emitCharacter(self, char); | | |
| 773 | }, | | |
| 774 | } | 795 | } |
| 775 | } | 796 | }, |
| 776 | }, | 797 | else => { |
| | 798 | strategy.emitCharacter(self, char); |
| | 799 | }, |
| | 800 | } |
| 777 | } | 801 | } |
| 778 | } | 802 | } |
| 779 | | 803 | |
| ... | @@ -791,9 +815,10 @@ pub const ArgIteratorWindows = struct { | ... | @@ -791,9 +815,10 @@ pub const ArgIteratorWindows = struct { |
| 791 | // | 815 | // |
| 792 | // - The end of the string always terminates the current argument. | 816 | // - The end of the string always terminates the current argument. |
| 793 | // - When not in 'inside_quotes' mode, a space or tab terminates the current argument. | 817 | // - When not in 'inside_quotes' mode, a space or tab terminates the current argument. |
| 794 | // - 2n backslashes followed by a quote emit n backslashes. If in 'inside_quotes' and the | 818 | // - 2n backslashes followed by a quote emit n backslashes (note: n can be zero). |
| 795 | // quote is immediately followed by a second quote, one quote is emitted and the other is | 819 | // If in 'inside_quotes' and the quote is immediately followed by a second quote, |
| 796 | // skipped, otherwise, the quote is skipped. Finally, 'inside_quotes' is toggled. | 820 | // one quote is emitted and the other is skipped, otherwise, the quote is skipped |
| | 821 | // and 'inside_quotes' is toggled. |
| 797 | // - 2n + 1 backslashes followed by a quote emit n backslashes followed by a quote. | 822 | // - 2n + 1 backslashes followed by a quote emit n backslashes followed by a quote. |
| 798 | // - n backslashes not followed by a quote emit n backslashes. | 823 | // - n backslashes not followed by a quote emit n backslashes. |
| 799 | var backslash_count: usize = 0; | 824 | var backslash_count: usize = 0; |
| ... | @@ -826,8 +851,9 @@ pub const ArgIteratorWindows = struct { | ... | @@ -826,8 +851,9 @@ pub const ArgIteratorWindows = struct { |
| 826 | { | 851 | { |
| 827 | strategy.emitCharacter(self, '"'); | 852 | strategy.emitCharacter(self, '"'); |
| 828 | self.index += 1; | 853 | self.index += 1; |
| | 854 | } else { |
| | 855 | inside_quotes = !inside_quotes; |
| 829 | } | 856 | } |
| 830 | inside_quotes = !inside_quotes; | | |
| 831 | } | 857 | } |
| 832 | }, | 858 | }, |
| 833 | '\\' => { | 859 | '\\' => { |
| ... | @@ -1215,10 +1241,10 @@ test ArgIteratorWindows { | ... | @@ -1215,10 +1241,10 @@ test ArgIteratorWindows { |
| 1215 | // Separators | 1241 | // Separators |
| 1216 | try t("aa bb cc", &.{ "aa", "bb", "cc" }); | 1242 | try t("aa bb cc", &.{ "aa", "bb", "cc" }); |
| 1217 | try t("aa\tbb\tcc", &.{ "aa", "bb", "cc" }); | 1243 | try t("aa\tbb\tcc", &.{ "aa", "bb", "cc" }); |
| 1218 | try t("aa\nbb\ncc", &.{ "aa", "bb\ncc" }); | 1244 | try t("aa\nbb\ncc", &.{"aa\nbb\ncc"}); |
| 1219 | try t("aa\r\nbb\r\ncc", &.{ "aa", "\nbb\r\ncc" }); | 1245 | try t("aa\r\nbb\r\ncc", &.{"aa\r\nbb\r\ncc"}); |
| 1220 | try t("aa\rbb\rcc", &.{ "aa", "bb\rcc" }); | 1246 | try t("aa\rbb\rcc", &.{"aa\rbb\rcc"}); |
| 1221 | try t("aa\x07bb\x07cc", &.{ "aa", "bb\x07cc" }); | 1247 | try t("aa\x07bb\x07cc", &.{"aa\x07bb\x07cc"}); |
| 1222 | try t("aa\x7Fbb\x7Fcc", &.{"aa\x7Fbb\x7Fcc"}); | 1248 | try t("aa\x7Fbb\x7Fcc", &.{"aa\x7Fbb\x7Fcc"}); |
| 1223 | try t("aa🦎bb🦎cc", &.{"aa🦎bb🦎cc"}); | 1249 | try t("aa🦎bb🦎cc", &.{"aa🦎bb🦎cc"}); |
| 1224 | | 1250 | |
| ... | @@ -1227,22 +1253,22 @@ test ArgIteratorWindows { | ... | @@ -1227,22 +1253,22 @@ test ArgIteratorWindows { |
| 1227 | try t(" aa bb ", &.{ "", "aa", "bb" }); | 1253 | try t(" aa bb ", &.{ "", "aa", "bb" }); |
| 1228 | try t("\t\t", &.{""}); | 1254 | try t("\t\t", &.{""}); |
| 1229 | try t("\t\taa\t\tbb\t\t", &.{ "", "aa", "bb" }); | 1255 | try t("\t\taa\t\tbb\t\t", &.{ "", "aa", "bb" }); |
| 1230 | try t("\n\n", &.{ "", "\n" }); | 1256 | try t("\n\n", &.{"\n\n"}); |
| 1231 | try t("\n\naa\n\nbb\n\n", &.{ "", "\naa\n\nbb\n\n" }); | 1257 | try t("\n\naa\n\nbb\n\n", &.{"\n\naa\n\nbb\n\n"}); |
| 1232 | | 1258 | |
| 1233 | // Executable name with quotes/backslashes | 1259 | // Executable name with quotes/backslashes |
| 1234 | try t("\"aa bb\tcc\ndd\"", &.{"aa bb\tcc\ndd"}); | 1260 | try t("\"aa bb\tcc\ndd\"", &.{"aa bb\tcc\ndd"}); |
| 1235 | try t("\"", &.{""}); | 1261 | try t("\"", &.{""}); |
| 1236 | try t("\"\"", &.{""}); | 1262 | try t("\"\"", &.{""}); |
| 1237 | try t("\"\"\"", &.{ "", "" }); | 1263 | try t("\"\"\"", &.{""}); |
| 1238 | try t("\"\"\"\"", &.{ "", "" }); | 1264 | try t("\"\"\"\"", &.{""}); |
| 1239 | try t("\"\"\"\"\"", &.{ "", "\"" }); | 1265 | try t("\"\"\"\"\"", &.{""}); |
| 1240 | try t("aa\"bb\"cc\"dd", &.{"aa\"bb\"cc\"dd"}); | 1266 | try t("aa\"bb\"cc\"dd", &.{"aabbccdd"}); |
| 1241 | try t("aa\"bb cc\"dd", &.{ "aa\"bb", "ccdd" }); | 1267 | try t("aa\"bb cc\"dd", &.{"aabb ccdd"}); |
| 1242 | try t("\"aa\\\"bb\"", &.{ "aa\\", "bb" }); | 1268 | try t("\"aa\\\"bb\"", &.{"aa\\bb"}); |
| 1243 | try t("\"aa\\\\\"", &.{"aa\\\\"}); | 1269 | try t("\"aa\\\\\"", &.{"aa\\\\"}); |
| 1244 | try t("aa\\\"bb", &.{"aa\\\"bb"}); | 1270 | try t("aa\\\"bb", &.{"aa\\bb"}); |
| 1245 | try t("aa\\\\\"bb", &.{"aa\\\\\"bb"}); | 1271 | try t("aa\\\\\"bb", &.{"aa\\\\bb"}); |
| 1246 | | 1272 | |
| 1247 | // Arguments with quotes/backslashes | 1273 | // Arguments with quotes/backslashes |
| 1248 | try t(". \"aa bb\tcc\ndd\"", &.{ ".", "aa bb\tcc\ndd" }); | 1274 | try t(". \"aa bb\tcc\ndd\"", &.{ ".", "aa bb\tcc\ndd" }); |
| ... | @@ -1252,29 +1278,66 @@ test ArgIteratorWindows { | ... | @@ -1252,29 +1278,66 @@ test ArgIteratorWindows { |
| 1252 | try t(". \"\"", &.{ ".", "" }); | 1278 | try t(". \"\"", &.{ ".", "" }); |
| 1253 | try t(". \"\"\"", &.{ ".", "\"" }); | 1279 | try t(". \"\"\"", &.{ ".", "\"" }); |
| 1254 | try t(". \"\"\"\"", &.{ ".", "\"" }); | 1280 | try t(". \"\"\"\"", &.{ ".", "\"" }); |
| 1255 | try t(". \"\"\"\"\"", &.{ ".", "\"" }); | 1281 | try t(". \"\"\"\"\"", &.{ ".", "\"\"" }); |
| 1256 | try t(". \"\"\"\"\"\"", &.{ ".", "\"\"" }); | 1282 | try t(". \"\"\"\"\"\"", &.{ ".", "\"\"" }); |
| 1257 | try t(". \" \"", &.{ ".", " " }); | 1283 | try t(". \" \"", &.{ ".", " " }); |
| 1258 | try t(". \" \"\"", &.{ ".", " \"" }); | 1284 | try t(". \" \"\"", &.{ ".", " \"" }); |
| 1259 | try t(". \" \"\"\"", &.{ ".", " \"" }); | 1285 | try t(". \" \"\"\"", &.{ ".", " \"" }); |
| 1260 | try t(". \" \"\"\"\"", &.{ ".", " \"" }); | 1286 | try t(". \" \"\"\"\"", &.{ ".", " \"\"" }); |
| 1261 | try t(". \" \"\"\"\"\"", &.{ ".", " \"\"" }); | 1287 | try t(". \" \"\"\"\"\"", &.{ ".", " \"\"" }); |
| 1262 | try t(". \" \"\"\"\"\"\"", &.{ ".", " \"\"" }); | 1288 | try t(". \" \"\"\"\"\"\"", &.{ ".", " \"\"\"" }); |
| 1263 | try t(". \\\"", &.{ ".", "\"" }); | 1289 | try t(". \\\"", &.{ ".", "\"" }); |
| 1264 | try t(". \\\"\"", &.{ ".", "\"" }); | 1290 | try t(". \\\"\"", &.{ ".", "\"" }); |
| 1265 | try t(". \\\"\"\"", &.{ ".", "\"" }); | 1291 | try t(". \\\"\"\"", &.{ ".", "\"" }); |
| 1266 | try t(". \\\"\"\"\"", &.{ ".", "\"\"" }); | 1292 | try t(". \\\"\"\"\"", &.{ ".", "\"\"" }); |
| 1267 | try t(". \\\"\"\"\"\"", &.{ ".", "\"\"" }); | 1293 | try t(". \\\"\"\"\"\"", &.{ ".", "\"\"" }); |
| 1268 | try t(". \\\"\"\"\"\"\"", &.{ ".", "\"\"" }); | 1294 | try t(". \\\"\"\"\"\"\"", &.{ ".", "\"\"\"" }); |
| 1269 | try t(". \" \\\"", &.{ ".", " \"" }); | 1295 | try t(". \" \\\"", &.{ ".", " \"" }); |
| 1270 | try t(". \" \\\"\"", &.{ ".", " \"" }); | 1296 | try t(". \" \\\"\"", &.{ ".", " \"" }); |
| 1271 | try t(". \" \\\"\"\"", &.{ ".", " \"\"" }); | 1297 | try t(". \" \\\"\"\"", &.{ ".", " \"\"" }); |
| 1272 | try t(". \" \\\"\"\"\"", &.{ ".", " \"\"" }); | 1298 | try t(". \" \\\"\"\"\"", &.{ ".", " \"\"" }); |
| 1273 | try t(". \" \\\"\"\"\"\"", &.{ ".", " \"\"" }); | 1299 | try t(". \" \\\"\"\"\"\"", &.{ ".", " \"\"\"" }); |
| 1274 | try t(". \" \\\"\"\"\"\"\"", &.{ ".", " \"\"\"" }); | 1300 | try t(". \" \\\"\"\"\"\"\"", &.{ ".", " \"\"\"" }); |
| 1275 | try t(". aa\\bb\\\\cc\\\\\\dd", &.{ ".", "aa\\bb\\\\cc\\\\\\dd" }); | 1301 | try t(". aa\\bb\\\\cc\\\\\\dd", &.{ ".", "aa\\bb\\\\cc\\\\\\dd" }); |
| 1276 | try t(". \\\\\\\"aa bb\"", &.{ ".", "\\\"aa", "bb" }); | 1302 | try t(". \\\\\\\"aa bb\"", &.{ ".", "\\\"aa", "bb" }); |
| 1277 | try t(". \\\\\\\\\"aa bb\"", &.{ ".", "\\\\aa bb" }); | 1303 | try t(". \\\\\\\\\"aa bb\"", &.{ ".", "\\\\aa bb" }); |
| | 1304 | |
| | 1305 | // From https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args#results-of-parsing-command-lines |
| | 1306 | try t( |
| | 1307 | \\foo.exe "abc" d e |
| | 1308 | , &.{ "foo.exe", "abc", "d", "e" }); |
| | 1309 | try t( |
| | 1310 | \\foo.exe a\\b d"e f"g h |
| | 1311 | , &.{ "foo.exe", "a\\\\b", "de fg", "h" }); |
| | 1312 | try t( |
| | 1313 | \\foo.exe a\\\"b c d |
| | 1314 | , &.{ "foo.exe", "a\\\"b", "c", "d" }); |
| | 1315 | try t( |
| | 1316 | \\foo.exe a\\\\"b c" d e |
| | 1317 | , &.{ "foo.exe", "a\\\\b c", "d", "e" }); |
| | 1318 | try t( |
| | 1319 | \\foo.exe a"b"" c d |
| | 1320 | , &.{ "foo.exe", "ab\" c d" }); |
| | 1321 | |
| | 1322 | // From https://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULESEX |
| | 1323 | try t("foo.exe CallMeIshmael", &.{ "foo.exe", "CallMeIshmael" }); |
| | 1324 | try t("foo.exe \"Call Me Ishmael\"", &.{ "foo.exe", "Call Me Ishmael" }); |
| | 1325 | try t("foo.exe Cal\"l Me I\"shmael", &.{ "foo.exe", "Call Me Ishmael" }); |
| | 1326 | try t("foo.exe CallMe\\\"Ishmael", &.{ "foo.exe", "CallMe\"Ishmael" }); |
| | 1327 | try t("foo.exe \"CallMe\\\"Ishmael\"", &.{ "foo.exe", "CallMe\"Ishmael" }); |
| | 1328 | try t("foo.exe \"Call Me Ishmael\\\\\"", &.{ "foo.exe", "Call Me Ishmael\\" }); |
| | 1329 | try t("foo.exe \"CallMe\\\\\\\"Ishmael\"", &.{ "foo.exe", "CallMe\\\"Ishmael" }); |
| | 1330 | try t("foo.exe a\\\\\\b", &.{ "foo.exe", "a\\\\\\b" }); |
| | 1331 | try t("foo.exe \"a\\\\\\b\"", &.{ "foo.exe", "a\\\\\\b" }); |
| | 1332 | |
| | 1333 | // Surrogate pair encoding of 𐐷 separated by quotes. |
| | 1334 | // Encoded as WTF-16: |
| | 1335 | // "<0xD801>"<0xDC37> |
| | 1336 | // Encoded as WTF-8: |
| | 1337 | // "<0xED><0xA0><0x81>"<0xED><0xB0><0xB7> |
| | 1338 | // During parsing, the quotes drop out and the surrogate pair |
| | 1339 | // should end up encoded as its normal UTF-8 representation. |
| | 1340 | try t("foo.exe \"\xed\xa0\x81\"\xed\xb0\xb7", &.{ "foo.exe", "𐐷" }); |
| 1278 | } | 1341 | } |
| 1279 | | 1342 | |
| 1280 | fn testArgIteratorWindows(cmd_line: []const u8, expected_args: []const []const u8) !void { | 1343 | fn testArgIteratorWindows(cmd_line: []const u8, expected_args: []const []const u8) !void { |