NOCLIP/source/converters.zig
torque 1ab4a113d2
functioning converters
The converters are hooked up, including some weird magic under the hood
to automatically handle multi-values as well as the many-to-one case
of the structure. The many-to-many case of arrays/slices is not
currently handled, but it should be straightforward to do.

Hooking up subcommands should be pretty straightforward if I've
designed this correctly, so that will be next. The final major piece
of the puzzle is help text generation, which in theory can be largely
carried over from the old implementation.

The error handling is very poorly done right now as well, and I need to
figure out a strategy. My plan for converters is to pass in a writable
buffer that the parser owns that they can write messages to when they
fail, to provide useful context. I will need to figure out how this
works for recursive converters (e.g. the struct converter) since the
failure happens inside-out, but the error message will read much
better if it's composed outside-in. There are many ways to tackle this.

The code will also need to be restructured to not be a monolithic 1000
line file.
2023-03-30 00:29:46 -07:00

129 lines
4.6 KiB
Zig

const std = @import("std");
const ParameterGenerics = @import("./doodle.zig").ParameterGenerics;
const CommandError = @import("./doodle.zig").Errors;
const ValueCount = @import("./doodle.zig").ValueCount;
const ParseError = @import("./doodle.zig").ParseError;
const ncmeta = @import("./meta.zig");
pub fn ConverterSignature(comptime gen: ParameterGenerics) type {
return *const fn (*gen.UserContext, gen.IntermediateType()) ParseError!gen.ConvertedType();
}
pub fn default_converter(comptime gen: ParameterGenerics) ?ConverterSignature(gen) {
return if (comptime gen.multi)
multi_converter(gen)
else switch (@typeInfo(gen.OutputType)) {
.Bool => flag_converter(gen),
.Int => int_converter(gen),
.Pointer => |info| if (info.size == .Slice and info.child == u8)
string_converter(gen)
else
null,
.Enum => choice_converter(gen),
// 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_converter(gen)
else
null,
else => null,
};
}
fn multi_converter(comptime gen: ParameterGenerics) ?ConverterSignature(gen) {
const converter = default_converter(
ncmeta.copy_struct(ParameterGenerics, gen, .{ .multi = false }),
) orelse
@compileError("no default converter");
const Intermediate = gen.IntermediateType();
return struct {
pub fn handler(context: *gen.UserContext, input: Intermediate) ParseError!std.ArrayList(gen.OutputType) {
var output = std.ArrayList(gen.OutputType).initCapacity(input.allocator, input.items.len) catch
return ParseError.ConversionFailed;
for (input.items) |item| {
output.appendAssumeCapacity(try converter(context, item));
}
return output;
}
}.handler;
}
fn flag_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
return struct {
pub fn handler(_: *gen.UserContext, input: []const u8) ParseError!bool {
// treat an empty string as falsy
if (input.len == 0) return false;
if (input.len <= 5) {
var lowerBuf: [5]u8 = undefined;
const comp = std.ascii.lowerString(&lowerBuf, input);
inline for ([_][]const u8{ "false", "no", "0" }) |candidate| {
if (std.mem.eql(u8, comp, candidate)) return false;
}
}
return true;
}
}.handler;
}
fn string_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
return struct {
pub fn handler(_: *gen.UserContext, value: []const u8) ParseError![]const u8 {
return value;
}
}.handler;
}
fn int_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
const IntType = gen.OutputType;
return struct {
pub fn handler(_: *gen.UserContext, value: []const u8) ParseError!IntType {
return std.fmt.parseInt(IntType, value, 0) catch return ParseError.ConversionFailed;
}
}.handler;
}
fn struct_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
const StructType = gen.OutputType;
const type_info = @typeInfo(StructType).Struct;
const Intermediate = gen.IntermediateType();
return struct {
pub fn handler(context: *gen.UserContext, value: Intermediate) ParseError!StructType {
if (value.items.len != type_info.fields.len) return ParseError.ConversionFailed;
var result: StructType = undefined;
inline for (comptime type_info.fields, 0..) |field, idx| {
const converter = comptime default_converter(
ncmeta.copy_struct(ParameterGenerics, gen, .{
.OutputType = field.type,
.value_count = .{ .fixed = 1 },
}),
) orelse
@compileError("cannot get converter for field" ++ field.name);
@field(result, field.name) = try converter(context, value.items[idx]);
}
return result;
}
}.handler;
}
fn choice_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
const EnumType = gen.OutputType;
return struct {
pub fn handler(_: *gen.UserContext, value: []const u8) ParseError!EnumType {
return std.meta.stringToEnum(gen.ConvertedType(), value) orelse ParseError.ConversionFailed;
}
}.handler;
}