const std = @import("std"); const Allocator = std.mem.Allocator; const interp_mod = @import("interp.zig"); const Value = interp_mod.Value; const Interpreter = interp_mod.Interpreter; // ── BuildConfig ───────────────────────────────────────────────────────── // Mutable build configuration accumulated by #run blocks via #compiler methods. pub const BuildConfig = struct { link_flags: std.ArrayList([]const u8) = .empty, frameworks: std.ArrayList([]const u8) = .empty, output_path: ?[]const u8 = null, wasm_shell_path: ?[]const u8 = null, pub fn deinit(self: *BuildConfig, alloc: Allocator) void { self.link_flags.deinit(alloc); self.frameworks.deinit(alloc); } }; // ── Hook system ───────────────────────────────────────────────────────── pub const HookError = error{ CannotEvalComptime, TypeError, }; /// Hook function signature. Receives the interpreter (for heap/string access), /// resolved argument values, and the mutable build config. pub const HookFn = *const fn ( interp: *const Interpreter, args: []const Value, bc: *BuildConfig, alloc: Allocator, ) HookError!Value; pub const Registry = struct { hooks: std.StringHashMap(HookFn), pub fn init(alloc: Allocator) Registry { return .{ .hooks = std.StringHashMap(HookFn).init(alloc) }; } pub fn deinit(self: *Registry) void { self.hooks.deinit(); } pub fn get(self: *const Registry, name: []const u8) ?HookFn { return self.hooks.get(name); } /// Register all built-in compiler hooks. pub fn registerDefaults(self: *Registry) void { self.hooks.put("build_options", &hookBuildOptions) catch {}; self.hooks.put("BuildOptions.add_link_flag", &hookAddLinkFlag) catch {}; self.hooks.put("BuildOptions.add_framework", &hookAddFramework) catch {}; self.hooks.put("BuildOptions.set_output_path", &hookSetOutputPath) catch {}; self.hooks.put("BuildOptions.set_wasm_shell", &hookSetWasmShell) catch {}; } }; // ── build_options() hook ──────────────────────────────────────────────── fn hookBuildOptions( _: *const Interpreter, _: []const Value, _: *BuildConfig, _: Allocator, ) HookError!Value { // build_options() returns a sentinel value; the real work happens // when methods like add_link_flag/set_output_path are called on it. return .void_val; } // ── BuildOptions hooks ────────────────────────────────────────────────── fn hookAddLinkFlag( interp: *const Interpreter, args: []const Value, bc: *BuildConfig, alloc: Allocator, ) HookError!Value { // args: [self (BuildOptions value), flag_string] if (args.len < 2) return .void_val; const str_val = args[1]; if (str_val.asString(interp)) |s| { bc.link_flags.append(alloc, alloc.dupe(u8, s) catch return error.CannotEvalComptime) catch return error.CannotEvalComptime; } return .void_val; } fn hookAddFramework( interp: *const Interpreter, args: []const Value, bc: *BuildConfig, alloc: Allocator, ) HookError!Value { // args: [self (BuildOptions value), framework_name] if (args.len < 2) return .void_val; const str_val = args[1]; if (str_val.asString(interp)) |s| { bc.frameworks.append(alloc, alloc.dupe(u8, s) catch return error.CannotEvalComptime) catch return error.CannotEvalComptime; } return .void_val; } fn hookSetOutputPath( interp: *const Interpreter, args: []const Value, bc: *BuildConfig, alloc: Allocator, ) HookError!Value { // args: [self (BuildOptions value), path_string] if (args.len < 2) return .void_val; const str_val = args[1]; if (str_val.asString(interp)) |s| { bc.output_path = alloc.dupe(u8, s) catch return error.CannotEvalComptime; } return .void_val; } fn hookSetWasmShell( interp: *const Interpreter, args: []const Value, bc: *BuildConfig, alloc: Allocator, ) HookError!Value { // args: [self (BuildOptions value), path_string] if (args.len < 2) return .void_val; const str_val = args[1]; if (str_val.asString(interp)) |s| { bc.wasm_shell_path = alloc.dupe(u8, s) catch return error.CannotEvalComptime; } return .void_val; }