parser: support unexposed values

Having thought about this more, it seems likely that complex converters
could benefit from being able to parse their arguments on the fly
without having to structure them into a rigid type. This is sort of a
get out of jail free card for custom converters as they can dump into
the user context type or whatever they want directly.
This commit is contained in:
torque 2023-04-03 01:36:45 -07:00
parent b01c10409d
commit 6ffc1c1a4c
Signed by: torque
SSH Key Fingerprint: SHA256:nCrXefBNo6EbjNSQhv0nXmEg/VuNq3sMF5b8zETw3Tk
3 changed files with 31 additions and 18 deletions

View File

@ -25,7 +25,6 @@ fn BuilderGenerics(comptime UserContext: type) type {
multi: bool = false, multi: bool = false,
pub fn arg_gen(comptime self: @This()) ParameterGenerics { pub fn arg_gen(comptime self: @This()) ParameterGenerics {
if (self.OutputType == void) @compileError("argument must have OutputType specified");
if (self.value_count == .flag) @compileError("argument may not be a flag"); if (self.value_count == .flag) @compileError("argument may not be a flag");
if (self.value_count == .count) @compileError("argument may not be a count"); if (self.value_count == .count) @compileError("argument may not be a count");
@ -39,8 +38,8 @@ fn BuilderGenerics(comptime UserContext: type) type {
} }
pub fn opt_gen(comptime self: @This()) ParameterGenerics { pub fn opt_gen(comptime self: @This()) ParameterGenerics {
if (self.OutputType == void) @compileError("option must have OutputType specified");
if (self.value_count == .flag) @compileError("option may not be a flag"); if (self.value_count == .flag) @compileError("option may not be a flag");
if (self.value_count == .count) @compileError("option may not be a count");
return ParameterGenerics{ return ParameterGenerics{
.UserContext = UserContext, .UserContext = UserContext,
@ -246,6 +245,7 @@ pub fn CommandBuilder(comptime UserContext: type) type {
var env_var_fields: []const StructField = &[_]StructField{}; var env_var_fields: []const StructField = &[_]StructField{};
paramloop: for (spec, 0..) |param, idx| { paramloop: for (spec, 0..) |param, idx| {
const PType = @TypeOf(param);
// these three blocks are to check for redundantly defined tags and // these three blocks are to check for redundantly defined tags and
// environment variables. This only works within a command. It // environment variables. This only works within a command. It
// doesn't support compile time checks for conflict into // doesn't support compile time checks for conflict into
@ -278,12 +278,13 @@ pub fn CommandBuilder(comptime UserContext: type) type {
.alignment = 0, .alignment = 0,
}}; }};
if (!PType.has_output) continue :paramloop;
while (flag_skip > 0) { while (flag_skip > 0) {
flag_skip -= 1; flag_skip -= 1;
continue :paramloop; continue :paramloop;
} }
const PType = @TypeOf(param);
if (PType.is_flag) { if (PType.is_flag) {
var peek = idx + 1; var peek = idx + 1;
var bias_seen: [ncmeta.enum_length(FlagBias)]bool = [_]bool{false} ** ncmeta.enum_length(FlagBias); var bias_seen: [ncmeta.enum_length(FlagBias)]bool = [_]bool{false} ** ncmeta.enum_length(FlagBias);

View File

@ -29,16 +29,20 @@ pub const FlagBias = enum {
pub const ParameterGenerics = struct { pub const ParameterGenerics = struct {
UserContext: type = void, UserContext: type = void,
/// If void, do not expose this parameter in the aggregate converted parameter
/// object. The converter for this parameter shall not return a value. This may be
/// useful for implementing complex conversion that produces output through its
/// side effects or by modifying the user context.
OutputType: type = void, OutputType: type = void,
param_type: ParameterType, param_type: ParameterType,
value_count: ValueCount, value_count: ValueCount,
/// allow this named parameter to be passed multiple times. /// allow this named parameter to be passed multiple times.
/// values will be appended when it is encountered. If false, only the /// values will be appended when it is encountered. If false, only the
/// final encountered instance will be used. /// final encountered instance will be used.
multi: bool,
// since we now use multi in place of greedy values for simplicity, we may want to // since we now use multi in place of greedy values for simplicity, we may want to
// convert this an enum or add an additional flag to distinguish between the // convert this an enum or add an additional flag to distinguish between the
// many-to-many and the many-to-one cases. // many-to-many and the many-to-one cases.
multi: bool,
pub fn fixed_value_count(comptime OutputType: type, comptime value_count: ValueCount) ValueCount { pub fn fixed_value_count(comptime OutputType: type, comptime value_count: ValueCount) ValueCount {
return comptime if (value_count == .fixed) return comptime if (value_count == .fixed)
@ -54,14 +58,14 @@ pub const ParameterGenerics = struct {
value_count; value_count;
} }
pub fn clone_without_multi(comptime self: @This()) @This() {
return .{ .UserContext = self.UserContext, .OutputType = self.OutputType, .param_type = self.param_type, .value_count = self.value_count, .multi = false };
}
pub fn has_context(comptime self: @This()) bool { pub fn has_context(comptime self: @This()) bool {
return comptime self.UserContext != void; return comptime self.UserContext != void;
} }
pub fn has_output(comptime self: @This()) bool {
return self.OutputType != void;
}
pub fn is_flag(comptime self: @This()) bool { pub fn is_flag(comptime self: @This()) bool {
return comptime switch (self.value_count) { return comptime switch (self.value_count) {
.flag, .count => true, .flag, .count => true,
@ -71,7 +75,7 @@ pub const ParameterGenerics = struct {
pub fn ConvertedType(comptime self: @This()) type { pub fn ConvertedType(comptime self: @This()) type {
// is this the correct way to collapse this? // is this the correct way to collapse this?
return comptime if (self.multi and self.value_count != .count) return comptime if (self.multi and self.value_count != .count and self.OutputType != void)
std.ArrayList(self.ReturnValue()) std.ArrayList(self.ReturnValue())
else else
self.ReturnValue(); self.ReturnValue();
@ -184,6 +188,7 @@ fn OptionType(comptime generics: ParameterGenerics) type {
pub const is_flag: bool = generics.is_flag(); pub const is_flag: bool = generics.is_flag();
pub const value_count: ValueCount = generics.value_count; pub const value_count: ValueCount = generics.value_count;
pub const multi: bool = generics.multi; pub const multi: bool = generics.multi;
pub const has_output: bool = generics.has_output();
name: []const u8, name: []const u8,
short_tag: ?[]const u8, short_tag: ?[]const u8,

View File

@ -425,18 +425,25 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
if (@field(self.intermediate, param.name)) |intermediate| { if (@field(self.intermediate, param.name)) |intermediate| {
var buffer = std.ArrayList(u8).init(self.allocator); var buffer = std.ArrayList(u8).init(self.allocator);
const writer = buffer.writer(); const writer = buffer.writer();
@field(self.output, param.name) = try param.converter(context, intermediate, writer);
if (comptime @TypeOf(param).has_output) {
@field(self.output, param.name) = try param.converter(context, intermediate, writer);
} else {
try param.converter(context, intermediate, writer);
}
} else { } else {
if (comptime param.required) { if (comptime param.required) {
return ParseError.RequiredParameterMissing; return ParseError.RequiredParameterMissing;
} else if (comptime param.default) |def| { } else if (comptime @TypeOf(param).has_output) {
// this has to be explicitly set because even though we set it as if (comptime param.default) |def| {
// the field default, it gets clobbered because self.output is // this has to be explicitly set because even though we set it as
// initialized as undefined. // the field default, it gets clobbered because self.output is
@field(self.output, param.name) = def; // initialized as undefined.
} else { @field(self.output, param.name) = def;
@field(self.output, param.name) = null; } else {
return; @field(self.output, param.name) = null;
return;
}
} }
} }
} }