diff options
author | 2021-09-24 15:33:02 -0700 | |
---|---|---|
committer | 2021-09-24 15:33:02 -0700 | |
commit | bdfb5a91b14a129df1b1a5d46aae5c8ef2ea2fd8 (patch) | |
tree | a05e1fff6e3cfbdaaf8fbbf247d62f4a0be56ced | |
parent | 29b986684dd157183b258a2598df102e0e4c9055 (diff) | |
download | bun-bdfb5a91b14a129df1b1a5d46aae5c8ef2ea2fd8.tar.gz bun-bdfb5a91b14a129df1b1a5d46aae5c8ef2ea2fd8.tar.zst bun-bdfb5a91b14a129df1b1a5d46aae5c8ef2ea2fd8.zip |
macro
-rw-r--r-- | src/ast/ast.js | 106 | ||||
-rw-r--r-- | src/bundler.zig | 78 | ||||
-rw-r--r-- | src/js_ast.zig | 501 |
3 files changed, 681 insertions, 4 deletions
diff --git a/src/ast/ast.js b/src/ast/ast.js new file mode 100644 index 000000000..06be333c5 --- /dev/null +++ b/src/ast/ast.js @@ -0,0 +1,106 @@ +globalThis.BunASTNode ??= class BunASTNode { + position = -1; +}; + +if (!globalThis.BunAST) { + globalThis.BunAST = { + EArray: class EArray extends BunASTNode { + #ptr = Number.MAX_SAFE_INTEGER; + }, + EUnary: class EUnary extends BunASTNode { + #ptr = Number.MAX_SAFE_INTEGER; + }, + EBinary: class EBinary extends BunASTNode { + #ptr = Number.MAX_SAFE_INTEGER; + }, + EClass: class EClass extends BunASTNode { + #ptr = Number.MAX_SAFE_INTEGER; + }, + ENew: class ENew extends BunASTNode { + #ptr = Number.MAX_SAFE_INTEGER; + }, + EFunction: class EFunction extends BunASTNode { + #ptr = Number.MAX_SAFE_INTEGER; + }, + ECall: class ECall extends BunASTNode { + #ptr = Number.MAX_SAFE_INTEGER; + }, + EDot: class EDot extends BunASTNode { + #ptr = Number.MAX_SAFE_INTEGER; + }, + EIndex: class EIndex extends BunASTNode { + #ptr = Number.MAX_SAFE_INTEGER; + }, + EArrow: class EArrow extends BunASTNode { + #ptr = Number.MAX_SAFE_INTEGER; + }, + EIdentifier: class EIdentifier extends BunASTNode { + #ptr = Number.MAX_SAFE_INTEGER; + }, + EImportIdentifier: class EImportIdentifier extends BunASTNode { + #ptr = Number.MAX_SAFE_INTEGER; + }, + EPrivateIdentifier: class EPrivateIdentifier extends BunASTNode { + #ptr = Number.MAX_SAFE_INTEGER; + }, + EJsxElement: class EJsxElement extends BunASTNode { + #ptr = Number.MAX_SAFE_INTEGER; + }, + EObject: class EObject extends BunASTNode { + #ptr = Number.MAX_SAFE_INTEGER; + }, + ESpread: class ESpread extends BunASTNode { + #ptr = Number.MAX_SAFE_INTEGER; + }, + ETemplatePart: class ETemplatePart extends BunASTNode { + #ptr = Number.MAX_SAFE_INTEGER; + }, + ETemplate: class ETemplate extends BunASTNode { + #ptr = Number.MAX_SAFE_INTEGER; + }, + ERegExp: class ERegExp extends BunASTNode { + #ptr = Number.MAX_SAFE_INTEGER; + }, + EAwait: class EAwait extends BunASTNode { + #ptr = Number.MAX_SAFE_INTEGER; + }, + EYield: class EYield extends BunASTNode { + #ptr = Number.MAX_SAFE_INTEGER; + }, + EIf: class EIf extends BunASTNode { + no = Number.MAX_SAFE_INTEGER; + yes = Number.MAX_SAFE_INTEGER; + }, + ERequire: class ERequire extends BunASTNode { + #ptr = Number.MAX_SAFE_INTEGER; + }, + EImport: class EImport extends BunASTNode { + #ptr = Number.MAX_SAFE_INTEGER; + }, + EBoolean: class EBoolean extends BunASTNode { + val = false; + }, + ENumber: class ENumber extends BunASTNode { + val = 0; + }, + EBigInt: class EBigInt extends BunASTNode { + #ptr = Number.MAX_SAFE_INTEGER; + }, + EString: class EString extends BunASTNode { + #ptr = Number.MAX_SAFE_INTEGER; + }, + EMissing: class EMissing extends BunASTNode { + #ptr = Number.MAX_SAFE_INTEGER; + }, + EThis: class EThis extends BunASTNode {}, + ESuper: class ESuper extends BunASTNode { + #ptr = Number.MAX_SAFE_INTEGER; + }, + ENull: class ENull extends BunASTNode {}, + EUndefined: class EUndefined extends BunASTNode {}, + ENewTarget: class ENewTarget extends BunASTNode { + #ptr = Number.MAX_SAFE_INTEGER; + }, + EImportMeta: class EImportMeta extends BunASTNode {}, + }; +} diff --git a/src/bundler.zig b/src/bundler.zig index 77534e602..ef38f7d96 100644 --- a/src/bundler.zig +++ b/src/bundler.zig @@ -123,6 +123,8 @@ pub const Bundler = struct { // must be pointer array because we can't we don't want the source to point to invalid memory if the array size is reallocated virtual_modules: std.ArrayList(*ClientEntryPoint), + macro_context: ?*js_ast.Macro.MacroContext = null, + pub const isCacheEnabled = cache_files; pub fn clone(this: *ThisBundler, allocator: *std.mem.Allocator, to: *ThisBundler) !void { @@ -2412,6 +2414,13 @@ pub const Bundler = struct { bundler.options.jsx.supports_fast_refresh; opts.filepath_hash_for_hmr = file_hash orelse 0; opts.warn_about_unbundled_modules = bundler.options.platform != .bun; + + if (bundler.macro_context == null) { + bundler.macro_context = js_ast.Macro.MacroContext.init(bundler); + } + + opts.macro_context = &bundler.macro_context.?; + const value = (bundler.resolver.caches.js.parse( allocator, opts, @@ -3317,3 +3326,72 @@ pub const ResolveQueue = std.fifo.LinearFifo( _resolver.Result, std.fifo.LinearFifoBufferType.Dynamic, ); + +// This is not very fast. +// The idea is: we want to generate a unique entry point per macro function export that registers the macro +// Registering the macro happens in VirtualMachine +// We "register" it which just marks the JSValue as protected. +// This is mostly a workaround for being unable to call ESM exported functions from C++. +// When that is resolved, we should remove this. +pub const MacroEntryPoint = struct { + code_buffer: [std.fs.MAX_PATH_BYTES * 2 + 500]u8 = undefined, + output_code_buffer: [std.fs.MAX_PATH_BYTES * 8 + 500]u8 = undefined, + source: logger.Source = undefined, + + pub fn generateID(entry_path: string, function_name: string, buf: []u8, len: *u32) i32 { + var hasher = std.hash.Wyhash.init(0); + hasher.update(js_ast.Macro.namespaceWithColon); + hasher.update(entry_path); + hasher.update(function_name); + const truncated_u32 = @truncate(u32, hasher.final()); + + const specifier = std.fmt.bufPrint(buf, js_ast.Macro.namespaceWithColon ++ "//{x}.js", .{truncated_u32}) catch unreachable; + len.* = @truncate(u32, specifier.len); + + return generateIDFromSpecifier(specifier); + } + + pub fn generateIDFromSpecifier(specifier: string) i32 { + return @bitCast(i32, @truncate(u32, std.hash.Wyhash.hash(0, specifier))); + } + + pub fn generate( + entry: *MacroEntryPoint, + bundler: *Bundler, + import_path: Fs.PathName, + function_name: string, + macro_id: i32, + macro_label_: string, + ) !void { + const dir_to_use: string = import_path.dirWithTrailingSlash(); + std.mem.copy(u8, entry.code_buffer[0..macro_label_.len], macro_label_); + const macro_label = entry.code_buffer[0..macro_label_.len]; + + const code = try std.fmt.bufPrint( + entry.code_buffer[macro_label.len..], + \\//Auto-generated file + \\import * as Macros from '{s}{s}'; + \\ + \\if (!('{s}' in Macros)) {{ + \\ throw new Error("Macro '{s}' not found in '{s}{s}'"); + \\}} + \\ + \\Bun.registerMacro({d}, Macros['{s}']); + , + .{ + dir_to_use, + import_path.filename, + function_name, + function_name, + dir_to_use, + import_path.filename, + macro_id, + function_name, + }, + ); + + entry.source = logger.Source.initPathString(macro_label, code); + entry.source.path.text = macro_label; + entry.source.path.namespace = js_ast.Macro.namespace; + } +}; diff --git a/src/js_ast.zig b/src/js_ast.zig index 0b98d1a38..9ddafdf80 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -321,7 +321,7 @@ pub const Binding = struct { *B.Object => { return Binding{ .loc = loc, .data = B{ .b_object = t } }; }, - *B.Missing => { + B.Missing => { return Binding{ .loc = loc, .data = B{ .b_missing = t } }; }, else => { @@ -1700,6 +1700,7 @@ pub const Stmt = struct { pub const All = NewBaseStore(Union, 128); threadlocal var has_inited = false; + pub threadlocal var disable_reset = false; pub fn create(allocator: *std.mem.Allocator) void { if (has_inited) { return; @@ -1710,6 +1711,7 @@ pub const Stmt = struct { } pub fn reset() void { + if (disable_reset) return; All.reset(); } @@ -2367,7 +2369,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_number = Data.Store.All.append(Type, st), + .e_number = st, }, }; }, @@ -2542,8 +2544,160 @@ pub const Expr = struct { e_class, e_require, + pub inline fn toPublicValue(this: Tag) u16 { + return @intCast(u16, @enumToInt(this)) + 16; + } + + pub inline fn fromPublicValue(comptime ValueType: type, value: ValueType) ?Tag { + if (value < 16 or value > @enumToInt(Tag.e_require)) return null; + + switch (comptime ValueType) { + f64 => { + return @intToEnum(@floatToInt(u16, value - 16), Tag); + }, + else => { + return @intToEnum(@intCast(u6, @intCast(u16, value) - 16), Tag); + }, + } + } + + pub const names_strings = [_]string{ + "<array>", + "<unary>", + "<binary>", + "<boolean>", + "<super>", + "<null>", + "<void>", + "<new>", + "<function>", + "<ntarget>", + "<import>", + "<call>", + "<dot>", + "<index>", + "<arrow>", + "<id>", + "<importid>", + "<private>", + "<jsx>", + "<missing>", + "<number>", + "<bigint>", + "<object>", + "<spread>", + "<string>", + "<tpart>", + "<template>", + "<regexp>", + "<await>", + "<yield>", + "<if>", + "<resolve>", + "<import>", + "<this>", + "<class>", + "<require>", + }; + pub const valid_names_list: string = brk: { + var names_list = names_strings[0]; + for (names_strings[1..]) |name_str, i| { + names_list = names_list ++ "\n " ++ name_str; + } + break :brk " " ++ names_list; + }; + + pub const TagName = std.EnumArray(Tag, string); + + pub const names: TagName = brk: { + var array = TagName.initUndefined(); + array.set(.e_array, names_strings[0]); + array.set(.e_unary, names_strings[1]); + array.set(.e_binary, names_strings[2]); + array.set(.e_boolean, names_strings[3]); + array.set(.e_super, names_strings[4]); + array.set(.e_null, names_strings[5]); + array.set(.e_undefined, names_strings[6]); + array.set(.e_new, names_strings[7]); + array.set(.e_function, names_strings[8]); + array.set(.e_new_target, names_strings[9]); + array.set(.e_import_meta, names_strings[10]); + array.set(.e_call, names_strings[11]); + array.set(.e_dot, names_strings[12]); + array.set(.e_index, names_strings[13]); + array.set(.e_arrow, names_strings[14]); + array.set(.e_identifier, names_strings[15]); + array.set(.e_import_identifier, names_strings[16]); + array.set(.e_private_identifier, names_strings[17]); + array.set(.e_jsx_element, names_strings[18]); + array.set(.e_missing, names_strings[19]); + array.set(.e_number, names_strings[20]); + array.set(.e_big_int, names_strings[21]); + array.set(.e_object, names_strings[22]); + array.set(.e_spread, names_strings[23]); + array.set(.e_string, names_strings[24]); + array.set(.e_template_part, names_strings[25]); + array.set(.e_template, names_strings[26]); + array.set(.e_reg_exp, names_strings[27]); + array.set(.e_await, names_strings[28]); + array.set(.e_yield, names_strings[29]); + array.set(.e_if, names_strings[30]); + array.set(.e_require_or_require_resolve, names_strings[31]); + array.set(.e_import, names_strings[32]); + array.set(.e_this, names_strings[33]); + array.set(.e_class, names_strings[34]); + array.set(.e_require, names_strings[35]); + break :brk array; + }; + pub const TagExactSizeMatcher = strings.ExactSizeMatcher(8); + pub fn find(name_: string) ?Tag { + return switch (TagExactSizeMatcher.match(name_)) { + TagExactSizeMatcher.case("array") => Tag.e_array, + TagExactSizeMatcher.case("unary") => Tag.e_unary, + TagExactSizeMatcher.case("binary") => Tag.e_binary, + TagExactSizeMatcher.case("boolean") => Tag.e_boolean, + TagExactSizeMatcher.case("true") => Tag.e_boolean, + TagExactSizeMatcher.case("false") => Tag.e_boolean, + TagExactSizeMatcher.case("super") => Tag.e_super, + TagExactSizeMatcher.case("null") => Tag.e_null, + TagExactSizeMatcher.case("void") => Tag.e_undefined, + TagExactSizeMatcher.case("new") => Tag.e_new, + TagExactSizeMatcher.case("function") => Tag.e_function, + TagExactSizeMatcher.case("ntarget") => Tag.e_new_target, + TagExactSizeMatcher.case("imeta") => Tag.e_import_meta, + TagExactSizeMatcher.case("call") => Tag.e_call, + TagExactSizeMatcher.case("dot") => Tag.e_dot, + TagExactSizeMatcher.case("index") => Tag.e_index, + TagExactSizeMatcher.case("arrow") => Tag.e_arrow, + TagExactSizeMatcher.case("id") => Tag.e_identifier, + TagExactSizeMatcher.case("importid") => Tag.e_import_identifier, + TagExactSizeMatcher.case("jsx") => Tag.e_jsx_element, + TagExactSizeMatcher.case("missing") => Tag.e_missing, + TagExactSizeMatcher.case("number") => Tag.e_number, + TagExactSizeMatcher.case("bigint") => Tag.e_big_int, + TagExactSizeMatcher.case("object") => Tag.e_object, + TagExactSizeMatcher.case("spread") => Tag.e_spread, + TagExactSizeMatcher.case("string") => Tag.e_string, + TagExactSizeMatcher.case("tpart") => Tag.e_template_part, + TagExactSizeMatcher.case("template") => Tag.e_template, + TagExactSizeMatcher.case("regexp") => Tag.e_reg_exp, + TagExactSizeMatcher.case("await") => Tag.e_await, + TagExactSizeMatcher.case("yield") => Tag.e_yield, + TagExactSizeMatcher.case("if") => Tag.e_if, + TagExactSizeMatcher.case("import") => Tag.e_import, + TagExactSizeMatcher.case("this") => Tag.e_this, + TagExactSizeMatcher.case("class") => Tag.e_class, + TagExactSizeMatcher.case("require") => Tag.e_require, + else => null, + }; + } + + pub inline fn name(this: Tag) string { + return names.get(this); + } + pub fn jsonStringify(self: @This(), opts: anytype, o: anytype) !void { - return try std.json.stringify(@tagName(self), opts, o); + return try std.json.stringify(self.name(), opts, o); } pub fn isArray(self: Tag) bool { @@ -3074,7 +3228,7 @@ pub const Expr = struct { e_import: *E.Import, e_boolean: E.Boolean, - e_number: *E.Number, + e_number: E.Number, e_big_int: *E.BigInt, e_string: *E.String, @@ -3129,6 +3283,7 @@ pub const Expr = struct { ); threadlocal var has_inited = false; + pub threadlocal var disable_reset = false; pub fn create(allocator: *std.mem.Allocator) void { if (has_inited) { return; @@ -3140,6 +3295,7 @@ pub const Expr = struct { } pub fn reset() void { + if (disable_reset) return; All.reset(); Identifier.reset(); } @@ -3921,6 +4077,343 @@ pub fn printmem(comptime format: string, args: anytype) void { // Output.print(format, args); } +pub const Macro = struct { + const JavaScript = @import("./javascript/jsc/javascript.zig"); + const JSC = @import("./javascript/jsc/bindings/bindings.zig"); + const JSCBase = @import("./javascript/jsc/base.zig"); + const Resolver = @import("./resolver/resolver.zig").Resolver; + const isPackagePath = @import("./resolver/resolver.zig").isPackagePath; + const ResolveResult = @import("./resolver/resolver.zig").Result; + const DotEnv = @import("./env_loader.zig"); + const js = @import("./javascript/jsc/JavascriptCore.zig"); + const Zig = @import("./javascript/jsc/bindings/exports.zig"); + const Bundler = @import("./bundler.zig").Bundler; + const MacroEntryPoint = @import("./bundler.zig").MacroEntryPoint; + + pub const namespace: string = "macro"; + pub const namespaceWithColon: string = namespace ++ ":"; + + pub fn isMacroPath(str: string) bool { + return (str.len > namespaceWithColon.len and strings.eqlComptimeIgnoreLen(str[0..namespaceWithColon.len], namespaceWithColon)); + } + + pub const MacroContext = struct { + pub const MacroMap = std.AutoArrayHashMap(i32, Macro); + + resolver: *Resolver, + env: *DotEnv.Loader, + macros: MacroMap, + + pub fn init(bundler: *Bundler) MacroContext { + return MacroContext{ + .macros = MacroMap.init(default_allocator), + .resolver = &bundler.resolver, + .env = bundler.env, + }; + } + + pub fn call( + this: *MacroContext, + import_record_path: string, + source_dir: string, + log: *logger.Log, + source: *const logger.Source, + import_range: logger.Range, + caller: Expr, + args: []Expr, + function_name: string, + ) anyerror!Expr { + Expr.Data.Store.disable_reset = true; + Stmt.Data.Store.disable_reset = true; + defer Expr.Data.Store.disable_reset = false; + defer Stmt.Data.Store.disable_reset = false; + // const is_package_path = isPackagePath(specifier); + std.debug.assert(isMacroPath(import_record_path)); + + const resolve_result = this.resolver.resolve(source_dir, import_record_path[namespaceWithColon.len..], .stmt) catch |err| { + switch (err) { + error.ModuleNotFound => { + log.addResolveError( + source, + import_range, + log.msgs.allocator, + "Macro \"{s}\" not found", + .{import_record_path}, + .stmt, + ) catch unreachable; + return error.MacroNotFound; + }, + else => { + log.addRangeErrorFmt( + source, + import_range, + log.msgs.allocator, + "{s} resolving macro \"{s}\"", + .{ @errorName(err), import_record_path }, + ) catch unreachable; + return err; + }, + } + }; + + var specifier_buf: [64]u8 = undefined; + var specifier_buf_len: u32 = 0; + const hash = MacroEntryPoint.generateID( + resolve_result.path_pair.primary.text, + function_name, + &specifier_buf, + &specifier_buf_len, + ); + + var macro_entry = this.macros.getOrPut(hash) catch unreachable; + if (!macro_entry.found_existing) { + macro_entry.value_ptr.* = Macro.init( + default_allocator, + this.resolver, + resolve_result, + log, + this.env, + function_name, + specifier_buf[0..specifier_buf_len], + hash, + ) catch |err| { + macro_entry.value_ptr.* = Macro{ .resolver = undefined, .disabled = true }; + return err; + }; + Output.flush(); + } + defer Output.flush(); + + const macro = macro_entry.value_ptr.*; + if (macro.disabled) { + return caller; + } + macro.vm.enableMacroMode(); + defer macro.vm.disableMacroMode(); + return Macro.Runner.run( + macro, + log, + default_allocator, + function_name, + caller, + args, + source, + hash, + ); + // this.macros.getOrPut(key: K) + } + }; + + pub const JSExpr = struct { + expr: Expr, + pub const Class = JSCBase.NewClass( + JSExpr, + .{ + .name = "JSExpr", + .read_only = true, + }, + .{ + .toString = .{ + .rfn = toString, + }, + // .toNumber = .{ + // .rfn = toNumber, + // }, + }, + .{ + .tag = .{ + .get = getTag, + .ro = true, + }, + .tagName = .{ + .get = getTagName, + .ro = true, + }, + .position = .{ + .get = getPosition, + .ro = true, + }, + }, + ); + + // pub fn isInstanceOf( + // ctx: js.JSContextRef, + // obj: js.JSObjectRef, + // value: js.JSValueRef, + // exception: js.ExceptionRef, + // ) bool { + // js.JSValueToNumber(ctx, value, exception); + // } + + pub fn toString( + this: *JSExpr, + ctx: js.JSContextRef, + function: js.JSObjectRef, + thisObject: js.JSObjectRef, + arguments: []const js.JSValueRef, + exception: js.ExceptionRef, + ) js.JSObjectRef { + switch (this.expr.data) { + .e_string => |str| { + if (str.isBlank()) { + return JSC.ZigString.init("").toValue(JavaScript.VirtualMachine.vm.global).asRef(); + } + + if (str.isUTF8()) { + return JSC.ZigString.init(str.utf8).toValue(JavaScript.VirtualMachine.vm.global).asRef(); + } else { + return js.JSValueMakeString(ctx, js.JSStringCreateWithCharactersNoCopy(str.value.ptr, str.value.len)); + } + }, + .e_template => |template| { + const str = template.head; + + if (str.isBlank()) { + return JSC.ZigString.init("").toValue(JavaScript.VirtualMachine.vm.global).asRef(); + } + + if (str.isUTF8()) { + return JSC.ZigString.init(str.utf8).toValue(JavaScript.VirtualMachine.vm.global).asRef(); + } else { + return js.JSValueMakeString(ctx, js.JSStringCreateWithCharactersNoCopy(str.value.ptr, str.value.len)); + } + }, + else => { + return JSC.ZigString.init("").toValue(JavaScript.VirtualMachine.vm.global).asRef(); + }, + } + } + + pub fn getTag( + this: *JSExpr, + ctx: js.JSContextRef, + thisObject: js.JSValueRef, + prop: js.JSStringRef, + exception: js.ExceptionRef, + ) js.JSObjectRef { + return JSC.JSValue.jsNumberFromU16(@intCast(u16, @enumToInt(std.meta.activeTag(this.expr.data)))).asRef(); + } + pub fn getTagName( + this: *JSExpr, + ctx: js.JSContextRef, + thisObject: js.JSValueRef, + prop: js.JSStringRef, + exception: js.ExceptionRef, + ) js.JSObjectRef { + return JSC.ZigString.init(@tagName(this.expr.data)).toValue(JavaScript.VirtualMachine.vm.global).asRef(); + } + pub fn getPosition( + this: *JSExpr, + ctx: js.JSContextRef, + thisObject: js.JSValueRef, + prop: js.JSStringRef, + exception: js.ExceptionRef, + ) js.JSObjectRef { + return JSC.JSValue.jsNumberFromInt32(this.expr.loc.start).asRef(); + } + }; + + resolver: *Resolver, + vm: *JavaScript.VirtualMachine = undefined, + + resolved: ResolveResult = undefined, + disabled: bool = false, + + pub fn init( + allocator: *std.mem.Allocator, + resolver: *Resolver, + resolved: ResolveResult, + log: *logger.Log, + env: *DotEnv.Loader, + function_name: string, + specifier: string, + hash: i32, + ) !Macro { + const path = resolved.path_pair.primary; + + var vm: *JavaScript.VirtualMachine = if (JavaScript.VirtualMachine.vm_loaded) + JavaScript.VirtualMachine.vm + else brk: { + var _vm = try JavaScript.VirtualMachine.init(default_allocator, resolver.opts.transform_options, null, log, env); + _vm.enableMacroMode(); + + _vm.bundler.configureLinker(); + _vm.bundler.configureDefines() catch unreachable; + break :brk _vm; + }; + + vm.enableMacroMode(); + + var loaded_result = try vm.loadMacroEntryPoint(path.text, function_name, specifier, hash); + + if (loaded_result.status(vm.global.vm()) == JSC.JSPromise.Status.Rejected) { + vm.defaultErrorHandler(loaded_result.result(vm.global.vm()), null); + vm.disableMacroMode(); + return error.MacroLoadError; + } + + JavaScript.VirtualMachine.vm_loaded = true; + + // We don't need to do anything with the result. + // We just want to make sure the promise is finished. + _ = loaded_result.result(vm.global.vm()); + + return Macro{ + .vm = vm, + .resolved = resolved, + .resolver = resolver, + }; + } + + pub const Runner = struct { + threadlocal var args_buf: [32]js.JSObjectRef = undefined; + threadlocal var expr_nodes_buf: [32]JSExpr = undefined; + threadlocal var exception_holder: Zig.ZigException.Holder = undefined; + pub fn run( + macro: Macro, + log: *logger.Log, + allocator: *std.mem.Allocator, + function_name: string, + caller: Expr, + args: []Expr, + source: *const logger.Source, + id: i32, + ) Expr { + if (comptime isDebug) Output.prettyln("<r><d>[macro]<r> call <d><b>{s}<r>", .{function_name}); + + exception_holder = Zig.ZigException.Holder.init(); + expr_nodes_buf[0] = JSExpr{ .expr = caller }; + args_buf[0] = JSExpr.Class.make( + macro.vm.global.ref(), + &expr_nodes_buf[0], + ); + for (args) |arg, i| { + expr_nodes_buf[i + 1] = JSExpr{ .expr = arg }; + args_buf[i + 1] = + JSExpr.Class.make( + macro.vm.global.ref(), + &expr_nodes_buf[i + 1], + ); + } + args_buf[args.len + 2] = null; + + var macro_callback = macro.vm.macros.get(id) orelse return caller; + var result = js.JSObjectCallAsFunctionReturnValue(macro.vm.global.ref(), macro_callback, null, args.len + 1, &args_buf); + var promise = JSC.JSPromise.resolvedPromise(macro.vm.global, result); + macro.vm.global.vm().drainMicrotasks(); + + if (promise.status(macro.vm.global.vm()) == .Rejected) { + macro.vm.defaultErrorHandler(promise.result(macro.vm.global.vm()), null); + return caller; + } + + const value = promise.result(macro.vm.global.vm()); + + return caller; + } + }; +}; + test "Binding.init" { var binding = Binding.alloc( std.heap.page_allocator, |