From 75002582ba5c001af3be0176634f0c9e8c496613 Mon Sep 17 00:00:00 2001 From: Fernando Araoz Date: Sat, 21 Dec 2024 06:11:16 -0500 Subject: [PATCH] feat: minimal error handling --- build.zig | 16 +++++++++++++++- src/01_lexic/root.zig | 1 - src/02_syntax/root.zig | 41 +++++++++++++++++++++++++++++++++++++---- src/errors/root.zig | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 src/errors/root.zig diff --git a/build.zig b/build.zig index 545b718..5055642 100644 --- a/build.zig +++ b/build.zig @@ -22,6 +22,16 @@ pub fn build(b: *std.Build) void { .optimize = optimize, }); + // + // Error handling module + // + const error_module = b.addModule("errors", .{ + .root_source_file = b.path("src/errors/root.zig"), + .target = target, + .optimize = optimize, + }); + exe.root_module.addImport("errors", error_module); + // // Lexic module // @@ -31,6 +41,7 @@ pub fn build(b: *std.Build) void { .optimize = optimize, }); exe.root_module.addImport("lexic", lexic_module); + lexic_module.addImport("errors", error_module); // // Syntax module @@ -40,8 +51,9 @@ pub fn build(b: *std.Build) void { .target = target, .optimize = optimize, }); - syntax_module.addImport("lexic", lexic_module); exe.root_module.addImport("syntax", syntax_module); + syntax_module.addImport("lexic", lexic_module); + syntax_module.addImport("errors", error_module); // This declares intent for the executable to be installed into the // standard location when the user invokes the "install" step (the default @@ -80,6 +92,7 @@ pub fn build(b: *std.Build) void { }); exe_unit_tests.root_module.addImport("lexic", lexic_module); exe_unit_tests.root_module.addImport("syntax", syntax_module); + exe_unit_tests.root_module.addImport("errors", error_module); const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); @@ -102,6 +115,7 @@ pub fn build(b: *std.Build) void { }); file_unit_test.root_module.addImport("lexic", lexic_module); file_unit_test.root_module.addImport("syntax", syntax_module); + file_unit_test.root_module.addImport("errors", error_module); var test_artifact = b.addRunArtifact(file_unit_test); test_step.dependOn(&test_artifact.step); diff --git a/src/01_lexic/root.zig b/src/01_lexic/root.zig index 22fd9eb..6f1165b 100644 --- a/src/01_lexic/root.zig +++ b/src/01_lexic/root.zig @@ -100,7 +100,6 @@ pub fn tokenize(input: []const u8, alloc: std.mem.Allocator) !std.ArrayList(Toke // TODO: check if this is a good error recovery strategy else { // no lexer matched - std.debug.print("unmatched args: anytype:c\n", .{}); break; } } diff --git a/src/02_syntax/root.zig b/src/02_syntax/root.zig index 2a2c3a3..4e7a0d8 100644 --- a/src/02_syntax/root.zig +++ b/src/02_syntax/root.zig @@ -1,5 +1,7 @@ const std = @import("std"); const lexic = @import("lexic"); +const errors = @import("errors"); + const expression = @import("./expression.zig"); const variable = @import("./variable.zig"); const types = @import("./types.zig"); @@ -14,7 +16,19 @@ pub const Module = struct { statements: std.ArrayList(*statement.Statement), alloc: std.mem.Allocator, - pub fn init(target: *@This(), tokens: *const TokenStream, pos: usize, allocator: std.mem.Allocator) ParseError!void { + /// Parses a module. + /// + /// If this function fails an error will be returned, and additionally the out parameter + /// `error_target` will be populated. If the error returned is OOM, nothing will be there. + /// In that case, the caller is responsible for calling the error `deinit` method, + /// which will clean it. + pub fn init( + target: *@This(), + tokens: *const TokenStream, + pos: usize, + allocator: std.mem.Allocator, + error_target: *errors.ErrorData, + ) ParseError!void { var arrl = std.ArrayList(*statement.Statement).init(allocator); errdefer arrl.deinit(); errdefer for (arrl.items) |i| { @@ -30,7 +44,20 @@ pub const Module = struct { var stmt = try allocator.create(statement.Statement); errdefer allocator.destroy(stmt); - const next_pos = try stmt.init(tokens, current_pos, allocator); + const next_pos = stmt.init(tokens, current_pos, allocator) catch |e| { + switch (e) { + error.Unmatched => { + // create the error value + try error_target.init( + "No statement found", + current_pos, + current_pos + 1, + ); + return error.Unmatched; + }, + else => return e, + } + }; current_pos = next_pos; try arrl.append(stmt); @@ -60,8 +87,11 @@ test "should parse a single statement" { const tokens = try lexic.tokenize(input, std.testing.allocator); defer tokens.deinit(); + const error_target = try std.testing.allocator.create(errors.ErrorData); + defer std.testing.allocator.destroy(error_target); + var module: Module = undefined; - _ = try module.init(&tokens, 0, std.testing.allocator); + _ = try module.init(&tokens, 0, std.testing.allocator, error_target); defer module.deinit(); } @@ -71,8 +101,11 @@ test "should clean memory if a statement parsing fails after one item has been i const tokens = try lexic.tokenize(input, std.testing.allocator); defer tokens.deinit(); + const error_target = try std.testing.allocator.create(errors.ErrorData); + defer std.testing.allocator.destroy(error_target); + var module: Module = undefined; - _ = module.init(&tokens, 0, std.testing.allocator) catch { + _ = module.init(&tokens, 0, std.testing.allocator, error_target) catch { return; }; defer module.deinit(); diff --git a/src/errors/root.zig b/src/errors/root.zig new file mode 100644 index 0000000..15e2a6b --- /dev/null +++ b/src/errors/root.zig @@ -0,0 +1,34 @@ +const std = @import("std"); + +pub const ErrorData = struct { + reason: []const u8, + start_position: usize, + end_position: usize, + + pub fn init( + target: *@This(), + reason: []const u8, + start_position: usize, + end_position: usize, + ) !void { + target.* = .{ + .reason = reason, + .start_position = start_position, + .end_position = end_position, + }; + } + + pub fn print(self: *@This()) void { + std.debug.print("Error: {s}\n", .{self.reason}); + } + + /// When called, this struct will clean its resources and then + /// clean itself. + pub fn deinit(self: *@This()) void { + _ = self; + } +}; + +test { + std.testing.refAllDecls(@This()); +}