~sircmpwn/hare

4fedf29d249ec7366f18f1ffda3b52293624fd2c — Drew DeVault 7 months ago 70fdee7
all: fix error handling oversights

Following a change to harec and the specification to make error handling
mandatory, a lot of issues were found throughout the stdlib. This fixes
them.

Signed-off-by: Drew DeVault <sir@cmpwn.com>
M cmd/hare/plan.ha => cmd/hare/plan.ha +1 -1
@@ 63,7 63,7 @@ fn mkplan(ctx: *module::context) plan = {
};

fn plan_finish(plan: *plan) void = {
	os::rmdirall(plan.workdir);
	os::rmdirall(plan.workdir)!;
	free(plan.workdir);

	for (let i = 0z; i < len(plan.complete); i += 1) {

M cmd/hare/schedule.ha => cmd/hare/schedule.ha +6 -1
@@ 1,5 1,6 @@
use encoding::hex;
use fmt;
use fs;
use hare::ast;
use hare::module;
use hare::unparse;


@@ 185,7 186,11 @@ fn sched_hare_object(
		for (let i = 0z; i < len(namespace); i += 1) {
			path = path::join(path, namespace[i]);
		};
		os::mkdirs(path);
		match (os::mkdirs(path)) {
			_: void => void,
			err: fs::error => fmt::fatal("Error: mkdirs {}: {}",
				path, fs::strerror(err)),
		};
		append(harec.cmd, "-t", path::join(path, td));
		path::join(path, name);
	} else {

M cmd/hare/subcmds.ha => cmd/hare/subcmds.ha +8 -8
@@ 331,20 331,20 @@ fn test(args: []str) void = {
};

fn version(args: []str) void = {
	fmt::printfln("Hare version {}", VERSION);
	fmt::errorln();
	fmt::printf("Build tags\t");
	fmt::printfln("Hare version {}", VERSION)!;
	fmt::errorln()!;
	fmt::printf("Build tags\t")!;
	const tags = default_tags();
	for (let i = 0z; i < len(tags); i += 1) {
		const tag = tags[i];
		const inclusive = (tag.mode & module::tag_mode::INCLUSIVE) == module::tag_mode::INCLUSIVE;
		fmt::printf("{}{}", if (inclusive) '+' else '-', tag.name);
		fmt::printf("{}{}", if (inclusive) '+' else '-', tag.name)!;
	};
	fmt::println();
	fmt::println()!;

	match (os::getenv("HAREPATH")) {
		_: void => fmt::printfln("HAREPATH\t{}", HAREPATH),
		s: str => fmt::printfln("HAREPATH\t{}\t(from environment)", s),
		_: void => fmt::printfln("HAREPATH\t{}", HAREPATH)!,
		s: str => fmt::printfln("HAREPATH\t{}\t(from environment)", s)!,
	};
	if (len(args) > 1 && args[1] == "-v") {
		fmt::errorln("


@@ 381,6 381,6 @@ fn version(args: []str) void = {
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⣀⣈⣀⣒⣓⡒⡒⠒⠒⢒⡀⠀⠀⠀⠀⠴⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠁⠀⣀⠀⠀⠤⠀⠤⠤⠀⠀⠒⠒⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠁⠉⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠄⠀⠀⠀⠐⠒⠒⠒⠀⠀⠀⠀⠀⠀⠈⠉⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠁");
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠁")!;
	};
};

M cmd/harec/errors.ha => cmd/harec/errors.ha +4 -4
@@ 31,10 31,10 @@ fn printerr_syntax(err: lex::syntax) void = {
	defer free(line);
	let line = strings::fromutf8(line);
	fmt::errorfln("{}:{},{}: Syntax error: {}",
		location.path, location.line, location.col, details);
	fmt::errorln(line);
		location.path, location.line, location.col, details)!;
	fmt::errorln(line)!;
	for (let i = 0u; i < location.col - 2; i += 1) {
		fmt::error(" ");
		fmt::error(" ")!;
	};
	fmt::errorln("^--- here");
	fmt::errorln("^--- here")!;
};

M cmd/haredoc/docstr.ha => cmd/haredoc/docstr.ha +5 -5
@@ 75,7 75,7 @@ fn scantext(par: *parser) (token | void) = {
				break;
			},
			'\n' => {
				strio::appendrune(buf, rn);
				strio::appendrune(buf, rn)!;
				const rn = match (bufio::scanrune(par.src)) {
					_: io::EOF => break,
					* => abort(),


@@ 87,7 87,7 @@ fn scantext(par: *parser) (token | void) = {
				};
				bufio::unreadrune(par.src, rn);
			},
			* => strio::appendrune(buf, rn),
			* => strio::appendrune(buf, rn)!,
		};
	};
	let result = strio::finish(buf);


@@ 158,10 158,10 @@ fn scansample(par: *parser) (token | void) = {
		};
		switch (rn) {
			* => {
				strio::appendrune(buf, rn);
				strio::appendrune(buf, rn)!;
				continue;
			},
			'\n' => strio::appendrune(buf, rn),
			'\n' => strio::appendrune(buf, rn)!,
		};

		// Consume whitespace


@@ 173,7 173,7 @@ fn scansample(par: *parser) (token | void) = {
					' '  => i += 1,
					'\t' => i += 8,
					'\n' => {
						strio::appendrune(buf, rn);
						strio::appendrune(buf, rn)!;
						i = 0;
					},
					* => {

M cmd/haredoc/html.ha => cmd/haredoc/html.ha +36 -36
@@ 21,16 21,16 @@ fn emit_html(ctx: *context) (void | error) = {
	if (ctx.template) head(ctx.ident)?;

	if (len(ident) == 0) {
		fmt::printf("<h2>The Hare standard library <span class='heading-extra'>", ident);
		fmt::printf("<h2>The Hare standard library <span class='heading-extra'>", ident)?;
	} else {
		fmt::printf("<h2>{} <span class='heading-extra'>", ident);
		fmt::printf("<h2>{} <span class='heading-extra'>", ident)?;
	};
	for (let i = 0z; i < len(ctx.tags); i += 1) {
		const mode = switch (ctx.tags[i].mode) {
			module::tag_mode::INCLUSIVE => '+',
			module::tag_mode::EXCLUSIVE => '-',
		};
		fmt::printf("{}{} ", mode, ctx.tags[i].name);
		fmt::printf("{}{} ", mode, ctx.tags[i].name)?;
	};
	fmt::println("</span></h2>")?;



@@ 62,11 62,11 @@ fn emit_html(ctx: *context) (void | error) = {
			let path = path::join("/", path, dir);
			defer free(path);

			fmt::printf("<li><a href='");
			html::escape(os::stdout, path);
			fmt::printf("'>");
			html::escape(os::stdout, dir);
			fmt::printfln("</a></li>");
			fmt::printf("<li><a href='")?;
			html::escape(os::stdout, path)?;
			fmt::printf("'>")?;
			html::escape(os::stdout, dir)?;
			fmt::printfln("</a></li>")?;
		};
		fmt::println("</ul>")?;
	};


@@ 150,12 150,12 @@ fn tocentry(decl: ast::decl) (void | error) = {
			_: []ast::decl_type => "type",
			_: []ast::decl_const => "const",
			_: []ast::decl_global => "let",
		});
	fmt::printf("<a href='#");
		})?;
	fmt::printf("<a href='#")?;
	unparse::ident(os::stdout, decl_ident(decl))?;
	fmt::printf("'>");
	fmt::printf("'>")?;
	unparse::ident(os::stdout, decl_ident(decl))?;
	fmt::print("</a>");
	fmt::print("</a>")?;

	match (decl.decl) {
		g: []ast::decl_global => {


@@ 173,12 173,12 @@ fn tocentry(decl: ast::decl) (void | error) = {
			f.prototype._type as ast::func_type,
			true)?,
	};
	fmt::println(";");
	fmt::println(";")?;
	return;
};

fn details(ctx: *context, decl: ast::decl) (void | error) = {
	fmt::println("<section class='member'>");
	fmt::println("<section class='member'>")?;
	fmt::print("<h4 id='")?;
	unparse::ident(os::stdout, decl_ident(decl))?;
	fmt::print("'>")?;


@@ 188,7 188,7 @@ fn details(ctx: *context, decl: ast::decl) (void | error) = {
			_: []ast::decl_type => "type",
			_: []ast::decl_const => "def",
			_: []ast::decl_global => "let",
		});
		})?;
	unparse::ident(os::stdout, decl_ident(decl))?;
	// TODO: Add source URL
	fmt::print("<span class='heading-extra'>


@@ 199,8 199,8 @@ fn details(ctx: *context, decl: ast::decl) (void | error) = {
	fmt::println("</h4>")?;

	if (len(decl.docs) == 0) {
		fmt::println("<details>");
		fmt::println("<summary>Show undocumented member</summary>");
		fmt::println("<details>")?;
		fmt::println("<summary>Show undocumented member</summary>")?;
	};

	fmt::println("<pre class='decl'>")?;


@@ 211,7 211,7 @@ fn details(ctx: *context, decl: ast::decl) (void | error) = {
		const buf = strings::toutf8(decl.docs);
		markup_html(ctx, bufio::fixed(buf, io::mode::READ))?;
	} else {
		fmt::println("</details>");
		fmt::println("</details>")?;
	};

	fmt::println("</section>")?;


@@ 222,7 222,7 @@ fn htmlref(ctx: *context, ref: ast::ident) (void | io::error) = {
	const ik = match (resolve(ctx, ref)) {
		_: void => {
			const ident = unparse::identstr(ref);
			fmt::errorfln("Warning: Unresolved reference: {}", ident);
			fmt::errorfln("Warning: Unresolved reference: {}", ident)?;
			fmt::printf("<a href='#' "
				"class='ref invalid' "
				"title='This reference could not be found'>{}</a>",


@@ 267,31 267,31 @@ fn markup_html(ctx: *context, in: *io::stream) (void | io::error) = {
			},
			tx: text => if (strings::has_prefix(tx, "https://")) {
				// Temporary hack
				fmt::print("<a rel='nofollow noopener' href='");
				html::escape(os::stdout, tx);
				fmt::print("'>");
				html::escape(os::stdout, tx);
				fmt::print("</a>");
				fmt::print("<a rel='nofollow noopener' href='")?;
				html::escape(os::stdout, tx)?;
				fmt::print("'>")?;
				html::escape(os::stdout, tx)?;
				fmt::print("</a>")?;
				free(tx);
			} else {
				html::escape(os::stdout, tx);
				html::escape(os::stdout, tx)?;
				free(tx);
			},
			re: reference => htmlref(ctx, re)?,
			sa: sample => {
				fmt::print("<pre class='sample'>")?;
				html::escape(os::stdout, sa);
				html::escape(os::stdout, sa)?;
				fmt::print("</pre>")?;
				free(sa);
			},
			li: list => {
				fmt::println("<ul>");
				fmt::println("<ul>")?;
				for (let i = 0z; i < len(li); i += 1) {
					fmt::println("<li>");
					html::escape(os::stdout, li[i]);
					fmt::println("</li>");
					fmt::println("<li>")?;
					html::escape(os::stdout, li[i])?;
					fmt::println("</li>")?;
				};
				fmt::println("</ul>");
				fmt::println("</ul>")?;
			},
		};
	};


@@ 618,13 618,13 @@ fn breadcrumb(ident: ast::ident) str = {
		return "";
	};
	let buf = strio::dynamic();
	fmt::fprintf(buf, "<a href='/'>stdlib</a> » ");
	fmt::fprintf(buf, "<a href='/'>stdlib</a> » ")!;
	for (let i = 0z; i < len(ident) - 1; i += 1) {
		let ipath = module::identpath(ident[..i+1]);
		defer free(ipath);
		fmt::fprintf(buf, "<a href='{}'>{}</a>::", ipath, ident[i]);
		fmt::fprintf(buf, "<a href='{}'>{}</a>::", ipath, ident[i])!;
	};
	fmt::fprint(buf, ident[len(ident) - 1]);
	fmt::fprint(buf, ident[len(ident) - 1])!;
	return strio::finish(buf);
};



@@ 782,8 782,8 @@ details pre {
	<ul>
		<li>
			<a href='https://harelang.org'>Home</a>
		</li>");
	fmt::printf("<li>{}</li>", breadcrumb);
		</li>")?;
	fmt::printf("<li>{}</li>", breadcrumb)?;
	fmt::print("</ul>
</nav>
<main>")?;

M crypto/md5/md5.ha => crypto/md5/md5.ha +1 -1
@@ 97,7 97,7 @@ fn sum(h: *hash::hash) []u8 = {
	let tmp: [1 + 63 + 8]u8 = [0x80, 0...];
	const pad = (55 - ln) % 64;
	endian::leputu32(tmp[1+pad..], (ln << 3) : u32);
	write(&h.hash.stream, tmp[..1+pad+8]); // append the length in bits
	write(&h.hash.stream, tmp[..1+pad+8])!; // append the length in bits

	assert(h.nx == 0);


M crypto/sha1/sha1.ha => crypto/sha1/sha1.ha +2 -2
@@ 102,12 102,12 @@ fn sum(h: *hash::hash) []u8 = {
	let tmp: [64]u8 = [0x80, 0...];
	const pad = if ((ln % 64z) < 56z) 56z - ln % 64z
		else 64 + 56z - ln % 64z;
	write(&h.hash.stream, tmp[..pad]);
	write(&h.hash.stream, tmp[..pad])!;

	// Length in bits.
	ln <<= 3;
	endian::beputu64(tmp, ln: u64);
	write(&h.hash.stream, tmp[..8]);
	write(&h.hash.stream, tmp[..8])!;

	assert(h.nx == 0);


M crypto/sha256/sha256.ha => crypto/sha256/sha256.ha +2 -2
@@ 118,11 118,11 @@ fn sum(h: *hash::hash) []u8 = {
	tmp[0] = 0x80;
	const n = if ((ln % 64z) < 56z) 56z - ln % 64z
		else 64z + 56z - ln % 64z;
	write(&h.hash.stream, tmp[..n]);
	write(&h.hash.stream, tmp[..n])!;

	ln <<= 3;
	endian::beputu64(tmp, ln: u64);
	write(&h.hash.stream, tmp[..8]);
	write(&h.hash.stream, tmp[..8])!;

	assert(h.nx == 0);


M crypto/sha512/sha512.ha => crypto/sha512/sha512.ha +3 -3
@@ 143,17 143,17 @@ fn sum(h: *hash::hash) []u8 = {
	let tmp: [chunk]u8 = [0x80, 0...];
	if ((ln % 128) < 112) {
		const n = 112 - (ln % 128);
		write(&d.hash.stream, tmp[..n]);
		write(&d.hash.stream, tmp[..n])!;
	} else {
		const n = 128 + 112 - (ln % 128);
		write(&d.hash.stream, tmp[..n]);
		write(&d.hash.stream, tmp[..n])!;
	};

	// Length in bits
	ln <<= 3;
	endian::beputu64(tmp, 0u64); // upper 64 bits are always zero
	endian::beputu64(tmp[8..], ln : u64);
	write(&d.hash.stream, tmp[..16]);
	write(&d.hash.stream, tmp[..16])!;

	assert(d.nx == 0);


M fmt/fmt.ha => fmt/fmt.ha +1 -1
@@ 49,7 49,7 @@ export fn bsprintf(buf: []u8, fmt: str, args: formattable...) str = {
// Formats text for printing and writes it to [[os::stderr]], followed by a line
// feed, then exits the program with an error status.
export @noreturn fn fatal(fmt: str, args: formattable...) void = {
	fprintfln(os::stderr, fmt, args...);
	fprintfln(os::stderr, fmt, args...)!;
	os::exit(1);
};


M format/xml/parser.ha => format/xml/parser.ha +7 -7
@@ 122,13 122,13 @@ fn poptag(par: *parser, expect: str) (str | error) = {
		return syntaxerr;
	};
	strio::reset(par.namebuf);
	strio::concat(par.namebuf, pop);
	strio::concat(par.namebuf, pop)!;
	return strio::string(par.namebuf);
};

fn scan_attr(par: *parser) (token | error) = {
	let name = scan_name(par, par.namebuf)?;
	want(par, OPTWS, '=', OPTWS);
	want(par, OPTWS, '=', OPTWS)?;
	let quot = quote(par)?;
	strio::reset(par.textbuf);
	for (true) match (bufio::scanrune(par.in)?) {


@@ 195,7 195,7 @@ fn scan_cdata(par: *parser) (text | error) = {
			rn: rune => rn,
		};
		if (rn != ']') {
			strio::appendrune(par.textbuf, rn);
			strio::appendrune(par.textbuf, rn)!;
			continue;
		};
		let rn = match (bufio::scanrune(par.in)?) {


@@ 203,7 203,7 @@ fn scan_cdata(par: *parser) (text | error) = {
			rn: rune => rn,
		};
		if (rn != ']') {
			strio::appendrune(par.textbuf, rn);
			strio::appendrune(par.textbuf, rn)!;
			continue;
		};
		let rn = match (bufio::scanrune(par.in)?) {


@@ 211,7 211,7 @@ fn scan_cdata(par: *parser) (text | error) = {
			rn: rune => rn,
		};
		if (rn == '>') break;
		strio::appendrune(par.textbuf, rn);
		strio::appendrune(par.textbuf, rn)!;
	};
	return strio::string(par.textbuf): text;
};


@@ 335,12 335,12 @@ fn scan_name(par: *parser, buf: *io::stream) (str | error) = {
	if (!isnamestart(rn)) {
		return syntaxerr;
	};
	strio::appendrune(buf, rn);
	strio::appendrune(buf, rn)!;

	for (true) match (bufio::scanrune(par.in)?) {
		_: io::EOF => return syntaxerr,
		rn: rune => if (isname(rn)) {
			strio::appendrune(buf, rn);
			strio::appendrune(buf, rn)!;
		} else {
			bufio::unreadrune(par.in, rn);
			break;

M fs/fs.ha => fs/fs.ha +5 -1
@@ 92,7 92,11 @@ export fn mkdirs(fs: *fs, path: str) (void | error) = {
			_: void => void,
		};
	};
	return mkdir(fs, path);
	return match (mkdir(fs, path)) {
		_: errors::exists => void,
		err: error => err,
		_: void => void,
	};
};

// Removes a directory. The target directory must be empty; see [[rmdirall]] to

M fs/mem/stream.ha => fs/mem/stream.ha +1 -1
@@ 47,7 47,7 @@ fn stream_open(
			bufio::truncate(s.source);
		};
	};
	io::seek(s.source, 0, io::whence::SET);
	io::seek(s.source, 0, io::whence::SET)?;
	return &s.stream;
};


M getopt/getopts.ha => getopt/getopts.ha +4 -4
@@ 228,7 228,7 @@ export fn printhelp(s: *io::stream, name: str, help: []help) void = {
		_: cmd_help => void,
		_: (flag_help | parameter_help) => {
			// Only print this if there are flags to show
			fmt::fprint(s, "\n");
			fmt::fprint(s, "\n")!;
			break;
		},
	};


@@ 236,10 236,10 @@ export fn printhelp(s: *io::stream, name: str, help: []help) void = {
	for (let i = 0z; i < len(help); i += 1) match (help[i]) {
		_: cmd_help => void,
		f: flag_help => {
			fmt::fprintfln(s, "-{}: {}", f.0: rune, f.1);
			fmt::fprintfln(s, "-{}: {}", f.0: rune, f.1)!;
		},
		p: parameter_help => {
			fmt::fprintfln(s, "-{} <{}>: {}", p.0: rune, p.1, p.2);
			fmt::fprintfln(s, "-{} <{}>: {}", p.0: rune, p.1, p.2)!;
		},
	};
};


@@ 248,7 248,7 @@ fn errmsg(name: str, err: str, opt: (rune | void), help: []help) void = {
	fmt::errorfln("{}: {}{}", name, err, match (opt) {
		r: rune => r,
		_: void => "",
	});
	})!;
	printusage(os::stderr, name, help);
};


M hare/lex/lex.ha => hare/lex/lex.ha +3 -3
@@ 195,7 195,7 @@ fn lex_string(lex: *lexer, loc: location) (token | error) = {
			if (r == '"') {
				const tok = lex_string(lex, loc)?;
				const next = tok.1 as str;
				strio::concat(buf, next);
				strio::concat(buf, next)!;
				free(next);
			} else {
				unget(lex, r);


@@ 232,7 232,7 @@ fn lex_name(lex: *lexer, loc: location, keyword: bool) (token | error) = {
	match (next(lex)) {
		r: rune => {
			assert(is_name(r, false));
			strio::appendrune(buf, r);
			strio::appendrune(buf, r)!;
		},
		_: (io::EOF | io::error) => abort(),
	};


@@ 279,7 279,7 @@ fn lex_comment(lexr: *lexer, loc: location) (token | error) = {
	for (true) match (next(lexr)?) {
		_: io::EOF => break,
		r: rune => {
			strio::appendrune(buf, r);
			strio::appendrune(buf, r)!;
			if (r == '\n') break;
		},
	};

M hare/module/manifest.ha => hare/module/manifest.ha +4 -4
@@ 264,16 264,16 @@ export fn manifest_write(ctx: *context, manifest: *manifest) (void | error) = {
		let hash = hex::encodestr(ver.hash);
		defer free(hash);

		fmt::fprintf(fd, "module {}", hash);
		fmt::fprintf(fd, "module {}", hash)?;

		for (let j = 0z; j < len(ver.inputs); j += 1) {
			let hash = hex::encodestr(ver.inputs[i].hash);
			defer free(hash);

			fmt::fprintf(fd, " {}", hash);
			fmt::fprintf(fd, " {}", hash)?;
		};

		fmt::fprintln(fd);
		fmt::fprintln(fd)?;
	};
};



@@ 296,7 296,7 @@ fn lock(fs: *fs::fs, cachedir: str) (*io::stream | error) = {
			err: fs::error => return err,
		};
		if (!logged) {
			fmt::errorfln("Waiting for lock on {}...", lockpath);
			fmt::errorfln("Waiting for lock on {}...", lockpath): void;
			logged = true;
		};
		time::sleep(1 * time::SECOND);

M hare/parse/parse.ha => hare/parse/parse.ha +3 -2
@@ 41,9 41,10 @@ fn want(lexer: *lex::lexer, want: lex::ltok...) (lex::token | error) = {
	let buf = strio::dynamic();
	defer io::close(buf);
	for (let i = 0z; i < len(want); i += 1) {
		fmt::fprintf(buf, "'{}'", lex::tokstr((want[i], void, mkloc(lexer))));
		const tstr = lex::tokstr((want[i], void, mkloc(lexer)));
		fmt::fprintf(buf, "'{}'", tstr)!;
		if (i + 1 < len(want)) {
			fmt::fprint(buf, ", ");
			fmt::fprint(buf, ", ")!;
		};
	};
	return syntaxerr(mkloc(lexer), "Unexpected '{}', was expecting {}",

M hare/parse/type.ha => hare/parse/type.ha +1 -1
@@ 24,7 24,7 @@ fn prototype(lexer: *lex::lexer) (ast::func_type | error) = {
			_: void => want(lexer, ltok::NAME)?.1 as str,
			_: lex::token => "",
		};
		want(lexer, ltok::COLON);
		want(lexer, ltok::COLON)?;
		append(params, ast::func_param {
			loc = loc,
			name = name,

M hare/unparse/expr.ha => hare/unparse/expr.ha +1 -1
@@ 381,7 381,7 @@ fn struct_constant(
	};
	indent -= 1;
	newline(out, indent)?;
	fmt::fprint(out, "}");
	fmt::fprint(out, "}")?;
	return z;
};


M hare/unparse/ident.ha => hare/unparse/ident.ha +1 -1
@@ 17,7 17,7 @@ export fn ident(out: *io::stream, id: ast::ident) (size | io::error) = {
// Unparses an identifier into a string. The caller must free the return value.
export fn identstr(id: ast::ident) str = {
	let buf = strio::dynamic();
	ident(buf, id);
	ident(buf, id)!;
	return strio::finish(buf);
};


M io/println+linux.ha => io/println+linux.ha +6 -6
@@ 7,14 7,14 @@ use rt;
export fn println(msgs: str...) void = {
	for (let i = 0z; i < len(msgs); i += 1) {
		let msg = msgs[i];
		rt::write(1, *(&msg: **void): *const char, len(msg));
		rt::write(1, *(&msg: **void): *const char, len(msg)): void;
		if (i + 1 < len(msgs)) {
			const sp = " ";
			rt::write(1, *(&sp: **void): *const char, 1);
			rt::write(1, *(&sp: **void): *const char, 1): void;
		};
	};
	const linefeed = "\n";
	rt::write(1, *(&linefeed: **void): *const char, 1);
	rt::write(1, *(&linefeed: **void): *const char, 1): void;
};

// Prints strings to stderr, separated by spaces, and followed by a newline.


@@ 24,12 24,12 @@ export fn println(msgs: str...) void = {
export fn errorln(msgs: str...) void = {
	for (let i = 0z; i < len(msgs); i += 1) {
		let msg = msgs[i];
		rt::write(2, *(&msg: **void): *const char, len(msg));
		rt::write(2, *(&msg: **void): *const char, len(msg)): void;
		if (i + 1 < len(msgs)) {
			const sp = " ";
			rt::write(2, *(&sp: **void): *const char, 1);
			rt::write(2, *(&sp: **void): *const char, 1): void;
		};
	};
	const linefeed = "\n";
	rt::write(2, *(&linefeed: **void): *const char, 1);
	rt::write(2, *(&linefeed: **void): *const char, 1): void;
};

M io/stream.ha => io/stream.ha +2 -2
@@ 50,9 50,9 @@ export fn write(s: *stream, buf: const []u8) (size | error) = {
};

// Closes the stream.
export fn close(s: *stream) (error | void) = {
export fn close(s: *stream) void = {
	return match (s.closer) {
		null => errors::unsupported,
		null => void,
		c: *closer => c(s),
	};
};

M net/+linux/socket.ha => net/+linux/socket.ha +2 -2
@@ 70,7 70,7 @@ fn listen_fd(
			p: portassignment => portout = p,
		};
	};
	setfcntl(sockfd, rt::O_CLOEXEC);
	setfcntl(sockfd, rt::O_CLOEXEC)!;

	wrap(rt::bind(sockfd, &addr, sockasz(addr)))?;
	wrap(rt::listen(sockfd, bk))?;


@@ 124,7 124,7 @@ fn stream_accept(l: *listener) (*io::stream | io::error) = {
fn stream_shutdown(l: *listener) void = {
	assert(l.shutdown == &stream_shutdown);
	let l = l: *stream_listener;
	rt::close(l.fd);
	rt::close(l.fd)!;
	free(l);
};


M net/+linux/util.ha => net/+linux/util.ha +1 -1
@@ 14,7 14,7 @@ fn setsockopt(sockfd: int, option: int, value: bool) (void | rt::errno) = {

fn setfcntl(sockfd: int, flag: int) (void | rt::errno) = {
	let flags = rt::fcntl(sockfd, rt::F_GETFL, 0)?;
	rt::fcntl(sockfd, rt::F_SETFL, flags | flag); // ? always detects io::error
	rt::fcntl(sockfd, rt::F_SETFL, flags | flag)!;
	return;
};


M os/+linux/dirfdfs.ha => os/+linux/dirfdfs.ha +2 -2
@@ 329,7 329,7 @@ fn fs_resolve(fs: *fs::fs, path: str) str = {

fn fs_close(fs: *fs::fs) void = {
	let fs = fs: *os_filesystem;
	rt::close(fs.dirfd);
	rt::close(fs.dirfd)!;
};

def BUFSIZ: size = 2048;


@@ 370,7 370,7 @@ fn iter_next(iter: *fs::iterator) (fs::dirent | void) = {
	if (iter.buf_pos >= iter.buf_end) {
		let n = rt::getdents64(iter.fd, &iter.buf, BUFSIZ) as size;
		if (n == 0) {
			rt::close(iter.fd);
			rt::close(iter.fd)!;
			free(iter);
			return;
		};

M os/+linux/fdstream.ha => os/+linux/fdstream.ha +2 -2
@@ 88,14 88,14 @@ fn fd_write(s: *io::stream, buf: const []u8) (size | io::error) = {

fn fd_close(s: *io::stream) void = {
	let stream = s: *fd_stream;
	rt::close(stream.fd);
	rt::close(stream.fd)!;
	free(s.name);
	free(stream);
};

fn fd_close_static(s: *io::stream) void = {
	let stream = s: *fd_stream;
	rt::close(stream.fd);
	rt::close(stream.fd)!;
	free(stream);
};


M os/exec/cmd.ha => os/exec/cmd.ha +1 -1
@@ 54,7 54,7 @@ export fn finish(cmd: *command) void = {
// running process with the new command.
export @noreturn fn exec(cmd: *command) void = {
	defer finish(cmd); // Note: doesn't happen if exec succeeds
	platform_exec(cmd);
	platform_exec(cmd): void;
	abort("os::exec::exec failed");
};


M os/exec/exec+linux.ha => os/exec/exec+linux.ha +5 -7
@@ 14,10 14,8 @@ export fn fork() (int | void | error) = match (rt::fork()) {

fn open(path: str) (platform_cmd | errors::opaque) = {
	match (rt::access(path, rt::X_OK)) {
		err: rt::errno => errors::errno(err),
		b: bool => if (!b) {
			return errors::errno(rt::EACCES);
		},
		err: rt::errno => return errors::errno(err),
		b: bool => if (!b) 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.


@@ 63,7 61,7 @@ fn platform_start(cmd: *command) (errors::opaque | process) = {
	match (rt::clone(null, 0, null, null, 0)) {
		err: rt::errno => return errors::errno(err),
		pid: int => {
			rt::close(pipe[1]);
			rt::close(pipe[1])!;
			let errno: int = 0;
			return match (rt::read(pipe[0], &errno, size(int))) {
				err: rt::errno => errors::errno(err),


@@ 75,10 73,10 @@ fn platform_start(cmd: *command) (errors::opaque | process) = {
			};
		},
		_: void => {
			rt::close(pipe[0]);
			rt::close(pipe[0])!;
			let err = platform_exec(cmd);
			let err = &err.data: *rt::errno;
			rt::write(pipe[1], &err, size(int));
			rt::write(pipe[1], &err, size(int))!;
			rt::exit(1);
		},
	};

M os/exec/process+linux.ha => os/exec/process+linux.ha +2 -2
@@ 38,7 38,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 => errors::errno(err),
		err: rt::errno => return errors::errno(err),
		pid: int => assert(pid == *proc),
	};
	rusage(&st, &ru);


@@ 51,7 51,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 => errors::errno(err),
		err: rt::errno => return errors::errno(err),
		pid: int => switch (pid) {
			0 => return void,
			* => assert(pid == *proc),

M rt/+linux/abort.ha => rt/+linux/abort.ha +4 -4
@@ 1,8 1,8 @@
fn platform_abort(msg: str) void = {
	const prefix = "Abort: ";
	const linefeed = "\n";
	write(2, *(&prefix: **void): *const char, len(prefix));
	write(2, *(&msg: **void): *const char, len(msg));
	write(2, *(&linefeed: **void): *const char, 1);
	kill(getpid(), SIGABRT);
	write(2, *(&prefix: **void): *const char, len(prefix))!;
	write(2, *(&msg: **void): *const char, len(msg))!;
	write(2, *(&linefeed: **void): *const char, 1)!;
	kill(getpid(), SIGABRT)!;
};

M rt/+linux/syscalls.ha => rt/+linux/syscalls.ha +4 -4
@@ 70,21 70,21 @@ export fn openat2(
export fn unlink(path: path) (void | errno) = {
	let path = kpath(path)?;
	wrap_return(syscall3(SYS_unlinkat,
		AT_FDCWD: u64, path: uintptr: u64, 0u64));
		AT_FDCWD: u64, path: uintptr: u64, 0u64))?;
	return;
};

export fn unlinkat(dirfd: int, path: path, flags: int) (void | errno) = {
	let path = kpath(path)?;
	wrap_return(syscall3(SYS_unlinkat,
		dirfd: u64, path: uintptr: u64, flags: u64));
		dirfd: u64, path: uintptr: u64, flags: u64))?;
	return;
};

export fn chmod(path: path, mode: uint) (void | errno) = {
	let path = kpath(path)?;
	wrap_return(syscall3(SYS_fchmodat,
		AT_FDCWD: u64, path: uintptr: u64, mode: u64));
		AT_FDCWD: u64, path: uintptr: u64, mode: u64))?;
	return;
};



@@ 98,7 98,7 @@ export fn fchmodat(dirfd: int, path: path, mode: uint) (void | errno) = {
export fn chown(path: path, uid: uint, gid: uint) (void | errno) = {
	let path = kpath(path)?;
	wrap_return(syscall4(SYS_fchownat,
		AT_FDCWD: u64, path: uintptr: u64, uid: u32, gid: u32));
		AT_FDCWD: u64, path: uintptr: u64, uid: u32, gid: u32))?;
	return;
};


M rt/abort.ha => rt/abort.ha +6 -6
@@ 14,10 14,10 @@ export @noreturn fn abort_fixed(loc: str, i: int) void = {
	const prefix = "Abort: ";
	const sep = ": ";
	const linefeed = "\n";
	write(2, *(&prefix: **void): *const char, len(prefix));
	write(2, *(&loc: **void): *const char, len(loc));
	write(2, *(&sep: **void): *const char, len(sep));
	write(2, *(&reasons[i]: **void): *const char, len(reasons[i]));
	write(2, *(&linefeed: **void): *const char, 1);
	kill(getpid(), SIGABRT);
	write(2, *(&prefix: **void): *const char, len(prefix))!;
	write(2, *(&loc: **void): *const char, len(loc))!;
	write(2, *(&sep: **void): *const char, len(sep))!;
	write(2, *(&reasons[i]: **void): *const char, len(reasons[i]))!;
	write(2, *(&linefeed: **void): *const char, 1)!;
	kill(getpid(), SIGABRT)!;
};

M strio/dynamic.ha => strio/dynamic.ha +7 -9
@@ 30,7 30,7 @@ export fn dynamic() *io::stream = {
// of it to the caller.
export fn finish(s: *io::stream) str = {
	assert(s.writer == &dynamic_write,
		"strio::finish called on non-strio stream");
		"strio::finish called on non-strio::dynamic stream");
	let s = s: *dynamic_stream;
	let buf = s.buf;
	free(s);


@@ 39,20 39,18 @@ 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 | errors::unsupported) = {
	if (s.writer != &dynamic_write || s.closer != &dynamic_close) {
		return errors::unsupported;
	};
export fn reset(s: *io::stream) void = {
	assert(s.writer == &dynamic_write,
		"strio::reset called on non-strio::dynamic stream");
	const s = s: *dynamic_stream;
	s.buf = s.buf[..0];
};

// Truncates the buffer, freeing memory associated with it and setting its
// length to zero.
export fn truncate(s: *io::stream) (void | errors::unsupported) = {
	if (s.writer != &dynamic_write || s.closer != &dynamic_close) {
		return errors::unsupported;
	};
export fn truncate(s: *io::stream) void = {
	assert(s.writer == &dynamic_write,
		"strio::truncate called on non-strio::dynamic stream");
	let s = s: *dynamic_stream;
	delete(s.buf[..]);
};