Add io stream primitives (#1626)

* io: implement MultiReader struct

Implement a MultiReader (InStream) which sequentially read from the
provided readers (InStreams). Return IoError.EOF when all of the readers
are read.

* io: implement MultiWriter struct

Implement a MultiWriter (OutStream). The MultiWriter duplicates its
writes to all the provided writers (OutStream).

* io: implement TeeReader struct

Implement a TeeReader (InStream) which reads from a wrapped reader
(InStream) and writes data to the provided writer (OutStream).
This commit is contained in:
konimarti
2024-11-15 23:18:29 +01:00
committed by GitHub
parent a233771433
commit f3304acc93
7 changed files with 227 additions and 0 deletions

View File

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

View File

@@ -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[..])!;
}

View File

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

View File

@@ -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

View File

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

View File

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

View File

@@ -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]);
}