hare

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

commit 0df9af142931b1150589e6f8c52e73b2c8092d4c
parent 1809e28695c9ed52c59b9571dafea3f794dbb4dc
Author: Drew DeVault <sir@cmpwn.com>
Date:   Sat, 16 Oct 2021 10:58:05 +0200

all: introduce io::handle and refactor usage

The goal of this round of refactoring is to simplify the API a bit and
reduce the ABI footprint of io::file (and io::handle).

Signed-off-by: Drew DeVault <sir@cmpwn.com>

Diffstat:
Mbufio/buffered.ha | 71+++++++++++++++++++++++++++--------------------------------------------
Mbufio/memstream.ha | 61++++++++++++++++++++++++++++---------------------------------
Mbufio/scanner.ha | 22++++++++++++----------
Mcmd/harec/context.ha | 4++--
Mcmd/harec/errors.ha | 6+++---
Mcmd/harec/gen.ha | 2+-
Mcmd/harec/main.ha | 7+++----
Mcmd/harec/qbe.ha | 4++--
Mcmd/haredoc/docstr.ha | 4++--
Mcmd/haredoc/hare.ha | 6+++---
Mcmd/haredoc/html.ha | 20++++++++++----------
Mcmd/haredoc/main.ha | 12++++++++----
Mcmd/haredoc/tty.ha | 10+++++-----
Mcompress/flate/inflate.ha | 6+++---
Mcompress/zlib/.testdata/gen.ha | 44++++++++++++++++----------------------------
Mcompress/zlib/reader.ha | 10+++++-----
Mcrypto/random/random.ha | 3+--
Mencoding/base64/README | 4++--
Mencoding/base64/base64.ha | 14+++++++-------
Mencoding/hex/hex.ha | 10+++++-----
Mfmt/fmt.ha | 40++++++++++++++++++++++------------------
Mformat/ini/scan.ha | 8++++----
Mformat/ini/types.ha | 4++--
Mformat/xml/parser.ha | 15++++++++-------
Mformat/xml/types.ha | 10+++++-----
Mfs/fs.ha | 4++--
Mfs/mem/+test.ha | 22++++++++++------------
Mfs/mem/mem.ha | 8++++----
Mfs/mem/stream.ha | 1-
Mfs/types.ha | 4++--
Mgetopt/getopts.ha | 36++++++++++++++++++------------------
Mhare/lex/README | 2+-
Mhare/lex/lex.ha | 6+++---
Mhare/module/manifest.ha | 26++++++++++++++------------
Mhare/unparse/decl.ha | 2+-
Mhare/unparse/expr.ha | 12++++++------
Mhare/unparse/ident.ha | 2+-
Mhare/unparse/import.ha | 2+-
Mhare/unparse/type.ha | 6+++---
Mhare/unparse/unit.ha | 2+-
Mhare/unparse/util.ha | 2+-
Mio/+linux/file.ha | 146++++++++++++++++++++-----------------------------------------------------------
Mio/+test/stream.ha | 1-
Aio/README | 23+++++++++++++++++++++++
Mio/copy.ha | 26++++++++++++++++++++++----
Aio/empty.ha | 12++++++++++++
Aio/filestream.ha | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Aio/handle.ha | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mio/limit.ha | 11++---------
Mio/stream.ha | 59+++++++++--------------------------------------------------
Mio/tee.ha | 13+++----------
Mnet/+linux.ha | 6+-----
Mnet/dns/query.ha | 16++++++++--------
Mnet/ip/ip.ha | 10+++++-----
Mnet/tcp/+linux.ha | 8+++-----
Mnet/udp/+linux.ha | 21+++++++++------------
Mnet/unix/+linux.ha | 4+---
Mos/+linux/dirfdfs.ha | 12++++++------
Mos/+linux/stdfd.ha | 39+++++++++++++++++++++++++--------------
Dos/stdfd.ha | 10----------
Mscripts/gen-stdlib | 6++++--
Mstdlib.mk | 12++++++++----
Mstrio/README | 2+-
Mstrio/dynamic.ha | 32++++++++++++++++++--------------
Mstrio/fixed.ha | 23++++++++++++++---------
Mstrio/ops.ha | 32++++++++++++++++----------------
Mtemp/+linux.ha | 14++++++++------
Munix/hosts/lookup.ha | 4++--
Munix/passwd/group.ha | 12++++++------
Munix/passwd/passwd.ha | 12++++++------
Munix/poll/+linux.ha | 6+++---
Munix/resolvconf/load.ha | 4++--
Munix/tty/+linux/isatty.ha | 8+-------
Munix/tty/+linux/winsize.ha | 12+++---------
Muuid/uuid.ha | 12++++++------
75 files changed, 626 insertions(+), 625 deletions(-)

