feat: begin work on error labels

This commit is contained in:
Fernando Araoz 2024-12-25 06:46:42 -05:00
parent 9a4942fd78
commit 079e8eb4d4
4 changed files with 43 additions and 34 deletions

View File

@ -19,6 +19,7 @@ pub fn lex(
cap: usize, cap: usize,
start: usize, start: usize,
err: *errors.ErrorData, err: *errors.ErrorData,
alloc: std.mem.Allocator,
) LexError!?LexReturn { ) LexError!?LexReturn {
assert(start < cap); assert(start < cap);
const first_char = input[start]; const first_char = input[start];
@ -27,9 +28,9 @@ pub fn lex(
if (first_char == '0' and cap > start + 1) { if (first_char == '0' and cap > start + 1) {
const second_char = input[start + 1]; const second_char = input[start + 1];
switch (second_char) { switch (second_char) {
'x', 'X' => return prefixed('x', input, cap, start, err), 'x', 'X' => return prefixed('x', input, cap, start, err, alloc),
'o', 'O' => return prefixed('o', input, cap, start, err), 'o', 'O' => return prefixed('o', input, cap, start, err, alloc),
'b', 'B' => return prefixed('b', input, cap, start, err), 'b', 'B' => return prefixed('b', input, cap, start, err, alloc),
else => { else => {
// Continue // Continue
}, },
@ -51,6 +52,7 @@ fn prefixed(
cap: usize, cap: usize,
start: usize, start: usize,
err: *errors.ErrorData, err: *errors.ErrorData,
alloc: std.mem.Allocator,
) !?LexReturn { ) !?LexReturn {
const validator = switch (prefix) { const validator = switch (prefix) {
'x' => utils.is_hex_digit, 'x' => utils.is_hex_digit,
@ -64,7 +66,7 @@ fn prefixed(
// There should be at least 1 valid digit // There should be at least 1 valid digit
if (end_position >= cap or !validator(input[end_position])) { if (end_position >= cap or !validator(input[end_position])) {
// populate error information // populate error information
err.init("Incomplete number", start, end_position); try err.init("Incomplete number", start, end_position, alloc);
// throw error // throw error
return LexError.Incomplete; return LexError.Incomplete;
@ -254,7 +256,7 @@ test "should return null if not an integer" {
test "should lex hex number" { test "should lex hex number" {
const input = "0xa"; const input = "0xa";
const result = try lex(input, input.len, 0, undefined); const result = try lex(input, input.len, 0, undefined, std.testing.allocator);
if (result) |tuple| { if (result) |tuple| {
const r = tuple[0]; const r = tuple[0];
@ -268,7 +270,7 @@ test "should fail on integer with leading zero" {
const input = "0322"; const input = "0322";
const errdata = try std.testing.allocator.create(errors.ErrorData); const errdata = try std.testing.allocator.create(errors.ErrorData);
defer std.testing.allocator.destroy(errdata); defer std.testing.allocator.destroy(errdata);
const result = lex(input, input.len, 0, errdata) catch |err| { const result = lex(input, input.len, 0, errdata, std.testing.allocator) catch |err| {
try std.testing.expect(err == token.LexError.LeadingZero); try std.testing.expect(err == token.LexError.LeadingZero);
return; return;
}; };
@ -285,7 +287,7 @@ test "should fail on integer with leading zero" {
test "should lex hex number 2" { test "should lex hex number 2" {
const input = " 0Xff00AA "; const input = " 0Xff00AA ";
const result = try lex(input, input.len, 2, undefined); const result = try lex(input, input.len, 2, undefined, std.testing.allocator);
if (result) |tuple| { if (result) |tuple| {
const r = tuple[0]; const r = tuple[0];
@ -299,7 +301,7 @@ test "shouldnt parse incomplete hex number" {
const input = "0xZZ"; const input = "0xZZ";
const errdata = try std.testing.allocator.create(errors.ErrorData); const errdata = try std.testing.allocator.create(errors.ErrorData);
defer std.testing.allocator.destroy(errdata); defer std.testing.allocator.destroy(errdata);
const result = lex(input, input.len, 0, errdata) catch |err| { const result = lex(input, input.len, 0, errdata, std.testing.allocator) catch |err| {
try std.testing.expect(err == token.LexError.Incomplete); try std.testing.expect(err == token.LexError.Incomplete);
return; return;
}; };
@ -318,7 +320,7 @@ test "shouldnt parse incomplete hex number 2" {
const input = "0x"; const input = "0x";
const errdata = try std.testing.allocator.create(errors.ErrorData); const errdata = try std.testing.allocator.create(errors.ErrorData);
defer std.testing.allocator.destroy(errdata); defer std.testing.allocator.destroy(errdata);
const result = lex(input, input.len, 0, errdata) catch |err| { const result = lex(input, input.len, 0, errdata, std.testing.allocator) catch |err| {
try std.testing.expect(err == token.LexError.Incomplete); try std.testing.expect(err == token.LexError.Incomplete);
return; return;
}; };
@ -335,7 +337,7 @@ test "shouldnt parse incomplete hex number 2" {
test "should lex octal number" { test "should lex octal number" {
const input = "0o755"; const input = "0o755";
const result = try lex(input, input.len, 0, undefined); const result = try lex(input, input.len, 0, undefined, std.testing.allocator);
if (result) |tuple| { if (result) |tuple| {
const r = tuple[0]; const r = tuple[0];
@ -347,7 +349,7 @@ test "should lex octal number" {
test "should lex octal number 2" { test "should lex octal number 2" {
const input = " 0o755 "; const input = " 0o755 ";
const result = try lex(input, input.len, 2, undefined); const result = try lex(input, input.len, 2, undefined, std.testing.allocator);
if (result) |tuple| { if (result) |tuple| {
const r = tuple[0]; const r = tuple[0];
@ -361,7 +363,7 @@ test "shouldnt parse incomplete octal number" {
const input = "0o8"; const input = "0o8";
const errdata = try std.testing.allocator.create(errors.ErrorData); const errdata = try std.testing.allocator.create(errors.ErrorData);
defer std.testing.allocator.destroy(errdata); defer std.testing.allocator.destroy(errdata);
const result = lex(input, input.len, 0, errdata) catch |err| { const result = lex(input, input.len, 0, errdata, std.testing.allocator) catch |err| {
try std.testing.expect(err == token.LexError.Incomplete); try std.testing.expect(err == token.LexError.Incomplete);
return; return;
}; };
@ -378,7 +380,7 @@ test "shouldnt parse incomplete octal number" {
test "should lex binary number" { test "should lex binary number" {
const input = "0b1011"; const input = "0b1011";
const result = try lex(input, input.len, 0, undefined); const result = try lex(input, input.len, 0, undefined, std.testing.allocator);
if (result) |tuple| { if (result) |tuple| {
const r = tuple[0]; const r = tuple[0];
@ -392,7 +394,7 @@ test "shouldnt parse incomplete binary number" {
const input = "0b2"; const input = "0b2";
const errdata = try std.testing.allocator.create(errors.ErrorData); const errdata = try std.testing.allocator.create(errors.ErrorData);
defer std.testing.allocator.destroy(errdata); defer std.testing.allocator.destroy(errdata);
const result = lex(input, input.len, 0, errdata) catch |err| { const result = lex(input, input.len, 0, errdata, std.testing.allocator) catch |err| {
try std.testing.expect(err == token.LexError.Incomplete); try std.testing.expect(err == token.LexError.Incomplete);
return; return;
}; };
@ -409,7 +411,7 @@ test "shouldnt parse incomplete binary number" {
test "should lex fp number 1" { test "should lex fp number 1" {
const input = "1.2"; const input = "1.2";
const result = try lex(input, input.len, 0, undefined); const result = try lex(input, input.len, 0, undefined, std.testing.allocator);
if (result) |tuple| { if (result) |tuple| {
const r = tuple[0]; const r = tuple[0];
@ -423,7 +425,7 @@ test "should lex fp number 2" {
const input = "0.1"; const input = "0.1";
const errdata = try std.testing.allocator.create(errors.ErrorData); const errdata = try std.testing.allocator.create(errors.ErrorData);
defer std.testing.allocator.destroy(errdata); defer std.testing.allocator.destroy(errdata);
const result = try lex(input, input.len, 0, errdata); const result = try lex(input, input.len, 0, errdata, std.testing.allocator);
if (result) |tuple| { if (result) |tuple| {
const r = tuple[0]; const r = tuple[0];
@ -435,7 +437,7 @@ test "should lex fp number 2" {
test "should lex fp number 3" { test "should lex fp number 3" {
const input = "123.456"; const input = "123.456";
const result = try lex(input, input.len, 0, undefined); const result = try lex(input, input.len, 0, undefined, std.testing.allocator);
if (result) |tuple| { if (result) |tuple| {
const r = tuple[0]; const r = tuple[0];
@ -449,7 +451,7 @@ test "should fail on incomplete fp number" {
const input = "123."; const input = "123.";
const errdata = try std.testing.allocator.create(errors.ErrorData); const errdata = try std.testing.allocator.create(errors.ErrorData);
defer std.testing.allocator.destroy(errdata); defer std.testing.allocator.destroy(errdata);
const result = lex(input, input.len, 0, errdata) catch |err| { const result = lex(input, input.len, 0, errdata, std.testing.allocator) catch |err| {
try std.testing.expect(err == token.LexError.IncompleteFloatingNumber); try std.testing.expect(err == token.LexError.IncompleteFloatingNumber);
return; return;
}; };
@ -466,7 +468,7 @@ test "should fail on incomplete fp number" {
test "should lex scientific number" { test "should lex scientific number" {
const input = "42e+3"; const input = "42e+3";
const result = try lex(input, input.len, 0, undefined); const result = try lex(input, input.len, 0, undefined, std.testing.allocator);
if (result) |tuple| { if (result) |tuple| {
const r = tuple[0]; const r = tuple[0];
@ -480,7 +482,7 @@ test "should fail on incomplete scientific number" {
const input = "123e"; const input = "123e";
const errdata = try std.testing.allocator.create(errors.ErrorData); const errdata = try std.testing.allocator.create(errors.ErrorData);
defer std.testing.allocator.destroy(errdata); defer std.testing.allocator.destroy(errdata);
const result = lex(input, input.len, 0, errdata) catch |err| { const result = lex(input, input.len, 0, errdata, std.testing.allocator) catch |err| {
try std.testing.expect(err == token.LexError.IncompleteScientificNumber); try std.testing.expect(err == token.LexError.IncompleteScientificNumber);
return; return;
}; };
@ -499,7 +501,7 @@ test "should fail on incomplete scientific number 2" {
const input = "123e+"; const input = "123e+";
const errdata = try std.testing.allocator.create(errors.ErrorData); const errdata = try std.testing.allocator.create(errors.ErrorData);
defer std.testing.allocator.destroy(errdata); defer std.testing.allocator.destroy(errdata);
const result = lex(input, input.len, 0, errdata) catch |err| { const result = lex(input, input.len, 0, errdata, std.testing.allocator) catch |err| {
try std.testing.expect(err == token.LexError.IncompleteScientificNumber); try std.testing.expect(err == token.LexError.IncompleteScientificNumber);
return; return;
}; };
@ -516,7 +518,7 @@ test "should fail on incomplete scientific number 2" {
test "should lex floating scientific number" { test "should lex floating scientific number" {
const input = "0.58e+3"; const input = "0.58e+3";
const result = try lex(input, input.len, 0, undefined); const result = try lex(input, input.len, 0, undefined, std.testing.allocator);
if (result) |tuple| { if (result) |tuple| {
const r = tuple[0]; const r = tuple[0];

View File

@ -43,7 +43,7 @@ pub fn tokenize(
// attempt to lex a number // attempt to lex a number
var current_error: errors.ErrorData = undefined; var current_error: errors.ErrorData = undefined;
const number_lex = number.lex(input, input_len, actual_next_pos, &current_error) catch |e| switch (e) { const number_lex = number.lex(input, input_len, actual_next_pos, &current_error, alloc) catch |e| switch (e) {
// recoverable errors // recoverable errors
LexError.Incomplete => { LexError.Incomplete => {
// add to list of errors // add to list of errors
@ -126,7 +126,7 @@ pub fn tokenize(
else { else {
// Create an error "nothing matched" and continue lexing // Create an error "nothing matched" and continue lexing
// after the whitespace // after the whitespace
current_error.init("Unrecognized character", actual_next_pos, actual_next_pos + 1); try current_error.init("Unrecognized character", actual_next_pos, actual_next_pos + 1, alloc);
try err_arrl.append(current_error); try err_arrl.append(current_error);
current_pos = ignore_until_whitespace(input, actual_next_pos); current_pos = ignore_until_whitespace(input, actual_next_pos);
continue; continue;

View File

@ -48,10 +48,11 @@ pub const Module = struct {
switch (e) { switch (e) {
error.Unmatched => { error.Unmatched => {
// create the error value // create the error value
error_target.init( try error_target.init(
"No statement found", "No statement found",
current_pos, current_pos,
current_pos + 1, current_pos + 1,
allocator,
); );
return error.Unmatched; return error.Unmatched;
}, },

View File

@ -1,22 +1,33 @@
const std = @import("std"); const std = @import("std");
pub const ErrorLabel = struct {
message: []const u8,
start: usize,
end: usize,
};
/// Holds information about errors generated during the compilation, /// Holds information about errors generated during the compilation,
/// and pretty prints them. /// and pretty prints them.
pub const ErrorData = struct { pub const ErrorData = struct {
reason: []const u8, reason: []const u8,
start_position: usize, start_position: usize,
end_position: usize, end_position: usize,
labels: std.ArrayList(ErrorLabel),
alloc: std.mem.Allocator,
pub fn init( pub fn init(
target: *@This(), target: *@This(),
reason: []const u8, reason: []const u8,
start_position: usize, start_position: usize,
end_position: usize, end_position: usize,
) void { alloc: std.mem.Allocator,
) !void {
target.* = .{ target.* = .{
.reason = reason, .reason = reason,
.start_position = start_position, .start_position = start_position,
.end_position = end_position, .end_position = end_position,
.labels = std.ArrayList(ErrorLabel).init(alloc),
.alloc = alloc,
}; };
} }
@ -28,15 +39,11 @@ pub const ErrorData = struct {
const error_message = try std.fmt.allocPrint(alloc, const error_message = try std.fmt.allocPrint(alloc,
\\Error: {s} \\Error: {s}
\\[{s}:{d}:{d}] \\[{s}:{d}:{d}]
\\
\\ {d} | {s}
, .{ , .{
self.reason, self.reason,
filename, filename,
faulty_line.line_number, faulty_line.line_number,
faulty_line.column_number, faulty_line.column_number,
faulty_line.line_number,
faulty_line.line,
}); });
return error_message; return error_message;
@ -47,9 +54,8 @@ pub const ErrorData = struct {
// - Get previous, current and next line // - Get previous, current and next line
// - Display message // - Display message
/// Does nothing at the moment
pub fn deinit(self: *@This()) void { pub fn deinit(self: *@This()) void {
_ = self; self.labels.deinit(self.alloc);
} }
}; };
@ -150,6 +156,8 @@ test "should gen error message" {
.reason = "Invalid identifier", .reason = "Invalid identifier",
.start_position = 6, .start_position = 6,
.end_position = 9, .end_position = 9,
.labels = std.ArrayList(ErrorLabel).init(std.testing.allocator),
.alloc = std.testing.allocator,
}; };
const out = try err.get_error_str(source, "repl", std.testing.allocator); const out = try err.get_error_str(source, "repl", std.testing.allocator);
defer std.testing.allocator.free(out); defer std.testing.allocator.free(out);
@ -157,7 +165,5 @@ test "should gen error message" {
try std.testing.expectEqualStrings( try std.testing.expectEqualStrings(
\\Error: Invalid identifier \\Error: Invalid identifier
\\[repl:1:7] \\[repl:1:7]
\\
\\ 1 | print(ehh)
, out); , out);
} }