Skip to content

Commit b61a6ec

Browse files
committed
implement command line argument parsing for windows
See #302
1 parent 717e791 commit b61a6ec

File tree

8 files changed

+320
-56
lines changed

8 files changed

+320
-56
lines changed

example/cat/main.zig

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ const mem = std.mem;
44
const os = std.os;
55

66
pub fn main() -> %void {
7-
const exe = os.args.at(0);
7+
const allocator = &std.debug.global_allocator;
8+
var args_it = os.args();
9+
const exe = %return unwrapArg(??args_it.next(allocator));
810
var catted_anything = false;
9-
var arg_i: usize = 1;
10-
while (arg_i < os.args.count()) : (arg_i += 1) {
11-
const arg = os.args.at(arg_i);
11+
while (args_it.next(allocator)) |arg_or_err| {
12+
const arg = %return unwrapArg(arg_or_err);
1213
if (mem.eql(u8, arg, "-")) {
1314
catted_anything = true;
1415
%return cat_stream(&io.stdin);
@@ -55,3 +56,10 @@ fn cat_stream(is: &io.InStream) -> %void {
5556
};
5657
}
5758
}
59+
60+
fn unwrapArg(arg: %[]u8) -> %[]u8 {
61+
return arg %% |err| {
62+
%%io.stderr.printf("Unable to parse command line: {}\n", err);
63+
return err;
64+
};
65+
}

std/array_list.zig

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub fn ArrayList(comptime T: type) -> type{
1414
len: usize,
1515
allocator: &Allocator,
1616

17+
/// Deinitialize with `deinit` or use `toOwnedSlice`.
1718
pub fn init(allocator: &Allocator) -> Self {
1819
Self {
1920
.items = []T{},
@@ -34,6 +35,25 @@ pub fn ArrayList(comptime T: type) -> type{
3435
return l.items[0..l.len];
3536
}
3637

38+
/// ArrayList takes ownership of the passed in slice. The slice must have been
39+
/// allocated with `allocator`.
40+
/// Deinitialize with `deinit` or use `toOwnedSlice`.
41+
pub fn fromOwnedSlice(allocator: &Allocator, slice: []T) -> Self {
42+
return Self {
43+
.items = slice,
44+
.len = slice.len,
45+
.allocator = allocator,
46+
};
47+
}
48+
49+
/// The caller owns the returned memory. ArrayList becomes empty.
50+
pub fn toOwnedSlice(self: &Self) -> []T {
51+
const allocator = self.allocator;
52+
const result = allocator.shrink(T, self.items, self.len);
53+
*self = init(allocator);
54+
return result;
55+
}
56+
3757
pub fn append(l: &Self, item: &const T) -> %void {
3858
const new_item_ptr = %return l.addOne();
3959
*new_item_ptr = *item;

std/buffer.zig

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,27 @@ pub const Buffer = struct {
3838
return Buffer.init(buffer.list.allocator, buffer.toSliceConst());
3939
}
4040

41+
/// Buffer takes ownership of the passed in slice. The slice must have been
42+
/// allocated with `allocator`.
43+
/// Must deinitialize with deinit.
44+
pub fn fromOwnedSlice(allocator: &Allocator, slice: []u8) -> Buffer {
45+
var self = Buffer {
46+
.list = ArrayList(u8).fromOwnedSlice(allocator, slice),
47+
};
48+
self.list.append(0);
49+
return self;
50+
}
51+
52+
/// The caller owns the returned memory. The Buffer becomes null and
53+
/// is safe to `deinit`.
54+
pub fn toOwnedSlice(self: &Buffer) -> []u8 {
55+
const allocator = self.list.allocator;
56+
const result = allocator.shrink(u8, self.list.items, self.len());
57+
*self = initNull(allocator);
58+
return result;
59+
}
60+
61+
4162
pub fn deinit(self: &Buffer) {
4263
self.list.deinit();
4364
}

std/os/index.zig

Lines changed: 230 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
const builtin = @import("builtin");
22
const Os = builtin.Os;
3+
const is_windows = builtin.os == Os.windows;
4+
35
pub const windows = @import("windows/index.zig");
46
pub const darwin = @import("darwin.zig");
57
pub const linux = @import("linux.zig");
@@ -37,6 +39,7 @@ const cstr = @import("../cstr.zig");
3739
const io = @import("../io.zig");
3840
const base64 = @import("../base64.zig");
3941
const ArrayList = @import("../array_list.zig").ArrayList;
42+
const Buffer = @import("../buffer.zig").Buffer;
4043

4144
error Unexpected;
4245
error SystemResources;
@@ -513,18 +516,6 @@ pub fn getEnv(key: []const u8) -> ?[]const u8 {
513516
return null;
514517
}
515518

516-
pub const args = struct {
517-
pub var raw: []&u8 = undefined;
518-
519-
pub fn count() -> usize {
520-
return raw.len;
521-
}
522-
pub fn at(i: usize) -> []const u8 {
523-
const s = raw[i];
524-
return cstr.toSlice(s);
525-
}
526-
};
527-
528519
/// Caller must free the returned memory.
529520
pub fn getCwd(allocator: &Allocator) -> %[]u8 {
530521
switch (builtin.os) {
@@ -1144,6 +1135,233 @@ pub fn posix_setregid(rgid: u32, egid: u32) -> %void {
11441135
};
11451136
}
11461137

1138+
pub const ArgIteratorPosix = struct {
1139+
index: usize,
1140+
count: usize,
1141+
1142+
pub fn init() -> ArgIteratorPosix {
1143+
return ArgIteratorPosix {
1144+
.index = 0,
1145+
.count = raw.len,
1146+
};
1147+
}
1148+
1149+
pub fn next(self: &ArgIteratorPosix) -> ?[]const u8 {
1150+
if (self.index == self.count)
1151+
return null;
1152+
1153+
const s = raw[self.index];
1154+
self.index += 1;
1155+
return cstr.toSlice(s);
1156+
}
1157+
1158+
pub fn skip(self: &ArgIteratorPosix) -> bool {
1159+
if (self.index == self.count)
1160+
return false;
1161+
1162+
self.index += 1;
1163+
return true;
1164+
}
1165+
1166+
/// This is marked as public but actually it's only meant to be used
1167+
/// internally by zig's startup code.
1168+
pub var raw: []&u8 = undefined;
1169+
};
1170+
1171+
pub const ArgIteratorWindows = struct {
1172+
index: usize,
1173+
cmd_line: &const u8,
1174+
backslash_count: usize,
1175+
in_quote: bool,
1176+
quote_count: usize,
1177+
seen_quote_count: usize,
1178+
1179+
pub fn init() -> ArgIteratorWindows {
1180+
return initWithCmdLine(windows.GetCommandLineA());
1181+
}
1182+
1183+
pub fn initWithCmdLine(cmd_line: &const u8) -> ArgIteratorWindows {
1184+
return ArgIteratorWindows {
1185+
.index = 0,
1186+
.cmd_line = cmd_line,
1187+
.backslash_count = 0,
1188+
.in_quote = false,
1189+
.quote_count = countQuotes(cmd_line),
1190+
.seen_quote_count = 0,
1191+
};
1192+
}
1193+
1194+
/// You must free the returned memory when done.
1195+
pub fn next(self: &ArgIteratorWindows, allocator: &Allocator) -> ?%[]u8 {
1196+
// march forward over whitespace
1197+
while (true) : (self.index += 1) {
1198+
const byte = self.cmd_line[self.index];
1199+
switch (byte) {
1200+
0 => return null,
1201+
' ', '\t' => continue,
1202+
else => break,
1203+
}
1204+
}
1205+
1206+
return self.internalNext(allocator);
1207+
}
1208+
1209+
pub fn skip(self: &ArgIteratorWindows) -> bool {
1210+
// march forward over whitespace
1211+
while (true) : (self.index += 1) {
1212+
const byte = self.cmd_line[self.index];
1213+
switch (byte) {
1214+
0 => return false,
1215+
' ', '\t' => continue,
1216+
else => break,
1217+
}
1218+
}
1219+
1220+
while (true) : (self.index += 1) {
1221+
const byte = self.cmd_line[self.index];
1222+
switch (byte) {
1223+
0 => return true,
1224+
'"' => {
1225+
const quote_is_real = self.backslash_count % 2 == 0;
1226+
if (quote_is_real) {
1227+
self.seen_quote_count += 1;
1228+
}
1229+
},
1230+
'\\' => {
1231+
self.backslash_count += 1;
1232+
},
1233+
' ', '\t' => {
1234+
if (self.seen_quote_count % 2 == 0 or self.seen_quote_count == self.quote_count) {
1235+
return true;
1236+
}
1237+
},
1238+
else => continue,
1239+
}
1240+
}
1241+
}
1242+
1243+
fn internalNext(self: &ArgIteratorWindows, allocator: &Allocator) -> %[]u8 {
1244+
var buf = %return Buffer.initSize(allocator, 0);
1245+
defer buf.deinit();
1246+
1247+
while (true) : (self.index += 1) {
1248+
const byte = self.cmd_line[self.index];
1249+
switch (byte) {
1250+
0 => return buf.toOwnedSlice(),
1251+
'"' => {
1252+
const quote_is_real = self.backslash_count % 2 == 0;
1253+
%return self.emitBackslashes(&buf, self.backslash_count / 2);
1254+
1255+
if (quote_is_real) {
1256+
self.seen_quote_count += 1;
1257+
if (self.seen_quote_count == self.quote_count and self.seen_quote_count % 2 == 1) {
1258+
%return buf.appendByte('"');
1259+
}
1260+
} else {
1261+
%return buf.appendByte('"');
1262+
}
1263+
},
1264+
'\\' => {
1265+
self.backslash_count += 1;
1266+
},
1267+
' ', '\t' => {
1268+
%return self.emitBackslashes(&buf, self.backslash_count);
1269+
if (self.seen_quote_count % 2 == 1 and self.seen_quote_count != self.quote_count) {
1270+
%return buf.appendByte(byte);
1271+
} else {
1272+
return buf.toOwnedSlice();
1273+
}
1274+
},
1275+
else => {
1276+
%return self.emitBackslashes(&buf, self.backslash_count);
1277+
%return buf.appendByte(byte);
1278+
},
1279+
}
1280+
}
1281+
}
1282+
1283+
fn emitBackslashes(self: &ArgIteratorWindows, buf: &Buffer, emit_count: usize) -> %void {
1284+
self.backslash_count = 0;
1285+
var i: usize = 0;
1286+
while (i < emit_count) : (i += 1) {
1287+
%return buf.appendByte('\\');
1288+
}
1289+
}
1290+
1291+
fn countQuotes(cmd_line: &const u8) -> usize {
1292+
var result: usize = 0;
1293+
var backslash_count: usize = 0;
1294+
var index: usize = 0;
1295+
while (true) : (index += 1) {
1296+
const byte = cmd_line[index];
1297+
switch (byte) {
1298+
0 => return result,
1299+
'\\' => backslash_count += 1,
1300+
'"' => {
1301+
result += 1 - (backslash_count % 2);
1302+
backslash_count = 0;
1303+
},
1304+
else => {
1305+
backslash_count = 0;
1306+
},
1307+
}
1308+
}
1309+
}
1310+
1311+
};
1312+
1313+
pub const ArgIterator = struct {
1314+
inner: if (builtin.os == Os.windows) ArgIteratorWindows else ArgIteratorPosix,
1315+
1316+
pub fn init() -> ArgIterator {
1317+
return ArgIterator {
1318+
.inner = if (builtin.os == Os.windows) ArgIteratorWindows.init() else ArgIteratorPosix.init(),
1319+
};
1320+
}
1321+
1322+
/// You must free the returned memory when done.
1323+
pub fn next(self: &ArgIterator, allocator: &Allocator) -> ?%[]u8 {
1324+
if (builtin.os == Os.windows) {
1325+
return self.inner.next(allocator);
1326+
} else {
1327+
return mem.dupe(allocator, u8, self.inner.next() ?? return null);
1328+
}
1329+
}
1330+
1331+
/// If you only are targeting posix you can call this and not need an allocator.
1332+
pub fn nextPosix(self: &ArgIterator) -> ?[]const u8 {
1333+
return self.inner.next();
1334+
}
1335+
1336+
/// Parse past 1 argument without capturing it.
1337+
/// Returns `true` if skipped an arg, `false` if we are at the end.
1338+
pub fn skip(self: &ArgIterator) -> bool {
1339+
return self.inner.skip();
1340+
}
1341+
};
1342+
1343+
pub fn args() -> ArgIterator {
1344+
return ArgIterator.init();
1345+
}
1346+
1347+
test "windows arg parsing" {
1348+
testWindowsCmdLine(c"a b\tc d", [][]const u8{"a", "b", "c", "d"});
1349+
testWindowsCmdLine(c"\"abc\" d e", [][]const u8{"abc", "d", "e"});
1350+
testWindowsCmdLine(c"a\\\\\\b d\"e f\"g h", [][]const u8{"a\\\\\\b", "de fg", "h"});
1351+
testWindowsCmdLine(c"a\\\\\\\"b c d", [][]const u8{"a\\\"b", "c", "d"});
1352+
testWindowsCmdLine(c"a\\\\\\\\\"b c\" d e", [][]const u8{"a\\\\b c", "d", "e"});
1353+
testWindowsCmdLine(c"a b\tc \"d f", [][]const u8{"a", "b", "c", "\"d", "f"});
1354+
}
1355+
1356+
fn testWindowsCmdLine(input_cmd_line: &const u8, expected_args: []const []const u8) {
1357+
var it = ArgIteratorWindows.initWithCmdLine(input_cmd_line);
1358+
for (expected_args) |expected_arg| {
1359+
const arg = %%??it.next(&debug.global_allocator);
1360+
assert(mem.eql(u8, arg, expected_arg));
1361+
}
1362+
assert(it.next(&debug.global_allocator) == null);
1363+
}
1364+
11471365
test "std.os" {
11481366
_ = @import("child_process.zig");
11491367
_ = @import("darwin_errno.zig");

std/os/windows/index.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: LPCSTR) -> bool;
1717

1818
pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) -> noreturn;
1919

20-
pub extern "kernel32" stdcallcc fn GetCommandLine() -> LPTSTR;
20+
pub extern "kernel32" stdcallcc fn GetCommandLineA() -> LPSTR;
2121

2222
pub extern "kernel32" stdcallcc fn GetConsoleMode(in_hConsoleHandle: HANDLE, out_lpMode: &DWORD) -> bool;
2323

std/special/bootstrap.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ fn posixCallMainAndExit() -> noreturn {
5252
}
5353

5454
fn callMain(argc: usize, argv: &&u8, envp: &?&u8) -> %void {
55-
std.os.args.raw = argv[0..argc];
55+
std.os.ArgIteratorPosix.raw = argv[0..argc];
5656

5757
var env_count: usize = 0;
5858
while (envp[env_count] != null) : (env_count += 1) {}

0 commit comments

Comments
 (0)