stage2 更多的工具库
This commit is contained in:
1 parent
4141e7d574
commit
16a1f030d4
15 files changed
+1914
-109
No files matched your search
@@ -0,0 +1,243 @@
|
||||
// Expect retcode = 0
|
||||
// 诊断收集与渲染:severity + 文件/行/列 + 消息;统计错误/警告数。
|
||||
// 消息与文件名按拥有型 Str 保存(深拷贝),Diag 拥有它们直到 clear/drop。
|
||||
// 渲染输出到 ByteBuffer;不直接写文件,便于 LSP/测试。
|
||||
|
||||
type .{ .Error, .Status, .make } = @import("error.spl");
|
||||
type .{ .Vec } = @import("vec.spl");
|
||||
type .{ .ByteBuffer } = @import("byte_buffer.spl");
|
||||
type .{ .Str } = @import("str.spl");
|
||||
type .{ .append_usize } = @import("fmt.spl");
|
||||
|
||||
type Severity = enum {
|
||||
Debug,
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
Fatal,
|
||||
}
|
||||
|
||||
type DiagItem = struct {
|
||||
severity: Severity,
|
||||
line: usize,
|
||||
col: usize,
|
||||
fname: Str,
|
||||
message: Str,
|
||||
}
|
||||
|
||||
type Diag = @self;
|
||||
|
||||
items: Vec, // 元素类型 DiagItem
|
||||
errors: usize,
|
||||
warnings: usize,
|
||||
|
||||
fn new(): Diag {
|
||||
ret Diag {
|
||||
.items = Vec.blank(@sizeof(DiagItem), 8),
|
||||
.errors = 0,
|
||||
.warnings = 0,
|
||||
};
|
||||
}
|
||||
|
||||
fn item_at(self: *Diag, index: usize): *DiagItem {
|
||||
match Vec.get(&self.items, index) {
|
||||
.Ok(ptr) => { ret ptr as *DiagItem; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
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 has_error(self: Diag): bool {
|
||||
ret self.errors > 0;
|
||||
}
|
||||
|
||||
// 添加一条诊断;失败不改变已有内容。
|
||||
fn emit(self: *Diag, fname: []u8, line: usize, col: usize, severity: Severity,
|
||||
msg: []u8): Status {
|
||||
var item = DiagItem {
|
||||
.severity = severity,
|
||||
.line = line,
|
||||
.col = col,
|
||||
.fname = Str.new(),
|
||||
.message = Str.new(),
|
||||
};
|
||||
|
||||
match Str.from_slice(fname) {
|
||||
.Ok(v) => { item.fname = v; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
match Str.from_slice(msg) {
|
||||
.Ok(v) => { item.message = v; },
|
||||
.Err(error) => {
|
||||
Str.drop(&item.fname);
|
||||
ret .Err(error);
|
||||
},
|
||||
}
|
||||
match Vec.push(&self.items, &item as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
Str.drop(&item.fname);
|
||||
Str.drop(&item.message);
|
||||
ret .Err(error);
|
||||
},
|
||||
}
|
||||
|
||||
match severity {
|
||||
.Error => { self.errors = self.errors + 1; },
|
||||
.Fatal => { self.errors = self.errors + 1; },
|
||||
.Warning => { self.warnings = self.warnings + 1; },
|
||||
_ => {},
|
||||
}
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
fn err(self: *Diag, fname: []u8, line: usize, col: usize, msg: []u8): Status {
|
||||
ret emit(self, fname, line, col, .Error, msg);
|
||||
}
|
||||
|
||||
fn warn(self: *Diag, fname: []u8, line: usize, col: usize, msg: []u8): Status {
|
||||
ret emit(self, fname, line, col, .Warning, msg);
|
||||
}
|
||||
|
||||
fn info(self: *Diag, fname: []u8, line: usize, col: usize, msg: []u8): Status {
|
||||
ret emit(self, fname, line, col, .Info, msg);
|
||||
}
|
||||
|
||||
fn level_name(severity: Severity): []u8 {
|
||||
match severity {
|
||||
.Debug => { ret "debug"; },
|
||||
.Info => { ret "info"; },
|
||||
.Warning => { ret "warning"; },
|
||||
.Error => { ret "error"; },
|
||||
.Fatal => { ret "fatal"; },
|
||||
}
|
||||
ret "?";
|
||||
}
|
||||
|
||||
fn append_str(out: *ByteBuffer, s: *Str): Status {
|
||||
ret ByteBuffer.append(out, Str.as_slice(s));
|
||||
}
|
||||
|
||||
// 渲染为 "file:line:col: level: message\n"(file 为空时省略前缀)。
|
||||
fn render(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)); }
|
||||
|
||||
if Str.size(it.fname) != 0 {
|
||||
match append_str(out, &it.fname) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append_byte(out, ':') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
}
|
||||
match append_usize(out, it.line, 10, false) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append_byte(out, ':') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match append_usize(out, it.col, 10, false) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append(out, ": ") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append(out, level_name(it.severity)) { .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); } }
|
||||
match ByteBuffer.append_byte(out, '\n') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
i = i + 1;
|
||||
}
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
// 丢弃所有条目内容但保留缓冲区。
|
||||
fn clear(self: *Diag) {
|
||||
var i: usize = 0;
|
||||
while i < self.items.len {
|
||||
var it = item_at(self, i);
|
||||
if it != null {
|
||||
Str.drop(&it.fname);
|
||||
Str.drop(&it.message);
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
Vec.clear(&self.items);
|
||||
self.errors = 0;
|
||||
self.warnings = 0;
|
||||
}
|
||||
|
||||
fn drop(self: *Diag) {
|
||||
var i: usize = 0;
|
||||
while i < self.items.len {
|
||||
var it = item_at(self, i);
|
||||
if it != null {
|
||||
Str.drop(&it.fname);
|
||||
Str.drop(&it.message);
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
Vec.drop(&self.items);
|
||||
self.errors = 0;
|
||||
self.warnings = 0;
|
||||
}
|
||||
|
||||
// ---- 测试辅助 ----
|
||||
|
||||
fn expect_ok(result: Status) {
|
||||
match result {
|
||||
.Ok => {},
|
||||
.Err(error) => { @assert(false, "unexpected diag error"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_bytes(out: *ByteBuffer, expected: []u8) {
|
||||
var actual = ByteBuffer.as_slice(out.*);
|
||||
@assert(actual.len == expected.len, "render length");
|
||||
var i: usize = 0;
|
||||
while i < expected.len {
|
||||
@assert(actual.ptr[i] == expected.ptr[i], "render byte");
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn test_collect_and_count() {
|
||||
var d = new();
|
||||
@assert(count(d) == 0 && !has_error(d), "empty");
|
||||
|
||||
expect_ok(err(&d, "a.spl", 3, 5, "undefined identifier `x`"));
|
||||
expect_ok(warn(&d, "a.spl", 7, 1, "unused variable"));
|
||||
expect_ok(info(&d, "a.spl", 9, 2, "note"));
|
||||
expect_ok(err(&d, "", 1, 1, "fatal-ish"));
|
||||
|
||||
@assert(count(d) == 4, "count");
|
||||
@assert(error_count(d) == 2, "errors");
|
||||
@assert(warning_count(d) == 1, "warnings");
|
||||
@assert(has_error(d), "has_error");
|
||||
|
||||
var out = ByteBuffer.new();
|
||||
expect_ok(render(&d, &out));
|
||||
expect_bytes(&out, "a.spl:3:5: error: undefined identifier `x`\na.spl:7:1: warning: unused variable\na.spl:9:2: info: note\n1:1: error: fatal-ish\n");
|
||||
ByteBuffer.drop(&out);
|
||||
drop(&d);
|
||||
}
|
||||
|
||||
fn test_clear_and_reuse() {
|
||||
var d = new();
|
||||
expect_ok(err(&d, "f", 1, 1, "one"));
|
||||
expect_ok(warn(&d, "f", 2, 2, "two"));
|
||||
clear(&d);
|
||||
@assert(count(d) == 0 && !has_error(d) && warning_count(d) == 0, "cleared");
|
||||
|
||||
expect_ok(err(&d, "f", 3, 3, "three"));
|
||||
@assert(count(d) == 1 && error_count(d) == 1, "reuse");
|
||||
drop(&d);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_collect_and_count();
|
||||
test_clear_and_reuse();
|
||||
}
|
||||
Reference in new issue
Block a user