2
0
gingerBill 4 жил өмнө
parent
commit
d9343ae997
4 өөрчлөгдсөн 851 нэмэгдсэн , 0 устгасан
  1. 185 0
      core/io/conv.odin
  2. 461 0
      core/io/io.odin
  3. 115 0
      core/io/multi.odin
  4. 90 0
      core/io/util.odin

+ 185 - 0
core/io/conv.odin

@@ -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 - 0
core/io/io.odin

@@ -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 - 0
core/io/multi.odin

@@ -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 - 0
core/io/util.odin

@@ -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;
+}