hare

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

commit 8cf001a76af06a43f7ead889952694429d720956
parent 5d450b1a647e0aeeb1e49e28e0d16af11358e12c
Author: Drew DeVault <sir@cmpwn.com>
Date:   Mon,  5 Apr 2021 11:11:34 -0400

all: generalize error handling

Diffstat:
Mbufio/buffered.ha | 3++-
Mbufio/dynamic.ha | 9+++++----
Mcmd/hare/plan.ha | 6+++---
Mcmd/hare/schedule.ha | 4++--
Mcmd/hare/subcmds.ha | 10+++++-----
Mcmd/harec/errors.ha | 2+-
Mcmd/harec/main.ha | 2+-
Mcrypto/random/+linux.ha | 16++--------------
Aerrors/common.ha | 32++++++++++++++++++++++++++++++++
Aerrors/docs.ha | 6++++++
Aerrors/opaque.ha | 26++++++++++++++++++++++++++
Aerrors/rt.ha | 16++++++++++++++++
Aerrors/string.ha | 20++++++++++++++++++++
Mfs/fs.ha | 21+++++++++++----------
Mfs/types.ha | 34++++++++++------------------------
Mfs/util.ha | 13+++++++------
Mhare/lex/+test.ha | 2+-
Mhare/lex/lex.ha | 4++--
Mhare/module/manifest.ha | 7++++---
Mhare/module/types.ha | 8++++----
Mhare/parse/types.ha | 2+-
Mio/+test/copy.ha | 5++++-
Mio/+test/limit.ha | 6++++--
Mio/copy.ha | 4+++-
Mio/stream.ha | 16+++++++++-------
Mio/types.ha | 22+++++-----------------
Mnet/+linux/socket.ha | 7++++---
Mnet/+linux/util.ha | 17+++--------------
Mnet/socket.ha | 3++-
Mos/+linux/dirfdfs.ha | 13+++++++------
Dos/+linux/errors.ha | 16----------------
Mos/+linux/fdstream.ha | 17+++++++++--------
Mos/+linux/fs.ha | 7++++---
Mos/exec/cmd.ha | 7++++---
Mos/exec/exec+linux.ha | 41+++++++++++++++--------------------------
Mos/exec/process+linux.ha | 5+++--
Mos/exec/types.ha | 16++++++----------
Mrt/+linux/errno.ha | 2+-
Mscripts/gen-stdlib | 28+++++++++++++++++++---------
Mstdlib.mk | 68++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Mstrio/dynamic.ha | 9+++++----
41 files changed, 318 insertions(+), 234 deletions(-)

