diff --git a/build.zig b/build.zig index cb95f2ab..39f8333f 100644 --- a/build.zig +++ b/build.zig @@ -10,18 +10,18 @@ pub fn build(b: *std.Build) void { .root_source_file = b.path("src/lib/libansi.zig"), }); - const wren = b.addModule("wren", .{ + const fiberscript_mod = b.addModule("fiberscript", .{ .target = target, .optimize = optimize, - .root_source_file = b.path("src/fiberscript/vm.zig"), + .root_source_file = b.path("src/fiberscript.zig"), }); - wren.addImport("ansi", ansi); + fiberscript_mod.addImport("ansi", ansi); - wren.addIncludePath(b.path("deps/wren/src/include")); - wren.addIncludePath(b.path("deps/wren/src/vm")); - wren.addIncludePath(b.path("deps/wren/src/optional")); - wren.addCSourceFiles(.{ + fiberscript_mod.addIncludePath(b.path("deps/wren/src/include")); + fiberscript_mod.addIncludePath(b.path("deps/wren/src/vm")); + fiberscript_mod.addIncludePath(b.path("deps/wren/src/optional")); + fiberscript_mod.addCSourceFiles(.{ .files = &.{ "deps/wren/src/vm/wren_compiler.c", "deps/wren/src/vm/wren_core.c", @@ -43,15 +43,7 @@ pub fn build(b: *std.Build) void { "-DDEBUG", }, }); - wren.addCMacro("abort", "zig_abort"); - - const libwren = b.addLibrary(.{ - .linkage = .static, - .name = "wren", - .root_module = wren, - }); - - libwren.linkLibC(); + fiberscript_mod.addCMacro("abort", "zig_abort"); // Create WASM-specific Wren library const libwren_wasm = b.addStaticLibrary(.{ @@ -98,6 +90,7 @@ pub fn build(b: *std.Build) void { }); xtc.addImport("ansi", ansi); + xtc.addImport("fiberscript", fiberscript_mod); // xtc.addImport("code_point", zg.module("code_point")); xtc.addImport("Graphemes", zg.module("Graphemes")); @@ -110,7 +103,7 @@ pub fn build(b: *std.Build) void { }); exe.linkSystemLibrary("m"); - exe.linkLibrary(libwren); + exe.linkLibC(); b.installArtifact(exe); @@ -120,7 +113,7 @@ pub fn build(b: *std.Build) void { }); exe_check.linkSystemLibrary("m"); - exe_check.linkLibrary(libwren); + exe_check.linkLibC(); const check_step = b.step("check", "Check the xtc executable"); check_step.dependOn(&exe_check.step); @@ -134,7 +127,7 @@ pub fn build(b: *std.Build) void { }, }); - unit_tests.linkLibrary(libwren); + unit_tests.linkLibC(); b.installArtifact(unit_tests); diff --git a/src/fiberscript.zig b/src/fiberscript.zig new file mode 100644 index 00000000..93bbe0c4 --- /dev/null +++ b/src/fiberscript.zig @@ -0,0 +1,5 @@ +pub usingnamespace @import("fiberscript/vm.zig"); + +pub const syscalls = @import("fiberscript/syscalls.zig"); +pub const allocator = @import("fiberscript/allocator.zig"); +pub const TrackingAllocator = @import("fiberscript/tracking_allocator.zig"); diff --git a/src/fiberscript/allocator.zig b/src/fiberscript/allocator.zig index 277f2aa7..017f4a6b 100644 --- a/src/fiberscript/allocator.zig +++ b/src/fiberscript/allocator.zig @@ -1,6 +1,6 @@ const std = @import("std"); const c = @import("wren.zig"); -const TrackingAllocator = @import("../lib/TrackingAllocator.zig"); +const TrackingAllocator = @import("tracking_allocator.zig"); /// Wren allocator wrapper that integrates with Zig's allocator system. /// diff --git a/src/fiberscript/syscalls.zig b/src/fiberscript/syscalls.zig index 10df0baa..cc3a6a74 100644 --- a/src/fiberscript/syscalls.zig +++ b/src/fiberscript/syscalls.zig @@ -19,7 +19,7 @@ pub fn RequestUnion(comptime Syscalls: type) type { const value_type = @TypeOf(value); if (@typeInfo(value_type) == .@"fn") { const fn_info = @typeInfo(value_type).@"fn"; - const PayloadType = fn_info.params[1].type.?; + const PayloadType = fn_info.params[2].type.?; union_fields[idx] = .{ .name = decl.name, .type = PayloadType, @@ -112,7 +112,7 @@ pub fn generateSlotParser(comptime Request: type, comptime Syscalls: type) type const value = @field(Syscalls, decl.name); const value_type = @TypeOf(value); if (@typeInfo(value_type) == .@"fn" and std.mem.eql(u8, operation, decl.name)) { - const PayloadType = @typeInfo(value_type).@"fn".params[1].type.?; + const PayloadType = @typeInfo(value_type).@"fn".params[2].type.?; const payload_fields = std.meta.fields(PayloadType); var payload: PayloadType = undefined; inline for (payload_fields) |pf| { @@ -127,12 +127,17 @@ pub fn generateSlotParser(comptime Request: type, comptime Syscalls: type) type } /// Generate a trampoline dispatcher that bridges Wren fiber yields to syscall implementations. -pub fn generateTrampoline(comptime Syscalls: type, comptime Context: type) type { +pub fn generateTrampoline( + comptime Syscalls: type, + comptime Engine: type, + comptime Context: type, +) type { return struct { const RequestType = RequestUnion(Syscalls); const ResultType = ResultUnion(Syscalls); const DispatchResult = SyscallResult(Syscalls); + engine: *Engine, context: *Context, /// Process a single request from a yielding fiber @@ -147,13 +152,13 @@ pub fn generateTrampoline(comptime Syscalls: type, comptime Context: type) type const fn_info = @typeInfo(value_type).@"fn"; const ReturnType = fn_info.return_type.?; if (ReturnType == Pending) { - try value(self.context, payload); + try value(self.engine, self.context, payload); return .{ .pending = {} }; } else if (ReturnType == void) { - try value(self.context, payload); + try value(self.engine, self.context, payload); return .{ .immediate = @unionInit(ResultType, decl.name, {}) }; } else { - const val = try value(self.context, payload); + const val = try value(self.engine, self.context, payload); return .{ .immediate = @unionInit(ResultType, decl.name, val) }; } } @@ -170,6 +175,8 @@ pub fn generateTrampoline(comptime Syscalls: type, comptime Context: type) type const testing = std.testing; +const TestEngine = struct {}; + const TestContext = struct { output_buffer: std.ArrayList(u8), @@ -183,23 +190,26 @@ const TestContext = struct { }; const TestSyscalls = struct { - pub fn print(context: *TestContext, payload: struct { message: []const u8 }) anyerror![]const u8 { + pub fn print(engine: *TestEngine, context: *TestContext, payload: struct { message: []const u8 }) anyerror![]const u8 { + _ = engine; try context.output_buffer.appendSlice(payload.message); return payload.message; } - pub fn sleep(context: *TestContext, payload: struct { duration_ms: u64 }) anyerror!void { + pub fn sleep(engine: *TestEngine, context: *TestContext, payload: struct { duration_ms: u64 }) anyerror!void { + _ = engine; _ = context; _ = payload; } }; test "can generate trampoline dispatcher" { - const Trampoline = generateTrampoline(TestSyscalls, TestContext); + const Trampoline = generateTrampoline(TestSyscalls, TestEngine, TestContext); + var engine = TestEngine{}; var context = TestContext.init(testing.allocator); defer context.deinit(); - var trampoline = Trampoline{ .context = &context }; + var trampoline = Trampoline{ .engine = &engine, .context = &context }; const request = Trampoline.RequestType{ .print = .{ .message = "hello" } }; const result = try trampoline.dispatch(request); switch (result) { diff --git a/src/lib/TrackingAllocator.zig b/src/fiberscript/tracking_allocator.zig similarity index 100% rename from src/lib/TrackingAllocator.zig rename to src/fiberscript/tracking_allocator.zig diff --git a/src/fiberscript/vm.zig b/src/fiberscript/vm.zig index dc4b3386..844173e8 100644 --- a/src/fiberscript/vm.zig +++ b/src/fiberscript/vm.zig @@ -5,105 +5,41 @@ const ErrorHandler = @import("error_handler.zig").ErrorHandler; const slots_api = @import("slots.zig"); const OutputHandler = @import("output.zig").OutputHandler; const syscalls = @import("syscalls.zig"); -const dom = @import("../dom.zig"); -const TrackingAllocator = @import("../lib/TrackingAllocator.zig"); +const TrackingAllocator = @import("tracking_allocator.zig"); const ansi = @import("ansi"); const tree = ansi.nest; -pub fn Syscalls(comptime Self: type) type { - return struct { - pub fn createElement(self: *Self, args: struct { style: []const u8 }) anyerror!dom.DomNodeId { - return self.document.addElement(args.style); - } - - pub fn updateText(self: *Self, args: struct { nodeId: u32, text: []const u8 }) anyerror!void { - try self.document.updateText(args.nodeId, args.text); - } - - pub fn updateClass(self: *Self, args: struct { nodeId: u32, className: []const u8 }) anyerror!void { - try self.document.updateClass(args.nodeId, args.className); - } - - pub fn appendChild(self: *Self, args: struct { parentId: u32, childId: u32 }) anyerror!void { - try self.document.appendChild(args.parentId, args.childId); - } - - pub fn removeChild(self: *Self, args: struct { parentId: u32, childId: u32 }) anyerror!void { - try self.document.removeChild(args.parentId, args.childId); - } - - pub fn requestRender(self: *Self, args: struct {}) anyerror!void { - _ = self; - _ = args; - @panic("requestRender not implemented"); - } - - pub fn clearScreen(self: *Self, args: struct {}) anyerror!void { - _ = self; - _ = args; - @panic("clearScreen not implemented"); - } - - pub fn requestAnimationFrame(self: *Self, args: struct {}) anyerror!void { - _ = self; - _ = args; - @panic("requestAnimationFrame not implemented"); - } - - pub fn setTimeout(self: *Self, args: struct { delayMs: f64 }) anyerror!void { - _ = self; - _ = args; - @panic("setTimeout not implemented"); - } - - pub fn addEventListener(self: *Self, args: struct { eventType: []const u8 }) anyerror!void { - _ = self; - _ = args; - @panic("addEventListener not implemented"); - } - - pub fn getViewportSize(self: *Self, args: struct {}) anyerror!void { - _ = self; - _ = args; - @panic("getViewportSize not implemented"); - } - - pub fn setViewportSize(self: *Self, args: struct { width: u32, height: u32 }) anyerror!void { - _ = self; - _ = args; - @panic("setViewportSize not implemented"); - } - }; -} - pub const Configuration = struct { - Syscalls: fn (comptime Self: type) type = Syscalls, + Syscalls: fn (comptime EngineType: type, comptime Context: type) type, + Context: type, }; pub const ErrorReport = ErrorHandler.ErrorReport; pub const StackTraceLine = ErrorHandler.StackTraceLine; -pub fn Engine(configuration: Configuration) type { +pub fn Engine(comptime configuration: Configuration) type { return struct { allocator: std.mem.Allocator, output_handler: OutputHandler, error_handler: ErrorHandler, fiber_queue: std.ArrayList(*c.Handle), trampoliner: Trampoline, - document: *dom.Dom, + syscall_context: *Context, vm: *c.VM, const Self = @This(); - const SyscallsType = configuration.Syscalls(Self); + const Context = configuration.Context; + const SyscallsType = configuration.Syscalls(Self, Context); const Request = syscalls.RequestUnion(SyscallsType); - const Trampoline = syscalls.generateTrampoline(SyscallsType, Self); + const Trampoline = syscalls.generateTrampoline(SyscallsType, Self, Context); const SlotParser = syscalls.generateSlotParser(Request, SyscallsType); pub const Options = struct { output_buffer_size: usize = 1024 * 32, error_buffer_size: usize = 1024 * 32, + syscall_context: *Context, }; pub fn init(base_allocator: std.mem.Allocator, options: Options) !*Self { @@ -133,7 +69,8 @@ pub fn Engine(configuration: Configuration) type { self.fiber_queue = std.ArrayList(*c.Handle).init(allocator); - self.trampoliner = Trampoline{ .context = self }; + self.syscall_context = options.syscall_context; + self.trampoliner = Trampoline{ .engine = self, .context = self.syscall_context }; var vmconf = c.Configuration{}; c.wrenInitConfiguration(&vmconf); @@ -372,10 +309,19 @@ pub fn Engine(configuration: Configuration) type { }; } +fn NoSyscalls(comptime EngineType: type, comptime Context: type) type { + _ = EngineType; + _ = Context; + return struct {}; +} + +const TestContext = struct {}; + test "we can create and destroy a VM" { const allocator = std.testing.allocator; - - var vm = try Engine(.{}).init(allocator, .{}); + const EngineType = Engine(.{ .Syscalls = NoSyscalls, .Context = TestContext }); + var context = TestContext{}; + var vm = try EngineType.init(allocator, .{ .syscall_context = &context }); defer vm.deinit(); const output = try vm.takeOutput(allocator); @@ -387,8 +333,9 @@ test "we can create and destroy a VM" { test "we can run a simple script" { const allocator = std.testing.allocator; - - var engine = try Engine(.{}).init(allocator, .{}); + const EngineType = Engine(.{ .Syscalls = NoSyscalls, .Context = TestContext }); + var context = TestContext{}; + var engine = try EngineType.init(allocator, .{ .syscall_context = &context }); defer engine.deinit(); try engine.runTopLevel("foo", @@ -404,8 +351,9 @@ test "we can run a simple script" { test "we can call Core.spawn" { const allocator = std.testing.allocator; - - var engine = try Engine(.{}).init(allocator, .{}); + const EngineType = Engine(.{ .Syscalls = NoSyscalls, .Context = TestContext }); + var context = TestContext{}; + var engine = try EngineType.init(allocator, .{ .syscall_context = &context }); defer engine.deinit(); engine.runTopLevel("main", @@ -450,8 +398,9 @@ test "we can call Core.spawn" { test "slots API - simple method call" { const allocator = std.testing.allocator; - - var engine = try Engine(.{}).init(allocator, .{}); + const EngineType = Engine(.{ .Syscalls = NoSyscalls, .Context = TestContext }); + var context = TestContext{}; + var engine = try EngineType.init(allocator, .{ .syscall_context = &context }); defer engine.deinit(); try engine.runTopLevel("test", @@ -482,8 +431,9 @@ test "slots API - simple method call" { test "slots API - working with strings" { const allocator = std.testing.allocator; - - var engine = try Engine(.{}).init(allocator, .{}); + const EngineType = Engine(.{ .Syscalls = NoSyscalls, .Context = TestContext }); + var context = TestContext{}; + var engine = try EngineType.init(allocator, .{ .syscall_context = &context }); defer engine.deinit(); try engine.runTopLevel("test", @@ -507,8 +457,9 @@ test "slots API - working with strings" { test "slots API - list operations" { const allocator = std.testing.allocator; - - var engine = try Engine(.{}).init(allocator, .{}); + const EngineType = Engine(.{ .Syscalls = NoSyscalls, .Context = TestContext }); + var context = TestContext{}; + var engine = try EngineType.init(allocator, .{ .syscall_context = &context }); defer engine.deinit(); try engine.runTopLevel("test", diff --git a/src/main.zig b/src/main.zig index d92946ac..8129d6a5 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,9 +1,12 @@ const std = @import("std"); const ansi = @import("ansi"); -const vm = @import("fiberscript/vm.zig"); +const fiberscript = @import("fiberscript"); +const scripting = @import("scripting.zig"); +const dom = @import("dom.zig"); -const Engine = vm.Engine(.{}); +const Engine = fiberscript.Engine(.{ .Syscalls = scripting.documentSyscalls, .Context = scripting.SyscallContext }); +const SyscallContext = scripting.SyscallContext; pub const version = "0.5.0"; @@ -11,7 +14,7 @@ pub const panic = ansi.panic; comptime { _ = @import("test/flex.test.zig"); - _ = @import("fiberscript/vm.zig"); + _ = @import("fiberscript"); } pub fn main() !void { @@ -57,7 +60,11 @@ fn run_script(allocator: std.mem.Allocator, name: []const u8) !void { const script = try file.readToEndAlloc(allocator, 1024 * 1024); defer allocator.free(script); - const engine = try Engine.init(allocator, .{}); + var document = try dom.Dom.init(allocator); + defer document.deinit(); + var sc = SyscallContext{ .document = document }; + + const engine = try Engine.init(allocator, .{ .syscall_context = &sc }); defer engine.deinit(); try engine.runTopLevel(name, script); // TODO: croak... diff --git a/src/scripting.zig b/src/scripting.zig new file mode 100644 index 00000000..84c13ecc --- /dev/null +++ b/src/scripting.zig @@ -0,0 +1,83 @@ +const dom = @import("dom.zig"); + +pub const SyscallContext = struct { + document: *dom.Dom, +}; + +pub fn documentSyscalls(comptime EngineType: type, comptime Context: type) type { + return struct { + pub fn createElement(engine: *EngineType, context: *Context, args: struct { style: []const u8 }) anyerror!dom.DomNodeId { + _ = engine; + return context.document.addElement(args.style); + } + + pub fn updateText(engine: *EngineType, context: *Context, args: struct { nodeId: u32, text: []const u8 }) anyerror!void { + _ = engine; + try context.document.updateText(args.nodeId, args.text); + } + + pub fn updateClass(engine: *EngineType, context: *Context, args: struct { nodeId: u32, className: []const u8 }) anyerror!void { + _ = engine; + try context.document.updateClass(args.nodeId, args.className); + } + + pub fn appendChild(engine: *EngineType, context: *Context, args: struct { parentId: u32, childId: u32 }) anyerror!void { + _ = engine; + try context.document.appendChild(args.parentId, args.childId); + } + + pub fn removeChild(engine: *EngineType, context: *Context, args: struct { parentId: u32, childId: u32 }) anyerror!void { + _ = engine; + try context.document.removeChild(args.parentId, args.childId); + } + + pub fn requestRender(engine: *EngineType, context: *Context, args: struct {}) anyerror!void { + _ = engine; + _ = context; + _ = args; + @panic("requestRender not implemented"); + } + + pub fn clearScreen(engine: *EngineType, context: *Context, args: struct {}) anyerror!void { + _ = engine; + _ = context; + _ = args; + @panic("clearScreen not implemented"); + } + + pub fn requestAnimationFrame(engine: *EngineType, context: *Context, args: struct {}) anyerror!void { + _ = engine; + _ = context; + _ = args; + @panic("requestAnimationFrame not implemented"); + } + + pub fn setTimeout(engine: *EngineType, context: *Context, args: struct { delayMs: f64 }) anyerror!void { + _ = engine; + _ = context; + _ = args; + @panic("setTimeout not implemented"); + } + + pub fn addEventListener(engine: *EngineType, context: *Context, args: struct { eventType: []const u8 }) anyerror!void { + _ = engine; + _ = context; + _ = args; + @panic("addEventListener not implemented"); + } + + pub fn getViewportSize(engine: *EngineType, context: *Context, args: struct {}) anyerror!void { + _ = engine; + _ = context; + _ = args; + @panic("getViewportSize not implemented"); + } + + pub fn setViewportSize(engine: *EngineType, context: *Context, args: struct { width: u32, height: u32 }) anyerror!void { + _ = engine; + _ = context; + _ = args; + @panic("setViewportSize not implemented"); + } + }; +}