Add package io

This commit is contained in:
gingerBill
2020-12-02 23:12:14 +00:00
parent 4d30b88927
commit d9343ae997
4 changed files with 851 additions and 0 deletions

185
core/io/conv.odin Normal file
View File

@@ -0,0 +1,185 @@
package io
Conversion_Error :: enum {
None,
Missing_Procedure,
Fallback_Possible,
}
to_reader :: proc(s: Stream) -> (r: Reader, err: Conversion_Error) {
r.stream = s;
if s.vtable == nil || s.impl_read == nil {
err = .Missing_Procedure;
}
return;
}
to_writer :: proc(s: Stream) -> (w: Writer, err: Conversion_Error) {
w.stream = s;
if s.vtable == nil || s.impl_write == nil {
err = .Missing_Procedure;
}
return;
}
to_closer :: proc(s: Stream) -> (c: Closer, err: Conversion_Error) {
c.stream = s;
if s.vtable == nil || s.impl_close == nil {
err = .Missing_Procedure;
}
return;
}
to_flusher :: proc(s: Stream) -> (f: Flusher, err: Conversion_Error) {
f.stream = s;
if s.vtable == nil || s.impl_flush == nil {
err = .Missing_Procedure;
}
return;
}
to_seeker :: proc(s: Stream) -> (seeker: Seeker, err: Conversion_Error) {
seeker.stream = s;
if s.vtable == nil || s.impl_seek == nil {
err = .Missing_Procedure;
}
return;
}
to_read_writer :: proc(s: Stream) -> (r: Read_Writer, err: Conversion_Error) {
r.stream = s;
if s.vtable == nil || s.impl_read == nil || s.impl_write == nil {
err = .Missing_Procedure;
}
return;
}
to_read_closer :: proc(s: Stream) -> (r: Read_Closer, err: Conversion_Error) {
r.stream = s;
if s.vtable == nil || s.impl_read == nil || s.impl_close == nil {
err = .Missing_Procedure;
}
return;
}
to_read_write_closer :: proc(s: Stream) -> (r: Read_Write_Closer, err: Conversion_Error) {
r.stream = s;
if s.vtable == nil || s.impl_read == nil || s.impl_write == nil || s.impl_close == nil {
err = .Missing_Procedure;
}
return;
}
to_read_write_seeker :: proc(s: Stream) -> (r: Read_Write_Seeker, err: Conversion_Error) {
r.stream = s;
if s.vtable == nil || s.impl_read == nil || s.impl_write == nil || s.impl_seek == nil {
err = .Missing_Procedure;
}
return;
}
to_write_flusher :: proc(s: Stream) -> (w: Write_Flusher, err: Conversion_Error) {
w.stream = s;
if s.vtable == nil || s.impl_write == nil || s.impl_flush == nil {
err = .Missing_Procedure;
}
return;
}
to_write_flush_closer :: proc(s: Stream) -> (w: Write_Flush_Closer, err: Conversion_Error) {
w.stream = s;
if s.vtable == nil || s.impl_write == nil || s.impl_flush == nil || s.impl_close == nil {
err = .Missing_Procedure;
}
return;
}
to_reader_at :: proc(s: Stream) -> (r: Reader_At, err: Conversion_Error) {
r.stream = s;
if s.vtable == nil || s.impl_read_at == nil {
err = .Missing_Procedure;
}
return;
}
to_writer_at :: proc(s: Stream) -> (w: Writer_At, err: Conversion_Error) {
w.stream = s;
if s.vtable == nil || s.impl_write_at == nil {
err = .Missing_Procedure;
}
return;
}
to_reader_from :: proc(s: Stream) -> (r: Reader_From, err: Conversion_Error) {
r.stream = s;
if s.vtable == nil || s.impl_read_from == nil {
err = .Missing_Procedure;
}
return;
}
to_writer_to :: proc(s: Stream) -> (w: Writer_To, err: Conversion_Error) {
w.stream = s;
if s.vtable == nil || s.impl_write_to == nil {
err = .Missing_Procedure;
}
return;
}
to_byte_reader :: proc(s: Stream) -> (b: Byte_Reader, err: Conversion_Error) {
b.stream = s;
if s.vtable == nil || s.impl_read_byte == nil {
err = .Missing_Procedure;
if s.vtable != nil && s.impl_read != nil {
err = .Fallback_Possible;
}
}
return;
}
to_byte_scanner :: proc(s: Stream) -> (b: Byte_Scanner, err: Conversion_Error) {
b.stream = s;
if s.vtable != nil {
if s.impl_unread_byte == nil {
err = .Missing_Procedure;
return;
}
if s.impl_read_byte != nil {
err = .None;
} else if s.impl_read != nil {
err = .Fallback_Possible;
} else {
err = .Missing_Procedure;
}
}
return;
}
to_byte_writer :: proc(s: Stream) -> (b: Byte_Writer, err: Conversion_Error) {
b.stream = s;
if s.vtable == nil || s.impl_write_byte == nil {
err = .Missing_Procedure;
if s.vtable != nil && s.impl_write != nil {
err = .Fallback_Possible;
}
}
return;
}
to_rune_reader :: proc(s: Stream) -> (r: Rune_Reader, err: Conversion_Error) {
r.stream = s;
if s.vtable == nil || s.impl_read_rune == nil {
err = .Missing_Procedure;
if s.vtable != nil && s.impl_read != nil {
err = .Fallback_Possible;
}
}
return;
}
to_rune_scanner :: proc(s: Stream) -> (r: Rune_Scanner, err: Conversion_Error) {
r.stream = s;
if s.vtable != nil {
if s.impl_unread_rune == nil {
err = .Missing_Procedure;
return;
}
if s.impl_read_rune != nil {
err = .None;
} else if s.impl_read != nil {
err = .Fallback_Possible;
} else {
err = .Missing_Procedure;
}
} else {
err = .Missing_Procedure;
}
return;
}

