lang F0.1: extractContext utility for diagnostic renderer

Adds LineInfo, ContextLines, and extractContext(allocator, source, span) to
errors.zig — a pure utility that returns the source lines covered by a span
plus columns for caret rendering. Prereq for F0.2's new render path which
will produce Rust-style multi-line diagnostics with code excerpts.

8 unit tests cover the boundary cases: single-line span, multi-line spans
(1 and 2 newlines crossed), span on an empty line, span at end-of-file
without trailing newline, empty source, and offsets beyond source.len
(clamping).

No render surface change yet; F0.2 wires this into a new render mode kept
behind a RenderStyle flag so old gcc-style output remains available during
the transition.
This commit is contained in:
agra
2026-05-28 19:37:00 +03:00
parent 29bd182f3f
commit e347f59e50
3 changed files with 176 additions and 0 deletions

View File

@@ -27,6 +27,72 @@ pub const SourceLoc = struct {
}
};
pub const LineInfo = struct {
line_num: u32,
text: []const u8,
};
pub const ContextLines = struct {
lines: []LineInfo,
start_col: u32,
end_col: u32,
pub fn deinit(self: *ContextLines, allocator: std.mem.Allocator) void {
allocator.free(self.lines);
}
};
pub fn extractContext(
allocator: std.mem.Allocator,
source: []const u8,
span: Span,
) !ContextLines {
const source_len: u32 = @intCast(source.len);
const start = @min(span.start, source_len);
const end = @max(start, @min(span.end, source_len));
var line_num: u32 = 1;
var line_start: u32 = 0;
var i: u32 = 0;
while (i < start) : (i += 1) {
if (source[i] == '\n') {
line_num += 1;
line_start = i + 1;
}
}
const start_col = (start - line_start) + 1;
var lines: std.ArrayList(LineInfo) = .empty;
defer lines.deinit(allocator);
var cur_line_num = line_num;
var cur_line_start = line_start;
while (true) {
var cur_line_end = cur_line_start;
while (cur_line_end < source_len and source[cur_line_end] != '\n') : (cur_line_end += 1) {}
try lines.append(allocator, .{
.line_num = cur_line_num,
.text = source[cur_line_start..cur_line_end],
});
if (end <= cur_line_end) {
const end_col = (end - cur_line_start) + 1;
const owned = try lines.toOwnedSlice(allocator);
return ContextLines{
.lines = owned,
.start_col = start_col,
.end_col = end_col,
};
}
cur_line_num += 1;
cur_line_start = cur_line_end + 1;
}
}
pub const Diagnostic = struct {
level: Level,
message: []const u8,