~sircmpwn/hare

69f1eebbc3a593ea3a5df02732b6897fd36e5eb1 — Drew DeVault 8 months ago 2136342
s/to_utf8/toutf8/g
M bufio/buffered.ha => bufio/buffered.ha +3 -3
@@ 262,8 262,8 @@ fn buffered_write(s: *io::stream, buf: const []u8) (size | io::error) = {
	let wbuf: [1024]u8 = [0...];
	let f = buffered(sink, [], wbuf);

	assert(io::write(f, strings::to_utf8("hello")) as size == 5);
	assert(io::write(f, strings::toutf8("hello")) as size == 5);
	assert(len(buffer(sink)) == 0);
	assert(io::write(f, strings::to_utf8(" world!\n")) as size == 8);
	assert(bytes::equal(buffer(sink), strings::to_utf8("hello world!\n")));
	assert(io::write(f, strings::toutf8(" world!\n")) as size == 8);
	assert(bytes::equal(buffer(sink), strings::toutf8("hello world!\n")));
};

M bufio/fixed.ha => bufio/fixed.ha +3 -3
@@ 56,7 56,7 @@ fn fixed_write(s: *io::stream, buf: const []u8) (size | io::error) = {
	static let buf: [1024]u8 = [0...];
	let stream = fixed(buf, io::mode::WRITE);
	let n = 0z;
	n += io::write(stream, strings::to_utf8("hello ")) as size;
	n += io::write(stream, strings::to_utf8("world")) as size;
	assert(bytes::equal(buf[..n], strings::to_utf8("hello world")));
	n += io::write(stream, strings::toutf8("hello ")) as size;
	n += io::write(stream, strings::toutf8("world")) as size;
	assert(bytes::equal(buf[..n], strings::toutf8("hello world")));
};

M bufio/scanner.ha => bufio/scanner.ha +3 -3
@@ 100,16 100,16 @@ export fn scanrune(stream: *io::stream) (rune | utf8::invalid | io::EOF | io::er
};

@test fn scanline() void = {
	let helloworld = strings::to_utf8("hello\nworld");
	let helloworld = strings::toutf8("hello\nworld");
	let buf = fixed(helloworld, io::mode::READ);

	let line = scanline(buf) as []u8;
	defer free(line);
	assert(bytes::equal(line, strings::to_utf8("hello")));
	assert(bytes::equal(line, strings::toutf8("hello")));

	let line = scanline(buf) as []u8;
	defer free(line);
	assert(bytes::equal(line, strings::to_utf8("world")));
	assert(bytes::equal(line, strings::toutf8("world")));

	assert(scanline(buf) is io::EOF);
};

