// Expect retcode = 0 // sys/file 之上的 I/O 便利层:C 字符串路径 open、精确读取/完整写入、整读、stdout/stderr 打印。 // 短读/短写零进展与关闭失败返回 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 { 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 { .handle = null }; if p != null && m != null { f = File.open(p, m); } ByteBuffer.drop(&pbuf); ByteBuffer.drop(&mbuf); ret f; } // 反复写入直到完成。发生错误时文件可能已经包含前缀,不承诺回滚。 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 .Ok; } fn print(bytes: []u8): Status { var out = File.stdout(); ret write_all(&out, bytes); } fn println(bytes: []u8): Status { match print(bytes) { .Ok => {}, .Err(error) => { ret .Err(error); }, } ret print("\n"); } fn eprint(bytes: []u8): Status { var err = File.stderr(); ret write_all(&err, bytes); } // 仅供已经隔离的临时存储使用;失败时可能写入目标前缀。 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); var fresh = ByteBuffer.new(); if sz != 0 { match ByteBuffer.reserve(&fresh, sz) { .Ok => {}, .Err(error) => { File.close(&f); ByteBuffer.drop(&fresh); ret .Err(error); }, } match read_fully(&f, fresh.storage.data, sz) { .Ok => {}, .Err(error) => { File.close(&f); ByteBuffer.drop(&fresh); ret .Err(error); }, } fresh.storage.len = sz; } if File.close(&f) != 0 { ByteBuffer.drop(&fresh); ret .Err(make(.Io)); } ByteBuffer.drop(out); out.* = ByteBuffer.take(&fresh); ret .Ok; } 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"); expect_ok(write_all(&f, "hello\nworld")); @assert(File.close(&f) == 0, "close writer"); 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"); 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"); expect_ok(write_all(&f, "cd")); @assert(File.close(&f) == 0, "close append writer"); 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"); } } }, } 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(); test_read_exact_and_errors(); expect_ok(println("io: ok")); }