hare

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

tty.ha (16300B)


      1 // License: GPL-3.0
      2 // (c) 2021 Alexey Yerin <yyp@disroot.org>
      3 // (c) 2021 Drew DeVault <sir@cmpwn.com>
      4 // (c) 2021 Eyal 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 		n += render(out, syn::PUNCTUATION)?;
    363 		n += fmt::fprint(out, " {")?;
    364 		yield st: []ast::struct_member;
    365 	case let ut: ast::union_type =>
    366 		n += render(out, syn::TYPE)?;
    367 		n += fmt::fprint(out, "union")?;
    368 		n += render(out, syn::PUNCTUATION)?;
    369 		n += fmt::fprint(out, " {")?;
    370 		yield ut: []ast::struct_member;
    371 	};
    372 
    373 	indent += 1z;
    374 	for (let i = 0z; i < len(membs); i += 1) {
    375 		n += newline(out, indent)?;
    376 		if (membs[i].docs != "") {
    377 			n += docs_tty(out, membs[i].docs, indent)?;
    378 		};
    379 
    380 		match (membs[i]._offset) {
    381 		case null => void;
    382 		case let ex: *ast::expr =>
    383 			n += render(out, syn::ATTRIBUTE)?;
    384 			n += fmt::fprint(out, "@offset(")?;
    385 			n += render(out, syn::NUMBER)?;
    386 			n += unparse::expr(out, indent, *ex)?;
    387 			n += render(out, syn::ATTRIBUTE)?;
    388 			n += fmt::fprint(out, ")")?;
    389 			n += render(out, syn::NORMAL)?;
    390 		};
    391 
    392 		match (membs[i].member) {
    393 		case let se: ast::struct_embedded =>
    394 			n += type_tty(out, indent, *se)?;
    395 		case let sa: ast::struct_alias =>
    396 			n += unparse::ident(out, sa)?;
    397 		case let sf: ast::struct_field =>
    398 			n += render(out, syn::SECONDARY)?;
    399 			n += fmt::fprint(out, sf.name)?;
    400 			n += render(out, syn::PUNCTUATION)?;
    401 			n += fmt::fprint(out, ": ")?;
    402 			n += type_tty(out, indent, *sf._type)?;
    403 		};
    404 
    405 		n += render(out, syn::PUNCTUATION)?;
    406 		n += fmt::fprint(out, ",")?;
    407 	};
    408 
    409 	indent -= 1;
    410 	n += newline(out, indent)?;
    411 	n += render(out, syn::PUNCTUATION)?;
    412 	n += fmt::fprint(out, "}")?;
    413 	return n;
    414 };
    415 
    416 // Forked from [[hare::unparse]]
    417 fn type_tty(
    418 	out: io::handle,
    419 	indent: size,
    420 	t: ast::_type,
    421 ) (size | io::error) = {
    422 	let n = 0z;
    423 	if (t.flags & ast::type_flags::CONST != 0
    424 			&& !(t.repr is ast::func_type)) {
    425 		n += render(out, syn::TYPE)?;
    426 		n += fmt::fprint(out, "const ")?;
    427 	};
    428 	if (t.flags & ast::type_flags::ERROR != 0) {
    429 		n += render(out, syn::OPERATOR)?;
    430 		n += fmt::fprint(out, "!")?;
    431 	};
    432 
    433 	match (t.repr) {
    434 	case let a: ast::alias_type =>
    435 		if (a.unwrap) {
    436 			n += render(out, syn::OPERATOR)?;
    437 			n += fmt::fprint(out, "...")?;
    438 		};
    439 		n += render(out, syn::TYPE)?;
    440 		n += unparse::ident(out, a.ident)?;
    441 	case let b: ast::builtin_type =>
    442 		n += render(out, syn::TYPE)?;
    443 		n += fmt::fprintf(out, "{}", unparse::builtin_type(b))?;
    444 	case let e: ast::enum_type =>
    445 		n += render(out, syn::TYPE)?;
    446 		n += fmt::fprint(out, "enum ")?;
    447 		if (e.storage != ast::builtin_type::INT) {
    448 			n += fmt::fprintf(out,
    449 				"{} ", unparse::builtin_type(e.storage))?;
    450 		};
    451 		n += render(out, syn::PUNCTUATION)?;
    452 		n += fmt::fprintln(out, "{")?;
    453 		indent += 1;
    454 		for (let i = 0z; i < len(e.values); i += 1) {
    455 			for (let i = 0z; i < indent; i += 1) {
    456 				n += fmt::fprint(out, "\t")?;
    457 			};
    458 			let value = e.values[i];
    459 			let wrotedocs = false;
    460 			if (value.docs != "") {
    461 				// Check if comment should go above or next to
    462 				// field
    463 				if (multiline_comment(value.docs)) {
    464 					n += docs_tty(out, value.docs, indent)?;
    465 					wrotedocs = true;
    466 				};
    467 			};
    468 			n += render(out, syn::SECONDARY)?;
    469 			n += fmt::fprint(out, value.name)?;
    470 			match (value.value) {
    471 			case null => void;
    472 			case let e: *ast::expr =>
    473 				n += render(out, syn::OPERATOR)?;
    474 				n += fmt::fprint(out, " = ")?;
    475 				n += render(out, syn::NORMAL)?;
    476 				n += unparse::expr(out, indent, *e)?;
    477 			};
    478 			n += render(out, syn::PUNCTUATION)?;
    479 			n += fmt::fprint(out, ",")?;
    480 			if (value.docs != "" && !wrotedocs) {
    481 				n += fmt::fprint(out, " ")?;
    482 				n += docs_tty(out, value.docs, 0)?;
    483 			} else {
    484 				n += fmt::fprintln(out)?;
    485 			};
    486 		};
    487 		indent -= 1;
    488 		for (let i = 0z; i < indent; i += 1) {
    489 			n += fmt::fprint(out, "\t")?;
    490 		};
    491 		n += render(out, syn::PUNCTUATION)?;
    492 		n += fmt::fprint(out, "}")?;
    493 	case let f: ast::func_type =>
    494 		if (f.attrs & ast::func_attrs::NORETURN != 0) {
    495 			n += render(out, syn::ATTRIBUTE)?;
    496 			n += fmt::fprint(out, "@noreturn ")?;
    497 		};
    498 		n += render(out, syn::TYPE)?;
    499 		n += fmt::fprint(out, "fn")?;
    500 		n += prototype_tty(out, indent, f)?;
    501 	case let l: ast::list_type =>
    502 		n += render(out, syn::OPERATOR)?;
    503 		n += fmt::fprint(out, "[")?;
    504 		match (l.length) {
    505 		case ast::len_slice => void;
    506 		case ast::len_unbounded =>
    507 			n += fmt::fprint(out, "*")?;
    508 		case ast::len_contextual =>
    509 			n += fmt::fprint(out, "_")?;
    510 		case let e: *ast::expr =>
    511 			n += unparse::expr(out, indent, *e)?;
    512 		};
    513 		n += render(out, syn::OPERATOR)?;
    514 		n += fmt::fprint(out, "]")?;
    515 		n += type_tty(out, indent, *l.members)?;
    516 	case let p: ast::pointer_type =>
    517 		if (p.flags & ast::pointer_flags::NULLABLE != 0) {
    518 			n += render(out, syn::TYPE)?;
    519 			n += fmt::fprint(out, "nullable ")?;
    520 		};
    521 		n += render(out, syn::OPERATOR)?;
    522 		n += fmt::fprint(out, "*")?;
    523 		n += type_tty(out, indent, *p.referent)?;
    524 	case ast::struct_type =>
    525 		n += struct_union_type_tty(out, indent, t)?;
    526 	case ast::union_type =>
    527 		n += struct_union_type_tty(out, indent, t)?;
    528 	case let t: ast::tagged_type =>
    529 		// rough estimate of current line length
    530 		let linelen: size = n + (indent + 1) * 8;
    531 		n = 0;
    532 		n += render(out, syn::PUNCTUATION)?;
    533 		linelen += fmt::fprint(out, "(")?;
    534 		for (let i = 0z; i < len(t); i += 1) {
    535 			linelen += type_tty(out, indent, *t[i])?;
    536 			if (i + 1 == len(t)) break;
    537 			n += render(out, syn::PUNCTUATION)?;
    538 			linelen += fmt::fprint(out, " |")?;
    539 			// use 72 instead of 80 to give a bit of leeway for long
    540 			// type names
    541 			if (linelen > 72) {
    542 				n += linelen;
    543 				linelen = (indent + 1) * 8;
    544 				n += fmt::fprintln(out)?;
    545 				for (let i = 0z; i <= indent; i += 1) {
    546 					n += fmt::fprint(out, "\t")?;
    547 				};
    548 			} else {
    549 				linelen += fmt::fprint(out, " ")?;
    550 			};
    551 		};
    552 		n += linelen;
    553 		n += render(out, syn::PUNCTUATION)?;
    554 		n += fmt::fprint(out, ")")?;
    555 	case let t: ast::tuple_type =>
    556 		// rough estimate of current line length
    557 		let linelen: size = n + (indent + 1) * 8;
    558 		n = 0;
    559 		n += render(out, syn::PUNCTUATION)?;
    560 		linelen += fmt::fprint(out, "(")?;
    561 		for (let i = 0z; i < len(t); i += 1) {
    562 			linelen += type_tty(out, indent, *t[i])?;
    563 			if (i + 1 == len(t)) break;
    564 			n += render(out, syn::PUNCTUATION)?;
    565 			linelen += fmt::fprint(out, ",")?;
    566 			// use 72 instead of 80 to give a bit of leeway for long
    567 			// type names
    568 			if (linelen > 72) {
    569 				n += linelen;
    570 				linelen = (indent + 1) * 8;
    571 				n += fmt::fprintln(out)?;
    572 				for (let i = 0z; i <= indent; i += 1) {
    573 					n += fmt::fprint(out, "\t")?;
    574 				};
    575 			} else {
    576 				linelen += fmt::fprint(out, " ")?;
    577 			};
    578 		};
    579 		n += linelen;
    580 		n += render(out, syn::PUNCTUATION)?;
    581 		n += fmt::fprint(out, ")")?;
    582 	};
    583 	return n;
    584 };