6 Commits

Author SHA1 Message Date
4bae2c1dd6 update for zig 0.14.0 2025-03-06 01:03:06 -07:00
fd5b724d52 build: update for zig 0.13 2024-06-18 18:05:12 -07:00
d513aa4eaf source: update struct layout enum usage
The enum member name case was changed in
ziglang/zig@099f3c4039. This appears to
be the only change required to bring us up to compatibility with zig
0.12.0-dev.3561+f45ba7d0c.
2024-04-07 14:08:49 -07:00
89360ac197 parser: produce helpful error messages on command line errors
This is the first cut at providing human-readable context for command
line parsing failures. Since these failures are due to incorrect
input (normally produced by a human), closing the information loop at
the human layer makes a hell of a lot more sense than dumping an error
traceback with a (possibly cryptic) error name and calling it a day.

This approach doesn't print anything out by default and still depends
on the user to choose exactly how the handle and print the error
message. Errors are propagated from subcommands, though they end up
being copied, which shouldn't be strictly necessary. Maybe this can be
improved in the future. OutOfMemory has been added to ParseError to
simplify the code a bit.

The demo has been updated with a simplistic example of what presenting
error messages to the user may look like. I don't know that this
produces useful messages for every possible failure scenario, but it
does for the most common ones.
2024-04-07 14:02:16 -07:00
a961b1930a parser: run subcommand parsing/callbacks iteratively
This changes the parse/callback flow so that subcommands are run
iteratively from the base command rather than recursively. The primary
advantages of this approach are some stack space savings and much less
convoluted backtraces for deeply nested command hierarchies. The
overall order of operations has not changed, i.e. the full command
line is parsed before command callback dispatch starts.

Fixes: #12
2024-04-06 16:00:34 -07:00
074db7f4f6 build: update for zig-0.12.0-dev.2208+4debd4338
I may move this commit to a separate branch, since there are a variety
of improvements that I think I want to get applied to the
0.11.x-compatible codebase still. However, I have also not been
motivated to work on those fixes, since this codebase is kind of
crusty due to being the first thing I ever wrote in zig. Doing a
bigger rewrite might supply the motivation to make those improvements.
I will have to think about it. For now, I am going to focus elsewhere.
2024-01-15 22:45:59 -08:00
8 changed files with 64 additions and 50 deletions

View File

