authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2017-10-11 10:16:13-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2017-10-11 22:50:16-04:00
logb61a6ec8a6ba7222efd3749a9c5ae30db7e4ef6b
tree252787d924d7753b3e8f5d1e1452c8986c1c9af8
parent717e791db2c49c46a913c574946e9f410a6d9168

implement command line argument parsing for windows

See #302

8 files changed, 320 insertions(+), 56 deletions(-)

example/cat/main.zig+12-4
...@@ -4,11 +4,12 @@ const mem = std.mem;...@@ -4,11 +4,12 @@ const mem = std.mem;
4const os = std.os;4const os = std.os;
55
6pub fn main() -> %void {6pub 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));
8 var catted_anything = false;10 var catted_anything = false;
9 var arg_i: usize = 1;11 while (args_it.next(allocator)) |arg_or_err| {
10 while (arg_i < os.args.count()) : (arg_i += 1) {12 const arg = %return unwrapArg(arg_or_err);
11 const arg = os.args.at(arg_i);
12 if (mem.eql(u8, arg, "-")) {13 if (mem.eql(u8, arg, "-")) {
13 catted_anything = true;14 catted_anything = true;
14 %return cat_stream(&io.stdin);15 %return cat_stream(&io.stdin);
...@@ -55,3 +56,10 @@ fn cat_stream(is: &io.InStream) -> %void {...@@ -55,3 +56,10 @@ fn cat_stream(is: &io.InStream) -> %void {
55 };56 };
56 }57 }
57}58}
59
60fn 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+20
...@@ -14,6 +14,7 @@ pub fn ArrayList(comptime T: type) -> type{...@@ -14,6 +14,7 @@ pub fn ArrayList(comptime T: type) -> type{
14 len: usize,14 len: usize,
15 allocator: &Allocator,15 allocator: &Allocator,
1616
17 /// Deinitialize with `deinit` or use `toOwnedSlice`.
17 pub fn init(allocator: &Allocator) -> Self {18 pub fn init(allocator: &Allocator) -> Self {
18 Self {19 Self {
19 .items = []T{},20 .items = []T{},
...@@ -34,6 +35,25 @@ pub fn ArrayList(comptime T: type) -> type{...@@ -34,6 +35,25 @@ pub fn ArrayList(comptime T: type) -> type{
34 return l.items[0..l.len];35 return l.items[0..l.len];
35 }36 }
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
37 pub fn append(l: &Self, item: &const T) -> %void {57 pub fn append(l: &Self, item: &const T) -> %void {
38 const new_item_ptr = %return l.addOne();58 const new_item_ptr = %return l.addOne();
39 *new_item_ptr = *item;59 *new_item_ptr = *item;
std/buffer.zig+21
...@@ -38,6 +38,27 @@ pub const Buffer = struct {...@@ -38,6 +38,27 @@ pub const Buffer = struct {
38 return Buffer.init(buffer.list.allocator, buffer.toSliceConst());38 return Buffer.init(buffer.list.allocator, buffer.toSliceConst());
39 }39 }
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
41 pub fn deinit(self: &Buffer) {62 pub fn deinit(self: &Buffer) {
42 self.list.deinit();63 self.list.deinit();
43 }64 }
std/os/index.zig+230-12
...@@ -1,5 +1,7 @@...@@ -1,5 +1,7 @@
1const builtin = @import("builtin");1const builtin = @import("builtin");
2const Os = builtin.Os;2const Os = builtin.Os;
3const is_windows = builtin.os == Os.windows;
4
3pub const windows = @import("windows/index.zig");5pub const windows = @import("windows/index.zig");
4pub const darwin = @import("darwin.zig");6pub const darwin = @import("darwin.zig");
5pub const linux = @import("linux.zig");7pub const linux = @import("linux.zig");
...@@ -37,6 +39,7 @@ const cstr = @import("../cstr.zig");...@@ -37,6 +39,7 @@ const cstr = @import("../cstr.zig");
37const io = @import("../io.zig");39const io = @import("../io.zig");
38const base64 = @import("../base64.zig");40const base64 = @import("../base64.zig");
39const ArrayList = @import("../array_list.zig").ArrayList;41const ArrayList = @import("../array_list.zig").ArrayList;
42const Buffer = @import("../buffer.zig").Buffer;
4043
41error Unexpected;44error Unexpected;
42error SystemResources;45error SystemResources;
...@@ -513,18 +516,6 @@ pub fn getEnv(key: []const u8) -> ?[]const u8 {...@@ -513,18 +516,6 @@ pub fn getEnv(key: []const u8) -> ?[]const u8 {
513 return null;516 return null;
514}517}
515518
516pub 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
528/// Caller must free the returned memory.519/// Caller must free the returned memory.
529pub fn getCwd(allocator: &Allocator) -> %[]u8 {520pub fn getCwd(allocator: &Allocator) -> %[]u8 {
530 switch (builtin.os) {521 switch (builtin.os) {
...@@ -1144,6 +1135,233 @@ pub fn posix_setregid(rgid: u32, egid: u32) -> %void {...@@ -1144,6 +1135,233 @@ pub fn posix_setregid(rgid: u32, egid: u32) -> %void {
1144 };1135 };
1145}1136}
11461137
1138pub 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
1171pub 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
1313pub 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
1343pub fn args() -> ArgIterator {
1344 return ArgIterator.init();
1345}
1346
1347test "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
1356fn 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
1147test "std.os" {1365test "std.os" {
1148 _ = @import("child_process.zig");1366 _ = @import("child_process.zig");
1149 _ = @import("darwin_errno.zig");1367 _ = @import("darwin_errno.zig");
std/os/windows/index.zig+1-1
...@@ -17,7 +17,7 @@ pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: LPCSTR) -> bool;...@@ -17,7 +17,7 @@ pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: LPCSTR) -> bool;
1717
18pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) -> noreturn;18pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) -> noreturn;
1919
20pub extern "kernel32" stdcallcc fn GetCommandLine() -> LPTSTR;20pub extern "kernel32" stdcallcc fn GetCommandLineA() -> LPSTR;
2121
22pub extern "kernel32" stdcallcc fn GetConsoleMode(in_hConsoleHandle: HANDLE, out_lpMode: &DWORD) -> bool;22pub extern "kernel32" stdcallcc fn GetConsoleMode(in_hConsoleHandle: HANDLE, out_lpMode: &DWORD) -> bool;
2323
std/special/bootstrap.zig+1-1
...@@ -52,7 +52,7 @@ fn posixCallMainAndExit() -> noreturn {...@@ -52,7 +52,7 @@ fn posixCallMainAndExit() -> noreturn {
52}52}
5353
54fn callMain(argc: usize, argv: &&u8, envp: &?&u8) -> %void {54fn 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
57 var env_count: usize = 0;57 var env_count: usize = 0;
58 while (envp[env_count] != null) : (env_count += 1) {}58 while (envp[env_count] != null) : (env_count += 1) {}
std/special/build_runner.zig+33-37
...@@ -10,37 +10,7 @@ const ArrayList = std.ArrayList;...@@ -10,37 +10,7 @@ const ArrayList = std.ArrayList;
10error InvalidArgs;10error InvalidArgs;
1111
12pub fn main() -> %void {12pub fn main() -> %void {
13 var arg_i: usize = 1;13 var arg_it = os.args();
14
15 const zig_exe = {
16 if (arg_i >= os.args.count()) {
17 %%io.stderr.printf("Expected first argument to be path to zig compiler\n");
18 return error.InvalidArgs;
19 }
20 const result = os.args.at(arg_i);
21 arg_i += 1;
22 result
23 };
24
25 const build_root = {
26 if (arg_i >= os.args.count()) {
27 %%io.stderr.printf("Expected second argument to be build root directory path\n");
28 return error.InvalidArgs;
29 }
30 const result = os.args.at(arg_i);
31 arg_i += 1;
32 result
33 };
34
35 const cache_root = {
36 if (arg_i >= os.args.count()) {
37 %%io.stderr.printf("Expected third argument to be cache root directory path\n");
38 return error.InvalidArgs;
39 }
40 const result = os.args.at(arg_i);
41 arg_i += 1;
42 result
43 };
4414
45 // TODO use a more general purpose allocator here15 // TODO use a more general purpose allocator here
46 var inc_allocator = %%mem.IncrementingAllocator.init(20 * 1024 * 1024);16 var inc_allocator = %%mem.IncrementingAllocator.init(20 * 1024 * 1024);
...@@ -48,6 +18,23 @@ pub fn main() -> %void {...@@ -48,6 +18,23 @@ pub fn main() -> %void {
4818
49 const allocator = &inc_allocator.allocator;19 const allocator = &inc_allocator.allocator;
5020
21
22 // skip my own exe name
23 _ = arg_it.skip();
24
25 const zig_exe = %return unwrapArg(arg_it.next(allocator) ?? {
26 %%io.stderr.printf("Expected first argument to be path to zig compiler\n");
27 return error.InvalidArgs;
28 });
29 const build_root = %return unwrapArg(arg_it.next(allocator) ?? {
30 %%io.stderr.printf("Expected second argument to be build root directory path\n");
31 return error.InvalidArgs;
32 });
33 const cache_root = %return unwrapArg(arg_it.next(allocator) ?? {
34 %%io.stderr.printf("Expected third argument to be cache root directory path\n");
35 return error.InvalidArgs;
36 });
37
51 var builder = Builder.init(allocator, zig_exe, build_root, cache_root);38 var builder = Builder.init(allocator, zig_exe, build_root, cache_root);
52 defer builder.deinit();39 defer builder.deinit();
5340
...@@ -55,8 +42,8 @@ pub fn main() -> %void {...@@ -55,8 +42,8 @@ pub fn main() -> %void {
5542
56 var prefix: ?[]const u8 = null;43 var prefix: ?[]const u8 = null;
5744
58 while (arg_i < os.args.count()) : (arg_i += 1) {45 while (arg_it.next(allocator)) |err_or_arg| {
59 const arg = os.args.at(arg_i);46 const arg = %return unwrapArg(err_or_arg);
60 if (mem.startsWith(u8, arg, "-D")) {47 if (mem.startsWith(u8, arg, "-D")) {
61 const option_contents = arg[2..];48 const option_contents = arg[2..];
62 if (option_contents.len == 0) {49 if (option_contents.len == 0) {
...@@ -76,10 +63,12 @@ pub fn main() -> %void {...@@ -76,10 +63,12 @@ pub fn main() -> %void {
76 if (mem.eql(u8, arg, "--verbose")) {63 if (mem.eql(u8, arg, "--verbose")) {
77 builder.verbose = true;64 builder.verbose = true;
78 } else if (mem.eql(u8, arg, "--help")) {65 } else if (mem.eql(u8, arg, "--help")) {
79 return usage(&builder, false, &io.stdout);66 return usage(&builder, false, &io.stdout);
80 } else if (mem.eql(u8, arg, "--prefix") and arg_i + 1 < os.args.count()) {67 } else if (mem.eql(u8, arg, "--prefix")) {
81 arg_i += 1;68 prefix = %return unwrapArg(arg_it.next(allocator) ?? {
82 prefix = os.args.at(arg_i);69 %%io.stderr.printf("Expected argument after --prefix\n\n");
70 return usage(&builder, false, &io.stderr);
71 });
83 } else {72 } else {
84 %%io.stderr.printf("Unrecognized argument: {}\n\n", arg);73 %%io.stderr.printf("Unrecognized argument: {}\n\n", arg);
85 return usage(&builder, false, &io.stderr);74 return usage(&builder, false, &io.stderr);
...@@ -151,3 +140,10 @@ fn usage(builder: &Builder, already_ran_build: bool, out_stream: &io.OutStream)...@@ -151,3 +140,10 @@ fn usage(builder: &Builder, already_ran_build: bool, out_stream: &io.OutStream)
151 if (out_stream == &io.stderr)140 if (out_stream == &io.stderr)
152 return error.InvalidArgs;141 return error.InvalidArgs;
153}142}
143
144fn unwrapArg(arg: %[]u8) -> %[]u8 {
145 return arg %% |err| {
146 %%io.stderr.printf("Unable to parse command line: {}\n", err);
147 return err;
148 };
149}
test/compare_output.zig+2-1
...@@ -346,7 +346,8 @@ pub fn addCases(cases: &tests.CompareOutputContext) {...@@ -346,7 +346,8 @@ pub fn addCases(cases: &tests.CompareOutputContext) {
346 \\ %%io.stdout.printf("before\n");346 \\ %%io.stdout.printf("before\n");
347 \\ defer %%io.stdout.printf("defer1\n");347 \\ defer %%io.stdout.printf("defer1\n");
348 \\ defer %%io.stdout.printf("defer2\n");348 \\ defer %%io.stdout.printf("defer2\n");
349 \\ if (os.args.count() == 1) return;349 \\ var args_it = @import("std").os.args();
350 \\ if (args_it.skip() and !args_it.skip()) return;
350 \\ defer %%io.stdout.printf("defer3\n");351 \\ defer %%io.stdout.printf("defer3\n");
351 \\ %%io.stdout.printf("after\n");352 \\ %%io.stdout.printf("after\n");
352 \\}353 \\}