stage2 更多的工具库
This commit is contained in:
1 parent
b3e38ba7e4
commit
c30143609a
8 files changed
+2711
-1138
No files matched your search
+129
-38
@@ -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"));
|
||||
}
|
||||
Reference in new issue
Block a user