From f349e66ae6a832fdc25eec508a90f44b59d24c96 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Wed, 15 Oct 2025 16:05:38 +0200 Subject: [PATCH 01/12] chore: zig 0.16.0-dev.728+87c18945c --- build.zig | 7 +++ src/Ast.zig | 9 +-- src/Codegen.zig | 12 ++-- src/FFI.zig | 24 +++---- src/GC.zig | 8 +-- src/Jit.zig | 119 +++++++++++++++++++---------------- src/Parser.zig | 101 ++++++++++++++--------------- src/Reporter.zig | 78 +++++++++++------------ src/behavior.zig | 10 +-- src/builtin/list.zig | 11 ++-- src/builtin/str.zig | 9 ++- src/buzz_api.zig | 6 +- src/io.zig | 45 +++++++------ src/lib/buzz_buffer.zig | 67 +++++++++++++------- src/lib/buzz_ffi.zig | 28 ++++++--- src/lib/buzz_io.zig | 52 ++++++++------- src/lib/buzz_os.zig | 99 ++++++++++++++++------------- src/lsp.zig | 47 ++++++++------ src/obj.zig | 16 ++--- src/renderer.zig | 61 +++++++++--------- src/repl.zig | 31 ++++----- src/tests/fmt.zig | 5 +- src/value.zig | 10 +-- src/vm.zig | 12 ++-- src/wasm_repl.zig | 18 +++--- src/writeable_array_list.zig | 47 -------------- 26 files changed, 490 insertions(+), 442 deletions(-) delete mode 100644 src/writeable_array_list.zig diff --git a/build.zig b/build.zig index 60cf6582..d97ace16 100644 --- a/build.zig +++ b/build.zig @@ -382,6 +382,11 @@ pub fn build(b: *Build) !void { }, ); + exe_check.root_module.addImport( + "clap", + clap.module("clap"), + ); + exe.root_module.sanitize_c = .off; if (behavior_exe) |bexe| bexe.root_module.sanitize_c = .off; if (!is_wasm) lsp_exe.?.root_module.sanitize_c = .off; @@ -659,6 +664,7 @@ pub fn buildPcre2(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin .{ .target = target, .optimize = optimize, + .sanitize_c = .off, }, ), }, @@ -782,6 +788,7 @@ pub fn buildLinenoise(b: *Build, target: Build.ResolvedTarget, optimize: std.bui .{ .target = target, .optimize = optimize, + .sanitize_c = .off, }, ), }, diff --git a/src/Ast.zig b/src/Ast.zig index cc9cf897..53af31d7 100644 --- a/src/Ast.zig +++ b/src/Ast.zig @@ -819,13 +819,14 @@ pub const Slice = struct { .String => string: { const elements = self.nodes.items(.components)[node].String; - var string = std.ArrayList(u8).empty; - const writer = &string.writer(gc.allocator); + var string = std.Io.Writer.Allocating.init(gc.allocator); + defer string.deinit(); + for (elements) |element| { - try (try self.toValue(element, gc)).toString(writer); + try (try self.toValue(element, gc)).toString(&string.writer); } - break :string (try gc.copyString(try string.toOwnedSlice(gc.allocator))).toValue(); + break :string (try gc.copyString(string.written())).toValue(); }, .Subscript => subscript: { const components = self.nodes.items(.components)[node].Subscript; diff --git a/src/Codegen.zig b/src/Codegen.zig index 08ba2893..10762622 100644 --- a/src/Codegen.zig +++ b/src/Codegen.zig @@ -24,6 +24,7 @@ pub const Error = error{ UnwrappedNull, OutOfBound, ReachedMaximumMemoryUsage, + WriteFailed, } || std.mem.Allocator.Error || std.fmt.BufPrintError; pub const Frame = struct { @@ -1246,10 +1247,11 @@ fn generateCall(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj } if (missing_arguments.count() > 0) { - var missing = std.ArrayList(u8).empty; - const missing_writer = missing.writer(self.gc.allocator); + var missing = std.Io.Writer.Allocating.init(self.gc.allocator); + defer missing.deinit(); + for (missing_arguments.keys(), 0..) |key, i| { - try missing_writer.print( + try missing.writer.print( "{s}{s}", .{ key, @@ -1260,7 +1262,7 @@ fn generateCall(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj }, ); } - defer missing.deinit(self.gc.allocator); + self.reporter.reportErrorFmt( .call_arguments, self.ast.tokens.get(locations[node]), @@ -1271,7 +1273,7 @@ fn generateCall(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj "s" else "", - missing.items, + missing.written(), }, ); } diff --git a/src/FFI.zig b/src/FFI.zig index bdff3803..67e2d391 100644 --- a/src/FFI.zig +++ b/src/FFI.zig @@ -212,15 +212,15 @@ pub fn parseTypeExpr(self: *Self, ztype: []const u8) !?*Zdef { return zdef; } - var full = std.ArrayList(u8).empty; - defer full.deinit(self.gc.allocator); + var full = std.Io.Writer.Allocating.init(self.gc.allocator); + defer full.deinit(); - full.writer(self.gc.allocator).print("const zig_type: {s};", .{ztype}) catch @panic("Out of memory"); + full.writer.print("const zig_type: {s};", .{ztype}) catch @panic("Out of memory"); const zdef = try self.parse( null, 0, - full.items, + full.written(), ); std.debug.assert(zdef == null or zdef.?.len == 1); @@ -527,10 +527,10 @@ fn unionContainer(self: *Self, name: []const u8, container: Ast.full.ContainerDe }, }; - var qualified_name = std.ArrayList(u8).empty; - defer qualified_name.deinit(self.gc.allocator); + var qualified_name = std.Io.Writer.Allocating.init(self.gc.allocator); + defer qualified_name.deinit(); - try qualified_name.writer(self.gc.allocator).print( + try qualified_name.writer.print( "{s}.{s}", .{ self.state.?.script, @@ -548,7 +548,7 @@ fn unionContainer(self: *Self, name: []const u8, container: Ast.full.ContainerDe .location = self.state.?.source, .name = try self.gc.copyString(name), // FIXME - .qualified_name = try self.gc.copyString(qualified_name.items), + .qualified_name = try self.gc.copyString(qualified_name.written()), .zig_type = zig_type, .buzz_type = buzz_fields, .fields = get_set_fields, @@ -918,16 +918,16 @@ fn fnProto(self: *Self, tag: Ast.Node.Tag, decl_index: Ast.Node.Index) anyerror! } fn reportZigError(self: *Self, err: Ast.Error) void { - var message = std.ArrayList(u8).empty; - defer message.deinit(self.gc.allocator); + var message = std.Io.Writer.Allocating.init(self.gc.allocator); + defer message.deinit(); - message.writer(self.gc.allocator).print("zdef could not be parsed: {}", .{err.tag}) catch unreachable; + message.writer.print("zdef could not be parsed: {}", .{err.tag}) catch unreachable; const location = self.state.?.buzz_ast.?.tokens.get(self.state.?.source); self.reporter.report( .zdef, location, location, - message.items, + message.written(), ); } diff --git a/src/GC.zig b/src/GC.zig index 52f64365..3364b590 100644 --- a/src/GC.zig +++ b/src/GC.zig @@ -944,10 +944,10 @@ pub const Debugger = struct { var items = std.ArrayList(Reporter.ReportItem).empty; defer items.deinit(self.allocator); - var message = std.ArrayList(u8).empty; - defer message.deinit(self.allocator); + var message = std.Io.Writer.Allocating.init(self.allocator); + defer message.deinit(); - message.writer(self.allocator).print( + message.writer.print( "Access to already collected {} {*}", .{ tracked.what, @@ -961,7 +961,7 @@ pub const Debugger = struct { .location = at.?, .end_location = at.?, .kind = .@"error", - .message = message.items, + .message = message.written(), }, ) catch unreachable; diff --git a/src/Jit.zig b/src/Jit.zig index cbdb0c1e..bc09a3a6 100644 --- a/src/Jit.zig +++ b/src/Jit.zig @@ -20,6 +20,7 @@ pub const Error = error{ UnwrappedNull, OutOfBound, ReachedMaximumMemoryUsage, + WriteFailed, } || std.mem.Allocator.Error || std.fmt.BufPrintError; const OptJump = struct { @@ -5140,16 +5141,16 @@ fn getQualifiedName(self: *Self, node: Ast.Node.Index, raw: bool) ![]const u8 { const function_type = function_def.function_type; const name = function_def.name.string; - var qualified_name = std.ArrayList(u8){}; + var qualified_name = std.Io.Writer.Allocating.init(self.vm.gc.allocator); - try qualified_name.appendSlice(self.vm.gc.allocator, name); + try qualified_name.writer.writeAll(name); // Main and script are not allowed to be compiled std.debug.assert(function_type != .ScriptEntryPoint and function_type != .Script); // Don't qualify extern functions if (function_type != .Extern) { - try qualified_name.writer(self.vm.gc.allocator).print( + try qualified_name.writer.print( ".{}.n{}", .{ components.id, @@ -5158,20 +5159,20 @@ fn getQualifiedName(self: *Self, node: Ast.Node.Index, raw: bool) ![]const u8 { ); } if (function_type != .Extern and raw) { - try qualified_name.appendSlice(self.vm.gc.allocator, ".raw"); + try qualified_name.writer.writeAll(".raw"); } - try qualified_name.append(self.vm.gc.allocator, 0); + try qualified_name.writer.writeByte(0); - return qualified_name.toOwnedSlice(self.vm.gc.allocator); + return qualified_name.toOwnedSlice(); }, .For, .ForEach, .While, => { - var qualified_name = std.ArrayList(u8){}; + var qualified_name = std.Io.Writer.Allocating.init(self.vm.gc.allocator); - try qualified_name.writer(self.vm.gc.allocator).print( + try qualified_name.writer.print( "{s}#{d}\x00", .{ @tagName(tag), @@ -5179,7 +5180,7 @@ fn getQualifiedName(self: *Self, node: Ast.Node.Index, raw: bool) ![]const u8 { }, ); - return qualified_name.toOwnedSlice(self.vm.gc.allocator); + return qualified_name.toOwnedSlice(); }, else => { @@ -5198,12 +5199,20 @@ fn getQualifiedName(self: *Self, node: Ast.Node.Index, raw: bool) ![]const u8 { } pub fn compileZdefContainer(self: *Self, ast: Ast.Slice, zdef_element: Ast.Zdef.ZdefElement) Error!void { - var wrapper_name = std.ArrayList(u8){}; - defer wrapper_name.deinit(self.vm.gc.allocator); + var wrapper_name = std.Io.Writer.Allocating.init(self.vm.gc.allocator); + defer wrapper_name.deinit(); - try wrapper_name.writer(self.vm.gc.allocator).print("zdef_{s}\x00", .{zdef_element.zdef.name}); + try wrapper_name.writer.print( + "zdef_{s}\x00", + .{ + zdef_element.zdef.name, + }, + ); - const module = m.MIR_new_module(self.ctx, @ptrCast(wrapper_name.items.ptr)); + const module = m.MIR_new_module( + self.ctx, + @ptrCast(wrapper_name.written().ptr), + ); defer m.MIR_finish_module(self.ctx); if (BuildOptions.jit_debug) { @@ -5477,10 +5486,10 @@ fn buildZigValueToBuzzValue(self: *Self, buzz_type: *o.ObjTypeDef, zig_type: Zig } pub fn compileZdef(self: *Self, buzz_ast: Ast.Slice, zdef: Ast.Zdef.ZdefElement) Error!*o.ObjNative { - var wrapper_name = std.ArrayList(u8){}; - defer wrapper_name.deinit(self.vm.gc.allocator); + var wrapper_name = std.Io.Writer.Allocating.init(self.vm.gc.allocator); + defer wrapper_name.deinit(); - try wrapper_name.writer(self.vm.gc.allocator).print("zdef_{s}\x00", .{zdef.zdef.name}); + try wrapper_name.writer.print("zdef_{s}\x00", .{zdef.zdef.name}); const dupped_symbol = self.vm.gc.allocator.dupeZ(u8, zdef.zdef.name) catch { self.vm.panic("Out of memory"); @@ -5488,7 +5497,7 @@ pub fn compileZdef(self: *Self, buzz_ast: Ast.Slice, zdef: Ast.Zdef.ZdefElement) }; defer self.vm.gc.allocator.free(dupped_symbol); - const module = m.MIR_new_module(self.ctx, @ptrCast(wrapper_name.items.ptr)); + const module = m.MIR_new_module(self.ctx, @ptrCast(wrapper_name.written().ptr)); defer m.MIR_finish_module(self.ctx); if (BuildOptions.jit_debug) { @@ -5516,7 +5525,7 @@ pub fn compileZdef(self: *Self, buzz_ast: Ast.Slice, zdef: Ast.Zdef.ZdefElement) // Build wrapper const wrapper_item = try self.buildZdefWrapper(zdef); - _ = m.MIR_new_export(self.ctx, @ptrCast(wrapper_name.items.ptr)); + _ = m.MIR_new_export(self.ctx, @ptrCast(wrapper_name.written().ptr)); if (BuildOptions.jit_debug) { self.outputModule(zdef.zdef.name, module); @@ -5607,15 +5616,15 @@ fn zigToMIRRegType(zig_type: ZigType) m.MIR_type_t { } fn buildZdefWrapper(self: *Self, zdef_element: Ast.Zdef.ZdefElement) Error!m.MIR_item_t { - var wrapper_name = std.ArrayList(u8){}; - defer wrapper_name.deinit(self.vm.gc.allocator); + var wrapper_name = std.Io.Writer.Allocating.init(self.vm.gc.allocator); + defer wrapper_name.deinit(); - try wrapper_name.writer(self.vm.gc.allocator).print("zdef_{s}\x00", .{zdef_element.zdef.name}); + try wrapper_name.writer.print("zdef_{s}\x00", .{zdef_element.zdef.name}); - var wrapper_protocol_name = std.ArrayList(u8){}; - defer wrapper_protocol_name.deinit(self.vm.gc.allocator); + var wrapper_protocol_name = std.Io.Writer.Allocating.init(self.vm.gc.allocator); + defer wrapper_protocol_name.deinit(); - try wrapper_protocol_name.writer(self.vm.gc.allocator).print("p_zdef_{s}\x00", .{zdef_element.zdef.name}); + try wrapper_protocol_name.writer.print("p_zdef_{s}\x00", .{zdef_element.zdef.name}); const dupped_symbol = self.vm.gc.allocator.dupeZ(u8, zdef_element.zdef.name) catch { self.vm.panic("Out of memory"); @@ -5631,7 +5640,7 @@ fn buildZdefWrapper(self: *Self, zdef_element: Ast.Zdef.ZdefElement) Error!m.MIR defer self.vm.gc.allocator.free(ctx_name); const function = m.MIR_new_func_arr( self.ctx, - @ptrCast(wrapper_name.items.ptr), + @ptrCast(wrapper_name.written().ptr), 1, &[_]m.MIR_type_t{m.MIR_T_U64}, 1, @@ -5731,7 +5740,7 @@ fn buildZdefWrapper(self: *Self, zdef_element: Ast.Zdef.ZdefElement) Error!m.MIR self.ctx, m.MIR_new_proto_arr( self.ctx, - @ptrCast(wrapper_protocol_name.items.ptr), + @ptrCast(wrapper_protocol_name.written().ptr), if (return_mir_type != null) 1 else 0, if (return_mir_type) |rmt| &[_]m.MIR_type_t{rmt} else null, arg_types.items.len, @@ -5823,10 +5832,10 @@ fn buildZdefUnionGetter( buzz_type: *o.ObjTypeDef, zig_type: *const ZigType, ) Error!m.MIR_item_t { - var getter_name = std.ArrayList(u8){}; - defer getter_name.deinit(self.vm.gc.allocator); + var getter_name = std.Io.Writer.Allocating.init(self.vm.gc.allocator); + defer getter_name.deinit(); - try getter_name.writer(self.vm.gc.allocator).print( + try getter_name.writer.print( "zdef_union_{s}_{s}_getter\x00", .{ union_name, @@ -5847,7 +5856,7 @@ fn buildZdefUnionGetter( defer self.vm.gc.allocator.free(data_name); const function = m.MIR_new_func_arr( self.ctx, - @ptrCast(getter_name.items.ptr), + @ptrCast(getter_name.written().ptr), 1, &[_]m.MIR_type_t{m.MIR_T_U64}, 2, @@ -5921,7 +5930,7 @@ fn buildZdefUnionGetter( m.MIR_finish_func(self.ctx); - _ = m.MIR_new_export(self.ctx, @ptrCast(getter_name.items.ptr)); + _ = m.MIR_new_export(self.ctx, @ptrCast(getter_name.written().ptr)); return function; } @@ -5933,10 +5942,10 @@ fn buildZdefUnionSetter( buzz_type: *o.ObjTypeDef, zig_type: *const ZigType, ) Error!m.MIR_item_t { - var setter_name = std.ArrayList(u8){}; - defer setter_name.deinit(self.vm.gc.allocator); + var setter_name = std.Io.Writer.Allocating.init(self.vm.gc.allocator); + defer setter_name.deinit(); - try setter_name.writer(self.vm.gc.allocator).print( + try setter_name.writer.print( "zdef_union_{s}_{s}_setter\x00", .{ union_name, @@ -5962,7 +5971,7 @@ fn buildZdefUnionSetter( defer self.vm.gc.allocator.free(new_value_name); const function = m.MIR_new_func_arr( self.ctx, - @ptrCast(setter_name.items.ptr), + @ptrCast(setter_name.written().ptr), 0, null, 3, @@ -6036,7 +6045,7 @@ fn buildZdefUnionSetter( m.MIR_finish_func(self.ctx); - _ = m.MIR_new_export(self.ctx, @ptrCast(setter_name.items.ptr)); + _ = m.MIR_new_export(self.ctx, @ptrCast(setter_name.written().ptr)); return function; } @@ -6049,10 +6058,10 @@ fn buildZdefContainerGetter( buzz_type: *o.ObjTypeDef, zig_type: *const ZigType, ) Error!m.MIR_item_t { - var getter_name = std.ArrayList(u8){}; - defer getter_name.deinit(self.vm.gc.allocator); + var getter_name = std.Io.Writer.Allocating.init(self.vm.gc.allocator); + defer getter_name.deinit(); - try getter_name.writer(self.vm.gc.allocator).print( + try getter_name.writer.print( "zdef_struct_{s}_{s}_getter\x00", .{ struct_name, @@ -6073,7 +6082,7 @@ fn buildZdefContainerGetter( defer self.vm.gc.allocator.free(data_name); const function = m.MIR_new_func_arr( self.ctx, - @ptrCast(getter_name.items.ptr), + @ptrCast(getter_name.written().ptr), 1, &[_]m.MIR_type_t{m.MIR_T_U64}, 2, @@ -6130,7 +6139,7 @@ fn buildZdefContainerGetter( m.MIR_finish_func(self.ctx); - _ = m.MIR_new_export(self.ctx, @ptrCast(getter_name.items.ptr)); + _ = m.MIR_new_export(self.ctx, @ptrCast(getter_name.written().ptr)); return function; } @@ -6143,10 +6152,10 @@ fn buildZdefContainerSetter( buzz_type: *o.ObjTypeDef, zig_type: *const ZigType, ) Error!m.MIR_item_t { - var setter_name = std.ArrayList(u8){}; - defer setter_name.deinit(self.vm.gc.allocator); + var setter_name = std.Io.Writer.Allocating.init(self.vm.gc.allocator); + defer setter_name.deinit(); - try setter_name.writer(self.vm.gc.allocator).print( + try setter_name.writer.print( "zdef_struct_{s}_{s}_setter\x00", .{ struct_name, @@ -6172,7 +6181,7 @@ fn buildZdefContainerSetter( defer self.vm.gc.allocator.free(new_value_name); const function = m.MIR_new_func_arr( self.ctx, - @ptrCast(setter_name.items.ptr), + @ptrCast(setter_name.written().ptr), 0, null, 3, @@ -6225,7 +6234,7 @@ fn buildZdefContainerSetter( m.MIR_finish_func(self.ctx); - _ = m.MIR_new_export(self.ctx, @ptrCast(setter_name.items.ptr)); + _ = m.MIR_new_export(self.ctx, @ptrCast(setter_name.written().ptr)); return function; } @@ -7116,21 +7125,21 @@ inline fn BEND(self: *Self, from: m.MIR_reg_t) void { } fn REG(self: *Self, name: [*:0]const u8, reg_type: m.MIR_type_t) !m.MIR_reg_t { - var actual_name = std.ArrayList(u8){}; - defer actual_name.deinit(self.vm.gc.allocator); + var actual_name = std.Io.Writer.Allocating.init(self.vm.gc.allocator); + defer actual_name.deinit(); const count = self.state.?.registers.get(name) orelse 0; if (count > 0) { - try actual_name.writer(self.vm.gc.allocator).print("{s}{d}\u{0}", .{ name, count + 1 }); + try actual_name.writer.print("{s}{d}\u{0}", .{ name, count + 1 }); } else { - try actual_name.writer(self.vm.gc.allocator).print("{s}\u{0}", .{name}); + try actual_name.writer.print("{s}\u{0}", .{name}); } const reg = m.MIR_new_func_reg( self.ctx, self.state.?.function.?.u.func, reg_type, - @ptrCast(actual_name.items.ptr), + @ptrCast(actual_name.written().ptr), ); try self.state.?.registers.put( @@ -7144,10 +7153,10 @@ fn REG(self: *Self, name: [*:0]const u8, reg_type: m.MIR_type_t) !m.MIR_reg_t { fn outputModule(self: *Self, name: []const u8, module: m.MIR_module_t) void { // Output MIR code to .mir file - var debug_path = std.ArrayList(u8){}; - defer debug_path.deinit(self.vm.gc.allocator); + var debug_path = std.Io.Writer.Allocating.init(self.vm.gc.allocator); + defer debug_path.deinit(); - debug_path.writer(self.vm.gc.allocator).print( + debug_path.writer.print( "./dist/gen/{s}.mod.mir\x00", .{ name, @@ -7155,7 +7164,7 @@ fn outputModule(self: *Self, name: []const u8, module: m.MIR_module_t) void { ) catch unreachable; const debug_file = std.c.fopen( - @ptrCast(debug_path.items.ptr), + @ptrCast(debug_path.written().ptr), "w", ).?; defer _ = std.c.fclose(debug_file); diff --git a/src/Parser.zig b/src/Parser.zig index 19b53d79..1bc4eb69 100644 --- a/src/Parser.zig +++ b/src/Parser.zig @@ -446,6 +446,7 @@ pub const Error = error{ UnwrappedNull, OutOfBound, ReachedMaximumMemoryUsage, + WriteFailed, } || std.mem.Allocator.Error || std.fmt.BufPrintError || CompileError; const ParseFn = *const fn (*Self, bool) Error!Ast.Node.Index; @@ -3474,14 +3475,14 @@ fn parseObjType(self: *Self, generic_types: ?std.AutoArrayHashMapUnmanaged(*obj. const qualifier = try std.mem.replaceOwned(u8, self.gc.allocator, self.script_name, "/", "."); defer self.gc.allocator.free(qualifier); - var qualified_name = std.ArrayList(u8).empty; - defer qualified_name.deinit(self.gc.allocator); - try qualified_name.writer(self.gc.allocator).print("{s}.anonymous", .{qualifier}); + var qualified_name = std.Io.Writer.Allocating.init(self.gc.allocator); + defer qualified_name.deinit(); + try qualified_name.writer.print("{s}.anonymous", .{qualifier}); const object_def = obj.ObjObject.ObjectDef.init( start_location, try self.gc.copyString("anonymous"), - try self.gc.copyString(qualified_name.items), + try self.gc.copyString(qualified_name.written()), true, ); @@ -4576,16 +4577,22 @@ fn anonymousObjectInit(self: *Self, _: bool) Error!Ast.Node.Index { const start_location = self.current_token.? - 1; try self.consume(.LeftBrace, "Expected `{` after `.`"); - const qualifier = try std.mem.replaceOwned(u8, self.gc.allocator, self.script_name, "/", "."); + const qualifier = try std.mem.replaceOwned( + u8, + self.gc.allocator, + self.script_name, + "/", + ".", + ); defer self.gc.allocator.free(qualifier); - var qualified_name = std.ArrayList(u8).empty; - defer qualified_name.deinit(self.gc.allocator); - try qualified_name.writer(self.gc.allocator).print("{s}.anonymous", .{qualifier}); + var qualified_name = std.Io.Writer.Allocating.init(self.gc.allocator); + defer qualified_name.deinit(); + try qualified_name.writer.print("{s}.anonymous", .{qualifier}); const object_def = obj.ObjObject.ObjectDef.init( start_location, try self.gc.copyString("anonymous"), - try self.gc.copyString(qualified_name.items), + try self.gc.copyString(qualified_name.written()), true, ); @@ -7343,9 +7350,9 @@ fn objectDeclaration(self: *Self) Error!Ast.Node.Index { // Qualified name to avoid cross script collision const qualifier = try std.mem.replaceOwned(u8, self.gc.allocator, self.script_name, "/", "\\"); defer self.gc.allocator.free(qualifier); - var qualified_object_name = std.ArrayList(u8).empty; - defer qualified_object_name.deinit(self.gc.allocator); - try qualified_object_name.writer(self.gc.allocator).print( + var qualified_object_name = std.Io.Writer.Allocating.init(self.gc.allocator); + defer qualified_object_name.deinit(); + try qualified_object_name.writer.print( "{s}.{s}", .{ qualifier, @@ -7360,7 +7367,7 @@ fn objectDeclaration(self: *Self) Error!Ast.Node.Index { var object_def = obj.ObjObject.ObjectDef.init( object_name_token, try self.gc.copyString(object_name_lexeme), - try self.gc.copyString(qualified_object_name.items), + try self.gc.copyString(qualified_object_name.written()), false, ); @@ -7828,9 +7835,9 @@ fn protocolDeclaration(self: *Self) Error!Ast.Node.Index { // Qualified name to avoid cross script collision const qualifier = try std.mem.replaceOwned(u8, self.gc.allocator, self.script_name, "/", "\\"); defer self.gc.allocator.free(qualifier); - var qualified_protocol_name = std.ArrayList(u8).empty; - defer qualified_protocol_name.deinit(self.gc.allocator); - try qualified_protocol_name.writer(self.gc.allocator).print( + var qualified_protocol_name = std.Io.Writer.Allocating.init(self.gc.allocator); + defer qualified_protocol_name.deinit(); + try qualified_protocol_name.writer.print( "{s}.{s}", .{ qualifier, @@ -7849,7 +7856,7 @@ fn protocolDeclaration(self: *Self) Error!Ast.Node.Index { .Protocol = obj.ObjObject.ProtocolDef.init( protocol_name, try self.gc.copyString(self.ast.tokens.items(.lexeme)[protocol_name]), - try self.gc.copyString(qualified_protocol_name.items), + try self.gc.copyString(qualified_protocol_name.written()), ), }, }; @@ -8002,9 +8009,9 @@ fn enumDeclaration(self: *Self) Error!Ast.Node.Index { // Qualified name to avoid cross script collision const qualifier = try std.mem.replaceOwned(u8, self.gc.allocator, self.script_name, "/", "."); defer self.gc.allocator.free(qualifier); - var qualified_name = std.ArrayList(u8).empty; - defer qualified_name.deinit(self.gc.allocator); - try qualified_name.writer(self.gc.allocator).print( + var qualified_name = std.Io.Writer.Allocating.init(self.gc.allocator); + defer qualified_name.deinit(); + try qualified_name.writer.print( "{s}.{s}", .{ qualifier, enum_name_lexeme }, ); @@ -8012,7 +8019,7 @@ fn enumDeclaration(self: *Self) Error!Ast.Node.Index { const enum_def = obj.ObjEnum.EnumDef{ .name = try self.gc.copyString(enum_name_lexeme), .location = enum_name, - .qualified_name = try self.gc.copyString(qualified_name.items), + .qualified_name = try self.gc.copyString(qualified_name.written()), .enum_type = enum_case_type, .cases = undefined, .cases_locations = undefined, @@ -8539,9 +8546,9 @@ fn searchLibPaths(self: *Self, file_name: []const u8) ![][]const u8 { } for (user_library_paths orelse &[_][]const u8{}) |path| { - var filled = std.ArrayList(u8).empty; + var filled = std.Io.Writer.Allocating.init(self.gc.allocator); - try filled.writer(self.gc.allocator).print( + try filled.writer.print( "{s}{s}{s}.{s}", .{ path, @@ -8561,11 +8568,11 @@ fn searchLibPaths(self: *Self, file_name: []const u8) ![][]const u8 { }, ); - try paths.append(self.gc.allocator, try filled.toOwnedSlice(self.gc.allocator)); + try paths.append(self.gc.allocator, try filled.toOwnedSlice()); - var prefixed_filled = std.ArrayList(u8).empty; + var prefixed_filled = std.Io.Writer.Allocating.init(self.gc.allocator); - try prefixed_filled.writer(self.gc.allocator).print( + try prefixed_filled.writer.print( "{s}{s}lib{s}.{s}", .{ path, @@ -8587,7 +8594,7 @@ fn searchLibPaths(self: *Self, file_name: []const u8) ![][]const u8 { try paths.append( self.gc.allocator, - try prefixed_filled.toOwnedSlice(self.gc.allocator), + try prefixed_filled.toOwnedSlice(), ); } @@ -8628,9 +8635,9 @@ fn searchZdefLibPaths(self: *Self, file_name: []const u8) ![][]const u8 { } for (Self.user_library_paths orelse &[_][]const u8{}) |path| { - var filled = std.ArrayList(u8).empty; + var filled = std.Io.Writer.Allocating.init(self.gc.allocator); - try filled.writer(self.gc.allocator).print( + try filled.writer.print( "{s}{s}{s}.{s}", .{ path, @@ -8650,11 +8657,11 @@ fn searchZdefLibPaths(self: *Self, file_name: []const u8) ![][]const u8 { }, ); - try paths.append(self.gc.allocator, try filled.toOwnedSlice(self.gc.allocator)); + try paths.append(self.gc.allocator, try filled.toOwnedSlice()); - var prefixed_filled = std.ArrayList(u8).empty; + var prefixed_filled = std.Io.Writer.Allocating.init(self.gc.allocator); - try prefixed_filled.writer(self.gc.allocator).print( + try prefixed_filled.writer.print( "{s}{s}lib{s}.{s}", .{ path, @@ -8676,7 +8683,7 @@ fn searchZdefLibPaths(self: *Self, file_name: []const u8) ![][]const u8 { try paths.append( self.gc.allocator, - try prefixed_filled.toOwnedSlice(self.gc.allocator), + try prefixed_filled.toOwnedSlice(), ); } @@ -8783,12 +8790,10 @@ fn readScript(self: *Self, file_name: []const u8) !?[2][]const u8 { } if (file == null) { - var search_report = std.ArrayList(u8).empty; - defer search_report.deinit(self.gc.allocator); - var writer = search_report.writer(self.gc.allocator); - + var search_report = std.Io.Writer.Allocating.init(self.gc.allocator); + defer search_report.deinit(); for (paths) |path| { - try writer.print(" no file `{s}`\n", .{path}); + try search_report.writer.print(" no file `{s}`\n", .{path}); } const location = self.ast.tokens.get(self.current_token.? - 1); @@ -8799,7 +8804,7 @@ fn readScript(self: *Self, file_name: []const u8) !?[2][]const u8 { "buzz script `{s}` not found:\n{s}", .{ file_name, - search_report.items, + search_report.written(), }, ); @@ -9104,12 +9109,11 @@ fn importLibSymbol( ); } - var search_report = std.ArrayList(u8).empty; - defer search_report.deinit(self.gc.allocator); - var writer = search_report.writer(self.gc.allocator); + var search_report = std.Io.Writer.Allocating.init(self.gc.allocator); + defer search_report.deinit(); for (paths) |path| { - try writer.print(" no file `{s}`\n", .{path}); + try search_report.writer.print(" no file `{s}`\n", .{path}); } self.reporter.reportErrorFmt( @@ -9123,7 +9127,7 @@ fn importLibSymbol( std.mem.sliceTo(dlerror(), 0) else "", - search_report.items, + search_report.written(), }, ); @@ -9347,12 +9351,11 @@ fn zdefStatement(self: *Self) Error!Ast.Node.Index { fn_ptr = opaque_symbol_method; } else { - var search_report = std.ArrayList(u8).empty; - defer search_report.deinit(self.gc.allocator); - var writer = search_report.writer(self.gc.allocator); + var search_report = std.Io.Writer.Allocating.init(self.gc.allocator); + defer search_report.deinit(); for (paths) |path| { - try writer.print(" no file `{s}`\n", .{path}); + try search_report.writer.print(" no file `{s}`\n", .{path}); } const location = self.ast.tokens.get(lib_name); @@ -9367,7 +9370,7 @@ fn zdefStatement(self: *Self) Error!Ast.Node.Index { std.mem.sliceTo(dlerror(), 0) else "", - search_report.items, + search_report.written(), }, ); } diff --git a/src/Reporter.zig b/src/Reporter.zig index cbde0dba..428d5ea1 100644 --- a/src/Reporter.zig +++ b/src/Reporter.zig @@ -643,15 +643,14 @@ pub fn warnAt(self: *Self, error_type: Error, location: Token, end_location: Tok pub fn reportErrorFmt(self: *Self, error_type: Error, location: Token, end_location: Token, comptime fmt: []const u8, args: anytype) void { @branchHint(.cold); - var message = std.ArrayList(u8).empty; + var message = std.Io.Writer.Allocating.init(self.allocator); defer { if (!self.collect) { - message.deinit(self.allocator); + message.deinit(); } } - var writer = message.writer(self.allocator); - writer.print(fmt, args) catch @panic("Unable to report error"); + message.writer.print(fmt, args) catch @panic("Unable to report error"); if (self.panic_mode) { return; @@ -661,25 +660,24 @@ pub fn reportErrorFmt(self: *Self, error_type: Error, location: Token, end_locat error_type, location, end_location, - message.toOwnedSlice(self.allocator) catch @panic("Untable to report error"), + message.written(), ); } pub fn warnFmt(self: *Self, error_type: Error, location: Token, end_location: Token, comptime fmt: []const u8, args: anytype) void { - var message = std.ArrayList(u8).empty; + var message = std.Io.Writer.Allocating.init(self.allocator); defer { if (!self.collect) { - message.deinit(self.allocator); + message.deinit(); } } - var writer = message.writer(self.allocator); - writer.print(fmt, args) catch @panic("Unable to report error"); + message.writer.print(fmt, args) catch @panic("Unable to report error"); self.warn( error_type, location, end_location, - message.toOwnedSlice(self.allocator) catch @panic("Unable to report error"), + message.written(), ); } @@ -696,22 +694,21 @@ pub fn reportWithOrigin( ) void { @branchHint(.cold); - var message = std.ArrayList(u8).empty; + var message = std.Io.Writer.Allocating.init(self.allocator); defer { if (!self.collect) { - message.deinit(self.allocator); + message.deinit(); } } - var writer = message.writer(self.allocator); - writer.print(fmt, args) catch @panic("Unable to report error"); + message.writer.print(fmt, args) catch @panic("Unable to report error"); const items = [_]ReportItem{ .{ .location = location, .end_location = end_location, .kind = .@"error", - .message = message.items, + .message = message.written(), }, .{ .location = decl_location, @@ -722,7 +719,7 @@ pub fn reportWithOrigin( }; var decl_report = Report{ - .message = message.items, + .message = message.written(), .error_type = error_type, // When collecting errors for LSP, those can't live on the stack .items = items: { @@ -757,50 +754,49 @@ pub fn reportTypeCheck( ) void { @branchHint(.cold); - var actual_message = std.ArrayList(u8).empty; + var actual_message = std.Io.Writer.Allocating.init(self.allocator); defer { if (!self.collect) { - actual_message.deinit(self.allocator); + actual_message.deinit(); } } - var writer = &actual_message.writer(self.allocator); + actual_message.writer.print("{s}: got type `", .{message}) catch @panic("Unable to report error"); + actual_type.toString(&actual_message.writer, false) catch @panic("Unable to report error"); + actual_message.writer.writeAll("`") catch @panic("Unable to report error"); - writer.print("{s}: got type `", .{message}) catch @panic("Unable to report error"); - actual_type.toString(writer, false) catch @panic("Unable to report error"); - writer.writeAll("`") catch @panic("Unable to report error"); - - var expected_message = std.ArrayList(u8).empty; + var expected_message = std.Io.Writer.Allocating.init(self.allocator); defer { if (!self.collect) { - expected_message.deinit(self.allocator); + expected_message.deinit(); } } - if (expected_location != null) { - writer = &expected_message.writer(self.allocator); - } + var following_message = if (expected_location != null) + expected_message + else + actual_message; - writer.writeAll("expected `") catch @panic("Unable to report error"); + following_message.writer.writeAll("expected `") catch @panic("Unable to report error"); - expected_type.toString(writer, false) catch @panic("Unable to report error"); - writer.writeAll("`") catch @panic("Unable to report error"); + expected_type.toString(&following_message.writer, false) catch @panic("Unable to report error"); + following_message.writer.writeAll("`") catch @panic("Unable to report error"); var full_message = if (expected_location == null) actual_message else - std.ArrayList(u8).empty; + std.Io.Writer.Allocating.init(self.allocator); defer { if (!self.collect and expected_location != null) { - full_message.deinit(self.allocator); + full_message.deinit(); } } if (expected_location != null) { - full_message.writer(self.allocator).print( + full_message.writer.print( "{s}, {s}", .{ - actual_message.toOwnedSlice(self.allocator) catch @panic("Unable to report error"), - expected_message.toOwnedSlice(self.allocator) catch @panic("Unable to report error"), + actual_message.written(), + expected_message.written(), }, ) catch @panic("Unable to report error"); } @@ -812,18 +808,18 @@ pub fn reportTypeCheck( .location = actual_location, .end_location = actual_end_location, .kind = .@"error", - .message = actual_message.items, + .message = actual_message.written(), }, .{ .location = location, .end_location = expected_end_location.?, .kind = .hint, - .message = expected_message.items, + .message = expected_message.written(), }, }; break :rpt Report{ - .message = full_message.items, + .message = full_message.written(), .error_type = error_type, // When collecting errors for LSP, those can't live on the stack .items = items: { @@ -844,12 +840,12 @@ pub fn reportTypeCheck( .location = actual_location, .end_location = actual_end_location, .kind = .hint, - .message = actual_message.items, + .message = actual_message.written(), }, }; break :rpt Report{ - .message = full_message.items, + .message = full_message.written(), .error_type = error_type, // When collecting errors for LSP, those can't live on the stack .items = items: { diff --git a/src/behavior.zig b/src/behavior.zig index 18655134..90e466e5 100644 --- a/src/behavior.zig +++ b/src/behavior.zig @@ -103,11 +103,13 @@ fn testCompileErrors(allocator: std.mem.Allocator) !Result { ); var buffer = [_]u8{0} ** 255; var file_reader = test_file.reader(buffer[0..]); - var reader = io.AllocatedReader{ - .reader = &file_reader.interface, - }; + var reader = io.AllocatedReader.init( + allocator, + &file_reader.interface, + null, + ); - const first_line = (try reader.readUntilDelimiterOrEof(allocator, '\n')).?; + const first_line = (try reader.readUntilDelimiterOrEof('\n')).?; defer allocator.free(first_line); test_file.close(); diff --git a/src/builtin/list.zig b/src/builtin/list.zig index e265c61e..3e86e62c 100644 --- a/src/builtin/list.zig +++ b/src/builtin/list.zig @@ -216,17 +216,16 @@ pub fn join(ctx: *o.NativeCtx) callconv(.c) c_int { const self = o.ObjList.cast(ctx.vm.peek(1).obj()).?; const separator = o.ObjString.cast(ctx.vm.peek(0).obj()).?; - var result = std.ArrayList(u8).empty; - var writer = result.writer(ctx.vm.gc.allocator); - defer result.deinit(ctx.vm.gc.allocator); + var result = std.Io.Writer.Allocating.init(ctx.vm.gc.allocator); + defer result.deinit(); for (self.items.items, 0..) |item, i| { - item.toString(&writer) catch { + item.toString(&result.writer) catch { ctx.vm.panic("Out of memory"); unreachable; }; if (i + 1 < self.items.items.len) { - writer.writeAll(separator.string) catch { + result.writer.writeAll(separator.string) catch { ctx.vm.panic("Out of memory"); unreachable; }; @@ -234,7 +233,7 @@ pub fn join(ctx: *o.NativeCtx) callconv(.c) c_int { } ctx.vm.push( - (ctx.vm.gc.copyString(result.items) catch { + (ctx.vm.gc.copyString(result.written()) catch { ctx.vm.panic("Out of memory"); unreachable; }).toValue(), diff --git a/src/builtin/str.zig b/src/builtin/str.zig index fc072c91..af59084d 100644 --- a/src/builtin/str.zig +++ b/src/builtin/str.zig @@ -440,18 +440,17 @@ pub fn hex(ctx: *o.NativeCtx) callconv(.c) c_int { return 1; } - var result = std.ArrayList(u8).empty; - defer result.deinit(ctx.vm.gc.allocator); - var writer = result.writer(ctx.vm.gc.allocator); + var result = std.Io.Writer.Allocating.init(ctx.vm.gc.allocator); + defer result.deinit(); for (str.string) |char| { - writer.print("{x:0>2}", .{char}) catch { + result.writer.print("{x:0>2}", .{char}) catch { ctx.vm.panic("Out of memory"); unreachable; }; } - var obj_string = ctx.vm.gc.copyString(result.items) catch { + var obj_string = ctx.vm.gc.copyString(result.written()) catch { ctx.vm.panic("Out of memory"); unreachable; }; diff --git a/src/buzz_api.zig b/src/buzz_api.zig index df34a234..663832c9 100644 --- a/src/buzz_api.zig +++ b/src/buzz_api.zig @@ -1497,14 +1497,14 @@ export fn bz_zigTypeAlignment(self: *ZigType) callconv(.c) u16 { } export fn bz_zigTypeToCString(self: *ZigType, vm: *VM) callconv(.c) [*:0]const u8 { - var out = std.ArrayList(u8).empty; + var out = std.Io.Writer.Allocating.init(vm.gc.allocator); - out.writer(vm.gc.allocator).print("{}\x00", .{self.*}) catch { + out.writer.print("{}\x00", .{self.*}) catch { vm.panic("Out of memory"); unreachable; }; - return @ptrCast(out.items.ptr); + return @ptrCast(out.written().ptr); } export fn bz_serialize(vm: *VM, value: v.Value, error_value: *v.Value) callconv(.c) v.Value { diff --git a/src/io.zig b/src/io.zig index fe1dd36d..54e94908 100644 --- a/src/io.zig +++ b/src/io.zig @@ -1,5 +1,5 @@ -// Because of https://ziglang.org/download/0.12.0/release-notes.html#Bring-Your-Own-OS-API-Layer-Regressed -// we have to add this abstraction layer to avoid using io.getStdIn/Err/Out +//! Because of https://ziglang.org/download/0.12.0/release-notes.html#Bring-Your-Own-OS-API-Layer-Regressed +//! we have to add this abstraction layer to avoid using io.getStdIn/Err/Out const std = @import("std"); const builtin = @import("builtin"); @@ -62,24 +62,35 @@ pub fn print(comptime fmt: []const u8, args: anytype) void { pub const AllocatedReader = struct { pub const Error = error{ ReadFailed, + WriteFailed, OutOfMemory, }; - buffer: std.ArrayList(u8) = .empty, + buffer: std.Io.Writer.Allocating, max_size: ?usize = null, reader: *std.Io.Reader, - pub fn readUntilDelimiterOrEof(self: *AllocatedReader, allocator: std.mem.Allocator, delimiter: u8) Error!?[]u8 { - std.debug.assert(self.reader.buffer.len > 0); + pub fn init(allocator: std.mem.Allocator, reader: *std.Io.Reader, max_size: ?usize) @This() { + return .{ + .buffer = .init(allocator), + .reader = reader, + .max_size = max_size, + }; + } + + pub fn deinit(self: *@This()) void { + self.buffer.deinit(); + } - var writer = self.buffer.writer(allocator); + pub fn readUntilDelimiterOrEof(self: *AllocatedReader, delimiter: u8) Error!?[]u8 { + std.debug.assert(self.reader.buffer.len > 0); var count: usize = 0; while (self.max_size == null or count < self.max_size.?) : (count += 1) { const byte = self.reader.takeByte() catch |err| { switch (err) { error.EndOfStream => return if (count > 0) - try self.buffer.toOwnedSlice(allocator) + try self.buffer.toOwnedSlice() else null, error.ReadFailed => return error.ReadFailed, @@ -90,17 +101,15 @@ pub const AllocatedReader = struct { break; } - try writer.writeByte(byte); + try self.buffer.writer.writeByte(byte); } - return try self.buffer.toOwnedSlice(allocator); + return try self.buffer.toOwnedSlice(); } - pub fn readAll(self: *AllocatedReader, allocator: std.mem.Allocator) Error![]u8 { + pub fn readAll(self: *AllocatedReader) Error![]u8 { std.debug.assert(self.reader.buffer.len > 0); - var writer = self.buffer.writer(allocator); - while (true) { const byte = self.reader.takeByte() catch |err| { switch (err) { @@ -109,17 +118,15 @@ pub const AllocatedReader = struct { } }; - try writer.writeByte(byte); + try self.buffer.writer.writeByte(byte); } - return try self.buffer.toOwnedSlice(allocator); + return try self.buffer.toOwnedSlice(); } - pub fn readN(self: *AllocatedReader, allocator: std.mem.Allocator, n: usize) Error![]u8 { + pub fn readN(self: *AllocatedReader, n: usize) Error![]u8 { std.debug.assert(self.reader.buffer.len > 0); - var writer = self.buffer.writer(allocator); - var count: usize = 0; while (count < n and (self.max_size == null or count < self.max_size.?)) : (count += 1) { const byte = self.reader.takeByte() catch |err| { @@ -129,9 +136,9 @@ pub const AllocatedReader = struct { } }; - try writer.writeByte(byte); + try self.buffer.writer.writeByte(byte); } - return try self.buffer.toOwnedSlice(allocator); + return try self.buffer.toOwnedSlice(); } }; diff --git a/src/lib/buzz_buffer.zig b/src/lib/buzz_buffer.zig index ae2bb03e..0daa4e76 100644 --- a/src/lib/buzz_buffer.zig +++ b/src/lib/buzz_buffer.zig @@ -138,10 +138,9 @@ const Buffer = struct { return null; } - var buffer_stream = std.io.fixedBufferStream(self.buffer.items[self.cursor..self.buffer.items.len]); - var reader = buffer_stream.reader(); + var buffer_stream = std.Io.Reader.fixed(self.buffer.items[self.cursor..self.buffer.items.len]); - const number = try reader.readInt(api.Integer, builtin.cpu.arch.endian()); + const number = try buffer_stream.takeInt(api.Integer, builtin.cpu.arch.endian()); self.cursor += @divExact(@typeInfo(api.Integer).int.bits, 8); @@ -153,10 +152,12 @@ const Buffer = struct { return Error.WriteWhileReading; } - var writer = self.buffer.writer(api.VM.allocator); + var buffer = std.Io.Writer.Allocating.fromArrayList(api.VM.allocator, &self.buffer); // Flag so we know it an integer - try writer.writeInt(api.Integer, integer, native_endian); + try buffer.writer.writeInt(api.Integer, integer, native_endian); + + self.buffer = buffer.toArrayList(); } pub fn readUserData(self: *Self, vm: *api.VM) !?api.Value { @@ -164,10 +165,9 @@ const Buffer = struct { return null; } - var buffer_stream = std.io.fixedBufferStream(self.buffer.items[self.cursor..self.buffer.items.len]); - var reader = buffer_stream.reader(); + var buffer_stream = std.Io.Reader.fixed(self.buffer.items[self.cursor..self.buffer.items.len]); - const number = try reader.readInt(u64, builtin.cpu.arch.endian()); + const number = try buffer_stream.takeInt(u64, builtin.cpu.arch.endian()); self.cursor += @sizeOf(u64); @@ -179,14 +179,16 @@ const Buffer = struct { return Error.WriteWhileReading; } - var writer = self.buffer.writer(api.VM.allocator); + var buffer = std.Io.Writer.Allocating.fromArrayList(api.VM.allocator, &self.buffer); // Flag so we know it an integer - try writer.writeInt( + try buffer.writer.writeInt( u64, userdata.bz_getUserDataPtr(), native_endian, ); + + self.buffer = buffer.toArrayList(); } pub fn readDouble(self: *Self) !?api.Double { @@ -194,10 +196,9 @@ const Buffer = struct { return null; } - var buffer_stream = std.io.fixedBufferStream(self.buffer.items[self.cursor..self.buffer.items.len]); - var reader = buffer_stream.reader(); + var buffer_stream = std.Io.Reader.fixed(self.buffer.items[self.cursor..self.buffer.items.len]); - const number = try reader.readInt(u64, builtin.cpu.arch.endian()); + const number = try buffer_stream.takeInt(u64, builtin.cpu.arch.endian()); self.cursor += @divExact(@typeInfo(u64).int.bits, 8); @@ -209,13 +210,15 @@ const Buffer = struct { return Error.WriteWhileReading; } - var writer = self.buffer.writer(api.VM.allocator); + var buffer = std.Io.Writer.Allocating.fromArrayList(api.VM.allocator, &self.buffer); - try writer.writeInt( + try buffer.writer.writeInt( u64, @as(u64, @bitCast(double)), native_endian, ); + + self.buffer = buffer.toArrayList(); } pub fn empty(self: *Self) void { @@ -327,6 +330,10 @@ pub export fn BufferReadInt(ctx: *api.NativeCtx) callconv(.c) c_int { return 1; }, + error.ReadFailed => { + ctx.vm.pushErrorEnum("errors.ReadWriteError", @errorName(err)); + return -1; + }, } }) |value| { ctx.vm.bz_push(api.Value.fromInteger(value)); @@ -348,6 +355,10 @@ pub export fn BufferReadUserData(ctx: *api.NativeCtx) callconv(.c) c_int { return 1; }, + error.ReadFailed => { + ctx.vm.pushErrorEnum("errors.ReadWriteError", @errorName(err)); + return -1; + }, } }) |value| { ctx.vm.bz_push(value); @@ -369,6 +380,10 @@ pub export fn BufferReadDouble(ctx: *api.NativeCtx) callconv(.c) c_int { return 1; }, + error.ReadFailed => { + ctx.vm.pushErrorEnum("errors.ReadWriteError", @errorName(err)); + return -1; + }, } }) |value| { ctx.vm.bz_push(api.Value.fromDouble(value)); @@ -387,7 +402,7 @@ pub export fn BufferWriteInt(ctx: *api.NativeCtx) callconv(.c) c_int { buffer.writeInteger(number.integer()) catch |err| { switch (err) { Buffer.Error.WriteWhileReading => ctx.vm.pushError("buffer.WriteWhileReadingError", null), - error.OutOfMemory => { + error.WriteFailed => { ctx.vm.bz_panic("Out of memory", "Out of memory".len); unreachable; }, @@ -406,7 +421,7 @@ pub export fn BufferWriteUserData(ctx: *api.NativeCtx) callconv(.c) c_int { buffer.writeUserData(userdata) catch |err| { switch (err) { Buffer.Error.WriteWhileReading => ctx.vm.pushError("buffer.WriteWhileReadingError", null), - error.OutOfMemory => { + error.WriteFailed => { ctx.vm.bz_panic("Out of memory", "Out of memory".len); unreachable; }, @@ -425,7 +440,7 @@ pub export fn BufferWriteDouble(ctx: *api.NativeCtx) callconv(.c) c_int { buffer.writeFloat(number.double()) catch |err| { switch (err) { Buffer.Error.WriteWhileReading => ctx.vm.pushError("buffer.WriteWhileReadingError", null), - error.OutOfMemory => { + error.WriteFailed => { ctx.vm.bz_panic("Out of memory", "Out of memory".len); unreachable; }, @@ -508,10 +523,10 @@ fn checkBuzzType( btype: api.Value, ) bool { if (!value.bz_valueIs(btype).boolean()) { - var err = std.ArrayList(u8).empty; - defer err.deinit(api.VM.allocator); + var err = std.Io.Writer.Allocating.init(api.VM.allocator); + defer err.deinit(); - err.writer(api.VM.allocator).print( + err.writer.print( "Expected buzz value of type `{s}` to match FFI type `{s}`", .{ btype.bz_valueCastToString(vm).bz_valueToCString().?, @@ -523,11 +538,17 @@ fn checkBuzzType( unreachable; }; + const err_owned = err.toOwnedSlice() catch { + const msg = "Out of memory"; + vm.bz_panic(msg.ptr, msg.len); + unreachable; + }; + vm.bz_pushError( "ffi.FFITypeMismatchError", "ffi.FFITypeMismatchError".len, - err.items.ptr, - err.items.len, + err_owned.ptr, + err_owned.len, ); return false; diff --git a/src/lib/buzz_ffi.zig b/src/lib/buzz_ffi.zig index 92706ff7..f00f1d48 100644 --- a/src/lib/buzz_ffi.zig +++ b/src/lib/buzz_ffi.zig @@ -11,10 +11,10 @@ pub export fn alignOf(ctx: *api.NativeCtx) callconv(.c) c_int { if (zig_type) |ztype| { ctx.vm.bz_push(api.Value.fromInteger(@intCast(ztype.bz_zigTypeAlignment()))); } else { - var msg = std.ArrayList(u8).empty; - defer msg.deinit(api.VM.allocator); + var msg = std.Io.Writer.Allocating.init(api.VM.allocator); + defer msg.deinit(); - msg.writer(api.VM.allocator).print( + msg.writer.print( "Could not parse zig type `{s}`", .{ zig_type_str[0..len], @@ -24,7 +24,13 @@ pub export fn alignOf(ctx: *api.NativeCtx) callconv(.c) c_int { unreachable; }; - ctx.vm.pushError("ffi.FFIZigTypeParseError", msg.items); + ctx.vm.pushError( + "ffi.FFIZigTypeParseError", + msg.toOwnedSlice() catch { + ctx.vm.bz_panic("Out of memory", "Out of memory".len); + unreachable; + }, + ); return -1; } @@ -42,10 +48,10 @@ pub export fn sizeOf(ctx: *api.NativeCtx) callconv(.c) c_int { if (zig_type) |ztype| { ctx.vm.bz_push(api.Value.fromInteger(@intCast(ztype.bz_zigTypeSize()))); } else { - var msg = std.ArrayList(u8).empty; - defer msg.deinit(api.VM.allocator); + var msg = std.Io.Writer.Allocating.init(api.VM.allocator); + defer msg.deinit(); - msg.writer(api.VM.allocator).print( + msg.writer.print( "Could not parse zig type `{s}`", .{ zig_type_str[0..len], @@ -55,7 +61,13 @@ pub export fn sizeOf(ctx: *api.NativeCtx) callconv(.c) c_int { unreachable; }; - ctx.vm.pushError("ffi.FFIZigTypeParseError", msg.items); + ctx.vm.pushError( + "ffi.FFIZigTypeParseError", + msg.toOwnedSlice() catch { + ctx.vm.bz_panic("Out of memory", "Out of memory".len); + unreachable; + }, + ); return -1; } diff --git a/src/lib/buzz_io.zig b/src/lib/buzz_io.zig index 5e1249ca..6778a590 100644 --- a/src/lib/buzz_io.zig +++ b/src/lib/buzz_io.zig @@ -56,10 +56,11 @@ const File = struct { self.reader_buffer = try allocator.alloc(u8, 255); self.io_reader = self.file.reader(self.reader_buffer.?); - self.reader = .{ - .reader = &self.io_reader.?.interface, - .max_size = max_size, - }; + self.reader = .init( + api.VM.allocator, + &self.io_reader.?.interface, + max_size, + ); return &self.reader.?; } @@ -253,13 +254,13 @@ pub export fn FileReadAll(ctx: *api.NativeCtx) callconv(.c) c_int { unreachable; }; - const content = reader.readAll(api.VM.allocator) catch |err| { + const content = reader.readAll() catch |err| { switch (err) { error.ReadFailed => { ctx.vm.pushErrorEnum("errors.ReadWriteError", @errorName(err)); return -1; }, - error.OutOfMemory => { + error.OutOfMemory, error.WriteFailed => { ctx.vm.bz_panic("Out of memory", "Out of memory".len); unreachable; }, @@ -291,13 +292,13 @@ pub export fn FileReadLine(ctx: *api.NativeCtx) callconv(.c) c_int { unreachable; }; - if (reader.readUntilDelimiterOrEof(api.VM.allocator, '\n') catch |err| { + if (reader.readUntilDelimiterOrEof('\n') catch |err| { switch (err) { error.ReadFailed => { ctx.vm.pushErrorEnum("errors.ReadWriteError", @errorName(err)); return -1; }, - error.OutOfMemory => { + error.OutOfMemory, error.WriteFailed => { ctx.vm.bz_panic("Out of memory", "Out of memory".len); unreachable; }, @@ -333,13 +334,13 @@ pub export fn FileRead(ctx: *api.NativeCtx) callconv(.c) c_int { unreachable; }; - const content = reader.readN(api.VM.allocator, @intCast(n)) catch |err| { + const content = reader.readN(@intCast(n)) catch |err| { switch (err) { error.ReadFailed => { ctx.vm.pushErrorEnum("errors.ReadWriteError", @errorName(err)); return -1; }, - error.OutOfMemory => { + error.OutOfMemory, error.WriteFailed => { ctx.vm.bz_panic("Out of memory", "Out of memory".len); unreachable; }, @@ -415,12 +416,12 @@ const FileEnum = enum { pub export fn FileGetPoller(ctx: *api.NativeCtx) callconv(.c) c_int { const file = File.fromUserData(ctx.vm.bz_peek(0).bz_getUserDataPtr()); - const poller = api.VM.allocator.create(std.io.Poller(FileEnum)) catch { + const poller = api.VM.allocator.create(std.Io.Poller(FileEnum)) catch { ctx.vm.bz_panic("Out of memory", "Out of memory".len); unreachable; }; - poller.* = std.io.poll( + poller.* = std.Io.poll( api.VM.allocator, FileEnum, .{ .file = file.file }, @@ -435,7 +436,7 @@ pub export fn FileGetPoller(ctx: *api.NativeCtx) callconv(.c) c_int { return 1; } -fn pollerFromUserData(userdata: u64) *std.io.Poller(FileEnum) { +fn pollerFromUserData(userdata: u64) *std.Io.Poller(FileEnum) { return @ptrCast(@alignCast(@as(*anyopaque, @ptrFromInt(@as(usize, @truncate(userdata)))))); } @@ -465,16 +466,19 @@ pub export fn PollerPoll(ctx: *api.NativeCtx) callconv(.c) c_int { if (got_something) { const poll_reader = poller.reader(.file); - var reader = io.AllocatedReader{ - .reader = poll_reader, - }; - const read = reader.readAll(api.VM.allocator) catch |err| { + var reader = io.AllocatedReader.init( + api.VM.allocator, + poll_reader, + null, + ); + + const read = reader.readAll() catch |err| { switch (err) { error.ReadFailed => { ctx.vm.pushErrorEnum("errors.ReadWriteError", @errorName(err)); return -1; }, - error.OutOfMemory => { + error.OutOfMemory, error.WriteFailed => { ctx.vm.bz_panic("Out of memory", "Out of memory".len); unreachable; }, @@ -565,17 +569,19 @@ pub export fn runFile(ctx: *api.NativeCtx) callconv(.c) c_int { var reader_buffer = [_]u8{0}; var file_reader = file.reader(reader_buffer[0..]); - var reader = io.AllocatedReader{ - .reader = &file_reader.interface, - }; + var reader = io.AllocatedReader.init( + api.VM.allocator, + &file_reader.interface, + null, + ); - const source = reader.readAll(api.VM.allocator) catch |err| { + const source = reader.readAll() catch |err| { switch (err) { error.ReadFailed => { ctx.vm.pushErrorEnum("errors.ReadWriteError", @errorName(err)); return -1; }, - error.OutOfMemory => { + error.WriteFailed, error.OutOfMemory => { ctx.vm.bz_panic("Out of memory", "Out of memory".len); unreachable; }, diff --git a/src/lib/buzz_os.zig b/src/lib/buzz_os.zig index 3f96b782..2fdcef0c 100644 --- a/src/lib/buzz_os.zig +++ b/src/lib/buzz_os.zig @@ -88,16 +88,17 @@ pub export fn tmpFilename(ctx: *api.NativeCtx) callconv(.c) c_int { const prefix_slice = if (prefix_len == 0) "" else prefix.?[0..prefix_len]; - var random_part = std.ArrayList(u8).empty; - defer random_part.deinit(api.VM.allocator); - random_part.writer(api.VM.allocator).print("{x}", .{std.crypto.random.int(api.Integer)}) catch { + var random_part = std.Io.Writer.Allocating.init(api.VM.allocator); + defer random_part.deinit(); + + random_part.writer.print("{x}", .{std.crypto.random.int(api.Integer)}) catch { ctx.vm.bz_panic("Out of memory", "Out of memory".len); unreachable; }; var random_part_b64 = std.ArrayList(u8).initCapacity( api.VM.allocator, - std.base64.standard.Encoder.calcSize(random_part.items.len), + std.base64.standard.Encoder.calcSize(random_part.written().len), ) catch { ctx.vm.bz_panic("Out of memory", "Out of memory".len); unreachable; @@ -105,12 +106,18 @@ pub export fn tmpFilename(ctx: *api.NativeCtx) callconv(.c) c_int { random_part_b64.expandToCapacity(); defer random_part_b64.deinit(api.VM.allocator); - _ = std.base64.standard.Encoder.encode(random_part_b64.items, random_part.items); + _ = std.base64.standard.Encoder.encode(random_part_b64.items, random_part.written()); - var final = std.ArrayList(u8).empty; - defer final.deinit(api.VM.allocator); + var final = std.Io.Writer.Allocating.init(api.VM.allocator); - final.writer(api.VM.allocator).print("{s}{s}-{s}", .{ sysTempDir(), prefix_slice, random_part_b64.items }) catch { + final.writer.print( + "{s}{s}-{s}", + .{ + sysTempDir(), + prefix_slice, + random_part_b64.items, + }, + ) catch { ctx.vm.bz_panic("Out of memory", "Out of memory".len); unreachable; }; @@ -118,11 +125,11 @@ pub export fn tmpFilename(ctx: *api.NativeCtx) callconv(.c) c_int { ctx.vm.bz_push( api.VM.bz_stringToValue( ctx.vm, - if (final.items.len > 0) - @as([*]const u8, @ptrCast(final.items)) + if (final.written().len > 0) + @as([*]const u8, @ptrCast(final.written())) else null, - final.items.len, + final.written().len, ), ); @@ -311,6 +318,7 @@ fn handleConnectUnixError(ctx: *api.NativeCtx, err: anytype) void { error.AddressFamilyNotSupported, error.AddressInUse, error.AddressNotAvailable, + error.AlreadyConnected, error.ConnectionPending, error.ConnectionRefused, error.ConnectionResetByPeer, @@ -432,33 +440,38 @@ pub export fn SocketRead(ctx: *api.NativeCtx) callconv(.c) c_int { ); const stream: std.net.Stream = .{ .handle = handle }; - var reader_buffer = [_]u8{0}; + var reader_buffer: [1024]u8 = undefined; var stream_reader = stream.reader(reader_buffer[0..]); - var reader = io.AllocatedReader{ - .reader = stream_reader.interface(), - }; + var reader = stream_reader.interface(); + + var content = std.Io.Writer.Allocating.init(api.VM.allocator); + defer content.deinit(); + + while (content.written().len <= n) { + const byte = reader.takeByte() catch |err| { + switch (err) { + error.EndOfStream => break, + error.ReadFailed => { + ctx.vm.pushErrorEnum("errors.ReadWriteError", @errorName(err)); + return -1; + }, + } + }; - const content = reader.readN(api.VM.allocator, @intCast(n)) catch |err| { - switch (err) { - error.ReadFailed => { - ctx.vm.pushErrorEnum("errors.ReadWriteError", @errorName(err)); - return -1; - }, - error.OutOfMemory => { - ctx.vm.bz_panic("Out of memory", "Out of memory".len); - unreachable; - }, - } - }; + content.writer.writeByte(byte) catch |err| { + ctx.vm.pushErrorEnum("errors.ReadWriteError", @errorName(err)); + return -1; + }; + } ctx.vm.bz_push( api.VM.bz_stringToValue( ctx.vm, - if (content.len > 0) - @as([*]const u8, @ptrCast(content)) + if (content.written().len > 0) + @as([*]const u8, @ptrCast(content.written())) else null, - content.len, + content.written().len, ), ); @@ -513,18 +526,19 @@ pub export fn SocketReadLine(ctx: *api.NativeCtx) callconv(.c) c_int { const stream: std.net.Stream = .{ .handle = handle }; var reader_buffer = [_]u8{0}; var stream_reader = stream.reader(reader_buffer[0..]); - var reader = io.AllocatedReader{ - .reader = stream_reader.interface(), - .max_size = if (max_size.isInteger()) @intCast(max_size.integer()) else null, - }; + var reader = io.AllocatedReader.init( + api.VM.allocator, + stream_reader.interface(), + if (max_size.isInteger()) @intCast(max_size.integer()) else null, + ); - if (reader.readUntilDelimiterOrEof(api.VM.allocator, '\n') catch |err| { + if (reader.readUntilDelimiterOrEof('\n') catch |err| { switch (err) { error.ReadFailed => { ctx.vm.pushErrorEnum("errors.ReadWriteError", @errorName(err)); return -1; }, - error.OutOfMemory => { + error.WriteFailed, error.OutOfMemory => { ctx.vm.bz_panic("Out of memory", "Out of memory".len); unreachable; }, @@ -556,18 +570,19 @@ pub export fn SocketReadAll(ctx: *api.NativeCtx) callconv(.c) c_int { const stream = std.net.Stream{ .handle = handle }; var reader_buffer = [_]u8{0}; var stream_reader = stream.reader(reader_buffer[0..]); - var reader = io.AllocatedReader{ - .reader = stream_reader.interface(), - .max_size = if (max_size.isInteger()) @intCast(max_size.integer()) else null, - }; + var reader = io.AllocatedReader.init( + api.VM.allocator, + stream_reader.interface(), + if (max_size.isInteger()) @intCast(max_size.integer()) else null, + ); - const content = reader.readAll(api.VM.allocator) catch |err| { + const content = reader.readAll() catch |err| { switch (err) { error.ReadFailed => { ctx.vm.pushErrorEnum("errors.ReadWriteError", @errorName(err)); return -1; }, - error.OutOfMemory => { + error.WriteFailed, error.OutOfMemory => { ctx.vm.bz_panic("Out of memory", "Out of memory".len); unreachable; }, diff --git a/src/lsp.zig b/src/lsp.zig index 1e6e7267..2f245be4 100644 --- a/src/lsp.zig +++ b/src/lsp.zig @@ -11,7 +11,6 @@ const Reporter = @import("Reporter.zig"); const CodeGen = @import("Codegen.zig"); const Token = @import("Token.zig"); const Renderer = @import("renderer.zig").Renderer; -const WriteableArrayList = @import("writeable_array_list.zig").WriteableArrayList; const log = std.log.scoped(.buzz_lsp); @@ -317,7 +316,12 @@ const Document = struct { const InlayHintsContext = struct { document: *Document, - pub fn processNode(self: *InlayHintsContext, allocator: std.mem.Allocator, ast: Ast.Slice, node: Ast.Node.Index) (std.mem.Allocator.Error || std.fmt.BufPrintError)!bool { + pub fn processNode( + self: *InlayHintsContext, + allocator: std.mem.Allocator, + ast: Ast.Slice, + node: Ast.Node.Index, + ) (std.mem.Allocator.Error || std.fmt.BufPrintError || error{WriteFailed})!bool { switch (ast.nodes.items(.tag)[node]) { .VarDeclaration => { const comp = ast.nodes.items(.components)[node].VarDeclaration; @@ -326,11 +330,10 @@ const Document = struct { // If type was omitted, provide it if (!comp.implicit and comp.type == null and type_def != null) { - var inlay = std.ArrayList(u8){}; - var writer = inlay.writer(allocator); + var inlay = std.Io.Writer.Allocating.init(allocator); - try writer.writeAll(": "); - try type_def.?.toString(writer, false); + try inlay.writer.writeAll(": "); + try type_def.?.toString(&inlay.writer, false); try self.document.inlay_hints.append( allocator, @@ -340,7 +343,7 @@ const Document = struct { .character = @intCast(@max(1, name.column + name.lexeme.len) - 1), }, .label = .{ - .string = try inlay.toOwnedSlice(allocator), + .string = try inlay.toOwnedSlice(), }, .kind = .Type, }, @@ -425,9 +428,9 @@ const Handler = struct { }, ); - var version = std.ArrayList(u8).empty; + var version = std.Io.Writer.Allocating.init(allocator); - try version.writer(allocator).print( + try version.writer.print( "{f}-{s}", .{ BuildOptions.version, @@ -438,7 +441,7 @@ const Handler = struct { return .{ .serverInfo = .{ .name = "Buzz LSP", - .version = version.items, + .version = version.written(), }, .capabilities = .{ .positionEncoding = switch (self.offset_encoding) { @@ -702,7 +705,12 @@ const Handler = struct { const DocumentSymbolContext = struct { document: *Document, - pub fn processNode(self: DocumentSymbolContext, _: std.mem.Allocator, ast: Ast.Slice, node: Ast.Node.Index) (std.mem.Allocator.Error || std.fmt.BufPrintError)!bool { + pub fn processNode( + self: DocumentSymbolContext, + _: std.mem.Allocator, + ast: Ast.Slice, + node: Ast.Node.Index, + ) (std.mem.Allocator.Error || std.fmt.BufPrintError || error{WriteFailed})!bool { const lexemes = ast.tokens.items(.lexeme); const locations = ast.nodes.items(.location); const end_locations = ast.nodes.items(.end_location); @@ -947,8 +955,7 @@ const Handler = struct { const markupEntry = try document.node_hover.getOrPut(allocator, origin); if (!markupEntry.found_existing) { - var markup = std.ArrayList(u8).empty; - const writer = markup.writer(self.allocator); + var markup = std.Io.Writer.Allocating.init(self.allocator); const def = try document.definition(origin); if (def) |udef| { @@ -957,18 +964,18 @@ const Handler = struct { var it = std.mem.tokenizeSequence(u8, doc, "/// "); while (it.next()) |text| { - try writer.print("{s}\n", .{text}); + try markup.writer.print("{s}\n", .{text}); } } } - try writer.writeAll("```buzz\n"); - td.toString(&writer, false) catch |err| { + try markup.writer.writeAll("```buzz\n"); + td.toString(&markup.writer, false) catch |err| { log.err("textDocument/hover: {any}", .{err}); }; - try writer.writeAll("\n```"); + try markup.writer.writeAll("\n```"); - markupEntry.value_ptr.* = try markup.toOwnedSlice(self.allocator); + markupEntry.value_ptr.* = try markup.toOwnedSlice(); } return .{ @@ -1032,7 +1039,7 @@ const Handler = struct { notification: lsp.types.getRequestMetadata("textDocument/formatting").?.Params.?, ) !lsp.types.getRequestMetadata("textDocument/formatting").?.Result { if (self.documents.get(notification.textDocument.uri)) |document| { - var result = WriteableArrayList(u8).init(self.allocator); + var result = std.Io.Writer.Allocating.init(self.allocator); Renderer.render( self.allocator, @@ -1055,7 +1062,7 @@ const Handler = struct { self.allocator, .{ .range = document.wholeDocumentRange(), - .newText = result.list.items, + .newText = result.written(), }, ); diff --git a/src/obj.zig b/src/obj.zig index 8e1b18fa..29840a4b 100644 --- a/src/obj.zig +++ b/src/obj.zig @@ -509,7 +509,7 @@ pub const Obj = struct { } } - pub fn toString(obj: *Obj, writer: *const std.ArrayList(u8).Writer) (Allocator.Error || std.fmt.BufPrintError)!void { + pub fn toString(obj: *Obj, writer: *std.Io.Writer) (Allocator.Error || std.fmt.BufPrintError || error{WriteFailed})!void { return switch (obj.obj_type) { .String => { const str = ObjString.cast(obj).?.string; @@ -4892,23 +4892,23 @@ pub const ObjTypeDef = struct { // FIXME } - pub fn toStringAlloc(self: *const Self, allocator: Allocator, qualified: bool) (Allocator.Error || std.fmt.BufPrintError)![]const u8 { - var str = std.ArrayList(u8).empty; + pub fn toStringAlloc(self: *const Self, allocator: Allocator, qualified: bool) (Allocator.Error || std.fmt.BufPrintError || error{WriteFailed})![]const u8 { + var str = std.Io.Writer.Allocating.init(allocator); - try self.toString(&str.writer(allocator), qualified); + try self.toString(&str.writer, qualified); - return try str.toOwnedSlice(allocator); + return try str.toOwnedSlice(); } - pub fn toString(self: *const Self, writer: anytype, qualified: bool) (Allocator.Error || std.fmt.BufPrintError)!void { + pub fn toString(self: *const Self, writer: anytype, qualified: bool) (Allocator.Error || std.fmt.BufPrintError || error{WriteFailed})!void { try self.toStringRaw(writer, qualified); } - pub fn toStringUnqualified(self: *const Self, writer: anytype) (Allocator.Error || std.fmt.BufPrintError)!void { + pub fn toStringUnqualified(self: *const Self, writer: anytype) (Allocator.Error || std.fmt.BufPrintError || error{WriteFailed})!void { try self.toStringRaw(writer, false); } - fn toStringRaw(self: *const Self, writer: anytype, qualified: bool) (Allocator.Error || std.fmt.BufPrintError)!void { + fn toStringRaw(self: *const Self, writer: anytype, qualified: bool) (Allocator.Error || std.fmt.BufPrintError || error{WriteFailed})!void { switch (self.def_type) { .Generic => try writer.print( "generic type #{}-{}", diff --git a/src/renderer.zig b/src/renderer.zig index 0a6b0d6b..a8f2bc78 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -3,7 +3,6 @@ const assert = std.debug.assert; const builtin = @import("builtin"); const Ast = @import("Ast.zig"); const Token = @import("Token.zig"); -const WriteableArrayList = @import("writeable_array_list.zig").WriteableArrayList; pub const Renderer = struct { const Self = @This(); @@ -189,7 +188,7 @@ pub const Renderer = struct { try self.renderToken(token, if (is_last) space else .None); if (!is_last) { - try self.ais.writer().writeByte('\\'); + try self.ais.writeByte('\\'); } } } @@ -252,14 +251,14 @@ pub const Renderer = struct { const lexeme = self.ast.tokens.items(.lexeme)[token]; if (self.ast.tokens.items(.tag)[token] == .Identifier and isAtIdentifier(lexeme)) { - try self.ais.writer().print( + try self.ais.print( "@\"{s}\"", .{ lexeme, }, ); } else { - try self.ais.writer().writeAll(lexeme); + try self.ais.writeAll(lexeme); } try self.renderSpace( @@ -315,7 +314,7 @@ pub const Renderer = struct { const next_token_tag = self.ast.tokens.items(.tag)[next_token]; if (space == .Comma and next_token_tag != .Comma) { - try self.ais.writer().writeByte(','); + try self.ais.writeByte(','); } if (space == .Semicolon or space == .Comma) self.ais.enableSpaceMode(space); @@ -352,7 +351,7 @@ pub const Renderer = struct { switch (space) { .None => {}, - .Space => if (!comment) try self.ais.writer().writeByte(' '), + .Space => if (!comment) try self.ais.writeByte(' '), .Newline => if (!comment) try self.ais.insertNewline(), .Comma => if (next_token_tag == .Comma) { @@ -364,7 +363,7 @@ pub const Renderer = struct { .CommaSpace => if (next_token_tag == .Comma) { try self.renderToken(next_token, .Space); } else if (!comment) { - try self.ais.writer().writeByte(' '); + try self.ais.writeByte(' '); }, .Semicolon => if (next_token_tag == .Semicolon) { @@ -376,7 +375,7 @@ pub const Renderer = struct { .SemicolonSpace => if (next_token_tag == .Semicolon) { try self.renderToken(next_token, .Space); } else if (!comment) { - try self.ais.writer().writeByte(' '); + try self.ais.writeByte(' '); }, .Skip => unreachable, @@ -412,7 +411,7 @@ pub const Renderer = struct { } else if (index == start and comment.len > 0) { // Otherwise if the first comment is on the same line as // the token before it, prefix it with a single space. - try self.ais.writer().writeByte(' '); + try self.ais.writeByte(' '); } } @@ -428,11 +427,11 @@ pub const Renderer = struct { self.ais.disabled_offset = null; } else if (self.ais.disabled_offset == null and std.mem.eql(u8, comment, "buzz fmt: off")) { // Write with the canonical single space. - try self.ais.writer().writeAll("// buzz fmt: off\n"); + try self.ais.writeAll("// buzz fmt: off\n"); self.ais.disabled_offset = index; } else { // Write the comment minus trailing whitespace. - try self.ais.writer().print( + try self.ais.print( "//{s}{s}\n", .{ if (comment.len > 1 and !std.mem.startsWith(u8, comment, " ")) @@ -1040,12 +1039,12 @@ pub const Renderer = struct { }; if (string_literal.delimiter == '`') { - var buffer = WriteableArrayList(u8).init(self.allocator); + var buffer = std.Io.Writer.Allocating.init(self.allocator); defer buffer.deinit(); try formatter.format(&buffer.writer); - try self.ais.writeFixingWhitespace(buffer.list.items); + try self.ais.writeFixingWhitespace(buffer.written()); } else { try formatter.format(self.ais.underlying_writer); } @@ -1064,22 +1063,22 @@ pub const Renderer = struct { const string_lexeme = self.ast.tokens.items(.lexeme)[locations[node]]; // " or ` - try self.ais.writer().writeAll(string_lexeme[0..1]); + try self.ais.writeAll(string_lexeme[0..1]); for (self.ast.nodes.items(.components)[node].String) |part| { if (tags[part] != .StringLiteral) { - try self.ais.writer().writeByte('{'); + try self.ais.writeByte('{'); try self.renderNode(part, .None); - try self.ais.writer().writeByte('}'); + try self.ais.writeByte('}'); } else { try self.renderNode(part, .None); } } // " or ` - try self.ais.writer().writeByte(string_lexeme[string_lexeme.len - 1]); + try self.ais.writeByte(string_lexeme[string_lexeme.len - 1]); try self.renderSpace( locations[node], string_lexeme.len, @@ -3477,13 +3476,6 @@ pub const Renderer = struct { const AutoIndentingStream = struct { const SelfAis = @This(); - pub const WriteError = std.io.Writer.Error; - pub const Writer = std.io.GenericWriter( - *SelfAis, - WriteError, - write, - ); - pub const IndentType = enum { normal, after_equals, @@ -3530,15 +3522,28 @@ pub const Renderer = struct { self.space_stack.deinit(allocator); } - pub fn writer(self: *SelfAis) Writer { - return .{ .context = self }; - } - pub fn write(self: *SelfAis, bytes: []const u8) std.Io.Writer.Error!usize { try self.applyIndent(); return try self.writeNoIndent(bytes); } + pub fn writeByte(self: *SelfAis, byte: u8) std.Io.Writer.Error!void { + _ = try self.write(&.{byte}); + } + + pub fn print(self: *SelfAis, comptime fmt: []const u8, args: anytype) std.Io.Writer.Error!void { + try self.applyIndent(); + if (self.disabled_offset == null) try self.underlying_writer.print(fmt, args); + if (fmt[fmt.len - 1] == '\n') self.resetLine(); + } + + pub fn writeAll(self: *SelfAis, bytes: []const u8) Error!void { + if (bytes.len == 0) return; + try self.applyIndent(); + if (self.disabled_offset == null) try self.underlying_writer.writeAll(bytes); + if (bytes[bytes.len - 1] == '\n') self.resetLine(); + } + // Change the indent delta without changing the final indentation level pub fn setIndentDelta(self: *SelfAis, new_indent_delta: usize) !void { if (self.indent_delta == new_indent_delta) { diff --git a/src/repl.zig b/src/repl.zig index 3c344371..2add2972 100644 --- a/src/repl.zig +++ b/src/repl.zig @@ -117,17 +117,17 @@ pub fn repl(allocator: std.mem.Allocator) !void { var stderr = io.stderrWriter; printBanner(stdout, false); - var buzz_history_path = std.ArrayList(u8).empty; - defer buzz_history_path.deinit(allocator); + var buzz_history_path = std.Io.Writer.Allocating.init(allocator); + defer buzz_history_path.deinit(); - try buzz_history_path.writer(allocator).print( + try buzz_history_path.writer.print( "{s}/.buzz_history\x00", .{envMap.get("HOME") orelse "."}, ); if (builtin.os.tag != .windows) { _ = ln.linenoiseHistorySetMaxLen(100); - _ = ln.linenoiseHistoryLoad(@ptrCast(buzz_history_path.items.ptr)); + _ = ln.linenoiseHistoryLoad(@ptrCast(buzz_history_path.written().ptr)); } // Import std and debug as commodity @@ -148,9 +148,11 @@ pub fn repl(allocator: std.mem.Allocator) !void { var reader_buffer = [_]u8{0}; var stdin_reader = std.fs.File.stdin().reader(reader_buffer[0..]); - var reader = io.AllocatedReader{ - .reader = &stdin_reader.interface, - }; + var reader = io.AllocatedReader.init( + allocator, + &stdin_reader.interface, + null, + ); while (true) { if (builtin.os.tag == .windows) { @@ -170,7 +172,7 @@ pub fn repl(allocator: std.mem.Allocator) !void { PROMPT, ) else // FIXME: in that case, at least use an arena? - reader.readUntilDelimiterOrEof(allocator, '\n') catch @panic("Could not read stdin"); + reader.readUntilDelimiterOrEof('\n') catch @panic("Could not read stdin"); if (read_source == null) { std.process.exit(0); @@ -235,7 +237,7 @@ pub fn repl(allocator: std.mem.Allocator) !void { if (parser.reporter.last_error == null and codegen.reporter.last_error == null) { if (builtin.os.tag != .windows) { _ = ln.linenoiseHistoryAdd(source); - _ = ln.linenoiseHistorySave(@ptrCast(buzz_history_path.items.ptr)); + _ = ln.linenoiseHistorySave(@ptrCast(buzz_history_path.written().ptr)); } // FIXME: why can't I deinit those? // previous_parser_globals.deinit(); @@ -251,22 +253,23 @@ pub fn repl(allocator: std.mem.Allocator) !void { const value = expr orelse vm.globals.items[previous_global_top]; - var value_str = std.ArrayList(u8).empty; - defer value_str.deinit(vm.gc.allocator); + var value_str = std.Io.Writer.Allocating.init(vm.gc.allocator); + defer value_str.deinit(); + var state = disassembler.DumpState{ .vm = &vm, }; state.valueDump( value, - value_str.writer(vm.gc.allocator), + &value_str.writer, false, ); var scanner = Scanner.init( gc.allocator, "REPL", - value_str.items, + value_str.written(), ); scanner.highlight(stdout, true_color); @@ -291,7 +294,7 @@ pub fn repl(allocator: std.mem.Allocator) !void { std.mem.copyForwards(u8, previous_input.?, source); } else if (builtin.os.tag != .windows) { _ = ln.linenoiseHistoryAdd(source); - _ = ln.linenoiseHistorySave(@ptrCast(buzz_history_path.items.ptr)); + _ = ln.linenoiseHistorySave(@ptrCast(buzz_history_path.written().ptr)); } } diff --git a/src/tests/fmt.zig b/src/tests/fmt.zig index 3cf60d20..251594d0 100644 --- a/src/tests/fmt.zig +++ b/src/tests/fmt.zig @@ -5,7 +5,6 @@ const Parser = @import("../Parser.zig"); const GC = @import("../GC.zig"); const TypeRegistry = @import("../TypeRegistry.zig"); const Renderer = @import("../renderer.zig").Renderer; -const WriteableArrayList = @import("../writeable_array_list.zig").WriteableArrayList; const ignore = std.StaticStringMap(void).initComptime( .{ @@ -71,7 +70,7 @@ fn testFmt(prefix: []const u8, entry: std.fs.Dir.Entry) !void { .Fmt, ); - var result = WriteableArrayList(u8).init(allocator); + var result = std.Io.Writer.Allocating.init(allocator); if (parser.parse(source, file_name, file_name) catch null) |ast| { try Renderer.render( @@ -82,7 +81,7 @@ fn testFmt(prefix: []const u8, entry: std.fs.Dir.Entry) !void { try std.testing.expectEqualStrings( source, - result.list.items, + result.written(), ); } else { try std.testing.expect(false); diff --git a/src/value.zig b/src/value.zig index 6a164272..6b40e4b0 100644 --- a/src/value.zig +++ b/src/value.zig @@ -159,16 +159,16 @@ pub const Value = packed struct { return self; } - pub fn toStringAlloc(value: Value, allocator: Allocator) (Allocator.Error || std.fmt.BufPrintError)![]const u8 { - var str = std.ArrayList(u8).empty; + pub fn toStringAlloc(value: Value, allocator: Allocator) (Allocator.Error || std.fmt.BufPrintError || error{WriteFailed})![]const u8 { + var str = std.Io.Writer.Allocating.init(allocator); - try value.toString(&str.writer(allocator)); + try value.toString(&str.writer); - return try str.toOwnedSlice(allocator); + return try str.toOwnedSlice(); } // FIXME: should be a std.io.Writer once it exists for ArrayLists - pub fn toString(self: Value, writer: *const std.ArrayList(u8).Writer) (Allocator.Error || std.fmt.BufPrintError)!void { + pub fn toString(self: Value, writer: *std.Io.Writer) (Allocator.Error || std.fmt.BufPrintError || error{WriteFailed})!void { if (self.isObj()) { try self.obj().toString(writer); diff --git a/src/vm.zig b/src/vm.zig index fbffe933..83af1176 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -421,6 +421,7 @@ pub const VM = struct { ReachedMaximumMemoryUsage, ReachedMaximumCPUUsage, ReachedMaximumRecursiveCall, + WriteFailed, Custom, // TODO: remove when user can use this set directly in buzz code } || std.mem.Allocator.Error || std.fmt.BufPrintError; @@ -4616,12 +4617,11 @@ pub const VM = struct { for (stack, 0..) |frame, i| { const next = if (i < stack.len - 1) stack[i + 1] else null; - var msg = std.ArrayList(u8).empty; - var writer = msg.writer(self.gc.allocator); + var msg = std.Io.Writer.Allocating.init(self.gc.allocator); if (next) |unext| { const function_name = unext.closure.function.type_def.resolved_type.?.Function.name.string; - writer.print( + msg.writer.print( if (builtin.os.tag != .windows) "\t{s} in \x1b[36m{s}\x1b[0m at {s}" else @@ -4641,7 +4641,7 @@ pub const VM = struct { }, ) catch @panic("Could not report error"); } else { - writer.print( + msg.writer.print( "\t{s} in {s}", .{ if (i == 0) @@ -4660,7 +4660,7 @@ pub const VM = struct { if (frame.call_site) |call_site| { if (frame.closure.function.type_def.resolved_type.?.Function.function_type != .ScriptEntryPoint) { - writer.print( + msg.writer.print( ":{d}:{d}", .{ self.current_ast.tokens.items(.line)[call_site] + 1, @@ -4673,7 +4673,7 @@ pub const VM = struct { notes.append( self.gc.allocator, .{ - .message = msg.toOwnedSlice(self.gc.allocator) catch @panic("Could not report error"), + .message = msg.toOwnedSlice() catch @panic("Could not report error"), .show_prefix = false, }, ) catch @panic("Could not report error"); diff --git a/src/wasm_repl.zig b/src/wasm_repl.zig index 179c36f7..2055aa8c 100644 --- a/src/wasm_repl.zig +++ b/src/wasm_repl.zig @@ -96,16 +96,18 @@ pub export fn runLine(ctx: *ReplCtx) void { var reader_buffer = [_]u8{0}; var stdin_reader = io.stdinReader(reader_buffer[0..]); - var stdin = io.AllocatedReader{ - .reader = &stdin_reader, - }; + var stdin = io.AllocatedReader.init( + ctx.vm.gc.allocator, + &stdin_reader, + null, + ); var previous_global_top = ctx.vm.globals_count; var previous_parser_globals = ctx.parser.globals.clone(ctx.parser.gc.allocator) catch unreachable; var previous_globals = ctx.vm.globals.clone(ctx.parser.gc.allocator) catch unreachable; var previous_type_registry = ctx.vm.gc.type_registry.registry.clone(ctx.parser.gc.allocator) catch unreachable; - const source = stdin.readAll(ctx.vm.gc.allocator) catch unreachable; + const source = stdin.readAll() catch unreachable; errdefer ctx.vm.gc.allocator.free(source); if (source.len == 0) { @@ -142,22 +144,22 @@ pub export fn runLine(ctx: *ReplCtx) void { const value = expr orelse ctx.vm.globals.items[previous_global_top]; - var value_str = std.ArrayList(u8).empty; - defer value_str.deinit(ctx.vm.gc.allocator); + var value_str = std.Io.Writer.Allocating.init(ctx.vm.gc.allocator); + defer value_str.deinit(); var state = DumpState{ .vm = ctx.vm, }; state.valueDump( value, - value_str.writer(ctx.vm.gc.allocator), + &value_str.writer, false, ); var scanner = Scanner.init( ctx.vm.gc.allocator, "REPL", - value_str.items, + value_str.written(), ); scanner.highlight(stdout, false); diff --git a/src/writeable_array_list.zig b/src/writeable_array_list.zig deleted file mode 100644 index 296ce802..00000000 --- a/src/writeable_array_list.zig +++ /dev/null @@ -1,47 +0,0 @@ -const std = @import("std"); - -/// Wraps std.ArrayList to provide a std.Io.Writer until its provided by std lib -pub fn WriteableArrayList(comptime T: type) type { - return struct { - const Self = @This(); - - list: std.array_list.Managed(T), - writer: std.Io.Writer = .{ - .buffer = &.{}, - .vtable = &.{ - .drain = drain, - }, - }, - - pub fn init(allocator: std.mem.Allocator) Self { - return .{ - .list = .init(allocator), - }; - } - - pub fn deinit(self: *Self) void { - self.list.deinit(); - } - - fn drain(w: *std.Io.Writer, data: []const []const u8, splat: usize) std.Io.Writer.Error!usize { - var self: *Self = @alignCast(@fieldParentPtr("writer", w)); - - if (w.buffer.len > 0) { - self.list.appendSlice(w.buffer) catch return error.WriteFailed; - } - - var written: usize = 0; - for (data[0 .. data.len - 1]) |element| { - self.list.appendSlice(element) catch return error.WriteFailed; - written += element.len; - } - - const last = data[data.len - 1]; - for (0..splat) |_| { - self.list.appendSlice(last) catch return error.WriteFailed; - } - - return written + last.len * splat; - } - }; -} From b6f52eff95574c4b07b0066fd888e7464351cc27 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Thu, 16 Oct 2025 11:18:04 +0200 Subject: [PATCH 02/12] fix(vm): Crazy bug catched by new safety check probably in ArrayList.pop We were accessing a ptr to a popped value of the stack ArrayList. Seems like now zig's freeing that spot in Debug mode. --- src/Reporter.zig | 5 ++--- src/disassembler.zig | 3 ++- src/vm.zig | 25 ++++++++++++++----------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/Reporter.zig b/src/Reporter.zig index 428d5ea1..94c10eab 100644 --- a/src/Reporter.zig +++ b/src/Reporter.zig @@ -773,12 +773,11 @@ pub fn reportTypeCheck( } var following_message = if (expected_location != null) - expected_message + &expected_message else - actual_message; + &actual_message; following_message.writer.writeAll("expected `") catch @panic("Unable to report error"); - expected_type.toString(&following_message.writer, false) catch @panic("Unable to report error"); following_message.writer.writeAll("`") catch @panic("Unable to report error"); diff --git a/src/disassembler.zig b/src/disassembler.zig index 47715b04..55f532c3 100644 --- a/src/disassembler.zig +++ b/src/disassembler.zig @@ -175,7 +175,8 @@ pub fn dumpStack(vm: *VM) void { print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n", .{}); var value: [*]Value = @ptrCast(vm.current_fiber.stack[0..]); - while (@intFromPtr(value) < @intFromPtr(vm.current_fiber.stack_top)) { + var count: usize = 0; + while (@intFromPtr(value) < @intFromPtr(vm.current_fiber.stack_top) and count < vm.current_fiber.stack.len) : (count += 1) { var value_str = value[0].toStringAlloc(global_allocator) catch unreachable; defer global_allocator.free(value_str); diff --git a/src/vm.zig b/src/vm.zig index 83af1176..7d354414 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -133,7 +133,7 @@ pub const Fiber = struct { .parent_fiber = parent_fiber, .stack = try allocator.alloc(Value, BuildOptions.stack_size), .stack_top = undefined, - .frames = std.ArrayList(CallFrame){}, + .frames = .empty, .open_upvalues = null, .instruction = instruction, .extra_instruction = extra_instruction, @@ -547,6 +547,8 @@ pub const VM = struct { } pub inline fn currentFrame(self: *Self) ?*CallFrame { + std.debug.assert(self.current_fiber.frame_count <= self.current_fiber.frames.items.len); + if (self.current_fiber.frame_count == 0) { return null; } @@ -2064,7 +2066,7 @@ pub const VM = struct { inline fn returnFrame(self: *Self) bool { const result = self.pop(); - const frame = self.currentFrame().?; + const frame = self.currentFrame().?.*; self.closeUpValues(&frame.slots[0]); @@ -4509,25 +4511,26 @@ pub const VM = struct { null; while (self.current_fiber.frame_count > 0 or self.current_fiber.parent_fiber != null) { - const frame = self.currentFrame(); + const frame_ptr = self.currentFrame(); + const frame_val = if (frame_ptr) |ptr| ptr.* else null; if (self.current_fiber.frame_count > 0) { - const function_type = frame.?.closure.function.type_def.resolved_type.?.Function.function_type; + const function_type = frame_ptr.?.closure.function.type_def.resolved_type.?.Function.function_type; if (function_type != .ScriptEntryPoint and function_type != .Repl) { - try stack.append(self.gc.allocator, frame.?.*); + try stack.append(self.gc.allocator, frame_val.?); } // Are we in a try-catch? - if (frame.?.try_ip) |try_ip| { + if (frame_ptr.?.try_ip) |try_ip| { // Push error and jump to start of the catch clauses self.push(payload); - frame.?.ip = try_ip; + frame_ptr.?.ip = try_ip; return; } // Pop frame - self.closeUpValues(&frame.?.slots[0]); + self.closeUpValues(&frame_ptr.?.slots[0]); self.current_fiber.frame_count -= 1; _ = self.current_fiber.frames.pop(); } @@ -4582,10 +4585,10 @@ pub const VM = struct { return; } - if (frame != null) { - self.current_fiber.stack_top = frame.?.slots; + if (frame_val != null) { + self.current_fiber.stack_top = frame_val.?.slots; - if (frame.?.error_value) |error_value| { + if (frame_val.?.error_value) |error_value| { // Push error_value as failed function return value self.push(error_value); From 2907fc9508972b17a58f6ef4e606478e38b3692b Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Thu, 16 Oct 2025 11:49:00 +0200 Subject: [PATCH 03/12] fix(ci): Simplified ci.yaml Using `strategy` instead of copying for each OS --- .github/workflows/ci.yaml | 106 ++++++-------------------------------- README.md | 2 +- build.zig | 2 +- 3 files changed, 17 insertions(+), 93 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 75c997f6..3bd0bbfc 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -5,51 +5,16 @@ on: branches: [main] jobs: - # test-windows: - # runs-on: windows-latest - # steps: - # - name: Checkout project - # uses: actions/checkout@v3.0.0 - # - name: Checkout submodules - # run: git submodule update --init --recursive - # - name: Setup nightly Zig - # uses: mlugg/setup-zig@v2 - # with: - # version: 0.15.1 - # - name: Build test ffi lib - # run: zig build-lib -dynamic tests/utils/foreign.zig && mv foreign.* tests/utils/ - - # - name: Build - # run: zig build && ls ./zig-out/lib/buzz - - # - name: Run behavior tests Debug - # run: zig build test-behavior - # - name: Cleanup - # run: rm -rf zig-out zig-cache - # - name: Run behavior tests Debug with JIT always on - # run: zig build -Djit_always_on test && zig build -Djit_always_on test-behavior - # - name: Cleanup - # run: rm -rf zig-out zig-cache - - # - name: Run behavior tests ReleaseSafe - # run: zig build -Doptimize=ReleaseSafe test && zig build -Doptimize=ReleaseSafe test-behavior - # - name: Cleanup - # run: rm -rf zig-out zig-cache - # - name: Run behavior tests ReleaseSafe with JIT always on - # run: zig build -Doptimize=ReleaseSafe -Djit_always_on test && zig build -Doptimize=ReleaseSafe -Djit_always_on test-behavior - # - name: Cleanup - # run: rm -rf zig-out zig-cache - - # - name: Run behavior tests ReleaseFast - # run: zig build -Doptimize=ReleaseFast test && zig build -Doptimize=ReleaseFast test-behavior - # - name: Cleanup - # run: rm -rf zig-out zig-cache - # - name: Run behavior tests ReleaseFast with JIT always on - # run: zig build -Doptimize=ReleaseFast -Djit_always_on test && zig build -Doptimize=ReleaseFast -Djit_always_on test-behavior - # - name: Cleanup - # run: rm -rf zig-out zig-cache - test-macos: - runs-on: macos-latest + tests: + runs-on: ${{ matrix.operating-system }} + + strategy: + fail-fast: false + matrix: + zig-version: + - "master" + operating-system: [ ubuntu-latest, macos-latest ] + steps: - name: Install homebrew run: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" @@ -60,50 +25,7 @@ jobs: - name: Setup nightly Zig uses: mlugg/setup-zig@v2 with: - version: 0.15.1 - - name: Build test ffi lib - run: zig build-lib -dynamic tests/utils/foreign.zig && mv libforeign.* tests/utils/ - - - name: Run behavior tests Debug - run: zig build test && zig build test-behavior - - name: Cleanup - run: rm -rf zig-out zig-cache - - - name: Run behavior tests Debug with JIT always on - run: zig build -Djit_always_on test && zig build -Djit_always_on test-behavior - - name: Cleanup - run: rm -rf zig-out zig-cache - - - name: Run behavior tests ReleaseSafe - run: zig build -Doptimize=ReleaseSafe test && zig build -Doptimize=ReleaseSafe test-behavior - - name: Cleanup - run: rm -rf zig-out zig-cache - - - name: Run behavior tests ReleaseSafe with JIT always on - run: zig build -Doptimize=ReleaseSafe -Djit_always_on test && zig build -Doptimize=ReleaseSafe -Djit_always_on test-behavior - - name: Cleanup - run: rm -rf zig-out zig-cache - - - name: Run behavior tests ReleaseFast - run: zig build -Doptimize=ReleaseFast test && zig build -Doptimize=ReleaseFast test-behavior - - name: Cleanup - run: rm -rf zig-out zig-cache - - - name: Run behavior tests ReleaseFast with JIT always on - run: zig build -Doptimize=ReleaseFast -Djit_always_on test && zig build -Doptimize=ReleaseFast -Djit_always_on test-behavior - - name: Cleanup - run: rm -rf zig-out zig-cache - test-linux: - runs-on: ubuntu-latest - steps: - - name: Checkout project - uses: actions/checkout@v3.0.0 - - name: Checkout submodules - run: git submodule update --init --recursive - - name: Setup nightly Zig - uses: mlugg/setup-zig@v2 - with: - version: 0.15.1 + version: ${{ matrix.zig-version }} - name: Build test ffi lib run: zig build-lib -dynamic tests/utils/foreign.zig && mv libforeign.* tests/utils/ @@ -136,6 +58,7 @@ jobs: run: zig build -Doptimize=ReleaseFast -Djit_always_on test && zig build -Doptimize=ReleaseFast -Djit_always_on test-behavior - name: Cleanup run: rm -rf zig-out zig-cache + wasm-build: runs-on: ubuntu-latest steps: @@ -146,11 +69,12 @@ jobs: - name: Setup nightly Zig uses: mlugg/setup-zig@v2 with: - version: 0.15.1 + version: master - name: Build for wasm run: zig build -Dtarget=wasm32-freestanding -freference-trace -Doptimize=ReleaseSmall - name: Cleanup run: rm -rf zig-out zig-cache + lint: runs-on: macos-latest steps: @@ -158,5 +82,5 @@ jobs: - name: Setup nightly Zig uses: mlugg/setup-zig@v2 with: - version: 0.15.1 + version: master - run: zig fmt --check src/*.zig src/**/*.zig diff --git a/README.md b/README.md index e075da4a..421c369f 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ A small/lightweight statically typed scripting language written in Zig ## How to build and install ### Requirements -- Zig 0.15.1 +- Zig 0.16.0-dev.732+2f3234c76 - Since this is built with Zig, you should be able to build buzz on a wide variety of architectures even though this has only been tested on x86/M1. - Linux or macOS (Windows support [is coming](https://github.com/buzz-language/buzz/issues/74)) - libc diff --git a/build.zig b/build.zig index d97ace16..44a0f7a4 100644 --- a/build.zig +++ b/build.zig @@ -101,7 +101,7 @@ pub fn build(b: *Build) !void { // Check minimum zig version const current_zig = builtin.zig_version; - const min_zig = std.SemanticVersion.parse("0.15.1") catch return; + const min_zig = std.SemanticVersion.parse("0.16.0-dev.732+2f3234c76") catch return; if (current_zig.order(min_zig).compare(.lt)) { @panic(b.fmt("Your Zig version v{f} does not meet the minimum build requirement of v{f}", .{ current_zig, min_zig })); } From 677616ab49e6f05844c5afc6106d84e02210b786 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Thu, 16 Oct 2025 12:06:42 +0200 Subject: [PATCH 04/12] fix: Value, Obj and ObjTypeDef had all a toString function but not a format function `format` is used when doing `.print("{f}", .{value})` --- src/Reporter.zig | 19 ++++++++++++------ src/lib/buzz_api.zig | 38 ++++++++++++++++++------------------ src/obj.zig | 32 ++++++++++++++++++++---------- src/value.zig | 46 ++++++++++++++++++++++++-------------------- 4 files changed, 79 insertions(+), 56 deletions(-) diff --git a/src/Reporter.zig b/src/Reporter.zig index 94c10eab..f533bf8f 100644 --- a/src/Reporter.zig +++ b/src/Reporter.zig @@ -761,9 +761,13 @@ pub fn reportTypeCheck( } } - actual_message.writer.print("{s}: got type `", .{message}) catch @panic("Unable to report error"); - actual_type.toString(&actual_message.writer, false) catch @panic("Unable to report error"); - actual_message.writer.writeAll("`") catch @panic("Unable to report error"); + actual_message.writer.print( + "{s}: got type `{f}`", + .{ + message, + actual_type, + }, + ) catch @panic("Unable to report error"); var expected_message = std.Io.Writer.Allocating.init(self.allocator); defer { @@ -777,9 +781,12 @@ pub fn reportTypeCheck( else &actual_message; - following_message.writer.writeAll("expected `") catch @panic("Unable to report error"); - expected_type.toString(&following_message.writer, false) catch @panic("Unable to report error"); - following_message.writer.writeAll("`") catch @panic("Unable to report error"); + following_message.writer.print( + "expected `{f}`", + .{ + expected_type, + }, + ) catch @panic("Unable to report error"); var full_message = if (expected_location == null) actual_message diff --git a/src/lib/buzz_api.zig b/src/lib/buzz_api.zig index f70c9477..70928272 100644 --- a/src/lib/buzz_api.zig +++ b/src/lib/buzz_api.zig @@ -56,71 +56,71 @@ pub const Value = packed struct { // We only need this so that an NativeFn can see the error returned by its raw function pub const Error = Value{ .val = ErrorMask }; - pub inline fn fromBoolean(val: bool) Value { + pub fn fromBoolean(val: bool) Value { return if (val) True else False; } - pub inline fn fromInteger(val: Integer) Value { + pub fn fromInteger(val: Integer) Value { return .{ .val = IntegerMask | @as(u48, @bitCast(val)) }; } - pub inline fn fromDouble(val: Double) Value { + pub fn fromDouble(val: Double) Value { return .{ .val = @as(u64, @bitCast(val)) }; } - pub inline fn fromObj(val: *anyopaque) Value { + pub fn fromObj(val: *anyopaque) Value { return .{ .val = PointerMask | @intFromPtr(val) }; } - pub inline fn getTag(self: Value) u3 { + pub fn getTag(self: Value) u3 { return @intCast(@as(u32, @intCast(self.val >> 32)) & TagMask); } - pub inline fn isBool(self: Value) bool { + pub fn isBool(self: Value) bool { return self.val & (TaggedPrimitiveMask | SignMask) == BooleanMask; } - pub inline fn isInteger(self: Value) bool { + pub fn isInteger(self: Value) bool { return self.val & (TaggedUpperValueMask | SignMask) == IntegerMask; } - pub inline fn isFloat(self: Value) bool { + pub fn isFloat(self: Value) bool { return self.val & TaggedValueMask != TaggedValueMask; } - pub inline fn isNumber(self: Value) bool { + pub fn isNumber(self: Value) bool { return self.isFloat() or self.isInteger(); } - pub inline fn isObj(self: Value) bool { + pub fn isObj(self: Value) bool { return self.val & PointerMask == PointerMask; } - pub inline fn isNull(self: Value) bool { + pub fn isNull(self: Value) bool { return self.val == NullMask; } - pub inline fn isVoid(self: Value) bool { + pub fn isVoid(self: Value) bool { return self.val == VoidMask; } - pub inline fn isError(self: Value) bool { + pub fn isError(self: Value) bool { return self.val == ErrorMask; } - pub inline fn boolean(self: Value) bool { + pub fn boolean(self: Value) bool { return self.val == TrueMask; } - pub inline fn integer(self: Value) Integer { + pub fn integer(self: Value) Integer { return @bitCast(@as(u48, @intCast(self.val & 0xffffffffffff))); } - pub inline fn double(self: Value) Double { + pub fn double(self: Value) Double { return @bitCast(self.val); } - pub inline fn obj(self: Value) *anyopaque { + pub fn obj(self: Value) *anyopaque { return @ptrFromInt(@as(usize, @truncate(self.val & ~PointerMask))); } @@ -213,7 +213,7 @@ pub const VM = opaque { pub extern fn bz_stringToValue(vm: *VM, string: ?[*]const u8, len: usize) callconv(.c) Value; pub extern fn bz_stringToValueZ(vm: *VM, string: ?[*:0]const u8) callconv(.c) Value; pub extern fn bz_newUserData(vm: *VM, userdata: u64) callconv(.c) Value; - pub inline fn pushError(self: *VM, qualified_name: []const u8, message: ?[]const u8) void { + pub fn pushError(self: *VM, qualified_name: []const u8, message: ?[]const u8) void { self.bz_pushError( qualified_name.ptr, qualified_name.len, @@ -221,7 +221,7 @@ pub const VM = opaque { if (message) |m| m.len else 0, ); } - pub inline fn pushErrorEnum(self: *VM, qualified_name: []const u8, case: []const u8) void { + pub fn pushErrorEnum(self: *VM, qualified_name: []const u8, case: []const u8) void { self.bz_pushErrorEnum( qualified_name.ptr, qualified_name.len, diff --git a/src/obj.zig b/src/obj.zig index 29840a4b..7f617c8d 100644 --- a/src/obj.zig +++ b/src/obj.zig @@ -509,6 +509,10 @@ pub const Obj = struct { } } + pub fn format(obj: *Obj, w: *std.Io.Writer) std.Io.Writer.Error!void { + obj.toString(w) catch return error.WriteFailed; + } + pub fn toString(obj: *Obj, writer: *std.Io.Writer) (Allocator.Error || std.fmt.BufPrintError || error{WriteFailed})!void { return switch (obj.obj_type) { .String => { @@ -675,18 +679,22 @@ pub const Obj = struct { if (bound.closure) |closure| { const closure_name: []const u8 = closure.function.type_def.resolved_type.?.Function.name.string; - try writer.writeAll("bound method: "); - - try (bound.receiver).toString(writer); - - try writer.print(" to {s}", .{closure_name}); + try writer.print( + "bound method: {f} to {s}", + .{ + bound.receiver, + closure_name, + }, + ); } else { assert(bound.native != null); - try writer.writeAll("bound method: "); - - try (bound.receiver).toString(writer); - - try writer.print(" to native 0x{}", .{@intFromPtr(bound.native.?)}); + try writer.print( + "bound method: {f} to native 0x{}", + .{ + bound.receiver, + @intFromPtr(bound.native.?), + }, + ); } }, .Native => { @@ -4900,6 +4908,10 @@ pub const ObjTypeDef = struct { return try str.toOwnedSlice(); } + pub fn format(self: *Self, w: *std.Io.Writer) std.Io.Writer.Error!void { + self.toString(w, false) catch return error.WriteFailed; + } + pub fn toString(self: *const Self, writer: anytype, qualified: bool) (Allocator.Error || std.fmt.BufPrintError || error{WriteFailed})!void { try self.toStringRaw(writer, qualified); } diff --git a/src/value.zig b/src/value.zig index 6b40e4b0..0931ae64 100644 --- a/src/value.zig +++ b/src/value.zig @@ -47,87 +47,87 @@ pub const Value = packed struct { // We only need this so that an NativeFn can see the error returned by its raw function pub const Error = Value{ .val = ErrorMask }; - pub inline fn fromBoolean(val: bool) Value { + pub fn fromBoolean(val: bool) Value { return if (val) True else False; } - pub inline fn fromInteger(val: Integer) Value { + pub fn fromInteger(val: Integer) Value { return .{ .val = IntegerMask | @as(u48, @bitCast(val)) }; } - pub inline fn fromDouble(val: Double) Value { + pub fn fromDouble(val: Double) Value { return .{ .val = @as(u64, @bitCast(val)) }; } - pub inline fn fromObj(val: *o.Obj) Value { + pub fn fromObj(val: *o.Obj) Value { return .{ .val = PointerMask | @intFromPtr(val) }; } - pub inline fn getTag(self: Value) Tag { + pub fn getTag(self: Value) Tag { return @as(Tag, @intCast(@as(u32, @intCast(self.val >> 32)) & TagMask)); } - pub inline fn isBool(self: Value) bool { + pub fn isBool(self: Value) bool { return self.val & (TaggedPrimitiveMask | SignMask) == BooleanMask; } - pub inline fn isInteger(self: Value) bool { + pub fn isInteger(self: Value) bool { return self.val & (TaggedUpperValueMask | SignMask) == IntegerMask; } - pub inline fn isDouble(self: Value) bool { + pub fn isDouble(self: Value) bool { return !self.isBool() and !self.isError() and !self.isInteger() and !self.isNull() and !self.isObj() and !self.isVoid(); } - pub inline fn isNumber(self: Value) bool { + pub fn isNumber(self: Value) bool { return self.isDouble() or self.isInteger(); } - pub inline fn isObj(self: Value) bool { + pub fn isObj(self: Value) bool { return self.val & PointerMask == PointerMask; } - pub inline fn isNull(self: Value) bool { + pub fn isNull(self: Value) bool { return self.val == NullMask; } - pub inline fn isVoid(self: Value) bool { + pub fn isVoid(self: Value) bool { return self.val == VoidMask; } - pub inline fn isError(self: Value) bool { + pub fn isError(self: Value) bool { return self.val == ErrorMask; } - pub inline fn boolean(self: Value) bool { + pub fn boolean(self: Value) bool { return self.val == TrueMask; } - pub inline fn integer(self: Value) Integer { + pub fn integer(self: Value) Integer { return @bitCast(@as(u48, @intCast(self.val & 0xffffffffffff))); } - pub inline fn double(self: Value) Double { + pub fn double(self: Value) Double { return @bitCast(self.val); } - pub inline fn obj(self: Value) *o.Obj { + pub fn obj(self: Value) *o.Obj { return @ptrFromInt(@as(usize, @truncate(self.val & ~PointerMask))); } - pub inline fn booleanOrNull(self: Value) ?bool { + pub fn booleanOrNull(self: Value) ?bool { return if (self.isBool()) self.boolean() else null; } - pub inline fn integerOrNull(self: Value) ?Integer { + pub fn integerOrNull(self: Value) ?Integer { return if (self.isInteger()) self.integer() else null; } - pub inline fn doubleOrNull(self: Value) ?Double { + pub fn doubleOrNull(self: Value) ?Double { return if (self.isDouble()) self.double() else null; } - pub inline fn objOrNull(self: Value) ?*o.Obj { + pub fn objOrNull(self: Value) ?*o.Obj { return if (self.isObj()) self.obj() else null; } @@ -159,6 +159,10 @@ pub const Value = packed struct { return self; } + pub fn format(value: Value, w: *std.Io.Writer) std.Io.Writer.Error!void { + value.toString(w) catch return error.WriteFailed; + } + pub fn toStringAlloc(value: Value, allocator: Allocator) (Allocator.Error || std.fmt.BufPrintError || error{WriteFailed})![]const u8 { var str = std.Io.Writer.Allocating.init(allocator); From 6d96ea9981d7b0d4c51caa2a72ece2756989b609 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Thu, 16 Oct 2025 17:00:24 +0200 Subject: [PATCH 05/12] feat: DAP New debugging opcodes are introduced to keep track of locals and globals name at runtime Also rationalized build.zig - It was a mess: it's less so now - We now link buzz_lib statically to executables: only third party user of buzz_lib would link to it dynamically --- .gitignore | 2 + build.zig | 732 +++++++++++------------ build.zig.zon | 4 + src/Ast.zig | 15 +- src/Chunk.zig | 80 +-- src/Codegen.zig | 86 ++- src/Debugger.zig | 975 +++++++++++++++++++++++++++++++ src/Jit.zig | 6 +- src/Parser.zig | 40 +- src/Runner.zig | 172 ++++++ src/behavior.zig | 7 +- src/buzz_api.zig | 2 + src/disassembler.zig | 42 +- src/lsp.zig | 1 + src/main.zig | 177 +----- src/obj.zig | 26 +- src/repl.zig | 3 +- src/vm.zig | 217 ++++++- src/wasm_repl.zig | 2 + tests/manual/008-write-file.buzz | 22 + 20 files changed, 1970 insertions(+), 641 deletions(-) create mode 100644 src/Debugger.zig create mode 100644 src/Runner.zig create mode 100644 tests/manual/008-write-file.buzz diff --git a/.gitignore b/.gitignore index 19829e49..e9e53a83 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ tests/utils/libforeign.* buzz_history node_modules +out.txt +AGENTS.md diff --git a/build.zig b/build.zig index 44a0f7a4..93e3361f 100644 --- a/build.zig +++ b/build.zig @@ -2,99 +2,6 @@ const std = @import("std"); const builtin = @import("builtin"); const Build = std.Build; -const BuildOptions = struct { - version: std.SemanticVersion, - sha: []const u8, - mimalloc: bool, - debug: DebugOptions, - gc: GCOptions, - jit: JITOptions, - target: Build.ResolvedTarget, - cycle_limit: ?u128, - recursive_call_limit: ?u32, - stack_size: usize = 100_000, - - pub fn step(self: @This(), b: *Build) *Build.Module { - var options = b.addOptions(); - options.addOption(@TypeOf(self.version), "version", self.version); - options.addOption(@TypeOf(self.sha), "sha", self.sha); - options.addOption(@TypeOf(self.mimalloc), "mimalloc", self.mimalloc); - options.addOption(@TypeOf(self.cycle_limit), "cycle_limit", self.cycle_limit); - options.addOption(@TypeOf(self.recursive_call_limit), "recursive_call_limit", self.recursive_call_limit); - options.addOption(@TypeOf(self.stack_size), "stack_size", self.stack_size); - - self.debug.step(options); - self.gc.step(options); - self.jit.step(options); - - return options.createModule(); - } - - pub fn needLibC(self: @This()) bool { - return !self.target.result.cpu.arch.isWasm(); - } - - const DebugOptions = struct { - debug: bool, - stack: bool, - current_instruction: bool, - perf: bool, - stop_on_report: bool, - placeholders: bool, - type_registry: bool, - - pub fn step(self: DebugOptions, options: *Build.Step.Options) void { - options.addOption(@TypeOf(self.debug), "debug", self.debug); - options.addOption(@TypeOf(self.stack), "debug_stack", self.stack); - options.addOption(@TypeOf(self.current_instruction), "debug_current_instruction", self.current_instruction); - options.addOption(@TypeOf(self.perf), "show_perf", self.perf); - options.addOption(@TypeOf(self.stop_on_report), "stop_on_report", self.stop_on_report); - options.addOption(@TypeOf(self.placeholders), "debug_placeholders", self.placeholders); - options.addOption(@TypeOf(self.type_registry), "debug_type_registry", self.type_registry); - } - }; - - const JITOptions = struct { - on: bool, - always_on: bool, - hotspot_always_on: bool, - hotspot_on: bool, - debug: bool, - prof_threshold: f128 = 0.05, - - pub fn step(self: JITOptions, options: *Build.Step.Options) void { - options.addOption(@TypeOf(self.debug), "jit_debug", self.debug); - options.addOption(@TypeOf(self.always_on), "jit_always_on", self.always_on); - options.addOption(@TypeOf(self.hotspot_always_on), "jit_hotspot_always_on", self.hotspot_always_on); - options.addOption(@TypeOf(self.on), "jit", self.on); - options.addOption(@TypeOf(self.prof_threshold), "jit_prof_threshold", self.prof_threshold); - options.addOption(@TypeOf(self.hotspot_on), "jit_hotspot_on", self.hotspot_on); - } - }; - - const GCOptions = struct { - debug: bool, - debug_light: bool, - debug_access: bool, - on: bool, - initial_gc: usize, - next_gc_ratio: usize, - next_full_gc_ratio: usize, - memory_limit: ?usize, - - pub fn step(self: GCOptions, options: *Build.Step.Options) void { - options.addOption(@TypeOf(self.debug), "gc_debug", self.debug); - options.addOption(@TypeOf(self.debug_light), "gc_debug_light", self.debug_light); - options.addOption(@TypeOf(self.debug_access), "gc_debug_access", self.debug_access); - options.addOption(@TypeOf(self.on), "gc", self.on); - options.addOption(@TypeOf(self.initial_gc), "initial_gc", self.initial_gc); - options.addOption(@TypeOf(self.next_gc_ratio), "next_gc_ratio", self.next_gc_ratio); - options.addOption(@TypeOf(self.next_full_gc_ratio), "next_full_gc_ratio", self.next_full_gc_ratio); - options.addOption(@TypeOf(self.memory_limit), "memory_limit", self.memory_limit); - } - }; -}; - pub fn build(b: *Build) !void { var envMap = try std.process.getEnvMap(b.allocator); defer envMap.deinit(); @@ -263,24 +170,52 @@ pub fn build(b: *Build) !void { const build_option_module = build_options.step(b); - const lib_pcre2 = if (!is_wasm) - try buildPcre2(b, target, build_mode) - else - null; - const lib_mimalloc = if (build_options.mimalloc) - try buildMimalloc(b, target, build_mode) - else - null; - const lib_linenoise = if (!is_wasm and target.result.os.tag != .windows) - try buildLinenoise(b, target, build_mode) - else - null; - const lib_mir = if (!is_wasm) - try buildMir(b, target, build_mode) + // Build non-zig dependencies + const ext_deps: []const *std.Build.Step.Compile = if (!is_wasm and target.result.os.tag != .windows) + &.{ + try buildPcre2(b, target, build_mode), + try buildMimalloc(b, target, build_mode), + try buildLinenoise(b, target, build_mode), + try buildMir(b, target, build_mode), + } + else if (!is_wasm) + &.{ + try buildPcre2(b, target, build_mode), + try buildMimalloc(b, target, build_mode), + try buildMir(b, target, build_mode), + } else - null; + &.{}; + + // Zig dependencies + const clap = b.dependency( + "clap", + .{ + .target = target, + .optimize = build_mode, + }, + ); + + const lsp = b.dependency( + "lsp_kit", + .{ + .target = target, + .optimize = build_mode, + }, + ); + + const dap = b.dependency( + "dap_kit", + .{ + .target = target, + .optimize = build_mode, + }, + ); + + // Build executables - var exe = b.addExecutable( + // buzz + const exe = b.addExecutable( .{ .name = "buzz", .use_llvm = true, @@ -289,13 +224,31 @@ pub fn build(b: *Build) !void { .target = target, .optimize = build_mode, .root_source_file = b.path("src/main.zig"), + .sanitize_c = .off, }, ), }, ); + if (is_wasm) { + exe.global_base = 6560; + exe.entry = .disabled; + exe.rdynamic = true; + exe.import_memory = true; + exe.stack_size = std.wasm.page_size; + + exe.initial_memory = std.wasm.page_size * 100; + exe.max_memory = std.wasm.page_size * 1000; + } b.installArtifact(exe); + const run_exe = b.addRunArtifact(exe); + run_exe.step.dependOn(install_step); + if (b.args) |args| { + run_exe.addArgs(args); + } + b.step("run", "run buzz").dependOn(&run_exe.step); - const behavior_exe = if (!is_wasm) b.addExecutable( + // buzz_behavior + const behavior_exe = b.addExecutable( .{ .name = "buzz_behavior", .use_llvm = true, @@ -304,71 +257,65 @@ pub fn build(b: *Build) !void { .root_source_file = b.path("src/behavior.zig"), .target = target, .optimize = build_mode, + .sanitize_c = .off, }, ), }, - ) else null; - if (behavior_exe) |bexe| b.installArtifact(bexe); - - if (behavior_exe) |bexe| { - const run_behavior = b.addRunArtifact(bexe); - run_behavior.step.dependOn(install_step); - b.step("test-behavior", "Test behavior").dependOn(&run_behavior.step); - } + ); + b.installArtifact(behavior_exe); + const run_behavior = b.addRunArtifact(behavior_exe); + run_behavior.step.dependOn(install_step); + b.step("test-behavior", "Test behavior").dependOn(&run_behavior.step); - const clap = b.dependency( - "clap", + // buzz_debugger + const debugger_exe = b.addExecutable( .{ - .target = target, - .optimize = build_mode, + .name = "buzz_debugger", + .use_llvm = true, + .root_module = b.createModule( + .{ + .root_source_file = b.path("src/Debugger.zig"), + .target = target, + .optimize = build_mode, + .sanitize_c = .off, + }, + ), }, ); + b.installArtifact(debugger_exe); + const run_debugger = b.addRunArtifact(debugger_exe); + run_debugger.step.dependOn(install_step); + b.step("run-debugger", "Run the debugger").dependOn(&run_debugger.step); - exe.root_module.addImport( - "clap", - clap.module("clap"), + // buzz_lsp + const lsp_exe = b.addExecutable( + .{ + .name = "buzz_lsp", + .use_llvm = true, + .root_module = b.createModule( + .{ + .root_source_file = b.path("src/lsp.zig"), + .target = target, + .optimize = build_mode, + .sanitize_c = .off, + }, + ), + }, ); - - var lsp_exe = if (!is_wasm) - b.addExecutable( - .{ - .name = "buzz_lsp", - .use_llvm = true, - .root_module = b.createModule( - .{ - .root_source_file = b.path("src/lsp.zig"), - .target = target, - .optimize = build_mode, - }, - ), - }, - ) - else - null; - - if (!is_wasm) { - b.installArtifact(lsp_exe.?); - - lsp_exe.?.root_module.addImport( - "clap", - clap.module("clap"), - ); - - const lsp = b.dependency( - "lsp_kit", - .{ - .target = target, - .optimize = build_mode, - }, - ); - - lsp_exe.?.root_module.addImport( - "lsp", - lsp.module("lsp"), - ); + lsp_exe.root_module.addImport( + "lsp", + lsp.module("lsp"), + ); + b.installArtifact(lsp_exe); + const run_lsp_exe = b.addRunArtifact(lsp_exe); + run_lsp_exe.step.dependOn(install_step); + if (b.args) |args| { + run_lsp_exe.addArgs(args); } + b.step("lsp", "run buzz lsp").dependOn(&run_lsp_exe.step); - var exe_check = b.addExecutable( + // check (exe not installed) + const check_exe = b.addExecutable( .{ .name = "buzz", .use_llvm = true, @@ -377,262 +324,194 @@ pub fn build(b: *Build) !void { .root_source_file = b.path("src/main.zig"), .target = target, .optimize = build_mode, + .sanitize_c = .off, }, ), }, ); + const check = b.step("check", "Check if buzz compiles"); + check.dependOn(&check_exe.step); - exe_check.root_module.addImport( - "clap", - clap.module("clap"), + // Building buzz api library + const lib = b.addLibrary( + .{ + .name = "buzz", + .linkage = .dynamic, + .use_llvm = true, + .root_module = b.createModule( + .{ + .root_source_file = b.path("src/buzz_api.zig"), + .target = target, + .optimize = build_mode, + }, + ), + }, ); + b.installArtifact(lib); - exe.root_module.sanitize_c = .off; - if (behavior_exe) |bexe| bexe.root_module.sanitize_c = .off; - if (!is_wasm) lsp_exe.?.root_module.sanitize_c = .off; + const static_lib = b.addLibrary( + .{ + .name = "buzz", + .linkage = .static, + .use_llvm = true, + .root_module = b.createModule( + .{ + .root_source_file = b.path("src/buzz_api.zig"), + .target = target, + .optimize = build_mode, + }, + ), + }, + ); + b.installArtifact(static_lib); - const check = b.step("check", "Check if buzz compiles"); - check.dependOn(&exe_check.step); + // Test + const tests = b.addTest( + .{ + .use_llvm = true, + .root_module = b.createModule( + .{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = build_mode, + .sanitize_c = .off, + }, + ), + }, + ); + tests.root_module.addImport("build_options", build_option_module); + const test_step = b.step("test", "Run all the tests"); + const run_tests = b.addRunArtifact(tests); + run_tests.cwd = b.path("."); + run_tests.setEnvironmentVariable("BUZZ_PATH", envMap.get("BUZZ_PATH") orelse std.fs.path.dirname(b.exe_dir).?); + run_tests.step.dependOn(install_step); // wait for libraries to be installed + test_step.dependOn(&run_tests.step); - if (is_wasm) { - exe.global_base = 6560; - exe.entry = .disabled; - exe.rdynamic = true; - exe.import_memory = true; - exe.stack_size = std.wasm.page_size; + for ([_]*std.Build.Step.Compile{ static_lib, lib, exe, behavior_exe, debugger_exe, lsp_exe, check_exe, tests }) |c| { + // BuildOptions + c.root_module.addImport("build_options", build_option_module); - exe.initial_memory = std.wasm.page_size * 100; - exe.max_memory = std.wasm.page_size * 1000; + // Link non-zig deps to executables and library + for (ext_deps) |dep| { + c.linkLibrary(dep); + + if (target.result.os.tag == .windows) { + c.linkSystemLibrary("bcrypt"); + } + + if (build_options.needLibC()) { + c.linkLibC(); + } + } } - const run_exe = b.addRunArtifact(exe); - run_exe.step.dependOn(install_step); - if (b.args) |args| { - run_exe.addArgs(args); + // So that JIT compiled function can reference buzz_api + for ([_]*std.Build.Step.Compile{ exe, behavior_exe, debugger_exe, lsp_exe, check_exe }) |c| { + c.linkLibrary(static_lib); } - b.step("run", "run buzz").dependOn(&run_exe.step); - if (!is_wasm) { - const run_lsp_exe = b.addRunArtifact(lsp_exe.?); - run_lsp_exe.step.dependOn(install_step); - if (b.args) |args| { - run_lsp_exe.addArgs(args); - } - b.step("lsp", "run buzz lsp").dependOn(&run_lsp_exe.step); + for ([_]*std.Build.Step.Compile{ static_lib, lib, exe, debugger_exe, lsp_exe, behavior_exe, check_exe }) |c| { + c.root_module.addImport( + "dap", + dap.module("dap_kit"), + ); } - if (build_options.needLibC()) { - exe.linkLibC(); - exe_check.linkLibC(); - if (behavior_exe) |bexe| bexe.linkLibC(); - if (!is_wasm) lsp_exe.?.linkLibC(); + for ([_]*std.Build.Step.Compile{ exe, debugger_exe, lsp_exe, check_exe }) |c| { + c.root_module.addImport( + "clap", + clap.module("clap"), + ); } - exe.root_module.addImport("build_options", build_option_module); - exe_check.root_module.addImport("build_options", build_option_module); - if (behavior_exe) |bexe| bexe.root_module.addImport("build_options", build_option_module); - if (!is_wasm) lsp_exe.?.root_module.addImport("build_options", build_option_module); + for ([_]*std.Build.Step.Compile{ exe, debugger_exe, lsp_exe }) |c| { + b.default_step.dependOn(&c.step); + } - if (!is_wasm) { - // Building buzz api library - var lib = b.addLibrary( + // Building std libraries + const Lib = struct { + path: ?[]const u8 = null, + name: []const u8, + wasm_compatible: bool = true, + }; + + const libraries = [_]Lib{ + .{ .name = "std", .path = "src/lib/buzz_std.zig" }, + .{ .name = "io", .path = "src/lib/buzz_io.zig", .wasm_compatible = false }, + .{ .name = "gc", .path = "src/lib/buzz_gc.zig" }, + .{ .name = "os", .path = "src/lib/buzz_os.zig", .wasm_compatible = false }, + .{ .name = "fs", .path = "src/lib/buzz_fs.zig", .wasm_compatible = false }, + .{ .name = "math", .path = "src/lib/buzz_math.zig" }, + .{ .name = "debug", .path = "src/lib/buzz_debug.zig" }, + .{ .name = "buffer", .path = "src/lib/buzz_buffer.zig" }, + .{ .name = "crypto", .path = "src/lib/buzz_crypto.zig" }, + // FIXME: API has changed + // .{ .name = "http", .path = "src/lib/buzz_http.zig", .wasm_compatible = false }, + .{ .name = "ffi", .path = "src/lib/buzz_ffi.zig", .wasm_compatible = false }, + .{ .name = "serialize", .path = "src/lib/buzz_serialize.zig" }, + .{ .name = "testing" }, + .{ .name = "errors" }, + }; + + for (libraries) |library| { + // Copy buzz definitions + const step = b.addInstallLibFile( + b.path( + b.fmt( + "src/lib/{s}.buzz", + .{library.name}, + ), + ), + b.fmt( + "buzz/{s}.buzz", + .{library.name}, + ), + ); + install_step.dependOn(&step.step); + + if (library.path == null or (!library.wasm_compatible and is_wasm)) { + continue; + } + + var std_lib = b.addLibrary( .{ - .name = "buzz", + .name = library.name, .linkage = .dynamic, .use_llvm = true, .root_module = b.createModule( .{ - .root_source_file = b.path("src/buzz_api.zig"), + .root_source_file = b.path(library.path.?), .target = target, .optimize = build_mode, + .sanitize_c = .off, }, ), }, ); - b.installArtifact(lib); - - if (build_options.needLibC()) { - lib.linkLibC(); - } - - lib.root_module.addImport( - "build_options", - build_option_module, + std_lib.root_module.addImport( + "dap", + dap.module("dap_kit"), ); - if (lib_pcre2) |pcre| { - lib.linkLibrary(pcre); - exe.linkLibrary(pcre); - if (behavior_exe) |bexe| bexe.linkLibrary(pcre); - if (!is_wasm) lsp_exe.?.linkLibrary(pcre); - } + const artifact = b.addInstallArtifact(std_lib, .{}); + install_step.dependOn(&artifact.step); + artifact.dest_dir = .{ .custom = "lib/buzz" }; - if (lib_mimalloc) |mimalloc| { - lib.linkLibrary(mimalloc); - exe.linkLibrary(mimalloc); - if (behavior_exe) |bexe| bexe.linkLibrary(mimalloc); - if (!is_wasm) lsp_exe.?.linkLibrary(mimalloc); - if (lib.root_module.resolved_target.?.result.os.tag == .windows) { - lib.linkSystemLibrary("bcrypt"); - exe.linkSystemLibrary("bcrypt"); - if (behavior_exe) |bexe| bexe.linkSystemLibrary("bcrypt"); - if (!is_wasm) lsp_exe.?.linkSystemLibrary("bcrypt"); - } - } - - if (lib_mir) |mir| { - lib.linkLibrary(mir); - exe.linkLibrary(mir); - if (behavior_exe) |bexe| bexe.linkLibrary(mir); - if (!is_wasm) lsp_exe.?.linkLibrary(mir); + // No need to link anything when building for wasm since everything is static + if (build_options.needLibC()) { + std_lib.linkLibC(); } - // So that JIT compiled function can reference buzz_api - exe.linkLibrary(lib); - if (behavior_exe) |bexe| bexe.linkLibrary(lib); - if (!is_wasm) lsp_exe.?.linkLibrary(lib); - exe_check.linkLibrary(lib); - if (lib_linenoise) |ln| { - exe.linkLibrary(ln); - if (behavior_exe) |bexe| bexe.linkLibrary(ln); - if (!is_wasm) lsp_exe.?.linkLibrary(ln); - exe_check.linkLibrary(ln); + for (ext_deps) |dep| { + std_lib.linkLibrary(dep); } - b.default_step.dependOn(&exe.step); - if (!is_wasm) b.default_step.dependOn(&lsp_exe.?.step); - b.default_step.dependOn(&lib.step); - - // Building std libraries - const Lib = struct { - path: ?[]const u8 = null, - name: []const u8, - wasm_compatible: bool = true, - }; - - const libraries = [_]Lib{ - .{ .name = "std", .path = "src/lib/buzz_std.zig" }, - .{ .name = "io", .path = "src/lib/buzz_io.zig", .wasm_compatible = false }, - .{ .name = "gc", .path = "src/lib/buzz_gc.zig" }, - .{ .name = "os", .path = "src/lib/buzz_os.zig", .wasm_compatible = false }, - .{ .name = "fs", .path = "src/lib/buzz_fs.zig", .wasm_compatible = false }, - .{ .name = "math", .path = "src/lib/buzz_math.zig" }, - .{ .name = "debug", .path = "src/lib/buzz_debug.zig" }, - .{ .name = "buffer", .path = "src/lib/buzz_buffer.zig" }, - .{ .name = "crypto", .path = "src/lib/buzz_crypto.zig" }, - // FIXME: API has changed - // .{ .name = "http", .path = "src/lib/buzz_http.zig", .wasm_compatible = false }, - .{ .name = "ffi", .path = "src/lib/buzz_ffi.zig", .wasm_compatible = false }, - .{ .name = "serialize", .path = "src/lib/buzz_serialize.zig" }, - .{ .name = "testing" }, - .{ .name = "errors" }, - }; - - var library_steps = std.ArrayList(*std.Build.Step.Compile){}; - for (libraries) |library| { - // Copy buzz definitions - const step = b.addInstallLibFile( - b.path( - b.fmt( - "src/lib/{s}.buzz", - .{library.name}, - ), - ), - b.fmt( - "buzz/{s}.buzz", - .{library.name}, - ), - ); - install_step.dependOn(&step.step); - - if (library.path == null or (!library.wasm_compatible and is_wasm)) { - continue; - } - - var std_lib = b.addLibrary( - .{ - .name = library.name, - .linkage = .dynamic, - .use_llvm = true, - .root_module = b.createModule( - .{ - .root_source_file = b.path(library.path.?), - .target = target, - .optimize = build_mode, - }, - ), - }, - ); - - const artifact = b.addInstallArtifact(std_lib, .{}); - install_step.dependOn(&artifact.step); - artifact.dest_dir = .{ .custom = "lib/buzz" }; + std_lib.linkLibrary(static_lib); + std_lib.root_module.addImport("build_options", build_option_module); - // No need to link anything when building for wasm since everything is static - if (build_options.needLibC()) { - std_lib.linkLibC(); - } - - if (lib_pcre2) |pcre| { - std_lib.linkLibrary(pcre); - } - - if (lib_mimalloc) |mimalloc| { - std_lib.linkLibrary(mimalloc); - if (std_lib.root_module.resolved_target.?.result.os.tag == .windows) { - std_lib.linkSystemLibrary("bcrypt"); - } - } - - if (lib_mir) |mir| { - std_lib.linkLibrary(mir); - } - - std_lib.linkLibrary(lib); - std_lib.root_module.addImport("build_options", build_option_module); - - b.default_step.dependOn(&std_lib.step); - - library_steps.append(b.allocator, std_lib) catch unreachable; - } + b.default_step.dependOn(&std_lib.step); } - - const tests = b.addTest( - .{ - .use_llvm = true, - .root_module = b.createModule( - .{ - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = build_mode, - }, - ), - }, - ); - if (build_options.needLibC()) { - tests.linkLibC(); - } - if (lib_pcre2) |pcre| { - tests.linkLibrary(pcre); - } - if (lib_mimalloc) |mimalloc| { - tests.linkLibrary(mimalloc); - if (tests.root_module.resolved_target.?.result.os.tag == .windows) { - tests.linkSystemLibrary("bcrypt"); - } - } - if (lib_mir) |mir| { - tests.linkLibrary(mir); - } - tests.root_module.addImport("build_options", build_option_module); - - const test_step = b.step("test", "Run all the tests"); - const run_tests = b.addRunArtifact(tests); - run_tests.cwd = b.path("."); - run_tests.setEnvironmentVariable("BUZZ_PATH", envMap.get("BUZZ_PATH") orelse std.fs.path.dirname(b.exe_dir).?); - run_tests.step.dependOn(install_step); // wait for libraries to be installed - test_step.dependOn(&run_tests.step); } pub fn buildPcre2(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) !*Build.Step.Compile { @@ -891,3 +770,96 @@ pub fn buildMir(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin.O return lib; } + +const BuildOptions = struct { + version: std.SemanticVersion, + sha: []const u8, + mimalloc: bool, + debug: DebugOptions, + gc: GCOptions, + jit: JITOptions, + target: Build.ResolvedTarget, + cycle_limit: ?u128, + recursive_call_limit: ?u32, + stack_size: usize = 100_000, + + pub fn step(self: @This(), b: *Build) *Build.Module { + var options = b.addOptions(); + options.addOption(@TypeOf(self.version), "version", self.version); + options.addOption(@TypeOf(self.sha), "sha", self.sha); + options.addOption(@TypeOf(self.mimalloc), "mimalloc", self.mimalloc); + options.addOption(@TypeOf(self.cycle_limit), "cycle_limit", self.cycle_limit); + options.addOption(@TypeOf(self.recursive_call_limit), "recursive_call_limit", self.recursive_call_limit); + options.addOption(@TypeOf(self.stack_size), "stack_size", self.stack_size); + + self.debug.step(options); + self.gc.step(options); + self.jit.step(options); + + return options.createModule(); + } + + pub fn needLibC(self: @This()) bool { + return !self.target.result.cpu.arch.isWasm(); + } + + const DebugOptions = struct { + debug: bool, + stack: bool, + current_instruction: bool, + perf: bool, + stop_on_report: bool, + placeholders: bool, + type_registry: bool, + + pub fn step(self: DebugOptions, options: *Build.Step.Options) void { + options.addOption(@TypeOf(self.debug), "debug", self.debug); + options.addOption(@TypeOf(self.stack), "debug_stack", self.stack); + options.addOption(@TypeOf(self.current_instruction), "debug_current_instruction", self.current_instruction); + options.addOption(@TypeOf(self.perf), "show_perf", self.perf); + options.addOption(@TypeOf(self.stop_on_report), "stop_on_report", self.stop_on_report); + options.addOption(@TypeOf(self.placeholders), "debug_placeholders", self.placeholders); + options.addOption(@TypeOf(self.type_registry), "debug_type_registry", self.type_registry); + } + }; + + const JITOptions = struct { + on: bool, + always_on: bool, + hotspot_always_on: bool, + hotspot_on: bool, + debug: bool, + prof_threshold: f128 = 0.05, + + pub fn step(self: JITOptions, options: *Build.Step.Options) void { + options.addOption(@TypeOf(self.debug), "jit_debug", self.debug); + options.addOption(@TypeOf(self.always_on), "jit_always_on", self.always_on); + options.addOption(@TypeOf(self.hotspot_always_on), "jit_hotspot_always_on", self.hotspot_always_on); + options.addOption(@TypeOf(self.on), "jit", self.on); + options.addOption(@TypeOf(self.prof_threshold), "jit_prof_threshold", self.prof_threshold); + options.addOption(@TypeOf(self.hotspot_on), "jit_hotspot_on", self.hotspot_on); + } + }; + + const GCOptions = struct { + debug: bool, + debug_light: bool, + debug_access: bool, + on: bool, + initial_gc: usize, + next_gc_ratio: usize, + next_full_gc_ratio: usize, + memory_limit: ?usize, + + pub fn step(self: GCOptions, options: *Build.Step.Options) void { + options.addOption(@TypeOf(self.debug), "gc_debug", self.debug); + options.addOption(@TypeOf(self.debug_light), "gc_debug_light", self.debug_light); + options.addOption(@TypeOf(self.debug_access), "gc_debug_access", self.debug_access); + options.addOption(@TypeOf(self.on), "gc", self.on); + options.addOption(@TypeOf(self.initial_gc), "initial_gc", self.initial_gc); + options.addOption(@TypeOf(self.next_gc_ratio), "next_gc_ratio", self.next_gc_ratio); + options.addOption(@TypeOf(self.next_full_gc_ratio), "next_full_gc_ratio", self.next_full_gc_ratio); + options.addOption(@TypeOf(self.memory_limit), "memory_limit", self.memory_limit); + } + }; +}; diff --git a/build.zig.zon b/build.zig.zon index dc6a6822..cb47c575 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -12,6 +12,10 @@ .url = "git+https://github.com/zigtools/lsp-kit.git#6274eebace9a6a82ce182e24468fef88e0b95f37", .hash = "lsp_kit-0.1.0-bi_PLzAyCgClDh8_M0U9Q50ysdsQBuRuBTZfwg6rZPd6", }, + .dap_kit = .{ + .url = "../dap-kit", + .hash = "dap_kit-0.0.0-OvEgyI5jAgAV-WFDuv3llJSM-P_DW4u9ATM2rhpm9gNE", + }, }, .paths = .{""}, } diff --git a/src/Ast.zig b/src/Ast.zig index 53af31d7..7e20e6a8 100644 --- a/src/Ast.zig +++ b/src/Ast.zig @@ -919,17 +919,13 @@ pub fn slice(self: Self) Slice { }; } -pub inline fn appendNode(self: *Self, node: Node) !Node.Index { +pub fn appendNode(self: *Self, node: Node) !Node.Index { try self.nodes.append(self.allocator, node); return @intCast(self.nodes.len - 1); } -pub inline fn appendToken(self: *Self, token: Token) !TokenIndex { - // if (token.tag == .Semicolon and self.tokens.items(.tag)[self.tokens.len - 1] == .Semicolon) { - // unreachable; - // } - +pub fn appendToken(self: *Self, token: Token) !TokenIndex { try self.tokens.append(self.allocator, token); return @intCast(self.tokens.len - 1); @@ -943,6 +939,11 @@ pub fn swapNodes(self: *Self, from: Node.Index, to: Node.Index) void { self.nodes.set(to, from_node); } +pub const Close = struct { + opcode: Chunk.OpCode, + slot: u8, +}; + pub const Node = struct { tag: Tag, /// First token of this node @@ -957,7 +958,7 @@ pub const Node = struct { /// Wether optional jumps must be patch before generate this node bytecode patch_opt_jumps: bool = false, /// Does this node closes a scope - ends_scope: ?[]const Chunk.OpCode = null, + ends_scope: ?[]const Close = null, /// Data related to this node components: Components, diff --git a/src/Chunk.zig b/src/Chunk.zig index 8098e564..1cf2d078 100644 --- a/src/Chunk.zig +++ b/src/Chunk.zig @@ -4,6 +4,42 @@ const Value = @import("value.zig").Value; const VM = @import("vm.zig").VM; const Token = @import("Token.zig"); +allocator: std.mem.Allocator, +/// AST +ast: Ast.Slice, +/// List of opcodes to execute +code: std.ArrayList(u32) = .empty, +/// List of locations +locations: std.ArrayList(Ast.TokenIndex) = .empty, +/// List of constants defined in this chunk +constants: std.ArrayList(Value) = .empty, + +pub fn init(allocator: std.mem.Allocator, ast: Ast.Slice) Self { + return Self{ + .allocator = allocator, + .ast = ast, + }; +} + +pub fn deinit(self: *Self) void { + self.code.deinit(self.allocator); + self.constants.deinit(self.allocator); + self.locations.deinit(self.allocator); +} + +pub fn write(self: *Self, code: u32, where: Ast.TokenIndex) !void { + try self.code.append(self.allocator, code); + try self.locations.append(self.allocator, where); +} + +pub fn addConstant(self: *Self, vm: ?*VM, value: Value) !u24 { + if (vm) |uvm| uvm.push(value); + try self.constants.append(self.allocator, value); + if (vm) |uvm| _ = uvm.pop(); + + return @intCast(self.constants.items.len - 1); +} + pub const OpCode = enum(u8) { OP_CONSTANT, OP_NULL, @@ -138,6 +174,10 @@ pub const OpCode = enum(u8) { OP_HOTSPOT, OP_HOTSPOT_CALL, + + OP_DBG_LOCAL_ENTER, + OP_DBG_LOCAL_EXIT, + OP_DBG_GLOBAL_DEFINE, }; /// A chunk of code to execute @@ -167,43 +207,3 @@ pub fn HashMap(V: type) type { std.hash_map.default_max_load_percentage, ); } - -allocator: std.mem.Allocator, -/// AST -ast: Ast.Slice, -// TODO: merge `code` and `lines` in a multiarray -/// List of opcodes to execute -code: std.ArrayList(u32), -/// List of locations -lines: std.ArrayList(Ast.TokenIndex), -/// List of constants defined in this chunk -constants: std.ArrayList(Value), - -pub fn init(allocator: std.mem.Allocator, ast: Ast.Slice) Self { - return Self{ - .allocator = allocator, - .ast = ast, - .code = .{}, - .constants = .{}, - .lines = .{}, - }; -} - -pub fn deinit(self: *Self) void { - self.code.deinit(self.allocator); - self.constants.deinit(self.allocator); - self.lines.deinit(self.allocator); -} - -pub fn write(self: *Self, code: u32, where: Ast.TokenIndex) !void { - try self.code.append(self.allocator, code); - try self.lines.append(self.allocator, where); -} - -pub fn addConstant(self: *Self, vm: ?*VM, value: Value) !u24 { - if (vm) |uvm| uvm.push(value); - try self.constants.append(self.allocator, value); - if (vm) |uvm| _ = uvm.pop(); - - return @intCast(self.constants.items.len - 1); -} diff --git a/src/Codegen.zig b/src/Codegen.zig index 10762622..163c31d4 100644 --- a/src/Codegen.zig +++ b/src/Codegen.zig @@ -68,6 +68,8 @@ opt_jumps: std.ArrayList(std.ArrayList(usize)) = .{}, /// Used to generate error messages parser: *Parser, jit: ?*JIT, +/// Wether we are debugging the program +debugging: bool, reporter: Reporter, @@ -142,6 +144,7 @@ pub fn init( parser: *Parser, flavor: RunFlavor, jit: ?*JIT, + debugging: bool, ) Self { return .{ .gc = gc, @@ -153,6 +156,7 @@ pub fn init( .collect = flavor == .Ast, }, .jit = jit, + .debugging = debugging, }; } @@ -400,8 +404,9 @@ fn endScope(self: *Self, node: Ast.Node.Index) Error!void { const location = self.ast.nodes.items(.location)[node]; if (self.ast.nodes.items(.ends_scope)[node]) |closing| { - for (closing) |op| { - try self.emitOpCode(location, op); + for (closing) |cls| { + try self.emitOpCode(location, cls.opcode); + try self.OP_DBG_LOCAL_EXIT(location, cls.slot); } } } @@ -2081,6 +2086,7 @@ fn generateEnum(self: *Self, node: Ast.Node.Index, _: ?*Breaks) Error!?*obj.ObjF try self.OP_DEFINE_GLOBAL( locations[node], @intCast(components.slot), + (try self.gc.copyString(self.ast.tokens.items(.lexeme)[components.name])).toValue(), ); try self.patchOptJumps(node); @@ -2702,6 +2708,13 @@ fn generateFunction(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!? var index: usize = 0; while (index < components.entry.?.exported_count) : (index += 1) { try self.OP_GET_GLOBAL(locations[node], @intCast(index)); + + if (self.debugging) { + try self.emitConstant( + locations[node], + Value.fromInteger(@intCast(index)), + ); + } } try self.OP_EXPORT(locations[node], @intCast(components.entry.?.exported_count)); @@ -2718,7 +2731,7 @@ fn generateFunction(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!? { // Repl and last expression is a lone statement, remove OP_POP, add OP_RETURN std.debug.assert(vm.VM.getCode(self.current.?.function.?.chunk.code.pop().?) == .OP_POP); - _ = self.current.?.function.?.chunk.lines.pop(); + _ = self.current.?.function.?.chunk.locations.pop(); try self.emitReturn(locations[node]); } else if (self.current.?.function.?.type_def.resolved_type.?.Function.return_type.def_type == .Void and !self.current.?.return_emitted) { @@ -2788,6 +2801,7 @@ fn generateFunDeclaration(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) E try self.OP_DEFINE_GLOBAL( self.ast.nodes.items(.location)[node], @intCast(components.slot), + self.ast.nodes.items(.type_def)[node].?.resolved_type.?.Function.name.toValue(), ); } @@ -3467,7 +3481,11 @@ fn generateObjectDeclaration(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks // Put object on the stack and define global with it try self.OP_OBJECT(location, object_type.toValue()); - try self.OP_DEFINE_GLOBAL(location, @intCast(components.slot)); + try self.OP_DEFINE_GLOBAL( + location, + @intCast(components.slot), + (try self.gc.copyString(self.ast.tokens.items(.lexeme)[components.name])).toValue(), + ); // Put the object on the stack to set its fields try self.OP_GET_GLOBAL(location, @intCast(components.slot)); @@ -3784,7 +3802,11 @@ fn generateProtocolDeclaration(self: *Self, node: Ast.Node.Index, _: ?*Breaks) E const type_def = self.ast.nodes.items(.type_def)[node].?; try self.emitConstant(location, type_def.toValue()); - try self.OP_DEFINE_GLOBAL(location, @intCast(components.slot)); + try self.OP_DEFINE_GLOBAL( + location, + @intCast(components.slot), + (try self.gc.copyString(self.ast.tokens.items(.lexeme)[components.name])).toValue(), + ); try self.patchOptJumps(node); try self.endScope(node); @@ -4539,8 +4561,20 @@ fn generateVarDeclaration(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) E try self.OP_NULL(location); } - if (components.slot_type == .Global) { - try self.OP_DEFINE_GLOBAL(location, @intCast(components.slot)); + switch (components.slot_type) { + .Global => try self.OP_DEFINE_GLOBAL( + location, + @intCast(components.slot), + (try self.gc.copyString(self.ast.tokens.items(.lexeme)[components.name])).toValue(), + ), + .Local => { + try self.OP_DBG_LOCAL_ENTER( + location, + @intCast(components.slot), + (try self.gc.copyString(self.ast.tokens.items(.lexeme)[components.name])).toValue(), + ); + }, + .UpValue => {}, // TODO: ?? } try self.patchOptJumps(node); @@ -4719,7 +4753,11 @@ fn generateZdef(self: *Self, node: Ast.Node.Index, _: ?*Breaks) Error!?*obj.ObjF }, else => unreachable, } - try self.OP_DEFINE_GLOBAL(location, @intCast(element.slot)); + try self.OP_DEFINE_GLOBAL( + location, + @intCast(element.slot), + (try self.gc.copyString(self.ast.tokens.items(.lexeme)[components.lib_name])).toValue(), + ); } } @@ -4753,6 +4791,30 @@ pub fn populateEmptyCollectionType(self: *Self, value: Ast.Node.Index, target_ty } } +fn OP_DBG_LOCAL_ENTER(self: *Self, location: Ast.TokenIndex, slot: u8, name: Value) !void { + // Don't emit those if we are not debugging + if (!self.debugging) return; + + try self.emitOpCode(location, .OP_DBG_LOCAL_ENTER); + try self.emitTwo(location, slot, try self.makeConstant(name)); +} + +fn OP_DBG_LOCAL_EXIT(self: *Self, location: Ast.TokenIndex, slot: u8) !void { + // Don't emit those if we are not debugging + if (!self.debugging) return; + + try self.emitCodeArg(location, .OP_DBG_LOCAL_EXIT, slot); +} + +fn OP_DBG_GLOBAL_DEFINE(self: *Self, location: Ast.TokenIndex, slot: u24, name: Value) !void { + // Don't emit those if we are not debugging + if (!self.debugging) return; + + try self.emitOpCode(location, .OP_DBG_GLOBAL_DEFINE); + try self.emit(location, slot); + try self.emit(location, try self.makeConstant(name)); +} + fn OP_SWAP(self: *Self, location: Ast.TokenIndex, slotA: u8, slotB: u8) !void { try self.emitCodeArgs(location, .OP_SWAP, slotA, slotB); } @@ -4981,12 +5043,18 @@ fn OP_HOTSPOT_CALL(self: *Self, location: Ast.TokenIndex) !void { try self.emitOpCode(location, .OP_HOTSPOT_CALL); } -fn OP_DEFINE_GLOBAL(self: *Self, location: Ast.TokenIndex, slot: u24) !void { +fn OP_DEFINE_GLOBAL(self: *Self, location: Ast.TokenIndex, slot: u24, name: Value) !void { try self.emitCodeArg( location, .OP_DEFINE_GLOBAL, slot, ); + + try self.OP_DBG_GLOBAL_DEFINE( + location, + slot, + name, + ); } fn OP_GET_GLOBAL(self: *Self, location: Ast.TokenIndex, slot: u24) !void { diff --git a/src/Debugger.zig b/src/Debugger.zig new file mode 100644 index 00000000..576224cf --- /dev/null +++ b/src/Debugger.zig @@ -0,0 +1,975 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const BuildOptions = @import("build_options"); +const dap = @import("dap"); +const ProtocolMessage = dap.ProtocolMessage; +const Arguments = dap.Arguments; +const Response = dap.Response; +const Server = dap.Server; +const Adapter = dap.Adapter; +const clap = @import("clap"); +const io = @import("io.zig"); +const v = @import("vm.zig"); +const printBanner = @import("repl.zig").printBanner; +const VM = v.VM; +const Runner = @import("Runner.zig"); +const o = @import("obj.zig"); +const Value = @import("value.zig").Value; + +const Debugger = @This(); + +allocator: std.mem.Allocator, +/// Queues to/from debugger/debuggee +transport: Server.Transport, +/// Server runs in its thread and reads requests over TCP connection to the debugger +server_thread: std.Thread, +/// Adapter reads incoming requests from the +adapter: Adapter(Debugger), +/// Debug session +session: ?DebugSession = null, + +const Error = error{ + InvalidArguments, + LaunchFailed, + SessionAlreadyStarted, + SessionNotStarted, + OutOfMemory, + FiberNotFound, +}; + +const DebugSession = struct { + runner: Runner, + /// If true, program must terminate + run_state: ?RunState = .resumed, + /// List of breakpoints to enforce + breakpoints: std.ArrayHashMapUnmanaged( + BreakpointKey, + ProtocolMessage.Breakpoint, + BreakpointKey.AutoContext, + true, + ) = .empty, + /// Variable cache + variables: std.ArrayList(Variable) = .empty, + + pub fn resetVariables(self: *DebugSession, allocator: std.mem.Allocator) void { + self.variables.deinit(allocator); + } + + pub const RunState = enum { + paused, + terminated, + resumed, + }; + + pub fn deinit(self: *DebugSession) void { + self.breakpoints.deinit(self.runner.gc.allocator); + self.runner.deinit(); + } +}; + +const MapEntry = struct { + map_type: *o.ObjTypeDef, + key: Value, + value: Value, +}; + +const BoundEntry = struct { + receiver: Value, + method: Value, +}; + +const VariableValue = union(enum) { + value: Value, + map_entry: MapEntry, + bound_entry: BoundEntry, + scope: union(enum) { + global: void, + frame: *v.CallFrame, + }, +}; + +const Variable = struct { + value: VariableValue, + variable: union(enum) { + variable: ProtocolMessage.Variable, + scope: ProtocolMessage.Scope, + }, + children: ?[]const Variable = null, +}; + +const BreakpointKey = struct { + script: []const u8, + line: u32, + + pub const AutoContext = struct { + pub fn hash(_: AutoContext, bk: BreakpointKey) u32 { + var hasher = std.hash.Wyhash.init(0); + + hasher.update(bk.script); + hasher.update(std.mem.asBytes(&bk.line)); + + return @truncate(hasher.final()); + } + + pub fn eql(_: AutoContext, bk_a: BreakpointKey, bk_b: BreakpointKey, _: usize) bool { + return bk_a.line == bk_b.line and std.mem.eql(u8, bk_a.script, bk_b.script); + } + }; +}; + +pub fn start(self: *Debugger, allocator: std.mem.Allocator, address: std.net.Address) (std.Thread.SpawnError || error{OutOfMemory})!void { + self.* = Debugger{ + .allocator = allocator, + .transport = .{ + .from = try .initCapacity(allocator, 256), + .to = try .initCapacity(allocator, 256), + }, + .server_thread = undefined, + .adapter = undefined, + }; + + self.adapter = .{ + .handler = self, + .transport = &self.transport, + }; + + self.server_thread = try Server.spawn( + allocator, + address, + &self.transport, + ); +} + +/// Returns true if program has been terminated +pub fn onDispatch(self: *Debugger) bool { + _ = self.adapter.handleRequest(); + + if (self.session.?.run_state == .terminated) { + return true; + } else if (self.session.?.run_state != .paused) { + // Did we reach a breakpoint? + const current_frame = self.session.?.runner.vm.currentFrame(); + + if (current_frame != null and + current_frame.?.ip < current_frame.?.closure.function.chunk.locations.items.len) + { + const location = current_frame.?.closure.function.chunk.locations.items[current_frame.?.ip]; + const line = self.session.?.runner.vm.current_ast.tokens.items(.line)[location] + 1; + const script = self.session.?.runner.vm.current_ast.tokens.items(.script_name)[location]; + + if (self.session.?.breakpoints.getPtr( + .{ + .line = @intCast(line), + .script = script, + }, + )) |breakpoint| { + self.session.?.run_state = .paused; + + // Notifiy we stopped + self.adapter.emitEvent( + .{ + .event = .stopped, + .body = .{ + .stopped = .{ + .reason = .breakpoint, + .threadId = @intFromPtr(self.session.?.runner.vm.current_fiber), + .hitBreakpointIds = if (breakpoint.id) |bid| &.{bid} else null, + }, + }, + }, + ); + + // If breakpoint breviously unverified, notify that too + if (!breakpoint.verified) { + breakpoint.verified = true; + + self.adapter.emitEvent( + .{ + .event = .breakpoint, + .body = .{ + .breakpoint = .{ + .breakpoint = breakpoint.*, + .reason = .changed, + }, + }, + }, + ); + } + } + } + } + + return false; +} + +// +// Adapter callbacks +// + +pub fn initialize(_: *Debugger, _: Arguments(.initialize)) Error!Response(.initialize) { + std.log.debug("Handling `initialize request...`", .{}); + + return .{ + .supportsConfigurationDoneRequest = true, + }; +} + +// FIXME: we might need to copy any data we're getting from incoming messages? +// => implement deinit on each ResponseBody structs, or use an arena + +pub fn launch(self: *Debugger, arguments: Arguments(.launch)) Error!Response(.launch) { + // Launch arguments are implementation specific, we assume there's what we need + const launch_data = arguments.launch_data orelse return error.InvalidArguments; + const program = (launch_data.object.get("program") orelse return error.InvalidArguments).string; + + if (self.session != null) { + return error.SessionAlreadyStarted; + } + + self.session = .{ + .runner = undefined, + }; + + try self.session.?.variables.append( + self.allocator, + .{ + .variable = .{ + .scope = .{ + .name = "Globals", + .variablesReference = 1, + .expensive = false, + .namedVariables = 0, + }, + }, + .value = .{ + .scope = .{ + .global = {}, + }, + }, + }, + ); + + // While debugger is active, the program won't start right away + // FIXME: needs to report stdout of the program with `output` events + self.session.?.runner.runFile( + self.allocator, + program, + &.{}, // TODO + .Run, // TODO: we should be able to debug tests too + self, + ) catch return error.LaunchFailed; +} + +pub fn setBreakpoints(self: *Debugger, arguments: Arguments(.setBreakpoints)) Error!Response(.setBreakpoints) { + if (self.session) |*session| { + session.breakpoints.clearRetainingCapacity(); + + for (arguments.breakpoints orelse &.{}) |point| { + try session.breakpoints.put( + self.allocator, + .{ + .script = arguments.source.path orelse arguments.source.name.?, + .line = @intCast(point.line), + }, + .{ + .id = session.breakpoints.count(), + .verified = false, + .source = arguments.source, + .line = point.line, + }, + ); + } + + return .{ + .breakpoints = session.breakpoints.values(), + }; + } + + return error.SessionNotStarted; +} + +pub fn setExceptionBreakpoints(_: *Debugger, _: Arguments(.setExceptionBreakpoints)) Error!Response(.setExceptionBreakpoints) { + return .{}; +} + +pub fn threads(self: *Debugger, _: Arguments(.threads)) Error!Response(.threads) { + if (self.session) |*session| { + var thds = std.ArrayList(ProtocolMessage.Thread).empty; + + var fiber: ?*v.Fiber = session.runner.vm.current_fiber; + while (fiber) |fb| : (fiber = fb.parent_fiber) { + try thds.append( + self.allocator, + .{ + .id = @intFromPtr(fb), + .name = if (fb.frames.items.len > 0) + fb.frames.items[fb.frames.items.len - 1].closure.function.type_def.resolved_type.?.Function.name.string + else + "thread", + }, + ); + } + + return .{ + // FIXME: free this once sent + .threads = try thds.toOwnedSlice(self.allocator), + }; + } + + return error.SessionNotStarted; +} + +pub fn stackTrace(self: *Debugger, arguments: Arguments(.stackTrace)) Error!Response(.stackTrace) { + if (self.session) |*session| { + // Retrieve appropriate fiber + var fiber: ?*v.Fiber = session.runner.vm.current_fiber; + while (@intFromPtr(fiber) != arguments.threadId and fiber != null) : (fiber = fiber.?.parent_fiber) {} + + if (fiber) |fb| { + var stack_frames = std.ArrayList(ProtocolMessage.StackFrame).empty; + + var i = if (fb.frame_count > 0) fb.frame_count - 1 else fb.frame_count; + while (i >= 0) : (i -= 1) { + const frame = &fb.frames.items[i]; + const location = frame.closure.function.chunk.ast.tokens + .get(frame.closure.function.chunk.locations.items[frame.ip]); + + try stack_frames.append( + self.allocator, + .{ + .id = @intFromPtr(frame), + .name = frame.closure.function.type_def.resolved_type.?.Function.name.string, + .line = location.line + 1, + .column = location.column, + .source = .{ + .name = if (std.mem.lastIndexOf(u8, frame.closure.function.type_def.resolved_type.?.Function.script_name.string, "/")) |slash| + frame.closure.function.type_def.resolved_type.?.Function.script_name.string[slash + 1 ..] + else + frame.closure.function.type_def.resolved_type.?.Function.script_name.string, + .path = frame.closure.function.type_def.resolved_type.?.Function.script_name.string, + }, + }, + ); + + if (i == 0) break; + } + + const count = @min(stack_frames.items.len, arguments.levels orelse stack_frames.items.len); + return .{ + .stackFrames = (try stack_frames.toOwnedSlice(self.allocator))[0..count], + .totalFrames = count, + }; + } + + return error.FiberNotFound; + } + + return error.SessionNotStarted; +} + +pub fn scopes(self: *Debugger, args: Arguments(.scopes)) Error!Response(.scopes) { + if (self.session) |*session| { + var scps = try std.ArrayList(ProtocolMessage.Scope).initCapacity( + self.allocator, + 2, + ); + + // Locals of the current frame + var found = false; + for (session.variables.items) |vbl| { + if (vbl.value == .scope and vbl.value.scope == .frame and @intFromPtr(vbl.value.scope.frame) == args.frameId) { + try scps.append(self.allocator, vbl.variable.scope); + found = true; + break; + } + } + + if (!found) { + for (session.runner.vm.current_fiber.frames.items) |*frame| { + if (@intFromPtr(frame) == args.frameId) { + const scope = Variable{ + .value = .{ + .scope = .{ + .frame = frame, + }, + }, + .variable = .{ + .scope = .{ + .name = "Locals", + .variablesReference = session.variables.items.len + 1, + .expensive = false, + }, + }, + }; + + try session.variables.append(self.allocator, scope); + try scps.append(self.allocator, scope.variable.scope); + + break; + } + } + } + + // Globals might be defined out of order, we need to count those that are actually defined + var global_count: usize = 0; + for (session.runner.vm.globals_dbg.items) |name| { + if (!name.isNull()) { + global_count += 1; + } + } + + var global_scope = session.variables.items[0]; + global_scope.variable.scope.namedVariables = global_count; + + try scps.append(self.allocator, global_scope.variable.scope); + + return .{ + .scopes = try scps.toOwnedSlice(self.allocator), + }; + } + + return error.SessionNotStarted; +} + +pub fn variables(self: *Debugger, arguments: Arguments(.variables)) Error!Response(.variables) { + if (self.session) |*session| { + var result = std.ArrayList(ProtocolMessage.Variable).empty; + const ref = arguments.variablesReference - 1; + + if (session.variables.items.len > ref) { + if (session.variables.items[ref].children) |children| { + for (children) |child| { + try result.append(self.allocator, child.variable.variable); + } + } else { + const previous_len = session.variables.items.len; + + switch (session.variables.items[ref].value) { + .scope => |sc| switch (sc) { + .global => { + for (session.runner.vm.globals_dbg.items, 0..) |global, slot| { + const value = session.runner.vm.globals.items[slot]; + + if (!global.isNull()) { + try result.append( + self.allocator, + try self.variable( + value, + global, + null, + ), + ); + } + } + }, + .frame => |frame| { + const stack = @as([*]Value, @ptrCast(session.runner.vm.current_fiber.stack)); + const frame_base_idx = frame.slots - stack; + const top = if (session.runner.vm.currentFrame() == frame) + session.runner.vm.current_fiber.stack_top + else top: { + var idx: ?usize = 0; + for (session.runner.vm.current_fiber.frames.items, 0..) |*f, i| { + if (frame == f) { + idx = i; + break; + } + } + + std.debug.assert(idx != null and idx.? < session.runner.vm.current_fiber.frames.items.len); + + break :top session.runner.vm.current_fiber.frames.items[idx.? + 1].slots; + }; + const top_idx = top - stack; + + for (frame_base_idx..top_idx - 1) |idx| { + if (!session.runner.vm.current_fiber.locals_dbg.items[idx].isNull()) { + try result.append( + self.allocator, + try self.variable( + session.runner.vm.current_fiber.stack[idx + 1], + session.runner.vm.current_fiber.locals_dbg.items[idx], + null, + ), + ); + } + } + }, + }, + .value => |vl| { + try self.valueChildren( + vl, + &result, + ); + }, + .bound_entry => |be| { + try result.append( + self.allocator, + try self.variable( + be.receiver, + (session.runner.gc.copyString("receiver") catch return error.OutOfMemory).toValue(), + null, + ), + ); + + try result.append( + self.allocator, + try self.variable( + be.method, + (session.runner.gc.copyString("method") catch return error.OutOfMemory).toValue(), + null, + ), + ); + }, + .map_entry => |me| { + try result.append( + self.allocator, + try self.variable( + me.key, + (session.runner.gc.copyString("key") catch return error.OutOfMemory).toValue(), + me.map_type.resolved_type.?.Map.key_type, + ), + ); + + try result.append( + self.allocator, + try self.variable( + me.value, + (session.runner.gc.copyString("value") catch return error.OutOfMemory).toValue(), + me.map_type.resolved_type.?.Map.value_type, + ), + ); + }, + } + + // Populate children cache + session.variables.items[ref].children = session.variables.items[previous_len..]; + std.debug.assert(session.variables.items[ref].children.?.len == result.items.len); + } + } + + return .{ + .variables = try result.toOwnedSlice(self.allocator), + }; + } + + return error.SessionNotStarted; +} + +pub fn configurationDone(self: *Debugger, _: Arguments(.configurationDone)) Error!Response(.configurationDone) { + if (self.session == null) return error.SessionNotStarted; +} + +pub fn disconnect(self: *Debugger, _: Arguments(.disconnect)) Error!Response(.disconnect) { + if (self.session) |*session| { + session.run_state = .terminated; + } + + return error.SessionNotStarted; +} + +pub fn pause(self: *Debugger, _: Arguments(.pause)) Error!Response(.pause) { + if (self.session) |*session| { + session.run_state = .paused; + } + + return error.SessionNotStarted; +} + +fn variable(self: *Debugger, value: Value, name: Value, explicit_type_def: ?*o.ObjTypeDef) Error!ProtocolMessage.Variable { + const indexed_count = self.valueIndexedChildren(value); + const named_count = self.valueNamedChildren(value); + + const vbl = Variable{ + .value = val: { + if (value.isObj()) { + break :val switch (value.obj().obj_type) { + .Bound => .{ + .bound_entry = .{ + .receiver = o.ObjBoundMethod.cast(value.obj()).?.receiver, + .method = if (o.ObjBoundMethod.cast(value.obj()).?.closure) |cls| + cls.toValue() + else + o.ObjBoundMethod.cast(value.obj()).?.native.?.toValue(), + }, + }, + else => .{ + .value = value, + }, + }; + } + + break :val .{ + .value = value, + }; + }, + .variable = .{ + .variable = .{ + .name = name.obj().cast(o.ObjString, .String).?.string, + .evaluateName = name.obj().cast(o.ObjString, .String).?.string, + .value = value.toStringAlloc(self.allocator) catch "Could not get value", + .variablesReference = if (indexed_count + named_count > 0) + self.session.?.variables.items.len + 1 + else + 0, + .type = if (explicit_type_def) |type_def| + type_def.toStringAlloc( + self.allocator, + false, + ) catch null + else if (value.typeOf(&self.session.?.runner.gc)) |type_def| + type_def.toStringAlloc( + self.allocator, + false, + ) catch null + else |_| + null, + .indexedVariables = indexed_count, + .namedVariables = named_count, + }, + }, + }; + + try self.session.?.variables.append(self.allocator, vbl); + + return vbl.variable.variable; +} + +fn valueIndexedChildren(_: *Debugger, value: Value) u64 { + if (value.isObj()) { + return switch (value.obj().obj_type) { + .List => o.ObjList.cast(value.obj()).?.items.items.len, + else => 0, + }; + } + + return 0; +} + +fn valueNamedChildren(_: *Debugger, value: Value) u64 { + if (value.isObj()) { + return switch (value.obj().obj_type) { + .Object => obj: { + const fields = o.ObjObject.cast(value.obj()).? + .type_def.resolved_type.?.Object + .fields.values(); + var count: u64 = 0; + for (fields) |field| { + if (field.static and !field.method) { + count += 1; + } + } + + break :obj count; + }, + .ObjectInstance => obj: { + const fields = o.ObjObjectInstance.cast(value.obj()).? + .type_def.resolved_type.?.ObjectInstance.of + .resolved_type.?.Object + .fields.values(); + var count: u64 = 0; + for (fields) |field| { + if (!field.static and !field.method) { + count += 1; + } + } + + break :obj count; + }, + .Enum => o.ObjEnum.cast(value.obj()).?.cases.len, + .ForeignContainer => o.ObjForeignContainer.cast(value.obj()).? + .type_def.resolved_type.?.ForeignContainer + .fields.count(), + .Map => 2, // key, value + .Bound => 2, // receiver, method + else => 0, + }; + } + + return 0; +} + +fn valueChildren(self: *Debugger, value: Value, result: *std.ArrayList(ProtocolMessage.Variable)) Error!void { + if (value.isObj()) { + switch (value.obj().obj_type) { + // No children + .String, + .Pattern, + .Fiber, + .Type, + .Closure, + .Function, + .Range, + .EnumInstance, + .Native, + .UserData, + => {}, + + .UpValue => try self.valueChildren( + o.ObjUpValue.cast(value.obj()).?.closed orelse + o.ObjUpValue.cast(value.obj()).?.location.*, + result, + ), + + // Proxied earlier + .Bound => unreachable, + + .ForeignContainer => { + const container = o.ObjForeignContainer.cast(value.obj()).?; + const def = container.type_def.resolved_type.?.ForeignContainer; + + var it = def.fields.iterator(); + while (it.next()) |kv| { + try result.append( + self.allocator, + try self.variable( + container.getField( + &self.session.?.runner.vm, + def.fields.getIndex(kv.key_ptr.*).?, + ), + (self.session.?.runner.gc.copyString(kv.key_ptr.*) catch return error.OutOfMemory).toValue(), + def.buzz_type.get(kv.key_ptr.*).?, + ), + ); + } + }, + + .Enum => { + const enm = o.ObjEnum.cast(value.obj()).?; + + for (enm.type_def.resolved_type.?.Enum.cases, 0..) |case_name, i| { + try result.append( + self.allocator, + try self.variable( + enm.cases[i], + (self.session.?.runner.gc.copyString(case_name) catch return error.OutOfMemory).toValue(), + enm.type_def.resolved_type.?.Enum.enum_type, + ), + ); + } + }, + + .Object => { + const object = o.ObjObject.cast(value.obj()).?; + + var it = object.type_def.resolved_type.?.Object.fields.iterator(); + while (it.next()) |kv| { + if (!kv.value_ptr.method and kv.value_ptr.static) { + try result.append( + self.allocator, + try self.variable( + object.fields[kv.value_ptr.index], + (self.session.?.runner.gc.copyString(kv.key_ptr.*) catch return error.OutOfMemory).toValue(), + kv.value_ptr.type_def, + ), + ); + } + } + }, + + .ObjectInstance => { + const instance = o.ObjObjectInstance.cast(value.obj()).?; + + var it = instance.type_def.resolved_type.?.ObjectInstance.of.resolved_type.?.Object.fields.iterator(); + while (it.next()) |kv| { + if (!kv.value_ptr.method and !kv.value_ptr.static) { + try result.append( + self.allocator, + try self.variable( + instance.fields[kv.value_ptr.index], + (self.session.?.runner.gc.copyString(kv.key_ptr.*) catch return error.OutOfMemory).toValue(), + kv.value_ptr.type_def, + ), + ); + } + } + }, + + .List => { + const list = o.ObjList.cast(value.obj()).?; + + for (list.items.items, 0..) |val, i| { + var name = std.Io.Writer.Allocating.init(self.allocator); + name.writer.print("#{}", .{i}) catch return error.OutOfMemory; + + try result.append( + self.allocator, + try self.variable( + val, + (self.session.?.runner.gc.copyString( + try name.toOwnedSlice(), + ) catch return error.OutOfMemory).toValue(), + list.type_def.resolved_type.?.List.item_type, + ), + ); + } + }, + + .Map => { + const map = o.ObjMap.cast(value.obj()).?; + + var it = map.map.iterator(); + var count: usize = 0; + while (it.next()) |kv| : (count += 1) { + var name = std.Io.Writer.Allocating.init(self.allocator); + name.writer.print("MapEntry#{}", .{count}) catch return error.OutOfMemory; + + const vbl = Variable{ + .value = .{ + .map_entry = .{ + .map_type = map.type_def, + .key = kv.key_ptr.*, + .value = kv.value_ptr.*, + }, + }, + .variable = .{ + .variable = .{ + .name = name.written(), + .evaluateName = name.written(), + .value = "{...}", + .variablesReference = self.session.?.variables.items.len + 3, + .indexedVariables = 0, + .namedVariables = 2, + }, + }, + }; + + try self.session.?.variables.append(self.allocator, vbl); + + try result.append(self.allocator, vbl.variable.variable); + } + }, + } + } +} + +pub fn main() !u8 { + var gpa = std.heap.GeneralPurposeAllocator(.{ .safety = builtin.mode == .Debug }){}; + const allocator: std.mem.Allocator = if (builtin.mode == .Debug) + gpa.allocator() + else if (BuildOptions.mimalloc) + @import("mimalloc.zig").mim_allocator + else + std.heap.c_allocator; + + const params = comptime clap.parseParamsComptime( + \\-h, --help Show help and exit + \\-p, --port On which port the debugger should be listening + \\-v, --version Print version and exit + \\-L, --library ... Add search path for external libraries + \\ + ); + + var diag = clap.Diagnostic{}; + var res = clap.parse( + clap.Help, + ¶ms, + clap.parsers.default, + .{ + .allocator = allocator, + .diagnostic = &diag, + }, + ) catch |err| { + // Report useful error and exit + diag.report(io.stderrWriter, err) catch {}; + return 1; + }; + defer res.deinit(); + + if (res.args.version == 1) { + printBanner(io.stdoutWriter, true); + + return 0; + } + + if (res.args.help == 1) { + io.print("👨‍🚀 Debugger for the buzz programming language\n\nUsage: buzz_debugger ", .{}); + + clap.usage( + io.stderrWriter, + clap.Help, + ¶ms, + ) catch return 1; + + io.print("\n\n", .{}); + + clap.help( + io.stderrWriter, + clap.Help, + ¶ms, + .{ + .description_on_new_line = false, + .description_indent = 4, + .spacing_between_parameters = 0, + }, + ) catch return 1; + + return 0; + } + + // Start the debugger + var debugger: Debugger = undefined; + try debugger.start( + allocator, + .initIp4( + .{ 127, 0, 0, 1 }, + res.args.port orelse 9000, + ), + ); + + // Read requests + while (true) { + if (debugger.adapter.handleRequest()) |handled_request| { + switch (handled_request) { + // We send the initalized event right after responsding to the request + .initialize => debugger.adapter.emitEvent( + .{ + .event = .initialized, + .body = .{ + .initialized = {}, + }, + }, + ), + .configurationDone => { + // Now the configuration phase is done, we start the program and it's up to the VM to call handlRequest at safepoints + debugger.session.?.runner.vm.run(); + + // Program is over, send terminated and stop event + debugger.adapter.emitEvent( + .{ + .event = .terminated, + .body = .{ + .terminated = .{}, + }, + }, + ); + + debugger.adapter.emitEvent( + .{ + .event = .exited, + .body = .{ + .exited = .{ + // TODO: grap actual exit code + .exitCode = 0, + }, + }, + }, + ); + + debugger.session.?.deinit(); + debugger.session = null; + + break; + }, + else => {}, + } + } + + std.Thread.sleep(10_000_000); + } + + // Await for the server to end + debugger.server_thread.join(); + + return 0; +} diff --git a/src/Jit.zig b/src/Jit.zig index bc09a3a6..747143c8 100644 --- a/src/Jit.zig +++ b/src/Jit.zig @@ -600,10 +600,10 @@ fn generateNode(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { fn closeScope(self: *Self, node: Ast.Node.Index) !void { if (self.state.?.ast.nodes.items(.ends_scope)[node]) |closing| { - for (closing) |op| { - if (op == .OP_CLOSE_UPVALUE) { + for (closing) |close| { + if (close.opcode == .OP_CLOSE_UPVALUE) { try self.buildCloseUpValues(); - } else if (op == .OP_POP) { + } else if (close.opcode == .OP_POP) { try self.buildPop(null); } else { unreachable; diff --git a/src/Parser.zig b/src/Parser.zig index 1bc4eb69..e8eff4fa 100644 --- a/src/Parser.zig +++ b/src/Parser.zig @@ -1242,19 +1242,31 @@ fn beginScope(self: *Self, at: ?Ast.Node.Index) !void { self.current.?.scope_depth += 1; } -fn endScope(self: *Self) ![]Chunk.OpCode { +fn endScope(self: *Self) ![]Ast.Close { const current = self.current.?; _ = current.scopes.pop(); - var closing = std.ArrayList(Chunk.OpCode).empty; + var closing = std.ArrayList(Ast.Close).empty; current.scope_depth -= 1; while (current.local_count > 0 and current.locals[current.local_count - 1].depth > current.scope_depth) { const local = current.locals[current.local_count - 1]; if (local.captured) { - try closing.append(self.gc.allocator, .OP_CLOSE_UPVALUE); + try closing.append( + self.gc.allocator, + .{ + .opcode = .OP_CLOSE_UPVALUE, + .slot = current.local_count - 1, + }, + ); } else { - try closing.append(self.gc.allocator, .OP_POP); + try closing.append( + self.gc.allocator, + .{ + .opcode = .OP_POP, + .slot = current.local_count - 1, + }, + ); } current.local_count -= 1; @@ -1263,16 +1275,28 @@ fn endScope(self: *Self) ![]Chunk.OpCode { return try closing.toOwnedSlice(self.gc.allocator); } -fn closeScope(self: *Self, upto_depth: usize) ![]Chunk.OpCode { +fn closeScope(self: *Self, upto_depth: usize) ![]Ast.Close { const current = self.current.?; - var closing = std.ArrayList(Chunk.OpCode).empty; + var closing = std.ArrayList(Ast.Close).empty; var local_count = current.local_count; while (local_count > 0 and current.locals[local_count - 1].depth > upto_depth - 1) { if (current.locals[local_count - 1].captured) { - try closing.append(self.gc.allocator, .OP_CLOSE_UPVALUE); + try closing.append( + self.gc.allocator, + .{ + .opcode = .OP_CLOSE_UPVALUE, + .slot = local_count - 1, + }, + ); } else { - try closing.append(self.gc.allocator, .OP_POP); + try closing.append( + self.gc.allocator, + .{ + .opcode = .OP_POP, + .slot = local_count - 1, + }, + ); } local_count -= 1; diff --git a/src/Runner.zig b/src/Runner.zig new file mode 100644 index 00000000..a15682ad --- /dev/null +++ b/src/Runner.zig @@ -0,0 +1,172 @@ +const std = @import("std"); +const is_wasm = builtin.cpu.arch.isWasm(); +const builtin = @import("builtin"); +const _vm = @import("vm.zig"); +const VM = _vm.VM; +const RunFlavor = _vm.RunFlavor; +const ImportRegistry = _vm.ImportRegistry; +const Parser = @import("Parser.zig"); +const CodeGen = @import("Codegen.zig"); +const Debugger = @import("Debugger.zig"); +const GC = @import("GC.zig"); +const JIT = if (!is_wasm) @import("Jit.zig") else void; +const TypeRegistry = @import("TypeRegistry.zig"); +const Ast = @import("Ast.zig"); +const BuildOptions = @import("build_options"); +const io = @import("io.zig"); +const Renderer = @import("renderer.zig").Renderer; + +const Runner = @This(); + +vm: VM, +gc: GC, +parser: Parser, +codegen: CodeGen, +import_registry: ImportRegistry = .empty, +imports: std.StringHashMapUnmanaged(Parser.ScriptImport) = .empty, + +pub fn deinit(self: *Runner) void { + self.codegen.deinit(); + self.parser.deinit(); + // self.gc.deinit(); + var it = self.imports.iterator(); + while (it.next()) |kv| { + kv.value_ptr.*.globals.deinit(self.gc.allocator); + } + self.imports.deinit(self.gc.allocator); + // TODO: free type_registry and its keys which are on the heap + if (!is_wasm and self.vm.jit != null) { + self.vm.jit.?.deinit(self.gc.allocator); + self.vm.jit = null; + } + self.vm.deinit(); +} + +pub fn runFile( + runner: *Runner, + allocator: std.mem.Allocator, + file_name: []const u8, + args: []const []const u8, + flavor: RunFlavor, + debugger: ?*Debugger, +) !void { + var total_timer = if (!is_wasm) std.time.Timer.start() catch unreachable else {}; + + runner.* = .{ + .gc = try GC.init(allocator), + .vm = undefined, + .parser = undefined, + .codegen = undefined, + }; + defer if (debugger == null) runner.deinit(); + + runner.gc.type_registry = try TypeRegistry.init(&runner.gc); + runner.vm = try VM.init( + &runner.gc, + &runner.import_registry, + flavor, + debugger, + ); + + runner.vm.jit = if (BuildOptions.jit and BuildOptions.cycle_limit == null and debugger == null) + JIT.init(&runner.vm) + else + null; + + runner.parser = Parser.init( + &runner.gc, + &runner.imports, + false, + flavor, + ); + + runner.codegen = CodeGen.init( + &runner.gc, + &runner.parser, + flavor, + if (runner.vm.jit) |*jit| jit else null, + debugger != null, + ); + + var file = (if (std.fs.path.isAbsolute(file_name)) + std.fs.openFileAbsolute(file_name, .{}) + else + std.fs.cwd().openFile(file_name, .{})) catch { + io.print("File not found", .{}); + return; + }; + defer file.close(); + + const source = try allocator.alloc(u8, (try file.stat()).size); + defer if (debugger == null) allocator.free(source); + + _ = try file.readAll(source); + + var timer = try std.time.Timer.start(); + var parsing_time: u64 = 0; + var codegen_time: u64 = 0; + var running_time: u64 = 0; + + if (try runner.parser.parse(source, null, file_name)) |ast| { + if (!is_wasm) { + parsing_time = timer.read(); + timer.reset(); + } + + if (flavor.runs()) { + const ast_slice = ast.slice(); + + if (try runner.codegen.generate(ast_slice)) |function| { + if (!is_wasm) { + codegen_time = timer.read(); + timer.reset(); + } + + try runner.vm.interpret( + ast_slice, + function, + args, + ); + + if (!is_wasm) { + running_time = timer.read(); + } + } else { + return Parser.CompileError.Recoverable; + } + + if (BuildOptions.show_perf and flavor != .Check and flavor != .Fmt) { + io.print( + if (builtin.os.tag != .windows) + "\x1b[2mParsing: {[parsing]D}\nCodegen: {[codegen]D}\nRun: {[running]D}\nJIT: {[jit]D}\nGC: {[gc]D}\nTotal: {[total]D}\nFull GC: {[gc_full]} | GC: {[gc_light]} | Max allocated: {[max_alloc]B}\n\x1b[0m" + else + "Parsing: {[parsing]D}\nCodegen: {[codegen]D}\nRun: {[running]D}\nJIT: {[jit]D}\nGC: {[gc]D}\nTotal: {[total]D}\nFull GC: {[gc_full]} | GC: {[gc_light]} | Max allocated: {[max_alloc]B}\n", + .{ + .parsing = parsing_time, + .codegen = codegen_time, + .running = running_time, + .jit = if (runner.vm.jit) |jit| jit.jit_time else 0, + .gc = runner.gc.gc_time, + .total = if (!is_wasm) total_timer.read() else 0, + .gc_full = runner.gc.full_collection_count, + .gc_light = runner.gc.light_collection_count, + .max_alloc = runner.gc.max_allocated, + }, + ); + } + } else if (flavor == .Fmt) { + var arena = std.heap.ArenaAllocator.init(allocator); + defer arena.deinit(); + + try Renderer.render( + arena.allocator(), + io.stdoutWriter, + ast, + ); + } else { + io.print("Formatting and Ast dump is deactivated", .{}); + } + } else { + return Parser.CompileError.Recoverable; + } +} diff --git a/src/behavior.zig b/src/behavior.zig index 90e466e5..1c005f80 100644 --- a/src/behavior.zig +++ b/src/behavior.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const runFile = @import("main.zig").runFile; +const Runner = @import("Runner.zig"); const io = @import("io.zig"); const Parser = @import("Parser.zig"); @@ -44,12 +44,15 @@ fn testBehaviors(allocator: std.mem.Allocator) !Result { continue; } + var runner: Runner = undefined; + var had_error: bool = false; - runFile( + runner.runFile( allocator, file_name, &[_][:0]u8{}, .Test, + null, ) catch { io.print("\u{001b}[31m[{s} ✕]\u{001b}[0m\n", .{file.name}); had_error = true; diff --git a/src/buzz_api.zig b/src/buzz_api.zig index 663832c9..1d4f5f3e 100644 --- a/src/buzz_api.zig +++ b/src/buzz_api.zig @@ -597,6 +597,7 @@ export fn bz_newVM() *VM { gc, import_registry, .Run, + null, ) catch @panic("Out of memory"); return vm; @@ -636,6 +637,7 @@ export fn bz_run( &parser, self.flavor, null, + self.debugger != null, ); defer { codegen.deinit(); diff --git a/src/disassembler.zig b/src/disassembler.zig index 55f532c3..42146748 100644 --- a/src/disassembler.zig +++ b/src/disassembler.zig @@ -212,10 +212,10 @@ pub fn disassembleInstruction(chunk: *Chunk, offset: usize) usize { print("\n{:0>3} ", .{offset}); const lines = chunk.ast.tokens.items(.line); - if (offset > 0 and lines[chunk.lines.items[offset]] == lines[chunk.lines.items[offset - 1]]) { + if (offset > 0 and lines[chunk.locations.items[offset]] == lines[chunk.locations.items[offset - 1]]) { print("| ", .{}); } else { - print("{:0>3} ", .{lines[chunk.lines.items[offset]]}); + print("{:0>3} ", .{lines[chunk.locations.items[offset]]}); } const full_instruction: u32 = chunk.code.items[offset]; @@ -314,6 +314,7 @@ pub fn disassembleInstruction(chunk: *Chunk, offset: usize) usize { .OP_GET_FCONTAINER_INSTANCE_PROPERTY, .OP_SET_FCONTAINER_INSTANCE_PROPERTY, .OP_COPY, + .OP_DBG_LOCAL_EXIT, => byteInstruction(instruction, chunk, offset), .OP_OBJECT, @@ -352,6 +353,43 @@ pub fn disassembleInstruction(chunk: *Chunk, offset: usize) usize { .OP_TAIL_CALL, => triInstruction(instruction, chunk, offset), + .OP_DBG_GLOBAL_DEFINE => { + const slot = chunk.code.items[offset + 1]; + const constant: u24 = @intCast(chunk.code.items[offset + 2]); + const value_str = chunk.constants.items[constant].toStringAlloc(global_allocator) catch @panic("Out of memory"); + defer global_allocator.free(value_str); + + print( + "OP_DBG_GLOBAL_DEFINE\t{} {} {s}", + .{ + slot, + constant, + value_str, + }, + ); + + return offset + 3; + }, + + .OP_DBG_LOCAL_ENTER => { + const arg_instruction = chunk.code.items[offset + 1]; + const slot: u8 = @intCast(arg_instruction >> 24); + const constant: u24 = @intCast(0x00ffffff & arg_instruction); + const value_str = chunk.constants.items[constant].toStringAlloc(global_allocator) catch @panic("Out of memory"); + defer global_allocator.free(value_str); + + print( + "OP_DBG_LOCAL_ENTER\t{} {} {s}", + .{ + slot, + constant, + value_str, + }, + ); + + return offset + 2; + }, + .OP_CLOSURE => closure: { const constant: u24 = arg; var off_offset: usize = offset + 1; diff --git a/src/lsp.zig b/src/lsp.zig index 2f245be4..adba5bb7 100644 --- a/src/lsp.zig +++ b/src/lsp.zig @@ -61,6 +61,7 @@ const Document = struct { &parser, .Ast, null, + false, ); const owned_uri = try allocator.dupe(u8, uri); diff --git a/src/main.zig b/src/main.zig index aa2f8f99..6011274e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -17,10 +17,12 @@ const clap = @import("clap"); const GC = @import("GC.zig"); const JIT = if (!is_wasm) @import("Jit.zig") else void; const is_wasm = builtin.cpu.arch.isWasm(); -const repl = if (!is_wasm) @import("repl.zig").repl else void; +const _repl = if (!is_wasm) @import("repl.zig") else void; +const repl = if (!is_wasm) _repl.repl else void; const wasm_repl = @import("wasm_repl.zig"); const Renderer = @import("renderer.zig").Renderer; const io = @import("io.zig"); +const Runner = @import("Runner.zig"); pub export const initRepl_export = wasm_repl.initRepl; pub export const runLine_export = wasm_repl.runLine; @@ -30,175 +32,13 @@ pub const os = if (is_wasm) else std.os; -fn printBanner(out: anytype, full: bool) void { - out.print( - "👨‍🚀 buzz {f}-{s} Copyright (C) 2021-present Benoit Giannangeli\n", - .{ - BuildOptions.version, - BuildOptions.sha, - }, - ) catch unreachable; - - if (full) { - out.print( - "Built with Zig {f} {s}\nAllocator: {s}, Memory limit: {} {s}\nJIT: {s}, CPU limit: {} {s}\n", - .{ - builtin.zig_version, - @tagName(builtin.mode), - if (builtin.mode == .Debug) - "gpa" - else if (BuildOptions.mimalloc) "mimalloc" else "c_allocator", - if (BuildOptions.memory_limit) |ml| - ml - else - 0, - if (BuildOptions.memory_limit != null) - "bytes" - else - "(unlimited)", - if (BuildOptions.jit and BuildOptions.cycle_limit == null) - "on" - else - "off", - if (BuildOptions.cycle_limit) |cl| cl else 0, - if (BuildOptions.cycle_limit != null) "cycles" else "(unlimited)", - }, - ) catch unreachable; - } -} - -pub fn runFile(allocator: Allocator, file_name: []const u8, args: []const []const u8, flavor: RunFlavor) !void { - var total_timer = if (!is_wasm) std.time.Timer.start() catch unreachable else {}; - var import_registry = ImportRegistry{}; - var gc = try GC.init(allocator); - gc.type_registry = try TypeRegistry.init(&gc); - var imports = std.StringHashMapUnmanaged(Parser.ScriptImport){}; - var vm = try VM.init(&gc, &import_registry, flavor); - vm.jit = if (BuildOptions.jit and BuildOptions.cycle_limit == null) - JIT.init(&vm) - else - null; - defer { - if (!is_wasm and vm.jit != null) { - vm.jit.?.deinit(vm.gc.allocator); - vm.jit = null; - } - } - var parser = Parser.init( - &gc, - &imports, - false, - flavor, - ); - var codegen = CodeGen.init( - &gc, - &parser, - flavor, - if (vm.jit) |*jit| jit else null, - ); - defer { - codegen.deinit(); - vm.deinit(); - parser.deinit(); - // gc.deinit(); - var it = imports.iterator(); - while (it.next()) |kv| { - kv.value_ptr.*.globals.deinit(allocator); - } - imports.deinit(allocator); - // TODO: free type_registry and its keys which are on the heap - } - - var file = (if (std.fs.path.isAbsolute(file_name)) - std.fs.openFileAbsolute(file_name, .{}) - else - std.fs.cwd().openFile(file_name, .{})) catch { - io.print("File not found", .{}); - return; - }; - defer file.close(); - - const source = try allocator.alloc(u8, (try file.stat()).size); - defer allocator.free(source); - - _ = try file.readAll(source); - - var timer = try std.time.Timer.start(); - var parsing_time: u64 = 0; - var codegen_time: u64 = 0; - var running_time: u64 = 0; - - if (try parser.parse(source, null, file_name)) |ast| { - if (!is_wasm) { - parsing_time = timer.read(); - timer.reset(); - } - - if (flavor == .Run or flavor == .Test) { - const ast_slice = ast.slice(); - - if (try codegen.generate(ast_slice)) |function| { - if (!is_wasm) { - codegen_time = timer.read(); - timer.reset(); - } - - try vm.interpret( - ast_slice, - function, - args, - ); - - if (!is_wasm) { - running_time = timer.read(); - } - } else { - return Parser.CompileError.Recoverable; - } - - if (BuildOptions.show_perf and flavor != .Check and flavor != .Fmt) { - io.print( - if (builtin.os.tag != .windows) - "\x1b[2mParsing: {[parsing]D}\nCodegen: {[codegen]D}\nRun: {[running]D}\nJIT: {[jit]D}\nGC: {[gc]D}\nTotal: {[total]D}\nFull GC: {[gc_full]} | GC: {[gc_light]} | Max allocated: {[max_alloc]B}\n\x1b[0m" - else - "Parsing: {[parsing]D}\nCodegen: {[codegen]D}\nRun: {[running]D}\nJIT: {[jit]D}\nGC: {[gc]D}\nTotal: {[total]D}\nFull GC: {[gc_full]} | GC: {[gc_light]} | Max allocated: {[max_alloc]B}\n", - .{ - .parsing = parsing_time, - .codegen = codegen_time, - .running = running_time, - .jit = if (vm.jit) |jit| jit.jit_time else 0, - .gc = gc.gc_time, - .total = if (!is_wasm) total_timer.read() else 0, - .gc_full = gc.full_collection_count, - .gc_light = gc.light_collection_count, - .max_alloc = gc.max_allocated, - }, - ); - } - } else if (flavor == .Fmt) { - var arena = std.heap.ArenaAllocator.init(allocator); - defer arena.deinit(); - - try Renderer.render( - arena.allocator(), - io.stdoutWriter, - ast, - ); - } else { - io.print("Formatting and Ast dump is deactivated", .{}); - } - } else { - return Parser.CompileError.Recoverable; - } -} - pub fn main() u8 { if (is_wasm) { return 1; } - var gpa = std.heap.GeneralPurposeAllocator(.{ .safety = builtin.mode == .Debug }){}; - const allocator: std.mem.Allocator = if (builtin.mode == .Debug or is_wasm) + var gpa = std.heap.DebugAllocator(.{ .safety = builtin.mode == .Debug }){}; + const allocator = if (builtin.mode == .Debug or is_wasm) gpa.allocator() else if (BuildOptions.mimalloc) @import("mimalloc.zig").mim_allocator @@ -233,7 +73,7 @@ pub fn main() u8 { defer res.deinit(); if (res.args.version == 1) { - printBanner(io.stdoutWriter, true); + _repl.printBanner(io.stdoutWriter, true); return 0; } @@ -289,11 +129,14 @@ pub fn main() u8 { return 1; }; } else if (!is_wasm and res.positionals[0].len > 0) { - runFile( + var runner: Runner = undefined; + + runner.runFile( allocator, res.positionals[0][0], res.positionals[0][1..], flavor, + null, ) catch { return 1; }; diff --git a/src/obj.zig b/src/obj.zig index 7f617c8d..5c4a25e7 100644 --- a/src/obj.zig +++ b/src/obj.zig @@ -360,7 +360,20 @@ pub const Obj = struct { .UserData => gc.type_registry.ud_type, // FIXME: apart from list/map types we actually can embark typedef of objnatives at runtime // Or since native are ptr to unique function we can keep a map of ptr => typedef - .Native => unreachable, + .Native => try gc.type_registry.getTypeDef( + .{ + .def_type = .Function, + .resolved_type = .{ + .Function = .{ + .id = 0, + .name = try gc.copyString("native"), + .script_name = try gc.copyString("native"), + .return_type = gc.type_registry.any_type, + .yield_type = gc.type_registry.any_type, + }, + }, + }, + ), }; } @@ -1371,13 +1384,13 @@ pub const ObjFunction = struct { yield_type: *ObjTypeDef, error_types: ?[]const *ObjTypeDef = null, // TODO: rename 'arguments' - parameters: std.AutoArrayHashMapUnmanaged(*ObjString, *ObjTypeDef) = .{}, + parameters: std.AutoArrayHashMapUnmanaged(*ObjString, *ObjTypeDef) = .empty, // Storing here the defaults means they can only be non-Obj values - defaults: std.AutoArrayHashMapUnmanaged(*ObjString, Value) = .{}, + defaults: std.AutoArrayHashMapUnmanaged(*ObjString, Value) = .empty, function_type: FunctionType = .Function, lambda: bool = false, - generic_types: std.AutoArrayHashMapUnmanaged(*ObjString, *ObjTypeDef) = .{}, + generic_types: std.AutoArrayHashMapUnmanaged(*ObjString, *ObjTypeDef) = .empty, resolved_generics: ?[]*ObjTypeDef = null, pub fn nextId() usize { @@ -3184,19 +3197,20 @@ pub const ObjRange = struct { pub const ObjMap = struct { const Self = @This(); + pub const Map = std.AutoArrayHashMapUnmanaged(Value, Value); + obj: Obj = .{ .obj_type = .Map }, type_def: *ObjTypeDef, // We need an ArrayHashMap for `next` - map: std.AutoArrayHashMapUnmanaged(Value, Value), + map: Map = .empty, methods: []?*ObjNative, pub fn init(allocator: Allocator, type_def: *ObjTypeDef) !Self { const self = Self{ .type_def = type_def, - .map = std.AutoArrayHashMapUnmanaged(Value, Value){}, .methods = try allocator.alloc( ?*ObjNative, Self.members.len, diff --git a/src/repl.zig b/src/repl.zig index 2add2972..29ec7873 100644 --- a/src/repl.zig +++ b/src/repl.zig @@ -77,7 +77,7 @@ pub fn repl(allocator: std.mem.Allocator) !void { var gc = try GC.init(allocator); gc.type_registry = try TypeRegistry.init(&gc); var imports = std.StringHashMapUnmanaged(Parser.ScriptImport){}; - var vm = try v.VM.init(&gc, &import_registry, .Repl); + var vm = try v.VM.init(&gc, &import_registry, .Repl, null); vm.jit = if (BuildOptions.jit and BuildOptions.cycle_limit == null) JIT.init(&vm) else @@ -99,6 +99,7 @@ pub fn repl(allocator: std.mem.Allocator) !void { &parser, .Repl, if (vm.jit) |*jit| jit else null, + false, ); defer { codegen.deinit(); diff --git a/src/vm.zig b/src/vm.zig index 7d354414..0210f59d 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -15,6 +15,7 @@ const Reporter = @import("Reporter.zig"); const FFI = if (!is_wasm) @import("FFI.zig") else void; const dispatch_call_modifier: std.builtin.CallModifier = if (!is_wasm) .always_tail else .auto; const io = @import("io.zig"); +const Debugger = @import("Debugger.zig"); const dumpStack = disassembler.dumpStack; const jmp = if (!is_wasm) @import("jmp.zig") else void; @@ -29,14 +30,21 @@ pub const RunFlavor = enum { Ast, Repl, - pub inline fn resolveImports(self: RunFlavor) bool { + pub fn runs(self: RunFlavor) bool { + return switch (self) { + .Run, .Test => true, + else => false, + }; + } + + pub fn resolveImports(self: RunFlavor) bool { return switch (self) { .Check, .Fmt => false, else => true, }; } - pub inline fn resolveDynLib(self: RunFlavor) bool { + pub fn resolveDynLib(self: RunFlavor) bool { return switch (self) { .Fmt, .Check, .Ast => false, else => true, @@ -50,7 +58,7 @@ pub const CallFrame = struct { closure: *obj.ObjClosure, /// Index into closure's chunk ip: usize, - // Frame + /// Frame slots: [*]Value, /// Default value in case of error @@ -109,6 +117,8 @@ pub const Fiber = struct { stack: []Value, stack_top: [*]Value, open_upvalues: ?*obj.ObjUpValue, + /// Debug info + locals_dbg: std.ArrayList(Value) = .empty, status: Status = .Instanciated, /// true: we did `resolve fiber`, false: we did `resume fiber` @@ -428,17 +438,20 @@ pub const VM = struct { gc: *GC, current_fiber: *Fiber, current_ast: Ast.Slice, - globals: std.ArrayList(Value) = .{}, + globals: std.ArrayList(Value) = .empty, // FIXME: remove globals_count: usize = 0, + globals_dbg: std.ArrayList(Value) = .empty, import_registry: *ImportRegistry, jit: ?JIT = null, + debugger: ?*Debugger = null, + paused: bool = false, hotspots_count: u128 = 0, flavor: RunFlavor, reporter: Reporter, ffi: FFI, - pub fn init(gc: *GC, import_registry: *ImportRegistry, flavor: RunFlavor) !Self { + pub fn init(gc: *GC, import_registry: *ImportRegistry, flavor: RunFlavor, debugger: ?*Debugger) !Self { return .{ .gc = gc, .import_registry = import_registry, @@ -449,6 +462,7 @@ pub const VM = struct { .allocator = gc.allocator, }, .ffi = if (!is_wasm) FFI.init(gc) else {}, + .debugger = debugger, }; } @@ -458,6 +472,7 @@ pub const VM = struct { if (!is_wasm) { self.ffi.deinit(); } + self.gc.unregisterVM(self); } pub fn cliArgs(self: *Self, args: ?[]const []const u8) !*obj.ObjList { @@ -596,7 +611,6 @@ pub const VM = struct { self.push((try self.cliArgs(args)).toValue()); try self.gc.registerVM(self); - defer self.gc.unregisterVM(self); try self.callValue( self.peek(1), @@ -606,7 +620,10 @@ pub const VM = struct { self.current_fiber.status = .Running; - return self.run(); + // If debugging, don't run the entry point right away + if (self.debugger == null or function.type_def.resolved_type.?.Function.function_type != .ScriptEntryPoint) { + self.run(); + } } fn readPreviousInstruction(self: *Self) ?u32 { @@ -793,6 +810,10 @@ pub const VM = struct { OP_HOTSPOT, OP_HOTSPOT_CALL, + + OP_DBG_LOCAL_ENTER, + OP_DBG_LOCAL_EXIT, + OP_DBG_GLOBAL_DEFINE, }; fn dispatch(self: *Self, current_frame: *CallFrame, full_instruction: u32, instruction: Chunk.OpCode, arg: u24) void { @@ -858,10 +879,19 @@ pub const VM = struct { } } + if (self.debugger) |debugger| { + if (debugger.onDispatch()) { + return; + } + } + // Tail call @call( dispatch_call_modifier, - op_table[@intFromEnum(instruction)], + if (self.debugger != null and self.debugger.?.session.?.run_state == .paused) + OP_NOOP + else + op_table[@intFromEnum(instruction)], .{ self, current_frame, @@ -880,6 +910,24 @@ pub const VM = struct { unreachable; } + /// Called instead of regular op_code when program is paused by the debugger + fn OP_NOOP(self: *Self, current_frame: *CallFrame, next_full_instruction: u32, code: Chunk.OpCode, args: u24) void { + // Wait a little for the user to resume the program + std.Thread.sleep(10_000_000); + + @call( + dispatch_call_modifier, + dispatch, + .{ + self, + current_frame, + next_full_instruction, + code, + args, + }, + ); + } + fn OP_NULL(self: *Self, _: *CallFrame, _: u32, _: Chunk.OpCode, _: u24) void { self.push(Value.Null); @@ -2173,7 +2221,12 @@ pub const VM = struct { unreachable; }; // FIXME: give reference to JIT? - vm.* = VM.init(self.gc, self.import_registry, self.flavor) catch { + vm.* = VM.init( + self.gc, + self.import_registry, + self.flavor, + self.debugger, + ) catch { self.panic("Out of memory"); unreachable; }; @@ -2200,14 +2253,36 @@ pub const VM = struct { var import_cache = std.ArrayList(Value).empty; if (exported_count > 0) { - var i = exported_count; - while (i > 0) : (i -= 1) { + var i: usize = @intCast( + if (self.debugger != null) + exported_count * 2 + else + exported_count, + ); + const increment: usize = if (self.debugger != null) 2 else 1; + while (i > 0) : (i -= increment) { + const idx = if (self.debugger != null) + vm.peek(@intCast(i - 1)) + else + null; + const global = vm.peek(@intCast(i)); + self.globals.append(self.gc.allocator, global) catch { self.panic("Out of memory"); unreachable; }; + if (self.debugger != null) { + self.globals_dbg.append( + self.gc.allocator, + vm.globals_dbg.items[@intCast(idx.?.integer())], + ) catch { + self.panic("Out of memory"); + unreachable; + }; + } + import_cache.append(self.gc.allocator, global) catch { self.panic("Out of memory"); unreachable; @@ -4455,6 +4530,107 @@ pub const VM = struct { ); } + fn addDbgLocal(self: *Self, slot: usize, name_constant: Value) void { + const previous_len = self.current_fiber.locals_dbg.items.len; + const new_len = @max(slot + 1, previous_len); + + self.current_fiber.locals_dbg.ensureTotalCapacity(self.gc.allocator, new_len) catch { + self.panic("Out of memory"); + unreachable; + }; + + // We don't always define a new local at the end of the list + self.current_fiber.locals_dbg.items.len = new_len; + + // We might have introduced a gap, initialize those with null + if (previous_len < new_len) { + for (previous_len..new_len) |i| { + self.current_fiber.locals_dbg.items[i] = Value.Null; + } + } + + self.current_fiber.locals_dbg.items[slot] = name_constant; + } + + fn OP_DBG_LOCAL_ENTER(self: *Self, current_frame: *CallFrame, _: u32, _: Chunk.OpCode, _: u24) void { + const arg_instruction = self.readInstruction(); + const slot = @as(u8, @intCast(arg_instruction >> 24)) + + (current_frame.slots - @as([*]Value, @ptrCast(self.current_fiber.stack))) - 1; + const name_constant = self.readConstant(@intCast(0x00ffffff & arg_instruction)); + + self.addDbgLocal(slot, name_constant); + + const next_full_instruction = self.readInstruction(); + @call( + dispatch_call_modifier, + dispatch, + .{ + self, + self.currentFrame().?, + next_full_instruction, + getCode(next_full_instruction), + getArg(next_full_instruction), + }, + ); + } + + fn OP_DBG_LOCAL_EXIT(self: *Self, current_frame: *CallFrame, _: u32, _: Chunk.OpCode, arg: u24) void { + const slot = arg + (current_frame.slots - @as([*]Value, @ptrCast(self.current_fiber.stack))) - 1; + + self.current_fiber.locals_dbg.items[slot] = Value.Null; + + const next_full_instruction = self.readInstruction(); + @call( + dispatch_call_modifier, + dispatch, + .{ + self, + self.currentFrame().?, + next_full_instruction, + getCode(next_full_instruction), + getArg(next_full_instruction), + }, + ); + } + + fn OP_DBG_GLOBAL_DEFINE(self: *Self, _: *CallFrame, _: u32, _: Chunk.OpCode, _: u24) void { + const slot = self.readInstruction(); + const name_constant = self.readConstant(@intCast(self.readInstruction())); + + const previous_len = self.globals_dbg.items.len; + const new_len = @max(slot + 1, previous_len); + + self.globals_dbg.ensureTotalCapacity(self.gc.allocator, new_len) catch { + self.panic("Out of memory"); + unreachable; + }; + + // We don't always define a new global at the end of the list + self.globals_dbg.items.len = new_len; + + // We might have introduced a gap, initialize those with null + if (previous_len < new_len) { + for (previous_len..new_len) |i| { + self.globals_dbg.items[i] = Value.Null; + } + } + + self.globals_dbg.items[slot] = name_constant; + + const next_full_instruction = self.readInstruction(); + @call( + dispatch_call_modifier, + dispatch, + .{ + self, + self.currentFrame().?, + next_full_instruction, + getCode(next_full_instruction), + getArg(next_full_instruction), + }, + ); + } + pub fn run(self: *Self) void { const next_current_frame: *CallFrame = self.currentFrame().?; const next_full_instruction = self.readInstruction(); @@ -4506,7 +4682,7 @@ pub const VM = struct { const error_site = if (previous_error_site) |perror_site| perror_site else if (self.currentFrame()) |current_frame| - current_frame.closure.function.chunk.lines.items[current_frame.ip - 1] + current_frame.closure.function.chunk.locations.items[current_frame.ip - 1] else null; @@ -4602,7 +4778,7 @@ pub const VM = struct { self.reportRuntimeError( message, if (self.currentFrame()) |frame| - frame.closure.function.chunk.lines.items[frame.ip - 1] + frame.closure.function.chunk.locations.items[frame.ip - 1] else null, self.current_fiber.frames.items, @@ -4827,6 +5003,15 @@ pub const VM = struct { self.current_fiber.frame_count += 1; + if (self.debugger != null) { + for (closure.function.type_def.resolved_type.?.Function.parameters.keys(), 0..) |arg_name, i| { + self.addDbgLocal( + (frame.slots - @as([*]Value, @ptrCast(self.current_fiber.stack))) + i, + arg_name.toValue(), + ); + } + } + if (BuildOptions.jit_debug) { io.print( "Calling uncompiled version of function `{s}.{}.n{}`\n", @@ -4841,11 +5026,11 @@ pub const VM = struct { fn getSite(self: *Self) ?Ast.TokenIndex { return if (self.currentFrame()) |current_frame| - current_frame.closure.function.chunk.lines.items[@max(1, current_frame.ip) - 1] + current_frame.closure.function.chunk.locations.items[@max(1, current_frame.ip) - 1] else if (self.current_fiber.parent_fiber) |parent_fiber| parent: { const parent_frame = parent_fiber.frames.items[parent_fiber.frame_count - 1]; - break :parent parent_frame.closure.function.chunk.lines.items[@max(1, parent_frame.ip - 1)]; + break :parent parent_frame.closure.function.chunk.locations.items[@max(1, parent_frame.ip - 1)]; } else null; } @@ -5279,7 +5464,7 @@ pub const VM = struct { &hotspot_call, ); - try chunk.lines.replaceRange( + try chunk.locations.replaceRange( chunk.allocator, to - hotspot_call.len, hotspot_call.len, diff --git a/src/wasm_repl.zig b/src/wasm_repl.zig index 2055aa8c..10218bd7 100644 --- a/src/wasm_repl.zig +++ b/src/wasm_repl.zig @@ -47,6 +47,7 @@ pub export fn initRepl() *ReplCtx { gc, import_registry, .Repl, + null, ); const parser = vm.gc.allocator.create(Parser) catch unreachable; @@ -63,6 +64,7 @@ pub export fn initRepl() *ReplCtx { parser, .Repl, null, + false, ); printBanner(io.stdoutWriter, true); diff --git a/tests/manual/008-write-file.buzz b/tests/manual/008-write-file.buzz new file mode 100644 index 00000000..9e8e4ccc --- /dev/null +++ b/tests/manual/008-write-file.buzz @@ -0,0 +1,22 @@ +import "io"; + +final list = [1, 2, 3]; + +final map = { list: "hello" }; + +fun writeFile(file: io\File, a: str, b: int, c: bool) > void !> any { + final data = "hello world"; + + file.write(data); +} + +fun main() > void !> any { + final file = io\File.open( + "/Users/giann/git/buzz/ooout.txt", + mode: io\FileMode.write, + ); + + writeFile(file, a: "hey", b: 12, c: true); + + file.close(); +} From 649695a46f8b91527f62575237d6e5554b648d2c Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Fri, 24 Oct 2025 08:30:37 +0200 Subject: [PATCH 06/12] fix(lsp): fixed condition to decide wether to attempt codegen in LSP --- src/GC.zig | 4 +--- src/Runner.zig | 18 +++++++++--------- src/lsp.zig | 13 +++++++++---- src/vm.zig | 2 +- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/GC.zig b/src/GC.zig index 3364b590..936db4ca 100644 --- a/src/GC.zig +++ b/src/GC.zig @@ -114,9 +114,7 @@ pub fn registerVM(self: *GC, vm: *v.VM) !void { } pub fn unregisterVM(self: *GC, vm: *v.VM) void { - const removed = self.active_vms.remove(vm); - - std.debug.assert(removed); + _ = self.active_vms.remove(vm); } pub fn deinit(self: *GC) void { diff --git a/src/Runner.zig b/src/Runner.zig index a15682ad..df18a209 100644 --- a/src/Runner.zig +++ b/src/Runner.zig @@ -113,7 +113,7 @@ pub fn runFile( timer.reset(); } - if (flavor.runs()) { + if (flavor != .Fmt) { const ast_slice = ast.slice(); if (try runner.codegen.generate(ast_slice)) |function| { @@ -122,11 +122,13 @@ pub fn runFile( timer.reset(); } - try runner.vm.interpret( - ast_slice, - function, - args, - ); + if (flavor.runs()) { + try runner.vm.interpret( + ast_slice, + function, + args, + ); + } if (!is_wasm) { running_time = timer.read(); @@ -154,7 +156,7 @@ pub fn runFile( }, ); } - } else if (flavor == .Fmt) { + } else { var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); @@ -163,8 +165,6 @@ pub fn runFile( io.stdoutWriter, ast, ); - } else { - io.print("Formatting and Ast dump is deactivated", .{}); } } else { return Parser.CompileError.Recoverable; diff --git a/src/lsp.zig b/src/lsp.zig index adba5bb7..2e09a829 100644 --- a/src/lsp.zig +++ b/src/lsp.zig @@ -70,18 +70,23 @@ const Document = struct { const ast = (parser.parse(std.mem.span(src), null, owned_uri) catch parser.ast) orelse parser.ast; - const errors = if (parser.reporter.reports.items.len == 0 and - (codegen.generate(ast.slice()) catch undefined) == null) + const codegen_errors = if (parser.reporter.last_error == null and + ((codegen.generate(ast.slice()) catch return error.OutOfMemory) == null) or codegen.reporter.reports.items.len > 0) try codegen.reporter.reports.toOwnedSlice(allocator) else - try parser.reporter.reports.toOwnedSlice(allocator); + &.{}; + const parse_errors = try parser.reporter.reports.toOwnedSlice(allocator); + + var errors = std.ArrayList(Reporter.Report).empty; + try errors.appendSlice(allocator, parse_errors); + try errors.appendSlice(allocator, codegen_errors); var doc = Document{ .arena = arena, .src = src, .uri = owned_uri, .ast = ast, - .errors = errors, + .errors = errors.items, }; if (ast.root != null) { diff --git a/src/vm.zig b/src/vm.zig index 0210f59d..cba8adee 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -32,7 +32,7 @@ pub const RunFlavor = enum { pub fn runs(self: RunFlavor) bool { return switch (self) { - .Run, .Test => true, + .Run, .Test, .Repl => true, else => false, }; } From 590c32427d08a98827cd7b61a3a10844074a055a Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Fri, 24 Oct 2025 10:08:48 +0200 Subject: [PATCH 07/12] fix(dap): Don't assume we're on the current_fiber --- src/Debugger.zig | 68 +++++++++++++++++++++++++------------------ tests/038-fibers.buzz | 2 +- 2 files changed, 41 insertions(+), 29 deletions(-) diff --git a/src/Debugger.zig b/src/Debugger.zig index 576224cf..6ceeba26 100644 --- a/src/Debugger.zig +++ b/src/Debugger.zig @@ -84,7 +84,10 @@ const VariableValue = union(enum) { bound_entry: BoundEntry, scope: union(enum) { global: void, - frame: *v.CallFrame, + frame: struct { + fiber: *v.Fiber, + frame: *v.CallFrame, + }, }, }; @@ -377,35 +380,42 @@ pub fn scopes(self: *Debugger, args: Arguments(.scopes)) Error!Response(.scopes) // Locals of the current frame var found = false; for (session.variables.items) |vbl| { - if (vbl.value == .scope and vbl.value.scope == .frame and @intFromPtr(vbl.value.scope.frame) == args.frameId) { + if (vbl.value == .scope and vbl.value.scope == .frame and @intFromPtr(vbl.value.scope.frame.frame) == args.frameId) { try scps.append(self.allocator, vbl.variable.scope); found = true; break; } } + // Otherwise search the appropriate frame if (!found) { - for (session.runner.vm.current_fiber.frames.items) |*frame| { - if (@intFromPtr(frame) == args.frameId) { - const scope = Variable{ - .value = .{ - .scope = .{ - .frame = frame, + var fiber: ?*v.Fiber = session.runner.vm.current_fiber; + while (fiber) |fb| : (fiber = fb.parent_fiber) fiber: { + for (fb.frames.items) |*frame| { + if (@intFromPtr(frame) == args.frameId) { + const scope = Variable{ + .value = .{ + .scope = .{ + .frame = .{ + .frame = frame, + .fiber = fb, + }, + }, }, - }, - .variable = .{ - .scope = .{ - .name = "Locals", - .variablesReference = session.variables.items.len + 1, - .expensive = false, + .variable = .{ + .scope = .{ + .name = "Locals", + .variablesReference = session.variables.items.len + 1, + .expensive = false, + }, }, - }, - }; + }; - try session.variables.append(self.allocator, scope); - try scps.append(self.allocator, scope.variable.scope); + try session.variables.append(self.allocator, scope); + try scps.append(self.allocator, scope.variable.scope); - break; + break :fiber; + } } } } @@ -462,33 +472,35 @@ pub fn variables(self: *Debugger, arguments: Arguments(.variables)) Error!Respon } } }, - .frame => |frame| { - const stack = @as([*]Value, @ptrCast(session.runner.vm.current_fiber.stack)); + .frame => |ctx| { + const frame = ctx.frame; + const fiber = ctx.fiber; + const stack = @as([*]Value, @ptrCast(fiber.stack)); const frame_base_idx = frame.slots - stack; const top = if (session.runner.vm.currentFrame() == frame) - session.runner.vm.current_fiber.stack_top + fiber.stack_top else top: { var idx: ?usize = 0; - for (session.runner.vm.current_fiber.frames.items, 0..) |*f, i| { + for (fiber.frames.items, 0..) |*f, i| { if (frame == f) { idx = i; break; } } - std.debug.assert(idx != null and idx.? < session.runner.vm.current_fiber.frames.items.len); + std.debug.assert(idx != null and idx.? < fiber.frames.items.len); - break :top session.runner.vm.current_fiber.frames.items[idx.? + 1].slots; + break :top fiber.frames.items[idx.? + 1].slots; }; const top_idx = top - stack; for (frame_base_idx..top_idx - 1) |idx| { - if (!session.runner.vm.current_fiber.locals_dbg.items[idx].isNull()) { + if (!fiber.locals_dbg.items[idx].isNull()) { try result.append( self.allocator, try self.variable( - session.runner.vm.current_fiber.stack[idx + 1], - session.runner.vm.current_fiber.locals_dbg.items[idx], + fiber.stack[idx + 1], + fiber.locals_dbg.items[idx], null, ), ); diff --git a/tests/038-fibers.buzz b/tests/038-fibers.buzz index 98e78aae..4799762b 100644 --- a/tests/038-fibers.buzz +++ b/tests/038-fibers.buzz @@ -1,6 +1,6 @@ import "std"; -test "fiber" { +fun main() > void !> any { // Async call, creates a new fiber, yield type must be nullable because resume will return null when nothing yielded final counter = &count(10); From a5e58de76e710cad09a34e4495911c21316b4129 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Fri, 24 Oct 2025 12:16:21 +0200 Subject: [PATCH 08/12] feat(dap): Relay output to client --- adapter-out.txt | 1 + build.zig.zon | 2 +- src/Debugger.zig | 122 +++++++++++++++++++++++++++++++++++++++++++++-- src/vm.zig | 2 +- 4 files changed, 121 insertions(+), 6 deletions(-) create mode 100644 adapter-out.txt diff --git a/adapter-out.txt b/adapter-out.txt new file mode 100644 index 00000000..e4b30294 --- /dev/null +++ b/adapter-out.txt @@ -0,0 +1 @@ +Got something 0 on stdout and 0 on stderr diff --git a/build.zig.zon b/build.zig.zon index cb47c575..505ab062 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -14,7 +14,7 @@ }, .dap_kit = .{ .url = "../dap-kit", - .hash = "dap_kit-0.0.0-OvEgyI5jAgAV-WFDuv3llJSM-P_DW4u9ATM2rhpm9gNE", + .hash = "dap_kit-0.0.0-OvEgyFdeAgCICFkifJ7HryViQ3lkeqwJmrKrhqZ7u6LY", }, }, .paths = .{""}, diff --git a/src/Debugger.zig b/src/Debugger.zig index 6ceeba26..08d97964 100644 --- a/src/Debugger.zig +++ b/src/Debugger.zig @@ -18,6 +18,39 @@ const Value = @import("value.zig").Value; const Debugger = @This(); +const OutputStream = enum { stdout, stderr }; + +var log_file: std.fs.File = undefined; +var log_mutex = std.Thread.Mutex{}; + +pub const std_options: std.Options = .{ + .log_level = if (builtin.mode == .Debug) .debug else .info, + .logFn = logFn, +}; + +/// We log to file to avoid polluting the program output forwarded to the client since +pub fn logFn( + comptime level: std.log.Level, + comptime scope: @Type(.enum_literal), + comptime format: []const u8, + args: anytype, +) void { + log_mutex.lock(); + defer log_mutex.unlock(); + + // Ensure we append to the file + log_file.seekFromEnd(0) catch {}; + + var writer = log_file.writerStreaming(&.{}); + writer.interface.print( + "[{s}] {s} " ++ format ++ "\n", + .{ + @tagName(scope), + @tagName(level), + } ++ args, + ) catch {}; +} + allocator: std.mem.Allocator, /// Queues to/from debugger/debuggee transport: Server.Transport, @@ -27,6 +60,12 @@ server_thread: std.Thread, adapter: Adapter(Debugger), /// Debug session session: ?DebugSession = null, +/// Stdout pipe so we can relay the output to the client +stdout_pipe: [2]std.posix.fd_t, +/// Stderr pipe so we can relay the output to the client +stderr_pipe: [2]std.posix.fd_t, +/// Output poller +out_poller: std.Io.Poller(OutputStream), const Error = error{ InvalidArguments, @@ -35,6 +74,7 @@ const Error = error{ SessionNotStarted, OutOfMemory, FiberNotFound, + CantCaptureOutput, }; const DebugSession = struct { @@ -120,7 +160,7 @@ const BreakpointKey = struct { }; }; -pub fn start(self: *Debugger, allocator: std.mem.Allocator, address: std.net.Address) (std.Thread.SpawnError || error{OutOfMemory})!void { +pub fn start(self: *Debugger, allocator: std.mem.Allocator, address: std.net.Address) (std.Thread.SpawnError || error{OutOfMemory} || Error)!void { self.* = Debugger{ .allocator = allocator, .transport = .{ @@ -129,8 +169,31 @@ pub fn start(self: *Debugger, allocator: std.mem.Allocator, address: std.net.Add }, .server_thread = undefined, .adapter = undefined, + .stdout_pipe = std.posix.pipe() catch return error.CantCaptureOutput, + .stderr_pipe = std.posix.pipe() catch return error.CantCaptureOutput, + .out_poller = undefined, }; + // FIXME: do this for windows with CreatePipe (https://github.com/ziglang/zig/blob/master/lib/std/os/windows.zig#L187) + + // Duplicate output pipe's ends to stdout/stderr + std.posix.dup2(self.stdout_pipe[1], std.posix.STDOUT_FILENO) catch return error.CantCaptureOutput; + std.posix.dup2(self.stderr_pipe[1], std.posix.STDERR_FILENO) catch return error.CantCaptureOutput; + + // Create poller on the read ends of the pipes + self.out_poller = std.Io.poll( + self.allocator, + OutputStream, + .{ + .stdout = std.fs.File{ + .handle = self.stdout_pipe[0], + }, + .stderr = std.fs.File{ + .handle = self.stderr_pipe[0], + }, + }, + ); + self.adapter = .{ .handler = self, .transport = &self.transport, @@ -144,7 +207,7 @@ pub fn start(self: *Debugger, allocator: std.mem.Allocator, address: std.net.Add } /// Returns true if program has been terminated -pub fn onDispatch(self: *Debugger) bool { +pub fn onDispatch(self: *Debugger) Error!bool { _ = self.adapter.handleRequest(); if (self.session.?.run_state == .terminated) { @@ -202,6 +265,39 @@ pub fn onDispatch(self: *Debugger) bool { } } + // Do we have output to relay to the client + if (self.out_poller.pollTimeout(500_000) catch false) { + const stdout = try self.out_poller.toOwnedSlice(.stdout); + if (stdout.len > 0) { + self.adapter.emitEvent( + .{ + .event = .output, + .body = .{ + .output = .{ + .category = .stdout, + .output = stdout, + }, + }, + }, + ); + } + + const stderr = try self.out_poller.toOwnedSlice(.stderr); + if (stderr.len > 0) { + self.adapter.emitEvent( + .{ + .event = .output, + .body = .{ + .output = .{ + .category = .stderr, + .output = stderr, + }, + }, + }, + ); + } + } + return false; } @@ -210,8 +306,6 @@ pub fn onDispatch(self: *Debugger) bool { // pub fn initialize(_: *Debugger, _: Arguments(.initialize)) Error!Response(.initialize) { - std.log.debug("Handling `initialize request...`", .{}); - return .{ .supportsConfigurationDoneRequest = true, }; @@ -869,6 +963,7 @@ pub fn main() !u8 { \\-p, --port On which port the debugger should be listening \\-v, --version Print version and exit \\-L, --library ... Add search path for external libraries + \\-o, --output File where any log output will be written (defaults to $PWD/log.txt) \\ ); @@ -919,6 +1014,25 @@ pub fn main() !u8 { return 0; } + // Open log file + const output_file = res.args.output orelse "./log.txt"; + log_file = if (std.fs.path.isAbsolute(output_file)) + try std.fs.createFileAbsolute( + output_file, + .{ + .truncate = false, + .read = false, + }, + ) + else + try std.fs.cwd().createFile( + output_file, + .{ + .truncate = false, + .read = false, + }, + ); + // Start the debugger var debugger: Debugger = undefined; try debugger.start( diff --git a/src/vm.zig b/src/vm.zig index cba8adee..3de7bf45 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -880,7 +880,7 @@ pub const VM = struct { } if (self.debugger) |debugger| { - if (debugger.onDispatch()) { + if (debugger.onDispatch() catch false) { return; } } From 3c56c7a13f0c21a00ff02328c7da69fa9bd32499 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Fri, 24 Oct 2025 15:58:18 +0200 Subject: [PATCH 09/12] feat(dap): continue/stepIn/stepOver/stepOut --- src/Debugger.zig | 267 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 218 insertions(+), 49 deletions(-) diff --git a/src/Debugger.zig b/src/Debugger.zig index 08d97964..af0fad4a 100644 --- a/src/Debugger.zig +++ b/src/Debugger.zig @@ -75,12 +75,13 @@ const Error = error{ OutOfMemory, FiberNotFound, CantCaptureOutput, + BadState, }; const DebugSession = struct { runner: Runner, /// If true, program must terminate - run_state: ?RunState = .resumed, + run_state: RunState = .resumed, /// List of breakpoints to enforce breakpoints: std.ArrayHashMapUnmanaged( BreakpointKey, @@ -95,33 +96,49 @@ const DebugSession = struct { self.variables.deinit(allocator); } - pub const RunState = enum { - paused, - terminated, - resumed, + pub const Step = struct { + line: u32, + frame: *v.CallFrame, + frame_count: usize, }; + pub const RunState = union(enum) { + paused: void, + /// Should stop at the next line after this value + step_over: Step, + step_in: Step, + /// Index of the frame that was paused, we pause when that frame_count is inferior to it + step_out: usize, + terminated: void, + resumed: void, + }; + + pub fn setState(self: *DebugSession, new_state: RunState) void { + self.run_state = new_state; + + // Reset variable cache (but keep globals) + if (self.variables.items.len > 1) { + self.variables.shrinkRetainingCapacity(1); + } + } + pub fn deinit(self: *DebugSession) void { self.breakpoints.deinit(self.runner.gc.allocator); self.runner.deinit(); } }; -const MapEntry = struct { - map_type: *o.ObjTypeDef, - key: Value, - value: Value, -}; - -const BoundEntry = struct { - receiver: Value, - method: Value, -}; - const VariableValue = union(enum) { value: Value, - map_entry: MapEntry, - bound_entry: BoundEntry, + map_entry: struct { + map_type: *o.ObjTypeDef, + key: Value, + value: Value, + }, + bound_entry: struct { + receiver: Value, + method: Value, + }, scope: union(enum) { global: void, frame: struct { @@ -206,30 +223,51 @@ pub fn start(self: *Debugger, allocator: std.mem.Allocator, address: std.net.Add ); } +fn currentLine(self: *Debugger, current_frame: *v.CallFrame) u32 { + const location = current_frame.closure.function.chunk.locations.items[current_frame.ip]; + return @intCast(self.session.?.runner.vm.current_ast.tokens.items(.line)[location] + 1); +} + /// Returns true if program has been terminated pub fn onDispatch(self: *Debugger) Error!bool { _ = self.adapter.handleRequest(); - if (self.session.?.run_state == .terminated) { - return true; - } else if (self.session.?.run_state != .paused) { - // Did we reach a breakpoint? - const current_frame = self.session.?.runner.vm.currentFrame(); + switch (self.session.?.run_state) { + .paused => {}, + .terminated => return true, + .step_over => |step| { + const current_frame = self.session.?.runner.vm.currentFrame(); - if (current_frame != null and - current_frame.?.ip < current_frame.?.closure.function.chunk.locations.items.len) - { - const location = current_frame.?.closure.function.chunk.locations.items[current_frame.?.ip]; - const line = self.session.?.runner.vm.current_ast.tokens.items(.line)[location] + 1; - const script = self.session.?.runner.vm.current_ast.tokens.items(.script_name)[location]; + // We pause on the next line in the same frame + if (current_frame != null and + (current_frame == step.frame or self.session.?.runner.vm.current_fiber.frame_count < step.frame_count) and + current_frame.?.ip < current_frame.?.closure.function.chunk.locations.items.len and + self.currentLine(current_frame.?) != step.line) + { + self.session.?.setState(.paused); - if (self.session.?.breakpoints.getPtr( - .{ - .line = @intCast(line), - .script = script, - }, - )) |breakpoint| { - self.session.?.run_state = .paused; + // Notifiy we stopped + self.adapter.emitEvent( + .{ + .event = .stopped, + .body = .{ + .stopped = .{ + .reason = .step, + .threadId = @intFromPtr(self.session.?.runner.vm.current_fiber), + }, + }, + }, + ); + } + }, + .step_out => |frame_count| { + const current_frame = self.session.?.runner.vm.currentFrame(); + + // We pause when we got out of the frame + if (current_frame != null and + self.session.?.runner.vm.current_fiber.frame_count < frame_count) + { + self.session.?.setState(.paused); // Notifiy we stopped self.adapter.emitEvent( @@ -237,32 +275,91 @@ pub fn onDispatch(self: *Debugger) Error!bool { .event = .stopped, .body = .{ .stopped = .{ - .reason = .breakpoint, + .reason = .step, .threadId = @intFromPtr(self.session.?.runner.vm.current_fiber), - .hitBreakpointIds = if (breakpoint.id) |bid| &.{bid} else null, }, }, }, ); + } + }, + .step_in => |step| { + const current_frame = self.session.?.runner.vm.currentFrame(); - // If breakpoint breviously unverified, notify that too - if (!breakpoint.verified) { - breakpoint.verified = true; + // We step when the frame or the line changes + if (current_frame != null and + (current_frame != step.frame or + (current_frame.?.ip < current_frame.?.closure.function.chunk.locations.items.len and + self.currentLine(current_frame.?) != step.line))) + { + self.session.?.setState(.paused); + // Notifiy we stopped + self.adapter.emitEvent( + .{ + .event = .stopped, + .body = .{ + .stopped = .{ + .reason = .step, + .threadId = @intFromPtr(self.session.?.runner.vm.current_fiber), + }, + }, + }, + ); + } + }, + .resumed => { + // Did we reach a breakpoint? + const current_frame = self.session.?.runner.vm.currentFrame(); + + if (current_frame != null and + current_frame.?.ip < current_frame.?.closure.function.chunk.locations.items.len) + { + const location = current_frame.?.closure.function.chunk.locations.items[current_frame.?.ip]; + const line = self.session.?.runner.vm.current_ast.tokens.items(.line)[location] + 1; + const script = self.session.?.runner.vm.current_ast.tokens.items(.script_name)[location]; + + if (self.session.?.breakpoints.getPtr( + .{ + .line = @intCast(line), + .script = script, + }, + )) |breakpoint| { + self.session.?.setState(.paused); + + // Notifiy we stopped self.adapter.emitEvent( .{ - .event = .breakpoint, + .event = .stopped, .body = .{ - .breakpoint = .{ - .breakpoint = breakpoint.*, - .reason = .changed, + .stopped = .{ + .reason = .breakpoint, + .threadId = @intFromPtr(self.session.?.runner.vm.current_fiber), + .hitBreakpointIds = if (breakpoint.id) |bid| &.{bid} else null, }, }, }, ); + + // If breakpoint breviously unverified, notify that too + if (!breakpoint.verified) { + breakpoint.verified = true; + + self.adapter.emitEvent( + .{ + .event = .breakpoint, + .body = .{ + .breakpoint = .{ + .breakpoint = breakpoint.*, + .reason = .changed, + }, + }, + }, + ); + } } } - } + }, } // Do we have output to relay to the client @@ -282,6 +379,7 @@ pub fn onDispatch(self: *Debugger) Error!bool { ); } + // FIXME: stderr doesn't seem to be relayed here? const stderr = try self.out_poller.toOwnedSlice(.stderr); if (stderr.len > 0) { self.adapter.emitEvent( @@ -389,6 +487,77 @@ pub fn setExceptionBreakpoints(_: *Debugger, _: Arguments(.setExceptionBreakpoin return .{}; } +pub fn next(self: *Debugger, _: Arguments(.next)) Error!Response(.next) { + // We ignore the threadId for now, until we actually support multi threading + if (self.session) |*session| { + if (session.run_state != .paused) { + return error.BadState; + } + + session.setState( + .{ + .step_over = .{ + .frame_count = session.runner.vm.current_fiber.frame_count, + .frame = session.runner.vm.currentFrame().?, + .line = self.currentLine(session.runner.vm.currentFrame().?), + }, + }, + ); + } + + return error.SessionNotStarted; +} + +pub fn stepIn(self: *Debugger, _: Arguments(.stepIn)) Error!Response(.stepIn) { + // We ignore the threadId for now, until we actually support multi threading + if (self.session) |*session| { + if (session.run_state != .paused) { + return error.BadState; + } + + session.setState( + .{ + .step_in = .{ + .frame_count = session.runner.vm.current_fiber.frame_count, + .frame = session.runner.vm.currentFrame().?, + .line = self.currentLine(session.runner.vm.currentFrame().?), + }, + }, + ); + } + + return error.SessionNotStarted; +} + +pub fn stepOut(self: *Debugger, _: Arguments(.stepOut)) Error!Response(.stepOut) { + // We ignore the threadId for now, until we actually support multi threading + if (self.session) |*session| { + if (session.run_state != .paused) { + return error.BadState; + } + + session.setState( + .{ + .step_out = session.runner.vm.current_fiber.frame_count, + }, + ); + } + + return error.SessionNotStarted; +} + +pub fn @"continue"(self: *Debugger, _: Arguments(.@"continue")) Error!Response(.@"continue") { + if (self.session) |*session| { + session.setState(.resumed); + + return .{}; + } + + return error.SessionNotStarted; +} + +// FIXME: right now we're saying fibers are threads but really threads should be threads. When the thread std lib will land, +// how will we reconciliate the two with DAP? pub fn threads(self: *Debugger, _: Arguments(.threads)) Error!Response(.threads) { if (self.session) |*session| { var thds = std.ArrayList(ProtocolMessage.Thread).empty; @@ -588,7 +757,7 @@ pub fn variables(self: *Debugger, arguments: Arguments(.variables)) Error!Respon }; const top_idx = top - stack; - for (frame_base_idx..top_idx - 1) |idx| { + for (frame_base_idx..@min(top_idx - 1, fiber.locals_dbg.items.len)) |idx| { if (!fiber.locals_dbg.items[idx].isNull()) { try result.append( self.allocator, @@ -668,7 +837,7 @@ pub fn configurationDone(self: *Debugger, _: Arguments(.configurationDone)) Erro pub fn disconnect(self: *Debugger, _: Arguments(.disconnect)) Error!Response(.disconnect) { if (self.session) |*session| { - session.run_state = .terminated; + session.setState(.terminated); } return error.SessionNotStarted; @@ -676,7 +845,7 @@ pub fn disconnect(self: *Debugger, _: Arguments(.disconnect)) Error!Response(.di pub fn pause(self: *Debugger, _: Arguments(.pause)) Error!Response(.pause) { if (self.session) |*session| { - session.run_state = .paused; + session.setState(.paused); } return error.SessionNotStarted; From 8983e7c04c577f4d66506ce2a9c94b6bfc0a2511 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Sun, 26 Oct 2025 21:38:06 +0100 Subject: [PATCH 10/12] feat(dap): evaluate --- adapter-out.txt | 1 - build.zig | 2 +- build.zig.zon | 4 +- src/Debugger.zig | 152 +++++++++++++-------- src/Runner.zig | 329 ++++++++++++++++++++++++++++++++++++---------- src/behavior.zig | 6 +- src/main.zig | 16 ++- src/repl.zig | 192 ++++++--------------------- src/vm.zig | 50 ++++--- vendors/linenoise | 2 +- 10 files changed, 438 insertions(+), 316 deletions(-) delete mode 100644 adapter-out.txt diff --git a/adapter-out.txt b/adapter-out.txt deleted file mode 100644 index e4b30294..00000000 --- a/adapter-out.txt +++ /dev/null @@ -1 +0,0 @@ -Got something 0 on stdout and 0 on stderr diff --git a/build.zig b/build.zig index 93e3361f..5186b396 100644 --- a/build.zig +++ b/build.zig @@ -410,7 +410,7 @@ pub fn build(b: *Build) !void { c.linkLibrary(static_lib); } - for ([_]*std.Build.Step.Compile{ static_lib, lib, exe, debugger_exe, lsp_exe, behavior_exe, check_exe }) |c| { + for ([_]*std.Build.Step.Compile{ tests, static_lib, lib, exe, debugger_exe, lsp_exe, behavior_exe, check_exe }) |c| { c.root_module.addImport( "dap", dap.module("dap_kit"), diff --git a/build.zig.zon b/build.zig.zon index 505ab062..1b58abd9 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -13,8 +13,8 @@ .hash = "lsp_kit-0.1.0-bi_PLzAyCgClDh8_M0U9Q50ysdsQBuRuBTZfwg6rZPd6", }, .dap_kit = .{ - .url = "../dap-kit", - .hash = "dap_kit-0.0.0-OvEgyFdeAgCICFkifJ7HryViQ3lkeqwJmrKrhqZ7u6LY", + .url = "git+https://github.com/buzz-language/dap-kit.git#0b82af2837f4ded84ae55a41d1591b3a9b75365d", + .hash = "dap_kit-0.0.0-OvEgyGFeAgATdO-Up0sxHeCpfQ6aOs6ekOwJ_nX4yOOX", }, }, .paths = .{""}, diff --git a/src/Debugger.zig b/src/Debugger.zig index af0fad4a..7bc08707 100644 --- a/src/Debugger.zig +++ b/src/Debugger.zig @@ -15,6 +15,7 @@ const VM = v.VM; const Runner = @import("Runner.zig"); const o = @import("obj.zig"); const Value = @import("value.zig").Value; +const assert = std.debug.assert; const Debugger = @This(); @@ -76,6 +77,7 @@ const Error = error{ FiberNotFound, CantCaptureOutput, BadState, + CantEvaluate, }; const DebugSession = struct { @@ -195,7 +197,7 @@ pub fn start(self: *Debugger, allocator: std.mem.Allocator, address: std.net.Add // Duplicate output pipe's ends to stdout/stderr std.posix.dup2(self.stdout_pipe[1], std.posix.STDOUT_FILENO) catch return error.CantCaptureOutput; - std.posix.dup2(self.stderr_pipe[1], std.posix.STDERR_FILENO) catch return error.CantCaptureOutput; + // std.posix.dup2(self.stderr_pipe[1], std.posix.STDERR_FILENO) catch return error.CantCaptureOutput; // Create poller on the read ends of the pipes self.out_poller = std.Io.poll( @@ -425,6 +427,12 @@ pub fn launch(self: *Debugger, arguments: Arguments(.launch)) Error!Response(.la .runner = undefined, }; + self.session.?.runner.init( + self.allocator, + .Run, + self, + ) catch return error.LaunchFailed; + try self.session.?.variables.append( self.allocator, .{ @@ -447,11 +455,8 @@ pub fn launch(self: *Debugger, arguments: Arguments(.launch)) Error!Response(.la // While debugger is active, the program won't start right away // FIXME: needs to report stdout of the program with `output` events self.session.?.runner.runFile( - self.allocator, program, &.{}, // TODO - .Run, // TODO: we should be able to debug tests too - self, ) catch return error.LaunchFailed; } @@ -597,7 +602,7 @@ pub fn stackTrace(self: *Debugger, arguments: Arguments(.stackTrace)) Error!Resp var i = if (fb.frame_count > 0) fb.frame_count - 1 else fb.frame_count; while (i >= 0) : (i -= 1) { const frame = &fb.frames.items[i]; - const location = frame.closure.function.chunk.ast.tokens + const location = session.runner.vm.current_ast.tokens .get(frame.closure.function.chunk.locations.items[frame.ip]); try stack_frames.append( @@ -633,6 +638,21 @@ pub fn stackTrace(self: *Debugger, arguments: Arguments(.stackTrace)) Error!Resp return error.SessionNotStarted; } +fn findFrame(self: *Debugger, frameId: u64) ?struct { *v.CallFrame, *v.Fiber } { + if (self.session) |*session| { + var fiber: ?*v.Fiber = session.runner.vm.current_fiber; + while (fiber) |fb| : (fiber = fb.parent_fiber) { + for (fb.frames.items) |*frame| { + if (@intFromPtr(frame) == frameId) { + return .{ frame, fb }; + } + } + } + } + + return null; +} + pub fn scopes(self: *Debugger, args: Arguments(.scopes)) Error!Response(.scopes) { if (self.session) |*session| { var scps = try std.ArrayList(ProtocolMessage.Scope).initCapacity( @@ -652,34 +672,27 @@ pub fn scopes(self: *Debugger, args: Arguments(.scopes)) Error!Response(.scopes) // Otherwise search the appropriate frame if (!found) { - var fiber: ?*v.Fiber = session.runner.vm.current_fiber; - while (fiber) |fb| : (fiber = fb.parent_fiber) fiber: { - for (fb.frames.items) |*frame| { - if (@intFromPtr(frame) == args.frameId) { - const scope = Variable{ - .value = .{ - .scope = .{ - .frame = .{ - .frame = frame, - .fiber = fb, - }, - }, - }, - .variable = .{ - .scope = .{ - .name = "Locals", - .variablesReference = session.variables.items.len + 1, - .expensive = false, - }, + if (self.findFrame(args.frameId)) |frame_fiber| { + const scope = Variable{ + .value = .{ + .scope = .{ + .frame = .{ + .frame = frame_fiber.@"0", + .fiber = frame_fiber.@"1", }, - }; - - try session.variables.append(self.allocator, scope); - try scps.append(self.allocator, scope.variable.scope); + }, + }, + .variable = .{ + .scope = .{ + .name = "Locals", + .variablesReference = session.variables.items.len + 1, + .expensive = false, + }, + }, + }; - break :fiber; - } - } + try session.variables.append(self.allocator, scope); + try scps.append(self.allocator, scope.variable.scope); } } @@ -740,33 +753,24 @@ pub fn variables(self: *Debugger, arguments: Arguments(.variables)) Error!Respon const fiber = ctx.fiber; const stack = @as([*]Value, @ptrCast(fiber.stack)); const frame_base_idx = frame.slots - stack; - const top = if (session.runner.vm.currentFrame() == frame) - fiber.stack_top - else top: { - var idx: ?usize = 0; - for (fiber.frames.items, 0..) |*f, i| { - if (frame == f) { - idx = i; - break; - } - } - - std.debug.assert(idx != null and idx.? < fiber.frames.items.len); - - break :top fiber.frames.items[idx.? + 1].slots; - }; + const top = session.runner.frameTop(fiber, frame); const top_idx = top - stack; for (frame_base_idx..@min(top_idx - 1, fiber.locals_dbg.items.len)) |idx| { if (!fiber.locals_dbg.items[idx].isNull()) { - try result.append( - self.allocator, - try self.variable( - fiber.stack[idx + 1], - fiber.locals_dbg.items[idx], - null, - ), - ); + const name = fiber.locals_dbg.items[idx]; + + // "Hidden" locals start with `$` + if (o.ObjString.cast(name.obj()).?.string[0] != '$') { + try result.append( + self.allocator, + try self.variable( + fiber.stack[idx + 1], + name, + null, + ), + ); + } } } }, @@ -819,7 +823,7 @@ pub fn variables(self: *Debugger, arguments: Arguments(.variables)) Error!Respon // Populate children cache session.variables.items[ref].children = session.variables.items[previous_len..]; - std.debug.assert(session.variables.items[ref].children.?.len == result.items.len); + assert(session.variables.items[ref].children.?.len == result.items.len); } } @@ -831,6 +835,41 @@ pub fn variables(self: *Debugger, arguments: Arguments(.variables)) Error!Respon return error.SessionNotStarted; } +pub fn evaluate(self: *Debugger, args: Arguments(.evaluate)) Error!Response(.evaluate) { + if (self.session) |*session| { + const frame, const fiber = if (args.frameId) |frameId| + self.findFrame(frameId) orelse .{ null, null } + else + .{ null, null }; + + const result = session.runner.evaluate( + fiber orelse session.runner.vm.current_fiber, + frame orelse session.runner.vm.currentFrame().?, + args.expression, + ) catch return error.CantEvaluate; + + if (result.isObj()) { + _ = try self.variable( + result, + (session.runner.gc.copyString("__eval__") catch return error.OutOfMemory).toValue(), + null, + ); + } + + return .{ + .result = result.toStringAlloc(self.allocator) catch return error.OutOfMemory, + .type = (result.typeOf(&session.runner.gc) catch return error.OutOfMemory) + .toStringAlloc(self.allocator, false) catch return error.OutOfMemory, + .variablesReference = if (result.isObj()) + session.variables.items.len - 1 + else + 0, + }; + } + + return error.SessionNotStarted; +} + pub fn configurationDone(self: *Debugger, _: Arguments(.configurationDone)) Error!Response(.configurationDone) { if (self.session == null) return error.SessionNotStarted; } @@ -839,8 +878,6 @@ pub fn disconnect(self: *Debugger, _: Arguments(.disconnect)) Error!Response(.di if (self.session) |*session| { session.setState(.terminated); } - - return error.SessionNotStarted; } pub fn pause(self: *Debugger, _: Arguments(.pause)) Error!Response(.pause) { @@ -1211,6 +1248,7 @@ pub fn main() !u8 { res.args.port orelse 9000, ), ); + errdefer debugger.server_thread.join(); // Read requests while (true) { diff --git a/src/Runner.zig b/src/Runner.zig index df18a209..280904c4 100644 --- a/src/Runner.zig +++ b/src/Runner.zig @@ -3,6 +3,8 @@ const is_wasm = builtin.cpu.arch.isWasm(); const builtin = @import("builtin"); const _vm = @import("vm.zig"); const VM = _vm.VM; +const CallFrame = _vm.CallFrame; +const Fiber = _vm.Fiber; const RunFlavor = _vm.RunFlavor; const ImportRegistry = _vm.ImportRegistry; const Parser = @import("Parser.zig"); @@ -15,6 +17,9 @@ const Ast = @import("Ast.zig"); const BuildOptions = @import("build_options"); const io = @import("io.zig"); const Renderer = @import("renderer.zig").Renderer; +const Value = @import("value.zig").Value; +const o = @import("obj.zig"); +const disassembler = @import("disassembler.zig"); const Runner = @This(); @@ -42,52 +47,50 @@ pub fn deinit(self: *Runner) void { self.vm.deinit(); } -pub fn runFile( - runner: *Runner, - allocator: std.mem.Allocator, - file_name: []const u8, - args: []const []const u8, - flavor: RunFlavor, - debugger: ?*Debugger, -) !void { - var total_timer = if (!is_wasm) std.time.Timer.start() catch unreachable else {}; - - runner.* = .{ +/// Runner must, most of the time be on the stack, and it contains several circular references +/// So the use provides the ptr to it and this function populates it +pub fn init(runner_ptr: *Runner, allocator: std.mem.Allocator, flavor: RunFlavor, debugger: ?*Debugger) !void { + runner_ptr.* = .{ .gc = try GC.init(allocator), .vm = undefined, .parser = undefined, .codegen = undefined, }; - defer if (debugger == null) runner.deinit(); - runner.gc.type_registry = try TypeRegistry.init(&runner.gc); - runner.vm = try VM.init( - &runner.gc, - &runner.import_registry, + runner_ptr.gc.type_registry = try TypeRegistry.init(&runner_ptr.gc); + runner_ptr.vm = try VM.init( + &runner_ptr.gc, + &runner_ptr.import_registry, flavor, debugger, ); - runner.vm.jit = if (BuildOptions.jit and BuildOptions.cycle_limit == null and debugger == null) - JIT.init(&runner.vm) + runner_ptr.vm.jit = if (BuildOptions.jit and BuildOptions.cycle_limit == null and debugger == null) + JIT.init(&runner_ptr.vm) else null; - runner.parser = Parser.init( - &runner.gc, - &runner.imports, + runner_ptr.parser = Parser.init( + &runner_ptr.gc, + &runner_ptr.imports, false, flavor, ); - runner.codegen = CodeGen.init( - &runner.gc, - &runner.parser, + runner_ptr.codegen = CodeGen.init( + &runner_ptr.gc, + &runner_ptr.parser, flavor, - if (runner.vm.jit) |*jit| jit else null, + if (runner_ptr.vm.jit) |*jit| jit else null, debugger != null, ); +} +pub fn runFile( + runner: *Runner, + file_name: []const u8, + args: []const []const u8, +) !void { var file = (if (std.fs.path.isAbsolute(file_name)) std.fs.openFileAbsolute(file_name, .{}) else @@ -97,67 +100,28 @@ pub fn runFile( }; defer file.close(); - const source = try allocator.alloc(u8, (try file.stat()).size); - defer if (debugger == null) allocator.free(source); + const source = try runner.gc.allocator.alloc(u8, (try file.stat()).size); + defer if (runner.vm.debugger == null) runner.gc.allocator.free(source); _ = try file.readAll(source); - var timer = try std.time.Timer.start(); - var parsing_time: u64 = 0; - var codegen_time: u64 = 0; - var running_time: u64 = 0; - if (try runner.parser.parse(source, null, file_name)) |ast| { - if (!is_wasm) { - parsing_time = timer.read(); - timer.reset(); - } - - if (flavor != .Fmt) { + if (runner.vm.flavor != .Fmt) { const ast_slice = ast.slice(); if (try runner.codegen.generate(ast_slice)) |function| { - if (!is_wasm) { - codegen_time = timer.read(); - timer.reset(); - } - - if (flavor.runs()) { + if (runner.vm.flavor.runs()) { try runner.vm.interpret( ast_slice, function, args, ); } - - if (!is_wasm) { - running_time = timer.read(); - } } else { return Parser.CompileError.Recoverable; } - - if (BuildOptions.show_perf and flavor != .Check and flavor != .Fmt) { - io.print( - if (builtin.os.tag != .windows) - "\x1b[2mParsing: {[parsing]D}\nCodegen: {[codegen]D}\nRun: {[running]D}\nJIT: {[jit]D}\nGC: {[gc]D}\nTotal: {[total]D}\nFull GC: {[gc_full]} | GC: {[gc_light]} | Max allocated: {[max_alloc]B}\n\x1b[0m" - else - "Parsing: {[parsing]D}\nCodegen: {[codegen]D}\nRun: {[running]D}\nJIT: {[jit]D}\nGC: {[gc]D}\nTotal: {[total]D}\nFull GC: {[gc_full]} | GC: {[gc_light]} | Max allocated: {[max_alloc]B}\n", - .{ - .parsing = parsing_time, - .codegen = codegen_time, - .running = running_time, - .jit = if (runner.vm.jit) |jit| jit.jit_time else 0, - .gc = runner.gc.gc_time, - .total = if (!is_wasm) total_timer.read() else 0, - .gc_full = runner.gc.full_collection_count, - .gc_light = runner.gc.light_collection_count, - .max_alloc = runner.gc.max_allocated, - }, - ); - } } else { - var arena = std.heap.ArenaAllocator.init(allocator); + var arena = std.heap.ArenaAllocator.init(runner.gc.allocator); defer arena.deinit(); try Renderer.render( @@ -170,3 +134,230 @@ pub fn runFile( return Parser.CompileError.Recoverable; } } + +/// Evaluate source using the current parser and vm state and return the value produced if any +/// Used by REPL and Debugger +pub fn runSource(self: *Runner, source: []const u8, name: []const u8) !?Value { + if (try self.parser.parse(source, null, name)) |ast| { + const ast_slice = ast.slice(); + if (try self.codegen.generate(ast_slice)) |function| { + try self.vm.interpret( + ast_slice, + function, + null, + ); + + // Does the user code ends with a lone expression? + const fnode = ast.nodes.items(.components)[ast.root.?].Function; + const statements = ast.nodes.items(.components)[fnode.body.?].Block; + const last_statement = if (statements.len > 0) statements[statements.len - 1] else null; + if (last_statement != null and ast.nodes.items(.tag)[last_statement.?] == .Expression) { + return self.vm.pop(); + } + } else { + return Parser.CompileError.Recoverable; + } + } else if (self.parser.reporter.last_error == .unclosed) { + return null; + } else { + return Parser.CompileError.Recoverable; + } + + return null; +} + +pub fn frameTop(self: *Runner, fiber: *Fiber, frame: *CallFrame) [*]Value { + return if (self.vm.currentFrame() == frame) + fiber.stack_top + else top: { + var idx: ?usize = 0; + for (fiber.frames.items, 0..) |*f, i| { + if (frame == f) { + idx = i; + break; + } + } + + std.debug.assert(idx != null and idx.? < fiber.frames.items.len); + + break :top fiber.frames.items[idx.? + 1].slots; + }; +} + +/// Evaluate expression in its own fiber, child of the selected fiber, and copying the selected frame's locals. +/// Can only work while debugging because local names are not available otherwise +pub fn evaluate(self: *Runner, parent_fiber: *Fiber, parent_frame: *CallFrame, expr: []const u8) !Value { + std.debug.assert(self.vm.debugger != null); + + // We wrap the expression into a function that takes parent_frame's locals as arguments + // (we don't need to do it for globals, they'll be accessible by the current state of the parser) + var source = std.Io.Writer.Allocating.init(self.gc.allocator); + defer source.deinit(); + + try source.writer.print("fun eval(", .{}); + + const stack: [*]Value = @ptrCast(parent_fiber.stack); + const frame_base_idx = parent_frame.slots - stack; + const top = self.frameTop(parent_fiber, parent_frame); + const top_idx = top - stack; + const local_count = @min(top_idx - 1, parent_fiber.locals_dbg.items.len); + for (frame_base_idx..local_count) |i| { + const local_dbg = parent_fiber.locals_dbg.items[i]; + + if (local_dbg.isObj()) { + const name = o.ObjString.cast(local_dbg.obj()).?.string; + + // "Hidden" locals start with `$` + if (name[0] != '$') { + try source.writer.print( + "{s}: {s}, ", + .{ + name, + try (try parent_fiber.stack[i + 1].typeOf(&self.gc)) + .toStringAlloc(self.gc.allocator, false), + }, + ); + } + } + } + + // Body + try source.writer.print(") > void !> any => ({s});", .{expr}); + + // We put temporarly the parser/codegen in repl flavor to avoid generating a call to main + const previous_flavor = self.parser.flavor; + self.parser.flavor = .Repl; + self.codegen.flavor = .Repl; + self.vm.flavor = .Repl; + // We don't want the evaluated expression to be considered by the debugger + const previous_debugger = self.vm.debugger; + self.codegen.debugging = false; + self.vm.debugger = null; + const previous_global_count = self.parser.globals.items.len; + defer { + self.parser.flavor = previous_flavor; + self.codegen.flavor = previous_flavor; + self.vm.flavor = previous_flavor; + self.codegen.debugging = previous_debugger != null; + self.vm.debugger = previous_debugger; + self.codegen.reporter.last_error = null; + self.codegen.reporter.panic_mode = false; + self.parser.reporter.last_error = null; + self.parser.reporter.panic_mode = false; + } + errdefer { // Do the same in case of error + self.parser.flavor = previous_flavor; + self.codegen.flavor = previous_flavor; + self.vm.flavor = previous_flavor; + self.codegen.debugging = previous_debugger != null; + self.vm.debugger = previous_debugger; + self.codegen.reporter.last_error = null; + self.codegen.reporter.panic_mode = false; + self.parser.reporter.last_error = null; + self.parser.reporter.panic_mode = false; + self.parser.globals.shrinkRetainingCapacity(previous_global_count); + } + + // Compile it + if (try self.parser.parse(source.written(), null, "__eval")) |ast| { + const ast_slice = ast.slice(); + self.vm.current_ast = ast_slice; // We still use the same AST, but the slice has changed + if (try self.codegen.generate(ast_slice)) |function| { + // Create a new fiber run the function into it with the selected frame's locals as arguments + var fiber = Fiber.init( + self.gc.allocator, + try self.gc.type_registry.getTypeDef( + .{ + .optional = false, + .def_type = .Fiber, + .resolved_type = .{ + .Fiber = .{ + .return_type = self.gc.type_registry.void_type, + .yield_type = self.gc.type_registry.void_type, + }, + }, + }, + ), + null, + null, + undefined, + null, + ) catch return error.OutOfMemory; + defer fiber.deinit(); + + fiber.is_eval = true; + + const previous_fiber = self.vm.current_fiber; + defer self.vm.current_fiber = previous_fiber; + // If program fails, return to original fiber + errdefer { + self.vm.current_fiber = previous_fiber; + } + self.vm.current_fiber = &fiber; + fiber.status = .Running; + + // Push function and locals + self.vm.push( + (try self.gc.allocateObject( + try o.ObjClosure.init( + self.gc.allocator, + &self.vm, + function, + ), + )).toValue(), + ); + + // Call + try self.vm.callValue( + self.vm.peek(0), + 0, + null, + ); + + // Run it + self.vm.run(); + + // This will only have defined our eval function as a new global, + // now we get that global and call it with the locals as arguments + // We also remove it from the globals to avoid polluting the globals and avoid collision with other evaluates + const eval_value = self.vm.globals.pop().?; + _ = self.parser.globals.pop(); + _ = self.vm.globals_dbg.pop(); + self.vm.globals_count -= 1; + + // Should be our eval function + std.debug.assert( + eval_value.isObj() and + o.ObjClosure.cast(eval_value.obj()) != null and + std.mem.eql( + u8, + o.ObjClosure.cast(eval_value.obj()).?.function.type_def.resolved_type.?.Function.name.string, + "eval", + ), + ); + + self.vm.push(eval_value); + + // Push locals + for (frame_base_idx..local_count) |i| { + self.vm.push(parent_fiber.stack[i + 1]); + } + + // Call + const arg_count: u8 = @intCast(local_count - frame_base_idx); + try self.vm.callValue( + self.vm.peek(arg_count), + arg_count, + null, + ); + + // Run again + self.vm.run(); + + // We always return something, even void + return self.vm.pop(); + } + } + + return Parser.CompileError.Recoverable; +} diff --git a/src/behavior.zig b/src/behavior.zig index 1c005f80..19bccf64 100644 --- a/src/behavior.zig +++ b/src/behavior.zig @@ -44,15 +44,13 @@ fn testBehaviors(allocator: std.mem.Allocator) !Result { continue; } + var had_error: bool = false; var runner: Runner = undefined; + try runner.init(allocator, .Test, null); - var had_error: bool = false; runner.runFile( - allocator, file_name, &[_][:0]u8{}, - .Test, - null, ) catch { io.print("\u{001b}[31m[{s} ✕]\u{001b}[0m\n", .{file.name}); had_error = true; diff --git a/src/main.zig b/src/main.zig index 6011274e..23d35806 100644 --- a/src/main.zig +++ b/src/main.zig @@ -37,10 +37,12 @@ pub fn main() u8 { return 1; } - var gpa = std.heap.DebugAllocator(.{ .safety = builtin.mode == .Debug }){}; - const allocator = if (builtin.mode == .Debug or is_wasm) - gpa.allocator() - else if (BuildOptions.mimalloc) + // DebugAllocator recently got super slow, will put this back on once its fixed + // var gpa = std.heap.DebugAllocator(.{ .safety = builtin.mode == .Debug }){}; + // const allocator = if (builtin.mode == .Debug or is_wasm) + // gpa.allocator() + // else + const allocator = if (BuildOptions.mimalloc) @import("mimalloc.zig").mim_allocator else std.heap.c_allocator; @@ -130,13 +132,13 @@ pub fn main() u8 { }; } else if (!is_wasm and res.positionals[0].len > 0) { var runner: Runner = undefined; + runner.init(allocator, flavor, null) catch { + return 1; + }; runner.runFile( - allocator, res.positionals[0][0], res.positionals[0][1..], - flavor, - null, ) catch { return 1; }; diff --git a/src/repl.zig b/src/repl.zig index 29ec7873..7155f25f 100644 --- a/src/repl.zig +++ b/src/repl.zig @@ -15,6 +15,7 @@ const Scanner = @import("Scanner.zig"); const io = @import("io.zig"); const GC = @import("GC.zig"); const TypeRegistry = @import("TypeRegistry.zig"); +const Runner = @import("Runner.zig"); pub const PROMPT = ">>> "; pub const MULTILINE_PROMPT = "... "; @@ -73,46 +74,9 @@ pub fn repl(allocator: std.mem.Allocator) !void { else false; - var import_registry = v.ImportRegistry{}; - var gc = try GC.init(allocator); - gc.type_registry = try TypeRegistry.init(&gc); - var imports = std.StringHashMapUnmanaged(Parser.ScriptImport){}; - var vm = try v.VM.init(&gc, &import_registry, .Repl, null); - vm.jit = if (BuildOptions.jit and BuildOptions.cycle_limit == null) - JIT.init(&vm) - else - null; - defer { - if (vm.jit != null) { - vm.jit.?.deinit(vm.gc.allocator); - vm.jit = null; - } - } - var parser = Parser.init( - &gc, - &imports, - false, - .Repl, - ); - var codegen = CodeGen.init( - &gc, - &parser, - .Repl, - if (vm.jit) |*jit| jit else null, - false, - ); - defer { - codegen.deinit(); - vm.deinit(); - parser.deinit(); - // gc.deinit(); - var it = imports.iterator(); - while (it.next()) |kv| { - kv.value_ptr.*.globals.deinit(vm.gc.allocator); - } - imports.deinit(allocator); - // TODO: free type_registry and its keys which are on the heap - } + var runner: Runner = undefined; + try runner.init(allocator, .Repl, null); + defer runner.deinit(); var stdout = io.stdoutWriter; var stderr = io.stderrWriter; @@ -126,25 +90,19 @@ pub fn repl(allocator: std.mem.Allocator) !void { .{envMap.get("HOME") orelse "."}, ); - if (builtin.os.tag != .windows) { + // Setup linenoise + if (ln != void) { _ = ln.linenoiseHistorySetMaxLen(100); _ = ln.linenoiseHistoryLoad(@ptrCast(buzz_history_path.written().ptr)); } // Import std and debug as commodity - _ = runSource( - "import \"std\"; import \"debug\";", - "REPL", - &vm, - &codegen, - &parser, - &gc, - ) catch unreachable; + _ = runner.runSource("import \"std\"; import \"debug\";", "REPL") catch unreachable; - var previous_global_top = vm.globals_count; - var previous_parser_globals = try parser.globals.clone(allocator); - var previous_globals = try vm.globals.clone(allocator); - var previous_type_registry = try gc.type_registry.registry.clone(allocator); + var previous_global_top = runner.vm.globals_count; + var previous_parser_globals = try runner.parser.globals.clone(allocator); + var previous_globals = try runner.vm.globals.clone(allocator); + var previous_type_registry = try runner.gc.type_registry.registry.clone(allocator); var previous_input: ?[]u8 = null; var reader_buffer = [_]u8{0}; @@ -165,7 +123,7 @@ pub fn repl(allocator: std.mem.Allocator) !void { ) catch @panic("Could not write to stdout"); } - const read_source = if (builtin.os.tag != .windows) + const read_source = if (ln != void) ln.linenoise( if (previous_input != null) MULTILINE_PROMPT @@ -185,7 +143,7 @@ pub fn repl(allocator: std.mem.Allocator) !void { if (source.len > 0) { // Highlight input var source_scanner = Scanner.init( - gc.allocator, + runner.gc.allocator, "REPL", original_source, ); @@ -208,7 +166,7 @@ pub fn repl(allocator: std.mem.Allocator) !void { if (previous_input) |previous| { source = std.mem.concatWithSentinel( - gc.allocator, + runner.gc.allocator, u8, &.{ previous, @@ -216,18 +174,11 @@ pub fn repl(allocator: std.mem.Allocator) !void { }, 0, ) catch @panic("Out of memory"); - gc.allocator.free(previous); + runner.gc.allocator.free(previous); previous_input = null; } - const expr = runSource( - source, - "REPL", - &vm, - &codegen, - &parser, - &gc, - ) catch |err| failed: { + const expr = runner.runSource(source, "REPL") catch |err| failed: { if (BuildOptions.debug) { stderr.print("Failed with error {}\n", .{err}) catch unreachable; } @@ -235,30 +186,30 @@ pub fn repl(allocator: std.mem.Allocator) !void { break :failed null; }; - if (parser.reporter.last_error == null and codegen.reporter.last_error == null) { - if (builtin.os.tag != .windows) { + if (runner.parser.reporter.last_error == null and runner.codegen.reporter.last_error == null) { + if (ln != void) { _ = ln.linenoiseHistoryAdd(source); _ = ln.linenoiseHistorySave(@ptrCast(buzz_history_path.written().ptr)); } // FIXME: why can't I deinit those? // previous_parser_globals.deinit(); - previous_parser_globals = try parser.globals.clone(allocator); + previous_parser_globals = try runner.parser.globals.clone(allocator); // previous_globals.deinit(); - previous_globals = try vm.globals.clone(allocator); + previous_globals = try runner.vm.globals.clone(allocator); // previous_type_registry.deinit(); - previous_type_registry = try gc.type_registry.registry.clone(allocator); + previous_type_registry = try runner.gc.type_registry.registry.clone(allocator); // Dump top of stack - if (previous_global_top != vm.globals_count or expr != null) { - previous_global_top = vm.globals_count; + if (previous_global_top != runner.vm.globals_count or expr != null) { + previous_global_top = runner.vm.globals_count; - const value = expr orelse vm.globals.items[previous_global_top]; + const value = expr orelse runner.vm.globals.items[previous_global_top]; - var value_str = std.Io.Writer.Allocating.init(vm.gc.allocator); + var value_str = std.Io.Writer.Allocating.init(runner.vm.gc.allocator); defer value_str.deinit(); var state = disassembler.DumpState{ - .vm = &vm, + .vm = &runner.vm, }; state.valueDump( @@ -268,7 +219,7 @@ pub fn repl(allocator: std.mem.Allocator) !void { ); var scanner = Scanner.init( - gc.allocator, + runner.gc.allocator, "REPL", value_str.written(), ); @@ -280,96 +231,29 @@ pub fn repl(allocator: std.mem.Allocator) !void { // We might have declared new globals, types, etc. and encounter an error // FIXME: why can't I deinit those? // parser.globals.deinit(); - parser.globals = previous_parser_globals; + runner.parser.globals = previous_parser_globals; // vm.globals.deinit(); - vm.globals = previous_globals; - vm.globals_count = previous_global_top; + runner.vm.globals = previous_globals; + runner.vm.globals_count = previous_global_top; // gc.type_registry.registry.deinit(); - gc.type_registry.registry = previous_type_registry; + runner.gc.type_registry.registry = previous_type_registry; // If syntax error was unclosed block, keep previous input - if (parser.reporter.last_error == .unclosed) { - previous_input = gc.allocator.alloc(u8, source.len) catch @panic("Out of memory"); + if (runner.parser.reporter.last_error == .unclosed) { + previous_input = runner.gc.allocator.alloc(u8, source.len) catch @panic("Out of memory"); std.mem.copyForwards(u8, previous_input.?, source); - } else if (builtin.os.tag != .windows) { + } else if (ln != void) { _ = ln.linenoiseHistoryAdd(source); _ = ln.linenoiseHistorySave(@ptrCast(buzz_history_path.written().ptr)); } } - parser.reporter.last_error = null; - parser.reporter.panic_mode = false; - codegen.reporter.last_error = null; - codegen.reporter.panic_mode = false; + runner.parser.reporter.last_error = null; + runner.parser.reporter.panic_mode = false; + runner.codegen.reporter.last_error = null; + runner.codegen.reporter.panic_mode = false; } } } - -fn runSource( - source: []const u8, - file_name: []const u8, - vm: *v.VM, - codegen: *CodeGen, - parser: *Parser, - gc: *GC, -) !?Value { - var total_timer = std.time.Timer.start() catch unreachable; - var timer = try std.time.Timer.start(); - var parsing_time: u64 = undefined; - var codegen_time: u64 = undefined; - var running_time: u64 = undefined; - - if (try parser.parse(source, null, file_name)) |ast| { - parsing_time = timer.read(); - timer.reset(); - - const ast_slice = ast.slice(); - if (try codegen.generate(ast_slice)) |function| { - codegen_time = timer.read(); - timer.reset(); - - try vm.interpret( - ast_slice, - function, - null, - ); - - // Does the user code ends with a lone expression? - const fnode = ast.nodes.items(.components)[ast.root.?].Function; - const statements = ast.nodes.items(.components)[fnode.body.?].Block; - const last_statement = if (statements.len > 0) statements[statements.len - 1] else null; - if (last_statement != null and ast.nodes.items(.tag)[last_statement.?] == .Expression) { - return vm.pop(); - } - - running_time = timer.read(); - } else { - return Parser.CompileError.Recoverable; - } - - if (BuildOptions.show_perf) { - io.print( - "\u{001b}[2mParsing: {D}\nCodegen: {D}\nRun: {D}\nJIT: {D}\nGC: {D}\nTotal: {D}\nFull GC: {} | GC: {} | Max allocated: {B} bytes\n\u{001b}[0m", - .{ - parsing_time, - codegen_time, - running_time, - if (vm.jit) |jit| jit.jit_time else 0, - gc.gc_time, - total_timer.read(), - gc.full_collection_count, - gc.light_collection_count, - gc.max_allocated, - }, - ); - } - } else if (parser.reporter.last_error == .unclosed) { - return null; - } else { - return Parser.CompileError.Recoverable; - } - - return null; -} diff --git a/src/vm.zig b/src/vm.zig index 3de7bf45..f69e701e 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -129,6 +129,10 @@ pub const Fiber = struct { type_def: *obj.ObjTypeDef, + /// If true, this fiber was created in order to evaluate an expression while debugging + /// This prevents the eval result to be lost + is_eval: bool = false, + pub fn init( allocator: std.mem.Allocator, type_def: *obj.ObjTypeDef, @@ -162,7 +166,6 @@ pub const Fiber = struct { pub fn deinit(self: *Self) void { self.allocator.free(self.stack); - self.frames.deinit(self.allocator); } @@ -554,14 +557,14 @@ pub const VM = struct { } } - pub inline fn cloneValue(self: *Self, value: Value) !Value { + pub fn cloneValue(self: *Self, value: Value) !Value { return if (value.isObj()) try obj.cloneObject(value.obj(), self) else value; } - pub inline fn currentFrame(self: *Self) ?*CallFrame { + pub fn currentFrame(self: *Self) ?*CallFrame { std.debug.assert(self.current_fiber.frame_count <= self.current_fiber.frames.items.len); if (self.current_fiber.frame_count == 0) { @@ -571,7 +574,7 @@ pub const VM = struct { return &self.current_fiber.frames.items[self.current_fiber.frame_count - 1]; } - pub inline fn currentGlobals(self: *Self) *std.ArrayList(Value) { + pub fn currentGlobals(self: *Self) *std.ArrayList(Value) { return self.currentFrame().?.closure.globals; } @@ -600,13 +603,15 @@ pub const VM = struct { null, ); - self.push((try self.gc.allocateObject( - try obj.ObjClosure.init( - self.gc.allocator, - self, - function, - ), - )).toValue()); + self.push( + (try self.gc.allocateObject( + try obj.ObjClosure.init( + self.gc.allocator, + self, + function, + ), + )).toValue(), + ); self.push((try self.cliArgs(args)).toValue()); @@ -636,7 +641,7 @@ pub const VM = struct { return null; } - inline fn readInstruction(self: *Self) u32 { + fn readInstruction(self: *Self) u32 { const current_frame = self.currentFrame().?; const instruction = current_frame.closure.function.chunk.code.items[current_frame.ip]; @@ -645,27 +650,27 @@ pub const VM = struct { return instruction; } - pub inline fn getCode(instruction: u32) Chunk.OpCode { + pub fn getCode(instruction: u32) Chunk.OpCode { return @enumFromInt(@as(u8, @intCast(instruction >> 24))); } - inline fn replaceCode(instruction: u32, new_code: Chunk.OpCode) u32 { + fn replaceCode(instruction: u32, new_code: Chunk.OpCode) u32 { return (@as(u32, @intCast(@intFromEnum(new_code))) << 24) | @as(u32, @intCast(getArg(instruction))); } - inline fn getArg(instruction: u32) u24 { + fn getArg(instruction: u32) u24 { return @as(u24, @intCast(0x00ffffff & instruction)); } - inline fn readByte(self: *Self) u8 { + fn readByte(self: *Self) u8 { return @as(u8, @intCast(self.readInstruction())); } - pub inline fn readConstant(self: *Self, arg: u24) Value { + pub fn readConstant(self: *Self, arg: u24) Value { return self.currentFrame().?.closure.function.chunk.constants.items[arg]; } - inline fn readString(self: *Self, arg: u24) *obj.ObjString { + fn readString(self: *Self, arg: u24) *obj.ObjString { return self.readConstant(arg).obj().access( obj.ObjString, .String, @@ -2111,7 +2116,7 @@ pub const VM = struct { } // result_count > 0 when the return is `export` - inline fn returnFrame(self: *Self) bool { + fn returnFrame(self: *Self) bool { const result = self.pop(); const frame = self.currentFrame().?.*; @@ -2135,6 +2140,11 @@ pub const VM = struct { if (self.flavor != .Repl) { _ = self.pop(); } + + if (self.current_fiber.is_eval) { + self.push(result); + } + return true; } @@ -4774,7 +4784,7 @@ pub const VM = struct { } } - inline fn reportRuntimeErrorWithCurrentStack(self: *Self, message: []const u8) void { + fn reportRuntimeErrorWithCurrentStack(self: *Self, message: []const u8) void { self.reportRuntimeError( message, if (self.currentFrame()) |frame| diff --git a/vendors/linenoise b/vendors/linenoise index 93b2db9b..880b9413 160000 --- a/vendors/linenoise +++ b/vendors/linenoise @@ -1 +1 @@ -Subproject commit 93b2db9bd4968f76148dd62cdadf050ed50b84b3 +Subproject commit 880b94130ffa5f8236392392b447ff2234b11983 From dc935076f7e84d845d521056a1e96a425a7b9f0e Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Tue, 28 Oct 2025 11:45:26 +0100 Subject: [PATCH 11/12] fix(build): Fixed build.zig for wasm --- build.zig | 216 ++++++++++++++++++++++++++--------------------- src/Runner.zig | 2 +- src/behavior.zig | 4 +- src/vm.zig | 15 ++-- 4 files changed, 134 insertions(+), 103 deletions(-) diff --git a/build.zig b/build.zig index 5186b396..456b4cdd 100644 --- a/build.zig +++ b/build.zig @@ -248,71 +248,86 @@ pub fn build(b: *Build) !void { b.step("run", "run buzz").dependOn(&run_exe.step); // buzz_behavior - const behavior_exe = b.addExecutable( - .{ - .name = "buzz_behavior", - .use_llvm = true, - .root_module = b.createModule( - .{ - .root_source_file = b.path("src/behavior.zig"), - .target = target, - .optimize = build_mode, - .sanitize_c = .off, - }, - ), - }, - ); - b.installArtifact(behavior_exe); - const run_behavior = b.addRunArtifact(behavior_exe); - run_behavior.step.dependOn(install_step); - b.step("test-behavior", "Test behavior").dependOn(&run_behavior.step); + const behavior_exe = if (!is_wasm) + b.addExecutable( + .{ + .name = "buzz_behavior", + .use_llvm = true, + .root_module = b.createModule( + .{ + .root_source_file = b.path("src/behavior.zig"), + .target = target, + .optimize = build_mode, + .sanitize_c = .off, + }, + ), + }, + ) + else + null; + if (!is_wasm) { + b.installArtifact(behavior_exe.?); + const run_behavior = b.addRunArtifact(behavior_exe.?); + run_behavior.step.dependOn(install_step); + b.step("test-behavior", "Test behavior").dependOn(&run_behavior.step); + } // buzz_debugger - const debugger_exe = b.addExecutable( - .{ - .name = "buzz_debugger", - .use_llvm = true, - .root_module = b.createModule( - .{ - .root_source_file = b.path("src/Debugger.zig"), - .target = target, - .optimize = build_mode, - .sanitize_c = .off, - }, - ), - }, - ); - b.installArtifact(debugger_exe); - const run_debugger = b.addRunArtifact(debugger_exe); - run_debugger.step.dependOn(install_step); - b.step("run-debugger", "Run the debugger").dependOn(&run_debugger.step); + const debugger_exe = if (!is_wasm) + b.addExecutable( + .{ + .name = "buzz_debugger", + .use_llvm = true, + .root_module = b.createModule( + .{ + .root_source_file = b.path("src/Debugger.zig"), + .target = target, + .optimize = build_mode, + .sanitize_c = .off, + }, + ), + }, + ) + else + null; + if (!is_wasm) { + b.installArtifact(debugger_exe.?); + const run_debugger = b.addRunArtifact(debugger_exe.?); + run_debugger.step.dependOn(install_step); + b.step("run-debugger", "Run the debugger").dependOn(&run_debugger.step); + } // buzz_lsp - const lsp_exe = b.addExecutable( - .{ - .name = "buzz_lsp", - .use_llvm = true, - .root_module = b.createModule( - .{ - .root_source_file = b.path("src/lsp.zig"), - .target = target, - .optimize = build_mode, - .sanitize_c = .off, - }, - ), - }, - ); - lsp_exe.root_module.addImport( - "lsp", - lsp.module("lsp"), - ); - b.installArtifact(lsp_exe); - const run_lsp_exe = b.addRunArtifact(lsp_exe); - run_lsp_exe.step.dependOn(install_step); - if (b.args) |args| { - run_lsp_exe.addArgs(args); + const lsp_exe = if (!is_wasm) + b.addExecutable( + .{ + .name = "buzz_lsp", + .use_llvm = true, + .root_module = b.createModule( + .{ + .root_source_file = b.path("src/lsp.zig"), + .target = target, + .optimize = build_mode, + .sanitize_c = .off, + }, + ), + }, + ) + else + null; + if (!is_wasm) { + lsp_exe.?.root_module.addImport( + "lsp", + lsp.module("lsp"), + ); + b.installArtifact(lsp_exe.?); + const run_lsp_exe = b.addRunArtifact(lsp_exe.?); + run_lsp_exe.step.dependOn(install_step); + if (b.args) |args| { + run_lsp_exe.addArgs(args); + } + b.step("lsp", "run buzz lsp").dependOn(&run_lsp_exe.step); } - b.step("lsp", "run buzz lsp").dependOn(&run_lsp_exe.step); // check (exe not installed) const check_exe = b.addExecutable( @@ -333,7 +348,7 @@ pub fn build(b: *Build) !void { check.dependOn(&check_exe.step); // Building buzz api library - const lib = b.addLibrary( + const lib = if (!is_wasm) b.addLibrary( .{ .name = "buzz", .linkage = .dynamic, @@ -346,8 +361,10 @@ pub fn build(b: *Build) !void { }, ), }, - ); - b.installArtifact(lib); + ) else null; + if (!is_wasm) { + b.installArtifact(lib.?); + } const static_lib = b.addLibrary( .{ @@ -387,67 +404,76 @@ pub fn build(b: *Build) !void { run_tests.step.dependOn(install_step); // wait for libraries to be installed test_step.dependOn(&run_tests.step); - for ([_]*std.Build.Step.Compile{ static_lib, lib, exe, behavior_exe, debugger_exe, lsp_exe, check_exe, tests }) |c| { - // BuildOptions - c.root_module.addImport("build_options", build_option_module); + for ([_]?*std.Build.Step.Compile{ static_lib, lib, exe, behavior_exe, debugger_exe, lsp_exe, check_exe, tests }) |comp| { + if (comp) |c| { + // BuildOptions + c.root_module.addImport("build_options", build_option_module); - // Link non-zig deps to executables and library - for (ext_deps) |dep| { - c.linkLibrary(dep); + // Link non-zig deps to executables and library + for (ext_deps) |dep| { + c.linkLibrary(dep); - if (target.result.os.tag == .windows) { - c.linkSystemLibrary("bcrypt"); - } + if (target.result.os.tag == .windows) { + c.linkSystemLibrary("bcrypt"); + } - if (build_options.needLibC()) { - c.linkLibC(); + if (build_options.needLibC()) { + c.linkLibC(); + } } } } // So that JIT compiled function can reference buzz_api - for ([_]*std.Build.Step.Compile{ exe, behavior_exe, debugger_exe, lsp_exe, check_exe }) |c| { - c.linkLibrary(static_lib); + for ([_]?*std.Build.Step.Compile{ exe, behavior_exe, debugger_exe, lsp_exe, check_exe }) |comp| { + if (comp) |c| { + c.linkLibrary(static_lib); + } } - for ([_]*std.Build.Step.Compile{ tests, static_lib, lib, exe, debugger_exe, lsp_exe, behavior_exe, check_exe }) |c| { - c.root_module.addImport( - "dap", - dap.module("dap_kit"), - ); + for ([_]?*std.Build.Step.Compile{ tests, static_lib, lib, exe, debugger_exe, lsp_exe, behavior_exe, check_exe }) |comp| { + if (comp) |c| { + c.root_module.addImport( + "dap", + dap.module("dap_kit"), + ); + } } - for ([_]*std.Build.Step.Compile{ exe, debugger_exe, lsp_exe, check_exe }) |c| { - c.root_module.addImport( - "clap", - clap.module("clap"), - ); + for ([_]?*std.Build.Step.Compile{ exe, debugger_exe, lsp_exe, check_exe }) |comp| { + if (comp) |c| { + c.root_module.addImport( + "clap", + clap.module("clap"), + ); + } } - for ([_]*std.Build.Step.Compile{ exe, debugger_exe, lsp_exe }) |c| { - b.default_step.dependOn(&c.step); + for ([_]?*std.Build.Step.Compile{ exe, debugger_exe, lsp_exe }) |comp| { + if (comp) |c| { + b.default_step.dependOn(&c.step); + } } // Building std libraries const Lib = struct { path: ?[]const u8 = null, name: []const u8, - wasm_compatible: bool = true, }; const libraries = [_]Lib{ .{ .name = "std", .path = "src/lib/buzz_std.zig" }, - .{ .name = "io", .path = "src/lib/buzz_io.zig", .wasm_compatible = false }, + .{ .name = "io", .path = "src/lib/buzz_io.zig" }, .{ .name = "gc", .path = "src/lib/buzz_gc.zig" }, - .{ .name = "os", .path = "src/lib/buzz_os.zig", .wasm_compatible = false }, - .{ .name = "fs", .path = "src/lib/buzz_fs.zig", .wasm_compatible = false }, + .{ .name = "os", .path = "src/lib/buzz_os.zig" }, + .{ .name = "fs", .path = "src/lib/buzz_fs.zig" }, .{ .name = "math", .path = "src/lib/buzz_math.zig" }, .{ .name = "debug", .path = "src/lib/buzz_debug.zig" }, .{ .name = "buffer", .path = "src/lib/buzz_buffer.zig" }, .{ .name = "crypto", .path = "src/lib/buzz_crypto.zig" }, // FIXME: API has changed - // .{ .name = "http", .path = "src/lib/buzz_http.zig", .wasm_compatible = false }, - .{ .name = "ffi", .path = "src/lib/buzz_ffi.zig", .wasm_compatible = false }, + // .{ .name = "http", .path = "src/lib/buzz_http.zig" }, + .{ .name = "ffi", .path = "src/lib/buzz_ffi.zig" }, .{ .name = "serialize", .path = "src/lib/buzz_serialize.zig" }, .{ .name = "testing" }, .{ .name = "errors" }, @@ -469,7 +495,7 @@ pub fn build(b: *Build) !void { ); install_step.dependOn(&step.step); - if (library.path == null or (!library.wasm_compatible and is_wasm)) { + if (library.path == null or is_wasm) { continue; } diff --git a/src/Runner.zig b/src/Runner.zig index 280904c4..f6ffe6f5 100644 --- a/src/Runner.zig +++ b/src/Runner.zig @@ -9,7 +9,7 @@ const RunFlavor = _vm.RunFlavor; const ImportRegistry = _vm.ImportRegistry; const Parser = @import("Parser.zig"); const CodeGen = @import("Codegen.zig"); -const Debugger = @import("Debugger.zig"); +const Debugger = if (!is_wasm) @import("Debugger.zig") else void; const GC = @import("GC.zig"); const JIT = if (!is_wasm) @import("Jit.zig") else void; const TypeRegistry = @import("TypeRegistry.zig"); diff --git a/src/behavior.zig b/src/behavior.zig index 19bccf64..ce0b9f95 100644 --- a/src/behavior.zig +++ b/src/behavior.zig @@ -9,7 +9,7 @@ const Parser = @import("Parser.zig"); const black_listed_tests = std.StaticStringMap(void).initComptime( .{ - // .{ "tests/022-io.buzz", {} }, + .{ "tests/027-run-file.buzz", {} }, }, ); @@ -44,6 +44,8 @@ fn testBehaviors(allocator: std.mem.Allocator) !Result { continue; } + io.print("\u{001b}[33m[{s} ...]\u{001b}[0m\n", .{file_name}); + var had_error: bool = false; var runner: Runner = undefined; try runner.init(allocator, .Test, null); diff --git a/src/vm.zig b/src/vm.zig index f69e701e..7b07054c 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -15,7 +15,7 @@ const Reporter = @import("Reporter.zig"); const FFI = if (!is_wasm) @import("FFI.zig") else void; const dispatch_call_modifier: std.builtin.CallModifier = if (!is_wasm) .always_tail else .auto; const io = @import("io.zig"); -const Debugger = @import("Debugger.zig"); +const Debugger = if (!is_wasm) @import("Debugger.zig") else void; const dumpStack = disassembler.dumpStack; const jmp = if (!is_wasm) @import("jmp.zig") else void; @@ -884,16 +884,19 @@ pub const VM = struct { } } - if (self.debugger) |debugger| { - if (debugger.onDispatch() catch false) { - return; - } + if (!is_wasm and + self.debugger != null and + self.debugger.?.onDispatch() catch false) + { + return; } // Tail call @call( dispatch_call_modifier, - if (self.debugger != null and self.debugger.?.session.?.run_state == .paused) + if (!is_wasm and + self.debugger != null and + self.debugger.?.session.?.run_state == .paused) OP_NOOP else op_table[@intFromEnum(instruction)], From eca1a80e84609209f6b7c94ead4302409e333b51 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Tue, 28 Oct 2025 21:24:03 +0100 Subject: [PATCH 12/12] fix(build): Use matrix instead of repeating everything for each optimize mode --- .github/workflows/ci.yaml | 43 +++++++++++++-------------------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3bd0bbfc..a583f092 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -14,6 +14,14 @@ jobs: zig-version: - "master" operating-system: [ ubuntu-latest, macos-latest ] + optimize: + - "Debug" + - "ReleaseSafe" + - "ReleaseFast" + - "ReleaseSmall" + options: + - "" + - "-Djit_always_on" steps: - name: Install homebrew @@ -29,36 +37,13 @@ jobs: - name: Build test ffi lib run: zig build-lib -dynamic tests/utils/foreign.zig && mv libforeign.* tests/utils/ - - name: Run behavior tests Debug - run: zig build test && zig build test-behavior + - name: Run tests ${{ matrix.optimize }} + run: zig build -Doptimize=${{ matrix.optimize }} ${{ matrix.options }} test + - name: Run behavior tests ${{ matrix.optimize }} + run: zig build -Doptimize=${{ matrix.optimize }} ${{ matrix.options }} test-behavior - name: Cleanup run: rm -rf zig-out zig-cache - - - name: Run behavior tests Debug with JIT always on - run: zig build -Djit_always_on test && zig build -Djit_always_on test-behavior - - name: Cleanup - run: rm -rf zig-out zig-cache - - - name: Run behavior tests ReleaseSafe - run: zig build -Doptimize=ReleaseSafe test && zig build -Doptimize=ReleaseSafe test-behavior - - name: Cleanup - run: rm -rf zig-out zig-cache - - - name: Run behavior tests ReleaseSafe with JIT always on - run: zig build -Doptimize=ReleaseSafe -Djit_always_on test && zig build -Doptimize=ReleaseSafe -Djit_always_on test-behavior - - name: Cleanup - run: rm -rf zig-out zig-cache - - - name: Run behavior tests ReleaseFast - run: zig build -Doptimize=ReleaseFast test && zig build -Doptimize=ReleaseFast test-behavior - - name: Cleanup - run: rm -rf zig-out zig-cache - - - name: Run behavior tests ReleaseFast with JIT always on - run: zig build -Doptimize=ReleaseFast -Djit_always_on test && zig build -Doptimize=ReleaseFast -Djit_always_on test-behavior - - name: Cleanup - run: rm -rf zig-out zig-cache - + wasm-build: runs-on: ubuntu-latest steps: @@ -71,7 +56,7 @@ jobs: with: version: master - name: Build for wasm - run: zig build -Dtarget=wasm32-freestanding -freference-trace -Doptimize=ReleaseSmall + run: zig build -Dtarget=wasm32-freestanding -Doptimize=ReleaseSmall - name: Cleanup run: rm -rf zig-out zig-cache