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:
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 = ©,
+ reader = &read,
+ ...
+};
+
+const fixed_vt_w: io::vtable = io::vtable {
+ seeker = &seek,
+ copier = ©,
+ writer = &fixed_write,
+ ...
+};
+
+const fixed_vt_rw: io::vtable = io::vtable {
+ seeker = &seek,
+ copier = ©,
+ 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 = ©,
- ...
- },
+ 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 = ©,
+ writer = &dynamic_write,
+ ...
+};
+
+const dynamic_vt_rw: io::vtable = io::vtable {
+ seeker = &seek,
+ copier = ©,
+ 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 = ©,
- ...
- },
+ 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 = [],
};
};