~turminal/hare

eabd5cd08462c5731fa5156d8e58c664ff671df2 — Alexey Yerin 6 months ago 10f02df
all: Use optional parameters for flags

Signed-off-by: Alexey Yerin <yyp@disroot.org>
M fnmatch/+test.ha => fnmatch/+test.ha +5 -1
@@ 186,7 186,11 @@ const testcases: [_]testcase = [

@test fn fnmatch() void = {
	for (let tc .. testcases) {
		assert(fnmatch(tc.0, tc.1, tc.3...) == tc.2);
		let flags = flag::NONE;
		for (let fl .. tc.3) {
			flags |= fl;
		};
		assert(fnmatch(tc.0, tc.1, flags) == tc.2);
	};

};

M fnmatch/fnmatch.ha => fnmatch/fnmatch.ha +4 -8
@@ 43,15 43,11 @@ type token = (rune | bracket | star | question | end);
//
// A set of flags that alter the matching behavior may be passed to
// [[fnmatch]]. For an explanation of their meaning, see [[flag]].
export fn fnmatch(pattern: str, string: str, flags: flag...) bool = {
	let fl: flag = 0;
	for (let flag .. flags) {
		fl |= flag;
	};
	let b = if (fl & flag::PATHNAME != 0) {
		yield fnmatch_pathname(pattern, string, fl);
export fn fnmatch(pattern: str, string: str, flags: flag = flag::NONE) bool = {
	let b = if (flags & flag::PATHNAME != 0) {
		yield fnmatch_pathname(pattern, string, flags);
	} else {
		yield fnmatch_internal(pattern, string, fl);
		yield fnmatch_internal(pattern, string, flags);
	};
	return b is bool && b: bool;
};

M fs/fs.ha => fs/fs.ha +16 -18
@@ 17,16 17,18 @@ export fn close(fs: *fs) void = {

// Opens a file.
//
// If no flags are provided, [[flag::RDONLY]] is used when opening the file.
//
// [[flag::CREATE]] isn't very useful with this function, since the new file's
// mode is set to zero. For this use-case, use [[create]] instead.
export fn open(fs: *fs, path: str, flags: flag...) (io::handle | error) = {
export fn open(
	fs: *fs,
	path: str,
	flags: flag = flag::RDONLY,
) (io::handle | error) = {
	match (fs.open) {
	case null =>
		return errors::unsupported;
	case let f: *openfunc =>
		return f(fs, path, flags...);
		return f(fs, path, flags);
	};
};



@@ 34,35 36,34 @@ export fn open(fs: *fs, path: str, flags: flag...) (io::handle | error) = {
// handle on the host operating system, which may not be possible with all
// filesystem implementations (such cases will return [[io::unsupported]]).
//
// If no flags are provided, [[flag::RDONLY]] is used when opening the file.
//
// [[flag::CREATE]] isn't very useful with this function, since the new file's
// mode is set to zero. For this use-case, use [[create_file]] instead.
export fn open_file(fs: *fs, path: str, flags: flag...) (io::file | error) = {
export fn open_file(
	fs: *fs,
	path: str,
	flags: flag = flag::RDONLY,
) (io::file | error) = {
	match (fs.openfile) {
	case null =>
		return errors::unsupported;
	case let f: *openfilefunc =>
		return f(fs, path, flags...);
		return f(fs, path, flags);
	};
};

// Creates a new file with the given mode if it doesn't already exist, and opens
// it for writing.
//
// If no flags are provided, [[flag::WRONLY]] and [[flag::TRUNC]] are used when
// opening the file.
export fn create(
	fs: *fs,
	path: str,
	mode: mode,
	flags: flag...
	flags: flag = flag::WRONLY | flag::TRUNC,
) (io::handle | error) = {
	match (fs.create) {
	case null =>
		return errors::unsupported;
	case let f: *createfunc =>
		return f(fs, path, mode, flags...);
		return f(fs, path, mode, flags);
	};
};



@@ 70,20 71,17 @@ export fn create(
// it as an [[io::file]] for writing. This file will be backed by an open file
// handle on the host operating system, which may not be possible with all
// filesystem implementations (such cases will return [[io::unsupported]]).
//
// If no flags are provided, [[flag::WRONLY]] and [[flag::TRUNC]] are used when
// opening the file.
export fn create_file(
	fs: *fs,
	path: str,
	mode: mode,
	flags: flag...
	flags: flag = flag::WRONLY | flag::TRUNC,
) (io::file | error) = {
	match (fs.createfile) {
	case null =>
		return errors::unsupported;
	case let f: *createfilefunc =>
		return f(fs, path, mode, flags...);
		return f(fs, path, mode, flags);
	};
};


M fs/types.ha => fs/types.ha +4 -4
@@ 222,27 222,27 @@ export type symlinkfunc = fn(fs: *fs, target: str, path: str) (void | error);
export type openfunc = fn(
	fs: *fs,
	path: str,
	flags: flag...
	flags: flag,
) (io::handle | error);

export type openfilefunc = fn(
	fs: *fs,
	path: str,
	flags: flag...
	flags: flag,
) (io::file | error);

export type createfunc = fn(
	fs: *fs,
	path: str,
	mode: mode,
	flags: flag...
	flags: flag,
) (io::handle | error);

export type createfilefunc = fn(
	fs: *fs,
	path: str,
	mode: mode,
	flags: flag...
	flags: flag,
) (io::file | error);

// An abstract implementation of a filesystem, which provides common filesystem

M glob/glob.ha => glob/glob.ha +2 -6
@@ 64,17 64,13 @@ export fn strerror(err: failure) str = {

// Returns a generator of pathnames matching a pattern. The result must be
// freed using [[finish]].
export fn glob(pattern: str, flags: flag...) generator = {
export fn glob(pattern: str, flags: flag = flag::NONE) generator = {
	let ss = strstack_init();
	memio::concat(strstack_push(&ss), pattern)!;
	let bs = flag::NONE;
	for (let flag .. flags) {
		bs |= flag;
	};
	return generator {
		pats = ss,
		matc = 0,
		flgs = bs,
		flgs = flags,
		tmpp = pattern_init(),
	};
};

M hare/lex/+test.ha => hare/lex/+test.ha +4 -4
@@ 7,7 7,7 @@ use io;
use memio;
use strings;

fn initbuf(in: []u8, flags: flag...) lexer = {
fn initbuf(in: []u8, flags: flag = flag::NONE) lexer = {
	static let buf: [256]u8 = [0...];
	static let s = memio::stream {
		stream = null: io::stream,


@@ 21,11 21,11 @@ fn initbuf(in: []u8, flags: flag...) lexer = {

	s = memio::fixed(in);
	sc = bufio::newscanner_static(&s, buf);
	return init(&sc, "<test>", flags...);
	return init(&sc, "<test>", flags);
};

fn initstr(in: str, flags: flag...) lexer = {
	return initbuf(strings::toutf8(in), flags...);
fn initstr(in: str, flags: flag = flag::NONE) lexer = {
	return initbuf(strings::toutf8(in), flags);
};

@test fn unlex() void = {

M hare/lex/lex.ha => hare/lex/lex.ha +6 -6
@@ 55,11 55,11 @@ export fn strerror(err: error) const str = {

// Initializes a new lexer for the given [[bufio::scanner]]. The path is
// borrowed.
export fn init(in: *bufio::scanner, path: str, flags: flag...) lexer = {
	let f = flag::NONE;
	for (let i = 0z; i < len(flags); i += 1) {
		f |= flags[i];
	};
export fn init(
	in: *bufio::scanner,
	path: str,
	flags: flag = flag::NONE,
) lexer = {
	const loc = location { path = path, line = 1, col = 1 };
	return lexer {
		in = in,


@@ 68,7 68,7 @@ export fn init(in: *bufio::scanner, path: str, flags: flag...) lexer = {
		prevrloc = (1, 1),
		un = (ltok::EOF, void, loc),
		prevunlocs = [((1, 1), (1, 1))...],
		flags = f,
		flags = flags,
		...
	};
};

M net/+freebsd.ha => net/+freebsd.ha +3 -8
@@ 19,14 19,9 @@ export type sockflag = enum int {
// Accepts the next connection from a socket. Blocks until a new connection is
// available. Optionally accepts NOCLOEXEC and NONBLOCK flags. If flags are
// supplied, the [[io::file]] returned will have the supplied flags set.
export fn accept(sock: socket, flags: sockflag...) (socket | error) = {
	// Apply any supplied flags
	let f = 0i;
	for (let i = 0z; i < len(flags); i += 1) {
		f |= flags[i];
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const fd = match (rt::accept4(sock, null, null, f)) {
export fn accept(sock: socket, flags: sockflag = 0) (socket | error) = {
	flags ^= rt::SOCK_CLOEXEC: sockflag; // invert CLOEXEC
	const fd = match (rt::accept4(sock, null, null, flags)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>

M net/+linux.ha => net/+linux.ha +3 -8
@@ 19,14 19,9 @@ export type sockflag = enum int {
// Accepts the next connection from a socket. Blocks until a new connection is
// available. Optionally accepts NOCLOEXEC and NONBLOCK flags. If flags are
// supplied, the [[io::file]] returned will have the supplied flags set.
export fn accept(sock: socket, flags: sockflag...) (socket | error) = {
	// Apply any supplied flags
	let f = 0i;
	for (let i = 0z; i < len(flags); i += 1) {
		f |= flags[i];
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const fd = match (rt::accept4(sock, null, null, f: int)) {
export fn accept(sock: socket, flags: sockflag = 0) (socket | error) = {
	flags ^= rt::SOCK_CLOEXEC: sockflag; // invert CLOEXEC
	const fd = match (rt::accept4(sock, null, null, flags)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>

M net/+openbsd.ha => net/+openbsd.ha +3 -8
@@ 19,14 19,9 @@ export type sockflag = enum int {
// Accepts the next connection from a socket. Blocks until a new connection is
// available. Optionally accepts NOCLOEXEC and NONBLOCK flags. If flags are
// supplied, the [[io::file]] returned will have the supplied flags set.
export fn accept(sock: socket, flags: sockflag...) (socket | error) = {
	// Apply any supplied flags
	let f = 0i;
	for (let i = 0z; i < len(flags); i += 1) {
		f |= flags[i];
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const fd = match (rt::accept4(sock, null, null, f: int)) {
export fn accept(sock: socket, flags: sockflag = 0) (socket | error) = {
	flags ^= rt::SOCK_CLOEXEC: sockflag; // invert CLOEXEC
	const fd = match (rt::accept4(sock, null, null, flags)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>

M net/tcp/listener.ha => net/tcp/listener.ha +2 -2
@@ 7,5 7,5 @@ use net;
// available. This is a convenience wrapper around [[net::accept]].
export fn accept(
	sock: net::socket,
	flags: net::sockflag...
) (net::socket | net::error) = net::accept(sock, flags...);
	flags: net::sockflag,
) (net::socket | net::error) = net::accept(sock, flags);

M net/unix/listener.ha => net/unix/listener.ha +2 -2
@@ 7,5 7,5 @@ use net;
// available. This is a convenience wrapper around [[net::accept]].
export fn accept(
	sock: net::socket,
	flags: net::sockflag...
) (net::socket | net::error) = net::accept(sock, flags...);
	flags: net::sockflag,
) (net::socket | net::error) = net::accept(sock, flags);

M net/unix/socketpair.ha => net/unix/socketpair.ha +3 -8
@@ 8,15 8,10 @@ use rt;

// A thin wrapper around socketpair(2) that presumes [[rt::AF_UNIX]] for the
// domain and returns an unnamed pair of sockets of type [[rt::SOCK_STREAM]].
export fn socketpair(flags: net::sockflag...) ((net::socket, net::socket) | net::error) = {
export fn socketpair(flags: net::sockflag = 0) ((net::socket, net::socket) | net::error) = {
	let sv: [2]int = [0...];
	// Apply any supplied flags
	let f = 0i;
	for (let i = 0z; i < len(flags); i += 1) {
		f |= flags[i];
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	match (rt::socketpair(rt::AF_UNIX: int, (rt::SOCK_STREAM | f): int, 0, &sv)) {
	flags ^= rt::SOCK_CLOEXEC: net::sockflag; // invert CLOEXEC
	match (rt::socketpair(rt::AF_UNIX: int, rt::SOCK_STREAM | flags, 0, &sv)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case =>

M os/+freebsd/dirfdfs.ha => os/+freebsd/dirfdfs.ha +9 -20
@@ 165,45 165,34 @@ fn fsflags_to_bsd(flags: fs::flag) (int | errors::unsupported) = {
fn fs_open_file(
	fs: *fs::fs,
	path: str,
	flags: fs::flag...
	flags: fs::flag,
) (io::file | fs::error) = {
	let oflags = fs::flag::RDONLY;
	for (let flag .. flags) {
		oflags |= flag;
	};
	return _fs_open(fs, path, fsflags_to_bsd(oflags)?, 0);
	return _fs_open(fs, path, fsflags_to_bsd(flags)?, 0);
};

fn fs_open(
	fs: *fs::fs,
	path: str,
	flags: fs::flag...
) (io::handle | fs::error) = fs_open_file(fs, path, flags...)?;
	flags: fs::flag,
) (io::handle | fs::error) = fs_open_file(fs, path, flags)?;

fn fs_create_file(
	fs: *fs::fs,
	path: str,
	mode: fs::mode,
	flags: fs::flag...
	flags: fs::flag,
) (io::file | fs::error) = {
	let oflags: fs::flag = 0;
	if (len(flags) == 0z) {
		oflags |= fs::flag::WRONLY | fs::flag::TRUNC;
	};
	for (let flag .. flags) {
		oflags |= flag;
	};
	oflags |= fs::flag::CREATE;
	return _fs_open(fs, path, fsflags_to_bsd(oflags)?, mode)?;
	flags |= fs::flag::CREATE;
	return _fs_open(fs, path, fsflags_to_bsd(flags)?, mode)?;
};

fn fs_create(
	fs: *fs::fs,
	path: str,
	mode: fs::mode,
	flags: fs::flag...
	flags: fs::flag,
) (io::handle | fs::error) = {
	return fs_create_file(fs, path, mode, flags...)?;
	return fs_create_file(fs, path, mode, flags)?;
};

fn fs_remove(fs: *fs::fs, path: str) (void | fs::error) = {

M os/+linux/dirfdfs.ha => os/+linux/dirfdfs.ha +24 -35
@@ 49,13 49,12 @@ type os_filesystem = struct {

// Opens a file descriptor as an [[fs::fs]]. This file descriptor must be a
// directory file. The file will be closed when the fs is closed.
export fn dirfdopen(fd: io::file, resolve_flags: resolve_flag...) *fs::fs = {
	let ofs = alloc(os_filesystem { ... });
export fn dirfdopen(
	fd: io::file,
	resolve_flags: resolve_flag = resolve_flag::NORMAL,
) *fs::fs = {
	let ofs = alloc(os_filesystem { resolve = resolve_flags, ... });
	let fs = static_dirfdopen(fd, ofs);

	for (let flag .. resolve_flags) {
		ofs.resolve |= flag;
	};
	fs.close = &fs_close;
	return fs;
};


@@ 95,13 94,14 @@ fn static_dirfdopen(fd: io::file, filesystem: *os_filesystem) *fs::fs = {

// Clones a dirfd filesystem, optionally adding additional [[resolve_flag]]
// constraints.
export fn dirfs_clone(fs: *fs::fs, resolve_flags: resolve_flag...) *fs::fs = {
export fn dirfs_clone(
	fs: *fs::fs,
	resolve_flags: resolve_flag = resolve_flag::NORMAL,
) *fs::fs = {
	assert(fs.open == &fs_open);
	let fs = fs: *os_filesystem;
	let new = alloc(*fs);
	for (let flag .. resolve_flags) {
		fs.resolve |= flag;
	};
	fs.resolve |= resolve_flags;
	new.dirfd = rt::fcntl(new.dirfd, rt::F_DUPFD_CLOEXEC, 0) as int;
	return &new.fs;
};


@@ 167,22 167,18 @@ fn _fs_open(
fn fs_open_file(
	fs: *fs::fs,
	path: str,
	flags: fs::flag...
	flags: fs::flag,
) (io::file | fs::error) = {
	let oflags = fs::flag::RDONLY: int;
	for (let flag .. flags) {
		oflags |= flag: int;
	};
	oflags ^= fs::flag::CTTY | fs::flag::NOCLOEXEC; // invert NOCTTY/CLOEXEC
	flags ^= fs::flag::CTTY | fs::flag::NOCLOEXEC; // invert NOCTTY/CLOEXEC

	if ((oflags: fs::flag & fs::flag::DIRECTORY) == fs::flag::DIRECTORY) {
	if ((flags & fs::flag::DIRECTORY) == fs::flag::DIRECTORY) {
		// This is arch-specific
		oflags &= ~fs::flag::DIRECTORY: int;
		oflags |= rt::O_DIRECTORY: int;
		flags &= ~fs::flag::DIRECTORY;
		flags |= rt::O_DIRECTORY: fs::flag;
	};

	let oh = rt::open_how {
		flags = oflags: u64,
		flags = flags: u64,
		...
	};
	return _fs_open(fs, path, &oh);


@@ 191,27 187,20 @@ fn fs_open_file(
fn fs_open(
	fs: *fs::fs,
	path: str,
	flags: fs::flag...
) (io::handle | fs::error) = fs_open_file(fs, path, flags...)?;
	flags: fs::flag,
) (io::handle | fs::error) = fs_open_file(fs, path, flags)?;

fn fs_create_file(
	fs: *fs::fs,
	path: str,
	mode: fs::mode,
	flags: fs::flag...
	flags: fs::flag,
) (io::file | fs::error) = {
	let oflags = 0;
	if (len(flags) == 0) {
		oflags |= fs::flag::WRONLY | fs::flag::TRUNC;
	};
	for (let flag .. flags) {
		oflags |= flag: int;
	};
	oflags ^= fs::flag::CTTY | fs::flag::NOCLOEXEC; // invert NOCTTY/CLOEXEC
	oflags |= fs::flag::CREATE: int;
	flags ^= fs::flag::CTTY | fs::flag::NOCLOEXEC; // invert NOCTTY/CLOEXEC
	flags |= fs::flag::CREATE;

	let oh = rt::open_how {
		flags = oflags: u64,
		flags = flags: u64,
		mode = mode: u64,
		...
	};


@@ 222,9 211,9 @@ fn fs_create(
	fs: *fs::fs,
	path: str,
	mode: fs::mode,
	flags: fs::flag...
	flags: fs::flag,
) (io::handle | fs::error) = {
	return fs_create_file(fs, path, mode, flags...)?;
	return fs_create_file(fs, path, mode, flags)?;
};

fn fs_remove(fs: *fs::fs, path: str) (void | fs::error) = {

M os/+openbsd/dirfdfs.ha => os/+openbsd/dirfdfs.ha +9 -20
@@ 87,20 87,16 @@ fn _fs_open(
fn fs_open_file(
	fs: *fs::fs,
	path: str,
	flags: fs::flag...
	flags: fs::flag,
) (io::file | fs::error) = {
	let oflags = fs::flag::RDONLY;
	for (let flag .. flags) {
		oflags |= flag;
	};
	return _fs_open(fs, path, fsflags_to_bsd(oflags)?, 0);
	return _fs_open(fs, path, fsflags_to_bsd(flags)?, 0);
};

fn fs_open(
	fs: *fs::fs,
	path: str,
	flags: fs::flag...
) (io::handle | fs::error) = fs_open_file(fs, path, flags...)?;
	flags: fs::flag,
) (io::handle | fs::error) = fs_open_file(fs, path, flags)?;

fn fs_readlink(fs: *fs::fs, path: str) (str | fs::error) = {
	let fs = fs: *os_filesystem;


@@ 123,25 119,18 @@ fn fs_create_file(
	fs: *fs::fs,
	path: str,
	mode: fs::mode,
	flags: fs::flag...
	flags: fs::flag,
) (io::file | fs::error) = {
	let oflags: fs::flag = 0;
	if (len(flags) == 0z) {
		oflags |= fs::flag::WRONLY | fs::flag::TRUNC;
	};
	for (let flag .. flags) {
		oflags |= flag;
	};
	oflags |= fs::flag::CREATE;
	return _fs_open(fs, path, fsflags_to_bsd(oflags)?, mode)?;
	flags |= fs::flag::CREATE;
	return _fs_open(fs, path, fsflags_to_bsd(flags)?, mode)?;
};

fn fs_create(
	fs: *fs::fs,
	path: str,
	mode: fs::mode,
	flags: fs::flag...
) (io::handle | fs::error) = fs_create_file(fs, path, mode, flags...)?;
	flags: fs::flag,
) (io::handle | fs::error) = fs_create_file(fs, path, mode, flags)?;

fn fs_remove(fs: *fs::fs, path: str) (void | fs::error) = {
	let fs = fs: *os_filesystem;

M os/os.ha => os/os.ha +5 -9
@@ 107,20 107,16 @@ export fn symlink(target: str, path: str) (void | fs::error) =

// Opens a file.
//
// If no flags are provided, [[fs::flag::RDONLY]] is used when opening the
// file.
//
// [[fs::flag::CREATE]] isn't very useful with this function, since the new
// file's mode is set to zero. For this use-case, use [[create]] instead.
export fn open(path: str, flags: fs::flag...) (io::file | fs::error) =
	fs::open_file(cwd, path, flags...);
export fn open(
	path: str,
	flags: fs::flag = fs::flag::RDONLY,
) (io::file | fs::error) = fs::open_file(cwd, path, flags);

// Creates a new file with the given mode if it doesn't already exist and opens
// it for writing.
//
// If no flags are provided, [[fs::flag::WRONLY]] and [[fs::flag::TRUNC]] are
// used when opening the file.
//
// Only the permission bits of the mode are used. If other bits are set, they
// are discarded.
//


@@ 128,7 124,7 @@ export fn open(path: str, flags: fs::flag...) (io::file | fs::error) =
export fn create(
	path: str,
	mode: fs::mode,
	flags: fs::flag...
	flags: fs::flag = fs::flag::WRONLY | fs::flag::TRUNC,
) (io::file | fs::error) = fs::create_file(cwd, path, mode, flags...);

// Canonicalizes a path in this filesystem by resolving all symlinks and

M unix/+freebsd/pipe.ha => unix/+freebsd/pipe.ha +3 -7
@@ 15,14 15,10 @@ export type pipe_flag = enum {

// Create a pair of two linked [[io::file]]s, such that any data written to the
// second [[io::file]] may be read from the first.
export fn pipe(flags: pipe_flag...) ((io::file, io::file) | errors::error) = {
export fn pipe(flags: pipe_flag = 0) ((io::file, io::file) | errors::error) = {
	let fds: [2]int = [0...];
	let flag: pipe_flag = 0;
	for (let i = 0z; i < len(flags); i += 1) {
		flag |= flags[i];
	};
	flag ^= pipe_flag::NOCLOEXEC; // invert CLOEXEC
	match (rt::pipe2(&fds, flag)) {
	flags ^= pipe_flag::NOCLOEXEC; // invert CLOEXEC
	match (rt::pipe2(&fds, flags)) {
	case void => void;
	case let e: rt::errno =>
		return errors::errno(e);

M unix/+linux/pipe.ha => unix/+linux/pipe.ha +3 -7
@@ 15,14 15,10 @@ export type pipe_flag = enum {

// Create a pair of two linked [[io::file]]s, such that any data written to the
// second [[io::file]] may be read from the first.
export fn pipe(flags: pipe_flag...) ((io::file, io::file) | errors::error) = {
export fn pipe(flags: pipe_flag = 0) ((io::file, io::file) | errors::error) = {
	let fds: [2]int = [0...];
	let flag: pipe_flag = 0;
	for (let i = 0z; i < len(flags); i += 1) {
		flag |= flags[i];
	};
	flag ^= pipe_flag::NOCLOEXEC; // invert CLOEXEC
	match (rt::pipe2(&fds, flag)) {
	flags ^= pipe_flag::NOCLOEXEC; // invert CLOEXEC
	match (rt::pipe2(&fds, flags)) {
	case void => void;
	case let e: rt::errno =>
		return errors::errno(e);

M unix/+openbsd/pipe.ha => unix/+openbsd/pipe.ha +3 -7
@@ 14,14 14,10 @@ export type pipe_flag = enum {

// Create a pair of two linked [[io::file]]s, such that any data written to the
// second [[io::file]] may be read from the first.
export fn pipe(flags: pipe_flag...) ((io::file, io::file) | errors::error) = {
export fn pipe(flags: pipe_flag = 0) ((io::file, io::file) | errors::error) = {
	let fds: [2]int = [0...];
	let flag: pipe_flag = 0;
	for (let i = 0z; i < len(flags); i += 1) {
		flag |= flags[i];
	};
	flag ^= pipe_flag::NOCLOEXEC; // invert CLOEXEC
	match (rt::pipe2(&fds, flag)) {
	flags ^= pipe_flag::NOCLOEXEC; // invert CLOEXEC
	match (rt::pipe2(&fds, flags)) {
	case void => void;
	case let e: rt::errno =>
		return errors::errno(e);

M wordexp/+test.ha => wordexp/+test.ha +1 -1
@@ 32,7 32,7 @@ fn streq(a: []str, b: []str) bool = {

	for (let i = 0z; i < len(cases); i += 1) {
		const (in, out) = cases[i];
		const words = wordexp(in, flag::NONE)!;
		const words = wordexp(in)!;
		defer strings::freeall(words);
		assert(streq(words, out));
	};

M wordexp/wordexp.ha => wordexp/wordexp.ha +1 -1
@@ 26,7 26,7 @@ export type flag = enum uint {
//
// Pass the return value to [[strings::freeall]] to free resources associated
// with the return value.
export fn wordexp(s: str, flags: flag) ([]str | error) = {
export fn wordexp(s: str, flags: flag = flag::NONE) ([]str | error) = {
	const (rd, wr) = exec::pipe();

	// "x" is added to handle the list of expanded words being empty