ERR/E3.0 (slice 3c): source snippet + caret in traces

Each trace frame now shows the offending source line with a `^` caret
under the column — in the catch-handler formatter, the failable-main C
reporter, and the comptime path.

The source line is embedded at compile time as a 5th Frame field
(line_text), not read from disk at runtime: the file field is a
basename and a runtime read would add a filesystem dependency that
fails under the test harness and on locked-down targets.

- errors.lineAt(src, offset): shared helper for the whole source line.
- Frame gains line_text (mirrored in emit_llvm getFrameStructType,
  trace.sx Frame, sx_trace.c SxFrame). emitTraceFrame embeds it; the
  interp .trace_resolve extracts it from the source map.
- trace.sx (new spaces helper) and the C reporter render the line +
  a col-aligned caret, guarded on a non-empty line_text.

Snapshots 243/244/247/253 regenerated. Gates: zig build, zig build
test, run_examples.sh -> 291 passed.
This commit is contained in:
agra
2026-06-01 15:43:22 +03:00
parent b5241243e6
commit 178449b548
10 changed files with 72 additions and 10 deletions

View File

@@ -33,6 +33,18 @@ pub const SourceLoc = struct {
}
};
/// The whole source line containing `byte_offset` (no trailing newline). Empty
/// when `source` is empty. Used to embed the offending line in a trace `Frame`.
pub fn lineAt(source: []const u8, byte_offset: u32) []const u8 {
if (source.len == 0) return source;
const at = @min(byte_offset, @as(u32, @intCast(source.len)));
var start: usize = at;
while (start > 0 and source[start - 1] != '\n') start -= 1;
var end: usize = at;
while (end < source.len and source[end] != '\n') end += 1;
return source[start..end];
}
pub const LineInfo = struct {
line_num: u32,
text: []const u8,

View File

@@ -4721,8 +4721,9 @@ pub const LLVMEmitter = struct {
self.cached_i32, // line
self.cached_i32, // col
str_ty, // func
str_ty, // line_text (the source line, for the snippet)
};
self.frame_struct_type = c.LLVMStructTypeInContext(self.context, &field_types, 4, 0);
self.frame_struct_type = c.LLVMStructTypeInContext(self.context, &field_types, 5, 0);
return self.frame_struct_type.?;
}
@@ -4761,9 +4762,10 @@ pub const LLVMEmitter = struct {
c.LLVMConstInt(self.cached_i32, loc.line, 0),
c.LLVMConstInt(self.cached_i32, loc.col, 0),
self.buildStringConst(func_name),
self.buildStringConst(errors.lineAt(src, instruction.span.start)),
};
const frame_ty = self.getFrameStructType();
const frame_const = c.LLVMConstNamedStruct(frame_ty, &fields, 4);
const frame_const = c.LLVMConstNamedStruct(frame_ty, &fields, 5);
const g = c.LLVMAddGlobal(self.llvm_module, frame_ty, "trace.frame");
c.LLVMSetInitializer(g, frame_const);
c.LLVMSetGlobalConstant(g, 1);

View File

@@ -677,18 +677,21 @@ pub const Interpreter = struct {
const file = std.fs.path.basename(file_full);
var line: i64 = 1;
var col: i64 = 1;
var line_text: []const u8 = "";
if (self.source_map) |sm| {
if (sm.get(file_full)) |src| {
const loc = errors.SourceLoc.compute(src, offset);
line = @intCast(loc.line);
col = @intCast(loc.col);
line_text = errors.lineAt(src, offset);
}
}
const fields = self.alloc.alloc(Value, 4) catch return .{ .value = .undef };
const fields = self.alloc.alloc(Value, 5) catch return .{ .value = .undef };
fields[0] = .{ .string = file };
fields[1] = .{ .int = line };
fields[2] = .{ .int = col };
fields[3] = .{ .string = func_name };
fields[4] = .{ .string = line_text };
return .{ .value = .{ .aggregate = fields } };
},
.const_type => |tid| return .{ .value = .{ .type_tag = tid } },