diff --git a/build.zig b/build.zig index 0cb19e4..ff38efa 100644 --- a/build.zig +++ b/build.zig @@ -113,6 +113,8 @@ pub fn build(b: *std.Build) void { const files = [_][]const u8{ "src/01_lexic/root.zig", "src/02_syntax/root.zig", + "src/02_syntax/root.zig", + "src/errors/root.zig", }; for (files) |file| { const file_unit_test = b.addTest(.{ diff --git a/src/errors/root.zig b/src/errors/root.zig index c2454a5..e157813 100644 --- a/src/errors/root.zig +++ b/src/errors/root.zig @@ -20,16 +20,109 @@ pub const ErrorData = struct { }; } - pub fn print(self: *@This()) void { - std.debug.print("Error: {s}\n", .{self.reason}); + /// 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: *@This(), source_code: []const u8, alloc: std.mem.Allocator) ![]u8 { + const faulty_line = get_line(source_code, self.start_position); + + const error_message = try std.fmt.allocPrint(alloc, + \\Error: {s} + \\{s} + , .{ self.reason, faulty_line }); + + return error_message; } + // TODO: + // - transform absolute position into line:column + // - Get previous, current and next line + // - Display message + /// Does nothing at the moment pub fn deinit(self: *@This()) void { _ = self; } }; +fn get_line(input: []const u8, at: usize) []const u8 { + 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; + } + } + + // 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 input[line_start..line_end]; +} + test { std.testing.refAllDecls(@This()); } + +test "should get a single line" { + const input = "print(hello)"; + const at = 4; + const output = get_line(input, at); + + try std.testing.expectEqualStrings("print(hello)", output); +} + +test "should get line from 2 lines (1)" { + const input = "print(hello)\nprint(bye)"; + const at = 4; + const output = get_line(input, at); + + try std.testing.expectEqualStrings("print(hello)", output); +} + +test "should get line from 2 lines (2)" { + const input = "print(hello)\nprint(bye)"; + const at = 15; + const output = get_line(input, at); + + try std.testing.expectEqualStrings("print(bye)", output); +} + +test "should get line from 2 lines (3)" { + const input = "print(hello)\nprint(sure?)\nprint(bye!)"; + const at = 15; + const output = get_line(input, at); + + try std.testing.expectEqualStrings("print(sure?)", output); +} + +test "should gen error message" { + // get_error_str + const source = "print(ehh)"; + var err = ErrorData{ + .reason = "Invalid identifier", + .start_position = 6, + .end_position = 9, + }; + const out = try err.get_error_str(source, std.testing.allocator); + defer std.testing.allocator.free(out); + + try std.testing.expectEqualStrings( + \\Error: Invalid identifier + \\print(ehh) + , out); +}