// Expect retcode = 0 // I/O 便利层(构建在 sys/file 之上): // - C 字符串路径/模式的 open 包装 // - 整块读写(write_all / read_all) // - stdout/stderr 打印 // 说明:read_all 会按文件大小一次性读入 ByteBuffer;不处理增长中的文件。 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"); // 把 view 复制到以 NUL 结尾的临时缓冲,返回其 data(归属 tmp,调用后勿再用)。 fn cstr(tmp: *ByteBuffer, view: ByteView): *u8 { ByteBuffer.clear(tmp); if view.len != 0 { match ByteBuffer.append(tmp, ByteView.as_slice(view)) { .Ok => {}, .Err(error) => { ret null; }, } } match ByteBuffer.append_byte(tmp, '\0') { .Ok => {}, .Err(error) => { ret null; }, } ret tmp.storage.data; } fn open_cstr(path: ByteView, mode: ByteView): File { var pbuf = ByteBuffer.new(); var mbuf = ByteBuffer.new(); var p = cstr(&pbuf, path); var m = cstr(&mbuf, mode); var 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; } ret total; } fn print(bytes: []u8) { var out = File.stdout(); write_all(&out, bytes); } fn println(bytes: []u8) { print(bytes); print("\n"); } fn eprint(bytes: []u8) { var err = File.stderr(); write_all(&err, bytes); } // 读取整个文件到 out(先清空)。打开失败或不完整返回 Err(.Io)。 fn read_all(path: ByteView, out: *ByteBuffer): Status { 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); if sz != 0 { match ByteBuffer.reserve(out, sz as usize) { .Ok => {}, .Err(error) => { File.close(&f); 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; } File.close(&f); ret .Ok; } // ---- 测试辅助 ---- fn view(text: []u8): ByteView { ret ByteView.from_slice(text); } 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); var out = ByteBuffer.new(); match read_all(path, &out) { .Ok => {}, .Err(error) => { @assert(false, "read_all"); }, } var got = ByteBuffer.as_slice(out); @assert(got.len == 11, "roundtrip length"); @assert(got.ptr[0] == 'h' && got.ptr[5] == '\n' && got.ptr[10] == 'd', "roundtrip bytes"); ByteBuffer.drop(&out); } fn test_append_and_missing() { var path = view("build/_io_append.tmp"); var f = open_cstr(path, view("wb")); @assert(File.valid(&f), "open wb"); write_all(&f, "ab"); File.close(&f); 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"); var out = ByteBuffer.new(); match read_all(path, &out) { .Ok => {}, .Err(error) => { @assert(false, "read append"); }, } var got = ByteBuffer.as_slice(out); @assert(got.len == 4 && got.ptr[2] == 'c' && got.ptr[3] == 'd', "append content"); // 不存在的文件 match read_all(view("build/_io_no_such_file.tmp"), &out) { .Ok => { @assert(false, "missing should fail"); }, .Err(error) => { match error.kind { .Io => {}, _ => { @assert(false, "error kind"); } } }, } ByteBuffer.drop(&out); } fn main() { test_roundtrip(); test_append_and_missing(); println("io: ok"); }