hare

The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

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:
Mbufio/buffered.ha | 10+++++-----
Dbufio/dynamic.ha | 193-------------------------------------------------------------------------------
Dbufio/fixed.ha | 66------------------------------------------------------------------
Abufio/memstream.ha | 258+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mscripts/gen-stdlib | 3+--
Mstdlib.mk | 6++----
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)