@@ -1,11 +1,11 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const target: std.zig.CrossTarget = b.standardTargetOptions(.{});
const target: std.Build.ResolvedTarget = b.standardTargetOptions(.{});
const optimize: std.builtin.Mode = b.standardOptimizeOption(.{});
const noclip = b.addModule("noclip", .{
.source_file = .{ .path = "source/noclip.zig" },
.root_source_file = b.path("source/noclip.zig"),
});
demo(b, noclip, target, optimize);
@@ -13,7 +13,7 @@ pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Run unit tests");
const tests = b.addTest(.{
.name = "tests",
.root_source_file = .{ .path = "source/noclip.zig" },
.root_source_file = b.path("source/noclip.zig"),
.target = target,
.optimize = optimize,
});
@@ -24,18 +24,18 @@ pub fn build(b: *std.Build) void {
fn demo(
b: *std.Build,
noclip: *std.Build.Module,
target: std.zig.CrossTarget,
target: std.Build.ResolvedTarget,
optimize: std.builtin.Mode,
) void {
const demo_step = b.step("demo", "Build and install CLI demo program");
const exe = b.addExecutable(.{
.name = "noclip-demo",
.root_source_file = .{ .path = "demo/demo.zig" },
.root_source_file = b.path("demo/demo.zig"),
.target = target,
.optimize = optimize,
});
exe.addModule("noclip", noclip);
exe.root_module.addImport("noclip", noclip);
const install_demo = b.addInstallArtifact(exe, .{});
demo_step.dependOn(&install_demo.step);

13
build.zig.zon Normal file
View File

@@ -0,0 +1,13 @@
.{
.name = .NOCLIP,
.fingerprint = 0xE4C223E8CB9C8ADF,
.version = "0.1.0-pre",
.minimum_zig_version = "0.14.0",
.dependencies = .{},
.paths = .{
"source",
"build.zig",
"build.zig.zon",
"license",
},
}

View File

@@ -22,7 +22,7 @@ ____
== Hello
Requires Zig `0.11.x`.
Requires Zig `0.13.x`. May work with `0.12.x`.
=== Features

View File

@@ -118,8 +118,8 @@ pub const InterfaceContextCategory = union(enum) {
pub fn fromType(comptime ContextType: type) InterfaceContextCategory {
return switch (@typeInfo(ContextType)) {
.Void => .empty,
.Pointer => |info| if (info.size == .Slice) .{ .value = ContextType } else .{ .pointer = ContextType },
.void => .empty,
.pointer => |info| if (info.size == .slice) .{ .value = ContextType } else .{ .pointer = ContextType },
// technically, i0, u0, and struct{} should be treated as empty, probably
else => .{ .value = ContextType },
};
@@ -172,7 +172,8 @@ pub fn CommandBuilder(comptime UserContext: type) type {
};
}
pub usingnamespace InterfaceCreator(@This());
pub const ifc = InterfaceCreator(@This());
pub const createInterface = ifc.createInterface;
fn _createInterfaceImpl(
comptime self: @This(),
@@ -376,7 +377,7 @@ pub fn CommandBuilder(comptime UserContext: type) type {
// [:0]const u8.
.name = short ++ "",
.type = void,
.default_value = null,
.default_value_ptr = null,
.is_comptime = false,
.alignment = 0,
}};
@@ -385,7 +386,7 @@ pub fn CommandBuilder(comptime UserContext: type) type {
tag_fields = tag_fields ++ &[_]StructField{.{
.name = long ++ "",
.type = void,
.default_value = null,
.default_value_ptr = null,
.is_comptime = false,
.alignment = 0,
}};
@@ -394,7 +395,7 @@ pub fn CommandBuilder(comptime UserContext: type) type {
env_var_fields = env_var_fields ++ &[_]StructField{.{
.name = env_var ++ "",
.type = void,
.default_value = null,
.default_value_ptr = null,
.is_comptime = false,
.alignment = 0,
}};
@@ -439,28 +440,28 @@ pub fn CommandBuilder(comptime UserContext: type) type {
fields = fields ++ &[_]StructField{.{
.name = param.name ++ "",
.type = FieldType,
.default_value = @ptrCast(default),
.default_value_ptr = @ptrCast(default),
.is_comptime = false,
.alignment = @alignOf(FieldType),
}};
}
_ = @Type(.{ .Struct = .{
.layout = .Auto,
_ = @Type(.{ .@"struct" = .{
.layout = .auto,
.fields = tag_fields,
.decls = &.{},
.is_tuple = false,
} });
_ = @Type(.{ .Struct = .{
.layout = .Auto,
_ = @Type(.{ .@"struct" = .{
.layout = .auto,
.fields = env_var_fields,
.decls = &.{},
.is_tuple = false,
} });
return @Type(.{ .Struct = .{
.layout = .Auto,
return @Type(.{ .@"struct" = .{
.layout = .auto,
.fields = fields,
.decls = &.{},
.is_tuple = false,
@@ -509,7 +510,7 @@ pub fn CommandBuilder(comptime UserContext: type) type {
fields = &(@as([fields.len]StructField, fields[0..fields.len].*) ++ [1]StructField{.{
.name = param.name ++ "",
.type = FieldType,
.default_value = @ptrCast(&@as(
.default_value_ptr = @ptrCast(&@as(
FieldType,
if (PType.value_count == .count) 0 else null,
)),
@@ -518,8 +519,8 @@ pub fn CommandBuilder(comptime UserContext: type) type {
}});
}
return @Type(.{ .Struct = .{
.layout = .Auto,
return @Type(.{ .@"struct" = .{
.layout = .auto,
.fields = fields,
.decls = &.{},
.is_tuple = false,

View File

@@ -21,16 +21,16 @@ pub fn DefaultConverter(comptime gen: ParameterGenerics) ?ConverterSignature(gen
return if (comptime gen.multi)
MultiConverter(gen)
else switch (@typeInfo(gen.OutputType)) {
.Bool => FlagConverter(gen),
.Int => IntConverter(gen),
.Pointer => |info| if (info.size == .Slice and info.child == u8)
.bool => FlagConverter(gen),
.int => IntConverter(gen),
.pointer => |info| if (info.size == .slice and info.child == u8)
StringConverter(gen)
else
null,
.Enum => |info| if (info.is_exhaustive) ChoiceConverter(gen) else null,
.@"enum" => |info| if (info.is_exhaustive) ChoiceConverter(gen) else null,
// TODO: how to handle structs with field defaults? maybe this should only work
// for tuples, which I don't think can have defaults.
.Struct => |info| if (gen.value_count == .fixed and gen.value_count.fixed == info.fields.len)
.@"struct" => |info| if (gen.value_count == .fixed and gen.value_count.fixed == info.fields.len)
StructConverter(gen)
else
null,
@@ -102,7 +102,7 @@ fn IntConverter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
fn StructConverter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
const StructType = gen.OutputType;
const type_info = @typeInfo(StructType).Struct;
const type_info = @typeInfo(StructType).@"struct";
const Intermediate = gen.IntermediateType();
return struct {
@@ -120,7 +120,7 @@ fn StructConverter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
const Converter = comptime DefaultConverter(
ncmeta.copyStruct(ParameterGenerics, gen, .{
.OutputType = field.type,
.value_count = .{ .fixed = 1 },
.value_count = @as(parameters.ValueCount, .{ .fixed = 1 }),
}),
) orelse
@compileError("cannot get converter for field" ++ field.name);

View File

@@ -57,7 +57,7 @@ pub fn StructuredPrinter(comptime Writer: type) type {
// this assumes output stream has already had the first line properly
// indented.
var splitter = std.mem.split(u8, text, "\n");
var splitter = std.mem.splitScalar(u8, text, '\n');
var location: usize = indent;
while (splitter.next()) |line| {
@@ -484,7 +484,7 @@ pub fn optInfo(comptime command: anytype) CommandHelp {
// than just the tag name. Roll our own eventually.
blk: {
switch (@typeInfo(@TypeOf(def))) {
.Pointer => |info| if (info.size == .Slice and info.child == u8) {
.pointer => |info| if (info.size == .Slice and info.child == u8) {
writer.print("{s}", .{def}) catch @compileError("no");
break :blk;
},

View File

@@ -10,7 +10,7 @@ pub fn UpdateDefaults(comptime input: type, comptime defaults: anytype) type {
comptime {
const inputInfo = @typeInfo(input);
const fieldcount = switch (inputInfo) {
.Struct => |spec| blk: {
.@"struct" => |spec| blk: {
if (spec.decls.len > 0) {
@compileError("UpdateDefaults only works on structs " ++
"without decls due to limitations in @Type.");
@@ -21,7 +21,7 @@ pub fn UpdateDefaults(comptime input: type, comptime defaults: anytype) type {
};
var fields: [fieldcount]StructField = undefined;
for (inputInfo.Struct.fields, 0..) |field, idx| {
for (inputInfo.@"struct".fields, 0..) |field, idx| {
fields[idx] = .{
.name = field.name,
.field_type = field.field_type,
@@ -29,27 +29,27 @@ pub fn UpdateDefaults(comptime input: type, comptime defaults: anytype) type {
// setting null defaults work, and it converts comptime_int to
// the appropriate type, which is nice for ergonomics. Not sure
// if it introduces weird edge cases. Probably it's fine?
.default_value = if (@hasField(@TypeOf(defaults), field.name))
.default_value_ptr = if (@hasField(@TypeOf(defaults), field.name))
@ptrCast(&@as(field.field_type, @field(defaults, field.name)))
else
field.default_value,
field.default_value_ptr,
.is_comptime = field.is_comptime,
.alignment = field.alignment,
};
}
return @Type(.{ .Struct = .{
.layout = inputInfo.Struct.layout,
.backing_integer = inputInfo.Struct.backing_integer,
return @Type(.{ .@"struct" = .{
.layout = inputInfo.@"struct".layout,
.backing_integer = inputInfo.@"struct".backing_integer,
.fields = &fields,
.decls = inputInfo.Struct.decls,
.is_tuple = inputInfo.Struct.is_tuple,
.decls = inputInfo.@"struct".decls,
.is_tuple = inputInfo.@"struct".is_tuple,
} });
}
}
pub fn enumLength(comptime T: type) comptime_int {
return @typeInfo(T).Enum.fields.len;
return @typeInfo(T).@"enum".fields.len;
}
pub fn partition(comptime T: type, input: []const T, wedge: []const []const T) [3][]const T {
@@ -210,11 +210,11 @@ pub fn MutatingZSplitter(comptime T: type) type {
pub fn copyStruct(comptime T: type, source: T, field_overrides: anytype) T {
var result: T = undefined;
comptime for (@typeInfo(@TypeOf(field_overrides)).Struct.fields) |field| {
comptime for (@typeInfo(@TypeOf(field_overrides)).@"struct".fields) |field| {
if (!@hasField(T, field.name)) @compileError("override contains bad field" ++ field);
};
inline for (comptime @typeInfo(T).Struct.fields) |field| {
inline for (comptime @typeInfo(T).@"struct".fields) |field| {
if (comptime @hasField(@TypeOf(field_overrides), field.name))
@field(result, field.name) = @field(field_overrides, field.name)
else
@@ -257,15 +257,15 @@ pub const TupleBuilder = struct {
fields[idx] = .{
.name = std.fmt.comptimePrint("{d}", .{idx}),
.type = Type,
.default_value = null,
.default_value_ptr = null,
// TODO: is this the right thing to do?
.is_comptime = false,
.alignment = if (@sizeOf(Type) > 0) @alignOf(Type) else 0,
};
}
return @Type(.{ .Struct = .{
.layout = .Auto,
return @Type(.{ .@"struct" = .{
.layout = .auto,
.fields = &fields,
.decls = &.{},
.is_tuple = true,

View File

@@ -48,11 +48,11 @@ pub const ParameterGenerics = struct {
pub fn fixedValueCount(comptime OutputType: type, comptime value_count: ValueCount) ValueCount {
return comptime if (value_count == .fixed)
switch (@typeInfo(OutputType)) {
.Struct => |info| .{ .fixed = info.fields.len },
.Array => |info| .{ .fixed = info.len },
.@"struct" => |info| .{ .fixed = info.fields.len },
.array => |info| .{ .fixed = info.len },
// TODO: this is a bit sloppy, but it can be refined later.
// .Pointer covers slices, which may be a many-to-many conversion.
.Pointer => value_count,
.pointer => value_count,
else => .{ .fixed = 1 },
}
else