feat: minimal error handling

This commit is contained in:
Fernando Araoz 2024-12-21 06:11:16 -05:00
parent fb30e1195e
commit 75002582ba
4 changed files with 86 additions and 6 deletions

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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();

34
src/errors/root.zig Normal file
View File

@ -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());
}