string_bytes: []const u8,
extra: []const u32,
pub const empty: ErrorBundle = .{
.string_bytes = &.{},
.extra = &.{},
};
pub const MessageIndex = enum(u32) {
_,
};
pub const SourceLocationIndex = enum(u32) {
none = 0,
_,
};
pub const ErrorMessageList = struct {
len: u32,
start: u32,
compile_log_text: u32,
};
pub const SourceLocation = struct {
src_path: u32,
line: u32,
column: u32,
span_start: u32,
span_main: u32,
span_end: u32,
source_line: u32 = 0,
reference_trace_len: u32 = 0,
};
pub const ErrorMessage = struct {
msg: u32,
count: u32 = 1,
src_loc: SourceLocationIndex = .none,
notes_len: u32 = 0,
};
pub const ReferenceTrace = struct {
decl_name: u32,
src_loc: SourceLocationIndex,
};
pub fn deinit(eb: *ErrorBundle, gpa: Allocator) void {
gpa.free(eb.string_bytes);
gpa.free(eb.extra);
eb.* = undefined;
}
pub fn errorMessageCount(eb: ErrorBundle) u32 {
if (eb.extra.len == 0) return 0;
return eb.getErrorMessageList().len;
}
pub fn getErrorMessageList(eb: ErrorBundle) ErrorMessageList {
return eb.extraData(ErrorMessageList, 0).data;
}
pub fn getMessages(eb: ErrorBundle) []const MessageIndex {
const list = eb.getErrorMessageList();
return @ptrCast([]const MessageIndex, eb.extra[list.start..][0..list.len]);
}
pub fn getErrorMessage(eb: ErrorBundle, index: MessageIndex) ErrorMessage {
return eb.extraData(ErrorMessage, @enumToInt(index)).data;
}
pub fn getSourceLocation(eb: ErrorBundle, index: SourceLocationIndex) SourceLocation {
assert(index != .none);
return eb.extraData(SourceLocation, @enumToInt(index)).data;
}
pub fn getNotes(eb: ErrorBundle, index: MessageIndex) []const MessageIndex {
const notes_len = eb.getErrorMessage(index).notes_len;
const start = @enumToInt(index) + @typeInfo(ErrorMessage).Struct.fields.len;
return @ptrCast([]const MessageIndex, eb.extra[start..][0..notes_len]);
}
pub fn getCompileLogOutput(eb: ErrorBundle) [:0]const u8 {
return nullTerminatedString(eb, getErrorMessageList(eb).compile_log_text);
}
fn extraData(eb: ErrorBundle, comptime T: type, index: usize) struct { data: T, end: usize } {
const fields = @typeInfo(T).Struct.fields;
var i: usize = index;
var result: T = undefined;
inline for (fields) |field| {
@field(result, field.name) = switch (field.type) {
u32 => eb.extra[i],
MessageIndex => @intToEnum(MessageIndex, eb.extra[i]),
SourceLocationIndex => @intToEnum(SourceLocationIndex, eb.extra[i]),
else => @compileError("bad field type"),
};
i += 1;
}
return .{
.data = result,
.end = i,
};
}
pub fn nullTerminatedString(eb: ErrorBundle, index: usize) [:0]const u8 {
const string_bytes = eb.string_bytes;
var end: usize = index;
while (string_bytes[end] != 0) {
end += 1;
}
return string_bytes[index..end :0];
}
pub const RenderOptions = struct {
ttyconf: std.debug.TTY.Config,
include_reference_trace: bool = true,
include_source_line: bool = true,
include_log_text: bool = true,
};
pub fn renderToStdErr(eb: ErrorBundle, options: RenderOptions) void {
std.debug.getStderrMutex().lock();
defer std.debug.getStderrMutex().unlock();
const stderr = std.io.getStdErr();
return renderToWriter(eb, options, stderr.writer()) catch return;
}
pub fn renderToWriter(eb: ErrorBundle, options: RenderOptions, writer: anytype) anyerror!void {
for (eb.getMessages()) |err_msg| {
try renderErrorMessageToWriter(eb, options, err_msg, writer, "error", .Red, 0);
}
if (options.include_log_text) {
const log_text = eb.getCompileLogOutput();
if (log_text.len != 0) {
try writer.writeAll("\nCompile Log Output:\n");
try writer.writeAll(log_text);
}
}
}
fn renderErrorMessageToWriter(
eb: ErrorBundle,
options: RenderOptions,
err_msg_index: MessageIndex,
stderr: anytype,
kind: []const u8,
color: std.debug.TTY.Color,
indent: usize,
) anyerror!void {
const ttyconf = options.ttyconf;
var counting_writer = std.io.countingWriter(stderr);
const counting_stderr = counting_writer.writer();
const err_msg = eb.getErrorMessage(err_msg_index);
if (err_msg.src_loc != .none) {
const src = eb.extraData(SourceLocation, @enumToInt(err_msg.src_loc));
try counting_stderr.writeByteNTimes(' ', indent);
try ttyconf.setColor(stderr, .Bold);
try counting_stderr.print("{s}:{d}:{d}: ", .{
eb.nullTerminatedString(src.data.src_path),
src.data.line + 1,
src.data.column + 1,
});
try ttyconf.setColor(stderr, color);
try counting_stderr.writeAll(kind);
try counting_stderr.writeAll(": ");
const prefix_len = @intCast(usize, counting_stderr.context.bytes_written);
try ttyconf.setColor(stderr, .Reset);
try ttyconf.setColor(stderr, .Bold);
if (err_msg.count == 1) {
try writeMsg(eb, err_msg, stderr, prefix_len);
try stderr.writeByte('\n');
} else {
try writeMsg(eb, err_msg, stderr, prefix_len);
try ttyconf.setColor(stderr, .Dim);
try stderr.print(" ({d} times)\n", .{err_msg.count});
}
try ttyconf.setColor(stderr, .Reset);
if (src.data.source_line != 0 and options.include_source_line) {
const line = eb.nullTerminatedString(src.data.source_line);
for (line) |b| switch (b) {
'\t' => try stderr.writeByte(' '),
else => try stderr.writeByte(b),
};
try stderr.writeByte('\n');
const before_caret = src.data.span_main - src.data.span_start;
const after_caret = src.data.span_end - src.data.span_main -| 1;
try stderr.writeByteNTimes(' ', src.data.column - before_caret);
try ttyconf.setColor(stderr, .Green);
try stderr.writeByteNTimes('~', before_caret);
try stderr.writeByte('^');
try stderr.writeByteNTimes('~', after_caret);
try stderr.writeByte('\n');
try ttyconf.setColor(stderr, .Reset);
}
for (eb.getNotes(err_msg_index)) |note| {
try renderErrorMessageToWriter(eb, options, note, stderr, "note", .Cyan, indent);
}
if (src.data.reference_trace_len > 0 and options.include_reference_trace) {
try ttyconf.setColor(stderr, .Reset);
try ttyconf.setColor(stderr, .Dim);
try stderr.print("referenced by:\n", .{});
var ref_index = src.end;
for (0..src.data.reference_trace_len) |_| {
const ref_trace = eb.extraData(ReferenceTrace, ref_index);
ref_index = ref_trace.end;
if (ref_trace.data.src_loc != .none) {
const ref_src = eb.getSourceLocation(ref_trace.data.src_loc);
try stderr.print(" {s}: {s}:{d}:{d}\n", .{
eb.nullTerminatedString(ref_trace.data.decl_name),
eb.nullTerminatedString(ref_src.src_path),
ref_src.line + 1,
ref_src.column + 1,
});
} else if (ref_trace.data.decl_name != 0) {
const count = ref_trace.data.decl_name;
try stderr.print(
" {d} reference(s) hidden; use '-freference-trace={d}' to see all references\n",
.{ count, count + src.data.reference_trace_len - 1 },
);
} else {
try stderr.print(
" remaining reference traces hidden; use '-freference-trace' to see all reference traces\n",
.{},
);
}
}
try stderr.writeByte('\n');
try ttyconf.setColor(stderr, .Reset);
}
} else {
try ttyconf.setColor(stderr, color);
try stderr.writeByteNTimes(' ', indent);
try stderr.writeAll(kind);
try stderr.writeAll(": ");
try ttyconf.setColor(stderr, .Reset);
const msg = eb.nullTerminatedString(err_msg.msg);
if (err_msg.count == 1) {
try stderr.print("{s}\n", .{msg});
} else {
try stderr.print("{s}", .{msg});
try ttyconf.setColor(stderr, .Dim);
try stderr.print(" ({d} times)\n", .{err_msg.count});
}
try ttyconf.setColor(stderr, .Reset);
for (eb.getNotes(err_msg_index)) |note| {
try renderErrorMessageToWriter(eb, options, note, stderr, "note", .Cyan, indent + 4);
}
}
}
fn writeMsg(eb: ErrorBundle, err_msg: ErrorMessage, stderr: anytype, indent: usize) !void {
var lines = std.mem.split(u8, eb.nullTerminatedString(err_msg.msg), "\n");
while (lines.next()) |line| {
try stderr.writeAll(line);
if (lines.index == null) break;
try stderr.writeByte('\n');
try stderr.writeByteNTimes(' ', indent);
}
}
const std = @import("std");
const ErrorBundle = @This();
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
pub const Wip = struct {
gpa: Allocator,
string_bytes: std.ArrayListUnmanaged(u8),
extra: std.ArrayListUnmanaged(u32),
root_list: std.ArrayListUnmanaged(MessageIndex),
pub fn init(wip: *Wip, gpa: Allocator) !void {
wip.* = .{
.gpa = gpa,
.string_bytes = .{},
.extra = .{},
.root_list = .{},
};
try wip.string_bytes.append(gpa, 0);
assert(0 == try addExtra(wip, ErrorMessageList{
.len = 0,
.start = 0,
.compile_log_text = 0,
}));
}
pub fn deinit(wip: *Wip) void {
const gpa = wip.gpa;
wip.root_list.deinit(gpa);
wip.string_bytes.deinit(gpa);
wip.extra.deinit(gpa);
wip.* = undefined;
}
pub fn toOwnedBundle(wip: *Wip, compile_log_text: []const u8) !ErrorBundle {
const gpa = wip.gpa;
if (wip.root_list.items.len == 0) {
assert(compile_log_text.len == 0);
wip.deinit();
wip.* = .{
.gpa = gpa,
.string_bytes = .{},
.extra = .{},
.root_list = .{},
};
return empty;
}
const compile_log_str_index = if (compile_log_text.len == 0) 0 else str: {
const str = @intCast(u32, wip.string_bytes.items.len);
try wip.string_bytes.ensureUnusedCapacity(gpa, compile_log_text.len + 1);
wip.string_bytes.appendSliceAssumeCapacity(compile_log_text);
wip.string_bytes.appendAssumeCapacity(0);
break :str str;
};
wip.setExtra(0, ErrorMessageList{
.len = @intCast(u32, wip.root_list.items.len),
.start = @intCast(u32, wip.extra.items.len),
.compile_log_text = compile_log_str_index,
});
try wip.extra.appendSlice(gpa, @ptrCast([]const u32, wip.root_list.items));
wip.root_list.clearAndFree(gpa);
return .{
.string_bytes = try wip.string_bytes.toOwnedSlice(gpa),
.extra = try wip.extra.toOwnedSlice(gpa),
};
}
pub fn tmpBundle(wip: Wip) ErrorBundle {
return .{
.string_bytes = wip.string_bytes.items,
.extra = wip.extra.items,
};
}
pub fn addString(wip: *Wip, s: []const u8) !u32 {
const gpa = wip.gpa;
const index = @intCast(u32, wip.string_bytes.items.len);
try wip.string_bytes.ensureUnusedCapacity(gpa, s.len + 1);
wip.string_bytes.appendSliceAssumeCapacity(s);
wip.string_bytes.appendAssumeCapacity(0);
return index;
}
pub fn printString(wip: *Wip, comptime fmt: []const u8, args: anytype) !u32 {
const gpa = wip.gpa;
const index = @intCast(u32, wip.string_bytes.items.len);
try wip.string_bytes.writer(gpa).print(fmt, args);
try wip.string_bytes.append(gpa, 0);
return index;
}
pub fn addRootErrorMessage(wip: *Wip, em: ErrorMessage) !void {
try wip.root_list.ensureUnusedCapacity(wip.gpa, 1);
wip.root_list.appendAssumeCapacity(try addErrorMessage(wip, em));
}
pub fn addErrorMessage(wip: *Wip, em: ErrorMessage) !MessageIndex {
return @intToEnum(MessageIndex, try addExtra(wip, em));
}
pub fn addErrorMessageAssumeCapacity(wip: *Wip, em: ErrorMessage) MessageIndex {
return @intToEnum(MessageIndex, addExtraAssumeCapacity(wip, em));
}
pub fn addSourceLocation(wip: *Wip, sl: SourceLocation) !SourceLocationIndex {
return @intToEnum(SourceLocationIndex, try addExtra(wip, sl));
}
pub fn addReferenceTrace(wip: *Wip, rt: ReferenceTrace) !void {
_ = try addExtra(wip, rt);
}
pub fn addBundle(wip: *Wip, other: ErrorBundle) !void {
const gpa = wip.gpa;
try wip.string_bytes.ensureUnusedCapacity(gpa, other.string_bytes.len);
try wip.extra.ensureUnusedCapacity(gpa, other.extra.len);
const other_list = other.getMessages();
const notes_start = wip.reserveNotes(@intCast(u32, other_list.len)) catch unreachable;
for (notes_start.., other_list) |note, message| {
wip.extra.items[note] = @enumToInt(wip.addOtherMessage(other, message) catch unreachable);
}
}
pub fn reserveNotes(wip: *Wip, notes_len: u32) !u32 {
try wip.extra.ensureUnusedCapacity(wip.gpa, notes_len +
notes_len * @typeInfo(ErrorBundle.ErrorMessage).Struct.fields.len);
wip.extra.items.len += notes_len;
return @intCast(u32, wip.extra.items.len - notes_len);
}
fn addOtherMessage(wip: *Wip, other: ErrorBundle, msg_index: MessageIndex) !MessageIndex {
const other_msg = other.getErrorMessage(msg_index);
const src_loc = try wip.addOtherSourceLocation(other, other_msg.src_loc);
const msg = try wip.addErrorMessage(.{
.msg = try wip.addString(other.nullTerminatedString(other_msg.msg)),
.count = other_msg.count,
.src_loc = src_loc,
.notes_len = other_msg.notes_len,
});
const notes_start = try wip.reserveNotes(other_msg.notes_len);
for (notes_start.., other.getNotes(msg_index)) |note, other_note| {
wip.extra.items[note] = @enumToInt(try wip.addOtherMessage(other, other_note));
}
return msg;
}
fn addOtherSourceLocation(
wip: *Wip,
other: ErrorBundle,
index: SourceLocationIndex,
) !SourceLocationIndex {
if (index == .none) return .none;
const other_sl = other.getSourceLocation(index);
const src_loc = try wip.addSourceLocation(.{
.src_path = try wip.addString(other.nullTerminatedString(other_sl.src_path)),
.line = other_sl.line,
.column = other_sl.column,
.span_start = other_sl.span_start,
.span_main = other_sl.span_main,
.span_end = other_sl.span_end,
.source_line = try wip.addString(other.nullTerminatedString(other_sl.source_line)),
.reference_trace_len = other_sl.reference_trace_len,
});
return src_loc;
}
fn addExtra(wip: *Wip, extra: anytype) Allocator.Error!u32 {
const gpa = wip.gpa;
const fields = @typeInfo(@TypeOf(extra)).Struct.fields;
try wip.extra.ensureUnusedCapacity(gpa, fields.len);
return addExtraAssumeCapacity(wip, extra);
}
fn addExtraAssumeCapacity(wip: *Wip, extra: anytype) u32 {
const fields = @typeInfo(@TypeOf(extra)).Struct.fields;
const result = @intCast(u32, wip.extra.items.len);
wip.extra.items.len += fields.len;
setExtra(wip, result, extra);
return result;
}
fn setExtra(wip: *Wip, index: usize, extra: anytype) void {
const fields = @typeInfo(@TypeOf(extra)).Struct.fields;
var i = index;
inline for (fields) |field| {
wip.extra.items[i] = switch (field.type) {
u32 => @field(extra, field.name),
MessageIndex => @enumToInt(@field(extra, field.name)),
SourceLocationIndex => @enumToInt(@field(extra, field.name)),
else => @compileError("bad field type"),
};
i += 1;
}
}
};