461
core/io/io.odin Normal file
View File

@@ -0,0 +1,461 @@
package io
import "intrinsics"
import "core:runtime"
import "core:unicode/utf8"
Seek_From :: enum {
Start = 0, // seek relative to the origin of the file
Current = 1, // seek relative to the current offset
End = 2, // seek relative to the end
}
Error :: enum i32 {
// No Error
None = 0,
// EOF is the error returned by `read` when no more input is available
EOF,
// Unexpected_EOF means that EOF was encountered in the middle of reading a fixed-sized block of data
Unexpected_EOF,
// Short_Write means that a write accepted fewer bytes than requested but failed to return an explicit error
Short_Write,
// Short_Buffer means that a read required a longer buffer than was provided
Short_Buffer,
// No_Progress is returned by some implementations of `io.Reader` when many calls
// to `read` have failed to return any data or error.
// This is usually a signed of a broken `io.Reader` implementation
No_Progress,
Invalid_Whence,
Invalid_Offset,
Invalid_Unread,
// Empty is returned when a procedure has not been implemented for an io.Stream
Empty = -1,
}
Close_Proc :: distinct proc(using s: Stream) -> Error;
Flush_Proc :: distinct proc(using s: Stream) -> Error;
Seek_Proc :: distinct proc(using s: Stream, offset: i64, whence: Seek_From) -> (i64, Error);
Size_Proc :: distinct proc(using s: Stream) -> i64;
Read_Proc :: distinct proc(using s: Stream, p: []byte) -> (n: int, err: Error);
Read_At_Proc :: distinct proc(using s: Stream, p: []byte, off: i64) -> (n: int, err: Error);
Read_From_Proc :: distinct proc(using s: Stream, r: Reader) -> (n: i64, err: Error);
Read_Byte_Proc :: distinct proc(using s: Stream) -> (byte, Error);
Read_Rune_Proc :: distinct proc(using s: Stream) -> (ch: rune, size: int, err: Error);
Unread_Byte_Proc :: distinct proc(using s: Stream) -> Error;
Unread_Rune_Proc :: distinct proc(using s: Stream) -> Error;
Write_Proc :: distinct proc(using s: Stream, p: []byte) -> (n: int, err: Error);
Write_At_Proc :: distinct proc(using s: Stream, p: []byte, off: i64) -> (n: int, err: Error);
Write_To_Proc :: distinct proc(using s: Stream, w: Writer) -> (n: i64, err: Error);
Write_Byte_Proc :: distinct proc(using s: Stream, c: byte) -> Error;
Destroy_Proc :: distinct proc(using s: Stream) -> Error;
Stream :: struct {
using vtable: ^Stream_VTable,
data: rawptr,
}
Stream_VTable :: struct {
impl_close: Close_Proc,
impl_flush: Flush_Proc,
impl_seek: Seek_Proc,
impl_size: Size_Proc,
impl_read: Read_Proc,
impl_read_at: Read_At_Proc,
impl_read_byte: Read_Byte_Proc,
impl_read_rune: Read_Rune_Proc,
impl_write_to: Write_To_Proc,
impl_write: Write_Proc,
impl_write_at: Write_At_Proc,
impl_write_byte: Write_Byte_Proc,
impl_read_from: Read_From_Proc,
impl_unread_byte: Unread_Byte_Proc,
impl_unread_rune: Unread_Rune_Proc,
impl_destroy: Destroy_Proc,
}
Reader :: struct {using stream: Stream};
Writer :: struct {using stream: Stream};
Closer :: struct {using stream: Stream};
Flusher :: struct {using stream: Stream};
Seeker :: struct {using stream: Stream};
Read_Writer :: struct {using stream: Stream};
Read_Closer :: struct {using stream: Stream};
Read_Write_Closer :: struct {using stream: Stream};
Read_Write_Seeker :: struct {using stream: Stream};
Write_Flusher :: struct {using stream: Stream};
Write_Flush_Closer :: struct {using stream: Stream};
Reader_At :: struct {using stream: Stream};
Writer_At :: struct {using stream: Stream};
Reader_From :: struct {using stream: Stream};
Writer_To :: struct {using stream: Stream};
Byte_Reader :: struct {using stream: Stream};
Byte_Scanner :: struct {using stream: Stream};
Byte_Writer :: struct {using stream: Stream};
Rune_Reader :: struct {using stream: Stream};
Rune_Scanner :: struct {using stream: Stream};
destroy :: proc(s: Stream) -> Error {
if s.vtable != nil && s.impl_destroy != nil {
return s->impl_destroy();
}
// Instead of .Empty, .None is fine in this case
return .None;
}
read :: proc(s: Reader, p: []byte) -> (n: int, err: Error) {
if s.vtable != nil && s.impl_read != nil {
return s->impl_read(p);
}
return 0, .Empty;
}
write :: proc(s: Writer, p: []byte) -> (n: int, err: Error) {
if s.vtable != nil && s.impl_write != nil {
return s->impl_write(p);
}
return 0, .Empty;
}
seek :: proc(s: Seeker, offset: i64, whence: Seek_From) -> (n: i64, err: Error) {
if s.vtable != nil && s.impl_seek != nil {
return s->impl_seek(offset, whence);
}
return 0, .Empty;
}
close :: proc(s: Closer, p: []byte) -> Error {
if s.vtable != nil && s.impl_close != nil {
return s->impl_close();
}
// Instead of .Empty, .None is fine in this case
return .None;
}
flush :: proc(s: Flusher, p: []byte) -> Error {
if s.vtable != nil && s.impl_flush != nil {
return s->impl_flush();
}
// Instead of .Empty, .None is fine in this case
return .None;
}
size :: proc(s: Stream, p: []byte) -> i64 {
if s.vtable == nil {
return 0;
}
if s.impl_size != nil {
return s->impl_size();
}
if s.impl_seek == nil {
return 0;
}
curr, end: i64;
err: Error;
if curr, err = s->impl_seek(0, .Current); err != nil {
return 0;
}
if end, err = s->impl_seek(0, .End); err != nil {
return 0;
}
if _, err = s->impl_seek(curr, .Start); err != nil {
return 0;
}
return end;
}
read_at :: proc(r: Reader_At, p: []byte, offset: i64) -> (n: int, err: Error) {
if r.vtable == nil {
return 0, .Empty;
}
if r.impl_read_at != nil {
return r->impl_read_at(p, offset);
}
if r.impl_seek == nil || r.impl_read == nil {
return 0, .Empty;
}
current_offset: i64;
current_offset, err = r->impl_seek(offset, .Current);
if err != nil {
return 0, err;
}
defer r->impl_seek(current_offset, .Start);
return r->impl_read(p);
}
write_at :: proc(w: Writer_At, p: []byte, offset: i64) -> (n: int, err: Error) {
if w.vtable == nil {
return 0, .Empty;
}
if w.impl_write_at != nil {
return w->impl_write_at(p, offset);
}
if w.impl_seek == nil || w.impl_write == nil {
return 0, .Empty;
}
current_offset: i64;
current_offset, err = w->impl_seek(offset, .Current);
if err != nil {
return 0, err;
}
defer w->impl_seek(current_offset, .Start);
return w->impl_write(p);
}
write_to :: proc(r: Reader, w: Writer) -> (n: i64, err: Error) {
if r.vtable == nil || w.vtable == nil {
return 0, .Empty;
}
if r.impl_write_to != nil {
return r->impl_write_to(w);
}
return 0, .Empty;
}
read_from :: proc(w: Writer, r: Reader) -> (n: i64, err: Error) {
if r.vtable == nil || w.vtable == nil {
return 0, .Empty;
}
if r.impl_read_from != nil {
return w->impl_read_from(r);
}
return 0, .Empty;
}
read_byte :: proc(r: Byte_Reader) -> (byte, Error) {
if r.vtable == nil {
return 0, .Empty;
}
if r.impl_read_byte != nil {
return r->impl_read_byte();
}
if r.impl_read == nil {
return 0, .Empty;
}
b: [1]byte;
_, err := r->impl_read(b[:]);
return b[0], err;
}
write_byte :: proc(w: Byte_Writer, c: byte) -> Error {
if w.vtable == nil {
return .Empty;
}
if w.impl_write_byte != nil {
return w->impl_write_byte(c);
}
if w.impl_write == nil {
return .Empty;
}
b := [1]byte{c};
_, err := w->impl_write(b[:]);
return err;
}
read_rune :: proc(br: Rune_Reader) -> (ch: rune, size: int, err: Error) {
if br.vtable == nil {
return 0, 0, .Empty;
}
if br.impl_read_rune != nil {
return br->impl_read_rune();
}
if br.impl_read == nil {
return 0, 0, .Empty;
}
b: [utf8.UTF_MAX]byte;
_, err = br->impl_read(b[:1]);
s0 := b[0];
ch = rune(s0);
size = 1;
if err != nil {
return;
}
if ch < utf8.RUNE_SELF {
return;
}
x := utf8.accept_sizes[s0];
if x >= 0xf0 {
mask := rune(x) << 31 >> 31;
ch = ch &~ mask | utf8.RUNE_ERROR&mask;
return;
}
sz := int(x&7);
n: int;
n, err = br->impl_read(b[1:sz]);
if err != nil || n+1 < sz {
ch = utf8.RUNE_ERROR;
return;
}
ch, size = utf8.decode_rune(b[:sz]);
return;
}
unread_byte :: proc(s: Byte_Scanner) -> Error {
if s.vtable != nil && s.impl_unread_byte != nil {
return s->impl_unread_byte();
}
return .Empty;
}
unread_rune :: proc(s: Rune_Scanner) -> Error {
if s.vtable != nil && s.impl_unread_rune != nil {
return s->impl_unread_rune();
}
return .Empty;
}
write_string :: proc(s: Writer, str: string) -> (n: int, err: Error) {
return write(s, transmute([]byte)str);
}
write_rune :: proc(s: Writer, r: rune) -> (n: int, err: Error) {
buf, w := utf8.encode_rune(r);
return write(s, buf[:w]);
}
read_full :: proc(r: Reader, buf: []byte) -> (n: int, err: Error) {
return read_at_least(r, buf, len(buf));
}
read_at_least :: proc(r: Reader, buf: []byte, min: int) -> (n: int, err: Error) {
if len(buf) < min {
return 0, .Short_Buffer;
}
for n < min && err == nil {
nn: int;
nn, err = read(r, buf[n:]);
n += n;
}
if n >= min {
err = nil;
} else if n > 0 && err == .EOF {
err = .Unexpected_EOF;
}
return;
}
// copy copies from src to dst till either EOF is reached on src or an error occurs
// It returns the number of bytes copied and the first error that occurred whilst copying, if any.
copy :: proc(dst: Writer, src: Reader) -> (written: i64, err: Error) {
return _copy_buffer(dst, src, nil);
}
// copy_buffer is the same as copy except that it stages through the provided buffer (if one is required)
// rather than allocating a temporary one on the stack through `intrinsics.alloca`
// If buf is `nil`, it is allocate through `intrinsics.alloca`; otherwise if it has zero length, it will panic
copy_buffer :: proc(dst: Writer, src: Reader, buf: []byte) -> (written: i64, err: Error) {
if buf != nil && len(buf) == 0 {
panic("empty buffer in io.copy_buffer");
}
return _copy_buffer(dst, src, buf);
}
// copy_n copies n bytes (or till an error) from src to dst.
// It returns the number of bytes copied and the first error that occurred whilst copying, if any.
// On return, written == n IFF err == nil
copy_n :: proc(dst: Writer, src: Reader, n: i64) -> (written: i64, err: Error) {
nsrc := inline_limited_reader(&Limited_Reader{}, src, n);
written, err = copy(dst, nsrc);
if written == n {
return n, nil;
}
if written < n && err == nil {
// src stopped early and must have been an EOF
err = .EOF;
}
return;
}
@(private)
_copy_buffer :: proc(dst: Writer, src: Reader, buf: []byte) -> (written: i64, err: Error) {
if dst.vtable == nil || src.vtable == nil {
return 0, .Empty;
}
if src.impl_write_to != nil {
return src->impl_write_to(dst);
}
if src.impl_read_from != nil {
return dst->impl_read_from(src);
}
buf := buf;
if buf == nil {
DEFAULT_SIZE :: 4 * 1024;
size := DEFAULT_SIZE;
if src.vtable == _limited_reader_vtable {
l := (^Limited_Reader)(src.data);
if i64(size) > l.n {
if l.n < 1 {
size = 1;
} else {
size = int(l.n);
}
}
}
// NOTE(bill): alloca is fine here
buf = transmute([]byte)runtime.Raw_Slice{intrinsics.alloca(size, 2*align_of(rawptr)), size};
}
for {
nr, er := read(src, buf);
if nr > 0 {
nw, ew := write(dst, buf[0:nr]);
if nw > 0 {
written += i64(nw);
}
if ew != nil {
err = ew;
break;
}
if nr != nw {
err = .Short_Write;
break;
}
}
if er != nil {
if er != .EOF {
err = er;
}
break;
}
}
return;
}

