153 lines
4.3 KiB
Plaintext
153 lines
4.3 KiB
Plaintext
// 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");
|
||
}
|