diff --git a/lib/std/io/stream/multireader.c3 b/lib/std/io/stream/multireader.c3 new file mode 100644 index 000000000..065940b4a --- /dev/null +++ b/lib/std/io/stream/multireader.c3 @@ -0,0 +1,70 @@ +module std::io; + +/* MultiReader implements the InStream interface and provides a logical + * concatenation of the provided readers. They are read sequentially. If all the + * data has been read, IoError.EOF is returned. + */ +struct MultiReader (InStream) +{ + InStream[] readers; + usz index; + Allocator allocator; +} + + +<* + @param [&inout] self + @param [&inout] allocator + @require self.readers.len == 0 "Init may not run on already initialized data" + @ensure self.index == 0 +*> +fn MultiReader* MultiReader.new_init(&self, InStream... readers, Allocator allocator = allocator::heap()) +{ + InStream []copy = allocator::new_array(allocator, InStream, readers.len); + copy[..] = readers[..]; + *self = { .readers = copy, .allocator = allocator }; + return self; +} + +<* + @param [&inout] self + @require self.readers.len == 0 "Init may not run on already initialized data" + @ensure self.index == 0 +*> +fn MultiReader* MultiReader.temp_init(&self, InStream... readers) +{ + return self.new_init(...readers, allocator: allocator::temp()); +} + +fn void MultiReader.free(&self) +{ + if (!self.allocator) return; + allocator::free(self.allocator, self.readers); + *self = {}; +} + +fn usz! MultiReader.read(&self, char[] bytes) @dynamic +{ + InStream r = self.readers[self.index]; + usz! n = r.read(bytes); + if (catch err = n) + { + case IoError.EOF: + self.index++; + if (self.index >= self.readers.len) + { + return IoError.EOF?; + } + return self.read(bytes); + default: + return err?; + } + return n; +} + +fn char! MultiReader.read_byte(&self) @dynamic +{ + char[1] data; + self.read(data[..])!; + return data[0]; +} diff --git a/lib/std/io/stream/multiwriter.c3 b/lib/std/io/stream/multiwriter.c3 new file mode 100644 index 000000000..787d5c762 --- /dev/null +++ b/lib/std/io/stream/multiwriter.c3 @@ -0,0 +1,59 @@ +module std::io; + +/* MultiWriter implements the OutStream interface and duplicates any write + * operation to all the wrapped writers. + */ +struct MultiWriter (OutStream) +{ + OutStream[] writers; + Allocator allocator; +} + +<* + @param [&inout] self + @param [&inout] allocator + @require writers.len > 0 + @require self.writers.len == 0 "Init may not run on already initialized data" +*> +fn MultiWriter* MultiWriter.new_init(&self, OutStream... writers, Allocator allocator = allocator::heap()) +{ + OutStream[] copy = allocator::new_array(allocator, OutStream, writers.len); + copy[..] = writers[..]; + *self = { .writers = copy, .allocator = allocator }; + return self; +} + +<* + @param [&inout] self + @require writers.len > 0 + @require self.writers.len == 0 "Init may not run on already initialized data" +*> +fn MultiWriter* MultiWriter.temp_init(&self, OutStream... writers) +{ + return self.new_init(...writers, allocator: allocator::temp()); +} + +fn void MultiWriter.free(&self) +{ + if (!self.allocator) return; + allocator::free(self.allocator, self.writers); + *self = {}; +} + +fn usz! MultiWriter.write(&self, char[] bytes) @dynamic +{ + usz n; + foreach (w : self.writers) + { + n = w.write(bytes)!; + if (n != bytes.len) return IoError.INCOMPLETE_WRITE?; + } + return bytes.len; +} + +fn void! MultiWriter.write_byte(&self, char c) @dynamic +{ + char[1] data; + data[0] = c; + self.write(data[..])!; +} diff --git a/lib/std/io/stream/teereader.c3 b/lib/std/io/stream/teereader.c3 new file mode 100644 index 000000000..fe71bcc0e --- /dev/null +++ b/lib/std/io/stream/teereader.c3 @@ -0,0 +1,42 @@ +module std::io; + +struct TeeReader (InStream) +{ + InStream r; + OutStream w; +} + +<* Returns a reader that implements InStream and that will write any data read + from the wrapped reader r to the writer w. There is no internal buffering. + + @param [&inout] r "Stream r to read from." + @param [&inout] w "Stream w to write to what it reads from r." +*> +macro TeeReader tee_reader(InStream r, OutStream w) => { r, w }; + +<* + @param [&inout] self + @param [&inout] r "Stream r to read from." + @param [&inout] w "Stream w to write to what it reads from r." + *> +fn TeeReader* TeeReader.init(&self, InStream r, OutStream w) +{ + *self = tee_reader(r, w); + return self; +} + +fn usz! TeeReader.read(&self, char[] bytes) @dynamic +{ + usz nr, nw; + nr = self.r.read(bytes)!; + nw = self.w.write(bytes[:nr])!; + if (nr != nw) return IoError.GENERAL_ERROR?; + return nr; +} + +fn char! TeeReader.read_byte(&self) @dynamic +{ + char[1] data; + self.read(data[..])!; + return data[0]; +} diff --git a/releasenotes.md b/releasenotes.md index e5718f2ad..df037f8d4 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -12,6 +12,7 @@ - Fix issue writing a single byte in the WriteBuffer ### Stdlib changes +- Add `io::MultiReader`, `io::MultiWriter`, and `io::TeeReader` structs. ## 0.6.4 Change list diff --git a/test/unit/stdlib/io/multireader.c3 b/test/unit/stdlib/io/multireader.c3 new file mode 100644 index 000000000..ba2a87492 --- /dev/null +++ b/test/unit/stdlib/io/multireader.c3 @@ -0,0 +1,21 @@ +module std::io @test; + +fn void! test_multireader() +{ + MultiReader mr; + mr.temp_init( + &&wrap_bytes("foo"), + &&wrap_bytes(" "), + &&wrap_bytes("bar"), + &&wrap_bytes("!"), + ); + defer mr.free(); + + ByteWriter w; + io::copy_to(&mr, w.temp_init())!; + + String want = "foo bar!"; + assert(w.str_view() == want, + "invalid data read; got: %s, want: %s", w.str_view(), want); +} + diff --git a/test/unit/stdlib/io/multiwriter.c3 b/test/unit/stdlib/io/multiwriter.c3 new file mode 100644 index 000000000..92ddd8bf8 --- /dev/null +++ b/test/unit/stdlib/io/multiwriter.c3 @@ -0,0 +1,17 @@ +module std::io @test; + +fn void! test_multiwriter() +{ + ByteWriter w1, w2; + MultiWriter mw; + mw.temp_init(w1.temp_init(), w2.temp_init()); + defer mw.free(); + + String want = "foobar"; + io::copy_to(ByteReader{}.init(want), &mw)!; + + assert(w1.str_view() == want, + "invalid write; got: %s, want: %s", w1.str_view(), want); + assert(w2.str_view() == want, + "invalid write; got: %s, want: %s", w2.str_view(), want); +} diff --git a/test/unit/stdlib/io/teereader.c3 b/test/unit/stdlib/io/teereader.c3 new file mode 100644 index 000000000..8a5f16c9f --- /dev/null +++ b/test/unit/stdlib/io/teereader.c3 @@ -0,0 +1,17 @@ +module std::io @test; + +fn void! test_teereader() +{ + String want = "foobar"; + + ByteWriter w; + TeeReader r = tee_reader(ByteReader{}.init(want), w.temp_init()); + + char[16] buf; + usz n = r.read(buf[..])!; + + String got = w.str_view(); + assert(n == want.len, "teereader: invalid length"); + assert(got == want, "teereader: got: %s, want: %s", got, want); + assert(got == (String)buf[:n], "teereader: got: %s, want: %s", got, (String)buf[:n]); +}