Skip to content

Commit

Permalink
Merge pull request #109 from Browsercore/variadic_args
Browse files Browse the repository at this point in the history
Variadic args
  • Loading branch information
francisbouvier authored Oct 6, 2023
2 parents 329539a + 1f551e9 commit c6ce454
Show file tree
Hide file tree
Showing 5 changed files with 262 additions and 33 deletions.
2 changes: 2 additions & 0 deletions src/api.zig
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ pub const test_utils = @import("tests/test_utils.zig");
const types = @import("types.zig");
pub const i64Num = types.i64Num;
pub const u64Num = types.u64Num;

pub const Iterable = types.Iterable;
pub const Variadic = types.Variadic;

pub const Loop = @import("loop.zig").SingleThreaded;
pub const Console = @import("console.zig").Console;
Expand Down
143 changes: 114 additions & 29 deletions src/engines/v8/generate.zig
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,62 @@ fn getNativeArg(
return value;
}

fn getArg(
alloc: std.mem.Allocator,
comptime T_refl: refl.Struct,
comptime all_T: []refl.Struct,
comptime func: refl.Func,
comptime arg: refl.Type,
iter: usize,
info: v8.FunctionCallbackInfo,
isolate: v8.Isolate,
ctx: v8.Context,
) arg.T {
var value: arg.T = undefined;

if (arg.isNative()) {

// native types
const js_value = info.getArg(@intCast(iter - func.index_offset));
value = getNativeArg(all_T[arg.T_refl_index.?], all_T, arg, js_value);
} else {

// builtin types
// and nested types (ie. JS anonymous objects)
value = switch (arg.T) {
std.mem.Allocator => alloc,
*Loop => utils.loop,
cbk.Func => cbk.Func.init(
alloc,
func,
info,
isolate,
) catch unreachable,
cbk.FuncSync => cbk.FuncSync.init(
alloc,
func,
info,
isolate,
) catch unreachable,
cbk.Arg => cbk.Arg{}, // stage1: we need type
else => jsToNative(
alloc,
T_refl,
arg,
info.getArg(@intCast(iter - func.index_offset)),
isolate,
ctx,
) catch unreachable,
};
}

return value;
}

