From b0c17a6becae820624da8cf41c880184ce0a104e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 3 Jun 2026 16:22:13 -0700 Subject: [PATCH 1/3] CLI: update sub-command to use StaticStringMap std.StaticStringMap: add initEnum method for when the tag name and the string to match are the same. In this case users should use getIndex to get the enum tag value. --- lib/std/static_string_map.zig | 57 ++++++ src/main.zig | 354 ++++++++++++++++++++-------------- 2 files changed, 269 insertions(+), 142 deletions(-) diff --git a/lib/std/static_string_map.zig b/lib/std/static_string_map.zig index 730acca0fe63c7f7429285143e5ce3c0c88a372f..0c4e444a5b92f83d40c28456ab63b7550f3e25d5 100644 --- a/lib/std/static_string_map.zig +++ b/lib/std/static_string_map.zig @@ -100,6 +100,51 @@ pub fn StaticStringMapWithEql( } } + /// Returns a map backed by static, comptime allocated memory. + /// + /// `V` must be `void`. The enum's tag names will be used as the keys. + pub inline fn initEnum(comptime E: type) Self { + comptime { + var self: Self = .{}; + + const field_names = @typeInfo(E).@"enum".field_names; + if (field_names.len == 0) return self; + + // Since the KVs are sorted, a linearly-growing bound will never + // be sufficient for extreme cases. So we grow proportional to + // N*log2(N). + @setEvalBranchQuota(10 * field_names.len * std.math.log2_int_ceil(usize, field_names.len)); + + var sorted_keys: [field_names.len][]const u8 = field_names[0..field_names.len].*; + var sorted_vals: [field_names.len]V = @splat({}); + + for (field_names) |field_name| { + self.min_len = @min(self.min_len, field_name.len); + self.max_len = @max(self.max_len, field_name.len); + } + + mem.sortUnstableContext(0, sorted_keys.len, SortContext{ + .keys = &sorted_keys, + .vals = &sorted_vals, + }); + + const final_keys = sorted_keys; + const final_vals = sorted_vals; + self.kvs = &.{ + .keys = &final_keys, + .values = &final_vals, + .len = @intCast(field_names.len), + }; + + var len_indexes: [self.max_len + 1]u32 = undefined; + self.initLenIndexes(&len_indexes); + const final_len_indexes = len_indexes; + self.len_indexes = &final_len_indexes; + self.len_indexes_len = @intCast(len_indexes.len); + return self; + } + } + /// Returns a map backed by memory allocated with `allocator`. /// /// Handles `kvs_list` the same way as `initComptime()`. @@ -536,3 +581,15 @@ test "sorting kvs doesn't exceed eval branch quota" { }); try testing.expectEqual(1, TypeToByteSizeLUT.get("t1")); } + +test "initEnum" { + const map = StaticStringMap(void).initEnum(TestEnum); + try testing.expect(map.has("A")); + try testing.expect(!map.has("a")); + try testing.expectEqual(0, map.getIndex("A")); + try testing.expectEqual(1, map.getIndex("B")); + try testing.expectEqual(2, map.getIndex("C")); + try testing.expectEqual(3, map.getIndex("D")); + try testing.expectEqual(4, map.getIndex("E")); + try testing.expectEqual(null, map.getIndex("F")); +} diff --git a/src/main.zig b/src/main.zig index 1e674077e461dc63c5be1c6a25dc0d0ca1056f12..38edface403010cc9a95203fe650641caba0973d 100644 --- a/src/main.zig +++ b/src/main.zig @@ -229,6 +229,57 @@ pub fn main(init: std.process.Init.Minimal) anyerror!void { return mainArgs(gpa, arena, io, args, &environ_map); } +const cmd_map = std.StaticStringMap(void).initEnum(Cmd); + +const Cmd = enum { + @"build-exe", + @"build-lib", + @"build-obj", + @"test", + @"test-obj", + run, + + dlltool, + ranlib, + lib, + ar, + + build, + + clang, + @"-cc1", + @"-cc1as", + + @"ld.lld", + @"lld-link", + @"wasm-ld", + + cc, + @"c++", + @"translate-c", + rc, + fmt, + objcopy, + objdump, + fetch, + libc, + std, + init, + targets, + version, + env, + reduce, + zen, + @"ast-check", + + help, + @"-h", + @"--help", + + changelist, + @"dump-zir", +}; + fn mainArgs( gpa: Allocator, arena: Allocator, @@ -271,144 +322,169 @@ fn mainArgs( const cmd = args[1]; const cmd_args = args[2..]; - if (mem.eql(u8, cmd, "build-exe")) { - dev.check(.build_exe_command); - return buildOutputType(gpa, arena, io, args, .{ .build = .Exe }, environ_map); - } else if (mem.eql(u8, cmd, "build-lib")) { - dev.check(.build_lib_command); - return buildOutputType(gpa, arena, io, args, .{ .build = .Lib }, environ_map); - } else if (mem.eql(u8, cmd, "build-obj")) { - dev.check(.build_obj_command); - return buildOutputType(gpa, arena, io, args, .{ .build = .Obj }, environ_map); - } else if (mem.eql(u8, cmd, "test")) { - dev.check(.test_command); - return buildOutputType(gpa, arena, io, args, .zig_test, environ_map); - } else if (mem.eql(u8, cmd, "test-obj")) { - dev.check(.test_command); - return buildOutputType(gpa, arena, io, args, .zig_test_obj, environ_map); - } else if (mem.eql(u8, cmd, "run")) { - dev.check(.run_command); - return buildOutputType(gpa, arena, io, args, .run, environ_map); - } else if (mem.eql(u8, cmd, "dlltool") or - mem.eql(u8, cmd, "ranlib") or - mem.eql(u8, cmd, "lib") or - mem.eql(u8, cmd, "ar")) - { - dev.check(.ar_command); - return process.exit(try llvmArMain(arena, args)); - } else if (mem.eql(u8, cmd, "build")) { - dev.check(.build_command); - return cmdBuild(gpa, arena, io, cmd_args, environ_map); - } else if (mem.eql(u8, cmd, "clang") or - mem.eql(u8, cmd, "-cc1") or mem.eql(u8, cmd, "-cc1as")) - { - dev.check(.clang_command); - return process.exit(try clangMain(arena, args)); - } else if (mem.eql(u8, cmd, "ld.lld") or - mem.eql(u8, cmd, "lld-link") or - mem.eql(u8, cmd, "wasm-ld")) - { - dev.check(.lld_linker); - return process.exit(try lldMain(arena, args, true)); - } else if (mem.eql(u8, cmd, "cc")) { - dev.check(.cc_command); - return buildOutputType(gpa, arena, io, args, .cc, environ_map); - } else if (mem.eql(u8, cmd, "c++")) { - dev.check(.cc_command); - return buildOutputType(gpa, arena, io, args, .cpp, environ_map); - } else if (mem.eql(u8, cmd, "translate-c")) { - dev.check(.translate_c_command); - return buildOutputType(gpa, arena, io, args, .translate_c, environ_map); - } else if (mem.eql(u8, cmd, "rc")) { - const use_server = cmd_args.len > 0 and std.mem.eql(u8, cmd_args[0], "--zig-integration"); - return jitCmd(gpa, arena, io, cmd_args, environ_map, .{ - .cmd_name = "resinator", - .root_src_path = "resinator/main.zig", - .depend_on_aro = true, - .prepend_zig_lib_dir_path = true, - .server = use_server, - .color = Color.settingFromEnvironment(environ_map), - }); - } else if (mem.eql(u8, cmd, "fmt")) { - dev.check(.fmt_command); - return @import("fmt.zig").run(gpa, arena, io, cmd_args); - } else if (mem.eql(u8, cmd, "objcopy")) { - return jitCmd(gpa, arena, io, cmd_args, environ_map, .{ - .cmd_name = "objcopy", - .root_src_path = "objcopy.zig", - .color = Color.settingFromEnvironment(environ_map), - }); - } else if (mem.eql(u8, cmd, "objdump")) { - return jitCmd(gpa, arena, io, cmd_args, environ_map, .{ - .cmd_name = "objdump", - .root_src_path = "objdump.zig", - .color = Color.settingFromEnvironment(environ_map), - }); - } else if (mem.eql(u8, cmd, "fetch")) { - return cmdFetch(gpa, arena, io, cmd_args, environ_map); - } else if (mem.eql(u8, cmd, "libc")) { - return jitCmd(gpa, arena, io, cmd_args, environ_map, .{ - .cmd_name = "libc", - .root_src_path = "libc.zig", - .prepend_zig_lib_dir_path = true, - .color = Color.settingFromEnvironment(environ_map), - }); - } else if (mem.eql(u8, cmd, "std")) { - return jitCmd(gpa, arena, io, cmd_args, environ_map, .{ - .cmd_name = "std", - .root_src_path = "std-docs.zig", - .prepend_zig_lib_dir_path = true, - .prepend_zig_exe_path = true, - .prepend_global_cache_path = true, - .color = Color.settingFromEnvironment(environ_map), - }); - } else if (mem.eql(u8, cmd, "init")) { - return cmdInit(gpa, arena, io, cmd_args); - } else if (mem.eql(u8, cmd, "targets")) { - dev.check(.targets_command); - const host = std.zig.resolveTargetQueryOrFatal(io, .{}); - var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer); - try @import("print_targets.zig").cmdTargets(arena, io, cmd_args, &stdout_writer.interface, &host); - return stdout_writer.interface.flush(); - } else if (mem.eql(u8, cmd, "version")) { - dev.check(.version_command); - try Io.File.stdout().writeStreamingAll(io, build_options.version ++ "\n"); - return; - } else if (mem.eql(u8, cmd, "env")) { - dev.check(.env_command); - const host = std.zig.resolveTargetQueryOrFatal(io, .{}); - var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer); - try @import("print_env.zig").cmdEnv( - arena, - io, - &stdout_writer.interface, - args, - preopens, - &host, - environ_map, - ); - return stdout_writer.interface.flush(); - } else if (mem.eql(u8, cmd, "reduce")) { - return jitCmd(gpa, arena, io, cmd_args, environ_map, .{ - .cmd_name = "reduce", - .root_src_path = "reduce.zig", - .color = Color.settingFromEnvironment(environ_map), - }); - } else if (mem.eql(u8, cmd, "zen")) { - dev.check(.zen_command); - return Io.File.stdout().writeStreamingAll(io, info_zen); - } else if (mem.eql(u8, cmd, "help") or mem.eql(u8, cmd, "-h") or mem.eql(u8, cmd, "--help")) { - dev.check(.help_command); - return Io.File.stdout().writeStreamingAll(io, usage); - } else if (mem.eql(u8, cmd, "ast-check")) { - return cmdAstCheck(arena, io, cmd_args, environ_map); - } else if (build_options.enable_debug_extensions and mem.eql(u8, cmd, "changelist")) { - return cmdChangelist(arena, io, cmd_args, environ_map); - } else if (build_options.enable_debug_extensions and mem.eql(u8, cmd, "dump-zir")) { - return cmdDumpZir(arena, io, cmd_args); - } else { + switch (@as(Cmd, @enumFromInt(cmd_map.getIndex(cmd) orelse { std.log.info("{s}", .{usage}); fatal("unknown command: {s}", .{args[1]}); + }))) { + .@"build-exe" => { + dev.check(.build_exe_command); + return buildOutputType(gpa, arena, io, args, .{ .build = .Exe }, environ_map); + }, + .@"build-lib" => { + dev.check(.build_lib_command); + return buildOutputType(gpa, arena, io, args, .{ .build = .Lib }, environ_map); + }, + .@"build-obj" => { + dev.check(.build_obj_command); + return buildOutputType(gpa, arena, io, args, .{ .build = .Obj }, environ_map); + }, + .@"test" => { + dev.check(.test_command); + return buildOutputType(gpa, arena, io, args, .zig_test, environ_map); + }, + .@"test-obj" => { + dev.check(.test_command); + return buildOutputType(gpa, arena, io, args, .zig_test_obj, environ_map); + }, + .run => { + dev.check(.run_command); + return buildOutputType(gpa, arena, io, args, .run, environ_map); + }, + .dlltool, .ranlib, .lib, .ar => { + dev.check(.ar_command); + return process.exit(try llvmArMain(arena, args)); + }, + .build => { + dev.check(.build_command); + return cmdBuild(gpa, arena, io, cmd_args, environ_map); + }, + .clang, .@"-cc1", .@"-cc1as" => { + dev.check(.clang_command); + return process.exit(try clangMain(arena, args)); + }, + .@"ld.lld", .@"lld-link", .@"wasm-ld" => { + dev.check(.lld_linker); + return process.exit(try lldMain(arena, args, true)); + }, + .cc => { + dev.check(.cc_command); + return buildOutputType(gpa, arena, io, args, .cc, environ_map); + }, + .@"c++" => { + dev.check(.cc_command); + return buildOutputType(gpa, arena, io, args, .cpp, environ_map); + }, + .@"translate-c" => { + dev.check(.translate_c_command); + return buildOutputType(gpa, arena, io, args, .translate_c, environ_map); + }, + .rc => { + const use_server = cmd_args.len > 0 and std.mem.eql(u8, cmd_args[0], "--zig-integration"); + return jitCmd(gpa, arena, io, cmd_args, environ_map, .{ + .cmd_name = "resinator", + .root_src_path = "resinator/main.zig", + .depend_on_aro = true, + .prepend_zig_lib_dir_path = true, + .server = use_server, + .color = Color.settingFromEnvironment(environ_map), + }); + }, + .fmt => { + dev.check(.fmt_command); + return @import("fmt.zig").run(gpa, arena, io, cmd_args); + }, + .objcopy => { + return jitCmd(gpa, arena, io, cmd_args, environ_map, .{ + .cmd_name = "objcopy", + .root_src_path = "objcopy.zig", + .color = Color.settingFromEnvironment(environ_map), + }); + }, + .objdump => { + return jitCmd(gpa, arena, io, cmd_args, environ_map, .{ + .cmd_name = "objdump", + .root_src_path = "objdump.zig", + .color = Color.settingFromEnvironment(environ_map), + }); + }, + .fetch => { + return cmdFetch(gpa, arena, io, cmd_args, environ_map); + }, + .libc => { + return jitCmd(gpa, arena, io, cmd_args, environ_map, .{ + .cmd_name = "libc", + .root_src_path = "libc.zig", + .prepend_zig_lib_dir_path = true, + .color = Color.settingFromEnvironment(environ_map), + }); + }, + .std => { + return jitCmd(gpa, arena, io, cmd_args, environ_map, .{ + .cmd_name = "std", + .root_src_path = "std-docs.zig", + .prepend_zig_lib_dir_path = true, + .prepend_zig_exe_path = true, + .prepend_global_cache_path = true, + .color = Color.settingFromEnvironment(environ_map), + }); + }, + .init => { + return cmdInit(gpa, arena, io, cmd_args); + }, + .targets => { + dev.check(.targets_command); + const host = std.zig.resolveTargetQueryOrFatal(io, .{}); + var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer); + try @import("print_targets.zig").cmdTargets(arena, io, cmd_args, &stdout_writer.interface, &host); + return stdout_writer.interface.flush(); + }, + .version => { + dev.check(.version_command); + try Io.File.stdout().writeStreamingAll(io, build_options.version ++ "\n"); + return; + }, + .env => { + dev.check(.env_command); + const host = std.zig.resolveTargetQueryOrFatal(io, .{}); + var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer); + try @import("print_env.zig").cmdEnv( + arena, + io, + &stdout_writer.interface, + args, + preopens, + &host, + environ_map, + ); + return stdout_writer.interface.flush(); + }, + .reduce => { + return jitCmd(gpa, arena, io, cmd_args, environ_map, .{ + .cmd_name = "reduce", + .root_src_path = "reduce.zig", + .color = Color.settingFromEnvironment(environ_map), + }); + }, + .zen => { + dev.check(.zen_command); + return Io.File.stdout().writeStreamingAll(io, info_zen); + }, + .help, .@"-h", .@"--help" => { + dev.check(.help_command); + return Io.File.stdout().writeStreamingAll(io, usage); + }, + .@"ast-check" => { + dev.check(.ast_check_command); + return cmdAstCheck(arena, io, cmd_args, environ_map); + }, + .changelist => { + dev.check(.changelist_command); + return cmdChangelist(arena, io, cmd_args, environ_map); + }, + .@"dump-zir" => { + dev.check(.dump_zir_command); + return cmdDumpZir(arena, io, cmd_args); + }, } } @@ -6537,8 +6613,6 @@ const usage_ast_check = ; fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8, environ_map: *const std.process.Environ.Map) !void { - dev.check(.ast_check_command); - const Zir = std.zig.Zir; var color: Color = Color.settingFromEnvironment(environ_map); @@ -6705,8 +6779,6 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8, environ_map: /// This is only enabled for debug builds. fn cmdDumpZir(arena: Allocator, io: Io, args: []const []const u8) !void { - dev.check(.dump_zir_command); - const Zir = std.zig.Zir; const cache_file = args[0]; @@ -6749,8 +6821,6 @@ fn cmdDumpZir(arena: Allocator, io: Io, args: []const []const u8) !void { /// This is only enabled for debug builds. fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8, environ_map: *const std.process.Environ.Map) !void { - dev.check(.changelist_command); - const color: Color = Color.settingFromEnvironment(environ_map); const Zir = std.zig.Zir; -- 2.54.0 From ac9f020c878c6397cee0f476b0afd5ce38178374 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 4 Jun 2026 00:02:36 -0700 Subject: [PATCH 2/3] StaticStringMap: failing unit test getIndex doesn't return an index based on the input, it returns an internal index to the generated sorted array this makes it unfit to be public --- lib/std/static_string_map.zig | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/std/static_string_map.zig b/lib/std/static_string_map.zig index 0c4e444a5b92f83d40c28456ab63b7550f3e25d5..0cb29d7a3789828b3054efffe7fdab2e2cf97672 100644 --- a/lib/std/static_string_map.zig +++ b/lib/std/static_string_map.zig @@ -240,7 +240,7 @@ pub fn StaticStringMapWithEql( return self.kvs.values[self.getIndex(str) orelse return null]; } - pub fn getIndex(self: Self, str: []const u8) ?usize { + fn getIndex(self: Self, str: []const u8) ?usize { const kvs = self.kvs.*; if (kvs.len == 0) return null; @@ -583,13 +583,13 @@ test "sorting kvs doesn't exceed eval branch quota" { } test "initEnum" { - const map = StaticStringMap(void).initEnum(TestEnum); + const UnsortedEnum = enum { BB, A, CCC, DDD }; + const map = StaticStringMap(void).initEnum(UnsortedEnum); try testing.expect(map.has("A")); try testing.expect(!map.has("a")); - try testing.expectEqual(0, map.getIndex("A")); - try testing.expectEqual(1, map.getIndex("B")); - try testing.expectEqual(2, map.getIndex("C")); - try testing.expectEqual(3, map.getIndex("D")); - try testing.expectEqual(4, map.getIndex("E")); + try testing.expectEqual(0, map.getIndex("BB")); + try testing.expectEqual(1, map.getIndex("A")); + try testing.expectEqual(2, map.getIndex("CCC")); + try testing.expectEqual(3, map.getIndex("DDD")); try testing.expectEqual(null, map.getIndex("F")); } -- 2.54.0 From e1980e2fe0d206942537cfb992c78e5800411846 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 4 Jun 2026 00:05:23 -0700 Subject: [PATCH 3/3] std.StaticStringMap: fix initEnum and usage --- lib/std/static_string_map.zig | 19 ++++++++++--------- src/main.zig | 6 +++--- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/std/static_string_map.zig b/lib/std/static_string_map.zig index 0cb29d7a3789828b3054efffe7fdab2e2cf97672..f0bfc2adb75cb7b4852d2c0719b8761db0e38cf4 100644 --- a/lib/std/static_string_map.zig +++ b/lib/std/static_string_map.zig @@ -102,12 +102,12 @@ pub fn StaticStringMapWithEql( /// Returns a map backed by static, comptime allocated memory. /// - /// `V` must be `void`. The enum's tag names will be used as the keys. - pub inline fn initEnum(comptime E: type) Self { + /// `V` must be an enum. The enum's tag names will be used as the keys. + pub inline fn initEnum() Self { comptime { var self: Self = .{}; - const field_names = @typeInfo(E).@"enum".field_names; + const field_names = @typeInfo(V).@"enum".field_names; if (field_names.len == 0) return self; // Since the KVs are sorted, a linearly-growing bound will never @@ -116,7 +116,8 @@ pub fn StaticStringMapWithEql( @setEvalBranchQuota(10 * field_names.len * std.math.log2_int_ceil(usize, field_names.len)); var sorted_keys: [field_names.len][]const u8 = field_names[0..field_names.len].*; - var sorted_vals: [field_names.len]V = @splat({}); + var sorted_vals: [field_names.len]V = undefined; + for (&sorted_vals, @typeInfo(V).@"enum".field_values) |*x, i| x.* = @enumFromInt(i); for (field_names) |field_name| { self.min_len = @min(self.min_len, field_name.len); @@ -584,12 +585,12 @@ test "sorting kvs doesn't exceed eval branch quota" { test "initEnum" { const UnsortedEnum = enum { BB, A, CCC, DDD }; - const map = StaticStringMap(void).initEnum(UnsortedEnum); + const map = StaticStringMap(UnsortedEnum).initEnum(); try testing.expect(map.has("A")); try testing.expect(!map.has("a")); - try testing.expectEqual(0, map.getIndex("BB")); - try testing.expectEqual(1, map.getIndex("A")); - try testing.expectEqual(2, map.getIndex("CCC")); - try testing.expectEqual(3, map.getIndex("DDD")); + try testing.expectEqual(.BB, map.get("BB")); + try testing.expectEqual(.A, map.get("A")); + try testing.expectEqual(.CCC, map.get("CCC")); + try testing.expectEqual(.DDD, map.get("DDD")); try testing.expectEqual(null, map.getIndex("F")); } diff --git a/src/main.zig b/src/main.zig index 38edface403010cc9a95203fe650641caba0973d..09073eaa08283031212cf062f4b09019b3d4043f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -229,7 +229,7 @@ pub fn main(init: std.process.Init.Minimal) anyerror!void { return mainArgs(gpa, arena, io, args, &environ_map); } -const cmd_map = std.StaticStringMap(void).initEnum(Cmd); +const cmd_map = std.StaticStringMap(Cmd).initEnum(); const Cmd = enum { @"build-exe", @@ -322,10 +322,10 @@ fn mainArgs( const cmd = args[1]; const cmd_args = args[2..]; - switch (@as(Cmd, @enumFromInt(cmd_map.getIndex(cmd) orelse { + switch (cmd_map.get(cmd) orelse { std.log.info("{s}", .{usage}); fatal("unknown command: {s}", .{args[1]}); - }))) { + }) { .@"build-exe" => { dev.check(.build_exe_command); return buildOutputType(gpa, arena, io, args, .{ .build = .Exe }, environ_map); -- 2.54.0