mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 12:01:16 +00:00
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:
70
lib/std/io/stream/multireader.c3
Normal file
70
lib/std/io/stream/multireader.c3
Normal 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];
|
||||
}
|
||||
59
lib/std/io/stream/multiwriter.c3
Normal file
59
lib/std/io/stream/multiwriter.c3
Normal 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[..])!;
|
||||
}
|
||||
42
lib/std/io/stream/teereader.c3
Normal file
42
lib/std/io/stream/teereader.c3
Normal 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];
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
21
test/unit/stdlib/io/multireader.c3
Normal file
21
test/unit/stdlib/io/multireader.c3
Normal 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);
|
||||
}
|
||||
|
||||
17
test/unit/stdlib/io/multiwriter.c3
Normal file
17
test/unit/stdlib/io/multiwriter.c3
Normal 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);
|
||||
}
|
||||
17
test/unit/stdlib/io/teereader.c3
Normal file
17
test/unit/stdlib/io/teereader.c3
Normal 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]);
|
||||
}
|
||||
Reference in New Issue
Block a user