diff --git a/bufio/buffered.ha b/bufio/buffered.ha @@ -6,7 +6,7 @@ use strings; export type bufstream = struct { stream: io::stream, - source: *io::stream, + source: io::handle, rbuffer: []u8, wbuffer: []u8, ravail: size, @@ -31,10 +31,10 @@ export type bufstream = struct { // let wbuf: [os::BUFSIZ]u8 = [0...]; // let buffered = bufio::buffered(source, rbuf, wbuf); export fn buffered( - src: *io::stream, + src: io::handle, rbuf: []u8, wbuf: []u8, -) *io::stream = { +) io::handle = { let s = alloc(bufstream { ... }); let st = static_buffered(src, rbuf, wbuf, s); st.closer = &buffered_close; @@ -42,7 +42,7 @@ export fn buffered( }; export fn static_buffered( - src: *io::stream, + src: io::handle, rbuf: []u8, wbuf: []u8, s: *bufstream, @@ -50,9 +50,7 @@ export fn static_buffered( static let flush_default = ['\n': u32: u8]; *s = bufstream { stream = io::stream { - name = src.name, closer = &buffered_close_static, - unwrap = &buffered_unwrap, ... }, source = src, @@ -74,11 +72,14 @@ export fn static_buffered( return &s.stream; }; +fn getstream(in: io::handle) *bufstream = { + assert(isbuffered(in), "Attempted to use bufio on unbuffered stream"); + return in as *io::stream: *bufstream; +}; + // Flushes pending writes to the underlying stream. -export fn flush(s: *io::stream) (io::error | void) = { - assert(s.writer == &buffered_write, - "bufio::flushed used on non-buffered stream"); - let s = s: *bufstream; +export fn flush(s: io::handle) (io::error | void) = { + let s = getstream(s); if (s.wavail == 0) { return; }; @@ -89,48 +90,36 @@ export fn flush(s: *io::stream) (io::error | void) = { // Sets the list of bytes which will cause the stream to flush when written. By // default, the stream will flush when a newline (\n) is written. -export fn setflush(s: *io::stream, b: []u8) void = { - assert(s.writer == &buffered_write, - "bufio: setflush used on non-buffered stream"); - let s = s: *bufstream; +export fn setflush(s: io::handle, b: []u8) void = { + let s = getstream(s); s.flush = b; }; -// Returns true if this is a buffered stream. -export fn isbuffered(s: *io::stream) bool = { - return s.reader == &buffered_read || s.writer == &buffered_write; -}; - -// Returns true if this stream or any underlying streams are buffered. -export fn any_isbuffered(s: *io::stream) bool = { - for (!isbuffered(s)) { - s = match (io::source(s)) { - case errors::unsupported => - return false; - case s: *io::stream => - yield s; - }; - }; - return true; -}; - // Unreads a slice of bytes. The next read calls on this buffer will consume the // un-read data before consuming further data from the underlying source, or any // buffered data. -export fn unread(s: *io::stream, buf: []u8) void = { - assert(isbuffered(s), "bufio: unread used on non-buffered stream"); - let s = s: *bufstream; +export fn unread(s: io::handle, buf: []u8) void = { + let s = getstream(s); append(s.unread, buf...); }; // Unreads a rune; see [[unread]]. -export fn unreadrune(s: *io::stream, rn: rune) void = { - assert(isbuffered(s), "bufio: unread used on non-buffered stream"); - let s = s: *bufstream; +export fn unreadrune(s: io::handle, rn: rune) void = { + let s = getstream(s); const buf = utf8::encoderune(rn); insert(s.unread[0], buf...); }; +// Returns true if an [[io::handle]] is a [[buffered]] stream. +export fn isbuffered(in: io::handle) bool = { + match (in) { + case io::file => + return false; + case st: *io::stream => + return st.reader == &buffered_read || st.writer == &buffered_write; + }; +}; + fn buffered_close(s: *io::stream) void = { assert(s.closer == &buffered_close); if (s.writer != null) { @@ -146,12 +135,6 @@ fn buffered_close_static(s: *io::stream) void = { }; }; -fn buffered_unwrap(s: *io::stream) *io::stream = { - assert(s.unwrap == &buffered_unwrap); - let s = s: *bufstream; - return s.source; -}; - fn buffered_read(s: *io::stream, buf: []u8) (size | io::EOF | io::error) = { assert(s.reader == &buffered_read); let s = s: *bufstream; diff --git a/bufio/memstream.ha b/bufio/memstream.ha @@ -15,7 +15,6 @@ type memstream = struct { export fn fixed(in: []u8, mode: io::mode) *io::stream = { let s = alloc(memstream { stream = io::stream { - name = "<bufio::fixed>", closer = &fixed_close, ... }, @@ -65,7 +64,6 @@ export fn dynamic(mode: io::mode) *io::stream = dynamic_from([], mode); 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, ... @@ -82,62 +80,59 @@ export fn dynamic_from(in: []u8, mode: io::mode) *io::stream = { return s; }; -fn dynamic_write(s: *io::stream, buf: const []u8) (size | io::error) = { - let s = s: *memstream; - insert(s.buf[s.pos], buf...); - s.pos += len(buf); - return len(buf); -}; - -fn dynamic_close(s: *io::stream) void = { - const s = s: *memstream; - free(s.buf); - free(s); +fn getmemstream(in: io::handle) *memstream = { + match (in) { + case io::file => + abort("Invalid use of bufio with unbuffered file"); + case st: *io::stream => + assert(st.closer == &dynamic_close); + return st: *memstream; + }; }; // 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::dynamic stream"); - }; - let s = s: *memstream; +export fn finish(in: io::handle) []u8 = { + let s = getmemstream(in); 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::dynamic stream"); - }; - let s = s: *memstream; +export fn buffer(in: io::handle) []u8 = { + const s = getmemstream(in); 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::dynamic stream"); - }; - const s = s: *memstream; +export fn reset(in: io::handle) void = { + const s = getmemstream(in); 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; +export fn truncate(in: io::handle) (void | errors::unsupported) = { + let s = getmemstream(in); s.pos = 0; delete(s.buf[..]); }; +fn dynamic_write(s: *io::stream, buf: const []u8) (size | io::error) = { + let s = s: *memstream; + insert(s.buf[s.pos], buf...); + s.pos += len(buf); + return len(buf); +}; + +fn dynamic_close(s: *io::stream) void = { + const s = s: *memstream; + free(s.buf); + free(s); +}; fn read(s: *io::stream, buf: []u8) (size | io::EOF | io::error) = { let s = s: *memstream; diff --git a/bufio/scanner.ha b/bufio/scanner.ha @@ -4,11 +4,11 @@ use io; use strings; use types; -// Reads a single byte from the stream. -export fn scanbyte(stream: *io::stream) (u8 | io::EOF | io::error) = { +// Reads a single byte from an [[io::handle]]. +export fn scanbyte(file: io::handle) (u8 | io::EOF | io::error) = { let buf: [1]u8 = [0...]; - match (io::read(stream, buf)?) { + match (io::read(file, buf)?) { case read: size => if (read > 0) { return buf[0]; @@ -22,11 +22,11 @@ export fn scanbyte(stream: *io::stream) (u8 | io::EOF | io::error) = { // Reads a slice of bytes until the delimiter. Delimiter is not included. The // return value must be freed by the caller. -export fn scantok(stream: *io::stream, delim: u8...) ([]u8 | io::EOF | io::error) = { +export fn scantok(file: io::handle, delim: u8...) ([]u8 | io::EOF | io::error) = { let buf: []u8 = []; for (true) { - match (scanbyte(stream)?) { + match (scanbyte(file)?) { case res: u8 => if (bytes::contains(delim, res)) { break; @@ -45,13 +45,15 @@ export fn scantok(stream: *io::stream, delim: u8...) ([]u8 | io::EOF | io::error // Reads a slice of bytes until a newline character (\n, 0x10). Newline itself // is not included. The return value must be freed by the caller. -export fn scanline(stream: *io::stream) ([]u8 | io::EOF | io::error) = - scantok(stream, '\n': u32: u8); +export fn scanline(file: io::handle) ([]u8 | io::EOF | io::error) = + scantok(file, '\n': u32: u8); // Reads a rune from a UTF-8 stream. -export fn scanrune(stream: *io::stream) (rune | utf8::invalid | io::EOF | io::error) = { +export fn scanrune( + file: io::handle, +) (rune | utf8::invalid | io::EOF | io::error) = { let b: [4]u8 = [0...]; - match (io::read(stream, b[..1])?) { + match (io::read(file, b[..1])?) { case n: size => assert(n == 1); case io::EOF => @@ -67,7 +69,7 @@ export fn scanrune(stream: *io::stream) (rune | utf8::invalid | io::EOF | io::er return b[0]: u32: rune; }; - match (io::read(stream, b[1..sz])?) { + match (io::read(file, b[1..sz])?) { case n: size => assert(n == sz - 1); case io::EOF => diff --git a/cmd/harec/context.ha b/cmd/harec/context.ha @@ -3,8 +3,8 @@ use hare::types; use hare::unit; type context = struct { - out: *io::stream, - buf: *io::stream, + out: io::handle, + buf: io::handle, store: *types::typestore, unit: *unit::unit, arch: struct { diff --git a/cmd/harec/errors.ha b/cmd/harec/errors.ha @@ -19,17 +19,17 @@ fn printerr(err: parse::error) void = { fn printerr_syntax(err: lex::syntax) void = { let location = err.0, details = err.1; let file = os::open(location.path)!; - defer io::close(&file); + defer io::close(file); let line = 1u; for (line < location.line) { - let r = bufio::scanrune(&file) as rune; + let r = bufio::scanrune(file) as rune; if (r == '\n') { line += 1u; }; }; - let line = bufio::scanline(&file) as []u8; + let line = bufio::scanline(file) as []u8; defer free(line); let line = strings::fromutf8(line); fmt::errorfln("{}:{},{}: Syntax error: {}", diff --git a/cmd/harec/gen.ha b/cmd/harec/gen.ha @@ -11,7 +11,7 @@ use io; use os; use strings; -fn gen(out: *io::stream, store: *types::typestore, unit: *unit::unit) void = { +fn gen(out: io::handle, store: *types::typestore, unit: *unit::unit) void = { // TODO: context_init let ctx = context { out = out, diff --git a/cmd/harec/main.ha b/cmd/harec/main.ha @@ -26,7 +26,6 @@ export fn main() void = { defer getopt::finish(&cmd); let out = os::stdout; - for (let i = 0z; i < len(cmd.opts); i += 1) { let opt = cmd.opts[i]; switch (opt.0) { @@ -36,7 +35,7 @@ export fn main() void = { case 'o' => out = match (os::create(opt.1, 0o644)) { case f: io::file => - yield &f; + yield f; case e: fs::error => fmt::fatal(fs::strerror(e)); }; @@ -67,9 +66,9 @@ export fn main() void = { fmt::fatal("Error opening {}: {}", cmd.args[i], fs::strerror(err)); }; - defer io::close(&input); + defer io::close(input); static let buf: [os::BUFSIZ]u8 = [0...]; - let bufin = bufio::buffered(&input, buf, []); + let bufin = bufio::buffered(input, buf, []); defer io::close(bufin); let lexer = lex::init(bufin, cmd.args[i]); diff --git a/cmd/harec/qbe.ha b/cmd/harec/qbe.ha @@ -22,7 +22,7 @@ const vvoid: value = value { }; fn emit( - to: *io::stream, + to: io::handle, out: (qtypeval | void), instr: qinstr, args: (value | qval | qtype)... @@ -230,7 +230,7 @@ fn emit( fmt::fprintln(to)!; }; -fn qval_emit(to: *io::stream, qv: qval) void = { +fn qval_emit(to: io::handle, qv: qval) void = { match (qv) { case g: global => fmt::fprintf(to, " ${}", g)!; diff --git a/cmd/haredoc/docstr.ha b/cmd/haredoc/docstr.ha @@ -21,11 +21,11 @@ type docstate = enum { }; type parser = struct { - src: *io::stream, + src: io::handle, state: docstate, }; -fn parsedoc(in: *io::stream) parser = { +fn parsedoc(in: io::handle) parser = { static let buf: [4096]u8 = [0...]; return parser { src = bufio::buffered(in, buf[..], []), diff --git a/cmd/haredoc/hare.ha b/cmd/haredoc/hare.ha @@ -17,7 +17,7 @@ fn emit_hare(ctx: *context) (void | error) = { case readme: io::file => first = false; for (true) { - match (bufio::scanline(&readme)?) { + match (bufio::scanline(readme)?) { case io::EOF => break; case b: []u8 => fmt::printfln("// {}", strings::fromutf8(b))?; @@ -87,7 +87,7 @@ fn details_hare(ctx: *context, decl: ast::decl) (void | error) = { }; // Forked from [[hare::unparse]] -fn unparse_hare(out: *io::stream, d: ast::decl) (size | io::error) = { +fn unparse_hare(out: io::handle, d: ast::decl) (size | io::error) = { let n = 0z; match (d.decl) { case g: []ast::decl_global => @@ -154,7 +154,7 @@ fn unparse_hare(out: *io::stream, d: ast::decl) (size | io::error) = { }; fn prototype_hare( - out: *io::stream, + out: io::handle, indent: size, t: ast::func_type, ) (size | io::error) = { diff --git a/cmd/haredoc/html.ha b/cmd/haredoc/html.ha @@ -12,9 +12,9 @@ use path; use strings; use strio; -// Prints a string to an output stream, escaping any of HTML's reserved +// Prints a string to an output handle, escaping any of HTML's reserved // characters. -fn html_escape(out: *io::stream, in: str) (size | io::error) = { +fn html_escape(out: io::handle, in: str) (size | io::error) = { let z = 0z; let iter = strings::iter(in); for (true) { @@ -85,7 +85,7 @@ fn emit_html(ctx: *context) (void | error) = { case void => void; case f: io::file => fmt::println("<div class='readme'>")?; - markup_html(ctx, &f)?; + markup_html(ctx, f)?; fmt::println("</div>")?; }; @@ -305,7 +305,7 @@ fn htmlref(ctx: *context, ref: ast::ident) (void | io::error) = { free(ident); }; -fn markup_html(ctx: *context, in: *io::stream) (void | io::error) = { +fn markup_html(ctx: *context, in: io::handle) (void | io::error) = { let parser = parsedoc(in); for (true) { const tok = match (scandoc(&parser)) { @@ -352,7 +352,7 @@ fn markup_html(ctx: *context, in: *io::stream) (void | io::error) = { }; // Forked from [[hare::unparse]] -fn unparse_html(out: *io::stream, d: ast::decl) (size | io::error) = { +fn unparse_html(out: io::handle, d: ast::decl) (size | io::error) = { let n = 0z; match (d.decl) { case c: []ast::decl_const => @@ -462,7 +462,7 @@ fn builtin_type(b: ast::builtin_type) str = { }; // Forked from [[hare::unparse]]. -fn newline(out: *io::stream, indent: size) (size | io::error) = { +fn newline(out: io::handle, indent: size) (size | io::error) = { let n = 0z; n += fmt::fprint(out, "\n")?; for (let i = 0z; i < indent; i += 1) { @@ -472,7 +472,7 @@ fn newline(out: *io::stream, indent: size) (size | io::error) = { }; fn enum_html( - out: *io::stream, + out: io::handle, indent: size, t: ast::enum_type ) (size | io::error) = { @@ -505,7 +505,7 @@ fn enum_html( }; fn struct_union_html( - out: *io::stream, + out: io::handle, indent: size, t: ast::_type, brief: bool, @@ -553,7 +553,7 @@ fn struct_union_html( }; fn type_html( - out: *io::stream, + out: io::handle, indent: size, _type: ast::_type, brief: bool, @@ -668,7 +668,7 @@ fn type_html( }; fn prototype_html( - out: *io::stream, + out: io::handle, indent: size, t: ast::func_type, brief: bool, diff --git a/cmd/haredoc/main.ha b/cmd/haredoc/main.ha @@ -32,7 +32,11 @@ type context = struct { }; export fn main() void = { - let fmt = if (tty::isatty(os::stdout)) format::TTY else format::HARE; + let fmt = + if (tty::isatty(os::stdout_file)) + format::TTY + else + format::HARE; let template = true; let show_undocumented = false; const help: [_]getopt::help = [ @@ -139,7 +143,7 @@ export fn main() void = { defer match (readme) { case void => void; case f: io::file => - io::close(&f); + io::close(f); }; if (decl != "") { @@ -212,8 +216,8 @@ fn scan(path: str) (ast::subunit | error) = { case err: fs::error => fmt::fatal("Error reading {}: {}", path, fs::strerror(err)); }; - defer io::close(&input); - const lexer = lex::init(&input, path, lex::flags::COMMENTS); + defer io::close(input); + const lexer = lex::init(input, path, lex::flags::COMMENTS); return parse::subunit(&lexer)?; }; diff --git a/cmd/haredoc/tty.ha b/cmd/haredoc/tty.ha @@ -17,7 +17,7 @@ fn emit_tty(ctx: *context) (void | error) = { match (ctx.readme) { case readme: io::file => - for (true) match (bufio::scanline(&readme)?) { + for (true) match (bufio::scanline(readme)?) { case io::EOF => break; case b: []u8 => firstline = false; @@ -88,7 +88,7 @@ fn details_tty(ctx: *context, decl: ast::decl) (void | error) = { }; // Forked from [[hare::unparse]] -fn unparse_tty(out: *io::stream, d: ast::decl) (size | io::error) = { +fn unparse_tty(out: io::handle, d: ast::decl) (size | io::error) = { let n = 0z; match (d.decl) { case g: []ast::decl_global => @@ -163,7 +163,7 @@ fn unparse_tty(out: *io::stream, d: ast::decl) (size | io::error) = { }; fn prototype_tty( - out: *io::stream, + out: io::handle, indent: size, t: ast::func_type, ) (size | io::error) = { @@ -194,7 +194,7 @@ fn prototype_tty( // Forked from [[hare::unparse]] fn struct_union_type_tty( - out: *io::stream, + out: io::handle, indent: size, t: ast::_type, ) (size | io::error) = { @@ -243,7 +243,7 @@ fn struct_union_type_tty( // Forked from [[hare::unparse]] fn type_tty( - out: *io::stream, + out: io::handle, indent: size, t: ast::_type, ) (size | io::error) = { diff --git a/compress/flate/inflate.ha b/compress/flate/inflate.ha @@ -36,7 +36,7 @@ type inflate_err = enum u8 { export type decompressor = struct { io::stream, - in: *io::stream, + in: io::handle, bitbuf: u32, cnt: u32, final: bool, @@ -77,10 +77,10 @@ export type decompressor = struct { }; // Creates a stream which decompresses Deflate (RFC 1951) data. -export fn inflate(s: *io::stream) decompressor = decompressor { +export fn inflate(src: io::handle) decompressor = decompressor { reader = &read, closer = &close, - in = s, + in = src, bitbuf = 0, cnt = 0, final = false, diff --git a/compress/zlib/.testdata/gen.ha b/compress/zlib/.testdata/gen.ha @@ -5,16 +5,16 @@ use io; use os; fn write(name: str, buf: []u8) void = { - fmt::printfln("const {}: []u8 = [", name); + fmt::printfln("const {}: []u8 = [", name)!; for (let i = 0z; i < len(buf); i += 1) { - fmt::print("\t"); + fmt::print("\t")!; for (let j = 0z; j < 11 && i < len(buf) - 1; j += 1) { - fmt::printf("0x{:02X}, ", buf[i]); + fmt::printf("0x{:02X}, ", buf[i])!; i += 1; }; - fmt::printfln("0x{:02X},", buf[i]); + fmt::printfln("0x{:02X},", buf[i])!; }; - fmt::println("];\n"); + fmt::println("];\n")!; }; export fn main() void = { @@ -25,38 +25,26 @@ export fn main() void = { ]; for (let i = 0z; i < len(vectors); i += 1) { - let in = match (os::open(vectors[i].0, fs::flags::RDONLY)) { - s: *io::stream => s, - e: fs::error => fmt::fatal(fs::strerror(e)), - }; + const in = os::open(vectors[i].0)!; defer io::close(in); - let ins = bufio::dynamic(io::mode::WRITE); - match (io::copy(ins, in)) { - size => void, - e: io::error => fmt::fatal(io::strerror(e)), - }; - let inb = bufio::finish(ins); + const ins = bufio::dynamic(io::mode::WRITE); + io::copy(ins, in)!; + const inb = bufio::finish(ins); defer free(inb); write(vectors[i].0, inb); - let out = match (os::open(vectors[i].1, fs::flags::RDONLY)) { - s: *io::stream => s, - e: fs::error => fmt::fatal(fs::strerror(e)), - }; + const out = os::open(vectors[i].1)!; defer io::close(out); - let outs = bufio::dynamic(io::mode::WRITE); - match (io::copy(outs, out)) { - size => void, - e: io::error => fmt::fatal(io::strerror(e)), - }; - let outb = bufio::finish(ins); + const outs = bufio::dynamic(io::mode::WRITE); + io::copy(outs, out)!; + const outb = bufio::finish(ins); defer free(outb); write(vectors[i].1, outb); }; - fmt::printfln("const vectors: [_](*[]u8, *[]u8) = ["); + fmt::printfln("const vectors: [_](*[]u8, *[]u8) = [")!; for (let i = 0z; i < len(vectors); i += 1) { - fmt::printfln("\t(&{}, &{}),", vectors[i].0, vectors[i].1); + fmt::printfln("\t(&{}, &{}),", vectors[i].0, vectors[i].1)!; }; - fmt::println("];"); + fmt::println("];")!; }; diff --git a/compress/zlib/reader.ha b/compress/zlib/reader.ha @@ -17,7 +17,7 @@ def FLEVEL: u8 = 0b11000000; export type reader = struct { io::stream, - source: *io::stream, + source: io::handle, flate: flate::decompressor, hash: adler32::state, }; @@ -85,10 +85,10 @@ fn close(s: *io::stream) void = { }; // Creates a stream which decompresses zlib (RFC 1950) data. -export fn decompress(s: *io::stream) (reader | io::error) = { +export fn decompress(src: io::handle) (reader | io::error) = { let buf: [2]u8 = [0...]; for (let n = 0z; n < len(buf)) { - match (io::read(s, buf[n..])?) { + match (io::read(src, buf[n..])?) { case io::EOF => return wraperror(decompress_err::EOF); case z: size => @@ -115,8 +115,8 @@ export fn decompress(s: *io::stream) (reader | io::error) = { return reader { reader = &read, closer = &close, - source = s, - flate = flate::inflate(s), + source = src, + flate = flate::inflate(src), hash = adler32::adler32(), ... }; diff --git a/crypto/random/random.ha b/crypto/random/random.ha @@ -2,12 +2,11 @@ use io; use rt; let _stream: io::stream = io::stream { - name = "<random>", reader = &rand_reader, ... }; -// An [[io::stream]] which returns cryptographically random data on reads. Be +// 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/encoding/base64/README b/encoding/base64/README @@ -1,9 +1,9 @@ Implementation of the base 64 encoding as defined by RFC 4648. There are various functions available for decoding and encoding. The decode -family accepts an [[io::stream]] as input, while the decodeslice and decodestr +family accepts an [[io::handle]] as input, while the decodeslice and decodestr family of functions accept slices and strings as input, respectively. -[[decode]] accepts an [[io::stream]] for the output, and [[decodeslice]] and +[[decode]] accepts an [[io::handle]] for the output, and [[decodeslice]] and [[decodestr]] dynamically allocate a slice or string to write the output to, and return it to the caller (who is then responsible for freeing it). The _static family of functions, such as [[decode_static]], accept a caller-allocated slice diff --git a/encoding/base64/base64.ha b/encoding/base64/base64.ha @@ -38,10 +38,10 @@ export def PADDING: u8 = '=': u32: u8; export type invalid = !size; // Encodes a byte slice using a base 64 encoding alphabet, with padding, and -// writes it to an [[io::stream]]. The number of bytes written is returned. +// writes it to an [[io::handle]]. The number of bytes written is returned. export fn encode( alphabet: []u8, - sink: *io::stream, + sink: io::handle, b: []u8 ) (size | io::error) = { let z = 0z; @@ -101,11 +101,11 @@ export fn encodestr(alphabet: []u8, b: []u8) str = { }; // Decodes base 64-encoded data in the given base 64 alphabet, with padding, -// from an [[io::stream]]. The number of bytes written is returned. +// from an [[io::handle]]. The number of bytes written is returned. export fn decode( alphabet: []u8, - in: *io::stream, - out: *io::stream, + in: io::handle, + out: io::handle, ) (size | invalid | io::error) = { const INVALID_OR_PAD = 255u8; let decoder: [256]u8 = [INVALID_OR_PAD...]; @@ -181,11 +181,11 @@ export fn decode( }; // Decodes base 64-encoded data in the given base 64 alphabet, with padding, -// from an [[io::stream]]. The number of bytes written is returned. +// from an [[io::handle]]. The number of bytes written is returned. export fn decode_static( alphabet: []u8, out: []u8, - in: *io::stream, + in: io::handle, ) (size | invalid) = { let buf = bufio::fixed(out, io::mode::WRITE); defer io::close(buf); diff --git a/encoding/hex/hex.ha b/encoding/hex/hex.ha @@ -10,8 +10,8 @@ use strio; // characters. export type invalid = !void; -// Encodes a byte slice as a hexadecimal string and writes it to a stream. -export fn encode(sink: *io::stream, b: []u8) (size | io::error) = { +// Encodes a byte slice as a hexadecimal string and writes it to an I/O handle. +export fn encode(sink: io::handle, b: []u8) (size | io::error) = { let z = 0z; for (let i = 0z; i < len(b); i += 1) { let s = strconv::u8tosb(b[i], strconv::base::HEX_LOWER); @@ -67,14 +67,14 @@ export fn decode(s: str) ([]u8 | invalid) = { decode("this is not hex") as invalid: void; }; -// Outputs a dump of hex data to a stream alongside the offset and an ASCII -// representation (if applicable). +// Outputs a dump of hex data alongside the offset and an ASCII representation +// (if applicable). // // Example output: // // 00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............| // 00000010 03 00 3e 00 01 00 00 00 80 70 01 00 00 00 00 00 |..>......p......| -export fn dump(out: *io::stream, data: []u8) (void | io::error) = { +export fn dump(out: io::handle, data: []u8) (void | io::error) = { let datalen = len(data): u32; for (let off = 0u32; off < datalen; off += 16) { diff --git a/fmt/fmt.ha b/fmt/fmt.ha @@ -57,14 +57,14 @@ export @noreturn fn fatal(fmt: str, args: field...) void = { os::exit(1); }; -// Formats text for printing and writes it to an [[io::stream]], followed by a +// Formats text for printing and writes it to an [[io::handle]], followed by a // line feed. export fn fprintfln( - s: *io::stream, + h: io::handle, fmt: str, args: field... ) (io::error | size) = { - return fprintf(s, fmt, args...)? + io::write(s, ['\n': u32: u8])?; + return fprintf(h, fmt, args...)? + io::write(h, ['\n': u32: u8])?; }; // Formats values for printing using the default format modifiers and writes @@ -107,20 +107,20 @@ export fn bsprint(buf: []u8, args: formattable...) str = { }; // Formats values for printing using the default format modifiers and writes -// them to an [[io::stream]] separated by spaces and followed by a line feed. -export fn fprintln(s: *io::stream, args: formattable...) (io::error | size) = { - return fprint(s, args...)? + io::write(s, ['\n': u32: u8])?; +// them to an [[io::handle]] separated by spaces and followed by a line feed. +export fn fprintln(h: io::handle, args: formattable...) (io::error | size) = { + return fprint(h, args...)? + io::write(h, ['\n': u32: u8])?; }; // Formats values for printing using the default format modifiers and writes -// them to an [[io::stream]] separated by spaces. -export fn fprint(s: *io::stream, args: formattable...) (io::error | size) = { +// them to an [[io::handle]] separated by spaces. +export fn fprint(h: io::handle, args: formattable...) (io::error | size) = { let mod = modifiers { base = strconv::base::DEC, ... }; let n = 0z; for (let i = 0z; i < len(args); i += 1) { - n += format(s, args[i], &mod)?; + n += format(h, args[i], &mod)?; if (i != len(args) - 1) { - n += io::write(s, [' ': u32: u8])?; + n += io::write(h, [' ': u32: u8])?; }; }; return n; @@ -162,9 +162,9 @@ type paramindex = (uint | nextparam | void); type nextparam = void; -// Formats text for printing and writes it to an [[io::stream]]. +// Formats text for printing and writes it to an [[io::handle]]. export fn fprintf( - s: *io::stream, + h: io::handle, fmt: str, args: field... ) (io::error | size) = { @@ -187,7 +187,7 @@ export fn fprintf( }; let arg = if (r == '{') { - n += io::write(s, utf8::encoderune('{'))?; + n += io::write(h, utf8::encoderune('{'))?; continue; } else if (ascii::isdigit(r)) { strings::push(&iter, r); @@ -247,7 +247,7 @@ export fn fprintf( mod.base = strconv::base::DEC; }; - n += format(s, arg, mod)?; + n += format(h, arg, mod)?; } else if (r == '}') { match (strings::next(&iter)) { case void => @@ -256,16 +256,20 @@ export fn fprintf( assert(r == '}', "Invalid format string (hanging '}')"); }; - n += io::write(s, utf8::encoderune('}'))?; + n += io::write(h, utf8::encoderune('}'))?; } else { - n += io::write(s, utf8::encoderune(r))?; + n += io::write(h, utf8::encoderune(r))?; }; }; return n; }; -fn format(out: *io::stream, arg: formattable, mod: *modifiers) (size | io::error) = { +fn format( + out: io::handle, + arg: formattable, + mod: *modifiers, +) (size | io::error) = { let z = format_raw(io::empty, arg, mod)?; let pad: []u8 = []; @@ -294,7 +298,7 @@ fn format(out: *io::stream, arg: formattable, mod: *modifiers) (size | io::error }; fn format_raw( - out: *io::stream, + out: io::handle, arg: formattable, mod: *modifiers, ) (size | io::error) = { diff --git a/format/ini/scan.ha b/format/ini/scan.ha @@ -5,7 +5,7 @@ use io; use strings; export type scanner = struct { - in: *io::stream, + in: io::handle, line: str, lineno: size, section: str, @@ -13,7 +13,7 @@ export type scanner = struct { // Creates an INI file scanner. Use [[next]] to read entries. The caller must // call [[finish]] once they're done with this object. -export fn scan(in: *io::stream) scanner = scanner { +export fn scan(in: io::handle) scanner = scanner { in = in, lineno = 1, ... @@ -69,7 +69,7 @@ export fn next(sc: *scanner) (entry | io::EOF | error) = { case idx: size => yield idx; case void => - return (sc.in.name, sc.lineno): syntaxerr; + return sc.lineno: syntaxerr; }; free(sc.section); sc.section = strings::dup(strings::sub(line, 1, end)); @@ -80,7 +80,7 @@ export fn next(sc: *scanner) (entry | io::EOF | error) = { case idx: size => yield idx; case void => - return (sc.in.name, sc.lineno): syntaxerr; + return sc.lineno: syntaxerr; }; return ( sc.section, diff --git a/format/ini/types.ha b/format/ini/types.ha @@ -3,7 +3,7 @@ use fmt; use io; // A syntax error occured during parsing. -export type syntaxerr = !(str, size); +export type syntaxerr = !size; // Any error that may occur during parsing. export type error = !(io::error | utf8::invalid | syntaxerr); @@ -17,5 +17,5 @@ case utf8::invalid => case s: syntaxerr => // XXX: tuple unpacking could improve this static let buf: [1024]u8 = [0...]; - yield fmt::bsprintf(buf, "{}:{}: Invalid syntax", s.0, s.1); + yield fmt::bsprintf(buf, "{}:{}: Invalid syntax", s: size); }; diff --git a/format/xml/parser.ha b/format/xml/parser.ha @@ -10,20 +10,20 @@ use strconv; use strings; use strio; -// Returns an XML parser which reads from a stream. The caller must call -// [[parser_free]] when they are finished with it. +// Creates an XML parser. The caller must call [[parser_free]] when they are +// finished with it. // // Hare's XML parser only supports UTF-8 encoded input files. // // This function will attempt to read the XML prologue before returning, and // will return an error if it is not valid. -export fn parse(in: *io::stream) (*parser | error) = { +export fn parse(in: io::handle) (*parser | error) = { // XXX: The main reason we allocate this instead of returning it on the // stack is so that we have a consistent address for the bufio buffer. // This is kind of lame, maybe we can avoid that. let par = alloc(parser { - orig = in, in = in, + close = false, namebuf = strio::dynamic(), entbuf = strio::dynamic(), textbuf = strio::dynamic(), @@ -31,15 +31,16 @@ export fn parse(in: *io::stream) (*parser | error) = { }); if (!bufio::isbuffered(in)) { par.in = bufio::buffered(par.in, par.buf[..], []); + par.close = true; }; prolog(par)?; return par; }; // Frees the resources associated with this parser. Does not close the -// underlying stream. +// underlying I/O handle. export fn parser_free(par: *parser) void = { - if (par.in != par.orig) { + if (par.close) { io::close(par.in); }; io::close(par.namebuf); @@ -358,7 +359,7 @@ fn scan_namedent(par: *parser) (rune | error) = { return syntaxerr; }; -fn scan_name(par: *parser, buf: *io::stream) (str | error) = { +fn scan_name(par: *parser, buf: io::handle) (str | error) = { strio::reset(buf); const rn = match (bufio::scanrune(par.in)?) { diff --git a/format/xml/types.ha b/format/xml/types.ha @@ -3,16 +3,16 @@ use io; use os; export type parser = struct { - orig: *io::stream, - in: *io::stream, + in: io::handle, buf: [os::BUFSIZ]u8, + close: bool, state: state, tags: []str, // strio buffers: - namebuf: *io::stream, - entbuf: *io::stream, - textbuf: *io::stream, + namebuf: io::handle, + entbuf: io::handle, + textbuf: io::handle, }; export type state = enum { diff --git a/fs/fs.ha b/fs/fs.ha @@ -13,7 +13,7 @@ export fn close(fs: *fs) void = { // Opens a file. If no flags are provided, the default read/write mode is // RDONLY. -export fn open(fs: *fs, path: str, flags: flags...) (*io::stream | error) = { +export fn open(fs: *fs, path: str, flags: flags...) (io::handle | error) = { match (fs.open) { case null => return errors::unsupported; @@ -43,7 +43,7 @@ export fn create( path: str, mode: mode, flags: flags... -) (*io::stream | error) = { +) (io::handle | error) = { match (fs.create) { case null => return errors::unsupported; diff --git a/fs/mem/+test.ha b/fs/mem/+test.ha @@ -13,8 +13,7 @@ use strconv; // fs::create, fs::stat for (let i = 0z; i < 6; i += 1) { - let f = fs::create(memfs, names[i], 0, fs::flags::RDWR); - let f = f as *io::stream; + let f = fs::create(memfs, names[i], 0, fs::flags::RDWR)!; io::write(f, input[i..])!; io::close(f); let st = fs::stat(memfs, names[i]) as fs::filestat; @@ -23,8 +22,7 @@ use strconv; assert(st.mode & fs::mode::REG == fs::mode::REG); }; - let f = fs::open(memfs, filename, fs::flags::WRONLY, fs::flags::APPEND); - let f = f as *io::stream; + let f = fs::open(memfs, filename, fs::flags::WRONLY, fs::flags::APPEND)!; io::write(f, input)!; io::close(f); let st = fs::stat(memfs, filename) as fs::filestat; @@ -41,8 +39,8 @@ use strconv; fs::remove(memfs, "nonexistent") as fs::error as errors::noentry: void; - let f = fs::open(memfs, filename, fs::flags::RDONLY) as *io::stream; - let f2 = fs::open(memfs, filename, fs::flags::RDONLY) as *io::stream; + let f = fs::open(memfs, filename, fs::flags::RDONLY)!; + let f2 = fs::open(memfs, filename, fs::flags::RDONLY)!; let output: [12]u8 = [0...]; assert(io::seek(f2, 3, io::whence::SET) as io::off == 3: io::off); assert(io::read(f2, output) as size == 9); @@ -79,10 +77,10 @@ use strconv; fs::rmdir(memfs, "") as fs::error as errors::invalid: void; fs::mkdir(memfs, "dir") as void; - f = fs::create(memfs, "dir/file", 0, fs::flags::WRONLY) as *io::stream; + f = fs::create(memfs, "dir/file", 0, fs::flags::WRONLY)!; assert(io::write(f, input[..]) as size == 6); io::close(f); - f = fs::open(memfs, "dir/file", fs::flags::RDONLY) as *io::stream; + f = fs::open(memfs, "dir/file", fs::flags::RDONLY)!; assert(io::read(f, output) as size == 6); assert(bytes::equal(input, output[..6])); io::close(f); @@ -96,7 +94,7 @@ use strconv; let sub = mksubdir(memfs, "dir") as *fs::fs; - let f = fs::create(sub, "file", 0, fs::flags::WRONLY) as *io::stream; + let f = fs::create(sub, "file", 0, fs::flags::WRONLY)!; io::write(f, [42])!; io::close(f); @@ -104,7 +102,7 @@ use strconv; assert(sub2 == sub); fs::close(sub); - let f = fs::open(sub2, "file", fs::flags::RDONLY) as *io::stream; + let f = fs::open(sub2, "file", fs::flags::RDONLY)!; assert(io::read(f, output) as size == 1); assert(output[0] == 42); io::close(f); @@ -126,8 +124,8 @@ use strconv; let limit = 32z; let memfs = memopen(); for (let i = 0z; i < limit; i += 1) { - let f = fs::create(memfs, strconv::ztos(i), 0, fs::flags::RDWR); - io::close(f as *io::stream); + let f = fs::create(memfs, strconv::ztos(i), 0, fs::flags::RDWR)!; + io::close(f); }; let ino = memfs: *inode; let dir = ino.data as directory; diff --git a/fs/mem/mem.ha b/fs/mem/mem.ha @@ -94,7 +94,7 @@ fn create( path: str, mode: fs::mode, flags: fs::flags..., -) (*io::stream | fs::error) = { +) (io::handle | fs::error) = { let t = file_flags(flags...)?; let mode = t.0, appnd = t.1; @@ -116,21 +116,21 @@ fn create( parent = parent, }); inode_insert(parent, ino); - return stream_open(ino, mode, appnd); + return stream_open(ino, mode, appnd)?; }; fn open( fs: *fs::fs, path: str, flags: fs::flags..., -) (*io::stream | fs::error) = { +) (io::handle | fs::error) = { let t = file_flags(flags...)?; let mode = t.0, appnd = t.1; let ino = inode_find(fs: *inode, path)?; if (ino.data is directory) { return fs::wrongtype; }; - return stream_open(ino, mode, appnd); + return stream_open(ino, mode, appnd)?; }; fn stat(fs: *fs::fs, path: str) (fs::filestat | fs::error) = { diff --git a/fs/mem/stream.ha b/fs/mem/stream.ha @@ -19,7 +19,6 @@ fn stream_open( let f = ino.data as file; let s = alloc(stream { stream = io::stream { - name = "<fs::mem::stream>", closer = &stream_close, seeker = &seek, ... diff --git a/fs/types.ha b/fs/types.ha @@ -182,7 +182,7 @@ export type openfunc = fn( fs: *fs, path: str, flags: flags... -) (*io::stream | error); +) (io::handle | error); export type openfilefunc = fn( fs: *fs, @@ -195,7 +195,7 @@ export type createfunc = fn( path: str, mode: mode, flags: flags... -) (*io::stream | error); +) (io::handle | error); export type createfilefunc = fn( fs: *fs, diff --git a/getopt/getopts.ha b/getopt/getopts.ha @@ -182,71 +182,71 @@ export fn finish(cmd: *command) void = { free(cmd.opts); }; -fn _printusage(s: *io::stream, name: str, indent: bool, help: []help) size = { - let z = fmt::fprint(s, "Usage:", name) as size; +fn _printusage(out: io::handle, name: str, indent: bool, help: []help) size = { + let z = fmt::fprint(out, "Usage:", name) as size; let started_flags = false; for (let i = 0z; i < len(help); i += 1) if (help[i] is flag_help) { if (!started_flags) { - z += fmt::fprint(s, " [-") as size; + z += fmt::fprint(out, " [-") as size; started_flags = true; }; const help = help[i] as flag_help; - z += fmt::fprint(s, help.0: rune) as size; + z += fmt::fprint(out, help.0: rune) as size; }; if (started_flags) { - z += fmt::fprint(s, "]") as size; + z += fmt::fprint(out, "]") as size; }; for (let i = 0z; i < len(help); i += 1) if (help[i] is parameter_help) { const help = help[i] as parameter_help; if (indent) { - z += fmt::fprintf(s, "\n\t") as size; + z += fmt::fprintf(out, "\n\t") as size; }; - z += fmt::fprintf(s, " [-{} <{}>]", help.0: rune, help.1) as size; + z += fmt::fprintf(out, " [-{} <{}>]", help.0: rune, help.1) as size; }; let first_arg = true; for (let i = 1z; i < len(help); i += 1) if (help[i] is cmd_help) { if (first_arg) { if (indent) { - z += fmt::fprintf(s, "\n\t") as size; + z += fmt::fprintf(out, "\n\t") as size; }; first_arg = false; }; - z += fmt::fprintf(s, " {}", help[i] as cmd_help: str) as size; + z += fmt::fprintf(out, " {}", help[i] as cmd_help: str) as size; }; - return z + fmt::fprint(s, "\n") as size; + return z + fmt::fprint(out, "\n") as size; }; // Prints command usage to the provided stream. -export fn printusage(s: *io::stream, name: str, help: []help) void = { +export fn printusage(out: io::handle, name: str, help: []help) void = { let z = _printusage(io::empty, name, false, help); - _printusage(s, name, if (z > 72) true else false, help); + _printusage(out, name, if (z > 72) true else false, help); }; // Prints command help to the provided stream. -export fn printhelp(s: *io::stream, name: str, help: []help) void = { +export fn printhelp(out: io::handle, name: str, help: []help) void = { if (help[0] is cmd_help) { - fmt::fprintfln(s, "{}: {}\n", name, help[0] as cmd_help: str)!; + fmt::fprintfln(out, "{}: {}\n", name, help[0] as cmd_help: str)!; }; - printusage(s, name, help); + printusage(out, name, help); for (let i = 0z; i < len(help); i += 1) match (help[i]) { case cmd_help => void; case (flag_help | parameter_help) => // Only print this if there are flags to show - fmt::fprint(s, "\n")!; + fmt::fprint(out, "\n")!; break; }; for (let i = 0z; i < len(help); i += 1) match (help[i]) { case cmd_help => void; case f: flag_help => - fmt::fprintfln(s, "-{}: {}", f.0: rune, f.1)!; + fmt::fprintfln(out, "-{}: {}", f.0: rune, f.1)!; case p: parameter_help => - fmt::fprintfln(s, "-{} <{}>: {}", p.0: rune, p.1, p.2)!; + fmt::fprintfln(out, "-{} <{}>: {}", p.0: rune, p.1, p.2)!; }; }; diff --git a/hare/lex/README b/hare/lex/README @@ -1,4 +1,4 @@ -hare::lex provides a lexer for Hare source code. A lexer takes an [[io::stream]] +hare::lex provides a lexer for Hare source code. A lexer takes an [[io::handle]] and returns a series of Hare [[token]]s. See the Hare specification for more details: diff --git a/hare/lex/lex.ha b/hare/lex/lex.ha @@ -10,7 +10,7 @@ use strio; use types; export type lexer = struct { - in: *io::stream, + in: io::handle, path: str, loc: (uint, uint), un: (token | void), @@ -47,8 +47,8 @@ export fn strerror(err: error) const str = { }; }; -// Initializes a new lexer for the given input stream. The path is borrowed. -export fn init(in: *io::stream, path: str, flags: flags...) lexer = { +// Initializes a new lexer for the given input. The path is borrowed. +export fn init(in: io::handle, path: str, flags: flags...) lexer = { let f = flags::NONE; for (let i = 0z; i < len(flags); i += 1) { f |= flags[i]; diff --git a/hare/module/manifest.ha b/hare/module/manifest.ha @@ -57,7 +57,7 @@ export fn manifest_load(ctx: *context, ident: ast::ident) (manifest | error) = { return manifest; case err: fs::error => return err; - case file: *io::stream => + case file: io::handle => yield file; }; defer io::close(file); @@ -282,17 +282,19 @@ export fn manifest_write(ctx: *context, manifest: *manifest) (void | error) = { defer free(mpath); let file = temp::named(ctx.fs, cachedir, io::mode::WRITE, 0o644)?; + let name = file.1, file = file.0; defer { - fs::remove(ctx.fs, file.name): void; - io::close(&file); + fs::remove(ctx.fs, name): void; + io::close(file); + free(name); }; let ident = unparse::identstr(manifest.ident); defer free(ident); - fmt::fprintfln(&file, "# {}", ident)?; - fmt::fprintln(&file, "# This file is an internal Hare implementation detail.")?; - fmt::fprintln(&file, "# The format is not stable.")?; - fmt::fprintfln(&file, "version {}", VERSION)?; + fmt::fprintfln(file, "# {}", ident)?; + fmt::fprintln(file, "# This file is an internal Hare implementation detail.")?; + fmt::fprintln(file, "# The format is not stable.")?; + fmt::fprintfln(file, "version {}", VERSION)?; for (let i = 0z; i < len(manifest.inputs); i += 1) { const input = manifest.inputs[i]; let hash = hex::encodestr(input.hash); @@ -300,7 +302,7 @@ export fn manifest_write(ctx: *context, manifest: *manifest) (void | error) = { const want = fs::stat_mask::INODE | fs::stat_mask::MTIME; assert(input.stat.mask & want == want); - fmt::fprintfln(&file, "input {} {} {} {}", + fmt::fprintfln(file, "input {} {} {} {}", hash, input.path, input.stat.inode, time::unix(input.stat.mtime))?; }; @@ -310,19 +312,19 @@ export fn manifest_write(ctx: *context, manifest: *manifest) (void | error) = { let hash = hex::encodestr(ver.hash); defer free(hash); - fmt::fprintf(&file, "module {}", hash)?; + fmt::fprintf(file, "module {}", hash)?; for (let j = 0z; j < len(ver.inputs); j += 1) { let hash = hex::encodestr(ver.inputs[i].hash); defer free(hash); - fmt::fprintf(&file, " {}", hash)?; + fmt::fprintf(file, " {}", hash)?; }; - fmt::fprintln(&file)?; + fmt::fprintln(file)?; }; - fs::move(ctx.fs, file.name, mpath)?; + fs::move(ctx.fs, name, mpath)?; }; fn input_finish(in: *input) void = { diff --git a/hare/unparse/decl.ha b/hare/unparse/decl.ha @@ -4,7 +4,7 @@ use hare::ast; use hare::lex; use strio; -export fn decl(out: *io::stream, d: ast::decl) (size | io::error) = { +export fn decl(out: io::handle, d: ast::decl) (size | io::error) = { let n = 0z; if (d.exported) { n += fmt::fprint(out, "export ")?; diff --git a/hare/unparse/expr.ha b/hare/unparse/expr.ha @@ -9,7 +9,7 @@ use hare::lex; // binary operators (e.g. +) is not accounted for, so such expressions may // produce a different AST if parsed again. export fn expr( - out: *io::stream, + out: io::handle, indent: size, e: ast::expr ) (size | io::error) = { @@ -390,7 +390,7 @@ export fn expr( }; fn constant( - out: *io::stream, + out: io::handle, indent: size, e: ast::constant_expr, ) (size | io::error) = { @@ -439,7 +439,7 @@ fn constant( }; fn struct_constant( - out: *io::stream, + out: io::handle, indent: size, sc: ast::struct_constant, ) (size | io::error) = { @@ -480,7 +480,7 @@ fn struct_constant( }; fn for_expr( - out: *io::stream, + out: io::handle, indent: size, e: ast::for_expr, ) (size | io::error) = { @@ -506,7 +506,7 @@ fn for_expr( }; fn switch_expr( - out: *io::stream, + out: io::handle, indent: size, e: ast::switch_expr, ) (size | io::error) = { @@ -545,7 +545,7 @@ fn switch_expr( }; fn match_expr( - out: *io::stream, + out: io::handle, indent: size, e: ast::match_expr, ) (size | io::error) = { diff --git a/hare/unparse/ident.ha b/hare/unparse/ident.ha @@ -4,7 +4,7 @@ use io; use strio; // Unparses an identifier. -export fn ident(out: *io::stream, id: ast::ident) (size | io::error) = { +export fn ident(out: io::handle, id: ast::ident) (size | io::error) = { let n = 0z; for (let i = 0z; i < len(id); i += 1) { n += fmt::fprintf(out, "{}{}", id[i], diff --git a/hare/unparse/import.ha b/hare/unparse/import.ha @@ -4,7 +4,7 @@ use hare::ast; use strio; // Unparses an [[ast::import]]. -export fn import(out: *io::stream, i: ast::import) (size | io::error) = { +export fn import(out: io::handle, i: ast::import) (size | io::error) = { let n = 0z; n += fmt::fprint(out, "use ")?; match (i) { diff --git a/hare/unparse/type.ha b/hare/unparse/type.ha @@ -52,7 +52,7 @@ case ast::builtin_type::VOID => }; fn prototype( - out: *io::stream, + out: io::handle, indent: size, t: ast::func_type, ) (size | io::error) = { @@ -80,7 +80,7 @@ fn prototype( }; fn struct_union_type( - out: *io::stream, + out: io::handle, indent: size, t: ast::_type, ) (size | io::error) = { @@ -127,7 +127,7 @@ fn struct_union_type( // Unparses an [[ast::_type]]. export fn _type( - out: *io::stream, + out: io::handle, indent: size, t: ast::_type, ) (size | io::error) = { diff --git a/hare/unparse/unit.ha b/hare/unparse/unit.ha @@ -3,7 +3,7 @@ use fmt; use hare::ast; // Unparses an [[ast::subunit]]. -export fn subunit(out: *io::stream, s: ast::subunit) (size | io::error) = { +export fn subunit(out: io::handle, s: ast::subunit) (size | io::error) = { let n = 0z; for (let i = 0z; i < len(s.imports); i += 1) { n += import(out, s.imports[i])?; diff --git a/hare/unparse/util.ha b/hare/unparse/util.ha @@ -1,7 +1,7 @@ use io; use fmt; -fn newline(out: *io::stream, indent: size) (size | io::error) = { +fn newline(out: io::handle, indent: size) (size | io::error) = { let n = 0z; n += fmt::fprint(out, "\n")?; for (let i = 0z; i < indent; i += 1) { diff --git a/io/+linux/file.ha b/io/+linux/file.ha @@ -7,73 +7,15 @@ use strings; // [[stream]] in most situations, but there are some APIs which require an // [[file]] with some OS-level handle backing it - this type is used for such // APIs. -export type file = struct { - stream, - fd: int, -}; +export type file = int; // Opens a Unix file descriptor as a file. This is a low-level interface, to // open files most programs will use something like [[os::open]]. This function // is not portable. -export fn fdopen(fd: int, name: str, mode: mode) file = { - let stream = file { - name = name, - closer = &fd_close_static, - copier = &fd_copy, - seeker = &fd_seek, - fd = fd, - ... - }; - if (mode & mode::READ == mode::READ) { - stream.reader = &fd_read; - }; - if (mode & mode::WRITE == mode::WRITE) { - stream.writer = &fd_write; - }; - return stream; -}; - -// Duplicates a [[file]] onto the heap, as if it were opened with [[fdalloc]]. -export fn filedup(f: *file) *file = { - let new = alloc(*f); - new.closer = &fd_close; - return new; -}; - -// Similar to [[fdopen]], but heap-allocates the file. Closing the stream will -// free the associated resources. -export fn fdalloc(fd: int, name: str, mode: mode) *file = - filedup(&fdopen(fd, strings::dup(name), mode)); - -// Returns true if a [[stream]] is a file. -export fn is_file(s: *stream) bool = { - return s.reader == &fd_read - || s.writer == &fd_write - || s.closer == &fd_close - || s.copier == &fd_copy; -}; +export fn fdopen(fd: int) file = fd; -// Returns the file descriptor for a given [[file]]. This function is not -// portable. -export fn fd(f: *file) int = f.fd; - -// Returns the file descriptor for a given [[stream]], returning void if the -// stream is not backed by a file. This function is not portable. -export fn unwrapfd(s: *stream) (int | void) = { - for (!is_file(s)) { - s = match (io::source(s)) { - case errors::unsupported => - return; - case s: *io::stream => - yield s; - }; - }; - return fd(s: *file); -}; - -fn fd_read(s: *stream, buf: []u8) (size | EOF | error) = { - let stream = s: *file; - match (rt::read(stream.fd, buf: *[*]u8, len(buf))) { +fn fd_read(fd: file, buf: []u8) (size | EOF | error) = { + match (rt::read(fd, buf: *[*]u8, len(buf))) { case err: rt::errno => return errors::errno(err); case n: size => @@ -86,9 +28,8 @@ fn fd_read(s: *stream, buf: []u8) (size | EOF | error) = { }; }; -fn fd_write(s: *stream, buf: const []u8) (size | error) = { - let stream = s: *file; - match (rt::write(stream.fd, buf: *const [*]u8, len(buf))) { +fn fd_write(fd: file, buf: const []u8) (size | error) = { + match (rt::write(fd, buf: *const [*]u8, len(buf))) { case err: rt::errno => return errors::errno(err); case n: size => @@ -96,61 +37,46 @@ fn fd_write(s: *stream, buf: const []u8) (size | error) = { }; }; -fn fd_close_static(s: *stream) void = { - let stream = s: *file; - rt::close(stream.fd)!; -}; +fn fd_close(fd: file) void = rt::close(fd)!; -fn fd_close(s: *stream) void = { - fd_close_static(s); - free(s); +fn fd_seek( + fd: file, + off: off, + whence: whence, +) (off | error) = { + match (rt::lseek(fd, off: i64, whence: uint)) { + case err: rt::errno => + return errors::errno(err); + case n: i64 => + return n: off; + }; }; def SENDFILE_MAX: size = 2147479552z; -fn fd_copy(to: *stream, from: *stream) (size | error) = { - if (!is_file(from)) { - return errors::unsupported; - }; - - let to = to: *file, from = from: *file; +fn fd_copy(to: file, from: file) (size | error) = { let sum = 0z; for (true) { - let n = match (rt::sendfile(to.fd, from.fd, - null, SENDFILE_MAX)) { - case err: rt::errno => - switch (err) { - case rt::EINVAL => - if (sum == 0) { - return errors::unsupported; - }; - return errors::errno(err); - case => - return errors::errno(err); - }; - case n: size => - yield switch (n) { - case 0 => - break; - case => - yield n; + let n = match (rt::sendfile(to, from, null, SENDFILE_MAX)) { + case err: rt::errno => + switch (err) { + case rt::EINVAL => + if (sum == 0) { + return errors::unsupported; }; + return errors::errno(err); + case => + return errors::errno(err); + }; + case n: size => + yield switch (n) { + case 0 => + break; + case => + yield n; + }; }; sum += n; }; return sum; }; - -fn fd_seek( - s: *stream, - off: off, - whence: whence, -) (off | error) = { - let stream = s: *file; - match (rt::lseek(stream.fd, off: i64, whence: uint)) { - case err: rt::errno => - return errors::errno(err); - case n: i64 => - return n: off; - }; -}; diff --git a/io/+test/stream.ha b/io/+test/stream.ha @@ -9,7 +9,6 @@ type teststream = struct { }; fn teststream_open() teststream = teststream { - name = "teststream", reader = &teststream_read, writer = &teststream_write, ... diff --git a/io/README b/io/README @@ -0,0 +1,23 @@ +The io module provides input and output (I/O) functionality for Hare programs, +such as reading from or writing to files. The I/O module is not generally +responsible for provisioning the I/O objects themselves; see modules like [[os]] +and [[net]] for this purpose. + +I/O operations such as [[read]] or [[write]] accept an I/O handle, +[[io::handle]], to specify the object of the I/O operation. This type is a +tagged union of [[io::file]] and *[[io::stream]]. Most programmers should prefer +to use [[io::handle]] unless they specifically require the special semantics of +one of its subtypes. + +The [[io::file]] type provides access to an object, usually a file descriptor, +which is provided by the host operating system. It represents objects such as a +file on disk, an open newtork connection, and so on. The use of [[io::file]] is +generally required when working with host I/O, such as for modules like +[[iobus]] or [[unix::poll]]. + +The [[io::stream]] type is an abstraction that allows Hare programs to implement +their own I/O objects by providing implementations of [[read]], [[write]], and +other functions, for an [[io::handle]]. Several standard library modules offer +implementations of [[io::stream]] for one reason or another, such as [[bufio]]. +Additionally, the io module provides some useful general-purpose I/O streams, +such as [[io::tee]]. diff --git a/io/copy.ha b/io/copy.ha @@ -1,15 +1,30 @@ use errors; -// Copies data from one stream into another. Note that this function will never -// return if the source stream is infinite. -export fn copy(dest: *stream, src: *stream) (error | size) = { +// Copies data from one handle into another. Note that this function will never +// return if the source handle is infinite. +export fn copy(dest: handle, src: handle) (error | size) = { + match (dest) { + case fd: file => + if (src is file) { + return fd_copy(fd, src as file); + }; + return copy_fallback(dest, src); + case st: *stream => + if (!(src is *stream)) { + return copy_fallback(dest, src); + }; + return copy_streams(st, src as *stream); + }; +}; + +fn copy_streams(dest: *stream, src: *stream) (error | size) = { match (dest.copier) { case null => void; case c: *copier => match (c(dest, src)) { case err: error => match (err) { - case errors::unsupported => void; // Use fallback + case errors::unsupported => void; case => return err; }; @@ -17,7 +32,10 @@ export fn copy(dest: *stream, src: *stream) (error | size) = { return s; }; }; + return copy_fallback(dest, src); +}; +fn copy_fallback(dest: handle, src: handle) (error | size) = { let w = 0z; static let buf: [4096]u8 = [0...]; for (true) { diff --git a/io/empty.ha b/io/empty.ha @@ -0,0 +1,12 @@ +const _empty: stream = stream { + reader = &empty_read, + writer = &empty_write, + ... +}; + +// A [[stream]] which always reads EOF and discards any writes. +export const empty: *io::stream = &_empty; + +fn empty_read(s: *stream, buf: []u8) (size | EOF | error) = EOF; + +fn empty_write(s: *stream, buf: const []u8) (size | error) = len(buf); diff --git a/io/filestream.ha b/io/filestream.ha @@ -0,0 +1,52 @@ +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 { + 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/handle.ha b/io/handle.ha @@ -0,0 +1,57 @@ +// TODO: Examine the ABI constraints of [[handle]]. Would it be better to make +// stream an integer representing an internal handle into a stream table? This +// would reduce performance for streams somewhat via the indirect lookup, but +// improve the ABI performance for files. + +// An I/O handle is a resource which I/O operations may be performed on. It is +// either a [[stream]], which is a userspace I/O abstraction, or a [[file]], +// which is backed by a resource on the host OS, such as a file descriptor. +export type handle = (file | *stream); + +// Reads up to len(buf) bytes from a [[handle]] into the given buffer, returning +// the number of bytes read. +export fn read(h: handle, buf: []u8) (size | EOF | error) = { + match (h) { + case fd: file => + return fd_read(fd, buf); + case st: *stream => + return st_read(st, buf); + }; +}; + +// Writes up to len(buf) bytes to the [[handle]] from the given buffer, +// returning the number of bytes written. +export fn write(h: handle, buf: const []u8) (size | error) = { + match (h) { + case fd: file => + return fd_write(fd, buf); + case st: *stream => + return st_write(st, buf); + }; +}; + +// Closes a [[handle]]. No further operations against this handle are permitted +// after calling this function. +export fn close(h: handle) void = { + match (h) { + case fd: file => + fd_close(fd); + case st: *stream => + st_close(st); + }; +}; + +// Sets the offset within a [[handle]]. +export fn seek(h: handle, off: off, w: whence) (off | error) = { + match (h) { + case fd: file => + return fd_seek(fd, off, w); + case st: *stream => + return st_seek(st, off, w); + }; +}; + +// Returns the current offset within a [[handle]]. +export fn tell(h: handle) (off | error) = { + return seek(h, 0, whence::CUR); +}; diff --git a/io/limit.ha b/io/limit.ha @@ -2,14 +2,12 @@ use strings; export type limitstream = struct { stream, - source: *stream, + source: handle, limit: size, }; -fn limitstream_create(source: *stream, limit: size) limitstream = { +fn limitstream_create(source: handle, limit: size) limitstream = { return limitstream { - name = source.name, - unwrap = &limit_unwrap, source = source, limit = limit, ... @@ -53,8 +51,3 @@ fn limit_write(s: *stream, buf: const []u8) (size | error) = { stream.limit -= len(slice); return write(stream.source, slice); }; - -fn limit_unwrap(s: *stream) *io::stream = { - let s = s: *limitstream; - return s.source; -}; diff --git a/io/stream.ha b/io/stream.ha @@ -1,8 +1,9 @@ use errors; -// A stream of bytes which supports some subset of read, write, close, or seek -// 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 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. // // export type my_stream = struct { // io::stream, @@ -12,7 +13,6 @@ use errors; // export fn open(path: str) my_stream = { // let fd = // ... // return my_stream { -// name = strings::dup(path), // reader = &my_stream_read, // writer = &my_stream_write, // closer = null, @@ -24,18 +24,14 @@ use errors; // let stream = open("example"); // io::read(&stream, buf)!; export type stream = struct { - name: str, reader: nullable *reader, writer: nullable *writer, closer: nullable *closer, - copier: nullable *copier, seeker: nullable *seeker, - unwrap: nullable *unwrap, + copier: nullable *copier, }; -// Reads up to len(buf) bytes from the reader into the given buffer, returning -// the number of bytes read. -export fn read(s: *stream, buf: []u8) (size | EOF | error) = { +fn st_read(s: *stream, buf: []u8) (size | EOF | error) = { match (s.reader) { case null => return errors::unsupported; @@ -44,9 +40,7 @@ export fn read(s: *stream, buf: []u8) (size | EOF | error) = { }; }; -// Writes up to len(buf) bytes to the stream from the given buffer, returning -// the number of bytes written. -export fn write(s: *stream, buf: const []u8) (size | error) = { +fn st_write(s: *stream, buf: const []u8) (size | error) = { match (s.writer) { case null => return errors::unsupported; @@ -55,8 +49,7 @@ export fn write(s: *stream, buf: const []u8) (size | error) = { }; }; -// Closes the stream. -export fn close(s: *stream) void = { +fn st_close(s: *stream) void = { match (s.closer) { case null => void; case c: *closer => @@ -64,8 +57,7 @@ export fn close(s: *stream) void = { }; }; -// Sets the offset within the stream. -export fn seek(s: *stream, off: off, w: whence) (off | error) = { +fn st_seek(s: *stream, off: off, w: whence) (off | error) = { match (s.seeker) { case null => return errors::unsupported; @@ -73,36 +65,3 @@ export fn seek(s: *stream, off: off, w: whence) (off | error) = { return sk(s, off, w); }; }; - -// Returns the current offset within the stream. -export fn tell(s: *stream) (off | error) = { - match (s.seeker) { - case null => - return errors::unsupported; - case sk: *seeker => - return sk(s, 0, whence::CUR); - }; -}; - -// Returns the underlying stream for a stream which wraps another stream. -export fn source(s: *stream) (*io::stream | errors::unsupported) = { - match (s.unwrap) { - case null => - return errors::unsupported; - case uw: *unwrap => - return uw(s); - }; -}; - -let _empty: io::stream = io::stream { - reader = &empty_read, - writer = &empty_write, - ... -}; - -// A [[stream]] which always reads EOF and discards any writes. -export let empty: *io::stream = &_empty; - -fn empty_read(s: *stream, buf: []u8) (size | EOF | error) = EOF; - -fn empty_write(s: *stream, buf: const []u8) (size | error) = len(buf); diff --git a/io/tee.ha b/io/tee.ha @@ -1,16 +1,15 @@ export type teestream = struct { stream, - source: *stream, - sink: *stream, + source: handle, + sink: handle, }; // 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: *stream, sink: *stream) teestream = { +export fn tee(source: handle, sink: handle) teestream = { return teestream { reader = &tee_read, - unwrap = &tee_unwrap, source = source, sink = sink, ... @@ -30,9 +29,3 @@ fn tee_read(s: *stream, buf: []u8) (size | EOF | error) = { }; return z; }; - -fn tee_unwrap(s: *stream) *io::stream = { - assert(s.unwrap == &tee_unwrap); - let s = s: *teestream; - return s.source; -}; diff --git a/net/+linux.ha b/net/+linux.ha @@ -30,11 +30,7 @@ export fn stream_accept(l: *listener) (io::file | error) = { case fd: int => yield fd; }; - - static let namebuf: [32]u8 = [0...]; - return io::fdopen(fd, - fmt::bsprintf(namebuf, "<network fd {}>", fd), - io::mode::READ | io::mode::WRITE); + return io::fdopen(fd); }; export fn stream_shutdown(l: *listener) void = { diff --git a/net/dns/query.ha b/net/dns/query.ha @@ -27,17 +27,17 @@ export fn query(query: *message, servers: ip::addr...) (*message | error) = { }; let socket4 = udp::listen(ip::ANY_V4, 0)?; - defer io::close(&socket4); + defer io::close(socket4); let socket6 = udp::listen(ip::ANY_V6, 0)?; - defer io::close(&socket6); + defer io::close(socket6); const pollfd: [_]poll::pollfd = [ poll::pollfd { - fd = io::fd(&socket4), + fd = socket4, events = poll::event::POLLIN, ... }, poll::pollfd { - fd = io::fd(&socket6), + fd = socket6, events = poll::event::POLLIN, ... }, @@ -50,9 +50,9 @@ export fn query(query: *message, servers: ip::addr...) (*message | error) = { // first one which sends us a reasonable answer. for (let i = 0z; i < len(servers); i += 1) match (servers[i]) { case ip::addr4 => - udp::sendto(&socket4, sendbuf[..z], servers[i], 53)?; + udp::sendto(socket4, sendbuf[..z], servers[i], 53)?; case ip::addr6 => - udp::sendto(&socket6, sendbuf[..z], servers[i], 53)?; + udp::sendto(socket6, sendbuf[..z], servers[i], 53)?; }; let header = header { ... }; @@ -65,10 +65,10 @@ export fn query(query: *message, servers: ip::addr...) (*message | error) = { let src: ip::addr = ip::ANY_V4; if (pollfd[0].revents & poll::event::POLLIN != 0) { - z = udp::recvfrom(&socket4, recvbuf, &src, null)?; + z = udp::recvfrom(socket4, recvbuf, &src, null)?; }; if (pollfd[1].revents & poll::event::POLLIN != 0) { - z = udp::recvfrom(&socket6, recvbuf, &src, null)?; + z = udp::recvfrom(socket6, recvbuf, &src, null)?; }; let expected = false; diff --git a/net/ip/ip.ha b/net/ip/ip.ha @@ -151,7 +151,7 @@ export fn parse(s: str) (addr | invalid) = { return invalid; }; -fn fmtv4(s: *io::stream, a: addr4) (io::error | size) = { +fn fmtv4(s: io::handle, a: addr4) (io::error | size) = { let ret = 0z; for (let i = 0; i < 4; i += 1) { if (i > 0) { @@ -162,7 +162,7 @@ fn fmtv4(s: *io::stream, a: addr4) (io::error | size) = { return ret; }; -fn fmtv6(s: *io::stream, a: addr6) (io::error | size) = { +fn fmtv6(s: io::handle, a: addr6) (io::error | size) = { let ret = 0z; let zstart: int = -1; let zend: int = -1; @@ -283,7 +283,7 @@ fn masklen(addr: []u8) (void | size) = { return n; }; -fn fmtmask(s: *io::stream, mask: addr) (io::error | size) = { +fn fmtmask(s: io::handle, mask: addr) (io::error | size) = { let ret = 0z; let slice = match (mask) { case v4: addr4 => @@ -305,7 +305,7 @@ fn fmtmask(s: *io::stream, mask: addr) (io::error | size) = { return ret; }; -fn fmtsubnet(s: *io::stream, subnet: subnet) (io::error | size) = { +fn fmtsubnet(s: io::handle, subnet: subnet) (io::error | size) = { let ret = 0z; ret += fmt(s, subnet.addr)?; ret += fmt::fprintf(s, "/")?; @@ -314,7 +314,7 @@ fn fmtsubnet(s: *io::stream, subnet: subnet) (io::error | size) = { }; // Formats an [[addr]] or [[subnet]] and prints it to a stream. -export fn fmt(s: *io::stream, item: (...addr | subnet)) (io::error | size) = { +export fn fmt(s: io::handle, item: (...addr | subnet)) (io::error | size) = { match (item) { case v4: addr4 => return fmtv4(s, v4)?; diff --git a/net/tcp/+linux.ha b/net/tcp/+linux.ha @@ -38,8 +38,7 @@ export fn connect( return errors::errno(err); case int => void; }; - return io::fdopen(sockfd, ip::string(addr), - io::mode::READ | io::mode::WRITE); + return io::fdopen(sockfd); }; // Binds a TCP listener to the given address. @@ -119,11 +118,10 @@ export fn listen( // Returns the remote address for a given connection, or void if none is // available. -export fn peeraddr(peer: *io::file) ((ip::addr, u16) | void) = { - let fd = io::fd(peer); +export fn peeraddr(peer: io::file) ((ip::addr, u16) | void) = { let sn = rt::sockaddr {...}; let sz = size(rt::sockaddr): u32; - if (rt::getpeername(fd, &sn, &sz) is rt::errno) { + if (rt::getpeername(peer, &sn, &sz) is rt::errno) { return; }; return ip::from_native(sn); diff --git a/net/udp/+linux.ha b/net/udp/+linux.ha @@ -27,8 +27,7 @@ export fn connect( const sz = size(rt::sockaddr): u32; match (rt::connect(sockfd, &sockaddr, sz)) { case int => - return io::fdopen(sockfd, "<udp connection>", - io::mode::READ | io::mode::WRITE); + return io::fdopen(sockfd); case err: rt::errno => return errors::errno(err); }; @@ -76,13 +75,12 @@ export fn listen( *options[i] = addr.1; }; - return io::fdopen(sockfd, "<udp listener>", - io::mode::READ | io::mode::WRITE); + return io::fdopen(sockfd); }; // Sends a UDP packet to the destination previously specified by [[connect]]. -export fn send(sock: *io::file, buf: []u8) (size | net::error) = { - match (rt::send(io::fd(sock), buf: *[*]u8, len(buf), 0)) { +export fn send(sock: io::file, buf: []u8) (size | net::error) = { + match (rt::send(sock, buf: *[*]u8, len(buf), 0)) { case sz: size => return sz; case err: rt::errno => @@ -92,15 +90,14 @@ export fn send(sock: *io::file, buf: []u8) (size | net::error) = { // Sends a UDP packet using this socket. export fn sendto( - sock: *io::file, + sock: io::file, buf: []u8, dest: ip::addr, port: u16, ) (size | net::error) = { const sockaddr = ip::to_native(dest, port); const sz = size(rt::sockaddr): u32; - match (rt::sendto(io::fd(sock), buf: *[*]u8, len(buf), - 0, &sockaddr, sz)) { + match (rt::sendto(sock, buf: *[*]u8, len(buf), 0, &sockaddr, sz)) { case sz: size => return sz; case err: rt::errno => @@ -110,15 +107,15 @@ export fn sendto( // Receives a UDP packet from a bound socket. export fn recvfrom( - sock: *io::file, + sock: io::file, buf: []u8, src: nullable *ip::addr, port: nullable *u16, ) (size | net::error) = { let addrsz = size(rt::sockaddr): u32; const sockaddr = rt::sockaddr { ... }; - const sz = match (rt::recvfrom(io::fd(sock), buf: *[*]u8, len(buf), - 0, &sockaddr, &addrsz)) { + const sz = match (rt::recvfrom(sock, buf: *[*]u8, len(buf), 0, + &sockaddr, &addrsz)) { case sz: size => yield sz; case err: rt::errno => diff --git a/net/unix/+linux.ha b/net/unix/+linux.ha @@ -32,9 +32,7 @@ export fn connect(addr: addr) (io::file | net::error) = { case int => void; }; static let buf: [rt::UNIX_PATH_MAX + 32]u8 = [0...]; - return io::fdopen(sockfd, - fmt::bsprintf(buf, "<unix connection {}>", addr), - io::mode::READ | io::mode::WRITE); + return io::fdopen(sockfd); }; // Binds a UNIX socket listener to the given path. diff --git a/os/+linux/dirfdfs.ha b/os/+linux/dirfdfs.ha @@ -51,7 +51,7 @@ type os_filesystem = struct { // a dirfdfs, [[fs::flags::NOCTTY]] and [[fs::flags::CLOEXEC]] are used when opening // the file. If you pass your own flags, it is recommended that you add these // unless you know that you do not want them. -export fn dirfdopen(fd: int, resolve: resolve_flags...) *fs::fs = { +export fn dirfdopen(fd: io::file, resolve: resolve_flags...) *fs::fs = { let ofs = alloc(os_filesystem { ... }); let fs = static_dirfdopen(fd, ofs); for (let i = 0z; i < len(resolve); i += 1) { @@ -61,7 +61,7 @@ export fn dirfdopen(fd: int, resolve: resolve_flags...) *fs::fs = { return fs; }; -fn static_dirfdopen(fd: int, filesystem: *os_filesystem) *fs::fs = { +fn static_dirfdopen(fd: io::file, filesystem: *os_filesystem) *fs::fs = { *filesystem = os_filesystem { fs = fs::fs { open = &fs_open, @@ -158,7 +158,7 @@ fn _fs_open( yield fd; }; - return io::fdopen(fd, path, mode); + return io::fdopen(fd); }; fn fs_open_file( @@ -202,7 +202,7 @@ fn fs_open( fs: *fs::fs, path: str, flags: fs::flags... -) (*io::stream | fs::error) = io::filedup(&fs_open_file(fs, path, flags...)?); +) (io::handle | fs::error) = fs_open_file(fs, path, flags...)?; fn fs_create_file( fs: *fs::fs, @@ -243,8 +243,8 @@ fn fs_create( path: str, mode: fs::mode, flags: fs::flags... -) (*io::stream | fs::error) = { - return io::filedup(&fs_create_file(fs, path, mode, flags...)?); +) (io::handle | fs::error) = { + return fs_create_file(fs, path, mode, flags...)?; }; fn fs_remove(fs: *fs::fs, path: str) (void | fs::error) = { diff --git a/os/+linux/stdfd.ha b/os/+linux/stdfd.ha @@ -1,29 +1,40 @@ use bufio; use io; -let static_stdin_fd: io::file = io::file { ... }; -let static_stdout_fd: io::file = io::file { ... }; -let static_stderr_fd: io::file = io::file { ... }; +let static_stdin_bufio: bufio::bufstream = bufio::bufstream { + source = 0, + ... +}; + +let static_stdout_bufio: bufio::bufstream = bufio::bufstream { + source = 1, + ... +}; + +// The standard input. This handle is buffered. +export let stdin: io::handle = 0; -let static_stdin_bufio: bufio::bufstream = bufio::bufstream { ... }; -let static_stdout_bufio: bufio::bufstream = bufio::bufstream { ... }; +// The standard input, as an [[io::file]]. This handle is unbuffered. +export let stdin_file: io::file = 0; + +// The standard output. This handle is buffered. +export let stdout: io::handle = 1; + +// The standard output, as an [[io::file]]. This handle is unbuffered. +export let stdout_file: io::file = 1; + +// The standard error. +export let stderr: io::file = 2; // The recommended buffer size for reading from disk. export def BUFSIZ: size = 4096; // 4 KiB @init fn init_stdfd() void = { - static_stdin_fd = io::fdopen(0, "<stdin>", io::mode::READ); - static_stdout_fd = io::fdopen(1, "<stdout>", io::mode::WRITE); - static_stderr_fd = io::fdopen(2, "<stderr>", io::mode::WRITE); - stdin = &static_stdin_fd; - stdout = &static_stdout_fd; - stderr = &static_stderr_fd; - static let stdinbuf: [BUFSIZ]u8 = [0...]; - stdin = bufio::static_buffered(stdin, stdinbuf, [], &static_stdin_bufio); + stdin = bufio::static_buffered(0, stdinbuf, [], &static_stdin_bufio); static let stdoutbuf: [BUFSIZ]u8 = [0...]; - stdout = bufio::static_buffered(stdout, [], stdoutbuf, &static_stdout_bufio); + stdout = bufio::static_buffered(1, [], stdoutbuf, &static_stdout_bufio); }; @fini fn fini_stdfd() void = { diff --git a/os/stdfd.ha b/os/stdfd.ha @@ -1,10 +0,0 @@ -use io; - -// The standard input. -export let stdin: *io::stream = null: *io::stream; - -// The standard output. -export let stdout: *io::stream = null: *io::stream; - -// The standard error. -export let stderr: *io::stream = null: *io::stream; diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib @@ -547,6 +547,9 @@ gensrcs_io() { 'arch$(ARCH).ha' \ copy.ha \ drain.ha \ + empty.ha \ + filestream.ha \ + handle.ha \ limit.ha \ 'println$(PLATFORM).ha' \ stream.ha \ @@ -706,7 +709,6 @@ os() { '$(PLATFORM)/dirfdfs.ha' \ '$(PLATFORM)/stdfd.ha' \ '$(PLATFORM)/fs.ha' \ - stdfd.ha \ fs.ha gen_ssa os io strings types fs encoding::utf8 bytes bufio errors } @@ -817,7 +819,7 @@ time() { temp() { gen_srcs temp \ '$(PLATFORM).ha' - gen_ssa temp crypto::random encoding::hex fs io os path strio fmt + gen_ssa temp crypto::random encoding::hex fs io os path strio fmt strings } gensrcs_types() { diff --git a/stdlib.mk b/stdlib.mk @@ -821,6 +821,9 @@ stdlib_io_srcs= \ $(STDLIB)/io/arch$(ARCH).ha \ $(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/println$(PLATFORM).ha \ $(STDLIB)/io/stream.ha \ @@ -1005,7 +1008,6 @@ stdlib_os_srcs= \ $(STDLIB)/os/$(PLATFORM)/dirfdfs.ha \ $(STDLIB)/os/$(PLATFORM)/stdfd.ha \ $(STDLIB)/os/$(PLATFORM)/fs.ha \ - $(STDLIB)/os/stdfd.ha \ $(STDLIB)/os/fs.ha $(HARECACHE)/os/os.ssa: $(stdlib_os_srcs) $(stdlib_rt) $(stdlib_io) $(stdlib_strings) $(stdlib_types) $(stdlib_fs) $(stdlib_encoding_utf8) $(stdlib_bytes) $(stdlib_bufio) $(stdlib_errors) @@ -1117,7 +1119,7 @@ $(HARECACHE)/strio/strio.ssa: $(stdlib_strio_srcs) $(stdlib_rt) $(stdlib_io) $(s stdlib_temp_srcs= \ $(STDLIB)/temp/$(PLATFORM).ha -$(HARECACHE)/temp/temp.ssa: $(stdlib_temp_srcs) $(stdlib_rt) $(stdlib_crypto_random) $(stdlib_encoding_hex) $(stdlib_fs) $(stdlib_io) $(stdlib_os) $(stdlib_path) $(stdlib_strio) $(stdlib_fmt) +$(HARECACHE)/temp/temp.ssa: $(stdlib_temp_srcs) $(stdlib_rt) $(stdlib_crypto_random) $(stdlib_encoding_hex) $(stdlib_fs) $(stdlib_io) $(stdlib_os) $(stdlib_path) $(stdlib_strio) $(stdlib_fmt) $(stdlib_strings) @printf 'HAREC \t$@\n' @mkdir -p $(HARECACHE)/temp @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Ntemp \ @@ -2072,6 +2074,9 @@ testlib_io_srcs= \ $(STDLIB)/io/arch$(ARCH).ha \ $(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/println$(PLATFORM).ha \ $(STDLIB)/io/stream.ha \ @@ -2261,7 +2266,6 @@ testlib_os_srcs= \ $(STDLIB)/os/$(PLATFORM)/dirfdfs.ha \ $(STDLIB)/os/$(PLATFORM)/stdfd.ha \ $(STDLIB)/os/$(PLATFORM)/fs.ha \ - $(STDLIB)/os/stdfd.ha \ $(STDLIB)/os/fs.ha $(TESTCACHE)/os/os.ssa: $(testlib_os_srcs) $(testlib_rt) $(testlib_io) $(testlib_strings) $(testlib_types) $(testlib_fs) $(testlib_encoding_utf8) $(testlib_bytes) $(testlib_bufio) $(testlib_errors) @@ -2376,7 +2380,7 @@ $(TESTCACHE)/strio/strio.ssa: $(testlib_strio_srcs) $(testlib_rt) $(testlib_io) testlib_temp_srcs= \ $(STDLIB)/temp/$(PLATFORM).ha -$(TESTCACHE)/temp/temp.ssa: $(testlib_temp_srcs) $(testlib_rt) $(testlib_crypto_random) $(testlib_encoding_hex) $(testlib_fs) $(testlib_io) $(testlib_os) $(testlib_path) $(testlib_strio) $(testlib_fmt) +$(TESTCACHE)/temp/temp.ssa: $(testlib_temp_srcs) $(testlib_rt) $(testlib_crypto_random) $(testlib_encoding_hex) $(testlib_fs) $(testlib_io) $(testlib_os) $(testlib_path) $(testlib_strio) $(testlib_fmt) $(testlib_strings) @printf 'HAREC \t$@\n' @mkdir -p $(TESTCACHE)/temp @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Ntemp \ diff --git a/strio/README b/strio/README @@ -1,5 +1,5 @@ strio provides string-related I/O operations, specifically to make efficient use of memory while building strings incrementally. The main entry points to strio are [[dynamic]] and [[fixed]]. Each of the utility functions (e.g. -[[appendrune]]) work correctly with any [[io::stream]], but for efficiency +[[appendrune]]) work correctly with any [[io::handle]], but for efficiency reasons it is recommended that they are either a strio or [[bufio]] stream. diff --git a/strio/dynamic.ha b/strio/dynamic.ha @@ -13,10 +13,9 @@ type dynamic_stream = struct { // Calling [[io::close]] on this stream will free the buffer. Call [[strio::finish]] // instead to free up resources associated with the stream, but transfer // ownership of the buffer to the caller. -export fn dynamic() *io::stream = { +export fn dynamic() io::handle = { let s = alloc(dynamic_stream { stream = io::stream { - name = "<strio::dynamic>", writer = &dynamic_write, closer = &dynamic_close, ... @@ -26,12 +25,21 @@ export fn dynamic() *io::stream = { return &s.stream; }; +fn get_dynamic_stream(in: io::handle) *dynamic_stream = { + match (in) { + case io::file => + abort("Invalid use of strio on io::file"); + case st: *io::stream => + assert(st.writer == &dynamic_write, + "Invalid use of strio on non-strio I/O stream"); + return st: *dynamic_stream; + }; +}; + // Closes the stream without freeing the buffer, instead transferring ownership // of it to the caller. -export fn finish(s: *io::stream) str = { - assert(s.writer == &dynamic_write, - "strio::finish called on non-strio::dynamic stream"); - let s = s: *dynamic_stream; +export fn finish(in: io::handle) str = { + let s = get_dynamic_stream(in); let buf = s.buf; free(s); return strings::fromutf8(buf); @@ -39,19 +47,15 @@ export fn finish(s: *io::stream) str = { // Resets the buffer's length to zero, but keeps the allocated memory around for // future writes. -export fn reset(s: *io::stream) void = { - assert(s.writer == &dynamic_write, - "strio::reset called on non-strio::dynamic stream"); - const s = s: *dynamic_stream; +export fn reset(in: io::handle) void = { + const s = get_dynamic_stream(in); 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 = { - assert(s.writer == &dynamic_write, - "strio::truncate called on non-strio::dynamic stream"); - let s = s: *dynamic_stream; +export fn truncate(in: io::handle) void = { + let s = get_dynamic_stream(in); delete(s.buf[..]); }; diff --git a/strio/fixed.ha b/strio/fixed.ha @@ -9,10 +9,9 @@ type fixed_stream = struct { // Creates a write-only string stream using the provided buffer for storage. // The program aborts if writes would exceed the buffer's capacity. -export fn fixed(in: []u8) *io::stream = { +export fn fixed(in: []u8) io::handle = { let s = alloc(fixed_stream { stream = io::stream { - name = "<strio::fixed>", writer = &fixed_write, closer = &fixed_close, ... @@ -25,15 +24,21 @@ export fn fixed(in: []u8) *io::stream = { // Returns the current contents of the buffer as a string. Aborts the program if // invalid UTF-8 has been written to the buffer. -export fn string(s: *io::stream) str = { - if (s.writer == &fixed_write) { - let stream = s: *fixed_stream; +export fn string(in: io::handle) str = { + let stream = match (in) { + case io::file => + abort("Invalid use of strio with io::file"); + case st: *io::stream => + yield st; + }; + + if (stream.writer == &fixed_write) { + let stream = stream: *fixed_stream; const n = len(stream.buf) - len(stream.cur); return strings::fromutf8(stream.buf[..n]); - } else if (s.writer == &dynamic_write) { - let s = s: *dynamic_stream; - let buf = s.buf; - return strings::fromutf8(buf); + } else if (stream.writer == &dynamic_write) { + let stream = stream: *dynamic_stream; + return strings::fromutf8(stream.buf); } else { abort("strio::string called on non-strio stream"); }; diff --git a/strio/ops.ha b/strio/ops.ha @@ -2,16 +2,16 @@ use encoding::utf8; use io; use strings; -// Appends zero or more strings to an [[io::stream]]. The stream needn't be a +// Appends zero or more strings to an [[io::handle]]. The output needn't be a // strio stream, but it's generally more efficient if it is. Returns the number // of bytes written, or an error. -export fn concat(st: *io::stream, strs: str...) (size | io::error) = { +export fn concat(out: io::handle, strs: str...) (size | io::error) = { let n = 0z; for (let i = 0z; i < len(strs); i += 1) { let q = 0z; let buf = strings::toutf8(strs[i]); for (q < len(buf)) { - let w = io::write(st, buf[q..])?; + let w = io::write(out, buf[q..])?; n += w; q -= w; }; @@ -27,24 +27,24 @@ export fn concat(st: *io::stream, strs: str...) (size | io::error) = { assert(string(st) == "hello world"); }; -// Joins several strings together by a delimiter and writes them to a stream. -// The stream needn't be a strio stream, but it's generally more efficient if it -// is. Returns the number of bytes written, or an error. -export fn join(st: *io::stream, delim: str, strs: str...) (size | io::error) = { +// Joins several strings together by a delimiter and writes them to a handle. +// The output needn't be a strio stream, but it's generally more efficient if it +// is. Returns the number of bytes written, or an error. +export fn join(out: io::handle, delim: str, strs: str...) (size | io::error) = { let n = 0z; let delim = strings::toutf8(delim); for (let i = 0z; i < len(strs); i += 1) { let q = 0z; let buf = strings::toutf8(strs[i]); for (q < len(buf)) { - let w = io::write(st, buf[q..])?; + let w = io::write(out, buf[q..])?; n += w; q -= w; }; if (i + 1 < len(strs)) { let q = 0z; for (q < len(delim)) { - let w = io::write(st, delim[q..])?; + let w = io::write(out, delim[q..])?; n += w; q -= w; }; @@ -66,24 +66,24 @@ export fn join(st: *io::stream, delim: str, strs: str...) (size | io::error) = { assert(string(st) == "foo"); }; -// Joins several strings together by a delimiter and writes them to a stream, in -// reverse order. The stream needn't be a strio stream, but it's generally more +// Joins several strings together by a delimiter and writes them to a handle, in +// reverse order. The output needn't be a strio stream, but it's generally more // efficient if it is. Returns the number of bytes written, or an error. -export fn rjoin(st: *io::stream, delim: str, strs: str...) (size | io::error) = { +export fn rjoin(out: io::handle, delim: str, strs: str...) (size | io::error) = { let n = 0z; let delim = strings::toutf8(delim); for (let i = len(strs); i > 0; i -= 1) { let q = 0z; let buf = strings::toutf8(strs[i - 1]); for (q < len(buf)) { - let w = io::write(st, buf[q..])?; + let w = io::write(out, buf[q..])?; n += w; q -= w; }; if (i - 1 > 0) { let q = 0z; for (q < len(delim)) { - let w = io::write(st, delim[q..])?; + let w = io::write(out, delim[q..])?; n += w; q -= w; }; @@ -106,5 +106,5 @@ export fn rjoin(st: *io::stream, delim: str, strs: str...) (size | io::error) = }; // Appends a rune to a stream. -export fn appendrune(st: *io::stream, r: rune) (size | io::error) = - io::write(st, utf8::encoderune(r)); +export fn appendrune(out: io::handle, r: rune) (size | io::error) = + io::write(out, utf8::encoderune(r)); diff --git a/temp/+linux.ha b/temp/+linux.ha @@ -7,6 +7,7 @@ use io; use os; use path; use strio; +use strings; fn get_tmpdir() str = os::tryenv("TMPDIR", "/tmp"); @@ -35,16 +36,17 @@ export fn file( // TODO: Add a custom "close" function which removes the named file match (os::create(get_tmpdir(), fmode, oflags)) { case err: fs::error => - return named(os::cwd, get_tmpdir(), iomode, mode...); + let file = named(os::cwd, get_tmpdir(), iomode, mode...)?; + free(file.1); + return file.0; case f: io::file => return f; }; }; // Creates a named temporary file in the given directory of the given -// filesystem. The path to the temporary file may be found via the name field of -// [[io::stream]]. The caller is responsible for removing the file when they're -// done with it. +// filesystem. The caller is responsible for removing the file and freeing the +// name when they're done with it. // // The I/O mode must be either [[io::mode::WRITE]] or [[io::mode::RDWR]]. // @@ -55,7 +57,7 @@ export fn named( path: str, iomode: io::mode, mode: fs::mode... -) (io::file | fs::error) = { +) ((io::file, str) | fs::error) = { assert(iomode == io::mode::WRITE || iomode == io::mode::RDWR); assert(len(mode) == 0 || len(mode) == 1); @@ -80,7 +82,7 @@ export fn named( case err: fs::error => return err; case f: io::file => - return f; + return (f, strings::dup(fpath)); }; }; abort(); // Unreachable diff --git a/unix/hosts/lookup.ha b/unix/hosts/lookup.ha @@ -13,11 +13,11 @@ export fn lookup(name: str) []ip::addr = { // XXX: Would be cool if we could do this without allocating anything // XXX: Would be cool to have meaningful error handling(?) const file = os::open(path)!; - defer io::close(&file); + defer io::close(file); let addrs: []ip::addr = []; for (true) { - const line = match (bufio::scanline(&file)) { + const line = match (bufio::scanline(file)) { case io::EOF => break; case line: []u8 => diff --git a/unix/passwd/group.ha b/unix/passwd/group.ha @@ -16,10 +16,10 @@ export type grent = struct { userlist: []str, }; -// Reads a Unix-like group entry from a stream. The caller must free the result -// using [[grent_finish]]. -export fn nextgr(stream: *io::stream) (grent | io::EOF | io::error | invalid) = { - let line = match (bufio::scanline(stream)?) { +// Reads a Unix-like group entry from an [[io::handle]]. The caller must free +// the result using [[grent_finish]]. +export fn nextgr(in: io::handle) (grent | io::EOF | io::error | invalid) = { + let line = match (bufio::scanline(in)?) { case ln: []u8 => yield ln; case io::EOF => @@ -72,10 +72,10 @@ export fn getgroup(name: str) (grent | void) = { case => abort("Unable to open /etc/group"); }; - defer io::close(&file); + defer io::close(file); for (true) { - let ent = match (nextgr(&file)) { + let ent = match (nextgr(file)) { case e: grent => yield e; case io::EOF => diff --git a/unix/passwd/passwd.ha b/unix/passwd/passwd.ha @@ -22,10 +22,10 @@ export type pwent = struct { shell: str, }; -// Reads a Unix-like password entry from a stream. The caller must free the -// result using [[pwent_finish]]. -export fn nextpw(stream: *io::stream) (pwent | io::EOF | io::error | invalid) = { - let line = match (bufio::scanline(stream)?) { +// Reads a Unix-like password entry from an [[io::handle]]. The caller must free +// the result using [[pwent_finish]]. +export fn nextpw(file: io::handle) (pwent | io::EOF | io::error | invalid) = { + let line = match (bufio::scanline(file)?) { case io::EOF => return io::EOF; case ln: []u8 => @@ -91,10 +91,10 @@ export fn getuser(username: str) (pwent | void) = { case => abort("Can't open /etc/passwd"); }; - defer io::close(&file); + defer io::close(file); for (true) { - let ent = match (nextpw(&file)) { + let ent = match (nextpw(file)) { case e: pwent => yield e; case io::EOF => diff --git a/unix/poll/+linux.ha b/unix/poll/+linux.ha @@ -1,6 +1,7 @@ use errors; -use time; +use io; use rt; +use time; // Events bitfield for the events and revents field of [[pollfd]]. export type event = enum i16 { @@ -13,8 +14,7 @@ export type event = enum i16 { // A single file descriptor to be polled. export type pollfd = struct { - // XXX: TODO: Update me to io::file? - fd: int, + fd: io::file, events: event, revents: event, }; diff --git a/unix/resolvconf/load.ha b/unix/resolvconf/load.ha @@ -23,10 +23,10 @@ export fn load() []ip::addr = { }; const file = os::open(path)!; - defer io::close(&file); + defer io::close(file); for (true) { - const line = match (bufio::scanline(&file)) { + const line = match (bufio::scanline(file)) { case io::EOF => break; case line: []u8 => diff --git a/unix/tty/+linux/isatty.ha b/unix/tty/+linux/isatty.ha @@ -3,13 +3,7 @@ use io; use os; // Returns whether the given stream is connected to a terminal. -export fn isatty(stream: *io::stream) bool = { - let fd = match (io::unwrapfd(stream)) { - case void => - return false; - case fd: int => - yield fd; - }; +export fn isatty(fd: io::file) bool = { let wsz = rt::winsize { ... }; match (rt::ioctl(fd, rt::TIOCGWINSZ, &wsz: *void)) { case e: rt::errno => diff --git a/unix/tty/+linux/winsize.ha b/unix/tty/+linux/winsize.ha @@ -3,14 +3,8 @@ use io; use os; use rt; -// Returns the dimensions of underlying terminal of the stream. -export fn winsize(tty: *io::stream) (ttysize | error) = { - let fd = match (io::unwrapfd(tty)) { - case void => - return errors::unsupported; - case fd: int => - yield fd; - }; +// Returns the dimensions of underlying terminal for an [[io::file]]. +export fn winsize(fd: io::file) (ttysize | error) = { let wsz = rt::winsize { ... }; match (rt::ioctl(fd, rt::TIOCGWINSZ, &wsz: *void)) { case e: rt::errno => @@ -24,7 +18,7 @@ export fn winsize(tty: *io::stream) (ttysize | error) = { }; case int => return ttysize { - rows = wsz.ws_row, + rows = wsz.ws_row, columns = wsz.ws_col, }; }; diff --git a/uuid/uuid.ha b/uuid/uuid.ha @@ -50,8 +50,8 @@ export fn generate() uuid = { // Returns true if two UUIDs are equal. export fn compare(a: uuid, b: uuid) bool = bytes::equal(a, b); -// Encodes a UUID as a string and writes it to a stream. -export fn encode(out: *io::stream, in: uuid) (size | io::error) = { +// Encodes a UUID as a string and writes it to an I/O handle. +export fn encode(out: io::handle, in: uuid) (size | io::error) = { let z = 0z; for (let i = TIME_LOW; i < TIME_LOW + 4; i += 1) { z += fmt::fprintf(out, "{:02x}", in[i])?; @@ -72,8 +72,8 @@ export fn encode(out: *io::stream, in: uuid) (size | io::error) = { return z; }; -// Encodes a UUID as a URI and writes it to a stream. -export fn uri(out: *io::stream, in: uuid) (size | io::error) = { +// Encodes a UUID as a URI and writes it to an I/O handle. +export fn uri(out: io::handle, in: uuid) (size | io::error) = { return fmt::fprintf(out, "urn:uuid:")? + encode(out, in)?; }; @@ -105,8 +105,8 @@ export fn encodeuri(in: uuid) str = { assert(encodestr(in) == "3ded910c-8080-4bc8-af39-b6cccee36741"); }; -// Decodes a UUID as a string from an [[io::stream]]. -export fn decode(in: *io::stream) (uuid | invalid | io::error) = { +// Decodes a UUID as a string from an [[io::handle]]. +export fn decode(in: io::handle) (uuid | invalid | io::error) = { let u: uuid = [0...]; for (let i = 0z; i < len(u); i += 1) { let buf: [2]u8 = [0...];