hare

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

tty.ha (16411B)


      1 // License: GPL-3.0
      2 // (c) 2021 Alexey Yerin <yyp@disroot.org>
      3 // (c) 2021 Drew DeVault <sir@cmpwn.com>
      4 // (c) 2021 Ember Sawady <ecs@d2evs.net>
      5 use ascii;
      6 use bufio;
      7 use fmt;
      8 use hare::ast;
      9 use hare::ast::{variadism};
     10 use hare::lex;
     11 use hare::unparse;
     12 use io;
     13 use os;
     14 use strings;
     15 use strio;
     16 
     17 let firstline: bool = true;
     18 
     19 // Formats output as Hare source code (prototypes) with syntax highlighting
     20 fn emit_tty(ctx: *context) (void | error) = {
     21 	init_colors();
     22 	const summary = ctx.summary;
     23 
     24 	match (ctx.readme) {
     25 	case let readme: io::file =>
     26 		for (true) match (bufio::scanline(readme)?) {
     27 		case io::EOF => break;
     28 		case let b: []u8 =>
     29 			defer free(b);
     30 			firstline = false;
     31 			insert(b[0], ' ');
     32 			comment_tty(ctx.out, strings::fromutf8(b)!)?;
     33 		};
     34 	case void => void;
     35 	};
     36 
     37 	emit_submodules_tty(ctx)?;
     38 
     39 	// XXX: Should we emit the dependencies, too?
     40 	for (let i = 0z; i < len(summary.types); i += 1) {
     41 		details_tty(ctx, summary.types[i])?;
     42 	};
     43 	for (let i = 0z; i < len(summary.constants); i += 1) {
     44 		details_tty(ctx, summary.constants[i])?;
     45 	};
     46 	for (let i = 0z; i < len(summary.errors); i += 1) {
     47 		details_tty(ctx, summary.errors[i])?;
     48 	};
     49 	for (let i = 0z; i < len(summary.globals); i += 1) {
     50 		details_tty(ctx, summary.globals[i])?;
     51 	};
     52 	for (let i = 0z; i < len(summary.funcs); i += 1) {
     53 		details_tty(ctx, summary.funcs[i])?;
     54 	};
     55 };
     56 
     57 fn emit_submodules_tty(ctx: *context) (void | error) = {
     58 	const submodules = submodules(ctx)?;
     59 	defer strings::freeall(submodules);
     60 
     61 	if (len(submodules) != 0) {
     62 		fmt::fprintln(ctx.out)?;
     63 		if (len(ctx.ident) == 0) {
     64 			render(ctx.out, syn::COMMENT)?;
     65 			fmt::fprintln(ctx.out, "// Modules")?;
     66 			render(ctx.out, syn::NORMAL)?;
     67 		} else {
     68 			render(ctx.out, syn::COMMENT)?;
     69 			fmt::fprintln(ctx.out, "// Submodules")?;
     70 			render(ctx.out, syn::NORMAL)?;
     71 		};
     72 		for (let i = 0z; i < len(submodules); i += 1) {
     73 			let submodule = if (len(ctx.ident) != 0) {
     74 				const s = unparse::identstr(ctx.ident);
     75 				defer free(s);
     76 				yield strings::concat(s, "::", submodules[i]);
     77 			} else {
     78 				yield strings::dup(submodules[i]);
     79 			};
     80 			defer free(submodule);
     81 
     82 			render(ctx.out, syn::COMMENT)?;
     83 			fmt::fprintfln(ctx.out, "// - [[{}]]", submodule)?;
     84 			render(ctx.out, syn::NORMAL)?;
     85 		};
     86 	};
     87 };
     88 
     89 fn comment_tty(out: io::handle, s: str) (size | io::error) = {
     90 	let n = 0z;
     91 	n += render(out, syn::COMMENT)?;
     92 	n += fmt::fprintfln(out, "//{}", s)?;
     93 	n += render(out, syn::NORMAL)?;
     94 	return n;
     95 };
     96 
     97 fn docs_tty(out: io::handle, s: str, indent: size) (size | io::error) = {
     98 	const iter = strings::tokenize(s, "\n");
     99 	let z = 0z;
    100 	for (true) match (strings::next_token(&iter)) {
    101 	case let s: str =>
    102 		if (!(strings::peek_token(&iter) is void)) {
    103 			z += comment_tty(out, s)?;
    104 			for (let i = 0z; i < indent; i += 1) {
    105 				z += fmt::fprint(out, "\t")?;
    106 			};
    107 		};
    108 	case void => break;
    109 	};
    110 
    111 	return z;
    112 };
    113 
    114 fn isws(s: str) bool = {
    115 	const iter = strings::iter(s);
    116 	for (true) {
    117 		match (strings::next(&iter)) {
    118 		case let r: rune =>
    119 			if (!ascii::isspace(r)) {
    120 				return false;
    121 			};
    122 		case void => break;
    123 		};
    124 	};
    125 	return true;
    126 };
    127 
    128 fn details_tty(ctx: *context, decl: ast::decl) (void | error) = {
    129 	if (len(decl.docs) == 0 && !ctx.show_undocumented) {
    130 		return;
    131 	};
    132 
    133 	if (!firstline) {
    134 		fmt::fprintln(ctx.out)?;
    135 	};
    136 	firstline = false;
    137 
    138 	docs_tty(ctx.out, decl.docs, 0)?;
    139 	unparse_tty(ctx.out, decl)?;
    140 	fmt::fprintln(ctx.out)?;
    141 };
    142 
    143 // Forked from [[hare::unparse]]
    144 fn unparse_tty(out: io::handle, d: ast::decl) (size | io::error) = {
    145 	let n = 0z;
    146 	match (d.decl) {
    147 	case let g: []ast::decl_global =>
    148 		n += render(out, syn::KEYWORD)?;
    149 		n += fmt::fprint(out, if (g[0].is_const) "const " else "let ")?;
    150 		for (let i = 0z; i < len(g); i += 1) {
    151 			if (len(g[i].symbol) != 0) {
    152 				n += render(out, syn::ATTRIBUTE)?;
    153 				n += fmt::fprintf(out, "@symbol(")?;
    154 				n += render(out, syn::STRING)?;
    155 				n += fmt::fprintf(out, `"{}"`, g[i].symbol)?;
    156 				n += render(out, syn::ATTRIBUTE)?;
    157 				n += fmt::fprintf(out, ") ")?;
    158 				n += render(out, syn::NORMAL)?;
    159 			};
    160 			n += render(out, syn::PRIMARY)?;
    161 			n += unparse::ident(out, g[i].ident)?;
    162 			n += render(out, syn::PUNCTUATION)?;
    163 			n += fmt::fprint(out, ": ")?;
    164 			n += type_tty(out, 0, g[i]._type)?;
    165 			if (i + 1 < len(g)) {
    166 				n += render(out, syn::PUNCTUATION)?;
    167 				n += fmt::fprint(out, ", ")?;
    168 			};
    169 			n += render(out, syn::NORMAL)?;
    170 		};
    171 	case let c: []ast::decl_const =>
    172 		n += render(out, syn::KEYWORD)?;
    173 		n += fmt::fprintf(out, "def ")?;
    174 		for (let i = 0z; i < len(c); i += 1) {
    175 			n += render(out, syn::PRIMARY)?;
    176 			n += unparse::ident(out, c[i].ident)?;
    177 			n += render(out, syn::PUNCTUATION)?;
    178 			n += fmt::fprint(out, ": ")?;
    179 			n += type_tty(out, 0, c[i]._type)?;
    180 			if (i + 1 < len(c)) {
    181 				n += render(out, syn::PUNCTUATION)?;
    182 				n += fmt::fprint(out, ", ")?;
    183 			};
    184 		};
    185 	case let t: []ast::decl_type =>
    186 		n += render(out, syn::KEYWORD)?;
    187 		n += fmt::fprint(out, "type ")?;
    188 		for (let i = 0z; i < len(t); i += 1) {
    189 			n += render(out, syn::PRIMARY)?;
    190 			n += unparse::ident(out, t[i].ident)?;
    191 			n += render(out, syn::PUNCTUATION)?;
    192 			n += fmt::fprint(out, " = ")?;
    193 			n += type_tty(out, 0, t[i]._type)?;
    194 			if (i + 1 < len(t)) {
    195 				n += render(out, syn::PUNCTUATION)?;
    196 				n += fmt::fprint(out, ", ")?;
    197 			};
    198 		};
    199 	case let f: ast::decl_func =>
    200 		n += render(out, syn::ATTRIBUTE)?;
    201 		n += fmt::fprint(out, switch (f.attrs) {
    202 		case ast::fndecl_attrs::NONE =>
    203 			yield "";
    204 		case ast::fndecl_attrs::FINI =>
    205 			yield "@fini ";
    206 		case ast::fndecl_attrs::INIT =>
    207 			yield "@init ";
    208 		case ast::fndecl_attrs::TEST =>
    209 			yield "@test ";
    210 		})?;
    211 		n += render(out, syn::NORMAL)?;
    212 
    213 		let p = f.prototype.repr as ast::func_type;
    214 		if (p.attrs & ast::func_attrs::NORETURN != 0) {
    215 			n += render(out, syn::ATTRIBUTE)?;
    216 			n += fmt::fprint(out, "@noreturn ")?;
    217 			n += render(out, syn::NORMAL)?;
    218 		};
    219 		if (len(f.symbol) != 0) {
    220 			n += render(out, syn::ATTRIBUTE)?;
    221 			n += fmt::fprintf(out, "@symbol(")?;
    222 			n += render(out, syn::STRING)?;
    223 			n += fmt::fprintf(out, `"{}"`, f.symbol)?;
    224 			n += render(out, syn::ATTRIBUTE)?;
    225 			n += fmt::fprintf(out, ") ")?;
    226 			n += render(out, syn::NORMAL)?;
    227 		};
    228 		n += render(out, syn::KEYWORD)?;
    229 		n += fmt::fprint(out, "fn ")?;
    230 		n += render(out, syn::PRIMARY)?;
    231 		n += unparse::ident(out, f.ident)?;
    232 		n += prototype_tty(out, 0,
    233 			f.prototype.repr as ast::func_type)?;
    234 	};
    235 	n += render(out, syn::PUNCTUATION)?;
    236 	n += fmt::fprint(out, ";")?;
    237 	return n;
    238 };
    239 
    240 fn prototype_tty(
    241 	out: io::handle,
    242 	indent: size,
    243 	t: ast::func_type,
    244 ) (size | io::error) = {
    245 	let n = 0z;
    246 	n += render(out, syn::PUNCTUATION)?;
    247 	n += fmt::fprint(out, "(")?;
    248 
    249 	let typenames: []str = [];
    250 	// TODO: https://todo.sr.ht/~sircmpwn/hare/581
    251 	if (len(t.params) > 0) {
    252 		typenames = alloc([""...], len(t.params));
    253 	};
    254 	defer strings::freeall(typenames);
    255 	let retname = "";
    256 	defer free(retname);
    257 
    258 	// estimate length of prototype to determine if it should span multiple
    259 	// lines
    260 	const linelen = if (len(t.params) == 0) {
    261 		let strm = strio::dynamic();
    262 		defer io::close(&strm)!;
    263 		type_tty(&strm, indent, *t.result)?;
    264 		retname = strings::dup(strio::string(&strm));
    265 		yield 0z; // only use one line if there's no parameters
    266 	} else {
    267 		let strm = strio::dynamic();
    268 		defer io::close(&strm)!;
    269 		let linelen = indent * 8 + 5;
    270 		linelen += if (len(t.params) != 0) len(t.params) * 3 - 1 else 0;
    271 		for (let i = 0z; i < len(t.params); i += 1) {
    272 			const param = t.params[i];
    273 			linelen += unparse::_type(&strm, indent, *param._type)?;
    274 			typenames[i] = strings::dup(strio::string(&strm));
    275 			linelen += if (param.name == "") 1 else len(param.name);
    276 			strio::reset(&strm);
    277 		};
    278 		switch (t.variadism) {
    279 		case variadism::NONE => void;
    280 		case variadism::HARE =>
    281 			linelen += 3;
    282 		case variadism::C =>
    283 			linelen += 5;
    284 		};
    285 		linelen += type_tty(&strm, indent, *t.result)?;
    286 		retname = strings::dup(strio::string(&strm));
    287 		yield linelen;
    288 	};
    289 
    290 	// use 72 instead of 80 to give a bit of leeway for preceding text
    291 	if (linelen > 72) {
    292 		indent += 1;
    293 		for (let i = 0z; i < len(t.params); i += 1) {
    294 			const param = t.params[i];
    295 			n += newline(out, indent)?;
    296 			n += render(out, syn::SECONDARY)?;
    297 			n += fmt::fprint(out,
    298 				if (param.name == "") "_" else param.name)?;
    299 			n += render(out, syn::PUNCTUATION)?;
    300 			n += fmt::fprint(out, ": ")?;
    301 			n += render(out, syn::TYPE)?;
    302 			n += fmt::fprint(out, typenames[i])?;
    303 			if (i + 1 == len(t.params)
    304 					&& t.variadism == variadism::HARE) {
    305 				n += render(out, syn::OPERATOR)?;
    306 				n += fmt::fprint(out, "...")?;
    307 			} else {
    308 				n += render(out, syn::PUNCTUATION)?;
    309 				n += fmt::fprint(out, ",")?;
    310 			};
    311 		};
    312 		if (t.variadism == variadism::C) {
    313 			n += newline(out, indent)?;
    314 			n += render(out, syn::OPERATOR)?;
    315 			n += fmt::fprint(out, "...")?;
    316 		};
    317 		indent -= 1;
    318 		n += newline(out, indent)?;
    319 	} else for (let i = 0z; i < len(t.params); i += 1) {
    320 		const param = t.params[i];
    321 		n += render(out, syn::SECONDARY)?;
    322 		n += fmt::fprint(out,
    323 			if (param.name == "") "_" else param.name)?;
    324 		n += render(out, syn::PUNCTUATION)?;
    325 		n += fmt::fprint(out, ": ")?;
    326 		n += render(out, syn::TYPE)?;
    327 		n += fmt::fprint(out, typenames[i])?;
    328 		if (i + 1 == len(t.params)) {
    329 			switch (t.variadism) {
    330 			case variadism::NONE => void;
    331 			case variadism::HARE =>
    332 				n += render(out, syn::OPERATOR)?;
    333 				n += fmt::fprint(out, "...")?;
    334 			case variadism::C =>
    335 				n += render(out, syn::PUNCTUATION)?;
    336 				n += fmt::fprint(out, ", ")?;
    337 				n += render(out, syn::OPERATOR)?;
    338 				n += fmt::fprint(out, "...")?;
    339 			};
    340 		} else {
    341 			n += render(out, syn::PUNCTUATION)?;
    342 			n += fmt::fprint(out, ", ")?;
    343 		};
    344 	};
    345 
    346 	n += render(out, syn::PUNCTUATION)?;
    347 	n += fmt::fprint(out, ")", retname)?;
    348 	return n;
    349 };
    350 
    351 // Forked from [[hare::unparse]]
    352 fn struct_union_type_tty(
    353 	out: io::handle,
    354 	indent: size,
    355 	t: ast::_type,
    356 ) (size | io::error) = {
    357 	let n = 0z;
    358 	let membs = match (t.repr) {
    359 	case let st: ast::struct_type =>
    360 		n += render(out, syn::TYPE)?;
    361 		n += fmt::fprint(out, "struct")?;
    362 		if (st.packed) {
    363 			n += render(out, syn::ATTRIBUTE)?;
    364 			n += fmt::fprint(out, " @packed")?;
    365 		};
    366 		n += render(out, syn::PUNCTUATION)?;
    367 		n += fmt::fprint(out, " {")?;
    368 		yield st.members: []ast::struct_member;
    369 	case let ut: ast::union_type =>
    370 		n += render(out, syn::TYPE)?;
    371 		n += fmt::fprint(out, "union")?;
    372 		n += render(out, syn::PUNCTUATION)?;
    373 		n += fmt::fprint(out, " {")?;
    374 		yield ut: []ast::struct_member;
    375 	};
    376 
    377 	indent += 1z;
    378 	for (let i = 0z; i < len(membs); i += 1) {
    379 		n += newline(out, indent)?;
    380 		if (membs[i].docs != "") {
    381 			n += docs_tty(out, membs[i].docs, indent)?;
    382 		};
    383 
    384 		match (membs[i]._offset) {
    385 		case null => void;
    386 		case let ex: *ast::expr =>
    387 			n += render(out, syn::ATTRIBUTE)?;
    388 			n += fmt::fprint(out, "@offset(")?;
    389 			n += render(out, syn::NUMBER)?;
    390 			n += unparse::expr(out, indent, *ex)?;
    391 			n += render(out, syn::ATTRIBUTE)?;
    392 			n += fmt::fprint(out, ")")?;
    393 			n += render(out, syn::NORMAL)?;
    394 		};
    395 
    396 		match (membs[i].member) {
    397 		case let se: ast::struct_embedded =>
    398 			n += type_tty(out, indent, *se)?;
    399 		case let sa: ast::struct_alias =>
    400 			n += unparse::ident(out, sa)?;
    401 		case let sf: ast::struct_field =>
    402 			n += render(out, syn::SECONDARY)?;
    403 			n += fmt::fprint(out, sf.name)?;
    404 			n += render(out, syn::PUNCTUATION)?;
    405 			n += fmt::fprint(out, ": ")?;
    406 			n += type_tty(out, indent, *sf._type)?;
    407 		};
    408 
    409 		n += render(out, syn::PUNCTUATION)?;
    410 		n += fmt::fprint(out, ",")?;
    411 	};
    412 
    413 	indent -= 1;
    414 	n += newline(out, indent)?;
    415 	n += render(out, syn::PUNCTUATION)?;
    416 	n += fmt::fprint(out, "}")?;
    417 	return n;
    418 };
    419 
    420 // Forked from [[hare::unparse]]
    421 fn type_tty(
    422 	out: io::handle,
    423 	indent: size,
    424 	t: ast::_type,
    425 ) (size | io::error) = {
    426 	let n = 0z;
    427 	if (t.flags & ast::type_flags::CONST != 0
    428 			&& !(t.repr is ast::func_type)) {
    429 		n += render(out, syn::TYPE)?;
    430 		n += fmt::fprint(out, "const ")?;
    431 	};
    432 	if (t.flags & ast::type_flags::ERROR != 0) {
    433 		n += render(out, syn::OPERATOR)?;
    434 		n += fmt::fprint(out, "!")?;
    435 	};
    436 
    437 	match (t.repr) {
    438 	case let a: ast::alias_type =>
    439 		if (a.unwrap) {
    440 			n += render(out, syn::OPERATOR)?;
    441 			n += fmt::fprint(out, "...")?;
    442 		};
    443 		n += render(out, syn::TYPE)?;
    444 		n += unparse::ident(out, a.ident)?;
    445 	case let b: ast::builtin_type =>
    446 		n += render(out, syn::TYPE)?;
    447 		n += fmt::fprintf(out, "{}", unparse::builtin_type(b))?;
    448 	case let e: ast::enum_type =>
    449 		n += render(out, syn::TYPE)?;
    450 		n += fmt::fprint(out, "enum ")?;
    451 		if (e.storage != ast::builtin_type::INT) {
    452 			n += fmt::fprintf(out,
    453 				"{} ", unparse::builtin_type(e.storage))?;
    454 		};
    455 		n += render(out, syn::PUNCTUATION)?;
    456 		n += fmt::fprintln(out, "{")?;
    457 		indent += 1;
    458 		for (let i = 0z; i < len(e.values); i += 1) {
    459 			for (let i = 0z; i < indent; i += 1) {
    460 				n += fmt::fprint(out, "\t")?;
    461 			};
    462 			let value = e.values[i];
    463 			let wrotedocs = false;
    464 			if (value.docs != "") {
    465 				// Check if comment should go above or next to
    466 				// field
    467 				if (multiline_comment(value.docs)) {
    468 					n += docs_tty(out, value.docs, indent)?;
    469 					wrotedocs = true;
    470 				};
    471 			};
    472 			n += render(out, syn::SECONDARY)?;
    473 			n += fmt::fprint(out, value.name)?;
    474 			match (value.value) {
    475 			case null => void;
    476 			case let e: *ast::expr =>
    477 				n += render(out, syn::OPERATOR)?;
    478 				n += fmt::fprint(out, " = ")?;
    479 				n += render(out, syn::NORMAL)?;
    480 				n += unparse::expr(out, indent, *e)?;
    481 			};
    482 			n += render(out, syn::PUNCTUATION)?;
    483 			n += fmt::fprint(out, ",")?;
    484 			if (value.docs != "" && !wrotedocs) {
    485 				n += fmt::fprint(out, " ")?;
    486 				n += docs_tty(out, value.docs, 0)?;
    487 			} else {
    488 				n += fmt::fprintln(out)?;
    489 			};
    490 		};
    491 		indent -= 1;
    492 		for (let i = 0z; i < indent; i += 1) {
    493 			n += fmt::fprint(out, "\t")?;
    494 		};
    495 		n += render(out, syn::PUNCTUATION)?;
    496 		n += fmt::fprint(out, "}")?;
    497 	case let f: ast::func_type =>
    498 		if (f.attrs & ast::func_attrs::NORETURN != 0) {
    499 			n += render(out, syn::ATTRIBUTE)?;
    500 			n += fmt::fprint(out, "@noreturn ")?;
    501 		};
    502 		n += render(out, syn::TYPE)?;
    503 		n += fmt::fprint(out, "fn")?;
    504 		n += prototype_tty(out, indent, f)?;
    505 	case let l: ast::list_type =>
    506 		n += render(out, syn::OPERATOR)?;
    507 		n += fmt::fprint(out, "[")?;
    508 		match (l.length) {
    509 		case ast::len_slice => void;
    510 		case ast::len_unbounded =>
    511 			n += fmt::fprint(out, "*")?;
    512 		case ast::len_contextual =>
    513 			n += fmt::fprint(out, "_")?;
    514 		case let e: *ast::expr =>
    515 			n += unparse::expr(out, indent, *e)?;
    516 		};
    517 		n += render(out, syn::OPERATOR)?;
    518 		n += fmt::fprint(out, "]")?;
    519 		n += type_tty(out, indent, *l.members)?;
    520 	case let p: ast::pointer_type =>
    521 		if (p.flags & ast::pointer_flags::NULLABLE != 0) {
    522 			n += render(out, syn::TYPE)?;
    523 			n += fmt::fprint(out, "nullable ")?;
    524 		};
    525 		n += render(out, syn::OPERATOR)?;
    526 		n += fmt::fprint(out, "*")?;
    527 		n += type_tty(out, indent, *p.referent)?;
    528 	case ast::struct_type =>
    529 		n += struct_union_type_tty(out, indent, t)?;
    530 	case ast::union_type =>
    531 		n += struct_union_type_tty(out, indent, t)?;
    532 	case let t: ast::tagged_type =>
    533 		// rough estimate of current line length
    534 		let linelen: size = n + (indent + 1) * 8;
    535 		n = 0;
    536 		n += render(out, syn::PUNCTUATION)?;
    537 		linelen += fmt::fprint(out, "(")?;
    538 		for (let i = 0z; i < len(t); i += 1) {
    539 			linelen += type_tty(out, indent, *t[i])?;
    540 			if (i + 1 == len(t)) break;
    541 			n += render(out, syn::PUNCTUATION)?;
    542 			linelen += fmt::fprint(out, " |")?;
    543 			// use 72 instead of 80 to give a bit of leeway for long
    544 			// type names
    545 			if (linelen > 72) {
    546 				n += linelen;
    547 				linelen = (indent + 1) * 8;
    548 				n += fmt::fprintln(out)?;
    549 				for (let i = 0z; i <= indent; i += 1) {
    550 					n += fmt::fprint(out, "\t")?;
    551 				};
    552 			} else {
    553 				linelen += fmt::fprint(out, " ")?;
    554 			};
    555 		};
    556 		n += linelen;
    557 		n += render(out, syn::PUNCTUATION)?;
    558 		n += fmt::fprint(out, ")")?;
    559 	case let t: ast::tuple_type =>
    560 		// rough estimate of current line length
    561 		let linelen: size = n + (indent + 1) * 8;
    562 		n = 0;
    563 		n += render(out, syn::PUNCTUATION)?;
    564 		linelen += fmt::fprint(out, "(")?;
    565 		for (let i = 0z; i < len(t); i += 1) {
    566 			linelen += type_tty(out, indent, *t[i])?;
    567 			if (i + 1 == len(t)) break;
    568 			n += render(out, syn::PUNCTUATION)?;
    569 			linelen += fmt::fprint(out, ",")?;
    570 			// use 72 instead of 80 to give a bit of leeway for long
    571 			// type names
    572 			if (linelen > 72) {
    573 				n += linelen;
    574 				linelen = (indent + 1) * 8;
    575 				n += fmt::fprintln(out)?;
    576 				for (let i = 0z; i <= indent; i += 1) {
    577 					n += fmt::fprint(out, "\t")?;
    578 				};
    579 			} else {
    580 				linelen += fmt::fprint(out, " ")?;
    581 			};
    582 		};
    583 		n += linelen;
    584 		n += render(out, syn::PUNCTUATION)?;
    585 		n += fmt::fprint(out, ")")?;
    586 	};
    587 	return n;
    588 };