// This function can only be used by function callbacks (ie. constructor and methods)
// as it takes a v8.FunctionCallbackInfo (with a getArg method).
fn getArgs(
alloc: std.mem.Allocator,
comptime T_refl: refl.Struct,
comptime all_T: []refl.Struct,
comptime func: refl.Func,
Expand All @@ -109,6 +162,8 @@ fn getArgs(
) func.args_T {
var args: func.args_T = undefined;

const js_args_nb = info.length();

// iter on function expected arguments
inline for (func.args, 0..) |arg, i| {

Expand All @@ -117,42 +172,56 @@ fn getArgs(
continue;
}

comptime var arg_real: refl.Type = undefined;

comptime {
if (try refl.Type.variadic(arg.under_T())) |arg_v| {
arg_real = arg_v;
} else {
arg_real = arg;
}
}

var value: arg.T = undefined;

if (arg.isNative()) {
if (arg.T == arg_real.T) {

// native types
const js_value = info.getArg(i - func.index_offset);
value = getNativeArg(all_T[arg.T_refl_index.?], all_T, arg, js_value);
// non-variadic arg
value = getArg(
alloc,
T_refl,
all_T,
func,
arg_real,
i,
info,
isolate,
ctx,
);
} else {

// builtin types
// and nested types (ie. JS anonymous objects)
value = switch (arg.T) {
std.mem.Allocator => utils.allocator,
*Loop => utils.loop,
cbk.Func => cbk.Func.init(
utils.allocator,
func,
info,
isolate,
) catch unreachable,
cbk.FuncSync => cbk.FuncSync.init(
utils.allocator,
// variadic arg
// take all trailing JS arg as variadic members
const rest_nb = js_args_nb - i;
const slice = alloc.alloc(arg_real.T, rest_nb) catch unreachable;
// TODO: alloc.free slice after func call
var iter: usize = 0;
while (iter < rest_nb) {
const slice_value = getArg(
alloc,
T_refl,
all_T,
func,
arg_real,
iter + i,
info,
isolate,
) catch unreachable,
cbk.Arg => cbk.Arg{}, // stage1: we need type
else => jsToNative(
utils.allocator,
T_refl,
arg,
info.getArg(i - func.index_offset),
isolate,
ctx,
) catch unreachable,
};
);
slice[iter] = slice_value;
iter += 1;
}
value = .{ .slice = slice };
}

// set argument
Expand Down Expand Up @@ -373,7 +442,15 @@ fn generateConstructor(
}

// prepare args
const args = getArgs(T_refl, all_T, func, info, isolate, ctx);
const args = getArgs(
utils.allocator,
T_refl,
all_T,
func,
info,
isolate,
ctx,
);

// call the native func
const cstr_func = @field(T_refl.T, func.name);
Expand Down Expand Up @@ -518,7 +595,15 @@ fn generateMethod(
}

// prepare args
var args = getArgs(T_refl, all_T, func, info, isolate, ctx);
var args = getArgs(
utils.allocator,
T_refl,
all_T,
func,
info,
isolate,
ctx,
);

// retrieve the zig object
if (!comptime T_refl.isEmpty()) {
Expand Down
102 changes: 101 additions & 1 deletion src/reflect.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const sort = @import("block.zig");
const builtin = @import("builtin");

const public = @import("api.zig");
const Variadic = public.Variadic;
const Loop = public.Loop;
const Callback = public.Callback;
const CallbackSync = public.CallbackSync;
Expand Down Expand Up @@ -71,6 +72,54 @@ pub const Type = struct {
}
}

// find if T is a VariadicType
// and returns the type of the slice members
fn _variadic(comptime T: type) ?type {
std.debug.assert(@inComptime());
const info = @typeInfo(T);

// it's a struct
if (info != .Struct) {
return null;
}

// with only 1 field
if (info.Struct.fields.len != 1) {
return null;
}

// which is called "slice"
const slice_field = info.Struct.fields[0];
if (!std.mem.eql(u8, slice_field.name, "slice")) {
return null;
}

// and it's a slice
const slice_info = @typeInfo(slice_field.type);
if (slice_info == .Pointer and slice_info.Pointer.size == .Slice) {
return slice_info.Pointer.child;
}

return null;
}

fn _is_variadic(comptime T: type) bool {
return Type._variadic(T) != null;
}

// find if T is a VariadicType
// and returns it as a reflect.Type
pub fn variadic(comptime T: type) !?Type {
std.debug.assert(@inComptime());

const tt = Type._variadic(T) orelse return null;

// avoid infinite calls
if (Type._is_variadic(tt)) return error.TypeVariadicNested;

return try Type.reflect(tt, null);
}

// check that user-defined types have been provided as an API
fn lookup(comptime self: *Type, comptime structs: []Struct) Error!void {

Expand All @@ -94,6 +143,12 @@ pub const Type = struct {
return;
}

// if variadic, lookup the concrete type
var variadic_type = try Type.variadic(self.under_T());
if (variadic_type) |*tt| {
return tt.lookup(structs);
}

// check under_T in all structs (and nested structs)
inline for (structs) |s| {
if (self.under_T() == s.T or self.under_T() == s.Self()) {
Expand Down Expand Up @@ -174,6 +229,14 @@ pub const Type = struct {
under_ptr = info.Pointer.child;
}

// variadic types must be optional
if (under_opt == null) {
const under = under_ptr orelse T;
if (Type._is_variadic(under)) {
return error.TypeVariadicNotOptional;
}
}

var t = Type{
.T = T,
.name = name,
Expand Down Expand Up @@ -402,6 +465,13 @@ pub const Func = struct {
if (args_types[i].T == CallbackArg) {
args_callback_nb += 1;
}

// variadic
// ensure only 1 variadic argument
// and that it's the last one
if (Type._is_variadic(args_types[i].under_T()) and i < (args.len - 1)) {
return error.FuncVariadicNotLastOne;
}
}

// first optional arg
Expand Down Expand Up @@ -430,6 +500,10 @@ pub const Func = struct {
const args_slice = args_types[0..];
const args_T = comptime Args.reflect(self_T, args_slice);

// reflect return type
const return_type = try Type.reflect(func.Fn.return_type.?, null);
if (Type._is_variadic(return_type.under_T())) return error.FuncReturnTypeVariadic;

return Func{
.js_name = js_name,
.name = name,
Expand All @@ -441,7 +515,7 @@ pub const Func = struct {

.index_offset = index_offset,

.return_type = try Type.reflect(func.Fn.return_type.?, null),
.return_type = return_type,

// func callback
.callback_index = callback_index,
Expand Down Expand Up @@ -1165,10 +1239,14 @@ const Error = error{
FuncGetterMethodFirstArgNotSelfOrSelfPtr,
FuncVoidArg,
FuncMultiCbk,
FuncVariadicNotLastOne,
FuncReturnTypeVariadic,

// type errors
TypeTaggedUnion,
TypeNestedPtr,
TypeVariadicNested, // TODO: test
TypeVariadicNotOptional,
TypeLookup,
};

Expand Down Expand Up @@ -1265,6 +1343,13 @@ const TestFuncVoidArg = struct {
const TestFuncMultiCbk = struct {
pub fn _example(_: TestFuncMultiCbk, _: Callback, _: Callback) void {}
};
const VariadicBool = Variadic(bool);
const TestFuncVariadicNotLastOne = struct {
pub fn _example(_: TestFuncVariadicNotLastOne, _: ?VariadicBool, _: bool) void {}
};
const TestFuncReturnTypeVariadic = struct {
pub fn _example(_: TestFuncReturnTypeVariadic) ?VariadicBool {}
};

// types tests
const TestTaggedUnion = union {
Expand All @@ -1278,6 +1363,9 @@ const TestTypeNestedPtr = struct {
pub const TestTypeNestedBase = struct {};
pub fn _example(_: TestTypeNestedPtr, _: *TestTypeNestedBase) void {}
};
const TestTypeVariadicNotOptional = struct {
pub fn _example(_: TestTypeVariadicNotOptional, _: VariadicBool) void {}
};
const TestType = struct {};
const TestTypeLookup = struct {
pub fn _example(_: TestTypeLookup, _: TestType) void {}
Expand Down Expand Up @@ -1364,6 +1452,14 @@ pub fn tests() !void {
.{TestFuncMultiCbk},
error.FuncMultiCbk,
);
try ensureErr(
.{TestFuncVariadicNotLastOne},
error.FuncVariadicNotLastOne,
);
try ensureErr(
.{TestFuncReturnTypeVariadic},
error.FuncReturnTypeVariadic,
);

// types checks
try ensureErr(
Expand All @@ -1380,6 +1476,10 @@ pub fn tests() !void {
.{TestTypeNestedPtr},
error.TypeNestedPtr,
);
try ensureErr(
.{TestTypeVariadicNotOptional},
error.TypeVariadicNotOptional,
);
try ensureErr(
.{TestTypeLookup},
error.TypeLookup,
Expand Down
Loading

0 comments on commit c6ce454

Please sign in to comment.