779 lines
26 KiB
Plaintext
779 lines
26 KiB
Plaintext
// Expect retcode = 0
|
|
|
|
// 丰富诊断:Severity / 稳定 E 码 / Span / Label / Child,渲染为人类可读或 JSON。
|
|
// 位置只存下标,渲染时经 Sources 解析行列;条目拥有其字符串与 labels/children。
|
|
|
|
type .{ .Error, .Status, .make } = @import("./utils/error.spl");
|
|
type .{ .Vec } = @import("./utils/vec.spl");
|
|
type .{ .ByteBuffer } = @import("./utils/byte_buffer.spl");
|
|
type .{ .Str } = @import("./utils/str.spl");
|
|
type .{ .ByteView } = @import("./utils/byte_view.spl");
|
|
type .{ .append_usize } = @import("./utils/fmt.spl");
|
|
type .{ .Sources, .Source, .set_new, .set_add, .set_at, .set_drop } = @import("./utils/source.spl");
|
|
|
|
type Severity = enum {
|
|
Note,
|
|
Help,
|
|
Warning,
|
|
Error,
|
|
Fatal,
|
|
}
|
|
|
|
type Stage = enum {
|
|
None,
|
|
Lex,
|
|
Parse,
|
|
Sema,
|
|
Lower,
|
|
Vm,
|
|
Driver,
|
|
}
|
|
|
|
type Code = enum {
|
|
None,
|
|
// 词法 / 语法
|
|
LexUnexpected,
|
|
ParseExpected,
|
|
ParseUnrecognized,
|
|
// sema
|
|
UndefinedSymbol,
|
|
DuplicateSymbol,
|
|
TypeMismatch,
|
|
ArgCountMismatch,
|
|
NotAFunction,
|
|
NoField,
|
|
NoMethod,
|
|
IndexBaseInvalid,
|
|
TupleIndexOob,
|
|
FitsOverflow,
|
|
CircularDep,
|
|
BreakOutsideLoop,
|
|
ContinueOutsideLoop,
|
|
UndefinedLabel,
|
|
CastInvalid,
|
|
UnknownAttr,
|
|
UnknownIntrin,
|
|
IntrinArgCount,
|
|
VarUninitialized,
|
|
CannotInfer,
|
|
MustReturnValue,
|
|
// lowering
|
|
UnhandledNode,
|
|
ImplicitCastFail,
|
|
NoLvalue,
|
|
BadFieldIndex,
|
|
BadBlock,
|
|
// vm
|
|
IrUnknownKind,
|
|
FrameOverflow,
|
|
// 通用
|
|
Internal,
|
|
}
|
|
|
|
// file 下标不可用时的哨兵。
|
|
fn no_file(): usize {
|
|
ret ~(0 as usize);
|
|
}
|
|
|
|
type Span = struct {
|
|
file: usize,
|
|
start: usize,
|
|
end: usize,
|
|
}
|
|
|
|
fn span_none(): Span {
|
|
ret Span { .file = no_file(), .start = 0, .end = 0 };
|
|
}
|
|
|
|
fn span_make(file: usize, start: usize, end: usize): Span {
|
|
ret Span { .file = file, .start = start, .end = end };
|
|
}
|
|
|
|
fn span_is_none(span: Span): bool {
|
|
ret span.file == no_file();
|
|
}
|
|
|
|
type Label = struct {
|
|
span: Span,
|
|
message: Str,
|
|
primary: bool,
|
|
}
|
|
|
|
type Child = struct {
|
|
severity: Severity,
|
|
span: Span,
|
|
message: Str,
|
|
}
|
|
|
|
type Item = struct {
|
|
severity: Severity,
|
|
stage: Stage,
|
|
code: Code,
|
|
span: Span,
|
|
message: Str,
|
|
labels: Vec, // Label
|
|
children: Vec, // Child
|
|
}
|
|
|
|
type Diag = @self;
|
|
|
|
items: Vec, // Item
|
|
sources: *Sources, // 借用;可为 null(则只输出消息)
|
|
errors: usize,
|
|
warnings: usize,
|
|
fatals: usize,
|
|
|
|
fn new(sources: *Sources): Diag {
|
|
ret Diag {
|
|
.items = Vec.blank(@sizeof(Item), @alignof(Item)),
|
|
.sources = sources,
|
|
.errors = 0,
|
|
.warnings = 0,
|
|
.fatals = 0,
|
|
};
|
|
}
|
|
|
|
fn item_at(self: *Diag, index: usize): *Item {
|
|
match Vec.get(&self.items, index) {
|
|
.Ok(ptr) => { ret ptr as *Item; },
|
|
.Err(error) => { ret null; },
|
|
}
|
|
ret null;
|
|
}
|
|
|
|
fn label_at(it: *Item, index: usize): *Label {
|
|
match Vec.get(&it.labels, index) {
|
|
.Ok(ptr) => { ret ptr as *Label; },
|
|
.Err(error) => { ret null; },
|
|
}
|
|
ret null;
|
|
}
|
|
|
|
fn child_at(it: *Item, index: usize): *Child {
|
|
match Vec.get(&it.children, index) {
|
|
.Ok(ptr) => { ret ptr as *Child; },
|
|
.Err(error) => { ret null; },
|
|
}
|
|
ret null;
|
|
}
|
|
|
|
fn last(self: *Diag): usize {
|
|
if self.items.len == 0 { ret no_file(); }
|
|
ret self.items.len - 1;
|
|
}
|
|
|
|
fn count(self: Diag): usize { ret self.items.len; }
|
|
|
|
fn error_count(self: Diag): usize { ret self.errors; }
|
|
|
|
fn warning_count(self: Diag): usize { ret self.warnings; }
|
|
|
|
fn fatal_count(self: Diag): usize { ret self.fatals; }
|
|
|
|
fn has_error(self: Diag): bool { ret self.errors > 0; }
|
|
|
|
fn has_fatal(self: Diag): bool { ret self.fatals > 0; }
|
|
|
|
|
|
// 追加一条诊断,返回其下标(失败返回 no_file())。span 为主位置。
|
|
fn emit(self: *Diag, severity: Severity, stage: Stage, code: Code, span: Span,
|
|
msg: []u8): usize {
|
|
var message = Str.new();
|
|
match Str.from_slice(msg) {
|
|
.Ok(v) => { message = v; },
|
|
.Err(error) => { ret no_file(); },
|
|
}
|
|
var item = Item {
|
|
.severity = severity,
|
|
.stage = stage,
|
|
.code = code,
|
|
.span = span,
|
|
.message = message,
|
|
.labels = Vec.blank(@sizeof(Label), @alignof(Label)),
|
|
.children = Vec.blank(@sizeof(Child), @alignof(Child)),
|
|
};
|
|
match Vec.push(&self.items, &item as *u8) {
|
|
.Ok => {},
|
|
.Err(error) => { Str.drop(&item.message); ret no_file(); },
|
|
}
|
|
match severity {
|
|
.Error => { self.errors = self.errors + 1; },
|
|
.Fatal => { self.errors = self.errors + 1; self.fatals = self.fatals + 1; },
|
|
.Warning => { self.warnings = self.warnings + 1; },
|
|
_ => {},
|
|
}
|
|
ret self.items.len - 1;
|
|
}
|
|
|
|
fn err(self: *Diag, span: Span, code: Code, msg: []u8): usize {
|
|
ret emit(self, .Error, .None, code, span, msg);
|
|
}
|
|
|
|
fn warn(self: *Diag, span: Span, code: Code, msg: []u8): usize {
|
|
ret emit(self, .Warning, .None, code, span, msg);
|
|
}
|
|
|
|
fn note(self: *Diag, span: Span, code: Code, msg: []u8): usize {
|
|
ret emit(self, .Note, .None, code, span, msg);
|
|
}
|
|
|
|
// 给最近一条诊断附加标签(primary 决定主/次)。
|
|
fn attach_label(self: *Diag, span: Span, msg: []u8, primary: bool): Status {
|
|
var idx = last(self);
|
|
if idx == no_file() { ret .Err(make(.OutOfRange)); }
|
|
var it = item_at(self, idx);
|
|
if it == null { ret .Err(make(.OutOfRange)); }
|
|
|
|
var message = Str.new();
|
|
match Str.from_slice(msg) {
|
|
.Ok(v) => { message = v; },
|
|
.Err(error) => { ret .Err(error); },
|
|
}
|
|
var lab = Label { .span = span, .message = message, .primary = primary };
|
|
match Vec.push(&it.labels, &lab as *u8) {
|
|
.Ok => {},
|
|
.Err(error) => { Str.drop(&lab.message); ret .Err(error); },
|
|
}
|
|
ret .Ok;
|
|
}
|
|
|
|
// 给最近一条诊断附加子诊断(note/help 等)。
|
|
fn attach_child(self: *Diag, severity: Severity, span: Span, msg: []u8): Status {
|
|
var idx = last(self);
|
|
if idx == no_file() { ret .Err(make(.OutOfRange)); }
|
|
var it = item_at(self, idx);
|
|
if it == null { ret .Err(make(.OutOfRange)); }
|
|
|
|
var message = Str.new();
|
|
match Str.from_slice(msg) {
|
|
.Ok(v) => { message = v; },
|
|
.Err(error) => { ret .Err(error); },
|
|
}
|
|
var ch = Child { .severity = severity, .span = span, .message = message };
|
|
match Vec.push(&it.children, &ch as *u8) {
|
|
.Ok => {},
|
|
.Err(error) => { Str.drop(&ch.message); ret .Err(error); },
|
|
}
|
|
ret .Ok;
|
|
}
|
|
|
|
|
|
fn severity_name(severity: Severity): []u8 {
|
|
match severity {
|
|
.Note => { ret "note"; },
|
|
.Help => { ret "help"; },
|
|
.Warning => { ret "warning"; },
|
|
.Error => { ret "error"; },
|
|
.Fatal => { ret "fatal"; },
|
|
}
|
|
ret "?";
|
|
}
|
|
|
|
fn stage_name(stage: Stage): []u8 {
|
|
match stage {
|
|
.None => { ret "none"; },
|
|
.Lex => { ret "lex"; },
|
|
.Parse => { ret "parse"; },
|
|
.Sema => { ret "sema"; },
|
|
.Lower => { ret "lower"; },
|
|
.Vm => { ret "vm"; },
|
|
.Driver => { ret "driver"; },
|
|
}
|
|
ret "?";
|
|
}
|
|
|
|
// 稳定 E 码(diff 依赖;新增 Code 时分配新码,勿改旧值)。
|
|
fn code_str(code: Code): []u8 {
|
|
match code {
|
|
.None => { ret "E0000"; },
|
|
.LexUnexpected => { ret "E0001"; },
|
|
.ParseExpected => { ret "E0002"; },
|
|
.ParseUnrecognized => { ret "E0003"; },
|
|
.UndefinedSymbol => { ret "E0101"; },
|
|
.DuplicateSymbol => { ret "E0102"; },
|
|
.TypeMismatch => { ret "E0103"; },
|
|
.ArgCountMismatch => { ret "E0104"; },
|
|
.NotAFunction => { ret "E0105"; },
|
|
.NoField => { ret "E0106"; },
|
|
.NoMethod => { ret "E0107"; },
|
|
.IndexBaseInvalid => { ret "E0108"; },
|
|
.TupleIndexOob => { ret "E0109"; },
|
|
.FitsOverflow => { ret "E0110"; },
|
|
.CircularDep => { ret "E0111"; },
|
|
.BreakOutsideLoop => { ret "E0112"; },
|
|
.ContinueOutsideLoop => { ret "E0113"; },
|
|
.UndefinedLabel => { ret "E0114"; },
|
|
.CastInvalid => { ret "E0115"; },
|
|
.UnknownAttr => { ret "E0116"; },
|
|
.UnknownIntrin => { ret "E0117"; },
|
|
.IntrinArgCount => { ret "E0118"; },
|
|
.VarUninitialized => { ret "E0119"; },
|
|
.CannotInfer => { ret "E0120"; },
|
|
.MustReturnValue => { ret "E0121"; },
|
|
.UnhandledNode => { ret "E0201"; },
|
|
.ImplicitCastFail => { ret "E0202"; },
|
|
.NoLvalue => { ret "E0203"; },
|
|
.BadFieldIndex => { ret "E0204"; },
|
|
.BadBlock => { ret "E0205"; },
|
|
.IrUnknownKind => { ret "E0301"; },
|
|
.FrameOverflow => { ret "E0302"; },
|
|
.Internal => { ret "E9000"; },
|
|
}
|
|
ret "E0000";
|
|
}
|
|
|
|
fn code_name(code: Code): []u8 {
|
|
match code {
|
|
.None => { ret "none"; },
|
|
.LexUnexpected => { ret "lex_unexpected"; },
|
|
.ParseExpected => { ret "parse_expected"; },
|
|
.ParseUnrecognized => { ret "parse_unrecognized"; },
|
|
.UndefinedSymbol => { ret "undefined_symbol"; },
|
|
.DuplicateSymbol => { ret "duplicate_symbol"; },
|
|
.TypeMismatch => { ret "type_mismatch"; },
|
|
.ArgCountMismatch => { ret "arg_count_mismatch"; },
|
|
.NotAFunction => { ret "not_a_function"; },
|
|
.NoField => { ret "no_field"; },
|
|
.NoMethod => { ret "no_method"; },
|
|
.IndexBaseInvalid => { ret "index_base_invalid"; },
|
|
.TupleIndexOob => { ret "tuple_index_oob"; },
|
|
.FitsOverflow => { ret "fits_overflow"; },
|
|
.CircularDep => { ret "circular_dep"; },
|
|
.BreakOutsideLoop => { ret "break_outside_loop"; },
|
|
.ContinueOutsideLoop => { ret "continue_outside_loop"; },
|
|
.UndefinedLabel => { ret "undefined_label"; },
|
|
.CastInvalid => { ret "cast_invalid"; },
|
|
.UnknownAttr => { ret "unknown_attr"; },
|
|
.UnknownIntrin => { ret "unknown_intrin"; },
|
|
.IntrinArgCount => { ret "intrin_arg_count"; },
|
|
.VarUninitialized => { ret "var_uninitialized"; },
|
|
.CannotInfer => { ret "cannot_infer"; },
|
|
.MustReturnValue => { ret "must_return_value"; },
|
|
.UnhandledNode => { ret "unhandled_node"; },
|
|
.ImplicitCastFail => { ret "implicit_cast_fail"; },
|
|
.NoLvalue => { ret "no_lvalue"; },
|
|
.BadFieldIndex => { ret "bad_field_index"; },
|
|
.BadBlock => { ret "bad_block"; },
|
|
.IrUnknownKind => { ret "ir_unknown_kind"; },
|
|
.FrameOverflow => { ret "frame_overflow"; },
|
|
.Internal => { ret "internal"; },
|
|
}
|
|
ret "none";
|
|
}
|
|
|
|
|
|
fn append_num(out: *ByteBuffer, value: usize): Status {
|
|
ret append_usize(out, value, 10, false);
|
|
}
|
|
|
|
fn append_str(out: *ByteBuffer, s: *Str): Status {
|
|
ret ByteBuffer.append(out, Str.as_slice(s));
|
|
}
|
|
|
|
fn append_view(out: *ByteBuffer, view: ByteView): Status {
|
|
ret ByteBuffer.append(out, ByteView.as_slice(view));
|
|
}
|
|
|
|
type Resolved = struct {
|
|
ok: bool,
|
|
fname: ByteView,
|
|
line: usize,
|
|
col: usize,
|
|
text: ByteView,
|
|
width: usize,
|
|
}
|
|
|
|
fn resolve(self: *Diag, span: Span): Resolved {
|
|
var r = Resolved {
|
|
.ok = false,
|
|
.fname = ByteView.empty(),
|
|
.line = 0,
|
|
.col = 0,
|
|
.text = ByteView.empty(),
|
|
.width = 1,
|
|
};
|
|
if self.sources == null { ret r; }
|
|
if span_is_none(span) { ret r; }
|
|
|
|
var src = set_at(self.sources, span.file);
|
|
if src == null { ret r; }
|
|
|
|
var pos = Source.line_col(src.*, span.start);
|
|
var width: usize = 1;
|
|
if span.end > span.start { width = span.end - span.start; }
|
|
r.ok = true;
|
|
r.fname = src.fname;
|
|
r.line = pos.line;
|
|
r.col = pos.col;
|
|
r.text = Source.line_text(src.*, pos.line);
|
|
r.width = width;
|
|
ret r;
|
|
}
|
|
|
|
fn render_header(it: *Item, r: *Resolved, out: *ByteBuffer): Status {
|
|
if r.ok {
|
|
match append_view(out, r.fname) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match ByteBuffer.append_byte(out, ':') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match append_num(out, r.line) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match ByteBuffer.append_byte(out, ':') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match append_num(out, r.col) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match ByteBuffer.append(out, ": ") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
}
|
|
match ByteBuffer.append(out, severity_name(it.severity)) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match ByteBuffer.append_byte(out, '[') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match ByteBuffer.append(out, code_str(it.code)) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match ByteBuffer.append(out, "]: ") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match append_str(out, &it.message) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
ret ByteBuffer.append_byte(out, '\n');
|
|
}
|
|
|
|
// 输出一行源码 + caret 下划线(列范围按字节;制表符不特殊处理)。
|
|
fn render_span_visual(self: *Diag, span: Span, out: *ByteBuffer): Status {
|
|
var r = resolve(self, span);
|
|
if !r.ok { ret .Ok; }
|
|
|
|
match ByteBuffer.append(out, " ") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match append_view(out, r.text) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match ByteBuffer.append_byte(out, '\n') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
|
|
match ByteBuffer.append(out, " ") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
var pad: usize = 0;
|
|
if r.col > 0 { pad = r.col - 1; }
|
|
match ByteBuffer.append_repeat(out, ' ', pad) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
|
|
var width = r.width;
|
|
if width == 0 { width = 1; }
|
|
if r.col > 0 {
|
|
var room: usize = 0;
|
|
if r.text.len >= pad { room = r.text.len - pad; }
|
|
if room == 0 { room = 1; }
|
|
if width > room { width = room; }
|
|
}
|
|
var k: usize = 0;
|
|
while k < width {
|
|
var ch: u8 = '~';
|
|
if k == 0 { ch = '^'; }
|
|
match ByteBuffer.append_byte(out, ch) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
k = k + 1;
|
|
}
|
|
ret ByteBuffer.append_byte(out, '\n');
|
|
}
|
|
|
|
fn render_child_line(ch: *Child, out: *ByteBuffer): Status {
|
|
match ByteBuffer.append(out, " ") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match ByteBuffer.append(out, severity_name(ch.severity)) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match ByteBuffer.append(out, ": ") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match append_str(out, &ch.message) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
ret ByteBuffer.append_byte(out, '\n');
|
|
}
|
|
|
|
fn render_human(self: *Diag, out: *ByteBuffer): Status {
|
|
var i: usize = 0;
|
|
while i < self.items.len {
|
|
var it = item_at(self, i);
|
|
if it == null { ret .Err(make(.OutOfRange)); }
|
|
|
|
var r = resolve(self, it.span);
|
|
match render_header(it, &r, out) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match render_span_visual(self, it.span, out) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
|
|
var j: usize = 0;
|
|
while j < it.labels.len {
|
|
var lab = label_at(it, j);
|
|
if lab != null {
|
|
match render_span_visual(self, lab.span, out) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
if Str.size(lab.message) != 0 {
|
|
match ByteBuffer.append(out, " ") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match append_str(out, &lab.message) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match ByteBuffer.append_byte(out, '\n') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
}
|
|
}
|
|
j = j + 1;
|
|
}
|
|
|
|
var c: usize = 0;
|
|
while c < it.children.len {
|
|
var ch = child_at(it, c);
|
|
if ch != null {
|
|
match render_child_line(ch, out) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match render_span_visual(self, ch.span, out) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
}
|
|
c = c + 1;
|
|
}
|
|
|
|
if i + 1 < self.items.len {
|
|
match ByteBuffer.append_byte(out, '\n') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
}
|
|
i = i + 1;
|
|
}
|
|
ret .Ok;
|
|
}
|
|
|
|
|
|
fn append_json_hex4(out: *ByteBuffer, b: u8): Status {
|
|
var hex: []u8 = "0123456789abcdef";
|
|
match ByteBuffer.append(out, "\\u00") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match ByteBuffer.append_byte(out, hex[(b >> 4) as usize]) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
ret ByteBuffer.append_byte(out, hex[(b & 15) as usize]);
|
|
}
|
|
|
|
fn append_json_str(out: *ByteBuffer, bytes: []u8): Status {
|
|
match ByteBuffer.append_byte(out, '"') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
var i: usize = 0;
|
|
while i < bytes.len {
|
|
var b = bytes[i];
|
|
if b == '"' {
|
|
match ByteBuffer.append(out, "\\\"") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
} else if b == '\\' {
|
|
match ByteBuffer.append(out, "\\\\") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
} else if b == '\n' {
|
|
match ByteBuffer.append(out, "\\n") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
} else if b == '\r' {
|
|
match ByteBuffer.append(out, "\\r") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
} else if b == '\t' {
|
|
match ByteBuffer.append(out, "\\t") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
} else if b < ' ' {
|
|
match append_json_hex4(out, b) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
} else {
|
|
match ByteBuffer.append_byte(out, b) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
}
|
|
i = i + 1;
|
|
}
|
|
ret ByteBuffer.append_byte(out, '"');
|
|
}
|
|
|
|
fn append_str_json(out: *ByteBuffer, s: *Str): Status {
|
|
ret append_json_str(out, Str.as_slice(s));
|
|
}
|
|
|
|
fn append_bool(out: *ByteBuffer, value: bool): Status {
|
|
if value { ret ByteBuffer.append(out, "true"); }
|
|
ret ByteBuffer.append(out, "false");
|
|
}
|
|
|
|
fn append_loc_json(self: *Diag, span: Span, out: *ByteBuffer): Status {
|
|
var r = resolve(self, span);
|
|
match ByteBuffer.append(out, "{\"file\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
if r.ok {
|
|
match append_json_str(out, ByteView.as_slice(r.fname)) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
} else {
|
|
match ByteBuffer.append(out, "null") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
}
|
|
match ByteBuffer.append(out, ",\"start\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match append_num(out, span.start) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match ByteBuffer.append(out, ",\"end\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match append_num(out, span.end) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match ByteBuffer.append(out, ",\"line\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
if r.ok {
|
|
match append_num(out, r.line) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
} else {
|
|
match ByteBuffer.append(out, "null") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
}
|
|
match ByteBuffer.append(out, ",\"col\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
if r.ok {
|
|
match append_num(out, r.col) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
} else {
|
|
match ByteBuffer.append(out, "null") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
}
|
|
ret ByteBuffer.append_byte(out, '}');
|
|
}
|
|
|
|
fn render_item_json(self: *Diag, it: *Item, out: *ByteBuffer): Status {
|
|
match ByteBuffer.append(out, "{\"severity\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match append_json_str(out, severity_name(it.severity)) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match ByteBuffer.append(out, ",\"stage\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match append_json_str(out, stage_name(it.stage)) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match ByteBuffer.append(out, ",\"code\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match append_json_str(out, code_str(it.code)) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match ByteBuffer.append(out, ",\"name\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match append_json_str(out, code_name(it.code)) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match ByteBuffer.append(out, ",\"message\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match append_str_json(out, &it.message) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
|
|
match ByteBuffer.append(out, ",\"span\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match append_loc_json(self, it.span, out) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
|
|
match ByteBuffer.append(out, ",\"labels\":[") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
var j: usize = 0;
|
|
while j < it.labels.len {
|
|
var lab = label_at(it, j);
|
|
if lab != null {
|
|
if j != 0 {
|
|
match ByteBuffer.append_byte(out, ',') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
}
|
|
match ByteBuffer.append(out, "{\"message\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match append_str_json(out, &lab.message) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match ByteBuffer.append(out, ",\"primary\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match append_bool(out, lab.primary) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match ByteBuffer.append(out, ",\"span\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match append_loc_json(self, lab.span, out) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match ByteBuffer.append_byte(out, '}') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
}
|
|
j = j + 1;
|
|
}
|
|
match ByteBuffer.append_byte(out, ']') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
|
|
match ByteBuffer.append(out, ",\"children\":[") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
var c: usize = 0;
|
|
while c < it.children.len {
|
|
var ch = child_at(it, c);
|
|
if ch != null {
|
|
if c != 0 {
|
|
match ByteBuffer.append_byte(out, ',') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
}
|
|
match ByteBuffer.append(out, "{\"severity\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match append_json_str(out, severity_name(ch.severity)) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match ByteBuffer.append(out, ",\"message\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match append_str_json(out, &ch.message) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match ByteBuffer.append(out, ",\"span\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match append_loc_json(self, ch.span, out) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
match ByteBuffer.append_byte(out, '}') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
}
|
|
c = c + 1;
|
|
}
|
|
match ByteBuffer.append_byte(out, ']') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
ret ByteBuffer.append_byte(out, '}');
|
|
}
|
|
|
|
fn render_json(self: *Diag, out: *ByteBuffer): Status {
|
|
match ByteBuffer.append_byte(out, '[') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
var i: usize = 0;
|
|
while i < self.items.len {
|
|
var it = item_at(self, i);
|
|
if it == null { ret .Err(make(.OutOfRange)); }
|
|
if i != 0 {
|
|
match ByteBuffer.append_byte(out, ',') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
}
|
|
match render_item_json(self, it, out) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
|
i = i + 1;
|
|
}
|
|
ret ByteBuffer.append_byte(out, ']');
|
|
}
|
|
|
|
|
|
fn drop_item(it: *Item) {
|
|
Str.drop(&it.message);
|
|
var j: usize = 0;
|
|
while j < it.labels.len {
|
|
var lab = label_at(it, j);
|
|
if lab != null { Str.drop(&lab.message); }
|
|
j = j + 1;
|
|
}
|
|
Vec.drop(&it.labels);
|
|
var c: usize = 0;
|
|
while c < it.children.len {
|
|
var ch = child_at(it, c);
|
|
if ch != null { Str.drop(&ch.message); }
|
|
c = c + 1;
|
|
}
|
|
Vec.drop(&it.children);
|
|
}
|
|
|
|
fn clear(self: *Diag) {
|
|
var i: usize = 0;
|
|
while i < self.items.len {
|
|
var it = item_at(self, i);
|
|
if it != null { drop_item(it); }
|
|
i = i + 1;
|
|
}
|
|
Vec.clear(&self.items);
|
|
self.errors = 0;
|
|
self.warnings = 0;
|
|
self.fatals = 0;
|
|
}
|
|
|
|
fn drop(self: *Diag) {
|
|
clear(self);
|
|
Vec.drop(&self.items);
|
|
self.sources = null;
|
|
}
|
|
|
|
|
|
fn view(text: []u8): ByteView {
|
|
ret ByteView.from_slice(text);
|
|
}
|
|
|
|
fn expect_ok(result: Status) {
|
|
match result {
|
|
.Ok => {},
|
|
.Err(error) => { @assert(false, "unexpected diag error"); },
|
|
}
|
|
}
|
|
|
|
fn has_bytes(out: *ByteBuffer, needle: []u8): bool {
|
|
ret ByteView.contains(ByteView.from_slice(ByteBuffer.as_slice(out.*)), view(needle));
|
|
}
|
|
|
|
fn test_collect_render_human() {
|
|
var sources = set_new();
|
|
match set_add(&sources, view("a.spl"), view("fn main() {\n y;\n}\n")) {
|
|
.Ok(index) => {},
|
|
.Err(error) => { @assert(false, "add source"); },
|
|
}
|
|
|
|
var d = new(&sources);
|
|
var idx = err(&d, span_make(0, 16, 17), .UndefinedSymbol, "undefined symbol `y`");
|
|
@assert(idx != no_file(), "emit");
|
|
expect_ok(attach_label(&d, span_make(0, 16, 17), "not found in scope", false));
|
|
expect_ok(attach_child(&d, .Note, span_none(), "did you mean `x`?"));
|
|
|
|
@assert(count(d) == 1, "count");
|
|
@assert(error_count(d) == 1 && has_error(d), "errors");
|
|
@assert(warning_count(d) == 0 && !has_fatal(d), "warnings/fatal");
|
|
|
|
var out = ByteBuffer.new();
|
|
expect_ok(render_human(&d, &out));
|
|
@assert(has_bytes(&out, "a.spl:2:5: error[E0101]: undefined symbol `y`"), "human header");
|
|
@assert(has_bytes(&out, " y;"), "human source line");
|
|
@assert(has_bytes(&out, " ^"), "human caret");
|
|
@assert(has_bytes(&out, "not found in scope"), "human label");
|
|
@assert(has_bytes(&out, "note: did you mean `x`?"), "human child");
|
|
ByteBuffer.drop(&out);
|
|
|
|
clear(&d);
|
|
@assert(count(d) == 0 && error_count(d) == 0, "clear");
|
|
drop(&d);
|
|
set_drop(&sources);
|
|
}
|
|
|
|
fn test_render_json() {
|
|
var sources = set_new();
|
|
match set_add(&sources, view("b.spl"), view("let z = 1;\n")) {
|
|
.Ok(index) => {},
|
|
.Err(error) => { @assert(false, "add source"); },
|
|
}
|
|
|
|
var d = new(&sources);
|
|
err(&d, span_make(0, 4, 5), .TypeMismatch, "expected i32, got bool");
|
|
expect_ok(attach_child(&d, .Help, span_none(), "annotate the type"));
|
|
|
|
var out = ByteBuffer.new();
|
|
expect_ok(render_json(&d, &out));
|
|
@assert(has_bytes(&out, "\"severity\":\"error\""), "json severity");
|
|
@assert(has_bytes(&out, "\"code\":\"E0103\""), "json code");
|
|
@assert(has_bytes(&out, "\"name\":\"type_mismatch\""), "json name");
|
|
@assert(has_bytes(&out, "\"line\":1"), "json line");
|
|
@assert(has_bytes(&out, "\"col\":5"), "json col");
|
|
@assert(has_bytes(&out, "\"children\":[{\"severity\":\"help\""), "json child");
|
|
ByteBuffer.drop(&out);
|
|
|
|
drop(&d);
|
|
set_drop(&sources);
|
|
}
|
|
|
|
fn test_empty_and_headerless() {
|
|
var d = new(null);
|
|
err(&d, span_none(), .Internal, "no location");
|
|
var out = ByteBuffer.new();
|
|
expect_ok(render_human(&d, &out));
|
|
@assert(has_bytes(&out, "error[E9000]: no location"), "headerless");
|
|
ByteBuffer.drop(&out);
|
|
drop(&d);
|
|
}
|
|
|
|
fn main() {
|
|
test_collect_render_human();
|
|
test_render_json();
|
|
test_empty_and_headerless();
|
|
}
|