2023-03-25 16:08:59 -07:00
|
|
|
const std = @import("std");
|
|
|
|
|
2023-03-30 17:00:49 -07:00
|
|
|
const ConversionError = @import("./errors.zig").ConversionError;
|
2023-03-29 23:10:42 -07:00
|
|
|
const ncmeta = @import("./meta.zig");
|
2023-03-30 17:00:49 -07:00
|
|
|
const parameters = @import("./parameters.zig");
|
|
|
|
|
|
|
|
const ValueCount = parameters.ValueCount;
|
|
|
|
const ParameterGenerics = parameters.ParameterGenerics;
|
2023-03-25 16:08:59 -07:00
|
|
|
|
2023-04-03 01:33:47 -07:00
|
|
|
const ErrorWriter = std.ArrayList(u8).Writer;
|
|
|
|
|
2023-03-25 16:08:59 -07:00
|
|
|
pub fn ConverterSignature(comptime gen: ParameterGenerics) type {
|
2023-04-03 01:33:47 -07:00
|
|
|
return *const fn (
|
parser: don't force pass userdata as a pointer
This is an interesting change. While I think generally passing in
constant userdata is not terribly useful, the previous implementation
precluded it entirely. Interface types, for example, are often passed
directly and stored as constants (they hold pointers to their mutable
state).
Since we type erase this so it can be bound to the generic interface
object, non-pointer objects must be passed by reference to avoid
binding the parser interface to a temporary stack copy of the object.
This means we have to handle these cases slightly differently. Also,
while technically being classified as pointers, slices don't really
behave like pointers, which is understandable but annoying. There's a
bit of asymmetry here, as CommandBuilder(*u32) and CommandBuilder
(u32) both require an *u32 when binding the parser interface. This is
of course because pointers do not need to be rewrapped to be type
erased. The same code path could be used for both cases, but then the
user would have to pass in a pointer to a pointer, which actually
looks a bit silly because then it potentially means having to
do &&my_var.
2023-04-08 15:13:00 -07:00
|
|
|
context: gen.UserContext,
|
2023-04-03 01:33:47 -07:00
|
|
|
input: gen.IntermediateType(),
|
|
|
|
failure: ErrorWriter,
|
|
|
|
) ConversionError!gen.ConvertedType();
|
2023-03-25 16:08:59 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn default_converter(comptime gen: ParameterGenerics) ?ConverterSignature(gen) {
|
2023-03-29 23:10:42 -07:00
|
|
|
return if (comptime gen.multi)
|
|
|
|
multi_converter(gen)
|
|
|
|
else switch (@typeInfo(gen.OutputType)) {
|
2023-03-25 16:08:59 -07:00
|
|
|
.Bool => flag_converter(gen),
|
|
|
|
.Int => int_converter(gen),
|
|
|
|
.Pointer => |info| if (info.size == .Slice and info.child == u8)
|
|
|
|
string_converter(gen)
|
|
|
|
else
|
|
|
|
null,
|
2023-04-02 15:11:50 -07:00
|
|
|
.Enum => |info| if (info.is_exhaustive) choice_converter(gen) else null,
|
2023-03-29 23:10:42 -07:00
|
|
|
// 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,
|
2023-03-25 16:08:59 -07:00
|
|
|
else => null,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-03-29 23:10:42 -07:00
|
|
|
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();
|
2023-03-28 23:35:54 -07:00
|
|
|
|
2023-03-29 23:10:42 -07:00
|
|
|
return struct {
|
parser: don't force pass userdata as a pointer
This is an interesting change. While I think generally passing in
constant userdata is not terribly useful, the previous implementation
precluded it entirely. Interface types, for example, are often passed
directly and stored as constants (they hold pointers to their mutable
state).
Since we type erase this so it can be bound to the generic interface
object, non-pointer objects must be passed by reference to avoid
binding the parser interface to a temporary stack copy of the object.
This means we have to handle these cases slightly differently. Also,
while technically being classified as pointers, slices don't really
behave like pointers, which is understandable but annoying. There's a
bit of asymmetry here, as CommandBuilder(*u32) and CommandBuilder
(u32) both require an *u32 when binding the parser interface. This is
of course because pointers do not need to be rewrapped to be type
erased. The same code path could be used for both cases, but then the
user would have to pass in a pointer to a pointer, which actually
looks a bit silly because then it potentially means having to
do &&my_var.
2023-04-08 15:13:00 -07:00
|
|
|
pub fn handler(context: gen.UserContext, input: Intermediate, failure: ErrorWriter) ConversionError!std.ArrayList(gen.OutputType) {
|
2023-03-29 23:10:42 -07:00
|
|
|
var output = std.ArrayList(gen.OutputType).initCapacity(input.allocator, input.items.len) catch
|
2023-03-30 17:00:49 -07:00
|
|
|
return ConversionError.ConversionFailed;
|
2023-03-28 23:35:54 -07:00
|
|
|
|
2023-03-29 23:10:42 -07:00
|
|
|
for (input.items) |item| {
|
2023-04-03 01:33:47 -07:00
|
|
|
output.appendAssumeCapacity(try converter(context, item, failure));
|
2023-03-29 23:10:42 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
}.handler;
|
|
|
|
}
|
2023-03-28 23:35:54 -07:00
|
|
|
|
2023-03-25 16:08:59 -07:00
|
|
|
fn flag_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
|
2023-03-28 01:07:02 -07:00
|
|
|
return struct {
|
parser: don't force pass userdata as a pointer
This is an interesting change. While I think generally passing in
constant userdata is not terribly useful, the previous implementation
precluded it entirely. Interface types, for example, are often passed
directly and stored as constants (they hold pointers to their mutable
state).
Since we type erase this so it can be bound to the generic interface
object, non-pointer objects must be passed by reference to avoid
binding the parser interface to a temporary stack copy of the object.
This means we have to handle these cases slightly differently. Also,
while technically being classified as pointers, slices don't really
behave like pointers, which is understandable but annoying. There's a
bit of asymmetry here, as CommandBuilder(*u32) and CommandBuilder
(u32) both require an *u32 when binding the parser interface. This is
of course because pointers do not need to be rewrapped to be type
erased. The same code path could be used for both cases, but then the
user would have to pass in a pointer to a pointer, which actually
looks a bit silly because then it potentially means having to
do &&my_var.
2023-04-08 15:13:00 -07:00
|
|
|
pub fn handler(_: gen.UserContext, input: []const u8, _: ErrorWriter) ConversionError!bool {
|
2023-03-28 01:07:02 -07:00
|
|
|
// treat an empty string as falsy
|
|
|
|
if (input.len == 0) return false;
|
2023-03-25 16:08:59 -07:00
|
|
|
|
2023-03-28 01:07:02 -07:00
|
|
|
if (input.len <= 5) {
|
|
|
|
var lowerBuf: [5]u8 = undefined;
|
|
|
|
const comp = std.ascii.lowerString(&lowerBuf, input);
|
2023-03-25 16:08:59 -07:00
|
|
|
|
2023-03-28 01:07:02 -07:00
|
|
|
inline for ([_][]const u8{ "false", "no", "0" }) |candidate| {
|
|
|
|
if (std.mem.eql(u8, comp, candidate)) return false;
|
2023-03-25 16:08:59 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-28 01:07:02 -07:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}.handler;
|
2023-03-25 16:08:59 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn string_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
|
2023-03-28 01:07:02 -07:00
|
|
|
return struct {
|
parser: don't force pass userdata as a pointer
This is an interesting change. While I think generally passing in
constant userdata is not terribly useful, the previous implementation
precluded it entirely. Interface types, for example, are often passed
directly and stored as constants (they hold pointers to their mutable
state).
Since we type erase this so it can be bound to the generic interface
object, non-pointer objects must be passed by reference to avoid
binding the parser interface to a temporary stack copy of the object.
This means we have to handle these cases slightly differently. Also,
while technically being classified as pointers, slices don't really
behave like pointers, which is understandable but annoying. There's a
bit of asymmetry here, as CommandBuilder(*u32) and CommandBuilder
(u32) both require an *u32 when binding the parser interface. This is
of course because pointers do not need to be rewrapped to be type
erased. The same code path could be used for both cases, but then the
user would have to pass in a pointer to a pointer, which actually
looks a bit silly because then it potentially means having to
do &&my_var.
2023-04-08 15:13:00 -07:00
|
|
|
pub fn handler(_: gen.UserContext, input: []const u8, _: ErrorWriter) ConversionError![]const u8 {
|
2023-04-03 01:33:47 -07:00
|
|
|
return input;
|
2023-03-28 01:07:02 -07:00
|
|
|
}
|
|
|
|
}.handler;
|
2023-03-25 16:08:59 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn int_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
|
2023-03-28 23:35:54 -07:00
|
|
|
const IntType = gen.OutputType;
|
2023-03-25 16:08:59 -07:00
|
|
|
|
2023-03-28 01:07:02 -07:00
|
|
|
return struct {
|
parser: don't force pass userdata as a pointer
This is an interesting change. While I think generally passing in
constant userdata is not terribly useful, the previous implementation
precluded it entirely. Interface types, for example, are often passed
directly and stored as constants (they hold pointers to their mutable
state).
Since we type erase this so it can be bound to the generic interface
object, non-pointer objects must be passed by reference to avoid
binding the parser interface to a temporary stack copy of the object.
This means we have to handle these cases slightly differently. Also,
while technically being classified as pointers, slices don't really
behave like pointers, which is understandable but annoying. There's a
bit of asymmetry here, as CommandBuilder(*u32) and CommandBuilder
(u32) both require an *u32 when binding the parser interface. This is
of course because pointers do not need to be rewrapped to be type
erased. The same code path could be used for both cases, but then the
user would have to pass in a pointer to a pointer, which actually
looks a bit silly because then it potentially means having to
do &&my_var.
2023-04-08 15:13:00 -07:00
|
|
|
pub fn handler(_: gen.UserContext, input: []const u8, failure: ErrorWriter) ConversionError!IntType {
|
2023-04-03 01:33:47 -07:00
|
|
|
return std.fmt.parseInt(IntType, input, 0) catch {
|
|
|
|
try failure.print("cannot interpret \"{s}\" as an integer", .{input});
|
|
|
|
return ConversionError.ConversionFailed;
|
|
|
|
};
|
2023-03-29 23:10:42 -07:00
|
|
|
}
|
|
|
|
}.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 {
|
parser: don't force pass userdata as a pointer
This is an interesting change. While I think generally passing in
constant userdata is not terribly useful, the previous implementation
precluded it entirely. Interface types, for example, are often passed
directly and stored as constants (they hold pointers to their mutable
state).
Since we type erase this so it can be bound to the generic interface
object, non-pointer objects must be passed by reference to avoid
binding the parser interface to a temporary stack copy of the object.
This means we have to handle these cases slightly differently. Also,
while technically being classified as pointers, slices don't really
behave like pointers, which is understandable but annoying. There's a
bit of asymmetry here, as CommandBuilder(*u32) and CommandBuilder
(u32) both require an *u32 when binding the parser interface. This is
of course because pointers do not need to be rewrapped to be type
erased. The same code path could be used for both cases, but then the
user would have to pass in a pointer to a pointer, which actually
looks a bit silly because then it potentially means having to
do &&my_var.
2023-04-08 15:13:00 -07:00
|
|
|
pub fn handler(context: gen.UserContext, input: Intermediate, failure: ErrorWriter) ConversionError!StructType {
|
2023-04-03 01:33:47 -07:00
|
|
|
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;
|
|
|
|
}
|
2023-03-29 23:10:42 -07:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2023-04-03 01:33:47 -07:00
|
|
|
@field(result, field.name) = try converter(context, input.items[idx], failure);
|
2023-03-29 23:10:42 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
2023-03-28 01:07:02 -07:00
|
|
|
}
|
|
|
|
}.handler;
|
2023-03-25 16:08:59 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn choice_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
|
2023-03-28 23:35:54 -07:00
|
|
|
const EnumType = gen.OutputType;
|
2023-03-25 16:08:59 -07:00
|
|
|
|
2023-03-28 01:07:02 -07:00
|
|
|
return struct {
|
parser: don't force pass userdata as a pointer
This is an interesting change. While I think generally passing in
constant userdata is not terribly useful, the previous implementation
precluded it entirely. Interface types, for example, are often passed
directly and stored as constants (they hold pointers to their mutable
state).
Since we type erase this so it can be bound to the generic interface
object, non-pointer objects must be passed by reference to avoid
binding the parser interface to a temporary stack copy of the object.
This means we have to handle these cases slightly differently. Also,
while technically being classified as pointers, slices don't really
behave like pointers, which is understandable but annoying. There's a
bit of asymmetry here, as CommandBuilder(*u32) and CommandBuilder
(u32) both require an *u32 when binding the parser interface. This is
of course because pointers do not need to be rewrapped to be type
erased. The same code path could be used for both cases, but then the
user would have to pass in a pointer to a pointer, which actually
looks a bit silly because then it potentially means having to
do &&my_var.
2023-04-08 15:13:00 -07:00
|
|
|
pub fn handler(_: gen.UserContext, input: []const u8, failure: ErrorWriter) ConversionError!EnumType {
|
2023-04-03 01:33:47 -07:00
|
|
|
return std.meta.stringToEnum(gen.ConvertedType(), input) orelse {
|
|
|
|
try failure.print("\"{s}\" is not a valid choice", .{input});
|
|
|
|
return ConversionError.ConversionFailed;
|
|
|
|
};
|
2023-03-28 01:07:02 -07:00
|
|
|
}
|
|
|
|
}.handler;
|
2023-03-25 16:08:59 -07:00
|
|
|
}
|