stage2 更多的工具库

This commit is contained in:
zzy committed 2026-09-18 11:17:33 +08:00
1 parent b3e38ba7e4
commit c30143609a
8 files changed
+2711 -1138

No files matched your search

+129 -38
View File
@@ -2,14 +2,16 @@
// I/O 便利层(构建在 sys/file 之上):
// - C 字符串路径/模式的 open 包装
// - 整块读写(write_all / read_all)
// - 精确读取、完整写入和普通文件整读
// - stdout/stderr 打印
// 说明:read_all 会按文件大小一次性读入 ByteBuffer;不处理增长中的文件。
// 短读、短写后的零进展及关闭失败返回 Err(.Io);文件写入不承诺回滚。
type .{ .Error, .Status, .make } = @import("error.spl");
type .{ .ByteView } = @import("byte_view.spl");
type .{ .ByteBuffer } = @import("byte_buffer.spl");
type File = @import("../sys/file.spl");
type Mem = @import("../sys/mem.spl");
// 把 view 复制到以 NUL 结尾的临时缓冲,返回其 data(归属 tmp,调用后勿再用)。
fn cstr(tmp: *ByteBuffer, view: ByteView): *u8 {
@@ -32,59 +34,99 @@ fn open_cstr(path: ByteView, mode: ByteView): File {
var mbuf = ByteBuffer.new();
var p = cstr(&pbuf, path);
var m = cstr(&mbuf, mode);
var f = File.open(p, m);
var f = File { .handle = null };
if p != null && m != null { f = File.open(p, m); }
ByteBuffer.drop(&pbuf);
ByteBuffer.drop(&mbuf);
ret f;
}
// 反复写入直到写完;返回实际写入字节数(!= bytes.len 表示中途失败)。
fn write_all(f: *File, bytes: []u8): u64 {
var total: u64 = 0;
var want = bytes.len as u64;
while total < want {
var offset = total as usize;
var w = File.write(f, bytes.ptr + offset, want - total);
if w == 0 { break; }
total = total + w;
// 反复写入直到完成。发生错误时文件可能已经包含前缀,不承诺回滚。
fn write_all(f: *File, bytes: []u8): Status {
if f == null || !File.valid(f) { ret .Err(make(.Io)); }
if bytes.len != 0 && bytes.ptr == null { ret .Err(make(.InvalidArgument)); }
var total: usize = 0;
while total < bytes.len {
var written = File.write(f, bytes.ptr + total, (bytes.len - total) as u64);
if written == 0 || written > (bytes.len - total) as u64 {
ret .Err(make(.Io));
}
total = total + written as usize;
}
ret total;
ret .Ok;
}
fn print(bytes: []u8) {
fn print(bytes: []u8): Status {
var out = File.stdout();
write_all(&out, bytes);
ret write_all(&out, bytes);
}
fn println(bytes: []u8) {
print(bytes);
print("\n");
fn println(bytes: []u8): Status {
match print(bytes) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
ret print("\n");
}
fn eprint(bytes: []u8) {
fn eprint(bytes: []u8): Status {
var err = File.stderr();
write_all(&err, bytes);
ret write_all(&err, bytes);
}
// 读取整个文件到 out(先清空)。打开失败或不完整返回 Err(.Io)。
// 仅供已经隔离的临时存储使用;失败时可能写入目标前缀。
fn read_fully(f: *File, data: *u8, len: usize): Status {
var total: usize = 0;
while total < len {
var got = File.read(f, data + total, len - total);
if got == 0 || got > len - total { ret .Err(make(.Io)); }
total = total + got;
}
ret .Ok;
}
// 精确读取 bytes.len 字节。短读返回 Err(.Io),失败保持 bytes 原内容不变。
fn read_exact(f: *File, bytes: []u8): Status {
if f == null || !File.valid(f) { ret .Err(make(.Io)); }
if bytes.len == 0 { ret .Ok; }
if bytes.ptr == null { ret .Err(make(.InvalidArgument)); }
var temporary = ByteBuffer.new();
defer { ByteBuffer.drop(&temporary); }
match ByteBuffer.reserve(&temporary, bytes.len) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
match read_fully(f, temporary.storage.data, bytes.len) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
Mem.copy(bytes.ptr as *any, temporary.storage.data as *any, bytes.len);
ret .Ok;
}
// 读取整个普通文件到 out。打开、短读或关闭失败时保持 out 不变。
fn read_all(path: ByteView, out: *ByteBuffer): Status {
if out == null { ret .Err(make(.InvalidArgument)); }
var f = open_cstr(path, ByteView.from_slice("rb"));
if !File.valid(&f) { ret .Err(make(.Io)); }
var sz = File.size(&f);
ByteBuffer.clear(out);
var fresh = ByteBuffer.new();
if sz != 0 {
match ByteBuffer.reserve(out, sz as usize) {
match ByteBuffer.reserve(&fresh, sz) {
.Ok => {},
.Err(error) => { File.close(&f); ret .Err(error); },
.Err(error) => { File.close(&f); ByteBuffer.drop(&fresh); ret .Err(error); },
}
var got = File.read(&f, out.storage.data, sz);
out.storage.len = got as usize;
File.close(&f);
if got != sz { ret .Err(make(.Io)); }
ret .Ok;
match read_fully(&f, fresh.storage.data, sz) {
.Ok => {},
.Err(error) => { File.close(&f); ByteBuffer.drop(&fresh); ret .Err(error); },
}
fresh.storage.len = sz;
}
File.close(&f);
if File.close(&f) != 0 { ByteBuffer.drop(&fresh); ret .Err(make(.Io)); }
ByteBuffer.drop(out);
out.* = ByteBuffer.take(&fresh);
ret .Ok;
}
@@ -94,14 +136,32 @@ fn view(text: []u8): ByteView {
ret ByteView.from_slice(text);
}
fn expect_ok(result: Status) {
match result {
.Ok => {},
.Err(error) => { @assert(false, "unexpected io error"); },
}
}
fn expect_io(result: Status) {
match result {
.Ok => { @assert(false, "expected io error"); },
.Err(error) => {
match error.kind {
.Io => {},
_ => { @assert(false, "expected Io kind"); },
}
},
}
}
fn test_roundtrip() {
var path = view("build/_io_roundtrip.tmp");
var f = open_cstr(path, view("wb"));
@assert(File.valid(&f), "open wb");
var w = write_all(&f, "hello\nworld");
@assert(w == 11, "write_all length");
File.close(&f);
expect_ok(write_all(&f, "hello\nworld"));
@assert(File.close(&f) == 0, "close writer");
var out = ByteBuffer.new();
match read_all(path, &out) {
@@ -119,14 +179,13 @@ fn test_append_and_missing() {
var f = open_cstr(path, view("wb"));
@assert(File.valid(&f), "open wb");
write_all(&f, "ab");
File.close(&f);
expect_ok(write_all(&f, "ab"));
@assert(File.close(&f) == 0, "close initial writer");
f = open_cstr(path, view("ab"));
@assert(File.valid(&f), "open ab");
var w2 = write_all(&f, "cd");
File.close(&f);
@assert(w2 == 2, "append length");
expect_ok(write_all(&f, "cd"));
@assert(File.close(&f) == 0, "close append writer");
var out = ByteBuffer.new();
match read_all(path, &out) {
@@ -143,11 +202,43 @@ fn test_append_and_missing() {
match error.kind { .Io => {}, _ => { @assert(false, "error kind"); } }
},
}
got = ByteBuffer.as_slice(out);
@assert(got.len == 4 && got.ptr[0] == 'a' && got.ptr[3] == 'd',
"failed read_all preserves output");
ByteBuffer.drop(&out);
}
fn test_read_exact_and_errors() {
var path = view("build/_io_exact.tmp");
var f = open_cstr(path, view("wb"));
@assert(File.valid(&f));
expect_ok(write_all(&f, "xyz"));
@assert(File.close(&f) == 0);
f = open_cstr(path, view("rb"));
@assert(File.valid(&f));
var too_long: [4]u8 = .[9, 8, 7, 6];
expect_io(read_exact(&f, too_long[0..4]));
@assert(too_long[0] == 9 && too_long[1] == 8 &&
too_long[2] == 7 && too_long[3] == 6,
"short read preserves destination");
@assert(File.close(&f) == 0);
f = open_cstr(path, view("rb"));
@assert(File.valid(&f));
var exact: [3]u8 = .[0, 0, 0];
expect_ok(read_exact(&f, exact[0..3]));
@assert(exact[0] == 'x' && exact[1] == 'y' && exact[2] == 'z');
@assert(File.close(&f) == 0);
var invalid = File { .handle = null };
expect_io(write_all(&invalid, "x"));
expect_io(read_exact(&invalid, exact[0..3]));
}
fn main() {
test_roundtrip();
test_append_and_missing();
println("io: ok");
test_read_exact_and_errors();
expect_ok(println("io: ok"));
}