| ... | ... | @@ -285,27 +285,35 @@ pub const ArgIteratorWasi = struct { |
| 285 | 285 | |
| 286 | 286 | pub const ArgIteratorWindows = struct { |
| 287 | 287 | index: usize, |
| 288 | | cmd_line: [*]const u8, |
| 288 | cmd_line: [*]const u16, |
| 289 | 289 | |
| 290 | | pub const NextError = error{OutOfMemory}; |
| 290 | pub const NextError = error{ OutOfMemory, InvalidCmdLine }; |
| 291 | 291 | |
| 292 | 292 | pub fn init() ArgIteratorWindows { |
| 293 | | return initWithCmdLine(os.windows.kernel32.GetCommandLineA()); |
| 293 | return initWithCmdLine(os.windows.kernel32.GetCommandLineW()); |
| 294 | 294 | } |
| 295 | 295 | |
| 296 | | pub fn initWithCmdLine(cmd_line: [*]const u8) ArgIteratorWindows { |
| 296 | pub fn initWithCmdLine(cmd_line: [*]const u16) ArgIteratorWindows { |
| 297 | 297 | return ArgIteratorWindows{ |
| 298 | 298 | .index = 0, |
| 299 | 299 | .cmd_line = cmd_line, |
| 300 | 300 | }; |
| 301 | 301 | } |
| 302 | 302 | |
| 303 | fn getPointAtIndex(self: *ArgIteratorWindows) u16 { |
| 304 | // According to |
| 305 | // https://docs.microsoft.com/en-us/windows/win32/intl/using-byte-order-marks |
| 306 | // Microsoft uses UTF16-LE. So we just read assuming it's little |
| 307 | // endian. |
| 308 | return std.mem.littleToNative(u16, self.cmd_line[self.index]); |
| 309 | } |
| 310 | |
| 303 | 311 | /// You must free the returned memory when done. |
| 304 | 312 | pub fn next(self: *ArgIteratorWindows, allocator: *Allocator) ?(NextError![:0]u8) { |
| 305 | 313 | // march forward over whitespace |
| 306 | 314 | while (true) : (self.index += 1) { |
| 307 | | const byte = self.cmd_line[self.index]; |
| 308 | | switch (byte) { |
| 315 | const character = self.getPointAtIndex(); |
| 316 | switch (character) { |
| 309 | 317 | 0 => return null, |
| 310 | 318 | ' ', '\t' => continue, |
| 311 | 319 | else => break, |
| ... | ... | @@ -318,8 +326,8 @@ pub const ArgIteratorWindows = struct { |
| 318 | 326 | pub fn skip(self: *ArgIteratorWindows) bool { |
| 319 | 327 | // march forward over whitespace |
| 320 | 328 | while (true) : (self.index += 1) { |
| 321 | | const byte = self.cmd_line[self.index]; |
| 322 | | switch (byte) { |
| 329 | const character = self.getPointAtIndex(); |
| 330 | switch (character) { |
| 323 | 331 | 0 => return false, |
| 324 | 332 | ' ', '\t' => continue, |
| 325 | 333 | else => break, |
| ... | ... | @@ -329,8 +337,8 @@ pub const ArgIteratorWindows = struct { |
| 329 | 337 | var backslash_count: usize = 0; |
| 330 | 338 | var in_quote = false; |
| 331 | 339 | while (true) : (self.index += 1) { |
| 332 | | const byte = self.cmd_line[self.index]; |
| 333 | | switch (byte) { |
| 340 | const character = self.getPointAtIndex(); |
| 341 | switch (character) { |
| 334 | 342 | 0 => return true, |
| 335 | 343 | '"' => { |
| 336 | 344 | const quote_is_real = backslash_count % 2 == 0; |
| ... | ... | @@ -356,15 +364,17 @@ pub const ArgIteratorWindows = struct { |
| 356 | 364 | } |
| 357 | 365 | |
| 358 | 366 | fn internalNext(self: *ArgIteratorWindows, allocator: *Allocator) NextError![:0]u8 { |
| 359 | | var buf = try std.ArrayListSentineled(u8, 0).init(allocator, ""); |
| 367 | var buf = std.ArrayList(u16).init(allocator); |
| 360 | 368 | defer buf.deinit(); |
| 361 | 369 | |
| 362 | 370 | var backslash_count: usize = 0; |
| 363 | 371 | var in_quote = false; |
| 364 | 372 | while (true) : (self.index += 1) { |
| 365 | | const byte = self.cmd_line[self.index]; |
| 366 | | switch (byte) { |
| 367 | | 0 => return buf.toOwnedSlice(), |
| 373 | const character = self.getPointAtIndex(); |
| 374 | switch (character) { |
| 375 | 0 => { |
| 376 | return convertFromWindowsCmdLineToUTF8(allocator, buf.items); |
| 377 | }, |
| 368 | 378 | '"' => { |
| 369 | 379 | const quote_is_real = backslash_count % 2 == 0; |
| 370 | 380 | try self.emitBackslashes(&buf, backslash_count / 2); |
| ... | ... | @@ -373,7 +383,7 @@ pub const ArgIteratorWindows = struct { |
| 373 | 383 | if (quote_is_real) { |
| 374 | 384 | in_quote = !in_quote; |
| 375 | 385 | } else { |
| 376 | | try buf.append('"'); |
| 386 | try buf.append(std.mem.nativeToLittle(u16, '"')); |
| 377 | 387 | } |
| 378 | 388 | }, |
| 379 | 389 | '\\' => { |
| ... | ... | @@ -383,24 +393,34 @@ pub const ArgIteratorWindows = struct { |
| 383 | 393 | try self.emitBackslashes(&buf, backslash_count); |
| 384 | 394 | backslash_count = 0; |
| 385 | 395 | if (in_quote) { |
| 386 | | try buf.append(byte); |
| 396 | try buf.append(std.mem.nativeToLittle(u16, character)); |
| 387 | 397 | } else { |
| 388 | | return buf.toOwnedSlice(); |
| 398 | return convertFromWindowsCmdLineToUTF8(allocator, buf.items); |
| 389 | 399 | } |
| 390 | 400 | }, |
| 391 | 401 | else => { |
| 392 | 402 | try self.emitBackslashes(&buf, backslash_count); |
| 393 | 403 | backslash_count = 0; |
| 394 | | try buf.append(byte); |
| 404 | try buf.append(std.mem.nativeToLittle(u16, character)); |
| 395 | 405 | }, |
| 396 | 406 | } |
| 397 | 407 | } |
| 398 | 408 | } |
| 399 | 409 | |
| 400 | | fn emitBackslashes(self: *ArgIteratorWindows, buf: *std.ArrayListSentineled(u8, 0), emit_count: usize) !void { |
| 410 | fn convertFromWindowsCmdLineToUTF8(allocator: *Allocator, buf: []u16) NextError![:0]u8 { |
| 411 | return std.unicode.utf16leToUtf8AllocZ(allocator, buf) catch |err| switch (err) { |
| 412 | error.ExpectedSecondSurrogateHalf, |
| 413 | error.DanglingSurrogateHalf, |
| 414 | error.UnexpectedSecondSurrogateHalf, |
| 415 | => return error.InvalidCmdLine, |
| 416 | |
| 417 | error.OutOfMemory => return error.OutOfMemory, |
| 418 | }; |
| 419 | } |
| 420 | fn emitBackslashes(self: *ArgIteratorWindows, buf: *std.ArrayList(u16), emit_count: usize) !void { |
| 401 | 421 | var i: usize = 0; |
| 402 | 422 | while (i < emit_count) : (i += 1) { |
| 403 | | try buf.append('\\'); |
| 423 | try buf.append(std.mem.nativeToLittle(u16, '\\')); |
| 404 | 424 | } |
| 405 | 425 | } |
| 406 | 426 | }; |
| ... | ... | @@ -552,14 +572,15 @@ pub fn argsFree(allocator: *mem.Allocator, args_alloc: []const [:0]u8) void { |
| 552 | 572 | } |
| 553 | 573 | |
| 554 | 574 | test "windows arg parsing" { |
| 555 | | testWindowsCmdLine("a b\tc d", &[_][]const u8{ "a", "b", "c", "d" }); |
| 556 | | testWindowsCmdLine("\"abc\" d e", &[_][]const u8{ "abc", "d", "e" }); |
| 557 | | testWindowsCmdLine("a\\\\\\b d\"e f\"g h", &[_][]const u8{ "a\\\\\\b", "de fg", "h" }); |
| 558 | | testWindowsCmdLine("a\\\\\\\"b c d", &[_][]const u8{ "a\\\"b", "c", "d" }); |
| 559 | | testWindowsCmdLine("a\\\\\\\\\"b c\" d e", &[_][]const u8{ "a\\\\b c", "d", "e" }); |
| 560 | | testWindowsCmdLine("a b\tc \"d f", &[_][]const u8{ "a", "b", "c", "d f" }); |
| 561 | | |
| 562 | | testWindowsCmdLine("\".\\..\\zig-cache\\build\" \"bin\\zig.exe\" \".\\..\" \".\\..\\zig-cache\" \"--help\"", &[_][]const u8{ |
| 575 | const utf16Literal = std.unicode.utf8ToUtf16LeStringLiteral; |
| 576 | testWindowsCmdLine(utf16Literal("a b\tc d"), &[_][]const u8{ "a", "b", "c", "d" }); |
| 577 | testWindowsCmdLine(utf16Literal("\"abc\" d e"), &[_][]const u8{ "abc", "d", "e" }); |
| 578 | testWindowsCmdLine(utf16Literal("a\\\\\\b d\"e f\"g h"), &[_][]const u8{ "a\\\\\\b", "de fg", "h" }); |
| 579 | testWindowsCmdLine(utf16Literal("a\\\\\\\"b c d"), &[_][]const u8{ "a\\\"b", "c", "d" }); |
| 580 | testWindowsCmdLine(utf16Literal("a\\\\\\\\\"b c\" d e"), &[_][]const u8{ "a\\\\b c", "d", "e" }); |
| 581 | testWindowsCmdLine(utf16Literal("a b\tc \"d f"), &[_][]const u8{ "a", "b", "c", "d f" }); |
| 582 | |
| 583 | testWindowsCmdLine(utf16Literal("\".\\..\\zig-cache\\build\" \"bin\\zig.exe\" \".\\..\" \".\\..\\zig-cache\" \"--help\""), &[_][]const u8{ |
| 563 | 584 | ".\\..\\zig-cache\\build", |
| 564 | 585 | "bin\\zig.exe", |
| 565 | 586 | ".\\..", |
| ... | ... | @@ -568,7 +589,7 @@ test "windows arg parsing" { |
| 568 | 589 | }); |
| 569 | 590 | } |
| 570 | 591 | |
| 571 | | fn testWindowsCmdLine(input_cmd_line: [*]const u8, expected_args: []const []const u8) void { |
| 592 | fn testWindowsCmdLine(input_cmd_line: [*]const u16, expected_args: []const []const u8) void { |
| 572 | 593 | var it = ArgIteratorWindows.initWithCmdLine(input_cmd_line); |
| 573 | 594 | for (expected_args) |expected_arg| { |
| 574 | 595 | const arg = it.next(std.testing.allocator).? catch unreachable; |