Files
spl/stage2/utils/io.spl
T

154 lines
4.3 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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");
}