commit 8cf001a76af06a43f7ead889952694429d720956
parent 5d450b1a647e0aeeb1e49e28e0d16af11358e12c
Author: Drew DeVault <sir@cmpwn.com>
Date: Mon, 5 Apr 2021 11:11:34 -0400
all: generalize error handling
Diffstat:
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[..]);