This commit is contained in:
2026-07-17 18:53:34 +02:00
parent 97f1c06057
commit 866e28adb8
13 changed files with 468 additions and 215 deletions
+1 -1
View File
@@ -177,7 +177,7 @@ as `math.divfloor(a, b)` resolve to ordinary functions.
- root `std` re-exports `ArrayList(T)` while its operations remain in `std/arraylist` - root `std` re-exports `ArrayList(T)` while its operations remain in `std/arraylist`
- `std/mem` generic slice equality, allocator contract with raw byte operations, typed `empty` / `alloc` / `realloc` / `free`, overflow checks, zero-sized-type support, and failure-preserving reallocation - `std/mem` generic slice equality, allocator contract with raw byte operations, typed `empty` / `alloc` / `realloc` / `free`, overflow checks, zero-sized-type support, and failure-preserving reallocation
- `std/arraylist` generic `ArrayList(T)` with direct `items` slice access, explicit capacity, allocator ownership, fallible reserve/append, clear, and deinit - `std/arraylist` generic `ArrayList(T)` with direct `items` slice access, explicit capacity, allocator ownership, fallible reserve/append, clear, and deinit
- `std/io` explicit `Io` capabilities, `Reader`/`Writer` stream values, allocation-free `write_all`, and comptime-expanded writer-first `print`; formatting supports natural `{}`, byte `{s}`, decimal `{d}`, integer `{b}` / `{o}` / `{x}` / `{X}`, byte-character `{c}`, scientific float `{e}`, and `{{` / `}}`, with malformed formats and incompatible tuple fields rejected at comptime - `std/io` explicit `Io` capabilities, provider-bound `Reader`/`Writer` handles, existing-file open/close operations, allocation-free `write_all`, and comptime-expanded writer-first `print`; formatting supports natural `{}`, byte `{s}`, decimal `{d}`, integer `{b}` / `{o}` / `{x}` / `{X}`, byte-character `{c}`, scientific float `{e}`, and `{{` / `}}`, with malformed formats and incompatible tuple fields rejected at comptime
- entry points are either `main func() ...` or `main func(init process.Init) ...`; `std/process.Init` carries startup capabilities, currently only `io`, while the system provider remains hidden inside `std/io` - entry points are either `main func() ...` or `main func(init process.Init) ...`; `std/process.Init` carries startup capabilities, currently only `io`, while the system provider remains hidden inside `std/io`
- `std/debug.print` is an allocation-free, failure-ignoring stderr escape hatch independent of `process.Init` - `std/debug.print` is an allocation-free, failure-ignoring stderr escape hatch independent of `process.Init`
+3 -6
View File
@@ -9,18 +9,15 @@ odin build . -out:build/brolang
./build/prototype ./build/prototype
``` ```
Programs may receive the system I/O capability explicitly. Readers and writers Programs may receive the system I/O capability explicitly. Standard-stream
pair that implementation with a stream; `main func() ...` remains valid. helpers bind the provider, handle, and callback; `main func() ...` remains valid.
```bro ```bro
io :: import "@std/io" io :: import "@std/io"
process :: import "@std/process" process :: import "@std/process"
main func(init process.Init) void { main func(init process.Init) void {
io.print(io.Writer { io.print(io.stdout(init.io), "hello {s} {d}\n", {"bro", 37}) catch |_| {
impl = init.io,
stream = .stdout,
}, "hello {s} {d}\n", {"bro", 37}) catch |_| {
return return
} }
} }
+5 -3
View File
@@ -627,7 +627,7 @@
parameters parameters
25. dynamic heap allocation (implemented; v1) 25. dynamic heap allocation (implemented; v1)
- `std/mem` exposes a plain-data `Allocator` contract with `?*mut anyopaque` context and `alloc`, `realloc`, and `free` `@func` pointers - `std/mem` exposes a plain-data `Allocator` contract with `?@mut anyopaque` context and `alloc`, `realloc`, and `free` `@func` pointers
- `mem.c_allocator` is the libc-backed allocator; `mem.alloc(mem.c_allocator, size, alignment)` returns nullable mutable byte memory - `mem.c_allocator` is the libc-backed allocator; `mem.alloc(mem.c_allocator, size, alignment)` returns nullable mutable byte memory
- `mem.free(mem.c_allocator, ptr, size, alignment)` frees with the same allocator; `malloc` handles default-aligned requests and `posix_memalign` handles larger power-of-two alignments - `mem.free(mem.c_allocator, ptr, size, alignment)` frees with the same allocator; `malloc` handles default-aligned requests and `posix_memalign` handles larger power-of-two alignments
- `mem.realloc` preserves alignment and the original allocation on failure; zero size frees, and over-aligned blocks use allocate/copy/free - `mem.realloc` preserves alignment and the original allocation on failure; zero size frees, and over-aligned blocks use allocate/copy/free
@@ -811,9 +811,11 @@
33. explicit I/O provider (implemented) 33. explicit I/O provider (implemented)
- `main` may take one canonical `@std/process Init`; parameterless entry points remain valid - `main` may take one canonical `@std/process Init`; parameterless entry points remain valid
- the compiler supplies a `hide system` macOS provider and constructs `Init` in the external C wrapper - the compiler supplies a `hide system` macOS provider and constructs `Init` in the external C wrapper
- readers and writers pair an explicit provider with `stdin`, `stdout`, or `stderr` - readers and writers bind provider context, a generalized handle, and a direct callback
- standard-stream helpers and existing-file operations use the injected provider; files retain it
- `read` and `write` validate provider counts; `write_all` handles partial writes and no progress - `read` and `write` validate provider counts; `write_all` handles partial writes and no progress
- the system provider uses unbuffered libc `read`/`write`, retries interruption, and allocates nothing - the system provider uses unbuffered POSIX file descriptors, retries interrupted open/read/write,
and allocates nothing
34. package declaration aliases and root `std.ArrayList` (implemented) 34. package declaration aliases and root `std.ArrayList` (implemented)
- bare qualified aliases use `Name :: alias package.Member` without adding a keyword - bare qualified aliases use `Name :: alias package.Member` without adding a keyword
+116 -2
View File
@@ -2266,6 +2266,120 @@ milestone_33_injects_explicit_io_provider_and_runs_std_io :: proc(t: ^testing.T)
) )
} }
@(test)
std_io_opens_existing_files_through_the_captured_provider :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-file-io"
main_path := "/tmp/brolang-test-file-io/main.bro"
data_path := "/tmp/brolang-test-file-io/data"
output := "/tmp/brolang-test-file-io/app"
seed := "xxxxx"
text := `io :: import "@std/io"
process :: import "@std/process"
missing_fails func(system io.Io) bool {
_ = io.open(system, "/tmp/brolang-test-file-io/missing", .read_only) catch |err| {
return err == .open_failed
}
return false
}
wrong_read_fails func(file io.File) bool {
buffer [1]mut u8 = [0]
_ = io.read(io.reader(file), buffer[..]) catch |err| {
return err == .not_open_for_reading
}
return false
}
wrong_write_fails func(file io.File) bool {
_ = io.write(io.writer(file), "x") catch |err| {
return err == .not_open_for_writing
}
return false
}
main func(init process.Init) i32 {
if !missing_fails(init.io) {
return 1
}
read_file io.File :: io.open(init.io, "/tmp/brolang-test-file-io/data", .read_only) catch |_| {
return 2
}
buffer [5]mut u8 = [0, 0, 0, 0, 0]
count usize = io.read(io.reader(read_file), buffer[..]) catch |_| {
return 4
}
if count != 5 or buffer[0] != 'x' or !wrong_write_fails(read_file) {
return 5
}
io.close(read_file) catch |_| {
return 6
}
write_file io.File :: io.open(init.io, "/tmp/brolang-test-file-io/data", .write_only) catch |_| {
return 7
}
if !wrong_read_fails(write_file) {
return 8
}
io.write_all(io.writer(write_file), "bro") catch |_| {
return 9
}
io.close(write_file) catch |_| {
return 10
}
read_write_file io.File :: io.open(init.io, "/tmp/brolang-test-file-io/data", .read_write) catch |_| {
return 11
}
count = io.read(io.reader(read_write_file), buffer[..]) catch |_| {
return 12
}
if count != 5 or buffer[0] != 'b' or buffer[1] != 'r' or buffer[2] != 'o' {
return 13
}
io.write_all(io.writer(read_write_file), "!") catch |_| {
return 14
}
io.close(read_write_file) catch |_| {
return 15
}
return 0
}
`
_ = os2.remove_all(directory)
defer _ = os2.remove_all(directory)
testing.expect(t, os.make_directory(directory) == nil)
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
testing.expect(t, os.write_entire_file(data_path, transmute([]byte)seed))
status := compiler_core.compile_package(
directory,
output,
nil,
target.DEFAULT,
cimport.Options{},
".",
)
testing.expect_value(t, status, 0)
state, stdout, stderr, _ := os2.process_exec(
os2.Process_Desc{command=[]string{output}},
context.allocator,
)
defer delete(stdout)
defer delete(stderr)
testing.expect_value(t, state.exit_code, 0)
testing.expect_value(t, len(stdout), 0)
testing.expect_value(t, len(stderr), 0)
data, ok := os.read_entire_file(data_path)
defer delete(data)
testing.expect(t, ok)
testing.expect_value(t, string(data), "broxx!")
}
@(test) @(test)
milestone_33_rejects_non_io_and_extra_main_parameters :: proc(t: ^testing.T) { milestone_33_rejects_non_io_and_extra_main_parameters :: proc(t: ^testing.T) {
cases := [?]string{ cases := [?]string{
@@ -2549,7 +2663,7 @@ milestone_37_format_errors_are_reported_at_comptime :: proc(t: ^testing.T) {
process :: import "@std/process" process :: import "@std/process"
main func(init process.Init) void { main func(init process.Init) void {
writer io.Writer :: io.Writer {impl = init.io, stream = .stdout} writer io.Writer :: io.stdout(init.io)
stored :: typeinfo!(i32) stored :: typeinfo!(i32)
_ = stored _ = stored
io.print(writer, "", 1) catch |_| {} io.print(writer, "", 1) catch |_| {}
@@ -4582,7 +4696,7 @@ allocator_contract_c_allocator_global_lowers :: proc(t: ^testing.T) {
found_anyopaque_context = pointer_ok && found_anyopaque_context = pointer_ok &&
pointer_item.kind == .Pointer && pointer_item.kind == .Pointer &&
pointer_item.mutable && pointer_item.mutable &&
pointer_item.many && !pointer_item.many &&
pointer_item.child == types.ANYOPAQUE pointer_item.child == types.ANYOPAQUE
} }
} else if name == "vtable" { } else if name == "vtable" {
+3 -3
View File
@@ -2,15 +2,15 @@ arraylist :: import "@std/arraylist"
mem :: import "@std/mem" mem :: import "@std/mem"
std :: import "@std" std :: import "@std"
hide fail_alloc func(_ ?*mut anyopaque, _ usize, _ usize) ?*mut u8 { hide fail_alloc func(_ ?@mut anyopaque, _ usize, _ usize) ?*mut u8 {
return none return none
} }
hide fail_realloc func(_ ?*mut anyopaque, _ ?*mut u8, _ usize, _ usize, _ usize) ?*mut u8 { hide fail_realloc func(_ ?@mut anyopaque, _ ?*mut u8, _ usize, _ usize, _ usize) ?*mut u8 {
return none return none
} }
hide fail_free func(_ ?*mut anyopaque, _ ?*mut u8, _ usize, _ usize) void {} hide fail_free func(_ ?@mut anyopaque, _ ?*mut u8, _ usize, _ usize) void {}
hide fail_vtable mem.AllocatorVTable :: mem.AllocatorVTable { hide fail_vtable mem.AllocatorVTable :: mem.AllocatorVTable {
alloc = fail_alloc, alloc = fail_alloc,
+56 -53
View File
@@ -1,7 +1,7 @@
io :: import "@std/io" io :: import "@std/io"
process :: import "@std/process" process :: import "@std/process"
hide read_ok func(_ ?*mut anyopaque, _ io.ReadStream, buffer []mut u8) usize ! io.ReadError { hide read_ok func(_ ?@mut anyopaque, _ io.Handle, buffer []mut u8) usize ! io.ReadError {
if buffer.len == 0 { if buffer.len == 0 {
return 0 return 0
} }
@@ -13,85 +13,94 @@ hide read_ok func(_ ?*mut anyopaque, _ io.ReadStream, buffer []mut u8) usize ! i
return 2 return 2
} }
hide read_too_much func(_ ?*mut anyopaque, _ io.ReadStream, buffer []mut u8) usize ! io.ReadError { hide read_too_much func(_ ?@mut anyopaque, _ io.Handle, buffer []mut u8) usize ! io.ReadError {
return buffer.len + 1 return buffer.len + 1
} }
hide read_eof func(_ ?*mut anyopaque, _ io.ReadStream, _ []mut u8) usize ! io.ReadError { hide read_eof func(_ ?@mut anyopaque, _ io.Handle, _ []mut u8) usize ! io.ReadError {
return 0 return 0
} }
hide write_short func(_ ?*mut anyopaque, _ io.WriteStream, bytes []u8) usize ! io.WriteError { hide write_short func(_ ?@mut anyopaque, _ io.Handle, bytes []u8) usize ! io.WriteError {
if bytes.len > 2 { if bytes.len > 2 {
return 2 return 2
} }
return bytes.len return bytes.len
} }
hide write_none func(_ ?*mut anyopaque, _ io.WriteStream, _ []u8) usize ! io.WriteError { hide write_none func(_ ?@mut anyopaque, _ io.Handle, _ []u8) usize ! io.WriteError {
return 0 return 0
} }
hide write_too_much func(_ ?*mut anyopaque, _ io.WriteStream, bytes []u8) usize ! io.WriteError { hide write_too_much func(_ ?@mut anyopaque, _ io.Handle, bytes []u8) usize ! io.WriteError {
return bytes.len + 1 return bytes.len + 1
} }
hide ok_vtable io.IoVTable :: io.IoVTable { ok_reader func() io.Reader {
read = read_ok,
write = write_short,
}
hide read_bad_vtable io.IoVTable :: io.IoVTable {
read = read_too_much,
write = write_short,
}
hide eof_vtable io.IoVTable :: io.IoVTable {
read = read_eof,
write = write_short,
}
hide write_none_vtable io.IoVTable :: io.IoVTable {
read = read_ok,
write = write_none,
}
hide write_bad_vtable io.IoVTable :: io.IoVTable {
read = read_ok,
write = write_too_much,
}
reader_for func(vtable @io.IoVTable) io.Reader {
return io.Reader { return io.Reader {
impl = io.Io {context = none, vtable = vtable}, context = none,
stream = .stdin, handle = io.Handle {file_desc = 0},
read = read_ok,
} }
} }
writer_for func(vtable @io.IoVTable) io.Writer { bad_reader func() io.Reader {
return io.Reader {
context = none,
handle = io.Handle {file_desc = 0},
read = read_too_much,
}
}
eof_reader func() io.Reader {
return io.Reader {
context = none,
handle = io.Handle {file_desc = 0},
read = read_eof,
}
}
short_writer func() io.Writer {
return io.Writer { return io.Writer {
impl = io.Io {context = none, vtable = vtable}, context = none,
stream = .stdout, handle = io.Handle {file_desc = 0},
write = write_short,
}
}
none_writer func() io.Writer {
return io.Writer {
context = none,
handle = io.Handle {file_desc = 0},
write = write_none,
}
}
bad_writer func() io.Writer {
return io.Writer {
context = none,
handle = io.Handle {file_desc = 0},
write = write_too_much,
} }
} }
rejects_bad_read func() bool { rejects_bad_read func() bool {
buffer [1]mut u8 = [0] buffer [1]mut u8 = [0]
_ = io.read(reader_for(&read_bad_vtable), buffer[..]) catch |err| { _ = io.read(bad_reader(), buffer[..]) catch |err| {
return err == .read_failed return err == .read_failed
} }
return false return false
} }
rejects_no_progress func() bool { rejects_no_progress func() bool {
io.print(writer_for(&write_none_vtable), "{s}", {"x",}) catch |err| { io.print(none_writer(), "{s}", {"x",}) catch |err| {
return err == .no_progress return err == .no_progress
} }
return false return false
} }
rejects_bad_write func() bool { rejects_bad_write func() bool {
_ = io.write(writer_for(&write_bad_vtable), "x") catch |err| { _ = io.write(bad_writer(), "x") catch |err| {
return err == .write_failed return err == .write_failed
} }
return false return false
@@ -100,32 +109,26 @@ rejects_bad_write func() bool {
main func(init process.Init) i32 { main func(init process.Init) i32 {
system io.Io :: init.io system io.Io :: init.io
buffer [2]mut u8 = [0, 0] buffer [2]mut u8 = [0, 0]
count usize :: io.read(reader_for(&ok_vtable), buffer[..]) catch 0 count usize :: io.read(ok_reader(), buffer[..]) catch 0
if count != 2 or buffer[0] != 'o' or buffer[1] != 'k' { if count != 2 or buffer[0] != 'o' or buffer[1] != 'k' {
return 1 return 1
} }
eof usize :: io.read(reader_for(&eof_vtable), buffer[..]) catch 1 eof usize :: io.read(eof_reader(), buffer[..]) catch 1
empty_read usize :: io.read(reader_for(&read_bad_vtable), buffer[0..0]) catch 1 empty_read usize :: io.read(bad_reader(), buffer[0..0]) catch 1
empty_write usize :: io.write(writer_for(&write_bad_vtable), "") catch 1 empty_write usize :: io.write(bad_writer(), "") catch 1
if eof != 0 or empty_read != 0 or empty_write != 0 { if eof != 0 or empty_read != 0 or empty_write != 0 {
return 5 return 5
} }
io.print(writer_for(&ok_vtable), "{s}{d}", {"partial", 37}) catch |_| { io.print(short_writer(), "{s}{d}", {"partial", 37}) catch |_| {
return 2 return 2
} }
if !rejects_bad_read() or !rejects_no_progress() or !rejects_bad_write() { if !rejects_bad_read() or !rejects_no_progress() or !rejects_bad_write() {
return 3 return 3
} }
io.print(io.Writer { io.print(io.stdout(system), "io-ok {d} {{bro}}\n", {37,}) catch |_| {
impl = system,
stream = .stdout,
}, "io-ok {d} {{bro}}\n", {37,}) catch |_| {
return 4 return 4
} }
io.print(io.Writer { io.print(io.stdout(system), "bounds={d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d}\n", {
impl = system,
stream = .stdout,
}, "bounds={d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d}\n", {
minval!(i8), maxval!(u8), minval!(i8), maxval!(u8),
minval!(i16), maxval!(u16), minval!(i16), maxval!(u16),
minval!(i32), maxval!(u32), minval!(i32), maxval!(u32),
@@ -1,23 +1,23 @@
mem :: import "@std/mem" mem :: import "@std/mem"
hide probe_count func(context ?*mut anyopaque) void { hide probe_count func(context ?@mut anyopaque) void {
if context |raw| { if context |raw| {
counts *mut usize :: ptrcast!(usize, raw) count @mut usize :: ptrcast!(usize, raw)
counts[0] += 1 count^ += 1
} }
} }
hide probe_alloc func(context ?*mut anyopaque, _ usize, _ usize) ?*mut u8 { hide probe_alloc func(context ?@mut anyopaque, _ usize, _ usize) ?*mut u8 {
probe_count(context) probe_count(context)
return none return none
} }
hide probe_realloc func(context ?*mut anyopaque, _ ?*mut u8, _ usize, _ usize, _ usize) ?*mut u8 { hide probe_realloc func(context ?@mut anyopaque, _ ?*mut u8, _ usize, _ usize, _ usize) ?*mut u8 {
probe_count(context) probe_count(context)
return none return none
} }
hide probe_free func(context ?*mut anyopaque, _ ?*mut u8, _ usize, _ usize) void { hide probe_free func(context ?@mut anyopaque, _ ?*mut u8, _ usize, _ usize) void {
probe_count(context) probe_count(context)
} }
@@ -33,11 +33,11 @@ typed_allocator_test func() i32 {
first_calls [1]mut usize = [0] first_calls [1]mut usize = [0]
second_calls [1]mut usize = [0] second_calls [1]mut usize = [0]
first_allocator mem.Allocator :: mem.Allocator { first_allocator mem.Allocator :: mem.Allocator {
context = (&first_calls).ptr, context = &first_calls,
vtable = &probe_vtable, vtable = &probe_vtable,
} }
second_allocator mem.Allocator :: mem.Allocator { second_allocator mem.Allocator :: mem.Allocator {
context = (&second_calls).ptr, context = &second_calls,
vtable = &probe_vtable, vtable = &probe_vtable,
} }
+1 -1
View File
@@ -79,7 +79,7 @@ main func(init process.Init) i32 {
return 2 return 2
} }
writer io.Writer :: io.Writer {impl = init.io, stream = .stdout} writer io.Writer :: io.stdout(init.io)
positive f64 = 1.0 positive f64 = 1.0
zero f64 = 0.0 zero f64 = 0.0
infinity f64 :: positive / zero infinity f64 :: positive / zero
+13
View File
@@ -1,3 +1,16 @@
O_RDONLY c_int :: 0
O_WRONLY c_int :: 1
O_RDWR c_int :: 2
STDIN_FILENO c_int :: 0
STDOUT_FILENO c_int :: 1
STDERR_FILENO c_int :: 2
EINTR c_int :: 4
EBADF c_int :: 9
open c_func(_ *c_char, _ c_int, ...) c_int
close c_func(_ c_int) c_int
read c_func(_ c_int, _ ?*mut anyopaque, _ c_ulong) c_long read c_func(_ c_int, _ ?*mut anyopaque, _ c_ulong) c_long
write c_func(_ c_int, _ ?*anyopaque, _ c_ulong) c_long write c_func(_ c_int, _ ?*anyopaque, _ c_ulong) c_long
__error c_func() *mut c_int __error c_func() *mut c_int
+12 -20
View File
@@ -1,36 +1,28 @@
import "@ffi/c" import "@ffi/c"
import "@std/io" import "@std/io"
hide write func(_ ?*mut anyopaque, _ io.WriteStream, bytes []u8) usize ! io.WriteError { print func($format []u8, $Args type, args Args) void {
writer io.Writer :: io.Writer{
context = none,
handle = io.Handle{ file_desc = c_int(io.Stream.stderr) },
write = write,
}
io.print(writer, format, Args, args) catch |_| {}
}
hide write func(_ ?@mut anyopaque, handle io.Handle, bytes []u8) usize ! io.WriteError {
request usize = bytes.len request usize = bytes.len
maximum usize :: usize(maxval!(c_long)) maximum usize :: usize(maxval!(c_long))
if request > maximum { if request > maximum {
request = maximum request = maximum
} }
while true { while true {
count c_long :: c.write(2, bytes.ptr, c_ulong(request)) count c_long :: c.write(handle.file_desc, bytes.ptr, c_ulong(request))
if count >= 0 { if count >= 0 {
return usize(count) return usize(count)
} }
if c.__error()^ != 4 { if c.__error()^ != c.EINTR {
return .write_failed return .write_failed
} }
} }
} }
hide read func(_ ?*mut anyopaque, _ io.ReadStream, _ []mut u8) usize ! io.ReadError {
return .read_failed
}
hide vtable io.IoVTable :: io.IoVTable {
read = read,
write = write,
}
print func($format []u8, $Args type, args Args) void {
writer io.Writer :: io.Writer {
impl = io.Io {context = none, vtable = &vtable},
stream = .stderr,
}
io.print(writer, format, Args, args) catch |_| {}
}
+144
View File
@@ -0,0 +1,144 @@
import "@ffi/c"
File :: struct {
io Io
handle Handle
}
FileMode :: enum {
read_only
write_only
read_write
}
OpenError :: enum {
open_failed
}
CloseError :: enum {
close_failed
}
open func(io Io, path [;0]u8, mode FileMode) File ! OpenError {
handle Handle :: io.vtable.open(io.context, path, mode) catch |err| {
return err
}
return File {io = io, handle = handle}
}
close func(file File) void ! CloseError {
try file.io.vtable.close(file.io.context, file.handle)
}
reader func(file File) Reader {
return Reader {
context = file.io.context,
handle = file.handle,
read = file.io.vtable.read,
}
}
writer func(file File) Writer {
return Writer {
context = file.io.context,
handle = file.handle,
write = file.io.vtable.write,
}
}
hide system_read func(_ ?@mut anyopaque, handle Handle, buffer []mut u8) usize ! ReadError {
request usize = buffer.len
maximum usize :: usize(maxval!(c_long))
if request > maximum {
request = maximum
}
while true {
count c_long :: c.read(handle.file_desc, buffer.ptr, c_ulong(request))
if count >= 0 {
return usize(count)
}
errno c_int :: c.__error()^
if errno == c.EINTR {
continue
}
if errno == c.EBADF {
return .not_open_for_reading
}
return .read_failed
}
}
hide system_write func(_ ?@mut anyopaque, handle Handle, bytes []u8) usize ! WriteError {
request usize = bytes.len
maximum usize :: usize(maxval!(c_long))
if request > maximum {
request = maximum
}
while true {
count c_long :: c.write(handle.file_desc, bytes.ptr, c_ulong(request))
if count >= 0 {
return usize(count)
}
errno c_int :: c.__error()^
if errno == c.EINTR {
continue
}
if errno == c.EBADF {
return .not_open_for_writing
}
return .write_failed
}
}
hide system_open func(_ ?@mut anyopaque, path [;0]u8, mode FileMode) Handle ! OpenError {
flags c_int = c.O_RDONLY
match mode {
.read_only: flags = c.O_RDONLY
.write_only: flags = c.O_WRONLY
.read_write: flags = c.O_RDWR
}
while true {
fd c_int :: c.open(ptrcast!(c_char, path.ptr), flags)
if fd >= 0 {
return Handle {file_desc = fd}
}
if c.__error()^ != c.EINTR {
return .open_failed
}
}
}
hide system_close func(_ ?@mut anyopaque, handle Handle) void ! CloseError {
if c.close(handle.file_desc) != 0 {
return .close_failed
}
}
hide system_stdin func(_ ?@mut anyopaque) Handle {
return Handle {file_desc = c_int(Stream.stdin)}
}
hide system_stdout func(_ ?@mut anyopaque) Handle {
return Handle {file_desc = c_int(Stream.stdout)}
}
hide system_stderr func(_ ?@mut anyopaque) Handle {
return Handle {file_desc = c_int(Stream.stderr)}
}
hide system_vtable IoVTable :: IoVTable {
read = system_read,
write = system_write,
open = system_open,
close = system_close,
stdin = system_stdin,
stdout = system_stdout,
stderr = system_stderr,
}
hide system func() Io {
return Io {
context = none,
vtable = &system_vtable,
}
}
+99 -111
View File
@@ -2,69 +2,80 @@ import "@ffi/c"
import "@std/meta" import "@std/meta"
ReadError :: enum { ReadError :: enum {
not_open_for_reading
read_failed read_failed
} }
WriteError :: enum { WriteError :: enum {
not_open_for_writing
write_failed write_failed
no_progress no_progress
} }
Handle :: union {
file_desc c_int
ptr @mut anyopaque
}
Stream :: enum(c_int) {
stdin = c.STDIN_FILENO
stdout = c.STDOUT_FILENO
stderr = c.STDERR_FILENO
}
Io :: struct { Io :: struct {
context ?*mut anyopaque context ?@mut anyopaque
vtable @IoVTable vtable @IoVTable
} }
IoVTable :: struct { IoVTable :: struct {
read @func(context ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize ! ReadError read @func(context ?@mut anyopaque, handle Handle, buffer []mut u8) usize ! ReadError
write @func(context ?*mut anyopaque, stream WriteStream, bytes []u8) usize ! WriteError write @func(context ?@mut anyopaque, handle Handle, bytes []u8) usize ! WriteError
} open @func(context ?@mut anyopaque, path [;0]u8, mode FileMode) Handle ! OpenError
close @func(context ?@mut anyopaque, handle Handle) void ! CloseError
ReadStream :: enum(c_int) { stdin @func(context ?@mut anyopaque) Handle
stdin = 0 stdout @func(context ?@mut anyopaque) Handle
} stderr @func(context ?@mut anyopaque) Handle
WriteStream :: enum(c_int) {
stdout = 1
stderr = 2
} }
Reader :: struct { Reader :: struct {
impl Io context ?@mut anyopaque
stream ReadStream handle Handle
read @func(context ?@mut anyopaque, handle Handle, buffer []mut u8) usize ! ReadError
} }
Writer :: struct { Writer :: struct {
impl Io context ?@mut anyopaque
stream WriteStream handle Handle
write @func(context ?@mut anyopaque, handle Handle, bytes []u8) usize ! WriteError
} }
read func(reader Reader, buffer []mut u8) usize ! ReadError { read func(input Reader, buffer []mut u8) usize ! ReadError {
if buffer.len == 0 { if buffer.len == 0 {
return 0 return 0
} }
count usize :: try reader.impl.vtable.read(reader.impl.context, reader.stream, buffer) count usize :: try input.read(input.context, input.handle, buffer)
if count > buffer.len { if count > buffer.len {
return .read_failed return .read_failed
} }
return count return count
} }
write func(writer Writer, bytes []u8) usize ! WriteError { write func(output Writer, bytes []u8) usize ! WriteError {
if bytes.len == 0 { if bytes.len == 0 {
return 0 return 0
} }
count usize :: try writer.impl.vtable.write(writer.impl.context, writer.stream, bytes) count usize :: try output.write(output.context, output.handle, bytes)
if count > bytes.len { if count > bytes.len {
return .write_failed return .write_failed
} }
return count return count
} }
write_all func(writer Writer, bytes []u8) void ! WriteError { write_all func(output Writer, bytes []u8) void ! WriteError {
offset usize = 0 offset usize = 0
while offset < bytes.len { while offset < bytes.len {
count usize :: write(writer, bytes[offset..]) catch |err| { count usize :: write(output, bytes[offset..]) catch |err| {
return err return err
} }
if count == 0 { if count == 0 {
@@ -75,7 +86,49 @@ write_all func(writer Writer, bytes []u8) void ! WriteError {
return return
} }
hide write_integer_signed func(writer Writer, value i64, base u64, uppercase bool) void ! WriteError { stdin func(io Io) Reader {
return Reader {
context = io.context,
handle = io.vtable.stdin(io.context),
read = io.vtable.read,
}
}
stdout func(io Io) Writer {
return Writer {
context = io.context,
handle = io.vtable.stdout(io.context),
write = io.vtable.write,
}
}
stderr func(io Io) Writer {
return Writer {
context = io.context,
handle = io.vtable.stderr(io.context),
write = io.vtable.write,
}
}
print func(output Writer, $format []u8, $Args type, args Args) void ! WriteError {
expand for parse_format(format.len, format, Args) |token| {
match token.kind {
.unused: break
.literal: try write_all(output, format[token.start..token.end])
.string: try write_all(output, field!(args, token.field))
.default: try write_default(output, field!(args, token.field))
.decimal: try write_decimal(output, field!(args, token.field))
.binary: try write_integer(output, field!(args, token.field), 2, false)
.octal: try write_integer(output, field!(args, token.field), 8, false)
.hex_lower: try write_integer(output, field!(args, token.field), 16, false)
.hex_upper: try write_integer(output, field!(args, token.field), 16, true)
.character: try write_character(output, field!(args, token.field))
else: try write_float(output, field!(args, token.field), true)
}
}
}
hide write_integer_signed func(output Writer, value i64, base u64, uppercase bool) void ! WriteError {
buffer [65]mut u8 = undefined buffer [65]mut u8 = undefined
end usize = buffer.len end usize = buffer.len
current i64 = value current i64 = value
@@ -104,11 +157,11 @@ hide write_integer_signed func(writer Writer, value i64, base u64, uppercase boo
end -= 1 end -= 1
buffer[end] = '-' buffer[end] = '-'
} }
try write_all(writer, buffer[end..]) try write_all(output, buffer[end..])
return return
} }
hide write_integer_unsigned func(writer Writer, value u64, base u64, uppercase bool) void ! WriteError { hide write_integer_unsigned func(output Writer, value u64, base u64, uppercase bool) void ! WriteError {
buffer [65]mut u8 = undefined buffer [65]mut u8 = undefined
end usize = buffer.len end usize = buffer.len
current u64 = value current u64 = value
@@ -127,7 +180,7 @@ hide write_integer_unsigned func(writer Writer, value u64, base u64, uppercase b
break break
} }
} }
try write_all(writer, buffer[end..]) try write_all(output, buffer[end..])
return return
} }
@@ -264,12 +317,12 @@ hide format_field_name func($T type, index usize) []u8 {
} }
} }
hide write_integer func(writer Writer, $T type, value T, base u64, uppercase bool) void ! WriteError { hide write_integer func(output Writer, $T type, value T, base u64, uppercase bool) void ! WriteError {
match typeinfo!(T) { match typeinfo!(T) {
.integer: if minval!(T) < 0 { .integer: if minval!(T) < 0 {
try write_integer_signed(writer, i64(value), base, uppercase) try write_integer_signed(output, i64(value), base, uppercase)
} else { } else {
try write_integer_unsigned(writer, u64(value), base, uppercase) try write_integer_unsigned(output, u64(value), base, uppercase)
} }
else: compile_error!("io.print integer format requires an integer argument") else: compile_error!("io.print integer format requires an integer argument")
} }
@@ -277,7 +330,7 @@ hide write_integer func(writer Writer, $T type, value T, base u64, uppercase boo
} }
# note: libc keeps float formatting small; replace it with a native shortest-roundtrip writer if locale independence matters. # note: libc keeps float formatting small; replace it with a native shortest-roundtrip writer if locale independence matters.
hide write_float func(writer Writer, $T type, value T, scientific bool) void ! WriteError { hide write_float func(output Writer, $T type, value T, scientific bool) void ! WriteError {
match typeinfo!(T) { match typeinfo!(T) {
.float: { .float: {
buffer [64]mut u8 = undefined buffer [64]mut u8 = undefined
@@ -296,53 +349,53 @@ hide write_float func(writer Writer, $T type, value T, scientific bool) void ! W
if count < 0 or usize(count) >= buffer.len { if count < 0 or usize(count) >= buffer.len {
return .write_failed return .write_failed
} }
try write_all(writer, buffer[0..usize(count)]) try write_all(output, buffer[0..usize(count)])
} }
else: compile_error!("io.print float format requires a float argument") else: compile_error!("io.print float format requires a float argument")
} }
return return
} }
hide write_decimal func(writer Writer, $T type, value T) void ! WriteError { hide write_decimal func(output Writer, $T type, value T) void ! WriteError {
match typeinfo!(T) { match typeinfo!(T) {
.integer: try write_integer(writer, value, 10, false) .integer: try write_integer(output, value, 10, false)
.float: try write_float(writer, value, false) .float: try write_float(output, value, false)
else: compile_error!("io.print '{d}' requires an integer or float argument") else: compile_error!("io.print '{d}' requires an integer or float argument")
} }
return return
} }
hide write_character func(writer Writer, $T type, value T) void ! WriteError { hide write_character func(output Writer, $T type, value T) void ! WriteError {
match typeinfo!(T) { match typeinfo!(T) {
.integer: { .integer: {
if minval!(T) < 0 or maxval!(T) > 255 { if minval!(T) < 0 or maxval!(T) > 255 {
compile_error!("io.print '{c}' requires an unsigned integer that fits in u8") compile_error!("io.print '{c}' requires an unsigned integer that fits in u8")
} }
buffer [1]u8 = [u8(value)] buffer [1]u8 = [u8(value)]
try write_all(writer, buffer[..]) try write_all(output, buffer[..])
} }
else: compile_error!("io.print '{c}' requires an unsigned integer that fits in u8") else: compile_error!("io.print '{c}' requires an unsigned integer that fits in u8")
} }
return return
} }
hide write_default func(writer Writer, $T type, value T) void ! WriteError { hide write_default func(output Writer, $T type, value T) void ! WriteError {
match typeinfo!(T) { match typeinfo!(T) {
.bool: if value { .bool: if value {
try write_all(writer, "true") try write_all(output, "true")
} else { } else {
try write_all(writer, "false") try write_all(output, "false")
} }
.integer: try write_integer(writer, value, 10, false) .integer: try write_integer(output, value, 10, false)
.float: try write_float(writer, value, false) .float: try write_float(output, value, false)
.array: try write_all(writer, value) .array: try write_all(output, value)
.pointer: try write_all(writer, value) .pointer: try write_all(output, value)
.slice: try write_all(writer, value) .slice: try write_all(output, value)
.enum |enum_info|: { .enum |enum_info|: {
expand for enum_info.fields |field| { expand for enum_info.fields |field| {
if value == field!(T, field.name) { if value == field!(T, field.name) {
try write_all(writer, ".") try write_all(output, ".")
try write_all(writer, field.name) try write_all(output, field.name)
return return
} }
} }
@@ -352,68 +405,3 @@ hide write_default func(writer Writer, $T type, value T) void ! WriteError {
} }
return return
} }
print func(writer Writer, $format []u8, $Args type, args Args) void ! WriteError {
expand for parse_format(format.len, format, Args) |token| {
match token.kind {
.unused: break
.literal: try write_all(writer, format[token.start..token.end])
.string: try write_all(writer, field!(args, token.field))
.default: try write_default(writer, field!(args, token.field))
.decimal: try write_decimal(writer, field!(args, token.field))
.binary: try write_integer(writer, field!(args, token.field), 2, false)
.octal: try write_integer(writer, field!(args, token.field), 8, false)
.hex_lower: try write_integer(writer, field!(args, token.field), 16, false)
.hex_upper: try write_integer(writer, field!(args, token.field), 16, true)
.character: try write_character(writer, field!(args, token.field))
else: try write_float(writer, field!(args, token.field), true)
}
}
}
hide system_read func(_ ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize ! ReadError {
request usize = buffer.len
maximum usize :: usize(maxval!(c_long))
if request > maximum {
request = maximum
}
while true {
count c_long :: c.read(c_int(stream), buffer.ptr, c_ulong(request))
if count >= 0 {
return usize(count)
}
if c.__error()^ != 4 {
return .read_failed
}
}
}
hide system_write func(_ ?*mut anyopaque, stream WriteStream, bytes []u8) usize ! WriteError {
fd c_int :: c_int(stream)
request usize = bytes.len
maximum usize :: usize(maxval!(c_long))
if request > maximum {
request = maximum
}
while true {
count c_long :: c.write(fd, bytes.ptr, c_ulong(request))
if count >= 0 {
return usize(count)
}
if c.__error()^ != 4 {
return .write_failed
}
}
}
hide system_vtable IoVTable :: IoVTable {
read = system_read,
write = system_write,
}
hide system func() Io {
return Io {
context = none,
vtable = &system_vtable,
}
}
+7 -7
View File
@@ -5,14 +5,14 @@ AllocError :: enum {
} }
Allocator :: struct { Allocator :: struct {
context ?*mut anyopaque context ?@mut anyopaque
vtable @AllocatorVTable vtable @AllocatorVTable
} }
AllocatorVTable :: struct { AllocatorVTable :: struct {
alloc @func(context ?*mut anyopaque, size usize, alignment usize) ?*mut u8 alloc @func(context ?@mut anyopaque, size usize, alignment usize) ?*mut u8
realloc @func(context ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 realloc @func(context ?@mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8
free @func(context ?*mut anyopaque, memory ?*mut u8, size usize, alignment usize) void free @func(context ?@mut anyopaque, memory ?*mut u8, size usize, alignment usize) void
} }
raw_alloc func(allocator Allocator, size usize, alignment usize) ?*mut u8 { raw_alloc func(allocator Allocator, size usize, alignment usize) ?*mut u8 {
@@ -135,7 +135,7 @@ hide power_of_two func(value usize) bool {
return true return true
} }
hide c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 { hide c_alloc func(_ ?@mut anyopaque, size usize, alignment usize) ?*mut u8 {
if power_of_two(alignment) == false { if power_of_two(alignment) == false {
return none return none
} }
@@ -153,7 +153,7 @@ hide c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
return ptrcast!(u8, memory[0]) return ptrcast!(u8, memory[0])
} }
hide c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 { hide c_realloc func(_ ?@mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
if power_of_two(alignment) == false { if power_of_two(alignment) == false {
return none return none
} }
@@ -186,7 +186,7 @@ hide c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size
return c_alloc(none, new_size, alignment) return c_alloc(none, new_size, alignment)
} }
hide c_free func(_ ?*mut anyopaque, memory ?*mut u8, _ usize, _ usize) void { hide c_free func(_ ?@mut anyopaque, memory ?*mut u8, _ usize, _ usize) void {
c.free(memory) c.free(memory)
} }