115
core/io/multi.odin Normal file
View File

@@ -0,0 +1,115 @@
package io
import "core:runtime"
@(private)
Multi_Reader :: struct {
using stream: Stream,
readers: [dynamic]Reader,
}
@(private)
_multi_reader_vtable := &Stream_VTable{
impl_read = proc(s: Stream, p: []byte) -> (n: int, err: Error) {
mr := (^Multi_Reader)(s.data);
for len(mr.readers) > 0 {
r := mr.readers[0];
n, err = read(r, p);
if err == .EOF {
ordered_remove(&mr.readers, 0);
}
if n > 0 || err != .EOF {
if err == .EOF && len(mr.readers) > 0 {
// Don't return EOF yet, more readers remain
err = nil;
}
return;
}
}
return 0, .EOF;
},
impl_destroy = proc(s: Stream) -> Error {
mr := (^Multi_Reader)(s.data);
context.allocator = mr.readers.allocator;
delete(mr.readers);
free(mr);
return .None;
},
};
mutlti_reader :: proc(readers: ..Reader, allocator := context.allocator) -> Reader {
context.allocator = allocator;
mr := new(Multi_Reader);
mr.vtable = _multi_reader_vtable;
mr.data = mr;
all_readers := make([dynamic]Reader, 0, len(readers));
for w in readers {
if w.vtable == _multi_reader_vtable {
other := (^Multi_Reader)(w.data);
append(&all_readers, ..other.readers[:]);
} else {
append(&all_readers, w);
}
}
mr.readers = all_readers;
res, _ := to_reader(mr^);
return res;
}
@(private)
Multi_Writer :: struct {
using stream: Stream,
writers: []Writer,
allocator: runtime.Allocator,
}
@(private)
_multi_writer_vtable := &Stream_VTable{
impl_write = proc(s: Stream, p: []byte) -> (n: int, err: Error) {
mw := (^Multi_Writer)(s.data);
for w in mw.writers {
n, err = write(w, p);
if err != nil {
return;
}
if n != len(p) {
err = .Short_Write;
return;
}
}
return len(p), nil;
},
impl_destroy = proc(s: Stream) -> Error {
mw := (^Multi_Writer)(s.data);
context.allocator = mw.allocator;
delete(mw.writers);
free(mw);
return .None;
},
};
mutlti_writer :: proc(writers: ..Writer, allocator := context.allocator) -> Writer {
context.allocator = allocator;
mw := new(Multi_Writer);
mw.vtable = _multi_writer_vtable;
mw.data = mw;
mw.allocator = allocator;
all_writers := make([dynamic]Writer, 0, len(writers));
for w in writers {
if w.vtable == _multi_writer_vtable {
other := (^Multi_Writer)(w.data);
append(&all_writers, ..other.writers);
} else {
append(&all_writers, w);
}
}
mw.writers = all_writers[:];
res, _ := to_writer(mw^);
return res;
}