diff --git a/bufio/buffered.ha b/bufio/buffered.ha @@ -1,4 +1,5 @@ use bytes; +use errors; use io; use strings; @@ -94,7 +95,7 @@ export fn isbuffered(s: *io::stream) bool = { export fn any_isbuffered(s: *io::stream) bool = { for (!isbuffered(s)) { s = match (io::source(s)) { - io::unsupported => return false, + errors::unsupported => return false, s: *io::stream => s, }; }; diff --git a/bufio/dynamic.ha b/bufio/dynamic.ha @@ -1,4 +1,5 @@ use bytes; +use errors; use io; type dynamic_stream = struct { @@ -141,9 +142,9 @@ export fn reset(s: *io::stream) void = { // Truncates the buffer, freeing memory associated with it and setting its // length to zero. -export fn truncate(s: *io::stream) (void | io::unsupported) = { +export fn truncate(s: *io::stream) (void | errors::unsupported) = { if (s.closer != &dynamic_close) { - return io::unsupported; + return errors::unsupported; }; let s = s: *dynamic_stream; s.pos = 0; @@ -177,7 +178,7 @@ export fn truncate(s: *io::stream) (void | io::unsupported) = { assert(io::seek(s, 0, io::whence::END) as io::off == 5: io::off); assert(io::write(s, [4, 5, 6]) as size == 3); assert(bytes::equal(buffer(s), [0, 0, 1, 2, 3, 4, 5, 6])); - // TODO: this should check for io::unsupported (harec bug prevents that) + // TODO: this should check for errors::unsupported (harec bug prevents that) assert(io::read(s, buf[..]) is io::error); io::close(s); @@ -186,6 +187,6 @@ export fn truncate(s: *io::stream) (void | io::unsupported) = { assert(io::read(s, buf[..1]) as size == 1 && buf[0] == 1); assert(io::seek(s, 1, io::whence::CUR) as io::off == 2: io::off); assert(io::read(s, buf[..]) is io::EOF); - // TODO: this should check for io::unsupported (harec bug prevents that) + // TODO: this should check for errors::unsupported (harec bug prevents that) assert(io::write(s, [1, 2]) is io::error); }; diff --git a/cmd/hare/plan.ha b/cmd/hare/plan.ha @@ -48,7 +48,7 @@ type plan = struct { fn mkplan(ctx: *module::context) plan = { const rtdir = match (module::lookup(ctx, ["rt"])) { err: module::error => fmt::fatal("Error resolving rt: {}", - module::errstr(err)), + module::strerror(err)), ver: module::version => ver.basedir, }; return plan { @@ -115,7 +115,7 @@ fn plan_execute(plan: *plan, verbose: bool) void = { match (execute(plan, task, verbose)) { err: exec::error => fmt::fatal("Error: {}: {}", - task.cmd[0], exec::errstr(err)), + task.cmd[0], exec::strerror(err)), err: exec::exit_status! => fmt::fatal("Error: {}: {}", task.cmd[0], exec::exitstr(err)), void => void, @@ -139,7 +139,7 @@ fn update_cache(plan: *plan, mod: modcache) void = { match (module::manifest_write(plan.context, &manifest)) { err: module::error => fmt::fatal( "Error updating module cache: {}", - module::errstr(err)), + module::strerror(err)), void => void, }; }; diff --git a/cmd/hare/schedule.ha b/cmd/hare/schedule.ha @@ -34,7 +34,7 @@ fn sched_module(plan: *plan, ident: ast::ident, link: *[]*task) *task = { err: module::error => { let ident = unparse::identstr(ident); fmt::fatal("Error resolving {}: {}", - ident, module::errstr(err)); + ident, module::strerror(err)); }, ver: module::version => ver, }; @@ -167,7 +167,7 @@ fn sched_hare_object( plan.context, namespace)) { err: module::error => fmt::fatal( "Error reading cache entry for {}: {}", - ns, module::errstr(err)), + ns, module::strerror(err)), m: module::manifest => m, }; defer module::manifest_finish(&manifest); diff --git a/cmd/hare/subcmds.ha b/cmd/hare/subcmds.ha @@ -80,7 +80,7 @@ fn build(args: []str) void = { ver: module::version => ver, err: module::error => fmt::fatal( "Error scanning input module: {}", - module::errstr(err)), + module::strerror(err)), }; let depends: []*task = []; @@ -178,7 +178,7 @@ fn run(args: []str) void = { ver: module::version => ver, err: module::error => fmt::fatal( "Error scanning input module: {}", - module::errstr(err)), + module::strerror(err)), }; let depends: []*task = []; @@ -193,7 +193,7 @@ fn run(args: []str) void = { sched_hare_exe(&plan, ver, output, depends...); plan_execute(&plan, verbose); let cmd = match (exec::cmd(output, runargs...)) { - err: exec::error => fmt::fatal("exec: {}", exec::errstr(err)), + err: exec::error => fmt::fatal("exec: {}", exec::strerror(err)), cmd: exec::command => cmd, }; exec::setname(&cmd, input); @@ -256,7 +256,7 @@ fn test(args: []str) void = { ver: module::version => ver, err: module::error => fmt::fatal( "Error scanning input module: {}", - module::errstr(err)), + module::strerror(err)), }; let depends: []*task = []; @@ -271,7 +271,7 @@ fn test(args: []str) void = { sched_hare_exe(&plan, ver, output, depends...); plan_execute(&plan, verbose); let cmd = match (exec::cmd(output, runargs...)) { - err: exec::error => fmt::fatal("exec: {}", exec::errstr(err)), + err: exec::error => fmt::fatal("exec: {}", exec::strerror(err)), cmd: exec::command => cmd, }; exec::setname(&cmd, input); diff --git a/cmd/harec/errors.ha b/cmd/harec/errors.ha @@ -10,7 +10,7 @@ use strings; fn printerr(err: parse::error) void = { match (err) { err: lex::syntax => printerr_syntax(err), - err: io::error => fmt::errorln(io::errstr(err)), + err: io::error => fmt::errorln(io::strerror(err)), }; }; diff --git a/cmd/harec/main.ha b/cmd/harec/main.ha @@ -41,7 +41,7 @@ export fn main() void = { let input = match (os::open(cmd.args[i])) { f: *io::stream => f, err: io::error => fmt::fatal("Error opening {}: {}", - cmd.args[i], io::errstr(err)), + cmd.args[i], io::strerror(err)), }; defer io::close(input); diff --git a/crypto/random/+linux.ha b/crypto/random/+linux.ha @@ -1,3 +1,4 @@ +use errors; use rt; use io; @@ -21,20 +22,7 @@ export fn buffer(buf: []u8) void = { fn rand_reader(s: *io::stream, buf: []u8) (size | io::EOF | io::error) = { assert(s == stream); return match (rt::getrandom(buf: *[*]u8, len(buf), 0)) { - err: rt::errno => errno_to_io(err), + err: rt::errno => errors::errno(err), n: size => n, }; }; - -fn io_errstr(data: *void) str = { - const errno = data: uintptr: int: rt::errno; - return rt::errstr(errno); -}; - -fn errno_to_io(err: rt::errno) io::error = { - let e = io::os_error { - string = &io_errstr, - data = err: uintptr: *void, - }; - return e: io::error; -}; diff --git a/errors/common.ha b/errors/common.ha @@ -0,0 +1,32 @@ +// The requested resource is not available. +export type busy = void!; + +// An attempt was made to create a resource which already exists. +export type exists = void!; + +// An function was called with an invalid combination of arguments. +export type invalid = void!; + +// The user does not have permission to use this resource. +export type noaccess = void!; + +// An entry was requested which does not exist. +export type noentry = void; + +// The requested operation caused a numeric overflow condition. +export type overflow = void!; + +// The requested operation is not supported. +export type unsupported = void!; + +// A tagged union of all error types. +export type error = ( + busy | + exists | + invalid | + noaccess | + noentry | + overflow | + unsupported | + opaque +); diff --git a/errors/docs.ha b/errors/docs.ha @@ -0,0 +1,6 @@ +// The error module provides a number of error types which describe error +// conditions common to many types of code, as well as convenience functions for +// turning these errors into strings, and an error type which may be used to +// wrap some implementation-defined error data. If your program's error cases +// can be modelled by the errors offered in this module, you may use them to +// make your error types compatible with a wider range of support modules. diff --git a/errors/opaque.ha b/errors/opaque.ha @@ -0,0 +1,26 @@ +// An "opaque" error is used as a portable error type for an underlying error +// which is implementation-specific. It provides a function which can be used to +// produce a string describing the error, and a small storage area for arbitrary +// implementation-specific storage. +// +// The following example shows the usage of this type for custom errors: +// +// fn wraperror(err: myerror) error::opaque = { +// static assert(size(myerror) <= size(error::opaque_data)); +// let wrapped = opaque { strerror = &opaque_strerror, ... }; +// let myptr = &wrapped.data: *myerror; +// *myptr = err; +// }; +// +// fn opaque_strerror(err: *opaque_data) const str = { +// let ptr = &err: *myerr; +// return strerror(*ptr); +// }; +export type opaque = struct { + strerror: *fn(op: *opaque_data) const str, + data: opaque_data, +}!; + +// Up to 24 bytes of arbitrary data that the opaque error type may use for +// domain-specific storage. +export type opaque_data = [24]u8; diff --git a/errors/rt.ha b/errors/rt.ha @@ -0,0 +1,16 @@ +use rt; + +// Wraps an [rt::errno] to produce an [errors::opaque]. This is a non-portable +// interface which is mainly provided to support internal stdlib requirements. +export fn errno(errno: rt::errno) opaque = { + static assert(size(rt::errno) <= size(opaque_data)); + let err = opaque { strerror = &rt_strerror, ... }; + let ptr = &err.data: *rt::errno; + *ptr = errno; + return err; +}; + +fn rt_strerror(err: *opaque_data) const str = { + let err = &err: *rt::errno; + return rt::strerror(*err); +}; diff --git a/errors/string.ha b/errors/string.ha @@ -0,0 +1,20 @@ +// Converts an [error] into a human-friendly string representation. +// +// Note that this strerror implementation lacks any context-specific information +// about the error types supported. For example, [exists] is stringified as "An +// attempt was made to create a resource which already exists", but if source of +// the error is, say, creating a file, it would likely be more appropriate to +// use the term "file" rather than "resource". For this reason, it is preferred +// that modules which return an error type from this module provide their own +// strerror function which provides more context-appropriate error messages for +// each of those types. +export fn strerror(err: error) const str = match (err) { + busy => "The requested resource is not available", + exists => "An attempt was made to create a resource which already exists", + invalid => "An function was called with an invalid combination of arguments", + noaccess => "The user does not have permission to use this resource", + noentry => "An entry was requested which does not exist", + overflow => "The requested operation caused a numeric overflow condition", + unsupported => "The requested operation is not supported", + op: opaque => op.strerror(&op), +}; diff --git a/fs/fs.ha b/fs/fs.ha @@ -1,3 +1,4 @@ +use errors; use io; use path; @@ -13,7 +14,7 @@ export fn close(fs: *fs) void = { // RDONLY. export fn open(fs: *fs, path: str, flags: flags...) (*io::stream | error) = { return match (fs.open) { - null => io::unsupported, + null => errors::unsupported, f: *openfunc => f(fs, path, flags...), }; }; @@ -31,7 +32,7 @@ export fn create( ) (*io::stream | error) = { mode = mode & 0o777; return match (fs.create) { - null => io::unsupported, + null => errors::unsupported, f: *createfunc => f(fs, path, mode, flags...), }; }; @@ -39,7 +40,7 @@ export fn create( // Removes a file. export fn remove(fs: *fs, path: str) (void | error) = { return match (fs.remove) { - null => io::unsupported, + null => errors::unsupported, f: *removefunc => f(fs, path), }; }; @@ -49,7 +50,7 @@ export fn remove(fs: *fs, path: str) (void | error) = { // returned is undefined. export fn iter(fs: *fs, path: str) (*iterator | error) = { return match (fs.iter) { - null => io::unsupported, + null => errors::unsupported, f: *iterfunc => f(fs, path), }; }; @@ -58,7 +59,7 @@ export fn iter(fs: *fs, path: str) (*iterator | error) = { // information is returned about the link, not its target. export fn stat(fs: *fs, path: str) (filestat | error) = { return match (fs.stat) { - null => io::unsupported, + null => errors::unsupported, f: *statfunc => f(fs, path), }; }; @@ -68,7 +69,7 @@ export fn stat(fs: *fs, path: str) (filestat | error) = { // its parent. export fn subdir(fs: *fs, path: str) (*fs | error) = { return match (fs.subdir) { - null => io::unsupported, + null => errors::unsupported, f: *subdirfunc => f(fs, path), }; }; @@ -76,7 +77,7 @@ export fn subdir(fs: *fs, path: str) (*fs | error) = { // Creates a directory. export fn mkdir(fs: *fs, path: str) (void | error) = { return match (fs.mkdir) { - null => io::unsupported, + null => errors::unsupported, f: *mkdirfunc => f(fs, path), }; }; @@ -86,7 +87,7 @@ export fn mkdirs(fs: *fs, path: str) (void | error) = { let parent = path::dirname(path); if (path != parent) { match (mkdirs(fs, parent)) { - exists => void, + errors::exists => void, err: error => return err, void => void, }; @@ -98,10 +99,10 @@ export fn mkdirs(fs: *fs, path: str) (void | error) = { // remove its contents as well. export fn rmdir(fs: *fs, path: str) (void | error) = { if (path == "") { - return invalid; + return errors::invalid; }; return match (fs.rmdir) { - null => io::unsupported, + null => errors::unsupported, f: *rmdirfunc => f(fs, path), }; }; diff --git a/fs/types.ha b/fs/types.ha @@ -1,36 +1,22 @@ +use errors; use io; -use strings; use path; +use strings; use time; -// An entry was requested which does not exist. -export type noentry = void!; - -// An attempt was made to create a file or directory which already exists. -export type exists = void!; - -// The user does not have permission to use this resource. -export type noaccess = void!; - -// The requested file is not available. -export type busy = void!; - // An entry of a particular type was sought, but is something else in practice. // For example, opening a file with [iter]. export type wrongtype = void!; -// An function was called with an invalid combination of arguments -// For example, calling [rmdir] with empty path -export type invalid = void!; - // All possible fs error types. -export type error = (noentry - | noaccess - | exists - | busy - | wrongtype - | invalid - | io::error)!; +export type error = ( + errors::noentry | + errors::noaccess | + errors::exists | + errors::busy | + errors::invalid | + wrongtype | + io::error)!; // File mode information. These bits do not necessarily reflect the underlying // operating system's mode representation, though they were chosen to be diff --git a/fs/util.ha b/fs/util.ha @@ -1,14 +1,15 @@ +use errors; use io; use path; use strings; // Returns a human-friendly representation of an error. -export fn errstr(err: error) const str = match (err) { - noentry => "File or directory not found", - noaccess => "Permission denied", - exists => "File or directory exists", - invalid => "Invalid argument", - err: io::error => io::errstr(err), +export fn strerror(err: error) const str = match (err) { + errors::noentry => "File or directory not found", + errors::noaccess => "Permission denied", + errors::exists => "File or directory exists", + errors::invalid => "Invalid argument", + err: io::error => io::strerror(err), }; // Converts a mode into a Unix-like mode string (e.g. "-rw-r--r--"). The string diff --git a/hare/lex/+test.ha b/hare/lex/+test.ha @@ -64,7 +64,7 @@ fn lextest(in: str, expected: [](uint, uint, token)) void = { abort(); }, err: error => { - fmt::errorfln("{}: {}", i, errstr(err)); + fmt::errorfln("{}: {}", i, strerror(err)); abort(); }, }; diff --git a/hare/lex/lex.ha b/hare/lex/lex.ha @@ -24,10 +24,10 @@ export type syntax = (location, str)!; export type error = (io::error | syntax)!; // Returns a human-friendly string for a given error -export fn errstr(err: error) const str = { +export fn strerror(err: error) const str = { static let buf: [2048]u8 = [0...]; return match (err) { - err: io::error => io::errstr(err), + err: io::error => io::strerror(err), s: syntax => fmt::bsprintf(buf, "{}:{},{}: Syntax error: {}", s.0.path, s.0.line, s.0.col, s.1), }; diff --git a/hare/module/manifest.ha b/hare/module/manifest.ha @@ -2,6 +2,7 @@ use bufio; use bytes; use encoding::hex; use encoding::utf8; +use errors; use fmt; use fs; use hare::ast; @@ -56,7 +57,7 @@ export fn manifest_load(ctx: *context, ident: ast::ident) (manifest | error) = { defer unlock(ctx.fs, cachedir, l); let file = match (fs::open(ctx.fs, mpath, fs::flags::RDONLY)) { - fs::noentry => return manifest, + errors::noentry => return manifest, err: fs::error => return err, file: *io::stream => file, }; @@ -280,7 +281,7 @@ fn lock(fs: *fs::fs, cachedir: str) (*io::stream | error) = { // XXX: I wonder if this should be some generic function in fs or // something match (os::mkdirs(cachedir)) { - fs::exists => void, + errors::exists => void, void => void, e: fs::error => return e, }; @@ -291,7 +292,7 @@ fn lock(fs: *fs::fs, cachedir: str) (*io::stream | error) = { for (true) { match (fs::create(fs, lockpath, 0o644, fs::flags::EXCL)) { fd: *io::stream => return fd, - (fs::busy | fs::exists) => void, + (errors::busy | errors::exists) => void, err: fs::error => return err, }; if (!logged) { diff --git a/hare/module/types.ha b/hare/module/types.ha @@ -64,13 +64,13 @@ export type error = ( module_not_found | ambiguous)!; -export fn errstr(err: error) const str = { +export fn strerror(err: error) const str = { // Should be more than enough for PATH_MAX * 2 static let buf: [4096]u8 = [0...]; return match (err) { - err: fs::error => fs::errstr(err), - err: io::error => io::errstr(err), - err: parse::error => parse::errstr(err), + err: fs::error => fs::strerror(err), + err: io::error => io::strerror(err), + err: parse::error => parse::strerror(err), module_not_found => "Module not found", amb: ambiguous => fmt::bsprintf(buf, "Cannot choose between {} and {}", amb.0, amb.1), diff --git a/hare/parse/types.ha b/hare/parse/types.ha @@ -5,7 +5,7 @@ use hare::lex; export type error = lex::error!; // Convert an error into a human-friendly string -export fn errstr(err: error) const str = lex::errstr(err: lex::error); +export fn strerror(err: error) const str = lex::strerror(err: lex::error); fn syntaxerr( loc: lex::location, diff --git a/io/+test/copy.ha b/io/+test/copy.ha @@ -1,3 +1,5 @@ +use errors; + fn test_copier_read(s: *stream, buf: []u8) (size | EOF | error) = { let stream = s: *test_stream; if (stream.r == 0) { @@ -24,7 +26,8 @@ fn test_copier_copy(a: *stream, b: *stream) (size | error) = { return 1337; }; -fn test_copy_unsupported(a: *stream, b: *stream) (size | error) = unsupported; +fn test_copy_unsupported(a: *stream, b: *stream) (size | error) = + errors::unsupported; fn io::println(msg: str) void; fn strconv::ztos(z: size) str; diff --git a/io/+test/limit.ha b/io/+test/limit.ha @@ -1,3 +1,5 @@ +use errors; + @test fn limit() void = { let buf: [15z]u8 = [0...]; let source_stream = test_stream_open(); @@ -5,7 +7,7 @@ let r_stream = limitreader(&source_stream.stream, 20); match (write(r_stream, buf)) { - unsupported => void, + errors::unsupported => void, * => abort(), }; match (read(r_stream, buf)) { @@ -20,7 +22,7 @@ let w_stream = limitwriter(&source_stream.stream, 20); match (read(w_stream, buf)) { - unsupported => void, + errors::unsupported => void, * => abort(), }; match (write(w_stream, buf)) { diff --git a/io/copy.ha b/io/copy.ha @@ -1,3 +1,5 @@ +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) = { @@ -5,7 +7,7 @@ export fn copy(dest: *stream, src: *stream) (error | size) = { null => void, c: *copier => match (c(dest, src)) { err: error => match (err) { - unsupported => void, // Use fallback + errors::unsupported => void, // Use fallback * => return err, }, s: size => return s, diff --git a/io/stream.ha b/io/stream.ha @@ -1,3 +1,5 @@ +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. @@ -33,7 +35,7 @@ export type stream = struct { // the number of bytes read. export fn read(s: *stream, buf: []u8) (size | EOF | error) = { return match (s.reader) { - null => unsupported, + null => errors::unsupported, r: *reader => r(s, buf), }; }; @@ -42,7 +44,7 @@ export fn read(s: *stream, buf: []u8) (size | EOF | error) = { // the number of bytes written. export fn write(s: *stream, buf: const []u8) (size | error) = { return match (s.writer) { - null => unsupported, + null => errors::unsupported, w: *writer => w(s, buf), }; }; @@ -50,7 +52,7 @@ export fn write(s: *stream, buf: const []u8) (size | error) = { // Closes the stream. export fn close(s: *stream) (error | void) = { return match (s.closer) { - null => unsupported, + null => errors::unsupported, c: *closer => c(s), }; }; @@ -58,7 +60,7 @@ export fn close(s: *stream) (error | void) = { // Sets the offset within the stream. export fn seek(s: *stream, off: off, w: whence) (off | error) = { return match (s.seeker) { - null => unsupported, + null => errors::unsupported, sk: *seeker => sk(s, off, w), }; }; @@ -66,15 +68,15 @@ export fn seek(s: *stream, off: off, w: whence) (off | error) = { // Returns the current offset within the stream. export fn tell(s: *stream) (off | error) = { return match (s.seeker) { - null => unsupported, + null => errors::unsupported, sk: *seeker => sk(s, 0, whence::CUR), }; }; // Returns the underlying stream for a stream which wraps another stream. -export fn source(s: *stream) (*io::stream | unsupported) = { +export fn source(s: *stream) (*io::stream | errors::unsupported) = { return match (s.unwrap) { - null => unsupported, + null => errors::unsupported, uw: *unwrap => uw(s), }; }; diff --git a/io/types.ha b/io/types.ha @@ -1,25 +1,13 @@ -// An error produced by the underlying source. -export type os_error = struct { - string: *fn(data: *void) str, - data: *void, -}!; - -// An error indicating that the requested operation is not supported. -export type unsupported = void!; +use errors; // Any error which may be returned from an I/O function. -export type error = (os_error | unsupported)!; +export type error = (errors::unsupported | errors::opaque)!; // Indicates an end-of-file condition. export type EOF = void; -// Converts an I/O error into a user-friendly string. -export fn errstr(err: error) str = { - return match (err) { - err: os_error => err.string(err.data), - unsupported => "The requested operation is not supported", - }; -}; +// Converts an I/O [error] into a user-friendly string. +export fn strerror(err: error) str = errors::strerror(err); // Used to indicate if a stream should be used for reading, or writing, or both. export type mode = enum u8 { @@ -58,7 +46,7 @@ export type closer = fn(s: *stream) void; // // Returns the number of bytes copied, or an error if one occured. Do not close // either stream. If the operation is unsupported for this particular pair of -// streams, return [io::unsupported] to have [io::copy] proceed with its +// streams, return [errors::unsupported] to have [io::copy] proceed with its // fallback implementation. export type copier = fn(to: *stream, from: *stream) (size | error); diff --git a/net/+linux/socket.ha b/net/+linux/socket.ha @@ -1,3 +1,4 @@ +use errors; use io; use net::ip; use net::unix; @@ -39,7 +40,7 @@ export fn connect_unix( ) (*io::stream | io::error) = { let sockaddr = match (unix::to_native(addr)) { a: rt::sockaddr => a, - unix::invalid => return io::unsupported, // path too long + unix::invalid => return errors::unsupported, // path too long }; const sockfd = connect_fd(sockaddr, options...)?; return os::fdopen(sockfd, string(addr), @@ -78,7 +79,7 @@ fn listen_fd( p: *u16 => { if (addr.in.sin_family != rt::AF_INET && addr.in.sin_family != rt::AF_INET6) { - return io::unsupported; + return errors::unsupported; }; let sn = rt::sockaddr {...}; let al = sockasz(addr); @@ -134,7 +135,7 @@ export fn listen_unix( ) (*listener | io::error) = { let sockaddr = match (unix::to_native(addr)) { a: rt::sockaddr => a, - unix::invalid => return io::unsupported, // path too long + unix::invalid => return errors::unsupported, // path too long }; let sockfd = listen_fd(sockaddr, options...)?; return alloc(stream_listener { diff --git a/net/+linux/util.ha b/net/+linux/util.ha @@ -1,3 +1,4 @@ +use errors; use io; use fmt; use net::ip; @@ -5,19 +6,6 @@ use net::unix; use os; use rt; -fn io_errstr(data: *void) str = { - const errno = data: uintptr: int: rt::errno; - return rt::errstr(errno); -}; - -fn errno_to_io(err: rt::errno) io::error = { - let err = io::os_error { - string = &io_errstr, - data = err: uintptr: *void, - }; - return err: io::error; -}; - fn setsockopt(sockfd: int, option: int, value: bool) (void | rt::errno) = { let val: int = if (value) 1 else 0; rt::setsockopt(sockfd, rt::SOL_SOCKET, option, @@ -33,7 +21,8 @@ fn setfcntl(sockfd: int, flag: int) (void | rt::errno) = { fn wrap(ie: (int | rt::errno)) (int | io::error) = { match (ie) { i: int => i, - er: rt::errno => errno_to_io(er) + // XXX: Why do we have to cast this? + er: rt::errno => errors::errno(er): io::error, }; }; diff --git a/net/socket.ha b/net/socket.ha @@ -1,3 +1,4 @@ +use errors; use io; use net::ip; @@ -53,7 +54,7 @@ export type listener = struct { export fn accept(l: *listener) (*io::stream | io::error) = { return match (l.accept) { f: *fn(l: *listener) (*io::stream | io::error) => f(l), - null => io::unsupported, + null => errors::unsupported, }; }; diff --git a/os/+linux/dirfdfs.ha b/os/+linux/dirfdfs.ha @@ -1,4 +1,5 @@ use bytes; +use errors; use encoding::utf8; use fs; use io; @@ -94,13 +95,13 @@ export fn dirfs_clone(fs: *fs::fs, resolve: resolve_flags...) *fs::fs = { }; fn errno_to_fs(err: rt::errno) fs::error = switch (err) { - rt::ENOENT => fs::noentry, - rt::EEXIST => fs::exists, - rt::EACCES => fs::noaccess, - rt::EBUSY => fs::busy, + rt::ENOENT => errors::noentry, + rt::EEXIST => errors::exists, + rt::EACCES => errors::noaccess, + rt::EBUSY => errors::busy, rt::ENOTDIR => fs::wrongtype, - rt::EOPNOTSUPP, rt::ENOSYS => io::unsupported, - * => errno_to_io(err), + rt::EOPNOTSUPP, rt::ENOSYS => errors::unsupported, + * => errors::errno(err), }; fn _fs_open( diff --git a/os/+linux/errors.ha b/os/+linux/errors.ha @@ -1,16 +0,0 @@ -use io; -use rt; - -fn io_errstr(data: *void) str = { - const errno = data: uintptr: int: rt::errno; - return rt::errstr(errno); -}; - -// TODO: Implement the inverse of this and make it public -fn errno_to_io(err: rt::errno) io::error = { - let err = io::os_error { - string = &io_errstr, - data = err: uintptr: *void, - }; - return err: io::error; -}; diff --git a/os/+linux/fdstream.ha b/os/+linux/fdstream.ha @@ -1,3 +1,4 @@ +use errors; use io; use rt; use strings; @@ -50,7 +51,7 @@ fn is_fdstream(s: *io::stream) bool = { export fn streamfd(s: *io::stream) (int | void) = { for (!is_fdstream(s)) { s = match (io::source(s)) { - io::unsupported => return, + errors::unsupported => return, s: *io::stream => s, }; }; @@ -61,7 +62,7 @@ export fn streamfd(s: *io::stream) (int | void) = { fn fd_read(s: *io::stream, buf: []u8) (size | io::EOF | io::error) = { let stream = s: *fd_stream; return match (rt::read(stream.fd, buf: *[*]u8, len(buf))) { - err: rt::errno => errno_to_io(err), + err: rt::errno => errors::errno(err), n: size => switch (n) { 0 => io::EOF, * => n, @@ -72,7 +73,7 @@ fn fd_read(s: *io::stream, buf: []u8) (size | io::EOF | io::error) = { fn fd_write(s: *io::stream, buf: const []u8) (size | io::error) = { let stream = s: *fd_stream; return match (rt::write(stream.fd, buf: *const [*]u8, len(buf))) { - err: rt::errno => errno_to_io(err), + err: rt::errno => errors::errno(err), n: size => n, }; }; @@ -94,7 +95,7 @@ def SENDFILE_MAX: size = 2147479552z; fn fd_copy(to: *io::stream, from: *io::stream) (size | io::error) = { if (!is_fdstream(from)) { - return io::unsupported; + return errors::unsupported; }; let to = to: *fd_stream, from = from: *fd_stream; @@ -105,11 +106,11 @@ fn fd_copy(to: *io::stream, from: *io::stream) (size | io::error) = { err: rt::errno => switch (err) { rt::EINVAL => { if (sum == 0) { - return io::unsupported; + return errors::unsupported; }; - return errno_to_io(err); + return errors::errno(err); }, - * => return errno_to_io(err), + * => return errors::errno(err), }, n: size => switch (n) { 0 => return sum, @@ -128,7 +129,7 @@ fn fd_seek( ) (io::off | io::error) = { let stream = s: *fd_stream; return match (rt::lseek(stream.fd, off: i64, whence: uint)) { - err: rt::errno => errno_to_io(err), + err: rt::errno => errors::errno(err), n: i64 => n: io::off, }; }; diff --git a/os/+linux/fs.ha b/os/+linux/fs.ha @@ -1,3 +1,4 @@ +use errors; use fs; use path; use rt; @@ -26,14 +27,14 @@ export fn chdir(target: (*fs::fs | str)) (void | fs::error) = { assert(fs.open == &fs_open); let fs = fs: *os_filesystem; return match (rt::fchdir(fs.dirfd)) { - err: rt::errno => errno_to_io(err): fs::error, + err: rt::errno => errors::errno(err), void => void, }; }, s: str => s, }; return match (rt::chdir(path)) { - err: rt::errno => errno_to_io(err): fs::error, + err: rt::errno => errors::errno(err), void => void, }; }; @@ -44,7 +45,7 @@ export fn chdir(target: (*fs::fs | str)) (void | fs::error) = { // This function is not appropriate for sandboxing. export fn chroot(target: str) (void | fs::error) = { return match (rt::chroot(target)) { - err: rt::errno => errno_to_io(err): fs::error, + err: rt::errno => errors::errno(err), void => void, }; }; diff --git a/os/exec/cmd.ha b/os/exec/cmd.ha @@ -1,4 +1,5 @@ use ascii; +use errors; use os; use strings; @@ -20,7 +21,7 @@ export fn cmd(name: str, args: str...) (command | error) = { let cmd = command { platform: platform_cmd = if (strings::contains(name, '/')) match (open(name)) { - err: os_error => return nocmd, + err: errors::opaque => return nocmd, p: platform_cmd => p, } else match (lookup(name)) { void => return nocmd, @@ -61,7 +62,7 @@ export @noreturn fn exec(cmd: *command) void = { export fn start(cmd: *command) (error | process) = { defer finish(cmd); return match (platform_start(cmd)) { - err: os_error => err, + err: errors::opaque => err, proc: process => proc, }; }; @@ -112,7 +113,7 @@ fn lookup(name: str) (platform_cmd | void) = { let path = strings::concat(item, "/", name); defer free(path); match (open(path)) { - err: os_error => continue, + err: errors::opaque => continue, p: platform_cmd => return p, }; }; diff --git a/os/exec/exec+linux.ha b/os/exec/exec+linux.ha @@ -1,3 +1,4 @@ +use errors; use rt; use strings; use os; @@ -5,40 +6,28 @@ use os; // Forks the current process, returning the pid of the child (to the parent) and // void (to the child), or an error. export fn fork() (int | void | error) = match (rt::fork()) { - err: rt::errno => errno_to_os(err), + err: rt::errno => errors::errno(err), i: (int | void) => i, }; -fn errno_errstr(data: *void) str = { - const errno = data: uintptr: int: rt::errno; - return rt::errstr(errno); -}; - -fn errno_to_os(err: rt::errno) os_error = { - return os_error { - string = &errno_errstr, - data = err: uintptr: *void, - }; -}; - -fn open(path: str) (platform_cmd | os_error) = { +fn open(path: str) (platform_cmd | errors::opaque) = { match (rt::access(path, rt::X_OK)) { - err: rt::errno => errno_to_os(err), + err: rt::errno => errors::errno(err), b: bool => if (!b) { - return errno_to_os(rt::EACCES); + return errors::errno(rt::EACCES); }, }; // O_PATH is used because it allows us to use an executable for which we // have execute permissions, but not read permissions. return match (rt::open(path, rt::O_PATH, 0u)) { fd: int => fd, - err: rt::errno => errno_to_os(err), + err: rt::errno => errors::errno(err), }; }; fn platform_finish(cmd: *command) void = rt::close(cmd.platform); -fn platform_exec(cmd: *command) os_error = { +fn platform_exec(cmd: *command) errors::opaque = { // We don't worry about freeing the return values from strings::to_c // because once we exec(2) our heap is fried anyway let argv: []nullable *const char = alloc([], len(cmd.argv) + 1z); @@ -57,27 +46,27 @@ fn platform_exec(cmd: *command) os_error = { envp = env: *[*]nullable *const char; }; - return errno_to_os(rt::execveat(cmd.platform, strings::c_empty, + return errors::errno(rt::execveat(cmd.platform, strings::c_empty, argv: *[*]nullable *const char, envp, rt::AT_EMPTY_PATH)); }; -fn platform_start(cmd: *command) (os_error | process) = { +fn platform_start(cmd: *command) (errors::opaque | process) = { // TODO: Let the user configure clone more to their taste (e.g. SIGCHLD) let pipe: [2]int = [0...]; match (rt::pipe2(&pipe, rt::O_CLOEXEC)) { - err: rt::errno => return errno_to_os(err), + err: rt::errno => return errors::errno(err), void => void, }; match (rt::clone(null, 0, null, null, 0)) { - err: rt::errno => return errno_to_os(err), + err: rt::errno => return errors::errno(err), pid: int => { rt::close(pipe[1]); let errno: int = 0; return match (rt::read(pipe[0], &errno, size(int))) { - err: rt::errno => errno_to_os(err), + err: rt::errno => errors::errno(err), n: size => switch (n) { - size(int) => errno_to_os(errno), + size(int) => errors::errno(errno), * => abort("Unexpected rt::read result"), 0 => pid, }, @@ -86,8 +75,8 @@ fn platform_start(cmd: *command) (os_error | process) = { void => { rt::close(pipe[0]); let err = platform_exec(cmd); - let errno = err.data: uintptr: int; - rt::write(pipe[1], &errno, size(int)); + let err = &err.data: *rt::errno; + rt::write(pipe[1], &err, size(int)); rt::exit(1); }, }; diff --git a/os/exec/process+linux.ha b/os/exec/process+linux.ha @@ -1,3 +1,4 @@ +use errors; use rt; use fmt; // TODO: Add function to wait on all/any children @@ -17,7 +18,7 @@ export fn wait(proc: *process) (status | error) = { let ru: rt::rusage = rt::rusage { ... }; let st: status = status { ... }; match (rt::wait4(*proc, &st.status, 0, &ru)) { - err: rt::errno => errno_to_os(err), + err: rt::errno => errors::errno(err), pid: int => assert(pid == *proc), }; rusage(&st, &ru); @@ -30,7 +31,7 @@ export fn peek(proc: *process) (status | void | error) = { let ru: rt::rusage = rt::rusage { ... }; let st: status = status { ... }; match (rt::wait4(*proc, &st.status, 0, &ru)) { - err: rt::errno => errno_to_os(err), + err: rt::errno => errors::errno(err), pid: int => switch (pid) { 0 => return void, * => assert(pid == *proc), diff --git a/os/exec/types.ha b/os/exec/types.ha @@ -1,3 +1,5 @@ +use errors; + // An executable command. export type command = struct { platform: platform_cmd, @@ -8,20 +10,14 @@ export type command = struct { // Returned when path resolution fails to find a command by its name. export type nocmd = void!; -// An error provided by the operating system. -export type os_error = struct { - string: *fn(data: *void) str, - data: *void, -}!; - // All errors that can be returned from os::exec. -export type error = (nocmd | os_error)!; +export type error = (nocmd | errors::opaque)!; // Returns a human-readable message for the given error. -export fn errstr(err: error) const str = { +export fn strerror(err: error) const str = { return match (err) { - err: os_error => err.string(err.data), - nocmd => "Command not found", + nocmd => "Command not found", + err: errors::opaque => errors::strerror(err), }; }; diff --git a/rt/+linux/errno.ha b/rt/+linux/errno.ha @@ -15,7 +15,7 @@ fn wrap_return(r: u64) (errno | u64) = { // Obtains a human-friendly reading of an [errno] (e.g. "Operation not // permitted"). -export fn errstr(err: errno) str = { +export fn strerror(err: errno) str = { return switch (err: int) { EPERM => "Operation not permitted", ENOENT => "No such file or directory", diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib @@ -111,7 +111,7 @@ bufio() { dynamic.ha \ fixed.ha \ scanner.ha - gen_ssa bufio io bytes strings encoding::utf8 + gen_ssa bufio io bytes strings encoding::utf8 errors } bytes() { @@ -223,6 +223,16 @@ endian() { gen_ssa endian } +errors() { + gen_srcs errors \ + common.ha \ + docs.ha \ + opaque.ha \ + string.ha \ + rt.ha + gen_ssa errors +} + fmt() { gen_srcs fmt \ fmt.ha @@ -240,7 +250,7 @@ fs() { types.ha \ fs.ha \ util.ha - gen_ssa fs io strings path time + gen_ssa fs io strings path time errors } getopt() { @@ -299,7 +309,7 @@ hare_module() { gen_ssa hare::module \ hare::ast hare::lex hare::parse hare::unparse strio fs io strings hash \ crypto::sha256 dirs bytes encoding::utf8 ascii fmt time slice bufio \ - strconv os encoding::hex sort + strconv os encoding::hex sort errors } gensrcs_hare_parse() { @@ -368,7 +378,7 @@ io() { +test/limit.ha \ +test/stream.ha fi - gen_ssa io strings + gen_ssa io strings errors } linux() { @@ -390,7 +400,7 @@ net() { '$(PLATFORM)/socket.ha' \ '$(PLATFORM)/util.ha' \ socket.ha - gen_ssa net io os strings net::ip net::unix + gen_ssa net io os strings net::ip net::unix errors } gensrcs_net_ip() { @@ -428,7 +438,6 @@ math_random() { os() { gen_srcs os \ '$(PLATFORM)/environ.ha' \ - '$(PLATFORM)/errors.ha' \ '$(PLATFORM)/exit.ha' \ '$(PLATFORM)/dirfdfs.ha' \ '$(PLATFORM)/fdstream.ha' \ @@ -437,7 +446,7 @@ os() { '$(PLATFORM)/fs.ha' \ stdfd.ha \ fs.ha - gen_ssa os io strings types fs encoding::utf8 bytes bufio + gen_ssa os io strings types fs encoding::utf8 bytes bufio errors } os_exec() { @@ -447,7 +456,7 @@ os_exec() { 'process$(PLATFORM).ha' \ types.ha \ cmd.ha - gen_ssa os::exec os strings fmt bytes path + gen_ssa os::exec os strings fmt bytes path errors } path() { @@ -529,7 +538,7 @@ strio() { dynamic.ha \ fixed.ha \ ops.ha - gen_ssa strio io strings encoding::utf8 + gen_ssa strio io strings encoding::utf8 errors } time() { @@ -588,6 +597,7 @@ dirs encoding_hex encoding_utf8 endian +errors fmt format_elf fs diff --git a/stdlib.mk b/stdlib.mk @@ -108,6 +108,9 @@ hare_stdlib_deps+=$(stdlib_encoding_utf8) stdlib_endian=$(HARECACHE)/endian/endian.o hare_stdlib_deps+=$(stdlib_endian) +stdlib_errors=$(HARECACHE)/errors/errors.o +hare_stdlib_deps+=$(stdlib_errors) + stdlib_fmt=$(HARECACHE)/fmt/fmt.o hare_stdlib_deps+=$(stdlib_fmt) @@ -225,7 +228,7 @@ stdlib_bufio_srcs= \ $(STDLIB)/bufio/fixed.ha \ $(STDLIB)/bufio/scanner.ha -$(HARECACHE)/bufio/bufio.ssa: $(stdlib_bufio_srcs) $(stdlib_rt) $(stdlib_io) $(stdlib_bytes) $(stdlib_strings) $(stdlib_encoding_utf8) +$(HARECACHE)/bufio/bufio.ssa: $(stdlib_bufio_srcs) $(stdlib_rt) $(stdlib_io) $(stdlib_bytes) $(stdlib_strings) $(stdlib_encoding_utf8) $(stdlib_errors) @printf 'HAREC \t$@\n' @mkdir -p $(HARECACHE)/bufio @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nbufio \ @@ -353,6 +356,20 @@ $(HARECACHE)/endian/endian.ssa: $(stdlib_endian_srcs) $(stdlib_rt) @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nendian \ -t$(HARECACHE)/endian/endian.td $(stdlib_endian_srcs) +# errors +stdlib_errors_srcs= \ + $(STDLIB)/errors/common.ha \ + $(STDLIB)/errors/docs.ha \ + $(STDLIB)/errors/opaque.ha \ + $(STDLIB)/errors/string.ha \ + $(STDLIB)/errors/rt.ha + +$(HARECACHE)/errors/errors.ssa: $(stdlib_errors_srcs) $(stdlib_rt) + @printf 'HAREC \t$@\n' + @mkdir -p $(HARECACHE)/errors + @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nerrors \ + -t$(HARECACHE)/errors/errors.td $(stdlib_errors_srcs) + # fmt stdlib_fmt_srcs= \ $(STDLIB)/fmt/fmt.ha @@ -379,7 +396,7 @@ stdlib_fs_srcs= \ $(STDLIB)/fs/fs.ha \ $(STDLIB)/fs/util.ha -$(HARECACHE)/fs/fs.ssa: $(stdlib_fs_srcs) $(stdlib_rt) $(stdlib_io) $(stdlib_strings) $(stdlib_path) $(stdlib_time) +$(HARECACHE)/fs/fs.ssa: $(stdlib_fs_srcs) $(stdlib_rt) $(stdlib_io) $(stdlib_strings) $(stdlib_path) $(stdlib_time) $(stdlib_errors) @printf 'HAREC \t$@\n' @mkdir -p $(HARECACHE)/fs @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nfs \ @@ -428,7 +445,7 @@ stdlib_hare_module_srcs= \ $(STDLIB)/hare/module/scan.ha \ $(STDLIB)/hare/module/manifest.ha -$(HARECACHE)/hare/module/hare_module.ssa: $(stdlib_hare_module_srcs) $(stdlib_rt) $(stdlib_hare_ast) $(stdlib_hare_lex) $(stdlib_hare_parse) $(stdlib_hare_unparse) $(stdlib_strio) $(stdlib_fs) $(stdlib_io) $(stdlib_strings) $(stdlib_hash) $(stdlib_crypto_sha256) $(stdlib_dirs) $(stdlib_bytes) $(stdlib_encoding_utf8) $(stdlib_ascii) $(stdlib_fmt) $(stdlib_time) $(stdlib_slice) $(stdlib_bufio) $(stdlib_strconv) $(stdlib_os) $(stdlib_encoding_hex) $(stdlib_sort) +$(HARECACHE)/hare/module/hare_module.ssa: $(stdlib_hare_module_srcs) $(stdlib_rt) $(stdlib_hare_ast) $(stdlib_hare_lex) $(stdlib_hare_parse) $(stdlib_hare_unparse) $(stdlib_strio) $(stdlib_fs) $(stdlib_io) $(stdlib_strings) $(stdlib_hash) $(stdlib_crypto_sha256) $(stdlib_dirs) $(stdlib_bytes) $(stdlib_encoding_utf8) $(stdlib_ascii) $(stdlib_fmt) $(stdlib_time) $(stdlib_slice) $(stdlib_bufio) $(stdlib_strconv) $(stdlib_os) $(stdlib_encoding_hex) $(stdlib_sort) $(stdlib_errors) @printf 'HAREC \t$@\n' @mkdir -p $(HARECACHE)/hare/module @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nhare::module \ @@ -507,7 +524,7 @@ stdlib_io_srcs= \ $(STDLIB)/io/tee.ha \ $(STDLIB)/io/types.ha -$(HARECACHE)/io/io.ssa: $(stdlib_io_srcs) $(stdlib_rt) $(stdlib_strings) +$(HARECACHE)/io/io.ssa: $(stdlib_io_srcs) $(stdlib_rt) $(stdlib_strings) $(stdlib_errors) @printf 'HAREC \t$@\n' @mkdir -p $(HARECACHE)/io @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nio \ @@ -541,7 +558,7 @@ stdlib_net_srcs= \ $(STDLIB)/net/$(PLATFORM)/util.ha \ $(STDLIB)/net/socket.ha -$(HARECACHE)/net/net.ssa: $(stdlib_net_srcs) $(stdlib_rt) $(stdlib_io) $(stdlib_os) $(stdlib_strings) $(stdlib_net_ip) $(stdlib_net_unix) +$(HARECACHE)/net/net.ssa: $(stdlib_net_srcs) $(stdlib_rt) $(stdlib_io) $(stdlib_os) $(stdlib_strings) $(stdlib_net_ip) $(stdlib_net_unix) $(stdlib_errors) @printf 'HAREC \t$@\n' @mkdir -p $(HARECACHE)/net @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nnet \ @@ -583,7 +600,6 @@ $(HARECACHE)/math/random/math_random.ssa: $(stdlib_math_random_srcs) $(stdlib_rt # os stdlib_os_srcs= \ $(STDLIB)/os/$(PLATFORM)/environ.ha \ - $(STDLIB)/os/$(PLATFORM)/errors.ha \ $(STDLIB)/os/$(PLATFORM)/exit.ha \ $(STDLIB)/os/$(PLATFORM)/dirfdfs.ha \ $(STDLIB)/os/$(PLATFORM)/fdstream.ha \ @@ -593,7 +609,7 @@ stdlib_os_srcs= \ $(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) +$(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) @printf 'HAREC \t$@\n' @mkdir -p $(HARECACHE)/os @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nos \ @@ -607,7 +623,7 @@ stdlib_os_exec_srcs= \ $(STDLIB)/os/exec/types.ha \ $(STDLIB)/os/exec/cmd.ha -$(HARECACHE)/os/exec/os_exec.ssa: $(stdlib_os_exec_srcs) $(stdlib_rt) $(stdlib_os) $(stdlib_strings) $(stdlib_fmt) $(stdlib_bytes) $(stdlib_path) +$(HARECACHE)/os/exec/os_exec.ssa: $(stdlib_os_exec_srcs) $(stdlib_rt) $(stdlib_os) $(stdlib_strings) $(stdlib_fmt) $(stdlib_bytes) $(stdlib_path) $(stdlib_errors) @printf 'HAREC \t$@\n' @mkdir -p $(HARECACHE)/os/exec @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nos::exec \ @@ -690,7 +706,7 @@ stdlib_strio_srcs= \ $(STDLIB)/strio/fixed.ha \ $(STDLIB)/strio/ops.ha -$(HARECACHE)/strio/strio.ssa: $(stdlib_strio_srcs) $(stdlib_rt) $(stdlib_io) $(stdlib_strings) $(stdlib_encoding_utf8) +$(HARECACHE)/strio/strio.ssa: $(stdlib_strio_srcs) $(stdlib_rt) $(stdlib_io) $(stdlib_strings) $(stdlib_encoding_utf8) $(stdlib_errors) @printf 'HAREC \t$@\n' @mkdir -p $(HARECACHE)/strio @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nstrio \ @@ -872,6 +888,9 @@ hare_testlib_deps+=$(testlib_encoding_utf8) testlib_endian=$(TESTCACHE)/endian/endian.o hare_testlib_deps+=$(testlib_endian) +testlib_errors=$(TESTCACHE)/errors/errors.o +hare_testlib_deps+=$(testlib_errors) + testlib_fmt=$(TESTCACHE)/fmt/fmt.o hare_testlib_deps+=$(testlib_fmt) @@ -989,7 +1008,7 @@ testlib_bufio_srcs= \ $(STDLIB)/bufio/fixed.ha \ $(STDLIB)/bufio/scanner.ha -$(TESTCACHE)/bufio/bufio.ssa: $(testlib_bufio_srcs) $(testlib_rt) $(testlib_io) $(testlib_bytes) $(testlib_strings) $(testlib_encoding_utf8) +$(TESTCACHE)/bufio/bufio.ssa: $(testlib_bufio_srcs) $(testlib_rt) $(testlib_io) $(testlib_bytes) $(testlib_strings) $(testlib_encoding_utf8) $(testlib_errors) @printf 'HAREC \t$@\n' @mkdir -p $(TESTCACHE)/bufio @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nbufio \ @@ -1121,6 +1140,20 @@ $(TESTCACHE)/endian/endian.ssa: $(testlib_endian_srcs) $(testlib_rt) @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nendian \ -t$(TESTCACHE)/endian/endian.td $(testlib_endian_srcs) +# errors +testlib_errors_srcs= \ + $(STDLIB)/errors/common.ha \ + $(STDLIB)/errors/docs.ha \ + $(STDLIB)/errors/opaque.ha \ + $(STDLIB)/errors/string.ha \ + $(STDLIB)/errors/rt.ha + +$(TESTCACHE)/errors/errors.ssa: $(testlib_errors_srcs) $(testlib_rt) + @printf 'HAREC \t$@\n' + @mkdir -p $(TESTCACHE)/errors + @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nerrors \ + -t$(TESTCACHE)/errors/errors.td $(testlib_errors_srcs) + # fmt testlib_fmt_srcs= \ $(STDLIB)/fmt/fmt.ha @@ -1147,7 +1180,7 @@ testlib_fs_srcs= \ $(STDLIB)/fs/fs.ha \ $(STDLIB)/fs/util.ha -$(TESTCACHE)/fs/fs.ssa: $(testlib_fs_srcs) $(testlib_rt) $(testlib_io) $(testlib_strings) $(testlib_path) $(testlib_time) +$(TESTCACHE)/fs/fs.ssa: $(testlib_fs_srcs) $(testlib_rt) $(testlib_io) $(testlib_strings) $(testlib_path) $(testlib_time) $(testlib_errors) @printf 'HAREC \t$@\n' @mkdir -p $(TESTCACHE)/fs @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nfs \ @@ -1197,7 +1230,7 @@ testlib_hare_module_srcs= \ $(STDLIB)/hare/module/scan.ha \ $(STDLIB)/hare/module/manifest.ha -$(TESTCACHE)/hare/module/hare_module.ssa: $(testlib_hare_module_srcs) $(testlib_rt) $(testlib_hare_ast) $(testlib_hare_lex) $(testlib_hare_parse) $(testlib_hare_unparse) $(testlib_strio) $(testlib_fs) $(testlib_io) $(testlib_strings) $(testlib_hash) $(testlib_crypto_sha256) $(testlib_dirs) $(testlib_bytes) $(testlib_encoding_utf8) $(testlib_ascii) $(testlib_fmt) $(testlib_time) $(testlib_slice) $(testlib_bufio) $(testlib_strconv) $(testlib_os) $(testlib_encoding_hex) $(testlib_sort) +$(TESTCACHE)/hare/module/hare_module.ssa: $(testlib_hare_module_srcs) $(testlib_rt) $(testlib_hare_ast) $(testlib_hare_lex) $(testlib_hare_parse) $(testlib_hare_unparse) $(testlib_strio) $(testlib_fs) $(testlib_io) $(testlib_strings) $(testlib_hash) $(testlib_crypto_sha256) $(testlib_dirs) $(testlib_bytes) $(testlib_encoding_utf8) $(testlib_ascii) $(testlib_fmt) $(testlib_time) $(testlib_slice) $(testlib_bufio) $(testlib_strconv) $(testlib_os) $(testlib_encoding_hex) $(testlib_sort) $(testlib_errors) @printf 'HAREC \t$@\n' @mkdir -p $(TESTCACHE)/hare/module @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nhare::module \ @@ -1280,7 +1313,7 @@ testlib_io_srcs= \ $(STDLIB)/io/+test/limit.ha \ $(STDLIB)/io/+test/stream.ha -$(TESTCACHE)/io/io.ssa: $(testlib_io_srcs) $(testlib_rt) $(testlib_strings) +$(TESTCACHE)/io/io.ssa: $(testlib_io_srcs) $(testlib_rt) $(testlib_strings) $(testlib_errors) @printf 'HAREC \t$@\n' @mkdir -p $(TESTCACHE)/io @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nio \ @@ -1314,7 +1347,7 @@ testlib_net_srcs= \ $(STDLIB)/net/$(PLATFORM)/util.ha \ $(STDLIB)/net/socket.ha -$(TESTCACHE)/net/net.ssa: $(testlib_net_srcs) $(testlib_rt) $(testlib_io) $(testlib_os) $(testlib_strings) $(testlib_net_ip) $(testlib_net_unix) +$(TESTCACHE)/net/net.ssa: $(testlib_net_srcs) $(testlib_rt) $(testlib_io) $(testlib_os) $(testlib_strings) $(testlib_net_ip) $(testlib_net_unix) $(testlib_errors) @printf 'HAREC \t$@\n' @mkdir -p $(TESTCACHE)/net @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nnet \ @@ -1357,7 +1390,6 @@ $(TESTCACHE)/math/random/math_random.ssa: $(testlib_math_random_srcs) $(testlib_ # os testlib_os_srcs= \ $(STDLIB)/os/$(PLATFORM)/environ.ha \ - $(STDLIB)/os/$(PLATFORM)/errors.ha \ $(STDLIB)/os/$(PLATFORM)/exit.ha \ $(STDLIB)/os/$(PLATFORM)/dirfdfs.ha \ $(STDLIB)/os/$(PLATFORM)/fdstream.ha \ @@ -1367,7 +1399,7 @@ testlib_os_srcs= \ $(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) +$(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) @printf 'HAREC \t$@\n' @mkdir -p $(TESTCACHE)/os @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nos \ @@ -1381,7 +1413,7 @@ testlib_os_exec_srcs= \ $(STDLIB)/os/exec/types.ha \ $(STDLIB)/os/exec/cmd.ha -$(TESTCACHE)/os/exec/os_exec.ssa: $(testlib_os_exec_srcs) $(testlib_rt) $(testlib_os) $(testlib_strings) $(testlib_fmt) $(testlib_bytes) $(testlib_path) +$(TESTCACHE)/os/exec/os_exec.ssa: $(testlib_os_exec_srcs) $(testlib_rt) $(testlib_os) $(testlib_strings) $(testlib_fmt) $(testlib_bytes) $(testlib_path) $(testlib_errors) @printf 'HAREC \t$@\n' @mkdir -p $(TESTCACHE)/os/exec @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nos::exec \ @@ -1467,7 +1499,7 @@ testlib_strio_srcs= \ $(STDLIB)/strio/fixed.ha \ $(STDLIB)/strio/ops.ha -$(TESTCACHE)/strio/strio.ssa: $(testlib_strio_srcs) $(testlib_rt) $(testlib_io) $(testlib_strings) $(testlib_encoding_utf8) +$(TESTCACHE)/strio/strio.ssa: $(testlib_strio_srcs) $(testlib_rt) $(testlib_io) $(testlib_strings) $(testlib_encoding_utf8) $(testlib_errors) @printf 'HAREC \t$@\n' @mkdir -p $(TESTCACHE)/strio @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nstrio \ diff --git a/strio/dynamic.ha b/strio/dynamic.ha @@ -1,3 +1,4 @@ +use errors; use io; use strings; @@ -38,9 +39,9 @@ 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 | io::unsupported) = { +export fn reset(s: *io::stream) (void | errors::unsupported) = { if (s.writer != &dynamic_write || s.closer != &dynamic_close) { - return io::unsupported; + return errors::unsupported; }; const s = s: *dynamic_stream; s.buf = s.buf[..0]; @@ -48,9 +49,9 @@ export fn reset(s: *io::stream) (void | io::unsupported) = { // Truncates the buffer, freeing memory associated with it and setting its // length to zero. -export fn truncate(s: *io::stream) (void | io::unsupported) = { +export fn truncate(s: *io::stream) (void | errors::unsupported) = { if (s.writer != &dynamic_write || s.closer != &dynamic_close) { - return io::unsupported; + return errors::unsupported; }; let s = s: *dynamic_stream; delete(s.buf[..]);