refactor: migrate error reporting to new compiler context

This commit is contained in:
Fernando Araoz 2025-01-30 21:43:26 -05:00
parent e330b2905e
commit c54d19bcf7
2 changed files with 164 additions and 3 deletions

View File

@ -93,6 +93,121 @@ pub const ErrorData = struct {
self.help = help;
}
/// Generates an error string. `alloc` is used to create the string,
/// the caller should call `free` on the returning slice.
pub fn get_error_str(self: *ErrorData, source_code: []const u8, filename: []const u8, alloc: std.mem.Allocator) ![]u8 {
const faulty_line = get_line(source_code, self.start_position);
var error_message = try std.fmt.allocPrint(alloc,
\\Error: {s}
\\[{s}:{d}:{d}]
, .{
self.reason,
filename,
faulty_line.line_number,
faulty_line.column_number,
});
errdefer alloc.free(error_message);
// generate errors for each label, and concat
for (self.labels.items) |label| {
const label_line = get_line(source_code, label.start);
// Build the error position indicator
const column_number_len_str = try std.fmt.allocPrint(
alloc,
"{d}",
.{label_line.line_number},
);
const column_number_len = column_number_len_str.len;
alloc.free(column_number_len_str);
// position up to where the error starts
const error_start_len = column_number_len + 4 + label_line.column_number - 1;
const error_len = label.end - label.start;
// chars for the error
const empty_space_before_indicator = try alloc.alloc(u8, error_start_len);
defer alloc.free(empty_space_before_indicator);
@memset(empty_space_before_indicator, ' ');
// top error indicator: unicode box drawing characters in the range U+250x-U+257x (3 bytes)
const error_indicator = try alloc.alloc(u8, error_len * 3);
defer alloc.free(error_indicator);
// the first char is always '╭', the rest are lines
error_indicator[0] = '\xe2';
error_indicator[1] = '\x95';
error_indicator[2] = '\xad';
// set bytes of the rest
var i: usize = 1;
while (i < error_len) : (i += 1) {
// set bytes
error_indicator[i * 3 + 0] = '\xe2';
error_indicator[i * 3 + 1] = '\x94';
error_indicator[i * 3 + 2] = '\x80';
}
// bottom error indicator: always
const bottom_error_indicator = "╰─";
const help_message: []u8 = msg: {
if (self.help) |help_text| {
// this will be manually freed later
break :msg try std.fmt.allocPrint(alloc, "\n Help: {s}", .{help_text});
} else {
break :msg "";
}
};
defer if (help_message.len > 0) {
alloc.free(help_message);
};
const label_error = try std.fmt.allocPrint(alloc,
\\
\\
\\ {d} | {s}
\\{s}{s}
\\{s}{s} {s}
\\{s}
, .{
label_line.line_number,
label_line.line,
empty_space_before_indicator,
error_indicator,
empty_space_before_indicator,
bottom_error_indicator,
switch (label.message) {
.static => label.message.static,
.dynamic => label.message.dynamic,
},
help_message,
});
errdefer alloc.free(label_error);
// append the previous bytes to the current ones,
// in a temp variable
const new_bytes = try std.mem.concat(alloc, u8, &[_][]const u8{ error_message, label_error });
// free the previous bytes
alloc.free(label_error);
alloc.free(error_message);
// reference the new bytes
error_message = new_bytes;
// continue
}
return error_message;
}
// TODO:
// - transform absolute position into line:column
// - Get previous, current and next line
// - Display message
pub fn deinit(self: *ErrorData, allocator: std.mem.Allocator) void {
// Clean any labels. Those are assumed to have been initialized
// by the same allocator this function receives
@ -118,3 +233,50 @@ pub const ErrorLabel = struct {
}
}
};
const LineInfo = struct {
line: []const u8,
/// 1 based
line_number: usize,
/// 1 based
column_number: usize,
};
fn get_line(input: []const u8, at: usize) LineInfo {
var line_number: usize = 1;
var line_start: usize = 0;
var line_end: usize = 0;
var current_pos: usize = 0;
const cap = input.len;
// search the start pos of the line
while (current_pos < cap and current_pos < at) : (current_pos += 1) {
if (input[current_pos] == '\n' and current_pos + 1 < cap) {
line_start = current_pos + 1;
line_number += 1;
}
}
// compute the column number
const column_number: usize = current_pos - line_start + 1;
// search the end pos of the line
while (current_pos < cap) : (current_pos += 1) {
// EOF is EOL
if (current_pos + 1 == cap) {
line_end = current_pos + 1;
break;
}
if (input[current_pos] == '\n') {
// dont count the newline as part of the... line
line_end = current_pos;
break;
}
}
return .{
.line = input[line_start..line_end],
.line_number = line_number,
.column_number = column_number,
};
}

View File

@ -106,9 +106,8 @@ fn repl() !void {
}
// Print errors and continue, if any
if (error_array.items.len > 0) {
for (error_array.items) |e| {
var err = e;
if (ctx.errors.items.len > 0) {
for (ctx.errors.items) |*err| {
const err_str = try err.get_error_str(line, "repl", alloc);
try stdout.print("\n{s}\n", .{err_str});
try bw.flush();