diff --git a/src/api.zig b/src/api.zig index 9c5a428..7b0d62e 100644 --- a/src/api.zig +++ b/src/api.zig @@ -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; diff --git a/src/engines/v8/generate.zig b/src/engines/v8/generate.zig index ca303b5..2dcca78 100644 --- a/src/engines/v8/generate.zig +++ b/src/engines/v8/generate.zig @@ -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, @@ -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| { @@ -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 @@ -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); @@ -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()) { diff --git a/src/reflect.zig b/src/reflect.zig index b0c8354..8f63268 100644 --- a/src/reflect.zig +++ b/src/reflect.zig @@ -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; @@ -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 { @@ -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()) { @@ -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, @@ -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 @@ -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, @@ -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, @@ -1165,10 +1239,14 @@ const Error = error{ FuncGetterMethodFirstArgNotSelfOrSelfPtr, FuncVoidArg, FuncMultiCbk, + FuncVariadicNotLastOne, + FuncReturnTypeVariadic, // type errors TypeTaggedUnion, TypeNestedPtr, + TypeVariadicNested, // TODO: test + TypeVariadicNotOptional, TypeLookup, }; @@ -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 { @@ -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 {} @@ -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( @@ -1380,6 +1476,10 @@ pub fn tests() !void { .{TestTypeNestedPtr}, error.TypeNestedPtr, ); + try ensureErr( + .{TestTypeVariadicNotOptional}, + error.TypeVariadicNotOptional, + ); try ensureErr( .{TestTypeLookup}, error.TypeLookup, diff --git a/src/tests/types_complex_test.zig b/src/tests/types_complex_test.zig index d178412..05a19f2 100644 --- a/src/tests/types_complex_test.zig +++ b/src/tests/types_complex_test.zig @@ -3,6 +3,7 @@ const std = @import("std"); const public = @import("../api.zig"); const tests = public.test_utils; const MyIterable = public.Iterable(u8); +const Variadic = public.Variadic; const MyList = struct { items: []u8, @@ -24,9 +25,35 @@ const MyList = struct { } }; +const MyVariadic = struct { + member: u8, + + const VariadicBool = Variadic(bool); + + pub fn constructor() MyVariadic { + return .{ .member = 0 }; + } + + pub fn _len(_: MyVariadic, variadic: ?VariadicBool) u64 { + return @as(u64, variadic.?.slice.len); + } + + pub fn _first(_: MyVariadic, _: []const u8, variadic: ?VariadicBool) bool { + return variadic.?.slice[0]; + } + + pub fn _last(_: MyVariadic, variadic: ?VariadicBool) bool { + return variadic.?.slice[variadic.?.slice.len - 1]; + } + + pub fn _empty(_: MyVariadic, _: ?VariadicBool) bool { + return true; + } +}; + // generate API, comptime pub fn generate() []public.API { - return public.compile(.{ MyList, MyIterable }); + return public.compile(.{ MyIterable, MyList, MyVariadic }); } // exec tests @@ -40,7 +67,7 @@ pub fn exec( js_env.start(apis); defer js_env.stop(); - var cases = [_]tests.Case{ + var iter = [_]tests.Case{ .{ .src = "let myList = new MyList(1, 2, 3);", .ex = "undefined" }, .{ .src = "myList.first();", .ex = "1" }, .{ .src = "let iter = myList[Symbol.iterator]();", .ex = "undefined" }, @@ -52,5 +79,14 @@ pub fn exec( .{ .src = "arr.length;", .ex = "3" }, .{ .src = "arr[0];", .ex = "1" }, }; - try tests.checkCases(js_env, &cases); + try tests.checkCases(js_env, &iter); + + var variadic = [_]tests.Case{ + .{ .src = "let myVariadic = new MyVariadic();", .ex = "undefined" }, + .{ .src = "myVariadic.len(true, false, true)", .ex = "3" }, + .{ .src = "myVariadic.first('a_str', true, false, true, false)", .ex = "true" }, + .{ .src = "myVariadic.last(true, false)", .ex = "false" }, + .{ .src = "myVariadic.empty()", .ex = "true" }, + }; + try tests.checkCases(js_env, &variadic); } diff --git a/src/types.zig b/src/types.zig index ad67b73..dbc278a 100644 --- a/src/types.zig +++ b/src/types.zig @@ -52,3 +52,9 @@ pub fn Iterable(comptime T: type) type { } }; } + +pub fn Variadic(comptime T: type) type { + return struct { + slice: []T, + }; +}