aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-09-24 15:33:02 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-09-24 15:33:02 -0700
commitbdfb5a91b14a129df1b1a5d46aae5c8ef2ea2fd8 (patch)
treea05e1fff6e3cfbdaaf8fbbf247d62f4a0be56ced
parent29b986684dd157183b258a2598df102e0e4c9055 (diff)
downloadbun-bdfb5a91b14a129df1b1a5d46aae5c8ef2ea2fd8.tar.gz
bun-bdfb5a91b14a129df1b1a5d46aae5c8ef2ea2fd8.tar.zst
bun-bdfb5a91b14a129df1b1a5d46aae5c8ef2ea2fd8.zip
macro
-rw-r--r--src/ast/ast.js106
-rw-r--r--src/bundler.zig78
-rw-r--r--src/js_ast.zig501
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,