From d9343ae9973f3a8983df786b28b5c8a891d9f61d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 2 Dec 2020 23:12:14 +0000 Subject: [PATCH] Add `package io` --- core/io/conv.odin | 185 ++++++++++++++++++ core/io/io.odin | 461 +++++++++++++++++++++++++++++++++++++++++++++ core/io/multi.odin | 115 +++++++++++ core/io/util.odin | 90 +++++++++ 4 files changed, 851 insertions(+) create mode 100644 core/io/conv.odin create mode 100644 core/io/io.odin create mode 100644 core/io/multi.odin create mode 100644 core/io/util.odin diff --git a/core/io/conv.odin b/core/io/conv.odin new file mode 100644 index 000000000..4afc3490d --- /dev/null +++ b/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; +} diff --git a/core/io/io.odin b/core/io/io.odin new file mode 100644 index 000000000..be3bca147 --- /dev/null +++ b/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; +} diff --git a/core/io/multi.odin b/core/io/multi.odin new file mode 100644 index 000000000..453ee279a --- /dev/null +++ b/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; +} diff --git a/core/io/util.odin b/core/io/util.odin new file mode 100644 index 000000000..9a2dc09af --- /dev/null +++ b/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; +}