commit 7a14688a94c324ef5267c83413bdc7aacf38eed7
parent e616ea4612c2356896c4e077af975e9092a99629
Author: Bor Grošelj Simić <bor.groseljsimic@telemach.net>
Date: Tue, 13 Apr 2021 00:28:54 +0200
bufio::fixed: merge with bufio/dynamic.ha
This enables seeking on readonly fixed streams, because implementations
of read and seek are the same on fixed and dynamic streams.
Diffstat:
6 files changed, 266 insertions(+), 270 deletions(-)
diff --git a/bufio/buffered.ha b/bufio/buffered.ha
@@ -222,7 +222,7 @@ fn buffered_write(s: *io::stream, buf: const []u8) (size | io::error) = {
@test fn buffered_read() void = {
let sourcebuf: []u8 = [1, 3, 3, 7];
let source = fixed(sourcebuf, io::mode::READ);
- let fb = source: *fixed_stream;
+ let fb = source: *memstream;
defer io::close(source);
let rbuf: [1024]u8 = [0...];
@@ -231,7 +231,7 @@ fn buffered_write(s: *io::stream, buf: const []u8) (size | io::error) = {
let buf: [1024]u8 = [0...];
assert(io::read(f, buf[..2]) as size == 2);
- assert(len(fb.buf) == 0, "fixed stream was not fully consumed");
+ assert(fb.pos == len(fb.buf), "fixed stream was not fully consumed");
assert(bytes::equal(buf[..2], [1, 3]));
assert(io::read(f, buf[2..]) as size == 2);
@@ -241,7 +241,7 @@ fn buffered_write(s: *io::stream, buf: const []u8) (size | io::error) = {
let sourcebuf: [32]u8 = [1, 3, 3, 7, 0...];
sourcebuf[32..36] = [7, 3, 3, 1];
let source = fixed(sourcebuf, io::mode::READ);
- let fb = source: *fixed_stream;
+ let fb = source: *memstream;
defer io::close(source);
let rbuf: [16]u8 = [0...];
@@ -250,12 +250,12 @@ fn buffered_write(s: *io::stream, buf: const []u8) (size | io::error) = {
let buf: [32]u8 = [0...];
assert(io::read(f, buf) as size == 16);
- assert(len(fb.buf) == 16);
+ assert(fb.pos == 16);
assert(io::read(f, buf[16..]) as size == 16);
assert(bytes::equal(buf, sourcebuf));
assert(io::read(f, buf) is io::EOF);
- assert(len(fb.buf) == 0);
+ assert(fb.pos == len(fb.buf));
};
@test fn buffered_write() void = {
diff --git a/bufio/dynamic.ha b/bufio/dynamic.ha
@@ -1,193 +0,0 @@
-use bytes;
-use errors;
-use io;
-
-type dynamic_stream = struct {
- stream: io::stream,
- buf: []u8,
- pos: size,
-};
-
-// Creates an [io::stream] which dynamically allocates a buffer to store writes
-// into. Subsequent reads will consume the buffered data. Upon failure to
-// allocate sufficient memory to store writes, the program aborts.
-//
-// Calling [io::close] on this stream will free the buffer. Call [bufio::finish]
-// instead to free up resources associated with the stream, but transfer
-// ownership of the buffer to the caller.
-export fn dynamic(mode: io::mode) *io::stream = dynamic_from([], mode);
-
-// Like [dynamic], but takes an existing slice as input. Writes are appended to
-// it and reads consume bytes from the initial buffer, plus any additional
-// writes. Like [dynamic], calling [io::close] will free the buffer, and
-// [bufio::finish] can be used to return ownership of the buffer to the caller.
-export fn dynamic_from(in: []u8, mode: io::mode) *io::stream = {
- let s = alloc(dynamic_stream {
- stream = io::stream {
- closer = &dynamic_close,
- seeker = &dynamic_seek,
- ...
- },
- buf = in,
- pos = 0,
- }): *io::stream;
- if (mode & io::mode::READ == io::mode::READ) {
- s.reader = &dynamic_read;
- };
- if (mode & io::mode::WRITE == io::mode::WRITE) {
- s.writer = &dynamic_write;
- };
- return s;
-};
-
-fn dynamic_write(s: *io::stream, buf: const []u8) (size | io::error) = {
- let s = s: *dynamic_stream;
- if (s.pos == len(s.buf)) {
- append(s.buf, buf...);
- } else {
- // TODO: Insert
- let new: []u8 = alloc([], len(s.buf) + len(buf));
- append(new, s.buf[..s.pos]...);
- append(new, buf[..]...);
- append(new, s.buf[s.pos..]...);
- free(s.buf);
- s.buf = new;
- };
-
- s.pos += len(buf);
- return len(buf);
-};
-
-fn dynamic_read(s: *io::stream, buf: []u8) (size | io::EOF | io::error) = {
- let s = s: *dynamic_stream;
- if (len(s.buf) == s.pos && len(buf) != 0) {
- return io::EOF;
- };
- const n = if (len(s.buf) - s.pos < len(buf)) {
- len(s.buf) - s.pos;
- } else {
- len(buf);
- };
- buf[..n] = s.buf[s.pos..s.pos + n];
- s.pos += n;
- return n;
-};
-
-fn dynamic_seek(
- s: *io::stream,
- off: io::off,
- w: io::whence
-) (io::off | io::error) = {
- let stream = s: *dynamic_stream;
- switch (w) {
- io::whence::SET => {
- if (len(stream.buf) < off: size) {
- abort("invalid offset");
- };
- stream.pos = off: size;
- },
- io::whence::CUR => {
- if (stream.pos + off: size > len(stream.buf)) {
- abort("invalid offset");
- };
- stream.pos += off: size;
- },
- io::whence::END => {
- if (len(stream.buf) - (-off): size < len(stream.buf)) {
- abort("invalid offset");
- };
- stream.pos = len(stream.buf) - (-off): size;
- },
- };
- return stream.pos: io::off;
-};
-
-fn dynamic_close(s: *io::stream) void = {
- const s = s: *dynamic_stream;
- free(s.buf);
- free(s);
-};
-
-// Closes the stream without freeing the buffer, instead transferring ownership
-// of it to the caller.
-export fn finish(s: *io::stream) []u8 = {
- if (s.closer != &dynamic_close) {
- abort("bufio::finish called on non-bufio stream");
- };
- let s = s: *dynamic_stream;
- let buf = s.buf;
- free(s);
- return buf;
-};
-
-// Returns the current buffer.
-export fn buffer(s: *io::stream) []u8 = {
- if (s.closer != &dynamic_close) {
- abort("bufio::buffer called on non-bufio stream");
- };
- let s = s: *dynamic_stream;
- return s.buf;
-};
-
-// Resets the buffer's length to zero, but keeps the allocated memory around for
-// future writes.
-export fn reset(s: *io::stream) void = {
- if (s.closer != &dynamic_close) {
- abort("bufio::reset called on non-bufio stream");
- };
- const s = s: *dynamic_stream;
- s.pos = 0;
- s.buf = s.buf[..0];
-};
-
-// Truncates the buffer, freeing memory associated with it and setting its
-// length to zero.
-export fn truncate(s: *io::stream) (void | errors::unsupported) = {
- if (s.closer != &dynamic_close) {
- return errors::unsupported;
- };
- let s = s: *dynamic_stream;
- s.pos = 0;
- delete(s.buf[..]);
-};
-
-@test fn dynamic() void = {
- // TODO: slice/array equality
- let s = dynamic(io::mode::RDWR);
- assert(io::write(s, [1, 2, 3]) as size == 3);
- assert(bytes::equal(buffer(s), [1, 2, 3]));
- assert(io::write(s, [4, 5]) as size == 2);
- assert(bytes::equal(buffer(s), [1, 2, 3, 4, 5]));
- let buf: [2]u8 = [0...];
- assert(io::seek(s, 0, io::whence::SET) as io::off == 0: io::off);
- assert(io::read(s, buf[..]) as size == 2 && bytes::equal(buf, [1, 2]));
- assert(io::read(s, buf[..]) as size == 2 && bytes::equal(buf, [3, 4]));
- assert(io::read(s, buf[..]) as size == 1 && buf[0] == 5);
- assert(io::read(s, buf[..]) is io::EOF);
- assert(io::write(s, [6, 7, 8]) as size == 3);
- assert(bytes::equal(buffer(s), [1, 2, 3, 4, 5, 6, 7, 8]));
- reset(s);
- assert(len(buffer(s)) == 0);
- assert(io::write(s, [1, 2, 3]) as size == 3);
- assert(truncate(s) is void);
- assert(len(buffer(s)) == 0);
-
- let sl: []u8 = alloc([1, 2, 3]);
- let s = dynamic_from(sl, io::mode::WRITE);
- assert(io::write(s, [0, 0]) as size == 2);
- assert(io::seek(s, 0, io::whence::END) as io::off == 5: io::off);
- assert(io::write(s, [4, 5, 6]) as size == 3);
- assert(bytes::equal(buffer(s), [0, 0, 1, 2, 3, 4, 5, 6]));
- // TODO: this should check for errors::unsupported (harec bug prevents that)
- assert(io::read(s, buf[..]) is io::error);
- io::close(s);
-
- sl = alloc([1, 2]);
- let s = dynamic_from(sl, io::mode::READ);
- assert(io::read(s, buf[..1]) as size == 1 && buf[0] == 1);
- assert(io::seek(s, 1, io::whence::CUR) as io::off == 2: io::off);
- assert(io::read(s, buf[..]) is io::EOF);
- // TODO: this should check for errors::unsupported (harec bug prevents that)
- assert(io::write(s, [1, 2]) is io::error);
- io::close(s);
-};
diff --git a/bufio/fixed.ha b/bufio/fixed.ha
@@ -1,66 +0,0 @@
-use bytes;
-use io;
-use strings;
-
-type fixed_stream = struct {
- stream: io::stream,
- buf: []u8,
-};
-
-// Creates an [io::stream] for a fixed, caller-supplied buffer. Supports either
-// read or write, but not both. The program aborts if writes would exceed the
-// buffer's capacity.
-export fn fixed(in: []u8, mode: io::mode) *io::stream = {
- let s = alloc(fixed_stream {
- stream = io::stream {
- name = "<bufio::fixed>",
- closer = &fixed_close,
- ...
- },
- buf = in,
- });
- if (mode & io::mode::READ == io::mode::READ) {
- assert(mode & io::mode::WRITE != io::mode::WRITE);
- s.stream.reader = &fixed_read;
- };
- if (mode & io::mode::WRITE == io::mode::WRITE) {
- assert(mode & io::mode::READ != io::mode::READ);
- s.stream.writer = &fixed_write;
- };
- return &s.stream;
-};
-
-fn fixed_read(s: *io::stream, buf: []u8) (size | io::error | io::EOF) = {
- let stream = s: *fixed_stream;
- if (len(stream.buf) == 0) {
- return io::EOF;
- };
- const n = if (len(buf) > len(stream.buf)) len(stream.buf) else len(buf);
- buf[..n] = stream.buf[..n];
- stream.buf = stream.buf[n..];
- return n;
-};
-
-fn fixed_write(s: *io::stream, buf: const []u8) (size | io::error) = {
- let stream = s: *fixed_stream;
- if (len(stream.buf) == 0) {
- abort("bufio::fixed buffer exceeded");
- };
- const n = if (len(buf) > len(stream.buf)) len(stream.buf) else len(buf);
- stream.buf[..n] = buf[..n];
- stream.buf = stream.buf[n..];
- return n;
-};
-
-fn fixed_close(s: *io::stream) void = free(s);
-
-@test fn fixed() void = {
- // TODO: add a read test too
- static let buf: [1024]u8 = [0...];
- let stream = fixed(buf, io::mode::WRITE);
- defer io::close(stream);
- let n = 0z;
- n += io::write(stream, strings::toutf8("hello ")) as size;
- n += io::write(stream, strings::toutf8("world")) as size;
- assert(bytes::equal(buf[..n], strings::toutf8("hello world")));
-};
diff --git a/bufio/memstream.ha b/bufio/memstream.ha
@@ -0,0 +1,258 @@
+use bytes;
+use io;
+use strings;
+use errors;
+
+type memstream = struct {
+ stream: io::stream,
+ buf: []u8,
+ pos: size,
+};
+
+// Creates an [io::stream] for a fixed, caller-supplied buffer. Supports either
+// read or write, but not both. Readable streams are seekable. The program
+// aborts if writes would exceed the buffer's capacity.
+export fn fixed(in: []u8, mode: io::mode) *io::stream = {
+ let s = alloc(memstream {
+ stream = io::stream {
+ name = "<bufio::fixed>",
+ closer = &fixed_close,
+ ...
+ },
+ buf = in,
+ pos = 0,
+ });
+ if (mode & io::mode::READ == io::mode::READ) {
+ assert(mode & io::mode::WRITE != io::mode::WRITE);
+ s.stream.reader = &read;
+ s.stream.seeker = &seek;
+ };
+ if (mode & io::mode::WRITE == io::mode::WRITE) {
+ assert(mode & io::mode::READ != io::mode::READ);
+ s.stream.writer = &fixed_write;
+ };
+ return &s.stream;
+};
+
+fn fixed_write(s: *io::stream, buf: const []u8) (size | io::error) = {
+ // TODO: make seekable
+ let stream = s: *memstream;
+ if (len(stream.buf) == 0) {
+ abort("bufio::fixed buffer exceeded");
+ };
+ const n = if (len(buf) > len(stream.buf)) len(stream.buf) else len(buf);
+ stream.buf[..n] = buf[..n];
+ stream.buf = stream.buf[n..];
+ return n;
+};
+
+fn fixed_close(s: *io::stream) void = free(s);
+
+// Creates an [io::stream] which dynamically allocates a buffer to store writes
+// into. Subsequent reads will consume the buffered data. Upon failure to
+// allocate sufficient memory to store writes, the program aborts.
+//
+// Calling [io::close] on this stream will free the buffer. Call [bufio::finish]
+// instead to free up resources associated with the stream, but transfer
+// ownership of the buffer to the caller.
+export fn dynamic(mode: io::mode) *io::stream = dynamic_from([], mode);
+
+// Like [dynamic], but takes an existing slice as input. Writes are appended to
+// it and reads consume bytes from the initial buffer, plus any additional
+// writes. Like [dynamic], calling [io::close] will free the buffer, and
+// [bufio::finish] can be used to return ownership of the buffer to the caller.
+export fn dynamic_from(in: []u8, mode: io::mode) *io::stream = {
+ let s = alloc(memstream {
+ stream = io::stream {
+ name = "<bufio::dynamic>",
+ closer = &dynamic_close,
+ seeker = &seek,
+ ...
+ },
+ buf = in,
+ pos = 0,
+ }): *io::stream;
+ if (mode & io::mode::READ == io::mode::READ) {
+ s.reader = &read;
+ };
+ if (mode & io::mode::WRITE == io::mode::WRITE) {
+ s.writer = &dynamic_write;
+ };
+ return s;
+};
+
+fn dynamic_write(s: *io::stream, buf: const []u8) (size | io::error) = {
+ let s = s: *memstream;
+ if (s.pos == len(s.buf)) {
+ append(s.buf, buf...);
+ } else {
+ // TODO: Insert
+ let new: []u8 = alloc([], len(s.buf) + len(buf));
+ append(new, s.buf[..s.pos]...);
+ append(new, buf[..]...);
+ append(new, s.buf[s.pos..]...);
+ free(s.buf);
+ s.buf = new;
+ };
+
+ s.pos += len(buf);
+ return len(buf);
+};
+
+fn dynamic_close(s: *io::stream) void = {
+ const s = s: *memstream;
+ free(s.buf);
+ free(s);
+};
+
+// Closes the stream without freeing the dynamic buffer, instead transferring
+// ownership of it to the caller.
+export fn finish(s: *io::stream) []u8 = {
+ if (s.closer != &dynamic_close) {
+ abort("bufio::finish called on non-bufio stream");
+ };
+ let s = s: *memstream;
+ let buf = s.buf;
+ free(s);
+ return buf;
+};
+
+// Returns the current buffer.
+export fn buffer(s: *io::stream) []u8 = {
+ if (s.closer != &dynamic_close) {
+ abort("bufio::buffer called on non-bufio stream");
+ };
+ let s = s: *memstream;
+ return s.buf;
+};
+
+// Resets the dynamic buffer's length to zero, but keeps the allocated memory
+// around for future writes.
+export fn reset(s: *io::stream) void = {
+ if (s.writer != &dynamic_write) {
+ abort("bufio::reset called on non-bufio stream");
+ };
+ const s = s: *memstream;
+ s.pos = 0;
+ s.buf = s.buf[..0];
+};
+
+// Truncates the dynamic buffer, freeing memory associated with it and setting
+// its length to zero.
+export fn truncate(s: *io::stream) (void | errors::unsupported) = {
+ if (s.writer != &dynamic_write) {
+ return errors::unsupported;
+ };
+ let s = s: *memstream;
+ s.pos = 0;
+ delete(s.buf[..]);
+};
+
+
+fn read(s: *io::stream, buf: []u8) (size | io::EOF | io::error) = {
+ let s = s: *memstream;
+ if (len(s.buf) == s.pos && len(buf) != 0) {
+ return io::EOF;
+ };
+ const n = if (len(s.buf) - s.pos < len(buf)) {
+ len(s.buf) - s.pos;
+ } else {
+ len(buf);
+ };
+ assert(s.pos + n <= len(s.buf));
+ buf[..n] = s.buf[s.pos..s.pos + n];
+ s.pos += n;
+ return n;
+};
+
+fn seek(
+ s: *io::stream,
+ off: io::off,
+ w: io::whence
+) (io::off | io::error) = {
+ let s = s: *memstream;
+ switch (w) {
+ io::whence::SET => {
+ if (len(s.buf) < off: size) {
+ abort("invalid offset");
+ };
+ s.pos = off: size;
+ },
+ io::whence::CUR => {
+ if (s.pos + off: size > len(s.buf)) {
+ abort("invalid offset");
+ };
+ s.pos += off: size;
+ },
+ io::whence::END => {
+ if (len(s.buf) - (-off): size < len(s.buf)) {
+ abort("invalid offset");
+ };
+ s.pos = len(s.buf) - (-off): size;
+ },
+ };
+ return s.pos: io::off;
+};
+
+@test fn dynamic() void = {
+ // TODO: slice/array equality
+ let s = dynamic(io::mode::RDWR);
+ assert(io::write(s, [1, 2, 3]) as size == 3);
+ assert(bytes::equal(buffer(s), [1, 2, 3]));
+ assert(io::write(s, [4, 5]) as size == 2);
+ assert(bytes::equal(buffer(s), [1, 2, 3, 4, 5]));
+ let buf: [2]u8 = [0...];
+ assert(io::seek(s, 0, io::whence::SET) as io::off == 0: io::off);
+ assert(io::read(s, buf[..]) as size == 2 && bytes::equal(buf, [1, 2]));
+ assert(io::read(s, buf[..]) as size == 2 && bytes::equal(buf, [3, 4]));
+ assert(io::read(s, buf[..]) as size == 1 && buf[0] == 5);
+ assert(io::read(s, buf[..]) is io::EOF);
+ assert(io::write(s, [6, 7, 8]) as size == 3);
+ assert(bytes::equal(buffer(s), [1, 2, 3, 4, 5, 6, 7, 8]));
+ reset(s);
+ assert(len(buffer(s)) == 0);
+ assert(io::write(s, [1, 2, 3]) as size == 3);
+ assert(truncate(s) is void);
+ assert(len(buffer(s)) == 0);
+
+ let sl: []u8 = alloc([1, 2, 3]);
+ let s = dynamic_from(sl, io::mode::WRITE);
+ assert(io::write(s, [0, 0]) as size == 2);
+ assert(io::seek(s, 0, io::whence::END) as io::off == 5: io::off);
+ assert(io::write(s, [4, 5, 6]) as size == 3);
+ assert(bytes::equal(buffer(s), [0, 0, 1, 2, 3, 4, 5, 6]));
+ // TODO: this should check for errors::unsupported (harec bug prevents that)
+ assert(io::read(s, buf[..]) is io::error);
+ io::close(s);
+
+ sl = alloc([1, 2]);
+ let s = dynamic_from(sl, io::mode::READ);
+ assert(io::read(s, buf[..1]) as size == 1 && buf[0] == 1);
+ assert(io::seek(s, 1, io::whence::CUR) as io::off == 2: io::off);
+ assert(io::read(s, buf[..]) is io::EOF);
+ // TODO: this should check for errors::unsupported (harec bug prevents that)
+ assert(io::write(s, [1, 2]) is io::error);
+ io::close(s);
+};
+
+@test fn fixed() void = {
+ let buf: [1024]u8 = [0...];
+ let stream = fixed(buf, io::mode::WRITE);
+ defer io::close(stream);
+
+ let n = 0z;
+ n += io::write(stream, strings::toutf8("hello ")) as size;
+ n += io::write(stream, strings::toutf8("world")) as size;
+ assert(bytes::equal(buf[..n], strings::toutf8("hello world")));
+ // TODO: this should check for errors::unsupported (harec bug prevents that)
+ io::seek(stream, 6, io::whence::SET) as io::error;
+
+ let out: [2]u8 = [0...];
+ let s = fixed([1u8, 2u8], io::mode::READ);
+ defer io::close(s);
+ assert(io::read(s, out[..1]) as size == 1 && out[0] == 1);
+ assert(io::seek(s, 1, io::whence::CUR) as io::off == 2: io::off);
+ assert(io::read(s, buf[..]) is io::EOF);
+ // TODO: this should check for errors::unsupported (harec bug prevents that)
+ assert(io::write(s, [1, 2]) is io::error);
+};
diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib
@@ -108,8 +108,7 @@ ascii() {
bufio() {
gen_srcs bufio \
buffered.ha \
- dynamic.ha \
- fixed.ha \
+ memstream.ha \
scanner.ha
gen_ssa bufio io bytes strings encoding::utf8 errors
}
diff --git a/stdlib.mk b/stdlib.mk
@@ -236,8 +236,7 @@ $(HARECACHE)/ascii/ascii.ssa: $(stdlib_ascii_srcs) $(stdlib_rt) $(stdlib_strings
# bufio
stdlib_bufio_srcs= \
$(STDLIB)/bufio/buffered.ha \
- $(STDLIB)/bufio/dynamic.ha \
- $(STDLIB)/bufio/fixed.ha \
+ $(STDLIB)/bufio/memstream.ha \
$(STDLIB)/bufio/scanner.ha
$(HARECACHE)/bufio/bufio.ssa: $(stdlib_bufio_srcs) $(stdlib_rt) $(stdlib_io) $(stdlib_bytes) $(stdlib_strings) $(stdlib_encoding_utf8) $(stdlib_errors)
@@ -1077,8 +1076,7 @@ $(TESTCACHE)/ascii/ascii.ssa: $(testlib_ascii_srcs) $(testlib_rt) $(testlib_stri
# bufio
testlib_bufio_srcs= \
$(STDLIB)/bufio/buffered.ha \
- $(STDLIB)/bufio/dynamic.ha \
- $(STDLIB)/bufio/fixed.ha \
+ $(STDLIB)/bufio/memstream.ha \
$(STDLIB)/bufio/scanner.ha
$(TESTCACHE)/bufio/bufio.ssa: $(testlib_bufio_srcs) $(testlib_rt) $(testlib_io) $(testlib_bytes) $(testlib_strings) $(testlib_encoding_utf8) $(testlib_errors)