hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

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:
Mcmd/hare/build/util.ha | 5+++--
Mcmd/haredoc/doc/hare.ha | 15++++++++++-----
Mcmd/haredoc/doc/html.ha | 15++++++++-------
Mcmd/haredoc/doc/tty.ha | 10++++++----
Mcmd/haretype/main.ha | 2+-
Mhare/parse/+test/roundtrip.ha | 2+-
Mhare/parse/+test/types.ha | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mhare/unparse/decl.ha | 150+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Mhare/unparse/expr.ha | 718++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Mhare/unparse/ident.ha | 20+++++++++++++++++---
Mhare/unparse/import.ha | 55+++++++++++++++++++++++++++++++++++++++----------------
Ahare/unparse/syn.ha | 231+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mhare/unparse/type.ha | 317++++++++++++++++++++++++++++++++++---------------------------------------------
Mhare/unparse/unit.ha | 10+++++++---
Mhare/unparse/util.ha | 18++++++++++++++----
Mmakefiles/freebsd.aarch64.mk | 2+-
Mmakefiles/freebsd.riscv64.mk | 2+-
Mmakefiles/freebsd.x86_64.mk | 2+-
Mmakefiles/linux.aarch64.mk | 2+-
Mmakefiles/linux.riscv64.mk | 2+-
Mmakefiles/linux.x86_64.mk | 2+-
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' "$@"