From b01c10409d1e4ab3a83c3599f13673b8dc57cc5e Mon Sep 17 00:00:00 2001 From: torque Date: Mon, 3 Apr 2023 01:33:47 -0700 Subject: [PATCH] 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. --- source/converters.zig | 45 ++++++++++++++++++++++++++++++------------- source/errors.zig | 1 + source/parser.zig | 4 +++- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/source/converters.zig b/source/converters.zig index d678a09..a77d27e 100644 --- a/source/converters.zig +++ b/source/converters.zig @@ -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; } diff --git a/source/errors.zig b/source/errors.zig index f07bfab..7eb8729 100644 --- a/source/errors.zig +++ b/source/errors.zig @@ -1,4 +1,5 @@ pub const ConversionError = error{ + OutOfMemory, ConversionFailed, }; diff --git a/source/parser.zig b/source/parser.zig index 8ef325b..7dec9b3 100644 --- a/source/parser.zig +++ b/source/parser.zig @@ -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;