stage2 更多的工具库

This commit is contained in:
zzy committed 2026-09-17 22:32:36 +08:00
1 parent 4141e7d574
commit 16a1f030d4
15 files changed
+1914 -109

No files matched your search

+243
View File
@@ -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();
}