90
core/io/util.odin Normal file
View File

@@ -0,0 +1,90 @@
package io
import "core:runtime"
@(private)
Tee_Reader :: struct {
using stream: Stream,
r: Reader,
w: Writer,
allocator: runtime.Allocator,
}
@(private)
_tee_reader_vtable := &Stream_VTable{
impl_read = proc(s: Stream, p: []byte) -> (n: int, err: Error) {
t := (^Tee_Reader)(s.data);
n, err = read(t.r, p);
if n > 0 {
if wn, werr := write(t.w, p[:n]); werr != nil {
return wn, werr;
}
}
return;
},
impl_destroy = proc(s: Stream) -> Error {
t := (^Tee_Reader)(s.data);
allocator := t.allocator;
free(t, allocator);
return .None;
},
};
// tee_reader
// tee_reader must call io.destroy when done with
tee_reader :: proc(r: Reader, w: Writer, allocator := context.allocator) -> Reader {
t := new(Tee_Reader, allocator);
t.r, t.w = r, w;
t.allocator = allocator;
t.data = t;
t.vtable = _tee_reader_vtable;
res, _ := to_reader(t^);
return res;
}
// A Limited_Reader reads from r but limits the amount of
// data returned to just n bytes. Each call to read
// updates n to reflect the new amount remaining.
// read returns EOF when n <= 0 or when the underlying r returns EOF.
Limited_Reader :: struct {
using stream: Stream,
r: Reader, // underlying reader
n: i64, // max_bytes
}
@(private)
_limited_reader_vtable := &Stream_VTable{
impl_read = proc(using s: Stream, p: []byte) -> (n: int, err: Error) {
l := (^Limited_Reader)(s.data);
if l.n <= 0 {
return 0, .EOF;
}
p := p;
if i64(len(p)) > l.n {
p = p[0:l.n];
}
n, err = read(l.r, p);
l.n -= i64(n);
return;
},
};
new_limited_reader :: proc(r: Reader, n: i64) -> ^Limited_Reader {
l := new(Limited_Reader);
l.vtable = _limited_reader_vtable;
l.data = l;
l.r = r;
l.n = n;
return l;
}
@(private="package")
inline_limited_reader :: proc(l: ^Limited_Reader, r: Reader, n: i64) -> Reader {
l.vtable = _limited_reader_vtable;
l.data = l;
l.r = r;
l.n = n;
res, _ := to_reader(l^);
return res;
}