diff --git a/lib/std/io/stream.c3 b/lib/std/io/stream.c3 index 962aa5e8d..cf21a4c7b 100644 --- a/lib/std/io/stream.c3 +++ b/lib/std/io/stream.c3 @@ -1,7 +1,5 @@ module std::io; import std::math; -import std::core::env; - interface InStream { @@ -36,7 +34,7 @@ fn usz? available(InStream s) s.seek(curr, Seek.SET)!; return len - curr; } - return 0; + return ^io::UNSUPPORTED_OPERATION; } macro bool @is_instream(#expr) @const @@ -90,21 +88,31 @@ macro usz? read_all(stream, char[] buffer) } <* + This function will read to the end of the stream. + @require @is_instream(stream) *> macro char[]? read_fully(Allocator allocator, stream) { - usz len = available(stream)!; - char* data = allocator::malloc_try(allocator, len)!; - defer catch allocator::free(allocator, data); - usz read = 0; - while (read < len) + // Efficient path if it is possible to pre-allocate + if (try len = available(stream)) { - read += stream.read(data[read:len - read])!; + char* data = allocator::malloc_try(allocator, len)!; + defer catch allocator::free(allocator, data); + usz read = 0; + while (read < len) + { + read += stream.read(data[read:len - read])!; + } + return data[:len]; } - return data[:len]; + ByteWriter writer; + writer.init(allocator); + copy_to(stream, &writer)!; + return writer.array_view(); } + <* @require @is_outstream(stream) *> diff --git a/lib/std/io/stream/bytewriter.c3 b/lib/std/io/stream/bytewriter.c3 index 8a87e506c..b982894e7 100644 --- a/lib/std/io/stream/bytewriter.c3 +++ b/lib/std/io/stream/bytewriter.c3 @@ -43,6 +43,11 @@ fn void? ByteWriter.destroy(&self) @dynamic *self = { }; } +fn char[] ByteWriter.array_view(self) @inline +{ + return self.bytes[:self.index]; +} + fn String ByteWriter.str_view(&self) @inline { return (String)self.bytes[:self.index]; @@ -91,7 +96,7 @@ fn usz? ByteWriter.read_from(&self, InStream reader) @dynamic } if (self.bytes.len == 0) { - self.ensure_capacity(16)!; + self.ensure_capacity(256)!; } while (true) { diff --git a/releasenotes.md b/releasenotes.md index e4d0d8f8a..7d71b0139 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -49,6 +49,7 @@ - `String.replace` no longer depends on `String.split`. - Fix the case where `\u` could crash the compiler on some platforms. - Designated initialization with ranges would not error on overflow by 1. +- `io::read_fully` now handles unbounded streams properly. ### Stdlib changes - Add `ThreadPool` join function to wait for all threads to finish in the pool without destroying the threads. diff --git a/test/unit/stdlib/io/read_full.c3 b/test/unit/stdlib/io/read_full.c3 new file mode 100644 index 000000000..4f1864085 --- /dev/null +++ b/test/unit/stdlib/io/read_full.c3 @@ -0,0 +1,30 @@ +module std::io @test; + +struct Test (InStream) +{ + char[] data; + int index; +} + +fn char? Test.read_byte(&self) @dynamic +{ + if (self.index >= self.data.len) return io::EOF~; + return self.data[self.index++]; +} + +fn usz? Test.read(&self, char[] buffer) @dynamic +{ + io::printn(*self); + if (self.index >= self.data.len) return io::EOF~; + int len = min((int)self.data.len - self.index, 16); + buffer[:len] = self.data[self.index:len]; + self.index += len; + return len; +} + +fn void read_fully_stream() +{ + Test t = { .data = "Heokep o opewkfpewkfpoew kfopwekfop ewkf ewopfkewop fopkewfopk ewopfkewpo fkopew kfpoewk fej!" }; + char[] data = io::read_fully(tmem, &t)!!; + test::eq(data, t.data); +}