hare

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

commit c15eb14639160b34d29b41b5cb63bcf8c6db8198
parent 4259f1348502576eaa6a0a8e0a23a794c997a2b2
Author: Drew DeVault <sir@cmpwn.com>
Date:   Wed, 20 Apr 2022 16:19:04 +0200

all: use vtable for io::stream

Implements: https://todo.sr.ht/~sircmpwn/hare/539
Signed-off-by: Drew DeVault <sir@cmpwn.com>

Diffstat:
Mbufio/buffered.ha | 36++++++++++++++++++++++++++----------
Mbufio/memstream.ha | 72+++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Mcrypto/blake2b/blake2b.ha | 9+++++++--
Mcrypto/hmac/hmac.ha | 7++++++-
Mcrypto/hmac/sha1.ha | 7++++++-
Mcrypto/hmac/sha256.ha | 7++++++-
Mcrypto/mac/mac.ha | 3++-
Mcrypto/poly1305/poly1305.ha | 7++++++-
Mcrypto/random/random.ha | 4+++-
Mcrypto/sha1/sha1.ha | 9+++++++--
Mcrypto/sha256/sha256.ha | 9+++++++--
Mcrypto/sha512/sha512.ha | 9+++++++--
Mencoding/base32/base32.ha | 20+++++++++++++++-----
Mencoding/base64/base64.ha | 20+++++++++++++++-----
Mhare/types/+test.ha | 6+++---
Mhash/adler32/adler32.ha | 7++++++-
Mhash/crc16/crc16.ha | 7++++++-
Mhash/crc32/crc32.ha | 7++++++-
Mhash/crc64/crc64.ha | 7++++++-
Mhash/fnv/fnv.ha | 18++++++++++++++----
Mhash/hash.ha | 3++-
Dio/+test/copy.ha | 93-------------------------------------------------------------------------------
Mio/+test/stream.ha | 11++++++++---
Mio/empty.ha | 4+++-
Dio/filestream.ha | 56--------------------------------------------------------
Mio/limit.ha | 32+++++++++++++++++++-------------
Mio/stream.ha | 28++++++++++++++++++----------
Mio/tee.ha | 11++++++++---
Mscripts/gen-stdlib | 3---
Mstdlib.mk | 6------
Mstrio/stream.ha | 22+++++++++++++---------
31 files changed, 276 insertions(+), 264 deletions(-)

