converters: doodle error reporting mechanism

This needs a bit more thought. To support outside-in error writing,
converters need access to an allocator so they can create new buffer
writers. This can be done by passing in a pointer to an ArrayList
object directly, rather than an already bound writer.
This commit is contained in:
torque 2023-04-03 01:33:47 -07:00
parent 0d5dd9b36c
commit b01c10409d
Signed by: torque
SSH Key Fingerprint: SHA256:nCrXefBNo6EbjNSQhv0nXmEg/VuNq3sMF5b8zETw3Tk
3 changed files with 36 additions and 14 deletions

View File

@ -7,8 +7,14 @@ const parameters = @import("./parameters.zig");
const ValueCount = parameters.ValueCount;
const ParameterGenerics = parameters.ParameterGenerics;
const ErrorWriter = std.ArrayList(u8).Writer;
pub fn ConverterSignature(comptime gen: ParameterGenerics) type {
return *const fn (*gen.UserContext, gen.IntermediateType()) ConversionError!gen.ConvertedType();
return *const fn (
context: *gen.UserContext,
input: gen.IntermediateType(),
failure: ErrorWriter,
) ConversionError!gen.ConvertedType();
}
pub fn default_converter(comptime gen: ParameterGenerics) ?ConverterSignature(gen) {
@ -40,12 +46,12 @@ fn multi_converter(comptime gen: ParameterGenerics) ?ConverterSignature(gen) {
const Intermediate = gen.IntermediateType();
return struct {
pub fn handler(context: *gen.UserContext, input: Intermediate) ConversionError!std.ArrayList(gen.OutputType) {
pub fn handler(context: *gen.UserContext, input: Intermediate, failure: ErrorWriter) ConversionError!std.ArrayList(gen.OutputType) {
var output = std.ArrayList(gen.OutputType).initCapacity(input.allocator, input.items.len) catch
return ConversionError.ConversionFailed;
for (input.items) |item| {
output.appendAssumeCapacity(try converter(context, item));
output.appendAssumeCapacity(try converter(context, item, failure));
}
return output;
@ -55,7 +61,7 @@ fn multi_converter(comptime gen: ParameterGenerics) ?ConverterSignature(gen) {
fn flag_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
return struct {
pub fn handler(_: *gen.UserContext, input: []const u8) ConversionError!bool {
pub fn handler(_: *gen.UserContext, input: []const u8, _: ErrorWriter) ConversionError!bool {
// treat an empty string as falsy
if (input.len == 0) return false;
@ -75,8 +81,8 @@ fn flag_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
fn string_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
return struct {
pub fn handler(_: *gen.UserContext, value: []const u8) ConversionError![]const u8 {
return value;
pub fn handler(_: *gen.UserContext, input: []const u8, _: ErrorWriter) ConversionError![]const u8 {
return input;
}
}.handler;
}
@ -85,8 +91,12 @@ fn int_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
const IntType = gen.OutputType;
return struct {
pub fn handler(_: *gen.UserContext, value: []const u8) ConversionError!IntType {
return std.fmt.parseInt(IntType, value, 0) catch return ConversionError.ConversionFailed;
pub fn handler(_: *gen.UserContext, input: []const u8, failure: ErrorWriter) ConversionError!IntType {
return std.fmt.parseInt(IntType, input, 0) catch {
// ignore the error
try failure.print("cannot interpret \"{s}\" as an integer", .{input});
return ConversionError.ConversionFailed;
};
}
}.handler;
}
@ -97,8 +107,14 @@ fn struct_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
const Intermediate = gen.IntermediateType();
return struct {
pub fn handler(context: *gen.UserContext, value: Intermediate) ConversionError!StructType {
if (value.items.len != type_info.fields.len) return ConversionError.ConversionFailed;
pub fn handler(context: *gen.UserContext, input: Intermediate, failure: ErrorWriter) ConversionError!StructType {
if (input.items.len != type_info.fields.len) {
try failure.print(
"Wrong number of fields provided. Got {d}, needed {d}",
.{ input.items.len, type_info.fields.len },
);
return ConversionError.ConversionFailed;
}
var result: StructType = undefined;
inline for (comptime type_info.fields, 0..) |field, idx| {
@ -110,7 +126,7 @@ fn struct_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
) orelse
@compileError("cannot get converter for field" ++ field.name);
@field(result, field.name) = try converter(context, value.items[idx]);
@field(result, field.name) = try converter(context, input.items[idx], failure);
}
return result;
@ -122,8 +138,11 @@ fn choice_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
const EnumType = gen.OutputType;
return struct {
pub fn handler(_: *gen.UserContext, value: []const u8) ConversionError!EnumType {
return std.meta.stringToEnum(gen.ConvertedType(), value) orelse ConversionError.ConversionFailed;
pub fn handler(_: *gen.UserContext, input: []const u8, failure: ErrorWriter) ConversionError!EnumType {
return std.meta.stringToEnum(gen.ConvertedType(), input) orelse {
try failure.print("\"{s}\" is not a valid choice", .{input});
return ConversionError.ConversionFailed;
};
}
}.handler;
}

View File

@ -1,4 +1,5 @@
pub const ConversionError = error{
OutOfMemory,
ConversionFailed,
};

View File

@ -423,7 +423,9 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
fn convert_param(self: *@This(), comptime param: anytype, context: *UserContext) NoclipError!void {
if (@field(self.intermediate, param.name)) |intermediate| {
@field(self.output, param.name) = try param.converter(context, intermediate);
var buffer = std.ArrayList(u8).init(self.allocator);
const writer = buffer.writer();
@field(self.output, param.name) = try param.converter(context, intermediate, writer);
} else {
if (comptime param.required) {
return ParseError.RequiredParameterMissing;