commit 10c5e27f2f64f11a50bf994a18428ea8f9ea88a2
parent f4d83fe36175b6aadafedabe7c74f5f6be7133d8
Author: Eyal Sawady <ecs@d2evs.net>
Date: Wed, 24 Mar 2021 01:30:01 -0400
hare::unparse: flesh out
Diffstat:
12 files changed, 675 insertions(+), 35 deletions(-)
diff --git a/cmd/hare/schedule.ha b/cmd/hare/schedule.ha
@@ -2,6 +2,7 @@ use encoding::hex;
use fmt;
use hare::ast;
use hare::module;
+use hare::unparse;
use hash::fnv;
use hash;
use os;
@@ -32,7 +33,7 @@ fn sched_module(plan: *plan, ident: ast::ident, link: *[]*task) *task = {
let ver = match (module::lookup(plan.context, ident)) {
err: module::error => {
- let ident = ast::ident_unparse_s(ident);
+ let ident = unparse::ident_s(ident);
fmt::fatal("Error resolving {}: {}",
ident, module::errstr(err));
},
@@ -156,7 +157,7 @@ fn sched_hare_object(
let output = if (len(namespace) != 0) {
// TODO: consult/update cache manifest
let version = hex::encode(ver.hash);
- let ns = ast::ident_unparse_s(namespace);
+ let ns = unparse::ident_s(namespace);
let env = module::ident_underscore(namespace);
defer free(env);
diff --git a/hare/ast/unparse.ha b/hare/ast/unparse.ha
@@ -1,21 +0,0 @@
-use fmt;
-use io;
-use strio;
-
-// Unparses an identifier.
-export fn ident_unparse(out: *io::stream, ident: ident) (size | io::error) = {
- let n = 0z;
- for (let i = 0z; i < len(ident); i += 1) {
- n += fmt::fprintf(out, "{}{}", ident[i],
- if (i + 1 < len(ident)) "::"
- else "")?;
- };
- return n;
-};
-
-// Unparses an identifier into a string. The caller must free the return value.
-export fn ident_unparse_s(ident: ident) str = {
- let buf = strio::dynamic();
- ident_unparse(buf, ident);
- return strio::finish(buf);
-};
diff --git a/hare/module/manifest.ha b/hare/module/manifest.ha
@@ -2,6 +2,7 @@ use encoding::hex;
use fmt;
use fs;
use hare::ast;
+use hare::unparse;
use io;
use path;
use time;
@@ -31,7 +32,7 @@ export fn manifest_write(ctx: *context, manifest: *manifest) (void | error) = {
let fd = fs::create(ctx.fs, mpath, 0o644)?;
defer io::close(fd);
- let ident = ast::ident_unparse_s(manifest.ident);
+ let ident = unparse::ident_s(manifest.ident);
defer free(ident);
fmt::fprintfln(fd, "# {}", ident)?;
fmt::fprintln(fd, "# This file is an internal Hare implementation detail.")?;
diff --git a/hare/unparse/decl.ha b/hare/unparse/decl.ha
@@ -0,0 +1,192 @@
+use io;
+use fmt;
+use hare::ast;
+use hare::lex;
+use strio;
+
+export fn decl(out: *io::stream, d: ast::decl) (size | io::error) = {
+ let n = 0z;
+ if (d.exported) {
+ n += fmt::fprint(out, "export ")?;
+ };
+ match (d.decl) {
+ g: []ast::decl_global => {
+ n += fmt::fprint(out,
+ if (g[0].is_const) "def " else "let ")?;
+ for (let i = 0z; i < len(g); i += 1) {
+ if (len(g[i].symbol) != 0) {
+ n += fmt::fprintf(out,
+ "@symbol(\"{}\") ", g[i].symbol)?;
+ };
+ n += ident(out, g[i].ident)?;
+ n += fmt::fprint(out, ": ")?;
+ n += _type(out, 0, g[i]._type)?;
+ n += fmt::fprint(out, " = ")?;
+ n += expr(out, 0, g[i].init)?;
+ if (i + 1 < len(g)) {
+ n += fmt::fprint(out, ", ")?;
+ };
+ };
+ },
+ t: []ast::decl_type => {
+ n += fmt::fprint(out, "type ")?;
+ for (let i = 0z; i < len(t); i += 1) {
+ n += ident(out, t[i].ident)?;
+ n += fmt::fprint(out, " = ")?;
+ n += _type(out, 0, t[i]._type)?;
+ if (i + 1 < len(t)) {
+ n += fmt::fprint(out, ", ")?;
+ };
+ };
+ },
+ f: ast::decl_func => {
+ n += fmt::fprint(out, switch (f.attrs) {
+ ast::fndecl_attrs::NONE => "",
+ ast::fndecl_attrs::FINI => "@fini ",
+ ast::fndecl_attrs::INIT => "@init ",
+ ast::fndecl_attrs::TEST => "@test ",
+ })?;
+ let p = f.prototype._type as ast::func_type;
+ if (p.attrs & ast::func_attrs::NORETURN != 0) {
+ n += fmt::fprint(out, "@noreturn ")?;
+ };
+ if (len(f.symbol) != 0) {
+ n += fmt::fprintf(out, "@symbol(\"{}\") ",
+ f.symbol)?;
+ };
+ n += fmt::fprint(out, "fn ")?;
+ n += ident(out, f.ident)?;
+ n += prototype(out, 0,
+ f.prototype._type as ast::func_type)?;
+ match (f.body) {
+ void => void,
+ e: ast::expr => {
+ n += fmt::fprint(out, " = ")?;
+ n += expr(out, 0, e)?;
+ },
+ };
+ },
+ };
+ n += fmt::fprint(out, ";")?;
+ return n;
+};
+
+fn decl_test(d: ast::decl, expected: str) bool = {
+ let buf = strio::dynamic();
+ decl(buf, d) as size;
+ let s = strio::finish(buf);
+ defer free(s);
+ return s == expected;
+};
+
+@test fn decl() void = {
+ let loc = lex::location {
+ path = "<test>",
+ line = 0,
+ col = 0,
+ };
+ let type_int = ast::_type {
+ loc = loc,
+ flags = 0,
+ _type = ast::builtin_type::INT,
+ };
+ let type_fn = ast::_type {
+ loc = loc,
+ flags = ast::type_flags::CONST,
+ _type = ast::func_type {
+ result = &type_int,
+ attrs = ast::func_attrs::NORETURN,
+ variadism = ast::variadism::HARE,
+ params = [
+ ast::func_param {
+ loc = loc,
+ name = "foo",
+ _type = &type_int,
+ },
+ ast::func_param {
+ loc = loc,
+ name = "bar",
+ _type = &type_int,
+ },
+ ],
+ },
+ };
+ let expr_void = void: ast::constant_expr: ast::expr;
+
+ let d = ast::decl {
+ loc = loc,
+ exported = false,
+ decl = [
+ ast::decl_global {
+ is_const = false,
+ symbol = "",
+ ident = ["foo", "bar"],
+ _type = type_int,
+ init = expr_void,
+ },
+ ast::decl_global {
+ is_const = false,
+ symbol = "foobar",
+ ident = ["baz"],
+ _type = type_int,
+ init = expr_void,
+ },
+ ],
+ };
+ assert(decl_test(d, "let foo::bar: int = void, @symbol(\"foobar\") baz: int = void;"));
+
+ d.exported = true;
+ d.decl = [
+ ast::decl_global {
+ is_const = true,
+ ident = ["foo"],
+ _type = type_int,
+ init = expr_void,
+ ...
+ },
+ ];
+ assert(decl_test(d, "export def foo: int = void;"));
+
+ d.exported = false;
+ d.decl = [
+ ast::decl_type {
+ ident = ["foo"],
+ _type = type_int,
+ },
+ ast::decl_type {
+ ident = ["bar"],
+ _type = type_int,
+ },
+ ];
+ assert(decl_test(d, "type foo = int, bar = int;"));
+
+ d.decl = ast::decl_func {
+ symbol = "foo",
+ ident = ["foo"],
+ prototype = type_fn,
+ body = void,
+ attrs = ast::fndecl_attrs::FINI,
+ };
+ assert(decl_test(d, "@fini @noreturn @symbol(\"foo\") fn foo(foo: int, bar: int...) int;"));
+
+ type_fn._type = ast::func_type {
+ result = &type_int,
+ attrs = 0,
+ variadism = ast::variadism::NONE,
+ params = [
+ ast::func_param {
+ loc = loc,
+ name = "",
+ _type = &type_int,
+ },
+ ],
+ };
+ d.decl = ast::decl_func {
+ symbol = "",
+ ident = ["foo"],
+ prototype = type_fn,
+ body = expr_void,
+ attrs = 0,
+ };
+ assert(decl_test(d, "fn foo(_: int) int = void;"));
+};
diff --git a/hare/unparse/expr.ha b/hare/unparse/expr.ha
@@ -0,0 +1,14 @@
+use io;
+use fmt;
+use hare::ast;
+
+// TODO
+export fn expr(
+ out: *io::stream,
+ indent: size,
+ t: ast::expr
+) (size | io::error) = {
+ let e = t as ast::constant_expr;
+ assert(e is void);
+ return fmt::fprint(out, "void")?;
+};
diff --git a/hare/unparse/ident.ha b/hare/unparse/ident.ha
@@ -0,0 +1,31 @@
+use fmt;
+use hare::ast;
+use io;
+use strio;
+
+// Unparses an identifier.
+export fn ident(out: *io::stream, id: ast::ident) (size | io::error) = {
+ let n = 0z;
+ for (let i = 0z; i < len(id); i += 1) {
+ n += fmt::fprintf(out, "{}{}", id[i],
+ if (i + 1 < len(id)) "::"
+ else "")?;
+ };
+ return n;
+};
+
+// Unparses an identifier into a string. The caller must free the return value.
+export fn ident_s(id: ast::ident) str = {
+ let buf = strio::dynamic();
+ ident(buf, id);
+ return strio::finish(buf);
+};
+
+@test fn ident() void = {
+ let s = ident_s(["foo", "bar", "baz"]);
+ assert(s == "foo::bar::baz");
+ free(s);
+ s = ident_s(["foo"]);
+ assert(s == "foo");
+ free(s);
+};
diff --git a/hare/unparse/import.ha b/hare/unparse/import.ha
@@ -0,0 +1,52 @@
+use fmt;
+use io;
+use hare::ast;
+use strio;
+
+export fn import(out: *io::stream, i: ast::import) (size | io::error) = {
+ let n = 0z;
+ n += fmt::fprint(out, "use ")?;
+ match (i) {
+ m: ast::import_module => n += ident(out, m)?,
+ a: ast::import_alias => {
+ n += fmt::fprint(out, a.alias, "= ")?;
+ n += ident(out, a.ident)?;
+ },
+ o: ast::import_objects => {
+ n += ident(out, o.ident)?;
+ n += fmt::fprint(out, "::{")?;
+ for (let i = 0z; i < len(o.objects); i += 1) {
+ n += fmt::fprintf(out, "{}{}", o.objects[i],
+ if (i + 1 < len(o.objects)) ", "
+ else "")?;
+ };
+ n += fmt::fprint(out, "}")?;
+ },
+ };
+ n += fmt::fprint(out, ";")?;
+ return n;
+};
+
+@test fn import() void = {
+ let buf = strio::dynamic();
+ import(buf, ["foo", "bar", "baz"]) as size;
+ let s = strio::finish(buf);
+ assert(s == "use foo::bar::baz;");
+ free(s);
+ buf = strio::dynamic();
+ import(buf, ast::import_alias {
+ ident = ["foo"],
+ alias = "bar",
+ }) as size;
+ s = strio::finish(buf);
+ assert(s == "use bar = foo;");
+ free(s);
+ buf = strio::dynamic();
+ import(buf, ast::import_objects {
+ ident = ["foo"],
+ objects = ["bar", "baz"],
+ }) as size;
+ s = strio::finish(buf);
+ assert(s == "use foo::{bar, baz};");
+ free(s);
+};
diff --git a/hare/unparse/type.ha b/hare/unparse/type.ha
@@ -0,0 +1,293 @@
+use fmt;
+use io;
+use hare::ast;
+use hare::lex;
+use strio;
+
+fn builtin_type(b: ast::builtin_type) str = switch (b) {
+ ast::builtin_type::BOOL => "bool",
+ ast::builtin_type::CHAR => "char",
+ ast::builtin_type::F32 => "f32",
+ ast::builtin_type::F64 => "f64",
+ ast::builtin_type::FCONST => abort("FCONST has no lexical representation"),
+ ast::builtin_type::I16 => "i16",
+ ast::builtin_type::I32 => "i32",
+ ast::builtin_type::I64 => "i64",
+ ast::builtin_type::I8 => "i8",
+ ast::builtin_type::ICONST => abort("ICONST has no lexical representation"),
+ ast::builtin_type::INT => "int",
+ ast::builtin_type::NULL => "null",
+ ast::builtin_type::RUNE => "rune",
+ ast::builtin_type::SIZE => "size",
+ ast::builtin_type::STR => "str",
+ ast::builtin_type::U16 => "u16",
+ ast::builtin_type::U32 => "u32",
+ ast::builtin_type::U64 => "u64",
+ ast::builtin_type::U8 => "u8",
+ ast::builtin_type::UINT => "uint",
+ ast::builtin_type::UINTPTR => "uintptr",
+ ast::builtin_type::VOID => "void",
+};
+
+fn prototype(
+ out: *io::stream,
+ indent: size,
+ t: ast::func_type,
+) (size | io::error) = {
+ let n = 0z;
+ n += fmt::fprint(out, "(")?;
+ for (let i = 0z; i < len(t.params); i += 1) {
+ let param = t.params[i];
+ n += fmt::fprintf(out, "{}: ",
+ if (len(param.name) == 0) "_" else param.name)?;
+ n += _type(out, indent, *param._type)?;
+ if (i + 1 == len(t.params)
+ && t.variadism == ast::variadism::HARE) {
+ n += fmt::fprintf(out, "...")?;
+ };
+ if (i + 1 < len(t.params)) {
+ n += fmt::fprint(out, ", ")?;
+ };
+ };
+ if (t.variadism == ast::variadism::C) {
+ n += fmt::fprint(out, ", ...")?;
+ };
+ n += fmt::fprint(out, ") ")?;
+ n += _type(out, indent, *t.result)?;
+ return n;
+};
+
+export fn _type(
+ out: *io::stream,
+ indent: size,
+ t: ast::_type,
+) (size | io::error) = {
+ let n = 0z;
+ if (t.flags & ast::type_flags::CONST != 0
+ && !(t._type is ast::func_type)) {
+ n += fmt::fprint(out, "const ")?;
+ };
+ match (t._type) {
+ a: ast::alias_type => {
+ if (a.unwrap) {
+ n += fmt::fprint(out, "...")?;
+ };
+ n += ident(out, a.ident)?;
+ },
+ b: ast::builtin_type => n += fmt::fprint(out, builtin_type(b))?,
+ e: ast::enum_type => {
+ n += fmt::fprint(out, "enum",
+ builtin_type(e.storage), "{")?;
+ indent += 1;
+ for (let i = 0z; i < len(e.values); i += 1) {
+ n += newline(out, indent)?;
+ let value = e.values[i];
+ n += fmt::fprint(out, value.name)?;
+ match (value.value) {
+ null => void,
+ e: *ast::expr => {
+ n += fmt::fprint(out, ": ")?;
+ n += expr(out, indent, *e)?;
+ },
+ };
+ n += fmt::fprint(out, ",")?;
+ };
+ indent -= 1;
+ n += newline(out, indent)?;
+ n += fmt::fprint(out, "}")?;
+ },
+ f: ast::func_type => {
+ if (f.attrs & ast::func_attrs::NORETURN != 0) {
+ n += fmt::fprint(out, "@noreturn ")?;
+ };
+ n += fmt::fprint(out, "fn")?;
+ n += prototype(out, indent, f)?;
+ },
+ l: ast::list_type => {
+ n += fmt::fprint(out, "[")?;
+ n += match (l.length) {
+ ast::len_slice => 0,
+ ast::len_unbounded => fmt::fprint(out, "*")?,
+ ast::len_contextual => fmt::fprint(out, "_")?,
+ e: *ast::expr => expr(out, indent, *e)?,
+ };
+ n += fmt::fprint(out, "]")?;
+ n += _type(out, indent, *l.members)?;
+ },
+ p: ast::pointer_type => {
+ if (p.flags & ast::pointer_flags::NULLABLE != 0) {
+ n += fmt::fprint(out, "nullable ")?;
+ };
+ n += fmt::fprint(out, "*")?;
+ n += _type(out, indent, *p.referent)?;
+ },
+ s: []ast::struct_member => abort(), // TODO
+ t: ast::tagged_type => {
+ n += fmt::fprint(out, "(")?;
+ for (let i = 0z; i < len(t); i += 1) {
+ n += _type(out, indent, *t[i])?;
+ if (i + 1 < len(t)) {
+ n += fmt::fprint(out, " | ")?;
+ };
+ };
+ n += fmt::fprint(out, ")")?;
+ },
+ t: ast::tuple_type => {
+ n += fmt::fprint(out, "(")?;
+ for (let i = 0z; i < len(t); i += 1) {
+ n += _type(out, indent, *t[i])?;
+ if (i + 1 < len(t)) {
+ n += fmt::fprint(out, ", ")?;
+ };
+ };
+ n += fmt::fprint(out, ")")?;
+ },
+ };
+ if (t.flags & ast::type_flags::ERROR != 0) {
+ n += fmt::fprint(out, "!")?;
+ };
+ return n;
+};
+
+fn type_test(t: ast::_type, expected: str) bool = {
+ let buf = strio::dynamic();
+ _type(buf, 0, t) as size;
+ let s = strio::finish(buf);
+ defer free(s);
+ return s == expected;
+};
+
+@test fn _type() void = {
+ let loc = lex::location {
+ path = "<test>",
+ line = 0,
+ col = 0,
+ };
+ let t = ast::_type {
+ loc = loc,
+ flags = ast::type_flags::CONST,
+ _type = ast::alias_type {
+ unwrap = false,
+ ident = ["foo", "bar"],
+ },
+ };
+ let type_int = ast::_type {
+ loc = loc,
+ flags = 0,
+ _type = ast::builtin_type::INT,
+ };
+ let expr_void = void: ast::constant_expr: ast::expr;
+
+ assert(type_test(t, "const foo::bar"));
+ t.flags = 0;
+ t._type = ast::alias_type {
+ unwrap = true,
+ ident = ["baz"],
+ };
+ assert(type_test(t, "...baz"));
+
+ t.flags = ast::type_flags::ERROR;
+ t._type = ast::builtin_type::INT;
+ assert(type_test(t, "int!"));
+
+ t.flags = ast::type_flags::CONST | ast::type_flags::ERROR;
+ t._type = ast::enum_type {
+ storage = ast::builtin_type::U32,
+ values = [
+ ast::enum_field {
+ name = "FOO",
+ value = null,
+ },
+ ast::enum_field {
+ name = "BAR",
+ value = &expr_void,
+ },
+ ],
+ };
+ assert(type_test(t, "const enum u32 {\n\tFOO,\n\tBAR: void,\n}!"));
+
+ t.flags = 0;
+
+ t._type = ast::func_type {
+ result = &type_int,
+ attrs = 0,
+ variadism = ast::variadism::NONE,
+ params = [],
+ };
+ assert(type_test(t, "fn() int"));
+ t._type = ast::func_type {
+ result = &type_int,
+ attrs = ast::func_attrs::NORETURN,
+ variadism = ast::variadism::C,
+ params = [
+ ast::func_param {
+ loc = loc,
+ name = "",
+ _type = &type_int,
+ },
+ ],
+ };
+ assert(type_test(t, "@noreturn fn(_: int, ...) int"));
+ t._type = ast::func_type {
+ result = &type_int,
+ attrs = 0,
+ variadism = ast::variadism::HARE,
+ params = [
+ ast::func_param {
+ loc = loc,
+ name = "foo",
+ _type = &type_int,
+ },
+ ast::func_param {
+ loc = loc,
+ name = "bar",
+ _type = &type_int,
+ },
+ ],
+ };
+ assert(type_test(t, "fn(foo: int, bar: int...) int"));
+
+ t.flags = ast::type_flags::CONST;
+ assert(type_test(t, "fn(foo: int, bar: int...) int"));
+
+ t.flags = 0;
+ t._type = ast::list_type {
+ length = ast::len_slice,
+ members = &type_int,
+ };
+ assert(type_test(t, "[]int"));
+ t._type = ast::list_type {
+ length = ast::len_unbounded,
+ members = &type_int,
+ };
+ assert(type_test(t, "[*]int"));
+ t._type = ast::list_type {
+ length = ast::len_contextual,
+ members = &type_int,
+ };
+ assert(type_test(t, "[_]int"));
+ t._type = ast::list_type {
+ length = &expr_void,
+ members = &type_int,
+ };
+ assert(type_test(t, "[void]int"));
+
+ t._type = ast::pointer_type {
+ referent = &type_int,
+ flags = 0,
+ };
+ assert(type_test(t, "*int"));
+ t._type = ast::pointer_type {
+ referent = &type_int,
+ flags = ast::pointer_flags::NULLABLE,
+ };
+ assert(type_test(t, "nullable *int"));
+
+ // TODO: struct_members
+
+ t._type = [&type_int, &type_int]: ast::tagged_type;
+ assert(type_test(t, "(int | int)"));
+
+ t._type = [&type_int, &type_int]: ast::tuple_type;
+ assert(type_test(t, "(int, int)"));
+};
diff --git a/hare/unparse/unit.ha b/hare/unparse/unit.ha
@@ -0,0 +1,16 @@
+use io;
+use fmt;
+use hare::ast;
+
+export fn subunit(out: *io::stream, s: ast::subunit) (size | io::error) = {
+ let n = 0z;
+ for (let i = 0z; i < len(s.imports); i += 1) {
+ n += import(out, s.imports[i])?;
+ n += fmt::fprintln(out)?;
+ };
+ for (let i = 0z; i < len(s.decls); i += 1) {
+ n += decl(out, s.decls[i])?;
+ n += fmt::fprintln(out)?;
+ };
+ return n;
+};
diff --git a/hare/unparse/util.ha b/hare/unparse/util.ha
@@ -0,0 +1,12 @@
+use io;
+use fmt;
+
+fn newline(out: *io::stream, indent: size) (size | io::error) = {
+ let n = 0z;
+ n += fmt::fprint(out, "\n")?;
+ for (let i = 0z; i < indent; i += 1) {
+ n += fmt::fprint(out, "\t")?;
+ };
+ return n;
+};
+
diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib
@@ -249,9 +249,21 @@ hare_ast() {
ident.ha \
import.ha \
type.ha \
+ unit.ha
+ gen_ssa hare::ast hare::lex
+}
+
+hare_unparse() {
+ printf '# hare::unparse\n'
+ gen_srcs hare::unparse \
+ expr.ha \
+ decl.ha \
+ ident.ha \
+ import.ha \
+ type.ha \
unit.ha \
- unparse.ha
- gen_ssa hare::ast hare::lex io fmt strio
+ util.ha
+ gen_ssa hare::unparse fmt io strio hare::ast
}
gensrcs_hare_lex() {
@@ -281,7 +293,7 @@ hare_module() {
scan.ha \
manifest.ha
gen_ssa hare::module \
- hare::ast hare::lex hare::parse strio fs io strings hash \
+ hare::ast hare::lex hare::parse hare::unparse strio fs io strings hash \
crypto::sha256 dirs bytes encoding::utf8 ascii fmt time
}
@@ -533,6 +545,7 @@ hare_ast
hare_lex
hare_module
hare_parse
+hare_unparse
hash
hash_fnv
io
diff --git a/stdlib.mk b/stdlib.mk
@@ -125,6 +125,9 @@ hare_stdlib_deps+=$(stdlib_hare_module)
stdlib_hare_parse=$(HARECACHE)/hare/parse/hare_parse.o
hare_stdlib_deps+=$(stdlib_hare_parse)
+stdlib_hare_unparse=$(HARECACHE)/hare/unparse/hare_unparse.o
+hare_stdlib_deps+=$(stdlib_hare_unparse)
+
stdlib_hash=$(HARECACHE)/hash/hash.o
hare_stdlib_deps+=$(stdlib_hash)
@@ -354,10 +357,9 @@ stdlib_hare_ast_srcs= \
$(STDLIB)/hare/ast/ident.ha \
$(STDLIB)/hare/ast/import.ha \
$(STDLIB)/hare/ast/type.ha \
- $(STDLIB)/hare/ast/unit.ha \
- $(STDLIB)/hare/ast/unparse.ha
+ $(STDLIB)/hare/ast/unit.ha
-$(HARECACHE)/hare/ast/hare_ast.ssa: $(stdlib_hare_ast_srcs) $(stdlib_rt) $(stdlib_hare_lex) $(stdlib_io) $(stdlib_fmt) $(stdlib_strio)
+$(HARECACHE)/hare/ast/hare_ast.ssa: $(stdlib_hare_ast_srcs) $(stdlib_rt) $(stdlib_hare_lex)
@printf 'HAREC \t$@\n'
@mkdir -p $(HARECACHE)/hare/ast
@HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nhare::ast \
@@ -381,7 +383,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_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)
+$(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)
@printf 'HAREC \t$@\n'
@mkdir -p $(HARECACHE)/hare/module
@HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nhare::module \
@@ -399,6 +401,22 @@ $(HARECACHE)/hare/parse/hare_parse.ssa: $(stdlib_hare_parse_srcs) $(stdlib_rt) $
@HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nhare::parse \
-t$(HARECACHE)/hare/parse/hare_parse.td $(stdlib_hare_parse_srcs)
+# hare::unparse
+stdlib_hare_unparse_srcs= \
+ $(STDLIB)/hare/unparse/expr.ha \
+ $(STDLIB)/hare/unparse/decl.ha \
+ $(STDLIB)/hare/unparse/ident.ha \
+ $(STDLIB)/hare/unparse/import.ha \
+ $(STDLIB)/hare/unparse/type.ha \
+ $(STDLIB)/hare/unparse/unit.ha \
+ $(STDLIB)/hare/unparse/util.ha
+
+$(HARECACHE)/hare/unparse/hare_unparse.ssa: $(stdlib_hare_unparse_srcs) $(stdlib_rt) $(stdlib_fmt) $(stdlib_io) $(stdlib_strio) $(stdlib_hare_ast)
+ @printf 'HAREC \t$@\n'
+ @mkdir -p $(HARECACHE)/hare/unparse
+ @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nhare::unparse \
+ -t$(HARECACHE)/hare/unparse/hare_unparse.td $(stdlib_hare_unparse_srcs)
+
# hash
stdlib_hash_srcs= \
$(STDLIB)/hash/hash.ha
@@ -751,6 +769,9 @@ hare_testlib_deps+=$(testlib_hare_module)
testlib_hare_parse=$(TESTCACHE)/hare/parse/hare_parse.o
hare_testlib_deps+=$(testlib_hare_parse)
+testlib_hare_unparse=$(TESTCACHE)/hare/unparse/hare_unparse.o
+hare_testlib_deps+=$(testlib_hare_unparse)
+
testlib_hash=$(TESTCACHE)/hash/hash.o
hare_testlib_deps+=$(testlib_hash)
@@ -982,10 +1003,9 @@ testlib_hare_ast_srcs= \
$(STDLIB)/hare/ast/ident.ha \
$(STDLIB)/hare/ast/import.ha \
$(STDLIB)/hare/ast/type.ha \
- $(STDLIB)/hare/ast/unit.ha \
- $(STDLIB)/hare/ast/unparse.ha
+ $(STDLIB)/hare/ast/unit.ha
-$(TESTCACHE)/hare/ast/hare_ast.ssa: $(testlib_hare_ast_srcs) $(testlib_rt) $(testlib_hare_lex) $(testlib_io) $(testlib_fmt) $(testlib_strio)
+$(TESTCACHE)/hare/ast/hare_ast.ssa: $(testlib_hare_ast_srcs) $(testlib_rt) $(testlib_hare_lex)
@printf 'HAREC \t$@\n'
@mkdir -p $(TESTCACHE)/hare/ast
@HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nhare::ast \
@@ -1010,7 +1030,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_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)
+$(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)
@printf 'HAREC \t$@\n'
@mkdir -p $(TESTCACHE)/hare/module
@HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nhare::module \
@@ -1029,6 +1049,22 @@ $(TESTCACHE)/hare/parse/hare_parse.ssa: $(testlib_hare_parse_srcs) $(testlib_rt)
@HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nhare::parse \
-t$(TESTCACHE)/hare/parse/hare_parse.td $(testlib_hare_parse_srcs)
+# hare::unparse
+testlib_hare_unparse_srcs= \
+ $(STDLIB)/hare/unparse/expr.ha \
+ $(STDLIB)/hare/unparse/decl.ha \
+ $(STDLIB)/hare/unparse/ident.ha \
+ $(STDLIB)/hare/unparse/import.ha \
+ $(STDLIB)/hare/unparse/type.ha \
+ $(STDLIB)/hare/unparse/unit.ha \
+ $(STDLIB)/hare/unparse/util.ha
+
+$(TESTCACHE)/hare/unparse/hare_unparse.ssa: $(testlib_hare_unparse_srcs) $(testlib_rt) $(testlib_fmt) $(testlib_io) $(testlib_strio) $(testlib_hare_ast)
+ @printf 'HAREC \t$@\n'
+ @mkdir -p $(TESTCACHE)/hare/unparse
+ @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nhare::unparse \
+ -t$(TESTCACHE)/hare/unparse/hare_unparse.td $(testlib_hare_unparse_srcs)
+
# hash
testlib_hash_srcs= \
$(STDLIB)/hash/hash.ha