diff --git a/bufio/buffered.ha b/bufio/buffered.ha @@ -9,6 +9,25 @@ use errors; use io; use strings; +const buffered_vtable_r: io::vtable = io::vtable { + closer = &buffered_close_static, + reader = &buffered_read, + ... +}; + +const buffered_vtable_w: io::vtable = io::vtable { + closer = &buffered_close_static, + writer = &buffered_write, + ... +}; + +const buffered_vtable_rw: io::vtable = io::vtable { + closer = &buffered_close_static, + reader = &buffered_read, + writer = &buffered_write, + ... +}; + export type bufstream = struct { stream: io::stream, source: io::handle, @@ -42,22 +61,19 @@ export fn buffered( ) bufstream = { static let flush_default = ['\n': u8]; let s = bufstream { - stream = io::stream { - closer = &buffered_close_static, - ... - }, source = src, rbuffer = rbuf, wbuffer = wbuf, flush = flush_default, ... }; - if (len(rbuf) != 0) { - s.stream.reader = &buffered_read; - }; - if (len(wbuf) != 0) { - s.stream.writer = &buffered_write; - }; + if (len(rbuf) != 0 && len(wbuf) != 0) { + s.stream = &buffered_vtable_rw; + } else if (len(rbuf) != 0) { + s.stream = &buffered_vtable_r; + } else if (len(wbuf) != 0) { + s.stream = &buffered_vtable_w; + } else abort("Must provide at least one buffer to bufio::buffered"); if (len(rbuf) != 0 && len(wbuf) != 0) { assert(rbuf: *[*]u8 != wbuf: *[*]u8, "Cannot use bufio::buffered with same buffer for reads and writes"); diff --git a/bufio/memstream.ha b/bufio/memstream.ha @@ -14,25 +14,44 @@ export type memstream = struct { pos: size, }; +const memstream_vt_r: io::vtable = io::vtable { + seeker = &seek, + copier = &copy, + reader = &read, + ... +}; + +const fixed_vt_w: io::vtable = io::vtable { + seeker = &seek, + copier = &copy, + writer = &fixed_write, + ... +}; + +const fixed_vt_rw: io::vtable = io::vtable { + seeker = &seek, + copier = &copy, + reader = &read, + writer = &fixed_write, + ... +}; + // Creates a stream for a fixed, caller-supplied buffer. All fixed streams are // seekable; seeking a write stream will cause subsequent writes to overwrite // existing contents of the buffer. The program aborts if writes would exceed // the buffer's capacity. The stream doesn't have to be closed. export fn fixed(in: []u8, mode: io::mode) memstream = { let s = memstream { - stream = io::stream { - seeker = &seek, - copier = &copy, - ... - }, + stream = &memstream_vt_r, buf = in, pos = 0, }; - if (mode & io::mode::READ == io::mode::READ) { - s.stream.reader = &read; - }; - if (mode & io::mode::WRITE == io::mode::WRITE) { - s.stream.writer = &fixed_write; + if (mode & io::mode::RDWR == io::mode::RDWR) { + s.stream = &fixed_vt_rw; + } else if (mode & io::mode::WRITE == io::mode::WRITE) { + s.stream = &fixed_vt_w; + } else if (mode & io::mode::READ == io::mode::READ) { + s.stream = &memstream_vt_r; }; return s; }; @@ -52,6 +71,21 @@ fn fixed_write(s: *io::stream, buf: const []u8) (size | io::error) = { return n; }; +const dynamic_vt_w: io::vtable = io::vtable { + seeker = &seek, + copier = &copy, + writer = &dynamic_write, + ... +}; + +const dynamic_vt_rw: io::vtable = io::vtable { + seeker = &seek, + copier = &copy, + reader = &read, + writer = &dynamic_write, + ... +}; + // 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. @@ -66,20 +100,16 @@ export fn dynamic(mode: io::mode) memstream = dynamic_from([], mode); // writes. Like [[dynamic]], calling [[io::close]] will free the buffer. export fn dynamic_from(in: []u8, mode: io::mode) memstream = { let s = memstream { - stream = io::stream { - closer = &dynamic_close, - seeker = &seek, - copier = &copy, - ... - }, + stream = &memstream_vt_r, buf = in, pos = 0, }; - if (mode & io::mode::READ == io::mode::READ) { - s.stream.reader = &read; - }; - if (mode & io::mode::WRITE == io::mode::WRITE) { - s.stream.writer = &dynamic_write; + if (mode & io::mode::RDWR == io::mode::RDWR) { + s.stream = &dynamic_vt_rw; + } else if (mode & io::mode::WRITE == io::mode::WRITE) { + s.stream = &dynamic_vt_w; + } else if (mode & io::mode::READ == io::mode::READ) { + s.stream = &memstream_vt_r; }; return s; }; diff --git a/crypto/blake2b/blake2b.ha b/crypto/blake2b/blake2b.ha @@ -48,6 +48,12 @@ export type digest = struct { keylen: size, }; +const blake2b_vtable: io::vtable = io::vtable { + writer = &write, + closer = &close, + ... +}; + // Creates a [[hash::hash]] which computes a BLAKE2b hash with a given key and a // given hash size. The size must be between 1 and 64, inclusive. If this // function is used to hash sensitive information, the caller should call @@ -62,8 +68,7 @@ export fn blake2b(key: []u8, sz: size) digest = { let keyblock: [BSIZE]u8 = [0...]; keyblock[..len(key)] = key; return digest { - writer = &write, - closer = &close, + stream = &blake2b_vtable, sum = &sum, reset = &reset, sz = sz, diff --git a/crypto/hmac/hmac.ha b/crypto/hmac/hmac.ha @@ -12,6 +12,11 @@ export type state = struct { keypad: []u8, }; +const hmac_vtable: io::vtable = io::vtable { + writer = &write, + ... +}; + // Creates a [[crypto::mac::mac]] that computes an HMAC using the provided hash // function 'h' with given 'key'. The caller must provide a 'buf' of // [[hash::bsz]] bytes. Use the BLOCKSIZE constant of the given hash function to @@ -32,9 +37,9 @@ export fn hmac(h: *hash::hash, key: []u8, buf: []u8) state = { return state { h = h, + stream = &hmac_vtable, sz = hash::sz(h), bsz = bsz, - writer = &write, sum = &gensum, finish = &finish, keypad = keypad, diff --git a/crypto/hmac/sha1.ha b/crypto/hmac/sha1.ha @@ -12,6 +12,11 @@ export type sha1state = struct { keypad: [sha1::BLOCKSIZE]u8, }; +const sha1_vtable: io::vtable = io::vtable { + writer = &sha1write, + ... +}; + // Creates a [[crypto::mac::mac]] that computes an HMAC with given 'key' using // SHA1 as underlying hash function. // @@ -22,9 +27,9 @@ export type sha1state = struct { export fn sha1(key: []u8) sha1state = { let s = sha1state { h = sha1::sha1(), + stream = &sha1_vtable, sz = sha1::SIZE, bsz = sha1::BLOCKSIZE, - writer = &sha1write, sum = &sha1sum, finish = &sha1finish, ... diff --git a/crypto/hmac/sha256.ha b/crypto/hmac/sha256.ha @@ -12,6 +12,11 @@ export type sha256state = struct { keypad: [sha256::BLOCKSIZE]u8, }; +const sha256_vtable: io::vtable = io::vtable { + writer = &sha256write, + ... +}; + // Creates a [[crypto::mac::mac]] that computes an HMAC with given 'key' using // SHA256 as underlying hash function. // @@ -21,10 +26,10 @@ export type sha256state = struct { // information. export fn sha256(key: []u8) sha256state = { let s = sha256state { + stream = &sha256_vtable, h = sha256::sha256(), sz = sha256::SIZE, bsz = sha256::BLOCKSIZE, - writer = &sha256write, sum = &sha256sum, finish = &sha256finish, ... diff --git a/crypto/mac/mac.ha b/crypto/mac/mac.ha @@ -1,11 +1,12 @@ // License: MPL-2.0 // (c) 2022 Armin Preiml <apreiml@strohwolke.at> use io; +// TODO: Use a vtable-based approach like io::stream // The general purpose interface for a MAC function. export type mac = struct { // A stream which only supports writes and never returns errors. - io::stream, + stream: io::stream, // Writes the resulting MAC to 'buf'. Must only be called once, and must // be followed by calling [[finish]]. diff --git a/crypto/poly1305/poly1305.ha b/crypto/poly1305/poly1305.ha @@ -26,12 +26,17 @@ export type state = struct { cidx: size, }; +const poly1305_vtable: io::vtable = io::vtable { + writer = &write, + ... +}; + // Creates a [[crypto::mac::mac]] that computes the poly1305 MAC. It needs to // be initialised using [[init]]. Like the other MACs it needs to be finished // using [[crypto::mac::finish]] after use. export fn poly1305() state = { return state { - writer = &write, + stream = &poly1305_vtable, sum = &sum, finish = &finish, sz = SIZE, diff --git a/crypto/random/random.ha b/crypto/random/random.ha @@ -4,11 +4,13 @@ use io; use rt; -let _stream: io::stream = io::stream { +let _stream_vt: io::vtable = io::vtable { reader = &rand_reader, ... }; +const _stream: io::stream = &_stream_vt; + // An [[io::handle]] which returns cryptographically random data on reads. Be // aware, it may return less than you asked for! export let stream: *io::stream = &_stream; diff --git a/crypto/sha1/sha1.ha b/crypto/sha1/sha1.ha @@ -29,6 +29,12 @@ export type state = struct { ln: size, }; +const sha1_vtable: io::vtable = io::vtable { + writer = &write, + closer = &close, + ... +}; + // Creates a [[hash::hash]] which computes a SHA-1 hash. Note that this // alogorithm is no longer considered secure. Where possible, applications are // encouraged to use [[crypto::sha256]] or [[crypto::sha512]] instead. If this @@ -37,8 +43,7 @@ export type state = struct { // use of [[hash::close]] is optional. export fn sha1() state = { let sha = state { - writer = &write, - closer = &close, + stream = &sha1_vtable, sum = &sum, reset = &reset, sz = SIZE, diff --git a/crypto/sha256/sha256.ha b/crypto/sha256/sha256.ha @@ -46,14 +46,19 @@ export type state = struct { ln: size, }; +const sha256_vtable: io::vtable = io::vtable { + writer = &write, + closer = &close, + ... +}; + // Creates a [[hash::hash]] which computes a SHA-256 hash. If this function is // used to hash sensitive information, the caller should call [[hash::close]] to // erase sentitive data from memory after use; if not, the use of // [[hash::close]] is optional. export fn sha256() state = { let sha = state { - writer = &write, - closer = &close, + stream = &sha256_vtable, sum = &sum, reset = &reset, sz = SIZE, diff --git a/crypto/sha512/sha512.ha b/crypto/sha512/sha512.ha @@ -96,11 +96,16 @@ export fn sha512_256() digest = init(variant::SHA512_256, SIZE256); // [[hash::close]] is optional. export fn sha384() digest = init(variant::SHA384, SIZE384); +const sha512_vtable: io::vtable = io::vtable { + writer = &write, + closer = &close, + ... +}; + // Internal initialization function fn init(var: variant, sz: size) digest = { let sha = digest { - writer = &write, - closer = &close, + stream = &sha512_vtable, sum = &sum, reset = &reset, sz = sz, diff --git a/encoding/base32/base32.ha b/encoding/base32/base32.ha @@ -44,7 +44,7 @@ export fn encoding_init(enc: *encoding, alphabet: str) void = { }; export type encoder = struct { - io::stream, + stream: io::stream, out: io::handle, enc: *encoding, buf: [4]u8, // leftover input @@ -52,6 +52,12 @@ export type encoder = struct { err: (void | io::error), }; +const encoder_vtable: io::vtable = io::vtable { + writer = &encode_writer, + closer = &encode_closer, + ... +}; + // Creates a stream that encodes writes as base-32 before writing them to a // secondary stream. The encoder stream must be closed to finalize any unwritten // bytes. Closing this stream will not close the underlying stream. @@ -60,8 +66,7 @@ export fn new_encoder( out: io::handle, ) encoder = { return encoder { - writer = &encode_writer, - closer = &encode_closer, + stream = &encoder_vtable, out = out, enc = enc, err = void, @@ -222,7 +227,7 @@ export fn encodestr(enc: *encoding, in: []u8) str = { }; export type decoder = struct { - io::stream, + stream: io::stream, in: io::handle, enc: *encoding, avail: []u8, // leftover decoded output @@ -230,6 +235,11 @@ export type decoder = struct { state: (void | io::EOF | io::error), }; +const decoder_vtable: io::vtable = io::vtable { + reader = &decode_reader, + ... +}; + // Creates a stream that reads and decodes base-32 data from a secondary stream. // This stream does not need to be closed, and closing it will not close the // underlying stream. @@ -238,7 +248,7 @@ export fn new_decoder( in: io::handle, ) decoder = { return decoder { - reader = &decode_reader, + stream = &decoder_vtable, in = in, enc = enc, state = void, diff --git a/encoding/base64/base64.ha b/encoding/base64/base64.ha @@ -49,7 +49,7 @@ export fn encoding_init(enc: *encoding, alphabet: str) void = { }; export type encoder = struct { - io::stream, + stream: io::stream, out: io::handle, enc: *encoding, buf: [2]u8, // leftover input @@ -57,6 +57,12 @@ export type encoder = struct { err: (void | io::error), }; +const encoder_vtable: io::vtable = io::vtable { + writer = &encode_writer, + closer = &encode_closer, + ... +}; + // Creates a stream that encodes writes as base64 before writing them to a // secondary stream. The encoder stream must be closed to finalize any unwritten // bytes. Closing this stream will not close the underlying stream. @@ -65,8 +71,7 @@ export fn new_encoder( out: io::handle, ) encoder = { return encoder { - writer = &encode_writer, - closer = &encode_closer, + stream = &encoder_vtable, out = out, enc = enc, err = void, @@ -213,7 +218,7 @@ export fn encodestr(enc: *encoding, in: []u8) str = { }; export type decoder = struct { - io::stream, + stream: io::stream, in: io::handle, enc: *encoding, avail: []u8, // leftover decoded output @@ -221,6 +226,11 @@ export type decoder = struct { state: (void | io::EOF | io::error), }; +const decoder_vtable: io::vtable = io::vtable { + reader = &decode_reader, + ... +}; + // Creates a stream that reads and decodes base 64 data from a secondary stream. // This stream does not need to be closed, and closing it will not close the // underlying stream. @@ -229,7 +239,7 @@ export fn new_decoder( in: io::handle, ) decoder = { return decoder { - reader = &decode_reader, + stream = &decoder_vtable, in = in, enc = enc, state = void, diff --git a/hare/types/+test.ha b/hare/types/+test.ha @@ -332,7 +332,7 @@ fn resolve( assert(htype.align == 8); let t = htype.repr as tagged; assert(len(t) == 3); - assert(t[0].repr as builtin == builtin::INT); - assert(t[1].repr as builtin == builtin::VOID); - assert(t[2].repr as builtin == builtin::STR); + assert(t[0].repr as builtin == builtin::STR); + assert(t[1].repr as builtin == builtin::INT); + assert(t[2].repr as builtin == builtin::VOID); }; diff --git a/hash/adler32/adler32.ha b/hash/adler32/adler32.ha @@ -16,11 +16,16 @@ export type state = struct { b: u32, }; +const adler32_vtable: io::vtable = io::vtable { + writer = &write, + ... +}; + // Creates a [[hash::hash]] which computes the Adler-32 checksum algorithm. This // hash does not allocate any state, so you do not need to call [[hash::close]] // when you are done with it. export fn adler32() state = state { - writer = &write, + stream = &adler32_vtable, sum = &sum, reset = &reset, sz = 4, diff --git a/hash/crc16/crc16.ha b/hash/crc16/crc16.ha @@ -181,6 +181,11 @@ export type state = struct { cval: u16, }; +const crc16_vtable: io::vtable = io::vtable { + writer = &write, + ... +}; + // Creates a [[hash::hash]] which computes the CRC-16 algorithm. This hash // function does not allocate any state, so you do not need to call // [[hash::close]] when you are done with it. @@ -189,7 +194,7 @@ export type state = struct { // [[ccitt_table]], [[dect_table]], or [[ansi_table]]); a table for a // custom polynomial, populated by [[memoize]] function, may also be used. export fn crc16(table: *[256]u16) state = state { - writer = &write, + stream = &crc16_vtable, sum = &sum, reset = &reset, sz = SIZE, diff --git a/hash/crc32/crc32.ha b/hash/crc32/crc32.ha @@ -188,6 +188,11 @@ export type state = struct { cval: u32, }; +const crc32_vtable: io::vtable = io::vtable { + writer = &write, + ... +}; + // Creates a [[hash::hash]] which computes the CRC-32 algorithm. This hash // function does not allocate any state, so you do not need to call // [[hash::close]] when you are done with it. @@ -196,7 +201,7 @@ export type state = struct { // [[ieee_table]], [[castagnoli_table]], or [[koopman_table]]); a table for a // custom polynomial, populated by [[memoize]] function, may also be used. export fn crc32(table: *[256]u32) state = state { - writer = &write, + stream = &crc32_vtable, sum = &sum, reset = &reset, sz = SIZE, diff --git a/hash/crc64/crc64.ha b/hash/crc64/crc64.ha @@ -220,6 +220,11 @@ export type state = struct { cval: u64, }; +const crc64_vtable: io::vtable = io::vtable { + writer = &write, + ... +}; + // Creates a [[hash::hash]] which computes the CRC-64 algorithm. This hash // function does not allocate any state, so you do not need to call // [[hash::close]] when you are done with it. @@ -228,7 +233,7 @@ export type state = struct { // [[iso_table]] or [[ecma_table]]); a table for a custom polynomial, populated // by [[memoize]] function, may also be used. export fn crc64(table: *[256]u64) state = state { - writer = &write, + stream = &crc64_vtable, sum = &sum, reset = &reset, sz = SIZE, diff --git a/hash/fnv/fnv.ha b/hash/fnv/fnv.ha @@ -38,13 +38,18 @@ export fn string64(s: str) u64 = { return sum64(&hash); }; +const fnv32_vtable: io::vtable = io::vtable { + writer = &fnv32_write, + ... +}; + // Creates a [[hash::hash]] which computes the FNV-1 32-bit hash function. This // hash does not allocate any state, so you do not need to call [[hash::close]] // when you're done with it. // // Unless you have a reason to use this, [[fnv32a]] is recommended instead. export fn fnv32() state32 = state32 { - writer = &fnv32_write, + stream = &fnv32_vtable, sum = &fnv32_sum, reset = &fnv32_reset, sz = 4, @@ -56,7 +61,7 @@ export fn fnv32() state32 = state32 { // hash does not allocate any state, so you do not need to call [[hash::close]] // when you're done with it. export fn fnv32a() state32 = state32 { - writer = &fnv32a_write, + stream = &fnv32_vtable, sum = &fnv32_sum, reset = &fnv32_reset, sz = 4, @@ -64,13 +69,18 @@ export fn fnv32a() state32 = state32 { ... }; +const fnv64_vtable: io::vtable = io::vtable { + writer = &fnv64_write, + ... +}; + // Creates a [[hash::hash]] which computes the FNV-1 64-bit hash function. This // hash does not allocate any state, so you do not need to call [[hash::close]] // when you're done with it. // // Unless you have a reason to use this, [[fnv64a]] is recommended instead. export fn fnv64() state64 = state64 { - writer = &fnv64_write, + stream = &fnv64_vtable, sum = &fnv64_sum, reset = &fnv64_reset, sz = 8, @@ -82,7 +92,7 @@ export fn fnv64() state64 = state64 { // hash does not allocate any state, so you do not need to call [[hash::close]] // when you're done with it. export fn fnv64a() state64 = state64 { - writer = &fnv64a_write, + stream = &fnv64_vtable, sum = &fnv64_sum, reset = &fnv64_reset, sz = 8, diff --git a/hash/hash.ha b/hash/hash.ha @@ -4,11 +4,12 @@ // (c) 2021 Kiëd Llaentenn <kiedtl@tilde.team> use io; use fmt; +// TODO: Use a vtable-based approach for this like io::stream // The general purpose interface for a hashing function. export type hash = struct { // A stream which only supports writes and never returns errors. - io::stream, + stream: io::stream, // Returns the current hash. sum: *fn(hash: *hash, buf: []u8) void, diff --git a/io/+test/copy.ha b/io/+test/copy.ha @@ -1,93 +0,0 @@ -// License: MPL-2.0 -// (c) 2021 Bor Grošelj Simić <bor.groseljsimic@telemach.net> -// (c) 2021 Drew DeVault <sir@cmpwn.com> -// (c) 2021 Eyal Sawady <ecs@d2evs.net> -use errors; - -fn test_copier_read(s: *stream, buf: []u8) (size | EOF | error) = { - let stream = s: *teststream; - if (stream.r == 0) { - assert(len(buf) > 42); - stream.nreads += 1; - stream.r = 42; - return 42; - } else { - return EOF; - }; -}; - -fn test_copier_open() teststream = { - let stream = teststream_open(); - stream.reader = &test_copier_read; - return stream; -}; - -fn test_copier_copy(a: *stream, b: *stream) (size | error) = { - assert(a != b); - assert(a.reader == &test_copier_read && b.reader == &test_copier_read); - let stream = a: *teststream; - stream.w = 62893; - return 1337; -}; - -fn test_copy_unsupported(a: *stream, b: *stream) (size | error) = - errors::unsupported; - -@test fn copy() void = { - let a = test_copier_open(), b = test_copier_open(); - match (copy(&b, &a)) { - case let n: size => - assert(n == 42); - assert(a.r == 42); - assert(b.w == 42); - case error => - abort(); - }; - close(&a: *stream); - close(&b: *stream); - - a = test_copier_open(); - b = test_copier_open(); - a.copier = &test_copier_copy; - b.copier = &test_copier_copy; - match (copy(&b, &a)) { - case let n: size => - assert(n == 1337); - assert(b.w == 62893); - case error => - abort(); - }; - close(&a: *stream); - close(&b: *stream); - - // Fallback - a = test_copier_open(); - b = test_copier_open(); - a.copier = &test_copy_unsupported; - b.copier = &test_copy_unsupported; - match (copy(&b, &a)) { - case let n: size => - assert(n == 42); - assert(a.r == 42); - assert(b.w == 42); - case error => - abort(); - }; - - // Fallback (+short writes) - a = test_copier_open(); - b = test_copier_open(); - a.copier = &test_copy_unsupported; - b.copier = &test_copy_unsupported; - b.writer = &teststream_write_short; - match (copy(&b, &a)) { - case let n: size => - assert(n == 42); - assert(a.r == 42); - assert(a.nreads == 1); - assert(b.w == 42); - assert(b.nwrites > 1); - case error => - abort(); - }; -}; diff --git a/io/+test/stream.ha b/io/+test/stream.ha @@ -3,8 +3,14 @@ // (c) 2021 Drew DeVault <sir@cmpwn.com> use strings; +const teststream_vtable: vtable = vtable { + reader = &teststream_read, + writer = &teststream_write, + ... +}; + type teststream = struct { - stream, + stream: stream, r: size, nreads: size, w: size, @@ -12,8 +18,7 @@ type teststream = struct { }; fn teststream_open() teststream = teststream { - reader = &teststream_read, - writer = &teststream_write, + stream = &teststream_vtable, ... }; diff --git a/io/empty.ha b/io/empty.ha @@ -2,12 +2,14 @@ // (c) 2022 Bor Grošelj Simić <bor.groseljsimic@telemach.net> // (c) 2021 Drew DeVault <sir@cmpwn.com> -const _empty: stream = stream { +const _empty_vt: vtable = vtable { reader = &empty_read, writer = &empty_write, ... }; +const _empty: stream = &_empty_vt; + // A [[stream]] which always reads EOF and discards any writes. export const empty: *stream = &_empty; diff --git a/io/filestream.ha b/io/filestream.ha @@ -1,56 +0,0 @@ -// License: MPL-2.0 -// (c) 2021 Alexey Yerin <yyp@disroot.org> -// (c) 2021 Drew DeVault <sir@cmpwn.com> -use errors; - -export type filestream = struct { - stream, - fd: file, -}; - -// Creates a [[filestream]] for a [[file]], for compatibility with -// [[stream]]-oriented APIs. Note that this is generally not thought to be -// necessary for most programs; most APIs should accept [[handle]] instead of -// [[stream]] in order to support both file-oriented and stream-oriented I/O. -export fn fdstream(fd: file) filestream = filestream { - fd = fd, - reader = &fdstream_read, - writer = &fdstream_write, - closer = &fdstream_close, - seeker = &fdstream_seek, - copier = &fdstream_copy, -}; - -fn fdstream_read(st: *stream, buf: []u8) (size | EOF | error) = { - const st = st: *filestream; - assert(st.reader == &fdstream_read); - return fd_read(st.fd, buf); -}; - -fn fdstream_write(st: *stream, buf: const []u8) (size | error) = { - const st = st: *filestream; - assert(st.writer == &fdstream_write); - return fd_write(st.fd, buf); -}; - -fn fdstream_close(st: *stream) void = { - const st = st: *filestream; - assert(st.closer == &fdstream_close); - fd_close(st.fd); -}; - -fn fdstream_seek(st: *stream, off: off, whence: whence) (off | error) = { - const st = st: *filestream; - assert(st.seeker == &fdstream_seek); - return fd_seek(st.fd, off, whence); -}; - -fn fdstream_copy(to: *stream, from: *stream) (size | error) = { - const to = to: *filestream; - const from = from: *filestream; - assert(to.copier == &fdstream_copy); - if (from.copier != &fdstream_copy) { - return errors::unsupported; - }; - return fd_copy(to.fd, from.fd); -}; diff --git a/io/limit.ha b/io/limit.ha @@ -4,17 +4,19 @@ // (c) 2021-2022 Drew DeVault <sir@cmpwn.com> export type limitstream = struct { - stream, + vtable: stream, source: handle, limit: size, }; -fn limitstream_create(source: handle, limit: size) limitstream = { - return limitstream { - source = source, - limit = limit, - ... - }; +const limit_vtable_reader: vtable = vtable { + reader = &limit_read, + ... +}; + +const limit_vtable_writer: vtable = vtable { + writer = &limit_write, + ... }; // Create an overlay stream that only allows a limited amount of bytes to be @@ -22,9 +24,11 @@ fn limitstream_create(source: handle, limit: size) limitstream = { // closing it does not close the underlying stream. Reading any data beyond the // given limit causes the reader to return [[EOF]]. export fn limitreader(source: handle, limit: size) limitstream = { - let stream = limitstream_create(source, limit); - stream.reader = &limit_read; - return stream; + return limitstream { + vtable = &limit_vtable_reader, + source = source, + limit = limit, + }; }; // Create an overlay stream that only allows a limited amount of bytes to be @@ -32,9 +36,11 @@ export fn limitreader(source: handle, limit: size) limitstream = { // closing it does not close the underlying stream. Writing beyond the given // limit causes the writer to return short writes (as few as zero bytes). export fn limitwriter(source: handle, limit: size) limitstream = { - let stream = limitstream_create(source, limit); - stream.writer = &limit_write; - return stream; + return limitstream { + vtable = &limit_vtable_writer, + source = source, + limit = limit, + }; }; fn limit_read(s: *stream, buf: []u8) (size | EOF | error) = { diff --git a/io/stream.ha b/io/stream.ha @@ -1,25 +1,33 @@ // License: MPL-2.0 -// (c) 2021 Drew DeVault <sir@cmpwn.com> +// (c) 2021-2022 Drew DeVault <sir@cmpwn.com> // (c) 2021 Eyal Sawady <ecs@d2evs.net> // (c) 2021 Thomas Bracht Laumann Jespersen <t@laumann.xyz> use errors; -// A stream of bytes which supports some subset of read, write, close, seek, or -// copy operations. To create a custom stream, embed this type as the first -// member of a struct with user-specific data and fill out these fields as -// appropriate. +// A stream is a pointer to a [[vtable]] which allows for user-defined I/O +// abstractions to implement some subset of read, write, seek, close, and other +// I/O functionality in userspace. +export type stream = *vtable; + +// The vtable type defines a set of virtual functions for a [[stream]]. To +// create a custom stream type, embed // // export type my_stream = struct { -// io::stream, +// vtable: io::stream, // fd: int, // }; // +// const my_vtable: io::vtable = io::vtable { +// reader = &my_stream_read, +// writer = &my_stream_write, +// closer = null, +// ... +// }; +// // export fn open(path: str) my_stream = { // let fd = // ... // return my_stream { -// reader = &my_stream_read, -// writer = &my_stream_write, -// closer = null, +// vtable = &my_vtable, // fd = fd, // ... // }); @@ -27,7 +35,7 @@ use errors; // // let stream = open("example"); // io::read(&stream, buf)!; -export type stream = struct { +export type vtable = struct { reader: nullable *reader, writer: nullable *writer, closer: nullable *closer, diff --git a/io/tee.ha b/io/tee.ha @@ -1,19 +1,24 @@ // License: MPL-2.0 -// (c) 2021 Drew DeVault <sir@cmpwn.com> +// (c) 2021-2022 Drew DeVault <sir@cmpwn.com> // (c) 2021 Eyal Sawady <ecs@d2evs.net> export type teestream = struct { - stream, + vtable: stream, source: handle, sink: handle, }; +const tee_vtable: vtable = vtable { + reader = &tee_read, + ... +}; + // Creates a reader which copies reads into a sink before forwarding them to the // caller. This stream does not need to be closed, and closing it will not close // the secondary streams. export fn tee(source: handle, sink: handle) teestream = { return teestream { - reader = &tee_read, + vtable = &tee_vtable, source = source, sink = sink, ... diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib @@ -740,7 +740,6 @@ gensrcs_io() { copy.ha \ drain.ha \ empty.ha \ - filestream.ha \ handle.ha \ limit.ha \ stream.ha \ @@ -756,7 +755,6 @@ gensrcs_io() { copy.ha \ drain.ha \ empty.ha \ - filestream.ha \ handle.ha \ limit.ha \ stream.ha \ @@ -772,7 +770,6 @@ io() { gensrcs_io else gensrcs_io \ - +test/copy.ha \ +test/limit.ha \ +test/stream.ha fi diff --git a/stdlib.mk b/stdlib.mk @@ -1273,7 +1273,6 @@ stdlib_io_linux_srcs= \ $(STDLIB)/io/copy.ha \ $(STDLIB)/io/drain.ha \ $(STDLIB)/io/empty.ha \ - $(STDLIB)/io/filestream.ha \ $(STDLIB)/io/handle.ha \ $(STDLIB)/io/limit.ha \ $(STDLIB)/io/stream.ha \ @@ -1290,7 +1289,6 @@ stdlib_io_freebsd_srcs= \ $(STDLIB)/io/copy.ha \ $(STDLIB)/io/drain.ha \ $(STDLIB)/io/empty.ha \ - $(STDLIB)/io/filestream.ha \ $(STDLIB)/io/handle.ha \ $(STDLIB)/io/limit.ha \ $(STDLIB)/io/stream.ha \ @@ -3250,14 +3248,12 @@ testlib_io_linux_srcs= \ $(STDLIB)/io/copy.ha \ $(STDLIB)/io/drain.ha \ $(STDLIB)/io/empty.ha \ - $(STDLIB)/io/filestream.ha \ $(STDLIB)/io/handle.ha \ $(STDLIB)/io/limit.ha \ $(STDLIB)/io/stream.ha \ $(STDLIB)/io/tee.ha \ $(STDLIB)/io/types.ha \ $(STDLIB)/io/util.ha \ - $(STDLIB)/io/+test/copy.ha \ $(STDLIB)/io/+test/limit.ha \ $(STDLIB)/io/+test/stream.ha @@ -3270,14 +3266,12 @@ testlib_io_freebsd_srcs= \ $(STDLIB)/io/copy.ha \ $(STDLIB)/io/drain.ha \ $(STDLIB)/io/empty.ha \ - $(STDLIB)/io/filestream.ha \ $(STDLIB)/io/handle.ha \ $(STDLIB)/io/limit.ha \ $(STDLIB)/io/stream.ha \ $(STDLIB)/io/tee.ha \ $(STDLIB)/io/types.ha \ $(STDLIB)/io/util.ha \ - $(STDLIB)/io/+test/copy.ha \ $(STDLIB)/io/+test/limit.ha \ $(STDLIB)/io/+test/stream.ha diff --git a/strio/stream.ha b/strio/stream.ha @@ -5,6 +5,11 @@ use io; use slices; use strings; +const fixed_vtable: io::vtable = io::vtable { + writer = &fixed_write, + ... +}; + export type stream = struct { stream: io::stream, buf: []u8, @@ -27,10 +32,7 @@ export fn reset(in: *stream) void = { // doesn't need to be closed. export fn fixed(in: []u8) stream = { return stream { - stream = io::stream { - writer = &fixed_write, - ... - }, + stream = &fixed_vtable, buf = in[..0], }; }; @@ -54,6 +56,12 @@ fn fixed_write(s: *io::stream, buf: const []u8) (size | io::error) = { assert(string(&stream) == "hello world"); }; +const dynamic_vtable: io::vtable = io::vtable { + writer = &dynamic_write, + closer = &dynamic_close, + ... +}; + // Creates a write-only string stream using an allocated buffer for storage, for // efficiently building strings. // @@ -62,11 +70,7 @@ fn fixed_write(s: *io::stream, buf: const []u8) (size | io::error) = { // freed. export fn dynamic() stream = { return stream { - stream = io::stream { - writer = &dynamic_write, - closer = &dynamic_close, - ... - }, + stream = &dynamic_vtable, buf = [], }; };