M cmd/hare/schedule.ha => cmd/hare/schedule.ha +1 -1
@@ 14,7 14,7 @@ fn ident_hash(ident: ast::ident) u32 = {
	let hash = fnv::fnv32();
	defer hash::close(hash);
	for (let i = 0z; i < len(ident); i += 1) {
		hash::write(hash, strings::to_utf8(ident[i]));
		hash::write(hash, strings::toutf8(ident[i]));
		hash::write(hash, [0]);
	};
	return fnv::sum32(hash);

M crypto/sha1/+test.ha => crypto/sha1/+test.ha +2 -2
@@ 27,7 27,7 @@ use io;
	for (let i = 0z; i < len(vectors); i += 1) {
		const vector = vectors[i];
		hash::reset(sha);
		hash::write(sha, strings::to_utf8(vector.0));
		hash::write(sha, strings::toutf8(vector.0));
		let sum = hash::sum(sha);
		defer free(sum);



@@ 54,7 54,7 @@ use io;
//	defer hash::finish(sha);
//
//	for (let i = 0z; i < 16777216; i += 1)
//		hash::write(sha, strings::to_utf8(input));
//		hash::write(sha, strings::toutf8(input));
//
//	let sum = hash::sum(sha);
//	defer free(sum);

M crypto/sha256/+test.ha => crypto/sha256/+test.ha +2 -2
@@ 24,7 24,7 @@ use strio;
	for (let i = 0z; i < len(vectors); i += 1) {
		const vector = vectors[i];
		hash::reset(sha);
		hash::write(sha, strings::to_utf8(vector.0));
		hash::write(sha, strings::toutf8(vector.0));
		let sum = hash::sum(sha);
		defer free(sum);



@@ 47,7 47,7 @@ use strio;
	//const expected = "50e72a0e26442fe2552dc3938ac58658228c0cbfb1d2ca872ae435266fcd055e";
	//hash::reset(sha);
	//for (let i = 0z; i < 16777216; i += 1) {
	//	hash::write(sha, strings::to_utf8(input));
	//	hash::write(sha, strings::toutf8(input));
	//};
	//let sum = hash::sum(sha);
	//defer free(sum);

M encoding/hex/hex.ha => encoding/hex/hex.ha +3 -3
@@ 16,7 16,7 @@ export fn encode(b: []u8) str = {
		if (len(s) == 1) {
			io::write(buf, ['0': u32: u8]);
		};
		io::write(buf, strings::to_utf8(s)) as size;
		io::write(buf, strings::toutf8(s)) as size;
	};
	return strio::finish(buf);
};


@@ 35,9 35,9 @@ export fn decode(s: str) ([]u8 | invalid) = {
		return invalid;
	};
	let buf: []u8 = alloc([], len(s) / 2);
	let s = strings::to_utf8(s);
	let s = strings::toutf8(s);
	for (let i = 0z; i < len(s) / 2; i += 1) {
		let oct = strings::from_utf8_unsafe(s[i * 2..i * 2 + 2]);
		let oct = strings::fromutf8_unsafe(s[i * 2..i * 2 + 2]);
		let u = match (strconv::stou8b(oct, 16)) {
			(strconv::invalid | strconv::overflow) => return invalid,
			u: u8 => u,

M encoding/utf8/decode.ha => encoding/utf8/decode.ha +2 -2
@@ 1,6 1,6 @@
use types;

fn to_utf8(in: str) []u8 = *(&in: *[]u8);
fn toutf8(in: str) []u8 = *(&in: *[]u8);

// The state for the UTF-8 decoder.
export type decoder = struct {


@@ 10,7 10,7 @@ export type decoder = struct {

// Initializes a new UTF-8 decoder.
export fn decode(src: (str | []u8)) decoder = match (src) {
	s: str  => decoder { src = to_utf8(s), ...  },
	s: str  => decoder { src = toutf8(s), ...  },
	b: []u8 => decoder { src = b, ...  },
};


M fmt/fmt.ha => fmt/fmt.ha +11 -11
@@ 72,7 72,7 @@ export fn errorfln(fmt: str, args: formattable...) (io::error | size) =
export fn asprintf(fmt: str, args: formattable...) str = {
	let buf = bufio::dynamic(io::mode::WRITE);
	assert(fprintf(buf, fmt, args...) is size);
	return strings::from_utf8_unsafe(bufio::finish(buf));
	return strings::fromutf8_unsafe(bufio::finish(buf));
};

// Formats text for printing and writes it into a caller supplied buffer. The


@@ 80,7 80,7 @@ export fn asprintf(fmt: str, args: formattable...) str = {
export fn bsprintf(buf: []u8, fmt: str, args: formattable...) str = {
	let sink = bufio::fixed(buf, io::mode::WRITE);
	let l = fprintf(sink, fmt, args...) as size;
	return strings::from_utf8_unsafe(buf[..l]);
	return strings::fromutf8_unsafe(buf[..l]);
};

// Formats text for printing and writes it to [os::stderr], followed by a line


@@ 126,7 126,7 @@ export fn errorln(args: formattable...) (io::error | size) =
export fn asprint(args: formattable...) str = {
	let buf = bufio::dynamic(io::mode::WRITE);
	assert(fprint(buf, args...) is size);
	return strings::from_utf8_unsafe(bufio::finish(buf));
	return strings::fromutf8_unsafe(bufio::finish(buf));
};

// Formats values for printing using the default format modifiers and writes


@@ 135,7 135,7 @@ export fn asprint(args: formattable...) str = {
export fn bsprint(buf: []u8, args: formattable...) str = {
	let sink = bufio::fixed(buf, io::mode::WRITE);
	assert(fprint(sink, args...) is size);
	return strings::from_utf8_unsafe(buf);
	return strings::fromutf8_unsafe(buf);
};

// Formats values for printing using the default format modifiers and writes


@@ 276,23 276,23 @@ fn format_raw(
	arg: formattable,
	mod: *modifiers,
) (size | io::error) = match (arg) {
	s: str => io::write(out, strings::to_utf8(s)),
	s: str => io::write(out, strings::toutf8(s)),
	r: rune => io::write(out, utf8::encoderune(r)),
	b: bool => io::write(out, strings::to_utf8(if (b) "true" else "false")),
	b: bool => io::write(out, strings::toutf8(if (b) "true" else "false")),
	n: types::numeric => {
		let s = strconv::numerictosb(n, mod.base);
		io::write(out, strings::to_utf8(s));
		io::write(out, strings::toutf8(s));
	},
	p: uintptr => {
		let s = strconv::uptrtosb(p, mod.base);
		io::write(out, strings::to_utf8(s));
		io::write(out, strings::toutf8(s));
	},
	v: nullable *void => match (v) {
		v: *void => {
			let s = strconv::uptrtosb(v: uintptr,
				strconv::base::HEX_LOWER);
			let n = io::write(out, strings::to_utf8("0x"))?;
			n += io::write(out, strings::to_utf8(s))?;
			let n = io::write(out, strings::toutf8("0x"))?;
			n += io::write(out, strings::toutf8(s))?;
			n;
		},
		null => format(out, "(null)", mod),


@@ 313,7 313,7 @@ fn scan_uint(iter: *strings::iterator) uint = {
			append(num, r: u32: u8);
		} else {
			strings::push(iter, r);
			match (strconv::stou(strings::from_utf8(num))) {
			match (strconv::stou(strings::fromutf8(num))) {
				(strconv::invalid | strconv::overflow) =>
					abort("Invalid format string (invalid index)"),
				u: uint => return u,

M fs/util.ha => fs/util.ha +1 -1
@@ 40,7 40,7 @@ export fn mode_str(m: mode) const str = {
			else if (m & mode::OTHER_X == mode::OTHER_X) 'x'
			else '-'): u32: u8,
	];
	return strings::from_utf8(buf);
	return strings::fromutf8(buf);
};

@test fn mode_str() void = {

M getopt/getopts.ha => getopt/getopts.ha +1 -1
@@ 151,7 151,7 @@ export fn parse(args: []str, help: help...) command = {
					i += 1;
					append(opts, (r, args[i]));
				} else {
					let s = strings::from_utf8(d.src[d.offs..]);
					let s = strings::fromutf8(d.src[d.offs..]);
					append(opts, (r, s));
				};
				continue :arg;

M hare/lex/+test.ha => hare/lex/+test.ha +3 -3
@@ 5,7 5,7 @@ use io::{mode};
use strings;

@test fn unget() void = {
	let buf = bufio::fixed(strings::to_utf8("z"), mode::READ);
	let buf = bufio::fixed(strings::toutf8("z"), mode::READ);
	let lexer = init(buf, "<test>");
	unget(&lexer, 'x');
	unget(&lexer, 'y');


@@ 52,7 52,7 @@ fn litassert(expected: literal, actual: literal) void = match (expected) {
};

fn lextest(in: str, expected: [](uint, uint, token)) void = {
	let buf = bufio::fixed(strings::to_utf8(in), mode::READ);
	let buf = bufio::fixed(strings::toutf8(in), mode::READ);
	let lexer = init(buf, "<test>");
	for (let i = 0z; i < len(expected); i += 1) {
		let eline = expected[i].0, ecol = expected[i].1,


@@ 182,7 182,7 @@ fn lextest(in: str, expected: [](uint, uint, token)) void = {
	let keywords = bmap[..btoken::LAST_KEYWORD+1];
	for (let i = 0z; i < len(keywords); i += 1) {
		let lexer = init(bufio::fixed(
			strings::to_utf8(keywords[i]), mode::READ),
			strings::toutf8(keywords[i]), mode::READ),
			"<test>");
		let tl = match (lex(&lexer)) {
			tl: (token, location) => tl,

M hare/lex/lex.ha => hare/lex/lex.ha +3 -3
@@ 119,7 119,7 @@ fn lex_unicode(lex: *lexer, loc: location, n: size) (rune | error) = {
		};
		buf[i] = r: u32: u8;
	};
	let s = strings::from_utf8_unsafe(buf[..n]);
	let s = strings::fromutf8_unsafe(buf[..n]);
	return strconv::stou32b(s, strconv::base::HEX) as u32: rune;
};



@@ 170,7 170,7 @@ fn lex_string(
				append(chars, ...utf8::encoderune(r));
			},
	};
	return (strings::from_utf8(chars): literal, loc);
	return (strings::fromutf8(chars): literal, loc);
};

fn lex_rn_str(


@@ 222,7 222,7 @@ fn lex_name(
		},
	};

	let n = strings::from_utf8(chars);
	let n = strings::fromutf8(chars);
	return match (sort::search(bmap[..btoken::LAST_KEYWORD+1],
			size(str), &n, &ncmp)) {
		// TODO: Validate that names are ASCII

M hare/module/manifest.ha => hare/module/manifest.ha +1 -1
@@ 69,7 69,7 @@ export fn manifest_load(ctx: *context, ident: ast::ident) (manifest | error) = {
	for (true) {
		let line = match (bufio::scanline(file)?) {
			io::EOF => break,
			line: []u8 => match (strings::try_from_utf8(line)) {
			line: []u8 => match (strings::try_fromutf8(line)) {
				// Treat an invalid manifest as empty
				utf8::invalid => return manifest,
				s: str => s,

M hare/module/scan.ha => hare/module/scan.ha +1 -1
@@ 123,7 123,7 @@ fn scan_directory(
		let tags = parse_name(name).2;
		defer tags_free(tags);

		let d = strings::to_utf8(name);
		let d = strings::toutf8(name);
		if (len(d) == 0 || (
			!strings::has_prefix(name, "+") &&
			!strings::has_prefix(name, "-"))) {

M hare/parse/+test.ha => hare/parse/+test.ha +9 -9
@@ 10,7 10,7 @@ use strio;
@test fn ident() void = {
	{
		const in = "foo";
		let buf = bufio::fixed(strings::to_utf8(in), mode::READ);
		let buf = bufio::fixed(strings::toutf8(in), mode::READ);
		let lexer = lex::init(buf, "<test>");
		let ident = ident(&lexer) as ast::ident;
		defer ast::ident_free(ident);


@@ 21,7 21,7 @@ use strio;

	{
		const in = "foo::bar";
		let buf = bufio::fixed(strings::to_utf8(in), mode::READ);
		let buf = bufio::fixed(strings::toutf8(in), mode::READ);
		let lexer = lex::init(buf, "<test>");
		let ident = ident(&lexer) as ast::ident;
		defer ast::ident_free(ident);


@@ 32,7 32,7 @@ use strio;

	{
		const in = "foo::bar::baz";
		let buf = bufio::fixed(strings::to_utf8(in), mode::READ);
		let buf = bufio::fixed(strings::toutf8(in), mode::READ);
		let lexer = lex::init(buf, "<test>");
		let ident = ident(&lexer) as ast::ident;
		defer ast::ident_free(ident);


@@ 44,7 44,7 @@ use strio;

	{
		const in = "foo::bar;";
		let buf = bufio::fixed(strings::to_utf8(in), mode::READ);
		let buf = bufio::fixed(strings::toutf8(in), mode::READ);
		let lexer = lex::init(buf, "<test>");
		let ident = ident(&lexer) as ast::ident;
		defer ast::ident_free(ident);


@@ 58,7 58,7 @@ use strio;
@test fn imports() void = {
	{
		const in = "use foo;";
		let buf = bufio::fixed(strings::to_utf8(in), mode::READ);
		let buf = bufio::fixed(strings::toutf8(in), mode::READ);
		let lexer = lex::init(buf, "<test>");
		let mods = imports(&lexer) as []ast::import;
		defer for (let i = 0z; i < len(mods); i += 1) {


@@ 79,7 79,7 @@ use strio;
			"use bar;\n"
			"use baz::bat;\n\n"
			"export fn main() void = void;";
		let buf = bufio::fixed(strings::to_utf8(in), mode::READ);
		let buf = bufio::fixed(strings::toutf8(in), mode::READ);
		let lexer = lex::init(buf, "<test>");
		let mods = imports(&lexer) as []ast::import;
		defer for (let i = 0z; i < len(mods); i += 1) {


@@ 108,7 108,7 @@ use strio;
			"use baz = bat;\n"
			"use qux = quux::corge;\n"
			"export fn main() void = void;";
		let buf = bufio::fixed(strings::to_utf8(in), mode::READ);
		let buf = bufio::fixed(strings::toutf8(in), mode::READ);
		let lexer = lex::init(buf, "<test>");
		let mods = imports(&lexer) as []ast::import;
		defer for (let i = 0z; i < len(mods); i += 1) {


@@ 139,7 139,7 @@ use strio;
			"use baz::{bat, qux};\n"
			"use quux::corge::{grault, garply,};\n"
			"export fn main() void = void;";
		let buf = bufio::fixed(strings::to_utf8(in), mode::READ);
		let buf = bufio::fixed(strings::toutf8(in), mode::READ);
		let lexer = lex::init(buf, "<test>");
		let mods = imports(&lexer) as []ast::import;
		defer for (let i = 0z; i < len(mods); i += 1) {


@@ 177,7 177,7 @@ use strio;
		"@symbol(\".f9$oo\") fn foo(bar: int, baz: int...) void;\n"
		"@test fn foo(_: int, ...) void;\n"
		"export fn main() void = void;\n";
	let buf = bufio::fixed(strings::to_utf8(in), mode::READ);
	let buf = bufio::fixed(strings::toutf8(in), mode::READ);
	let lexer = lex::init(buf, "<test>");
	let u = ast::subunit {
		imports = [],

M hash/fnv/fnv.ha => hash/fnv/fnv.ha +1 -1
@@ 177,7 177,7 @@ export fn sum64(h: *hash::hash) u64 = {
	for (let i = 0z; i < len(vectors); i += 1) {
		let vec = vectors[i];
		hash::reset(hash);
		hash::write(hash, strings::to_utf8(vec.0));
		hash::write(hash, strings::toutf8(vec.0));
		assert(sum32(hash) == vec.1);
	};
};

M os/+linux/environ.ha => os/+linux/environ.ha +2 -2
@@ 34,7 34,7 @@ let args_static: [32]str = [""...];

// Looks up an environment variable and returns its value, or void if unset.
export fn getenv(name: const str) (str | void) = {
	const name_b = strings::to_utf8(name);
	const name_b = strings::toutf8(name);
	for (let i = 0z; rt::envp[i] != null; i += 1) {
		const item = rt::envp[i]: *[*]u8;
		const eq: size = match (bytes::index(item[..], '=': u32: u8)) {


@@ 43,7 43,7 @@ export fn getenv(name: const str) (str | void) = {
		};
		if (bytes::equal(name_b, item[..eq])) {
			const ln = strings::cstrlen(item: *const char);
			return strings::from_utf8(item[eq+1..ln]);
			return strings::fromutf8(item[eq+1..ln]);
		};
	};
};

M path/iter.ha => path/iter.ha +3 -3
@@ 18,7 18,7 @@ let pathsep: []u8 = [PATHSEP];
// absolute, the first component will be the root path (e.g. /).
export fn iter(path: str) iterator = {
	let flags = iflags::NONE;
	let pb = strings::to_utf8(path);
	let pb = strings::toutf8(path);
	if (len(pb) > 0 && pb[0] == PATHSEP) {
		flags |= iflags::ABSOLUTE;
		pb = pb[1..];


@@ 38,10 38,10 @@ export fn next(iter: *iterator) (str | void) = {
	if (iter.flags & iflags::ABSOLUTE == iflags::ABSOLUTE) {
		iter.flags &= ~iflags::ABSOLUTE;
		static assert(PATHSEP <= 0x7F);
		return strings::from_utf8_unsafe(pathsep);
		return strings::fromutf8_unsafe(pathsep);
	};
	return match (bytes::next_token(&iter.tok)) {
		b: []u8 => strings::from_utf8_unsafe(b),
		b: []u8 => strings::fromutf8_unsafe(b),
		void => void,
	};
};

M path/join.ha => path/join.ha +2 -2
@@ 11,7 11,7 @@ export fn join(paths: str...) str = {
	let sink = bufio::dynamic(io::mode::WRITE);
	let utf8 = true;
	for (let i = 0z; i < len(paths); i += 1) {
		let buf = strings::to_utf8(paths[i]);
		let buf = strings::toutf8(paths[i]);
		let l = len(buf);
		if (l == 0) continue;
		for (l > 0 && buf[l - 1] == PATHSEP) {


@@ 26,7 26,7 @@ export fn join(paths: str...) str = {
		};
	};

	return strings::from_utf8_unsafe(bufio::finish(sink));
	return strings::fromutf8_unsafe(bufio::finish(sink));
};

@test fn join() void = {

M path/names.ha => path/names.ha +7 -7
@@ 7,7 7,7 @@ use strings;
// this returns the path to its parent directory. The return value is borrowed
// from the input, use [dup] to extend its lifetime.
export fn dirname(path: str) str = {
	let b = strings::to_utf8(path);
	let b = strings::toutf8(path);
	let i = match (bytes::rindex(b, PATHSEP)) {
		void => return path,
		z: size => z,


@@ 15,7 15,7 @@ export fn dirname(path: str) str = {
	if (i == 0) {
		i += 1;
	};
	return strings::from_utf8_unsafe(b[..i]);
	return strings::fromutf8_unsafe(b[..i]);
};

@test fn dirname() void = {


@@ 31,12 31,12 @@ export fn dirname(path: str) str = {
// name. The return value is borrowed from the input, use [dup] to extend its
// lifetime.
export fn basename(path: str) str = {
	let b = strings::to_utf8(path);
	let b = strings::toutf8(path);
	let i = match (bytes::rindex(b, PATHSEP)) {
		void => return path,
		z: size => if (z + 1 < len(b)) z + 1z else 0z,
	};
	return strings::from_utf8_unsafe(b[i..]);
	return strings::fromutf8_unsafe(b[i..]);
};

@test fn basename() void = {


@@ 57,18 57,18 @@ export fn basename(path: str) str = {
// extension("foo/example.tar.gz") => ("example", ".tar.gz")
export fn extension(p: str) (str, str) = {
	let p = basename(p);
	let b = strings::to_utf8(p);
	let b = strings::toutf8(p);
	if (len(b) == 0 || b[len(b) - 1] == PATHSEP) {
		return (p, "");
	};
	let b = strings::to_utf8(p);
	let b = strings::toutf8(p);
	let i = match (bytes::index(b, '.': u32: u8)) {
		void => return (p, ""),
		z: size => z,
	};
	let e = b[i..];
	let n = b[..i];
	return (strings::from_utf8_unsafe(n), strings::from_utf8_unsafe(e));
	return (strings::fromutf8_unsafe(n), strings::fromutf8_unsafe(e));
};

@test fn extension() void = {

M path/util.ha => path/util.ha +1 -1
@@ 2,7 2,7 @@ use strings;

// Returns true if a path is an absolute path.
export fn abs(path: str) bool = {
	let b = strings::to_utf8(path);
	let b = strings::toutf8(path);
	if (len(b) == 0) {
		return false;
	};

M strconv/itos.ha => strconv/itos.ha +1 -1
@@ 17,7 17,7 @@ export fn i64tosb(i: i64, b: base) const str = {
	buf[0] = '-': u32: u8;
	s.length = 1;

	let u = strings::to_utf8(u64tosb((-i): u64, b));
	let u = strings::toutf8(u64tosb((-i): u64, b));
	assert(len(u) + 1 < len(buf));

	bytes::copy(buf[1..len(u) + 1], u);

M strconv/stoi.ha => strconv/stoi.ha +2 -2
@@ 7,14 7,14 @@ use strings;
// by an i64, [strconv::overflow] is returned.
export fn stoi64(s: str) (i64 | invalid | overflow) = {
	if (len(s) == 0) return 0: invalid;
	let b = strings::to_utf8(s);
	let b = strings::toutf8(s);
	let sign = 1i64;
	let max = types::I64_MAX: u64;
	if (b[0] == '-': u32: u8) {
		sign = -1;
		max += 1;
	};
	let u = if (sign < 0) stou64(strings::from_utf8_unsafe(b[1..]))
	let u = if (sign < 0) stou64(strings::fromutf8_unsafe(b[1..]))
		else stou64(s);
	let n = u?;
	if (n > max) {

M strings/concat.ha => strings/concat.ha +2 -2
@@ 6,10 6,10 @@ export fn concat(strs: str...) str = {
	};
	let new: []u8 = alloc([], z + 1);
	for (let i = 0z; i < len(strs); i += 1) {
		append(new, ...to_utf8(strs[i]));
		append(new, ...toutf8(strs[i]));
	};
	append(new, 0);
	return from_utf8_unsafe(new[..z]);
	return fromutf8_unsafe(new[..z]);
};

@test fn concat() void = {

M strings/contains.ha => strings/contains.ha +2 -2
@@ 3,8 3,8 @@ use encoding::utf8;

// Returns true if a string contains a rune or a sub-string.
export fn contains(haystack: str, needle: (str | rune)) bool = match (needle) {
	s: str  => bytes::contains(to_utf8(haystack), to_utf8(s)),
	r: rune => bytes::contains(to_utf8(haystack), utf8::encoderune(r)),
	s: str  => bytes::contains(toutf8(haystack), toutf8(s)),
	r: rune => bytes::contains(toutf8(haystack), utf8::encoderune(r)),
};

@test fn contains() void = {

M strings/iter.ha => strings/iter.ha +1 -1
@@ 64,7 64,7 @@ export fn push(iter: *iterator, r: rune) void = {
// Return a substring from the next rune to the end of the string.
export fn iter_str(iter: *iterator) str = {
	assert(iter.push is void);
	return from_utf8(iter.dec.src[iter.dec.offs..]);
	return fromutf8(iter.dec.src[iter.dec.offs..]);
};

@test fn iter() void = {

M strings/sub.ha => strings/sub.ha +2 -2
@@ 39,8 39,8 @@ export fn sub(s: str, start: size, end: (size | end)) str = {
		sz: size => starti + utf8_byte_len_bounded(&iter, sz - start),
		end => starti + utf8_byte_len_unbounded(&iter),
	};
	let bytes = to_utf8(s);
	return from_utf8_unsafe(bytes[starti..endi]);
	let bytes = toutf8(s);
	return fromutf8_unsafe(bytes[starti..endi]);
};

@test fn sub() void = {

M strings/suffix.ha => strings/suffix.ha +2 -2
@@ 1,6 1,6 @@
// Returns true if 'in' has the given prefix.
export fn has_prefix(in: str, prefix: str) bool = {
	let a = to_utf8(in), b = to_utf8(prefix);
	let a = toutf8(in), b = toutf8(prefix);
	if (len(a) < len(b)) {
		return false;
	};


@@ 24,7 24,7 @@ export fn has_prefix(in: str, prefix: str) bool = {

// Returns true if 'in' has the given prefix.
export fn has_suffix(in: str, suff: str) bool = {
	let a = to_utf8(in), b = to_utf8(suff);
	let a = toutf8(in), b = toutf8(suff);
	if (len(a) < len(b)) {
		return false;
	};

M strings/tokenize.ha => strings/tokenize.ha +4 -4
@@ 12,13 12,13 @@ export type tokenizer = bytes::tokenizer;
// 	assert(strings::next_token(tok) == "name");
// 	assert(strings::remaining_tokens(tok) == "is drew");
export fn tokenize(s: str, delim: str) tokenizer =
	bytes::tokenize(to_utf8(s), to_utf8(delim));
	bytes::tokenize(toutf8(s), toutf8(delim));

// Returns the next string from a tokenizer, and advances the cursor. Returns
// void if there are no tokens left.
export fn next_token(s: *tokenizer) (str | void) = {
	return match (bytes::next_token(s)) {
		b: []u8 => from_utf8(b),
		b: []u8 => fromutf8(b),
		void => void,
	};
};


@@ 26,7 26,7 @@ export fn next_token(s: *tokenizer) (str | void) = {
// Same as next_token(), but does not advance the cursor
export fn peek_token(s: *tokenizer) (str | void) = {
	return match (bytes::peek_token(s)) {
		b: []u8 => from_utf8(b),
		b: []u8 => fromutf8(b),
		void => void,
	};
};


@@ 34,7 34,7 @@ export fn peek_token(s: *tokenizer) (str | void) = {
// Returns the remainder of the string associated with a tokenizer, without doing
// any further tokenization.
export fn remaining_tokens(s: *tokenizer) str = {
	return from_utf8(bytes::remaining_tokens(s));
	return fromutf8(bytes::remaining_tokens(s));
};

@test fn tokenize() void = {

M strings/utf8.ha => strings/utf8.ha +8 -8
@@ 3,7 3,7 @@ use types;

// Converts a byte slice into a string WITHOUT checking that the byte slice is a
// valid UTF-8 string.
export fn from_utf8_unsafe(in: []u8) str = {
export fn fromutf8_unsafe(in: []u8) str = {
	const s = types::string {
		data     = in: *[*]u8,
		length   = len(in),


@@ 15,16 15,16 @@ export fn from_utf8_unsafe(in: []u8) str = {
// Converts a byte slice into a string. Aborts if the bytes contain invalid
// UTF-8. To handle such an error without aborting, see
// [encoding::utf8::decode] instead.
export fn from_utf8(in: []u8) str = {
	let s = from_utf8_unsafe(in);
export fn fromutf8(in: []u8) str = {
	let s = fromutf8_unsafe(in);
	assert(utf8::valid(s), "attempted to load invalid UTF-8 string");
	return s;
};

// Converts a byte slice into a string. If the slice contains invalid UTF-8
// sequences, void is returned instead.
export fn try_from_utf8(in: []u8) (str | utf8::invalid) = {
	let s = from_utf8_unsafe(in);
export fn try_fromutf8(in: []u8) (str | utf8::invalid) = {
	let s = fromutf8_unsafe(in);
	if (!utf8::valid(s)) {
		return utf8::invalid;
	};


@@ 32,11 32,11 @@ export fn try_from_utf8(in: []u8) (str | utf8::invalid) = {
};

// Converts a string to a UTF-8 slice.
export fn to_utf8(in: str) []u8 = *(&in: *[]u8);
export fn toutf8(in: str) []u8 = *(&in: *[]u8);

@test fn utf8() void = {
	assert(from_utf8([
	assert(fromutf8([
		0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64,
	]) == "hello world");
	assert(from_utf8([]) == "");
	assert(fromutf8([]) == "");
};

M strio/dynamic.ha => strio/dynamic.ha +3 -3
@@ 33,7 33,7 @@ export fn finish(s: *io::stream) str = {
	let s = s: *dynamic_stream;
	let buf = s.buf;
	free(s);
	return strings::from_utf8(buf);
	return strings::fromutf8(buf);
};

// Resets the buffer's length to zero, but keeps the allocated memory around for


@@ 70,8 70,8 @@ fn dynamic_close(s: *io::stream) void = {

@test fn dynamic() void = {
	let stream = dynamic();
	io::write(stream, strings::to_utf8("hello ")) as size;
	io::write(stream, strings::to_utf8("world")) as size;
	io::write(stream, strings::toutf8("hello ")) as size;
	io::write(stream, strings::toutf8("world")) as size;
	assert(string(stream) == "hello world");
	let s = finish(stream);
	assert(s == "hello world");

M strio/fixed.ha => strio/fixed.ha +4 -4
@@ 29,11 29,11 @@ export fn string(s: *io::stream) str = {
	if (s.writer == &fixed_write) {
		let stream = s: *fixed_stream;
		const n = len(stream.buf) - len(stream.cur);
		return strings::from_utf8(stream.buf[..n]);
		return strings::fromutf8(stream.buf[..n]);
	} else if (s.writer == &dynamic_write) {
		let s = s: *dynamic_stream;
		let buf = s.buf;
		return strings::from_utf8(buf);
		return strings::fromutf8(buf);
	} else {
		abort("strio::string called on non-strio stream");
	};


@@ 57,7 57,7 @@ fn fixed_close(s: *io::stream) void = {
@test fn fixed() void = {
	static let buf: [1024]u8 = [0...];
	let stream = fixed(buf);
	io::write(stream, strings::to_utf8("hello ")) as size;
	io::write(stream, strings::to_utf8("world")) as size;
	io::write(stream, strings::toutf8("hello ")) as size;
	io::write(stream, strings::toutf8("world")) as size;
	assert(string(stream) == "hello world");
};

M strio/ops.ha => strio/ops.ha +5 -5
@@ 9,7 9,7 @@ export fn concat(st: *io::stream, strs: str...) (size | io::error) = {
	let n = 0z;
	for (let i = 0z; i < len(strs); i += 1) {
		let q = 0z;
		let buf = strings::to_utf8(strs[i]);
		let buf = strings::toutf8(strs[i]);
		for (q < len(buf)) {
			let w = io::write(st, buf[q..])?;
			n += w;


@@ 32,10 32,10 @@ export fn concat(st: *io::stream, strs: str...) (size | io::error) = {
// Returns the number of bytes written, or an error.
export fn join(st: *io::stream, delim: str, strs: str...) (size | io::error) = {
	let n = 0z;
	let delim = strings::to_utf8(delim);
	let delim = strings::toutf8(delim);
	for (let i = 0z; i < len(strs); i += 1) {
		let q = 0z;
		let buf = strings::to_utf8(strs[i]);
		let buf = strings::toutf8(strs[i]);
		for (q < len(buf)) {
			let w = io::write(st, buf[q..])?;
			n += w;


@@ 71,10 71,10 @@ export fn join(st: *io::stream, delim: str, strs: str...) (size | io::error) = {
// efficient if it is. Returns the number of bytes written, or an error.
export fn rjoin(st: *io::stream, delim: str, strs: str...) (size | io::error) = {
	let n = 0z;
	let delim = strings::to_utf8(delim);
	let delim = strings::toutf8(delim);
	for (let i = len(strs); i > 0; i -= 1) {
		let q = 0z;
		let buf = strings::to_utf8(strs[i - 1]);
		let buf = strings::toutf8(strs[i - 1]);
		for (q < len(buf)) {
			let w = io::write(st, buf[q..])?;
			n += w;