commit c7ac5d879554e12bc5caa75f190c3c4bf9622f2b
parent acee14d7de4a42d4b5a98b0db076e749dbf59988
Author: Sebastian <sebastian@sebsite.pw>
Date: Sat, 30 Sep 2023 23:36:47 -0400
hare::unparse: overhaul with synfunc
Most functions in hare::unparse now take in a 'syn' parameter, which is
a pointer to a function that's called for each thing unparsed. This
gives the user lots of freedom for how the AST should be unparsed.
Two synfuncs are provided in hare::unparse: syn_nowrap and syn_wrap,
which don't do any styling, but the latter wraps long lines.
The bare minimum was done to get haredoc compiling; haredoc is made to
fully take advantage of this in a future commit.
Closes: https://todo.sr.ht/~sircmpwn/hare/842
Signed-off-by: Sebastian <sebastian@sebsite.pw>
Diffstat:
21 files changed, 1075 insertions(+), 566 deletions(-)
diff --git a/cmd/hare/build/util.ha b/cmd/hare/build/util.ha
@@ -152,10 +152,11 @@ fn get_flags(ctx: *context, t: *task) ([]str | error) = {
case null => void;
case let t: *ast::_type =>
memio::concat(&buf, ":")!;
- unparse::_type(&buf, 0, *t)!;
+ unparse::_type(&buf, &unparse::syn_nowrap, *t)!;
};
memio::concat(&buf, "=")!;
- unparse::expr(&buf, 0, *ctx.defines[i].init)!;
+ unparse::expr(&buf, &unparse::syn_nowrap,
+ *ctx.defines[i].init)!;
append(flags, memio::string(&buf)!);
};
diff --git a/cmd/haredoc/doc/hare.ha b/cmd/haredoc/doc/hare.ha
@@ -137,7 +137,8 @@ fn unparse_hare(out: io::handle, d: ast::decl) (size | io::error) = {
yield;
case let ty: *ast::_type =>
n += fmt::fprint(out, ": ")?;
- n += unparse::_type(out, 0, *ty)?;
+ n += unparse::_type(out,
+ &unparse::syn_nowrap, *ty)?;
};
if (i + 1 < len(g)) {
n += fmt::fprint(out, ", ")?;
@@ -148,7 +149,8 @@ fn unparse_hare(out: io::handle, d: ast::decl) (size | io::error) = {
for (let i = 0z; i < len(t); i += 1) {
n += unparse::ident(out, t[i].ident)?;
n += fmt::fprint(out, " = ")?;
- n += unparse::_type(out, 0, t[i]._type)?;
+ n += unparse::_type(out,
+ &unparse::syn_nowrap, t[i]._type)?;
if (i + 1 < len(t)) {
n += fmt::fprint(out, ", ")?;
};
@@ -163,7 +165,8 @@ fn unparse_hare(out: io::handle, d: ast::decl) (size | io::error) = {
yield;
case let ty: *ast::_type =>
n += fmt::fprint(out, ": ")?;
- n += unparse::_type(out, 0, *ty)?;
+ n += unparse::_type(out,
+ &unparse::syn_nowrap, *ty)?;
};
if (i + 1 < len(c)) {
n += fmt::fprint(out, ", ")?;
@@ -187,8 +190,10 @@ fn unparse_hare(out: io::handle, d: ast::decl) (size | io::error) = {
};
n += fmt::fprint(out, "fn ")?;
n += unparse::ident(out, f.ident)?;
- n += unparse::prototype(out, 0,
- f.prototype.repr as ast::func_type)?;
+ n += unparse::prototype(&unparse::context {
+ out = out,
+ ...
+ }, &unparse::syn_nowrap, f.prototype.repr as ast::func_type)?;
};
n += fmt::fprint(out, ";")?;
return n;
diff --git a/cmd/haredoc/doc/html.ha b/cmd/haredoc/doc/html.ha
@@ -517,7 +517,7 @@ fn enum_html(
case null => void;
case let expr: *ast::expr =>
z += fmt::fprint(out, " = ")?;
- z += unparse::expr(out, indent, *expr)?;
+ z += unparse::expr(out, &unparse::syn_nowrap, *expr)?;
};
z += fmt::fprint(out, ",")?;
@@ -570,7 +570,7 @@ fn struct_union_html(
case null => void;
case let expr: *ast::expr =>
z += fmt::fprint(out, "@offset(")?;
- z += unparse::expr(out, indent, *expr)?;
+ z += unparse::expr(out, &unparse::syn_nowrap, *expr)?;
z += fmt::fprint(out, ") ")?;
};
@@ -602,7 +602,7 @@ fn type_html(
if (brief) {
let buf = memio::dynamic();
defer io::close(&buf)!;
- unparse::_type(&buf, indent, _type)?;
+ unparse::_type(&buf, &unparse::syn_nowrap, _type)?;
return html_escape(out, memio::string(&buf)!)?;
};
@@ -718,7 +718,7 @@ fn type_html(
z += fmt::fprint(out, "[")?;
match (t.length) {
case let expr: *ast::expr =>
- z += unparse::expr(out, indent, *expr)?;
+ z += unparse::expr(out, &unparse::syn_nowrap, *expr)?;
case ast::len_slice =>
z += 0;
case ast::len_unbounded =>
@@ -756,8 +756,8 @@ fn prototype_html(
linelen += if (len(t.params) != 0) len(t.params) * 3 - 1 else 0;
for (let i = 0z; i < len(t.params); i += 1) {
const param = t.params[i];
- linelen += unparse::_type(io::empty, indent,
- *param._type)?;
+ linelen += unparse::_type(io::empty,
+ &unparse::syn_nowrap, *param._type)?;
linelen += if (param.name == "") 1 else len(param.name);
};
switch (t.variadism) {
@@ -767,7 +767,8 @@ fn prototype_html(
case variadism::C =>
linelen += 5;
};
- linelen += unparse::_type(io::empty, indent, *t.result)?;
+ linelen += unparse::_type(io::empty,
+ &unparse::syn_nowrap, *t.result)?;
yield linelen;
};
diff --git a/cmd/haredoc/doc/tty.ha b/cmd/haredoc/doc/tty.ha
@@ -276,7 +276,8 @@ fn prototype_tty(
linelen += if (len(t.params) != 0) len(t.params) * 3 - 1 else 0;
for (let i = 0z; i < len(t.params); i += 1) {
const param = t.params[i];
- linelen += unparse::_type(&strm, indent, *param._type)?;
+ linelen += unparse::_type(&strm,
+ &unparse::syn_nowrap, *param._type)?;
typenames[i] = strings::dup(memio::string(&strm)!);
linelen += if (param.name == "") 1 else len(param.name);
memio::reset(&strm);
@@ -393,7 +394,7 @@ fn struct_union_type_tty(
n += render(out, syn::ATTRIBUTE)?;
n += fmt::fprint(out, "@offset(")?;
n += render(out, syn::NUMBER)?;
- n += unparse::expr(out, indent, *ex)?;
+ n += unparse::expr(out, &unparse::syn_nowrap, *ex)?;
n += render(out, syn::ATTRIBUTE)?;
n += fmt::fprint(out, ")")?;
n += render(out, syn::NORMAL)?;
@@ -483,7 +484,8 @@ fn type_tty(
n += render(out, syn::OPERATOR)?;
n += fmt::fprint(out, " = ")?;
n += render(out, syn::NORMAL)?;
- n += unparse::expr(out, indent, *e)?;
+ n += unparse::expr(out,
+ &unparse::syn_nowrap, *e)?;
};
n += render(out, syn::PUNCTUATION)?;
n += fmt::fprint(out, ",")?;
@@ -514,7 +516,7 @@ fn type_tty(
case ast::len_contextual =>
n += fmt::fprint(out, "_")?;
case let e: *ast::expr =>
- n += unparse::expr(out, indent, *e)?;
+ n += unparse::expr(out, &unparse::syn_nowrap, *e)?;
};
n += render(out, syn::OPERATOR)?;
n += fmt::fprint(out, "]")?;
diff --git a/cmd/haretype/main.ha b/cmd/haretype/main.ha
@@ -29,7 +29,7 @@ fn typeinfo(
const atype = parse::_type(&lexer)?;
defer ast::type_finish(atype);
const typ = types::lookup(store, &atype)?;
- unparse::_type(os::stdout, 0, atype)?;
+ unparse::_type(os::stdout, &unparse::syn_nowrap, atype)?;
fmt::println()?;
yield typ;
};
diff --git a/hare/parse/+test/roundtrip.ha b/hare/parse/+test/roundtrip.ha
@@ -42,7 +42,7 @@ fn _roundtrip(src: str) str = {
};
defer ast::subunit_finish(u);
let out = memio::dynamic();
- let z = unparse::subunit(&out, u) as size;
+ let z = unparse::subunit(&out, &unparse::syn_wrap, u)!;
let unsrc = memio::string(&out)!;
assert(z == len(unsrc));
return unsrc;
diff --git a/hare/parse/+test/types.ha b/hare/parse/+test/types.ha
@@ -70,7 +70,8 @@ export type baz = enum rune {
export type bar = (a, b::c, d);
export type baz = (bat, foo::bar::baz, long_type_name, yet_another_very_long_type_name,
- this_spans_multiple_lines, for_readability);
+ this_spans_multiple_lines, for_readability, never_gonna_give_you_up,
+ never_gonna_let_you_down);
");
roundtrip_reparse("export type foo = (int, str,);\n");
};
@@ -81,7 +82,8 @@ export type baz = (bat, foo::bar::baz, long_type_name, yet_another_very_long_typ
export type bar = (a | b::c | ...d);
export type baz = (bat | foo::bar::baz | long_type_name | yet_another_very_long_type_name |
- this_spans_multiple_lines | for_readability);
+ this_spans_multiple_lines | for_readability | never_gonna_give_you_up |
+ never_gonna_let_you_down);
");
};
@@ -93,3 +95,58 @@ export type baz = (bat | foo::bar::baz | long_type_name | yet_another_very_long_
};
");
};
+
+@test fn func() void = {
+ roundtrip("export type foo = fn(int) void;
+
+export type foo = fn(int...) void;
+
+export type foo = fn(int, ...) void;
+
+export type foo = fn(
+ long_param_name: long_type_name,
+ another_one: blablablabla,
+ this_spans: multiple_lines,
+ for_readability: and_stuff,
+) void;
+
+export type foo = fn(
+ long_param_name: long_type_name,
+ another_one: blablablabla,
+ this_spans: multiple_lines,
+ for_readability: and_stuff...
+) void;
+
+export type foo = fn(
+ long_param_name: long_type_name,
+ another_one: blablablabla,
+ this_spans: multiple_lines,
+ for_readability: and_stuff,
+ ...
+) void;
+
+export type foo = fn(
+ long_type_name,
+ blablablabla,
+ multiple_lines,
+ and_stuff,
+) void;
+
+export type foo = fn(
+ long_type_name,
+ blablablabla,
+ multiple_lines,
+ and_stuff...
+) void;
+
+export type foo = fn(
+ long_type_name,
+ blablablabla,
+ multiple_lines,
+ and_stuff,
+ ...
+) void;
+");
+
+ roundtrip_reparse("type foo = fn(int,) void;");
+};
diff --git a/hare/unparse/decl.ha b/hare/unparse/decl.ha
@@ -9,118 +9,176 @@ use memio;
use strings;
// Unparses a [[hare::ast::decl]].
-export fn decl(out: io::handle, d: ast::decl) (size | io::error) = {
+export fn decl(
+ out: io::handle,
+ syn: *synfunc,
+ d: ast::decl,
+) (size | io::error) = {
let n = 0z;
+ let ctx = context {
+ out = out,
+ stack = &stack {
+ cur = &d,
+ ...
+ },
+ ...
+ };
if (len(d.docs) > 0) {
- n += comment(out, d.docs, 0)?;
+ n += comment(&ctx, syn, d.docs)?;
};
if (d.exported) {
- n += fmt::fprint(out, "export ")?;
+ n += syn(&ctx, "export", synkind::KEYWORD)?;
+ n += space(&ctx)?;
};
match (d.decl) {
case let c: []ast::decl_const =>
- n += fmt::fprint(out, "def ")?;
+ n += syn(&ctx, "def", synkind::KEYWORD)?;
+ n += space(&ctx)?;
for (let i = 0z; i < len(c); i += 1) {
- n += ident(out, c[i].ident)?;
+ n += _ident(&ctx, syn, c[i].ident, synkind::CONSTANT)?;
match (c[i]._type) {
case null =>
yield;
case let ty: *ast::_type =>
- n += fmt::fprint(out, ": ")?;
- n += _type(out, 0, *ty)?;
+ n += syn(&ctx, ":", synkind::PUNCTUATION)?;
+ n += space(&ctx)?;
+ n += __type(&ctx, syn, *ty)?;
};
- n += fmt::fprint(out, " = ")?;
- n += expr(out, 0, *c[i].init)?;
+ n += space(&ctx)?;
+ n += syn(&ctx, "=", synkind::OPERATOR)?;
+ n += space(&ctx)?;
+ n += _expr(&ctx, syn, *c[i].init)?;
if (i + 1 < len(c)) {
- n += fmt::fprint(out, ", ")?;
+ n += syn(&ctx, ",", synkind::PUNCTUATION)?;
+ n += space(&ctx)?;
};
};
case let g: []ast::decl_global =>
- n += fmt::fprint(out,
- if (g[0].is_const) "const " else "let ")?;
+ n += syn(&ctx,
+ if (g[0].is_const) "const" else "let",
+ synkind::KEYWORD)?;
+ n += space(&ctx)?;
for (let i = 0z; i < len(g); i += 1) {
if (len(g[i].symbol) != 0) {
- n += fmt::fprintf(out,
- "@symbol(\"{}\") ", g[i].symbol)?;
+ n += syn(&ctx, "@symbol(", synkind::ATTRIBUTE)?;
+ n += constant(&ctx, syn, g[i].symbol)?;
+ n += syn(&ctx, ")", synkind::ATTRIBUTE)?;
+ n += space(&ctx)?;
} else if (g[i].is_threadlocal) {
- n += fmt::fprintf(out, "@threadlocal ")?;
+ n += syn(&ctx, "@threadlocal",
+ synkind::ATTRIBUTE)?;
+ n += space(&ctx)?;
};
- n += ident(out, g[i].ident)?;
+ n += _ident(&ctx, syn, g[i].ident, synkind::GLOBAL)?;
match (g[i]._type) {
case null =>
yield;
case let ty: *ast::_type =>
- n += fmt::fprint(out, ": ")?;
- n += _type(out, 0, *ty)?;
+ n += syn(&ctx, ":", synkind::PUNCTUATION)?;
+ n += space(&ctx)?;
+ n += __type(&ctx, syn, *ty)?;
};
match (g[i].init) {
case null => void;
case let ex: *ast::expr =>
- n += fmt::fprint(out, " = ")?;
- n += expr(out, 0, *ex)?;
+ n += space(&ctx)?;
+ n += syn(&ctx, "=", synkind::OPERATOR)?;
+ n += space(&ctx)?;
+ n += _expr(&ctx, syn, *ex)?;
};
if (i + 1 < len(g)) {
- n += fmt::fprint(out, ", ")?;
+ n += syn(&ctx, ",", synkind::OPERATOR)?;
+ n += space(&ctx)?;
};
};
case let t: []ast::decl_type =>
- n += fmt::fprint(out, "type ")?;
+ n += syn(&ctx, "type", synkind::KEYWORD)?;
+ n += space(&ctx)?;
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)?;
+ n += _ident(&ctx, syn, t[i].ident, synkind::TYPEDEF)?;
+ n += space(&ctx)?;
+ n += syn(&ctx, "=", synkind::OPERATOR)?;
+ n += space(&ctx)?;
+ n += __type(&ctx, syn, t[i]._type)?;
if (i + 1 < len(t)) {
- n += fmt::fprint(out, ", ")?;
+ n += syn(&ctx, ",", synkind::PUNCTUATION)?;
+ n += space(&ctx)?;
};
};
case let f: ast::decl_func =>
- n += fmt::fprint(out, switch (f.attrs) {
- case ast::fndecl_attrs::NONE =>
- yield "";
+ ctx.stack = &stack {
+ cur = &f.prototype,
+ up = ctx.stack,
+ ...
+ };
+ defer {
+ let stack = &(ctx.stack as *stack);
+ match (stack.extra) {
+ case let p: *opaque =>
+ free(p);
+ case null => void;
+ };
+ ctx.stack = stack.up;
+ };
+
+ switch (f.attrs) {
+ case ast::fndecl_attrs::NONE => void;
case ast::fndecl_attrs::FINI =>
- yield "@fini ";
+ n += syn(&ctx, "@fini", synkind::ATTRIBUTE)?;
+ n += space(&ctx)?;
case ast::fndecl_attrs::INIT =>
- yield "@init ";
+ n += syn(&ctx, "@init", synkind::ATTRIBUTE)?;
+ n += space(&ctx)?;
case ast::fndecl_attrs::TEST =>
- yield "@test ";
- })?;
+ n += syn(&ctx, "@test", synkind::ATTRIBUTE)?;
+ n += space(&ctx)?;
+ };
let p = f.prototype.repr as ast::func_type;
if (len(f.symbol) != 0) {
- n += fmt::fprintf(out, "@symbol(\"{}\") ",
- f.symbol)?;
+ n += syn(&ctx, "@symbol(", synkind::ATTRIBUTE)?;
+ n += constant(&ctx, syn, f.symbol)?;
+ n += syn(&ctx, ")", synkind::ATTRIBUTE)?;
+ n += space(&ctx)?;
};
- n += fmt::fprint(out, "fn ")?;
- n += ident(out, f.ident)?;
+ n += syn(&ctx, "fn", synkind::KEYWORD)?;
+ n += space(&ctx)?;
+ n += _ident(&ctx, syn, f.ident, synkind::FUNCTION)?;
const fntype = f.prototype.repr as ast::func_type;
- n += prototype(out, 0, fntype)?;
+ n += prototype(&ctx, syn, fntype)?;
match (f.body) {
case void => void;
case let e: ast::expr =>
- n += fmt::fprint(out, " = ")?;
- n += expr(out, 0, e)?;
+ n += space(&ctx)?;
+ n += syn(&ctx, "=", synkind::OPERATOR)?;
+ n += space(&ctx)?;
+ n += _expr(&ctx, syn, e)?;
};
};
- n += fmt::fprint(out, ";")?;
+ n += syn(&ctx, ";", synkind::PUNCTUATION)?;
return n;
};
-fn comment(out: io::handle, s: str, indent: size) (size | io::error) = {
+fn comment(ctx: *context, syn: *synfunc, s: str) (size | io::error) = {
let n = 0z;
const lines = strings::split(s, "\n");
defer free(lines);
assert(len(lines) > 0);
for (let i = 0z; i < len(lines) - 1; i += 1) {
- for (let j = 0z; j < indent; j += 1) {
- n += fmt::fprint(out, "\t")?;
+ for (let j = 0z; j < ctx.indent; j += 1) {
+ n += fmt::fprint(ctx.out, "\t")?;
+ ctx.linelen += 8;
};
- n += fmt::fprintfln(out, "//{}", lines[i])?;
+ n += syn(ctx, "//", synkind::COMMENT)?;
+ n += syn(ctx, lines[i], synkind::COMMENT)?;
+ n += fmt::fprintln(ctx.out)?;
+ ctx.linelen = 0;
};
return n;
};
fn decl_test(d: ast::decl, expected: str) bool = {
let buf = memio::dynamic();
- decl(&buf, d) as size;
+ decl(&buf, &syn_nowrap, d)!;
let s = memio::string(&buf)!;
defer free(s);
return s == expected;
diff --git a/hare/unparse/expr.ha b/hare/unparse/expr.ha
@@ -12,128 +12,166 @@ use strings;
// Unparses a [[hare::ast::expr]].
export fn expr(
out: io::handle,
- indent: size,
- e: ast::expr
+ syn: *synfunc,
+ e: ast::expr,
) (size | io::error) = {
+ let ctx = context {
+ out = out,
+ ...
+ };
+ return _expr(&ctx, syn, e);
+};
+
+fn _expr(ctx: *context, syn: *synfunc, e: ast::expr) (size | io::error) = {
+ ctx.stack = &stack {
+ cur = &e,
+ up = ctx.stack,
+ ...
+ };
+ defer {
+ let stack = &(ctx.stack as *stack);
+ match (stack.extra) {
+ case let p: *opaque =>
+ free(p);
+ case null => void;
+ };
+ ctx.stack = stack.up;
+ };
+
match (e.expr) {
case let e: ast::access_expr =>
match (e) {
case let id: ast::access_identifier =>
- return ident(out, id);
+ return _ident(ctx, syn, id, synkind::IDENT);
case let ix: ast::access_index =>
let z = 0z;
const needs_parens = !is_postfix(ix.object);
if (needs_parens) {
- z += fmt::fprint(out, "(")?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
};
- z += expr(out, indent, *ix.object)?;
+ z += _expr(ctx, syn, *ix.object)?;
if (needs_parens) {
- z += fmt::fprint(out, ")")?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
};
- z += fmt::fprintf(out, "[")?;
- z += expr(out, indent, *ix.index)?;
- z += fmt::fprintf(out, "]")?;
+ z += syn(ctx, "[", synkind::PUNCTUATION)?;
+ z += _expr(ctx, syn, *ix.index)?;
+ z += syn(ctx, "]", synkind::PUNCTUATION)?;
return z;
case let fi: ast::access_field =>
let z = 0z;
const needs_parens = !is_postfix(fi.object);
if (needs_parens) {
- z += fmt::fprint(out, "(")?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
};
- z += expr(out, indent, *fi.object)?;
+ z += _expr(ctx, syn, *fi.object)?;
if (needs_parens) {
- z += fmt::fprint(out, ")")?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
};
- z += fmt::fprintf(out, ".{}", fi.field)?;
+ z += syn(ctx, ".", synkind::OPERATOR)?;
+ z += syn(ctx, fi.field, synkind::SECONDARY)?;
return z;
case let tp: ast::access_tuple =>
let z = 0z;
const needs_parens = !is_postfix(tp.object);
if (needs_parens) {
- z += fmt::fprint(out, "(")?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
};
- z += expr(out, indent, *tp.object)?;
+ z += _expr(ctx, syn, *tp.object)?;
if (needs_parens) {
- z += fmt::fprint(out, ")")?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
};
- z += fmt::fprintf(out, ".")?;
- z += expr(out, indent, *tp.value)?;
+ z += syn(ctx, ".", synkind::OPERATOR)?;
+ z += _expr(ctx, syn, *tp.value)?;
return z;
};
case let e: ast::align_expr =>
- let z = fmt::fprint(out, "align(")?;
- z += _type(out, indent, *e)?;
- z += fmt::fprint(out, ")")?;
+ let z = syn(ctx, "align", synkind::KEYWORD)?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
+ z += __type(ctx, syn, *e)?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
return z;
case let e: ast::alloc_expr =>
- let z = fmt::fprint(out, "alloc(")?;
- z += expr(out, indent, *e.init)?;
+ let z = syn(ctx, "alloc", synkind::KEYWORD)?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
+ z += _expr(ctx, syn, *e.init)?;
match (e.capacity) {
case null =>
if (e.form == ast::alloc_form::COPY) {
- z += fmt::fprint(out, "...")?;
+ z += syn(ctx, "...", synkind::OPERATOR)?;
};
case let e: *ast::expr =>
- z += fmt::fprint(out, ", ")?;
- z += expr(out, indent, *e)?;
+ z += syn(ctx, ",", synkind::PUNCTUATION)?;
+ z += space(ctx)?;
+ z += _expr(ctx, syn, *e)?;
};
- z += fmt::fprint(out, ")")?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
return z;
case let e: ast::append_expr =>
- let z = if (e.is_static) fmt::fprint(out, "static ")? else 0z;
- z += fmt::fprint(out, "append(")?;
- z += expr(out, indent, *e.object)?;
- z += fmt::fprint(out, ", ")?;
+ let z = 0z;
+ if (e.is_static) {
+ z += syn(ctx, "static", synkind::KEYWORD)?;
+ z += space(ctx)?;
+ };
+ z += syn(ctx, "append", synkind::KEYWORD)?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
+ z += _expr(ctx, syn, *e.object)?;
+ z += syn(ctx, ",", synkind::PUNCTUATION)?;
+ z += space(ctx)?;
for (let i = 0z; i < len(e.values); i += 1) {
let val = e.values[i];
- z += expr(out, indent, *val)?;
+ z += _expr(ctx, syn, *val)?;
if (i + 1 < len(e.values)) {
- z += fmt::fprint(out, ", ")?;
+ z += syn(ctx, ",", synkind::PUNCTUATION)?;
+ z += space(ctx)?;
};
};
match (e.variadic) {
case null => void;
case let v: *ast::expr =>
if (len(e.values) != 0) {
- z += fmt::fprint(out, ", ")?;
+ z += syn(ctx, ",", synkind::PUNCTUATION)?;
+ z += space(ctx)?;
};
- z += expr(out, indent, *v)?;
- z += fmt::fprint(out, "...")?;
+ z += _expr(ctx, syn, *v)?;
+ z += syn(ctx, "...", synkind::OPERATOR)?;
};
- z += fmt::fprint(out, ")")?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
return z;
case let e: ast::assert_expr =>
- let z = fmt::fprint(
- out, if (e.is_static) "static " else "")?;
+ let z = 0z;
+ if (e.is_static) {
+ z += syn(ctx, "static", synkind::KEYWORD)?;
+ z += space(ctx)?;
+ };
// assert without a condition = abort
- z += match (e.cond) {
+ match (e.cond) {
case let e: *ast::expr =>
- yield fmt::fprint(out, "assert(")? +
- expr(out, indent, *e)?;
+ z += syn(ctx, "assert", synkind::KEYWORD)?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
+ z += _expr(ctx, syn, *e)?;
case null =>
- yield fmt::fprint(out, "abort(")?;
+ z += syn(ctx, "abort", synkind::KEYWORD)?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
};
- z += match (e.message) {
+ match (e.message) {
case let m: *ast::expr =>
- let z = 0z;
match (e.cond) {
case null => void;
case *ast::expr =>
- z += fmt::fprint(out, ", ")?;
+ z += syn(ctx, ",", synkind::PUNCTUATION)?;
+ z += space(ctx)?;
};
- z += expr(out, indent, *m)?;
- yield z;
- case null =>
- yield 0;
+ z += _expr(ctx, syn, *m)?;
+ case null => void;
};
- z += fmt::fprint(out, ")")?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
return z;
case let e: ast::assign_expr =>
let z = 0z;
if (e.indirect) {
- z += fmt::fprint(out, "*")?;
+ z += syn(ctx, "*", synkind::OPERATOR)?;
};
- z += expr(out, indent, *e.object)?;
+ z += _expr(ctx, syn, *e.object)?;
const op = match (e.op) {
case void =>
yield "=";
@@ -171,13 +209,16 @@ export fn expr(
abort(); // unreachable
};
};
- z += fmt::fprintf(out, " {} ", op)?;
- z += expr(out, indent, *e.value)?;
+ z += space(ctx)?;
+ z += syn(ctx, op, synkind::OPERATOR)?;
+ z += space(ctx)?;
+ z += _expr(ctx, syn, *e.value)?;
return z;
case let e: ast::binarithm_expr =>
const prec = binprecedence(e.op);
- let z = binexprval(out, indent, *e.lvalue, prec)?;
- z += fmt::fprintf(out, " {} ", switch (e.op) {
+ let z = binexprval(ctx, syn, *e.lvalue, prec)?;
+ z += space(ctx)?;
+ z += syn(ctx, switch (e.op) {
case binarithm_op::BAND =>
yield "&";
case binarithm_op::BOR =>
@@ -216,246 +257,296 @@ export fn expr(
yield "*";
case binarithm_op::BXOR =>
yield "^";
- })?;
- z += binexprval(out, indent, *e.rvalue, prec)?;
+ }, synkind::OPERATOR)?;
+ z += space(ctx)?;
+ z += binexprval(ctx, syn, *e.rvalue, prec)?;
return z;
case let e: ast::binding_expr =>
- let z = fmt::fprintf(out, "{}{}",
- if (e.is_static) "static " else "",
- if (e.is_const) "const " else "let ")?;
+ let z = 0z;
+ if (e.is_static) {
+ z += syn(ctx, "static", synkind::KEYWORD)?;
+ z += space(ctx)?;
+ };
+ z += syn(ctx, if (e.is_const) "const" else "let",
+ synkind::KEYWORD)?;
+ z += space(ctx)?;
for (let i = 0z; i < len(e.bindings); i += 1) {
let binding = e.bindings[i];
match (binding.name) {
case let s: str =>
- z += fmt::fprint(out, s)?;
+ z += syn(ctx, s, synkind::IDENT)?;
case let u: ast::binding_unpack =>
- z += fmt::fprint(out, "(")?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
for (let i = 0z; i < len(u); i += 1) {
match (u[i]) {
case let s: str =>
- z += fmt::fprint(out, s)?;
+ z += syn(ctx, s,
+ synkind::IDENT)?;
case void =>
- z += fmt::fprint(out, "_")?;
+ z += syn(ctx, "_",
+ synkind::OPERATOR)?;
};
if (i + 1 < len(u)) {
- z += fmt::fprint(out, ", ")?;
+ z += syn(ctx, ",",
+ synkind::PUNCTUATION)?;
+ z += space(ctx)?;
};
};
- z += fmt::fprint(out, ")")?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
};
- z += match (binding._type) {
+ match (binding._type) {
case let t: *ast::_type =>
- let z = 0z;
- z += fmt::fprintf(out, ": ")?;
- z += _type(out, indent, *t)?;
- yield z;
- case null =>
- yield 0z;
+ z += syn(ctx, ":", synkind::PUNCTUATION)?;
+ z += space(ctx)?;
+ z += __type(ctx, syn, *t)?;
+ case null => void;
};
- z += fmt::fprint(out, " = ")?;
- z += expr(out, indent, *binding.init)?;
+ z += space(ctx)?;
+ z += syn(ctx, "=", synkind::OPERATOR)?;
+ z += space(ctx)?;
+ z += _expr(ctx, syn, *binding.init)?;
if (i + 1 < len(e.bindings)) {
- z += fmt::fprint(out, ", ")?;
+ z += syn(ctx, ",", synkind::PUNCTUATION)?;
+ z += space(ctx)?;
};
};
return z;
case let e: ast::break_expr =>
- let z = fmt::fprint(out, "break")?;
+ let z = syn(ctx, "break", synkind::KEYWORD)?;
if (e != "") {
- z += fmt::fprintf(out, " :{}", e)?;
+ z += space(ctx)?;
+ z += syn(ctx, ":", synkind::LABEL)?;
+ z += syn(ctx, e, synkind::LABEL)?;
};
return z;
case let e: ast::call_expr =>
let z = 0z;
const needs_parens = !is_postfix(e.lvalue);
if (needs_parens) {
- z += fmt::fprint(out, "(")?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
};
- z += expr(out, indent, *e.lvalue)?;
+ z += _expr(ctx, syn, *e.lvalue)?;
if (needs_parens) {
- z += fmt::fprint(out, ")")?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
};
- z += fmt::fprintf(out, "(")?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
for (let i = 0z; i < len(e.args); i += 1) {
- z += expr(out, indent, *e.args[i])?;
+ z += _expr(ctx, syn, *e.args[i])?;
if (i + 1 < len(e.args)) {
- z += fmt::fprintf(out, ", ")?;
+ z += syn(ctx, ",", synkind::PUNCTUATION)?;
+ z += space(ctx)?;
};
};
if (e.variadic) {
- z += fmt::fprintf(out, "...")?;
+ z += syn(ctx, "...", synkind::OPERATOR)?;
};
- z += fmt::fprintf(out, ")")?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
return z;
case let e: ast::cast_expr =>
let z = 0z;
const needs_parens = !is_cast(e.value);
if (needs_parens) {
- z += fmt::fprint(out, "(")?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
};
- z += expr(out, indent, *e.value)?;
+ z += _expr(ctx, syn, *e.value)?;
if (needs_parens) {
- z += fmt::fprint(out, ")")?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
};
- const op = switch (e.kind) {
+ switch (e.kind) {
case ast::cast_kind::CAST =>
- yield ": ";
+ z += syn(ctx, ":", synkind::OPERATOR)?;
+ z += space(ctx)?;
case ast::cast_kind::ASSERTION =>
- yield " as ";
+ z += space(ctx)?;
+ z += syn(ctx, "as", synkind::OPERATOR)?;
+ z += space(ctx)?;
case ast::cast_kind::TEST =>
- yield " is ";
+ z += space(ctx)?;
+ z += syn(ctx, "is", synkind::OPERATOR)?;
+ z += space(ctx)?;
};
- z += fmt::fprintf(out, "{}", op)?;
- z += _type(out, indent, *e._type)?;
+ z += __type(ctx, syn, *e._type)?;
return z;
case let e: ast::constant_expr =>
- return constant(out, indent, e)?;
+ return constant(ctx, syn, e)?;
case let e: ast::continue_expr =>
- let z = fmt::fprint(out, "continue")?;
+ let z = syn(ctx, "continue", synkind::KEYWORD)?;
if (e != "") {
- z += fmt::fprintf(out, " :{}", e)?;
+ z += space(ctx)?;
+ z += syn(ctx, ":", synkind::LABEL)?;
+ z += syn(ctx, e, synkind::LABEL)?;
};
return z;
case let e: ast::defer_expr =>
- return fmt::fprint(out, "defer ")? + expr(out, indent, *e)?;
+ let z = syn(ctx, "defer", synkind::KEYWORD)?;
+ z += space(ctx)?;
+ z += _expr(ctx, syn, *e)?;
+ return z;
case let e: ast::delete_expr =>
- let z = if (e.is_static) fmt::fprint(out, "static ")? else 0z;
- z += fmt::fprint(out, "delete(")?;
- z += expr(out, indent, *e.object)?;
- z += fmt::fprint(out, ")")?;
+ let z = 0z;
+ if (e.is_static) {
+ z += syn(ctx, "static", synkind::KEYWORD)?;
+ z += space(ctx)?;
+ };
+ z += syn(ctx, "delete", synkind::KEYWORD)?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
+ z += _expr(ctx, syn, *e.object)?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
return z;
case let e: ast::error_assert_expr =>
let z = 0z;
const needs_parens = !is_postfix(e);
if (needs_parens) {
- z += fmt::fprint(out, "(")?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
};
- z += expr(out, indent, *e)?;
+ z += _expr(ctx, syn, *e)?;
if (needs_parens) {
- z += fmt::fprint(out, ")")?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
};
- z += fmt::fprint(out, "!")?;
+ z += syn(ctx, "!", synkind::OPERATOR)?;
return z;
case let e: ast::for_expr =>
- return for_expr(out, indent, e)?;
+ return for_expr(ctx, syn, e)?;
case let e: ast::free_expr =>
- return fmt::fprint(out, "free(")?
- + expr(out, indent, *e)?
- + fmt::fprint(out, ")")?;
+ let z = syn(ctx, "free", synkind::KEYWORD)?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
+ z += _expr(ctx, syn, *e)?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
+ return z;
case let e: ast::if_expr =>
- let z = fmt::fprint(out, "if (")?;
- z += expr(out, indent, *e.cond)?;
- z += fmt::fprint(out, ") ")?;
- z += expr(out, indent, *e.tbranch)?;
+ let z = syn(ctx, "if", synkind::KEYWORD)?;
+ z += space(ctx)?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
+ z += _expr(ctx, syn, *e.cond)?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
+ z += space(ctx)?;
+ z += _expr(ctx, syn, *e.tbranch)?;
match (e.fbranch) {
case null => void;
case let e: *ast::expr =>
- z += fmt::fprint(out, " else ")?;
- z += expr(out, indent, *e)?;
+ z += space(ctx)?;
+ z += syn(ctx, "else", synkind::KEYWORD)?;
+ z += space(ctx)?;
+ z += _expr(ctx, syn, *e)?;
};
return z;
case let e: ast::insert_expr =>
- let z = if (e.is_static) fmt::fprint(out, "static ")? else 0z;
- z += fmt::fprint(out, "insert(")?;
- z += expr(out, indent, *e.object)?;
- z += fmt::fprint(out, ", ")?;
+ let z = 0z;
+ if (e.is_static) {
+ z += syn(ctx, "static", synkind::KEYWORD)?;
+ z += space(ctx)?;
+ };
+ z += syn(ctx, "insert", synkind::KEYWORD)?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
+ z += _expr(ctx, syn, *e.object)?;
+ z += syn(ctx, ",", synkind::PUNCTUATION)?;
+ z += space(ctx)?;
for (let i = 0z; i < len(e.values); i += 1) {
let val = e.values[i];
- z += expr(out, indent, *val)?;
+ z += _expr(ctx, syn, *val)?;
if (i + 1 < len(e.values)) {
- z += fmt::fprint(out, ", ")?;
+ z += syn(ctx, ",", synkind::PUNCTUATION)?;
+ z += space(ctx)?;
};
};
match (e.variadic) {
case null => void;
case let v: *ast::expr =>
if (len(e.values) != 0) {
- z += fmt::fprint(out, ", ")?;
+ z += syn(ctx, ",", synkind::PUNCTUATION)?;
+ z += space(ctx)?;
};
- z += expr(out, indent, *v)?;
- z += fmt::fprint(out, "...")?;
+ z += _expr(ctx, syn, *v)?;
+ z += syn(ctx, "...", synkind::OPERATOR)?;
};
- z += fmt::fprint(out, ")")?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
return z;
case let e: ast::compound_expr =>
let z = 0z;
if (e.label != "") {
- z += fmt::fprintf(out, ":{} ", e.label)?;
+ z += syn(ctx, ":", synkind::LABEL)?;
+ z += syn(ctx, e.label, synkind::LABEL)?;
+ z += space(ctx)?;
};
- z += fmt::fprintf(out, "{{")?;
+ z += syn(ctx, "{", synkind::PUNCTUATION)?;
+ ctx.indent += 1;
for (let i = 0z; i < len(e.exprs); i += 1) {
- z += newline(out, indent + 1)?;
- z += stmt(out, indent + 1, *e.exprs[i])?;
+ z += newline(ctx)?;
+ z += stmt(ctx, syn, *e.exprs[i])?;
};
- z += newline(out, indent)?;
- z += fmt::fprintf(out, "}}")?;
+ ctx.indent -= 1;
+ z += newline(ctx)?;
+ z += syn(ctx, "}", synkind::PUNCTUATION)?;
return z;
case let e: ast::match_expr =>
- return match_expr(out, indent, e)?;
+ return match_expr(ctx, syn, e)?;
case let e: ast::len_expr =>
- let z = fmt::fprint(out, "len(")?;
- z += expr(out, indent, *e)?;
- z += fmt::fprint(out, ")")?;
+ let z = syn(ctx, "len", synkind::KEYWORD)?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
+ z += _expr(ctx, syn, *e)?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
return z;
case let e: ast::size_expr =>
- let z = fmt::fprint(out, "size(")?;
- z += _type(out, indent, *e)?;
- z += fmt::fprint(out, ")")?;
+ let z = syn(ctx, "size", synkind::KEYWORD)?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
+ z += __type(ctx, syn, *e)?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
return z;
case let e: ast::offset_expr =>
- let z = fmt::fprint(out, "offset(")?;
- z += expr(out, indent, *e)?;
- z += fmt::fprint(out, ")")?;
+ let z = syn(ctx, "offset", synkind::KEYWORD)?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
+ z += _expr(ctx, syn, *e)?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
return z;
case let e: ast::propagate_expr =>
let z = 0z;
const needs_parens = !is_postfix(e);
if (needs_parens) {
- z += fmt::fprint(out, "(")?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
};
- z += expr(out, indent, *e)?;
+ z += _expr(ctx, syn, *e)?;
if (needs_parens) {
- z += fmt::fprint(out, ")")?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
};
- z += fmt::fprint(out, "?")?;
+ z += syn(ctx, "?", synkind::OPERATOR)?;
return z;
case let e: ast::return_expr =>
- let z = fmt::fprint(out, "return")?;
+ let z = syn(ctx, "return", synkind::KEYWORD)?;
match (e) {
case null => void;
case let e: *ast::expr =>
- z += fmt::fprint(out, " ")?;
- z += expr(out, indent, *e)?;
+ z += space(ctx)?;
+ z += _expr(ctx, syn, *e)?;
};
return z;
case let e: ast::slice_expr =>
let z = 0z;
const needs_parens = !is_postfix(e.object);
if (needs_parens) {
- z += fmt::fprint(out, "(")?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
};
- z += expr(out, indent, *e.object)?;
+ z += _expr(ctx, syn, *e.object)?;
if (needs_parens) {
- z += fmt::fprint(out, ")")?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
};
- z += fmt::fprint(out, "[")?;
+ z += syn(ctx, "[", synkind::PUNCTUATION)?;
match (e.start) {
case null => void;
case let e: *ast::expr =>
- z += expr(out, indent, *e)?;
+ z += _expr(ctx, syn, *e)?;
};
- z += fmt::fprint(out, "..")?;
+ z += syn(ctx, "..", synkind::OPERATOR)?;
match (e.end) {
case null => void;
case let e: *ast::expr =>
- z += expr(out, indent, *e)?;
+ z += _expr(ctx, syn, *e)?;
};
- z += fmt::fprint(out, "]")?;
+ z += syn(ctx, "]", synkind::PUNCTUATION)?;
return z;
case let e: ast::switch_expr =>
- return switch_expr(out, indent, e)?;
+ return switch_expr(ctx, syn, e)?;
case let e: ast::unarithm_expr =>
- let z = fmt::fprintf(out, "{}", switch (e.op) {
+ let z = syn(ctx, switch (e.op) {
case ast::unarithm_op::ADDR =>
yield "&";
case ast::unarithm_op::BNOT =>
@@ -466,7 +557,7 @@ export fn expr(
yield "!";
case ast::unarithm_op::MINUS =>
yield "-";
- })?;
+ }, synkind::OPERATOR)?;
const needs_parens = match (e.operand.expr) {
case let inner: ast::unarithm_expr =>
yield e.op == ast::unarithm_op::ADDR
@@ -475,39 +566,48 @@ export fn expr(
yield !is_unary(e.operand);
};
if (needs_parens) {
- z += fmt::fprint(out, "(")?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
};
- z += expr(out, indent, *e.operand)?;
+ z += _expr(ctx, syn, *e.operand)?;
if (needs_parens) {
- z += fmt::fprint(out, ")")?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
};
return z;
case let e: ast::variadic_expr =>
match (e) {
case ast::vastart_expr =>
- return fmt::fprint(out, "vastart()")?;
+ let z = syn(ctx, "vastart", synkind::KEYWORD)?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
+ return z;
case let e: ast::vaarg_expr =>
- let z = fmt::fprint(out, "vaarg(")?;
- z += expr(out, indent, *e)?;
- z += fmt::fprint(out, ")")?;
+ let z = syn(ctx, "vaarg", synkind::KEYWORD)?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
+ z += _expr(ctx, syn, *e)?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
return z;
case let e: ast::vaend_expr =>
- let z = fmt::fprint(out, "vaend(")?;
- z += expr(out, indent, *e)?;
- z += fmt::fprint(out, ")")?;
+ let z = syn(ctx, "vaend", synkind::KEYWORD)?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
+ z += _expr(ctx, syn, *e)?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
return z;
};
case let e: ast::yield_expr =>
- let z = fmt::fprint(out, "yield")?;
+ let z = syn(ctx, "yield", synkind::KEYWORD)?;
if (e.label != "") {
- z += fmt::fprintf(out, " :{}", e.label)?;
+ z += space(ctx)?;
+ z += syn(ctx, ":", synkind::LABEL)?;
+ z += syn(ctx, e.label, synkind::LABEL)?;
};
match (e.value) {
case null => void;
case let v: *ast::expr =>
- z += fmt::fprint(out, if (e.label == "")
- " " else ", ")?;
- z += expr(out, indent, *v)?;
+ if (e.label != "") {
+ z += syn(ctx, ",", synkind::PUNCTUATION)?;
+ };
+ z += space(ctx)?;
+ z += _expr(ctx, syn, *v)?;
};
return z;
};
@@ -542,8 +642,8 @@ fn binprecedence(op: binarithm_op) uint = {
};
fn binexprval(
- out: io::handle,
- indent: size,
+ ctx: *context,
+ syn: *synfunc,
e: ast::expr,
prec: uint,
) (size | io::error) = {
@@ -551,75 +651,79 @@ fn binexprval(
match (e.expr) {
case let b: ast::binarithm_expr =>
if (binprecedence(b.op) < prec) {
- z += fmt::fprint(out, "(")?;
- z += expr(out, indent, e)?;
- z += fmt::fprint(out, ")")?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
+ z += _expr(ctx, syn, e)?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
return z;
};
case => void;
};
const needs_parens = !is_cast(&e) && !(e.expr is ast::binarithm_expr);
if (needs_parens) {
- z += fmt::fprint(out, "(")?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
};
- z += expr(out, indent, e)?;
+ z += _expr(ctx, syn, e)?;
if (needs_parens) {
- z += fmt::fprint(out, ")")?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
};
return z;
};
-fn stmt(
- out: io::handle,
- indent: size,
- e: ast::expr
-) (size | io::error) = {
- let n = 0z;
- n += expr(out, indent, e)?;
- n += fmt::fprint(out, ";")?;
+fn stmt(ctx: *context, syn: *synfunc, e: ast::expr) (size | io::error) = {
+ let n = _expr(ctx, syn, e)?;
+ n += syn(ctx, ";", synkind::PUNCTUATION)?;
return n;
};
fn constant(
- out: io::handle,
- indent: size,
+ ctx: *context,
+ syn: *synfunc,
e: ast::constant_expr,
) (size | io::error) = {
match (e) {
case void =>
- return fmt::fprint(out, "void");
+ return syn(ctx, "void", synkind::KEYWORD)?;
case let v: ast::value =>
- return fmt::fprint(out, match (v) {
+ match (v) {
case void => abort();
case ast::_null =>
- yield "null";
+ return syn(ctx, "null", synkind::KEYWORD)?;
case let b: bool =>
- return fmt::fprint(out, b);
+ return syn(ctx, if (b) "true" else "false",
+ synkind::KEYWORD)?;
case let s: str =>
const s = strings::multireplace(s,
(`\`, `\\`), (`"`, `\"`));
defer free(s);
- return fmt::fprintf(out, `"{}"`, s);
+ const s = fmt::asprintf(`"{}"`, s);
+ defer free(s);
+ return syn(ctx, s, synkind::RUNE_STRING)?;
case let r: rune =>
+ let buf: [4]u8 = [0...];
if (r == '\'' || r == '\\') {
- return fmt::fprintf(out, `'\{}'`, r);
+ return syn(ctx, fmt::bsprintf(buf, `'\{}'`, r),
+ synkind::RUNE_STRING)?;
} else {
- return fmt::fprintf(out, "'{}'", r);
+ return syn(ctx, fmt::bsprintf(buf, "'{}'", r),
+ synkind::RUNE_STRING)?;
};
- });
+ };
case let ac: ast::array_constant =>
- let z = fmt::fprint(out, "[")?;
+ let z = syn(ctx, "[", synkind::PUNCTUATION)?;
for (let i = 0z; i < len(ac.values); i += 1) {
- z += expr(out, indent, *ac.values[i])?;
+ z += _expr(ctx, syn, *ac.values[i])?;
if (i + 1 < len(ac.values)) {
- z += fmt::fprint(out, ", ")?;
+ z += syn(ctx, ",", synkind::PUNCTUATION)?;
+ z += space(ctx)?;
};
};
- z += fmt::fprintf(out, "{}]",
- if (ac.expand) "..." else "")?;
+ if (ac.expand) {
+ z += syn(ctx, "...", synkind::OPERATOR)?;
+ };
+ z += syn(ctx, "]", synkind::PUNCTUATION)?;
return z;
case let v: ast::number_constant =>
- return fmt::fprintf(out, "{}{}", v.value, switch (v.suff) {
+ const s = fmt::asprintf("{}{}", v.value, switch (v.suff) {
case ltok::LIT_U8 =>
yield "u8";
case ltok::LIT_U16 =>
@@ -650,164 +754,195 @@ fn constant(
yield "f64";
case => abort();
});
+ defer free(s);
+ return syn(ctx, s, synkind::NUMBER)?;
case let sc: ast::struct_constant =>
- return struct_constant(out, indent, sc)?;
+ return struct_constant(ctx, syn, sc)?;
case let tu: ast::tuple_constant =>
- let z = fmt::fprint(out, "(")?;
+ let z = syn(ctx, "(", synkind::PUNCTUATION)?;
for (let i = 0z; i < len(tu); i += 1) {
- z += expr(out, indent, *tu[i])?;
+ z += _expr(ctx, syn, *tu[i])?;
if (i + 1 < len(tu)) {
- z += fmt::fprint(out, ", ")?;
+ z += syn(ctx, ",", synkind::PUNCTUATION)?;
+ z += space(ctx)?;
};
};
- z += fmt::fprint(out, ")")?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
return z;
};
};
fn struct_constant(
- out: io::handle,
- indent: size,
+ ctx: *context,
+ syn: *synfunc,
sc: ast::struct_constant,
) (size | io::error) = {
let z = 0z;
z += if (len(sc.alias) != 0) {
- yield ident(out, sc.alias)?;
+ yield _ident(ctx, syn, sc.alias, synkind::IDENT)?;
} else {
- yield fmt::fprint(out, "struct")?;
+ yield syn(ctx, "struct", synkind::KEYWORD)?;
};
- z += fmt::fprint(out, " {")?;
- indent += 1;
+ z += space(ctx)?;
+ z += syn(ctx, "{", synkind::PUNCTUATION)?;
+ ctx.indent += 1;
for (let i = 0z; i < len(sc.fields); i += 1) {
- z += newline(out, indent)?;
+ z += newline(ctx)?;
match (sc.fields[i]) {
case let sv: ast::struct_value =>
+ z += syn(ctx, sv.name, synkind::SECONDARY)?;
match (sv._type) {
- case null =>
- z += fmt::fprintf(out, "{}", sv.name)?;
+ case null => void;
case let t: *ast::_type =>
- z += fmt::fprintf(out, "{}: ", sv.name)?;
- z += _type(out, indent, *t)?;
+ z += syn(ctx, ":", synkind::PUNCTUATION)?;
+ z += space(ctx)?;
+ z += __type(ctx, syn, *t)?;
};
- z += fmt::fprint(out, " = ")?;
- z += expr(out, indent, *sv.init)?;
+ z += space(ctx)?;
+ z += syn(ctx, "=", synkind::OPERATOR)?;
+ z += space(ctx)?;
+ z += _expr(ctx, syn, *sv.init)?;
case let sc: *ast::struct_constant =>
- z += constant(out, indent, *sc)?;
+ z += constant(ctx, syn, *sc)?;
};
- z += fmt::fprint(out, ",")?;
+ z += syn(ctx, ",", synkind::PUNCTUATION)?;
};
if (sc.autofill) {
- z += newline(out, indent)?;
- z += fmt::fprint(out, "...")?;
+ z += newline(ctx)?;
+ z += syn(ctx, "...", synkind::OPERATOR)?;
};
- indent -= 1;
- z += newline(out, indent)?;
- z += fmt::fprint(out, "}")?;
+ ctx.indent -= 1;
+ z += newline(ctx)?;
+ z += syn(ctx, "}", synkind::PUNCTUATION)?;
return z;
};
fn for_expr(
- out: io::handle,
- indent: size,
+ ctx: *context,
+ syn: *synfunc,
e: ast::for_expr,
) (size | io::error) = {
- let z = fmt::fprintf(out, "for (")?;
+ let z = syn(ctx, "for", synkind::KEYWORD)?;
+ z += space(ctx)?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
match (e.bindings) {
case null => void;
case let e: *ast::expr =>
- z += expr(out, indent, *e)?;
- z += fmt::fprint(out, "; ")?;
+ z += _expr(ctx, syn, *e)?;
+ z += syn(ctx, ";", synkind::PUNCTUATION)?;
+ z += space(ctx)?;
};
- z += expr(out, indent, *e.cond)?;
+ z += _expr(ctx, syn, *e.cond)?;
match (e.afterthought) {
case null => void;
case let e: *ast::expr =>
- z += fmt::fprint(out, "; ")?;
- z += expr(out, indent, *e)?;
+ z += syn(ctx, ";", synkind::PUNCTUATION)?;
+ z += space(ctx)?;
+ z += _expr(ctx, syn, *e)?;
};
- z += fmt::fprintf(out, ") ")?;
- return z + expr(out, indent, *e.body)?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
+ z += space(ctx)?;
+ z += _expr(ctx, syn, *e.body)?;
+ return z;
};
fn switch_expr(
- out: io::handle,
- indent: size,
+ ctx: *context,
+ syn: *synfunc,
e: ast::switch_expr,
) (size | io::error) = {
- let z = fmt::fprint(out, "switch (")?;
- z += expr(out, indent, *e.value)?;
- z += fmt::fprint(out, ") {")?;
+ let z = syn(ctx, "switch", synkind::KEYWORD)?;
+ z += space(ctx)?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
+ z += _expr(ctx, syn, *e.value)?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
+ z += space(ctx)?;
+ z += syn(ctx, "{", synkind::PUNCTUATION)?;
for (let i = 0z; i < len(e.cases); i += 1) {
- z += newline(out, indent)?;
+ z += newline(ctx)?;
const item = e.cases[i];
- z += fmt::fprint(out, "case ")?;
+ z += syn(ctx, "case", synkind::KEYWORD)?;
+ z += space(ctx)?;
if (len(item.options) == 0) {
- z += fmt::fprint(out, "=>")?;
+ z += syn(ctx, "=>", synkind::OPERATOR)?;
} else {
for (let j = 0z; j < len(item.options); j += 1) {
const opt = item.options[j];
- z += expr(out, indent, *opt)?;
+ z += _expr(ctx, syn, *opt)?;
if (j + 1 < len(item.options)) {
- z += fmt::fprint(out, ", ")?;
+ z += syn(ctx, ",",
+ synkind::PUNCTUATION)?;
+ z += space(ctx)?;
};
};
- z += fmt::fprint(out, " =>")?;
+ z += space(ctx)?;
+ z += syn(ctx, "=>", synkind::OPERATOR)?;
};
- z += case_exprs(out, indent + 1, item.exprs)?;
+ z += case_exprs(ctx, syn, item.exprs)?;
};
- z += newline(out, indent)?;
- z += fmt::fprint(out, "}")?;
+ z += newline(ctx)?;
+ z += syn(ctx, "}", synkind::PUNCTUATION)?;
return z;
};
fn match_expr(
- out: io::handle,
- indent: size,
+ ctx: *context,
+ syn: *synfunc,
e: ast::match_expr,
) (size | io::error) = {
- let z = fmt::fprint(out, "match (")?;
- z += expr(out, indent, *e.value)?;
- z += fmt::fprint(out, ") {")?;
+ let z = syn(ctx, "match", synkind::KEYWORD)?;
+ z += space(ctx)?;
+ z += syn(ctx, "(", synkind::PUNCTUATION)?;
+ z += _expr(ctx, syn, *e.value)?;
+ z += syn(ctx, ")", synkind::PUNCTUATION)?;
+ z += space(ctx)?;
+ z += syn(ctx, "{", synkind::PUNCTUATION)?;
for (let i = 0z; i < len(e.cases); i += 1) {
- z += newline(out, indent)?;
- z += fmt::fprint(out, "case")?;
+ z += newline(ctx)?;
+ z += syn(ctx, "case", synkind::KEYWORD)?;
const item = e.cases[i];
if (len(item.name) > 0) {
- z += fmt::fprintf(out, " let {}", item.name)?;
+ z += space(ctx)?;
+ z += syn(ctx, "let", synkind::KEYWORD)?;
+ z += space(ctx)?;
+ z += syn(ctx, item.name, synkind::IDENT)?;
};
match (item._type) {
case let typ: *ast::_type =>
if (len(item.name) > 0) {
- z += fmt::fprint(out, ":")?;
+ z += syn(ctx, ":", synkind::PUNCTUATION)?;
};
- z += fmt::fprint(out, " ")?;
- z += _type(out, indent, *typ)?;
+ z += space(ctx)?;
+ z += __type(ctx, syn, *typ)?;
case null => void;
};
- z += fmt::fprint(out, " =>")?;
- z += case_exprs(out, indent + 1, item.exprs)?;
+ z += space(ctx)?;
+ z += syn(ctx, "=>", synkind::OPERATOR)?;
+ z += case_exprs(ctx, syn, item.exprs)?;
};
if (len(e.default) != 0) {
- z += newline(out, indent)?;
- z += fmt::fprint(out, "case =>")?;
- z += case_exprs(out, indent + 1, e.default)!;
+ z += newline(ctx)?;
+ z += syn(ctx, "case", synkind::KEYWORD)?;
+ z += space(ctx)?;
+ z += syn(ctx, "=>", synkind::OPERATOR)?;
+ z += case_exprs(ctx, syn, e.default)?;
};
- z += newline(out, indent)?;
- z += fmt::fprint(out, "}")?;
+ z += newline(ctx)?;
+ z += syn(ctx, "}", synkind::PUNCTUATION)?;
return z;
};
fn case_exprs(
- out: io::handle,
- indent: size,
+ ctx: *context,
+ syn: *synfunc,
exprs: []*ast::expr,
) (size | io::error) = {
let z = 0z;
@@ -816,18 +951,33 @@ fn case_exprs(
case let e: ast::assert_expr =>
if (e.cond == null) {
// abort() expression
- z += fmt::fprint(out, " ")?;
- z += stmt(out, indent, *exprs[0])?;
+ z += space(ctx)?;
+ z += stmt(ctx, syn, *exprs[0])?;
return z;
};
case let e: ast::value =>
- if (e is void) return fmt::fprint(out, " void;")?;
+ if (e is void) {
+ z += space(ctx)?;
+ {
+ ctx.stack = &stack {
+ cur = exprs[0],
+ up = ctx.stack,
+ ...
+ };
+ defer ctx.stack = (ctx.stack as *stack).up;
+ z += syn(ctx, "void", synkind::KEYWORD)?;
+ };
+ z += syn(ctx, ";", synkind::PUNCTUATION)?;
+ return z;
+ };
case => void;
};
+ ctx.indent += 1;
for (let j = 0z; j < len(exprs); j += 1) {
- z += newline(out, indent)?;
- z += stmt(out, indent, *exprs[j])?;
+ z += newline(ctx)?;
+ z += stmt(ctx, syn, *exprs[j])?;
};
+ ctx.indent -= 1;
return z;
};
diff --git a/hare/unparse/ident.ha b/hare/unparse/ident.ha
@@ -8,11 +8,25 @@ use memio;
// Unparses an identifier.
export fn ident(out: io::handle, id: ast::ident) (size | io::error) = {
+ let ctx = context {
+ out = out,
+ ...
+ };
+ return _ident(&ctx, &syn_nowrap, id, synkind::IDENT);
+};
+
+fn _ident(
+ ctx: *context,
+ syn: *synfunc,
+ id: ast::ident,
+ kind: synkind,
+) (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 "")?;
+ n += syn(ctx, id[i], kind)?;
+ if (i + 1 < len(id)) {
+ n += syn(ctx, "::", kind)?;
+ };
};
return n;
};
diff --git a/hare/unparse/import.ha b/hare/unparse/import.ha
@@ -7,31 +7,54 @@ use hare::ast;
use memio;
// Unparses a [[hare::ast::import]].
-export fn import(out: io::handle, import: ast::import) (size | io::error) = {
+export fn import(
+ out: io::handle,
+ syn: *synfunc,
+ import: ast::import,
+) (size | io::error) = {
let n = 0z;
- n += fmt::fprint(out, "use ")?;
+ let ctx = context {
+ out = out,
+ stack = &stack {
+ cur = &import,
+ ...
+ },
+ ...
+ };
+ n += syn(&ctx, "use", synkind::KEYWORD)?;
+ n += space(&ctx)?;
if (import.mode & ast::import_mode::ALIAS != 0) {
- n += fmt::fprint(out, import.alias, "= ")?;
+ n += syn(&ctx, import.alias, synkind::IMPORT_ALIAS)?;
+ n += space(&ctx)?;
+ n += syn(&ctx, "=", synkind::OPERATOR)?;
+ n += space(&ctx)?;
};
- n += ident(out, import.ident)?;
+ n += _ident(&ctx, syn, import.ident, synkind::IDENT)?;
if (import.mode & ast::import_mode::MEMBERS != 0) {
- n += fmt::fprint(out, "::{")?;
+ n += syn(&ctx, "::", synkind::IDENT)?;
+ n += syn(&ctx, "{", synkind::PUNCTUATION)?;
for (let i = 0z; i < len(import.objects); i += 1) {
- let tup = match (import.objects[i].0) {
- case void =>
- yield ("", "");
+ match (import.objects[i].0) {
case let s: str =>
- yield (s, " = ");
+ n += syn(&ctx, s, synkind::IMPORT_ALIAS)?;
+ n += space(&ctx)?;
+ n += syn(&ctx, "=", synkind::OPERATOR)?;
+ n += space(&ctx)?;
+ case void => void;
+ };
+ n += syn(&ctx, import.objects[i].1,
+ synkind::SECONDARY)?;
+ if (i + 1 < len(import.objects)) {
+ n += syn(&ctx, ",", synkind::PUNCTUATION)?;
+ n += space(&ctx)?;
};
- n += fmt::fprintf(out, "{}{}{}{}", tup.0, tup.1,
- import.objects[i].1,
- if (i + 1 < len(import.objects)) ", " else "")?;
};
- n += fmt::fprint(out, "}")?;
+ n += syn(&ctx, "}", synkind::PUNCTUATION)?;
} else if (import.mode & ast::import_mode::WILDCARD != 0) {
- n += fmt::fprint(out, "::*")?;
+ n += syn(&ctx, "::", synkind::IDENT)?;
+ n += syn(&ctx, "*", synkind::IDENT)?;
};
- n += fmt::fprint(out, ";")?;
+ n += syn(&ctx, ";", synkind::PUNCTUATION)?;
return n;
};
@@ -81,7 +104,7 @@ export fn import(out: io::handle, import: ast::import) (size | io::error) = {
];
for (let i = 0z; i < len(tests); i += 1) {
let buf = memio::dynamic();
- import(&buf, tests[i].0) as size;
+ import(&buf, &syn_nowrap, tests[i].0)!;
let s = memio::string(&buf)!;
assert(s == tests[i].1);
free(s);
diff --git a/hare/unparse/syn.ha b/hare/unparse/syn.ha
@@ -0,0 +1,231 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use fmt;
+use hare::ast;
+use io;
+
+// A user-supplied function which writes unparsed Hare source code to a handle,
+// optionally including extra stylistic features. The function is expected to
+// write to, at the minimum, write the provided string to ctx.out, and update
+// ctx.linelen based on how much data was written.
+//
+// [[syn_nowrap]] and [[syn_wrap]] are provided for when no additional styling
+// is desired.
+export type synfunc = fn(
+ ctx: *context,
+ s: str,
+ kind: synkind,
+) (size | io::error);
+
+// The kind of thing being unparsed.
+export type synkind = enum {
+ IDENT,
+ COMMENT,
+ CONSTANT,
+ FUNCTION,
+ GLOBAL,
+ TYPEDEF,
+ IMPORT_ALIAS,
+ SECONDARY,
+ KEYWORD,
+ TYPE,
+ ATTRIBUTE,
+ OPERATOR,
+ PUNCTUATION,
+ RUNE_STRING,
+ NUMBER,
+ LABEL,
+};
+
+// Context about the unparsing state supplied to a [[synfunc]]. The linelen and
+// indent fields may be mutated.
+export type context = struct {
+ out: io::handle,
+ stack: nullable *stack,
+ linelen: size,
+ indent: size,
+};
+
+// A linked list of AST nodes currently being unparsed.
+export type stack = struct {
+ cur: (*ast::decl | *ast::expr | *ast::_type | *ast::import),
+ up: nullable *stack,
+ extra: nullable *opaque,
+};
+
+// A [[synfunc]] implementation which unparses without additional styling, and
+// without wrapping any long lines.
+export fn syn_nowrap(
+ ctx: *context,
+ s: str,
+ kind: synkind,
+) (size | io::error) = {
+ const z = fmt::fprint(ctx.out, s)?;
+ ctx.linelen += z;
+ return z;
+};
+
+type syn_wrap_extra = enum {
+ NONE,
+ MULTILINE_FN_PARAM,
+ MULTILINE_FN_OTHER,
+ MULTILINE_TAGGED_OR_TUPLE,
+};
+
+// A [[synfunc]] implementation which unparses without additional styling, but
+// which wraps some long lines at 80 columns, in accordance with the style
+// guide.
+export fn syn_wrap(ctx: *context, s: str, kind: synkind) (size | io::error) = {
+ let extra = :extra {
+ let st = match (ctx.stack) {
+ case let st: *stack =>
+ yield st;
+ case null =>
+ yield :extra, &syn_wrap_extra::NONE;
+ };
+
+ match (st.extra) {
+ case let p: *opaque =>
+ yield :extra, p: *syn_wrap_extra;
+ case null =>
+ match (st.up) {
+ case let st: *stack =>
+ match (st.extra) {
+ case let p: *opaque =>
+ const p = p: *syn_wrap_extra;
+ if (*p == syn_wrap_extra::MULTILINE_FN_PARAM) {
+ yield :extra, p;
+ };
+ case null => void;
+ };
+ case null => void;
+ };
+ };
+
+ if (s == "(") match (st.cur) {
+ case let t: *ast::_type =>
+ match (t.repr) {
+ case ast::func_type => void;
+ case =>
+ yield :extra, &syn_wrap_extra::NONE;
+ };
+
+ let z = _type(io::empty, &syn_nowrap, *t)!;
+ if (ctx.linelen + z < 80) yield;
+ st.extra = alloc(syn_wrap_extra::MULTILINE_FN_PARAM);
+ z = fmt::fprintln(ctx.out, s)?;
+ ctx.linelen = 0;
+ ctx.indent += 1;
+ return z;
+ case =>
+ yield :extra, &syn_wrap_extra::NONE;
+ };
+
+ // use 72 as max linelen instead of 80 to give a bit of leeway.
+ // XXX: this probably could be made more accurate
+ if (ctx.linelen < 72 || (s != "," && s != "|")) {
+ yield :extra, &syn_wrap_extra::NONE;
+ };
+
+ const t = match (st.cur) {
+ case let t: *ast::_type =>
+ yield t;
+ case =>
+ yield :extra, &syn_wrap_extra::NONE;
+ };
+
+ match (t.repr) {
+ case (ast::tagged_type | ast::tuple_type) => void;
+ case =>
+ yield :extra, &syn_wrap_extra::NONE;
+ };
+
+ st.extra = alloc(syn_wrap_extra::MULTILINE_TAGGED_OR_TUPLE);
+ let z = fmt::fprintln(ctx.out, s)?;
+ ctx.indent += 1;
+ ctx.linelen = ctx.indent * 8;
+ for (let i = 0z; i < ctx.indent; i += 1) {
+ z += fmt::fprint(ctx.out, "\t")?;
+ };
+ return z;
+ };
+
+ let z = 0z;
+
+ switch (*extra) {
+ case syn_wrap_extra::NONE => void;
+ case syn_wrap_extra::MULTILINE_FN_PARAM =>
+ switch (s) {
+ case ")" =>
+ match (ctx.stack) {
+ case let st: *stack =>
+ free(st.extra);
+ st.extra = null;
+ case null => void;
+ };
+ ctx.indent -= 1;
+ case "..." =>
+ match (ctx.stack) {
+ case let st: *stack =>
+ free(st.extra);
+ st.extra = null;
+ case null => void;
+ };
+ for (let i = 0z; i < ctx.indent; i += 1) {
+ z += fmt::fprint(ctx.out, "\t")?;
+ };
+ z += fmt::fprintln(ctx.out, s)?;
+ ctx.indent -= 1;
+ ctx.linelen = 0;
+ return z;
+ case =>
+ *extra = syn_wrap_extra::MULTILINE_FN_OTHER;
+ ctx.linelen = ctx.indent * 8;
+ for (let i = 0z; i < ctx.indent; i += 1) {
+ z += fmt::fprint(ctx.out, "\t")?;
+ };
+ };
+ case syn_wrap_extra::MULTILINE_FN_OTHER =>
+ switch (s) {
+ case ")" =>
+ match (ctx.stack) {
+ case let st: *stack =>
+ free(st.extra);
+ st.extra = null;
+ case null => void;
+ };
+ ctx.indent -= 1;
+ ctx.linelen = ctx.indent * 8;
+ z += fmt::fprintln(ctx.out, ",")?;
+ for (let i = 0z; i < ctx.indent; i += 1) {
+ z += fmt::fprint(ctx.out, "\t")?;
+ };
+ case ",", "..." =>
+ *extra = syn_wrap_extra::MULTILINE_FN_PARAM;
+ ctx.linelen = 0;
+ return fmt::fprintln(ctx.out, s)?;
+ case => void;
+ };
+ case syn_wrap_extra::MULTILINE_TAGGED_OR_TUPLE =>
+ switch (s) {
+ case ")" =>
+ let st = ctx.stack as *stack;
+ free(st.extra);
+ st.extra = null;
+ ctx.indent -= 1;
+ case ",", "|" =>
+ if (ctx.linelen < 72) yield;
+ z += fmt::fprintln(ctx.out, s)?;
+ ctx.linelen = ctx.indent * 8;
+ for (let i = 0z; i < ctx.indent; i += 1) {
+ z += fmt::fprint(ctx.out, "\t")?;
+ };
+ return z;
+ case => void;
+ };
+ };
+
+ z += syn_nowrap(ctx, s, kind)?;
+ return z;
+};
diff --git a/hare/unparse/type.ha b/hare/unparse/type.ha
@@ -62,151 +62,98 @@ case ast::builtin_type::VOID =>
// Unparses a prototype.
export fn prototype(
- out: io::handle,
- indent: size,
+ ctx: *context,
+ syn: *synfunc,
t: ast::func_type,
) (size | io::error) = {
let n = 0z;
- n += fmt::fprint(out, "(")?;
-
- let typenames: []str = [];
- // TODO: https://todo.sr.ht/~sircmpwn/hare/581
- if (len(t.params) > 0) {
- typenames = alloc([""...], len(t.params));
- };
- defer strings::freeall(typenames);
- let retname = "";
- defer free(retname);
+ n += syn(ctx, "(", synkind::PUNCTUATION)?;
- // estimate length of prototype to determine if it should span multiple
- // lines
- const linelen = if (len(t.params) == 0) {
- let strm = memio::dynamic();
- defer io::close(&strm)!;
- _type(&strm, indent, *t.result)?;
- retname = strings::dup(memio::string(&strm)!);
- yield 0z; // only use one line if there's no parameters
- } else {
- let strm = memio::dynamic();
- defer io::close(&strm)!;
- let linelen = indent * 8 + 5;
- linelen += if (len(t.params) != 0) len(t.params) * 3 - 1 else 0;
- for (let i = 0z; i < len(t.params); i += 1) {
- const param = t.params[i];
- linelen += _type(&strm, indent, *param._type)?;
- typenames[i] = strings::dup(memio::string(&strm)!);
- linelen += if (param.name == "") -2 else len(param.name);
- memio::reset(&strm);
+ for (let i = 0z; i < len(t.params); i += 1) {
+ const param = t.params[i];
+ if (param.name != "") {
+ n += syn(ctx, param.name, synkind::SECONDARY)?;
+ n += syn(ctx, ":", synkind::PUNCTUATION)?;
+ n += space(ctx)?;
};
- switch (t.variadism) {
- case variadism::NONE => void;
- case variadism::HARE =>
- linelen += 3;
- case variadism::C =>
- linelen += 5;
+ n += __type(ctx, syn, *param._type)?;
+ if (i + 1 < len(t.params) || t.variadism == variadism::C) {
+ n += syn(ctx, ",", synkind::PUNCTUATION)?;
+ n += space(ctx)?;
};
- linelen += _type(&strm, indent, *t.result)?;
- retname = strings::dup(memio::string(&strm)!);
- yield linelen;
};
-
- // use 72 instead of 80 to give a bit of leeway for preceding text
- if (linelen > 72) {
- indent += 1;
- for (let i = 0z; i < len(t.params); i += 1) {
- const param = t.params[i];
- n += newline(out, indent)?;
- if (param.name != "") {
- n += fmt::fprintf(out, "{}: ", param.name)?;
- };
- n += fmt::fprint(out, typenames[i])?;
- if (i + 1 == len(t.params)
- && t.variadism == variadism::HARE) {
- n += fmt::fprint(out, "...")?;
- } else {
- n += fmt::fprint(out, ",")?;
- };
- };
- if (t.variadism == variadism::C) {
- n += newline(out, indent)?;
- n += fmt::fprint(out, "...")?;
- };
- indent -= 1;
- n += newline(out, indent)?;
- } else {
- for (let i = 0z; i < len(t.params); i += 1) {
- const param = t.params[i];
- if (param.name != "") {
- n += fmt::fprintf(out, "{}: ", param.name)?;
- };
- n += fmt::fprint(out, typenames[i])?;
- if (i + 1 < len(t.params) || t.variadism == variadism::C) {
- n += fmt::fprint(out, ", ")?;
- };
- };
- if (t.variadism != variadism::NONE) {
- n += fmt::fprint(out, "...")?;
- };
+ if (t.variadism != variadism::NONE) {
+ n += syn(ctx, "...", synkind::OPERATOR)?;
};
- n += fmt::fprint(out, ")", retname)?;
+ n += syn(ctx, ")", synkind::PUNCTUATION)?;
+ n += space(ctx)?;
+ n += __type(ctx, syn, *t.result)?;
return n;
};
-// Unparses a struct or union type.
fn struct_union_type(
- out: io::handle,
- indent: size,
+ ctx: *context,
+ syn: *synfunc,
t: ast::_type,
) (size | io::error) = {
let z = 0z;
let membs = match (t.repr) {
case let st: ast::struct_type =>
- z += fmt::fprint(out, "struct ")?;
+ z += syn(ctx, "struct", synkind::TYPE)?;
+ z += space(ctx)?;
if (st.packed) {
- z += fmt::fprint(out, "@packed ")?;
+ z += syn(ctx, "@packed", synkind::ATTRIBUTE)?;
+ z += space(ctx)?;
};
- z += fmt::fprint(out, "{")?;
+ z += syn(ctx, "{", synkind::PUNCTUATION)?;
yield st.members: []ast::struct_member;
case let ut: ast::union_type =>
- z += fmt::fprint(out, "union {")?;
+ z += syn(ctx, "union", synkind::TYPE)?;
+ z += space(ctx)?;
+ z += syn(ctx, "{", synkind::PUNCTUATION)?;
yield ut: []ast::struct_member;
};
- indent += 1z;
+ ctx.indent += 1z;
for (let i = 0z; i < len(membs); i += 1) {
- z += fmt::fprintln(out)?;
+ z += fmt::fprintln(ctx.out)?;
+ ctx.linelen = 0;
if (membs[i].docs != "") {
- z += comment(out, membs[i].docs, indent)?;
+ z += comment(ctx, syn, membs[i].docs)?;
};
- for (let i = 0z; i < indent; i += 1) {
- z += fmt::fprint(out, "\t")?;
+ for (let i = 0z; i < ctx.indent; i += 1) {
+ z += fmt::fprint(ctx.out, "\t")?;
+ ctx.linelen += 8;
};
match (membs[i]._offset) {
case null => void;
case let ex: *ast::expr =>
- z += fmt::fprint(out, "@offset(")?;
- z += expr(out, indent, *ex)?;
- z += fmt::fprint(out, ") ")?;
+ z += syn(ctx, "@offset(", synkind::ATTRIBUTE)?;
+ z += _expr(ctx, syn, *ex)?;
+ z += syn(ctx, ")", synkind::ATTRIBUTE)?;
+ z += space(ctx)?;
};
match (membs[i].member) {
case let se: ast::struct_embedded =>
- z += _type(out, indent, *se)?;
+ z += __type(ctx, syn, *se)?;
case let sa: ast::struct_alias =>
- z += ident(out, sa)?;
+ z += _ident(ctx, syn, sa, synkind::IDENT)?;
case let sf: ast::struct_field =>
- z += fmt::fprintf(out, "{}: ", sf.name)?;
- z += _type(out, indent, *sf._type)?;
+ z += syn(ctx, sf.name, synkind::SECONDARY)?;
+ z += syn(ctx, ":", synkind::PUNCTUATION)?;
+ z += space(ctx)?;
+ z += __type(ctx, syn, *sf._type)?;
};
- z += fmt::fprint(out, ",")?;
+ z += syn(ctx, ",", synkind::PUNCTUATION)?;
};
- indent -= 1;
- z += newline(out, indent)?;
- z += fmt::fprint(out, "}")?;
+ ctx.indent -= 1;
+ z += newline(ctx)?;
+ z += syn(ctx, "}", synkind::PUNCTUATION)?;
return z;
};
@@ -216,33 +163,59 @@ fn multiline_comment(s: str) bool =
// Unparses a [[hare::ast::_type]].
export fn _type(
out: io::handle,
- indent: size,
+ syn: *synfunc,
t: ast::_type,
) (size | io::error) = {
+ let ctx = context {
+ out = out,
+ ...
+ };
+ return __type(&ctx, syn, t);
+};
+
+fn __type(ctx: *context, syn: *synfunc, t: ast::_type) (size | io::error) = {
+ ctx.stack = &stack {
+ cur = &t,
+ up = ctx.stack,
+ ...
+ };
+ defer {
+ let stack = &(ctx.stack as *stack);
+ match (stack.extra) {
+ case let p: *opaque =>
+ free(p);
+ case null => void;
+ };
+ ctx.stack = stack.up;
+ };
+
let n = 0z;
if (t.flags & ast::type_flag::CONST != 0) {
- n += fmt::fprint(out, "const ")?;
+ n += syn(ctx, "const", synkind::TYPE)?;
+ n += space(ctx)?;
};
if (t.flags & ast::type_flag::ERROR != 0) {
- n += fmt::fprint(out, "!")?;
+ n += syn(ctx, "!", synkind::TYPE)?;
};
match (t.repr) {
case let a: ast::alias_type =>
if (a.unwrap) {
- n += fmt::fprint(out, "...")?;
+ n += syn(ctx, "...", synkind::TYPE)?;
};
- n += ident(out, a.ident)?;
+ n += _ident(ctx, syn, a.ident, synkind::TYPE)?;
case let b: ast::builtin_type =>
- n += fmt::fprint(out, builtin_type(b))?;
+ n += syn(ctx, builtin_type(b), synkind::TYPE)?;
case let e: ast::enum_type =>
+ n += syn(ctx, "enum", synkind::TYPE)?;
+ n += space(ctx)?;
if (e.storage != ast::builtin_type::INT) {
- n += fmt::fprint(out, "enum",
- builtin_type(e.storage), "{")?;
- } else {
- n += fmt::fprint(out, "enum {")?;
+ n += syn(ctx, builtin_type(e.storage), synkind::TYPE)?;
+ n += space(ctx)?;
};
- indent += 1;
- n += fmt::fprintln(out)?;
+ n += syn(ctx, "{", synkind::PUNCTUATION)?;
+ ctx.indent += 1;
+ n += fmt::fprintln(ctx.out)?;
+ ctx.linelen = 0;
for (let i = 0z; i < len(e.values); i += 1) {
let value = e.values[i];
let wrotedocs = false;
@@ -250,114 +223,94 @@ export fn _type(
// Check if comment should go above or next to
// field
if (multiline_comment(value.docs)) {
- n += comment(out, value.docs, indent)?;
+ n += comment(ctx, syn, value.docs)?;
wrotedocs = true;
};
};
- for (let i = 0z; i < indent; i += 1) {
- n += fmt::fprint(out, "\t")?;
+ for (let i = 0z; i < ctx.indent; i += 1) {
+ n += fmt::fprint(ctx.out, "\t")?;
+ ctx.linelen += 8;
};
- n += fmt::fprint(out, value.name)?;
+ n += syn(ctx, value.name, synkind::SECONDARY)?;
match (value.value) {
case null => void;
case let e: *ast::expr =>
- n += fmt::fprint(out, " = ")?;
- n += expr(out, indent, *e)?;
+ n += space(ctx)?;
+ n += syn(ctx, "=", synkind::OPERATOR)?;
+ n += space(ctx)?;
+ n += _expr(ctx, syn, *e)?;
};
- n += fmt::fprint(out, ",")?;
+ n += syn(ctx, ",", synkind::PUNCTUATION)?;
if (value.docs != "" && !wrotedocs) {
- n += fmt::fprint(out, " ")?;
- n += comment(out, value.docs, 0)?;
+ n += space(ctx)?;
+ const oldindent = ctx.indent;
+ ctx.indent = 0;
+ n += comment(ctx, syn, value.docs)?;
+ ctx.indent = oldindent;
} else {
- n += fmt::fprintln(out)?;
+ n += fmt::fprintln(ctx.out)?;
+ ctx.linelen = 0;
};
};
- indent -= 1;
- for (let i = 0z; i < indent; i += 1) {
- n += fmt::fprint(out, "\t")?;
+ ctx.indent -= 1;
+ for (let i = 0z; i < ctx.indent; i += 1) {
+ n += fmt::fprint(ctx.out, "\t")?;
+ ctx.linelen += 8;
};
- n += fmt::fprint(out, "}")?;
+ n += syn(ctx, "}", synkind::PUNCTUATION)?;
case let f: ast::func_type =>
- n += fmt::fprint(out, "fn")?;
- n += prototype(out, indent, f)?;
+ n += syn(ctx, "fn", synkind::TYPE)?;
+ n += prototype(ctx, syn, f)?;
case let l: ast::list_type =>
- n += fmt::fprint(out, "[")?;
+ n += syn(ctx, "[", synkind::TYPE)?;
match (l.length) {
case ast::len_slice => void;
case ast::len_unbounded =>
- n += fmt::fprint(out, "*")?;
+ n += syn(ctx, "*", synkind::TYPE)?;
case ast::len_contextual =>
- n += fmt::fprint(out, "_")?;
+ n += syn(ctx, "_", synkind::TYPE)?;
case let e: *ast::expr =>
- n += expr(out, indent, *e)?;
+ n += _expr(ctx, syn, *e)?;
};
- n += fmt::fprint(out, "]")?;
- n += _type(out, indent, *l.members)?;
+ n += syn(ctx, "]", synkind::TYPE)?;
+ n += __type(ctx, syn, *l.members)?;
case let p: ast::pointer_type =>
if (p.flags & ast::pointer_flag::NULLABLE != 0) {
- n += fmt::fprint(out, "nullable ")?;
+ n += syn(ctx, "nullable", synkind::TYPE)?;
+ n += space(ctx)?;
};
- n += fmt::fprint(out, "*")?;
- n += _type(out, indent, *p.referent)?;
+ n += syn(ctx, "*", synkind::TYPE)?;
+ n += __type(ctx, syn, *p.referent)?;
case ast::struct_type =>
- n += struct_union_type(out, indent, t)?;
+ n += struct_union_type(ctx, syn, t)?;
case ast::union_type =>
- n += struct_union_type(out, indent, t)?;
+ n += struct_union_type(ctx, syn, t)?;
case let t: ast::tagged_type =>
- // rough estimate of current line length
- let linelen = n;
- n = 0;
- linelen += fmt::fprint(out, "(")?;
+ n += syn(ctx, "(", synkind::TYPE)?;
for (let i = 0z; i < len(t); i += 1) {
- linelen += _type(out, indent, *t[i])?;
+ n += __type(ctx, syn, *t[i])?;
if (i + 1 == len(t)) break;
- linelen += fmt::fprint(out, " |")?;
- // use 72 instead of 80 to give a bit of leeway for long
- // type names
- if (linelen + (indent + 1) * 8 > 72) {
- n += linelen;
- linelen = 0;
- n += fmt::fprintln(out)?;
- for (let i = 0z; i <= indent; i += 1) {
- n += fmt::fprint(out, "\t")?;
- };
- } else {
- linelen += fmt::fprint(out, " ")?;
- };
+ n += space(ctx)?;
+ n += syn(ctx, "|", synkind::TYPE)?;
+ n += space(ctx)?;
};
- n += linelen;
- n += fmt::fprint(out, ")")?;
+ n += syn(ctx, ")", synkind::TYPE)?;
case let t: ast::tuple_type =>
- // rough estimate of current line length
- let linelen = n;
- n = 0;
- linelen += fmt::fprint(out, "(")?;
+ n += syn(ctx, "(", synkind::TYPE)?;
for (let i = 0z; i < len(t); i += 1) {
- linelen += _type(out, indent, *t[i])?;
+ n += __type(ctx, syn, *t[i])?;
if (i + 1 == len(t)) break;
- linelen += fmt::fprint(out, ",")?;
- // use 72 instead of 80 to give a bit of leeway for long
- // type names
- if (linelen + (indent + 1) * 8 > 72) {
- n += linelen;
- linelen = 0;
- n += fmt::fprintln(out)?;
- for (let i = 0z; i <= indent; i += 1) {
- n += fmt::fprint(out, "\t")?;
- };
- } else {
- linelen += fmt::fprint(out, " ")?;
- };
+ n += syn(ctx, ",", synkind::TYPE)?;
+ n += space(ctx)?;
};
- n += linelen;
- n += fmt::fprint(out, ")")?;
+ n += syn(ctx, ")", synkind::TYPE)?;
};
return n;
};
fn type_test(t: ast::_type, expected: str) void = {
let buf = memio::dynamic();
- _type(&buf, 0, t) as size;
+ _type(&buf, &syn_nowrap, t)!;
let s = memio::string(&buf)!;
defer free(s);
if (s != expected) {
diff --git a/hare/unparse/unit.ha b/hare/unparse/unit.ha
@@ -6,17 +6,21 @@ use fmt;
use hare::ast;
// Unparses a [[hare::ast::subunit]].
-export fn subunit(out: io::handle, s: ast::subunit) (size | io::error) = {
+export fn subunit(
+ out: io::handle,
+ syn: *synfunc,
+ 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 += import(out, syn, s.imports[i])?;
n += fmt::fprintln(out)?;
};
if (len(s.imports) > 0) {
n += fmt::fprintln(out)?;
};
for (let i = 0z; i < len(s.decls); i += 1) {
- n += decl(out, s.decls[i])?;
+ n += decl(out, syn, s.decls[i])?;
if (i < len(s.decls) - 1) n += fmt::fprintln(out)?;
n += fmt::fprintln(out)?;
};
diff --git a/hare/unparse/util.ha b/hare/unparse/util.ha
@@ -4,11 +4,21 @@
use io;
use fmt;
-fn newline(out: io::handle, indent: size) (size | io::error) = {
+fn newline(ctx: *context) (size | io::error) = {
let n = 0z;
- n += fmt::fprint(out, "\n")?;
- for (let i = 0z; i < indent; i += 1) {
- n += fmt::fprint(out, "\t")?;
+ n += fmt::fprint(ctx.out, "\n")?;
+ ctx.linelen = 0;
+ for (let i = 0z; i < ctx.indent; i += 1) {
+ n += fmt::fprint(ctx.out, "\t")?;
+ ctx.linelen += 8;
};
return n;
};
+
+fn space(ctx: *context) (size | io::error) = {
+ if (ctx.linelen <= ctx.indent * 8) {
+ return 0z;
+ };
+ ctx.linelen += 1;
+ return fmt::fprint(ctx.out, " ");
+};
diff --git a/makefiles/freebsd.aarch64.mk b/makefiles/freebsd.aarch64.mk
@@ -177,7 +177,7 @@ $(HARECACHE)/hare_parse.ssa: $(hare_parse_ha) $(HARECACHE)/ascii.td $(HARECACHE)
@printf 'HAREC\t%s\n' "$@"
@$(TDENV) $(HAREC) $(HARECFLAGS) -o $(HARECACHE)/hare_parse.ssa -t $(HARECACHE)/hare_parse.td.tmp -N hare::parse $(hare_parse_ha)
-hare_unparse_ha = hare/unparse/decl.ha hare/unparse/expr.ha hare/unparse/ident.ha hare/unparse/import.ha hare/unparse/type.ha hare/unparse/unit.ha hare/unparse/util.ha
+hare_unparse_ha = hare/unparse/decl.ha hare/unparse/expr.ha hare/unparse/ident.ha hare/unparse/import.ha hare/unparse/syn.ha hare/unparse/type.ha hare/unparse/unit.ha hare/unparse/util.ha
$(HARECACHE)/hare_unparse.ssa: $(hare_unparse_ha) $(HARECACHE)/fmt.td $(HARECACHE)/hare_ast.td $(HARECACHE)/hare_lex.td $(HARECACHE)/io.td $(HARECACHE)/memio.td $(HARECACHE)/strings.td
@mkdir -p -- "$(HARECACHE)"
@printf 'HAREC\t%s\n' "$@"
diff --git a/makefiles/freebsd.riscv64.mk b/makefiles/freebsd.riscv64.mk
@@ -177,7 +177,7 @@ $(HARECACHE)/hare_parse.ssa: $(hare_parse_ha) $(HARECACHE)/ascii.td $(HARECACHE)
@printf 'HAREC\t%s\n' "$@"
@$(TDENV) $(HAREC) $(HARECFLAGS) -o $(HARECACHE)/hare_parse.ssa -t $(HARECACHE)/hare_parse.td.tmp -N hare::parse $(hare_parse_ha)
-hare_unparse_ha = hare/unparse/decl.ha hare/unparse/expr.ha hare/unparse/ident.ha hare/unparse/import.ha hare/unparse/type.ha hare/unparse/unit.ha hare/unparse/util.ha
+hare_unparse_ha = hare/unparse/decl.ha hare/unparse/expr.ha hare/unparse/ident.ha hare/unparse/import.ha hare/unparse/syn.ha hare/unparse/type.ha hare/unparse/unit.ha hare/unparse/util.ha
$(HARECACHE)/hare_unparse.ssa: $(hare_unparse_ha) $(HARECACHE)/fmt.td $(HARECACHE)/hare_ast.td $(HARECACHE)/hare_lex.td $(HARECACHE)/io.td $(HARECACHE)/memio.td $(HARECACHE)/strings.td
@mkdir -p -- "$(HARECACHE)"
@printf 'HAREC\t%s\n' "$@"
diff --git a/makefiles/freebsd.x86_64.mk b/makefiles/freebsd.x86_64.mk
@@ -177,7 +177,7 @@ $(HARECACHE)/hare_parse.ssa: $(hare_parse_ha) $(HARECACHE)/ascii.td $(HARECACHE)
@printf 'HAREC\t%s\n' "$@"
@$(TDENV) $(HAREC) $(HARECFLAGS) -o $(HARECACHE)/hare_parse.ssa -t $(HARECACHE)/hare_parse.td.tmp -N hare::parse $(hare_parse_ha)
-hare_unparse_ha = hare/unparse/decl.ha hare/unparse/expr.ha hare/unparse/ident.ha hare/unparse/import.ha hare/unparse/type.ha hare/unparse/unit.ha hare/unparse/util.ha
+hare_unparse_ha = hare/unparse/decl.ha hare/unparse/expr.ha hare/unparse/ident.ha hare/unparse/import.ha hare/unparse/syn.ha hare/unparse/type.ha hare/unparse/unit.ha hare/unparse/util.ha
$(HARECACHE)/hare_unparse.ssa: $(hare_unparse_ha) $(HARECACHE)/fmt.td $(HARECACHE)/hare_ast.td $(HARECACHE)/hare_lex.td $(HARECACHE)/io.td $(HARECACHE)/memio.td $(HARECACHE)/strings.td
@mkdir -p -- "$(HARECACHE)"
@printf 'HAREC\t%s\n' "$@"
diff --git a/makefiles/linux.aarch64.mk b/makefiles/linux.aarch64.mk
@@ -195,7 +195,7 @@ $(HARECACHE)/hare_parse.ssa: $(hare_parse_ha) $(HARECACHE)/ascii.td $(HARECACHE)
@printf 'HAREC\t%s\n' "$@"
@$(TDENV) $(HAREC) $(HARECFLAGS) -o $(HARECACHE)/hare_parse.ssa -t $(HARECACHE)/hare_parse.td.tmp -N hare::parse $(hare_parse_ha)
-hare_unparse_ha = hare/unparse/decl.ha hare/unparse/expr.ha hare/unparse/ident.ha hare/unparse/import.ha hare/unparse/type.ha hare/unparse/unit.ha hare/unparse/util.ha
+hare_unparse_ha = hare/unparse/decl.ha hare/unparse/expr.ha hare/unparse/ident.ha hare/unparse/import.ha hare/unparse/syn.ha hare/unparse/type.ha hare/unparse/unit.ha hare/unparse/util.ha
$(HARECACHE)/hare_unparse.ssa: $(hare_unparse_ha) $(HARECACHE)/fmt.td $(HARECACHE)/hare_ast.td $(HARECACHE)/hare_lex.td $(HARECACHE)/io.td $(HARECACHE)/memio.td $(HARECACHE)/strings.td
@mkdir -p -- "$(HARECACHE)"
@printf 'HAREC\t%s\n' "$@"
diff --git a/makefiles/linux.riscv64.mk b/makefiles/linux.riscv64.mk
@@ -195,7 +195,7 @@ $(HARECACHE)/hare_parse.ssa: $(hare_parse_ha) $(HARECACHE)/ascii.td $(HARECACHE)
@printf 'HAREC\t%s\n' "$@"
@$(TDENV) $(HAREC) $(HARECFLAGS) -o $(HARECACHE)/hare_parse.ssa -t $(HARECACHE)/hare_parse.td.tmp -N hare::parse $(hare_parse_ha)
-hare_unparse_ha = hare/unparse/decl.ha hare/unparse/expr.ha hare/unparse/ident.ha hare/unparse/import.ha hare/unparse/type.ha hare/unparse/unit.ha hare/unparse/util.ha
+hare_unparse_ha = hare/unparse/decl.ha hare/unparse/expr.ha hare/unparse/ident.ha hare/unparse/import.ha hare/unparse/syn.ha hare/unparse/type.ha hare/unparse/unit.ha hare/unparse/util.ha
$(HARECACHE)/hare_unparse.ssa: $(hare_unparse_ha) $(HARECACHE)/fmt.td $(HARECACHE)/hare_ast.td $(HARECACHE)/hare_lex.td $(HARECACHE)/io.td $(HARECACHE)/memio.td $(HARECACHE)/strings.td
@mkdir -p -- "$(HARECACHE)"
@printf 'HAREC\t%s\n' "$@"
diff --git a/makefiles/linux.x86_64.mk b/makefiles/linux.x86_64.mk
@@ -195,7 +195,7 @@ $(HARECACHE)/hare_parse.ssa: $(hare_parse_ha) $(HARECACHE)/ascii.td $(HARECACHE)
@printf 'HAREC\t%s\n' "$@"
@$(TDENV) $(HAREC) $(HARECFLAGS) -o $(HARECACHE)/hare_parse.ssa -t $(HARECACHE)/hare_parse.td.tmp -N hare::parse $(hare_parse_ha)
-hare_unparse_ha = hare/unparse/decl.ha hare/unparse/expr.ha hare/unparse/ident.ha hare/unparse/import.ha hare/unparse/type.ha hare/unparse/unit.ha hare/unparse/util.ha
+hare_unparse_ha = hare/unparse/decl.ha hare/unparse/expr.ha hare/unparse/ident.ha hare/unparse/import.ha hare/unparse/syn.ha hare/unparse/type.ha hare/unparse/unit.ha hare/unparse/util.ha
$(HARECACHE)/hare_unparse.ssa: $(hare_unparse_ha) $(HARECACHE)/fmt.td $(HARECACHE)/hare_ast.td $(HARECACHE)/hare_lex.td $(HARECACHE)/io.td $(HARECACHE)/memio.td $(HARECACHE)/strings.td
@mkdir -p -- "$(HARECACHE)"
@printf 'HAREC\t%s\n' "$@"