hare

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

tty.ha (16657B)


      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 memio;
     14 use os;
     15 use strings;
     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::GLOBAL)?;
    161 			n += unparse::ident(out, g[i].ident)?;
    162 			match (g[i]._type) {
    163 			case null =>
    164 				yield;
    165 			case let ty: *ast::_type =>
    166 				n += render(out, syn::PUNCTUATION)?;
    167 				n += fmt::fprint(out, ": ")?;
    168 				n += type_tty(out, 0, *ty)?;
    169 			};
    170 			if (i + 1 < len(g)) {
    171 				n += render(out, syn::PUNCTUATION)?;
    172 				n += fmt::fprint(out, ", ")?;
    173 			};
    174 			n += render(out, syn::NORMAL)?;
    175 		};
    176 	case let c: []ast::decl_const =>
    177 		n += render(out, syn::KEYWORD)?;
    178 		n += fmt::fprintf(out, "def ")?;
    179 		for (let i = 0z; i < len(c); i += 1) {
    180 			n += render(out, syn::CONSTANT)?;
    181 			n += unparse::ident(out, c[i].ident)?;
    182 			n += render(out, syn::PUNCTUATION)?;
    183 			match (c[i]._type) {
    184 			case null =>
    185 				yield;
    186 			case let ty: *ast::_type =>
    187 				n += render(out, syn::PUNCTUATION)?;
    188 				n += fmt::fprint(out, ": ")?;
    189 				n += type_tty(out, 0, *ty)?;
    190 			};
    191 			if (i + 1 < len(c)) {
    192 				n += render(out, syn::PUNCTUATION)?;
    193 				n += fmt::fprint(out, ", ")?;
    194 			};
    195 		};
    196 	case let t: []ast::decl_type =>
    197 		n += render(out, syn::KEYWORD)?;
    198 		n += fmt::fprint(out, "type ")?;
    199 		for (let i = 0z; i < len(t); i += 1) {
    200 			n += render(out, syn::TYPEDEF)?;
    201 			n += unparse::ident(out, t[i].ident)?;
    202 			n += render(out, syn::PUNCTUATION)?;
    203 			n += fmt::fprint(out, " = ")?;
    204 			n += type_tty(out, 0, t[i]._type)?;
    205 			if (i + 1 < len(t)) {
    206 				n += render(out, syn::PUNCTUATION)?;
    207 				n += fmt::fprint(out, ", ")?;
    208 			};
    209 		};
    210 	case let f: ast::decl_func =>
    211 		n += render(out, syn::ATTRIBUTE)?;
    212 		n += fmt::fprint(out, switch (f.attrs) {
    213 		case ast::fndecl_attrs::NONE =>
    214 			yield "";
    215 		case ast::fndecl_attrs::FINI =>
    216 			yield "@fini ";
    217 		case ast::fndecl_attrs::INIT =>
    218 			yield "@init ";
    219 		case ast::fndecl_attrs::TEST =>
    220 			yield "@test ";
    221 		})?;
    222 		n += render(out, syn::NORMAL)?;
    223 
    224 		let p = f.prototype.repr as ast::func_type;
    225 		if (p.attrs & ast::func_attrs::NORETURN != 0) {
    226 			n += render(out, syn::ATTRIBUTE)?;
    227 			n += fmt::fprint(out, "@noreturn ")?;
    228 			n += render(out, syn::NORMAL)?;
    229 		};
    230 		if (len(f.symbol) != 0) {
    231 			n += render(out, syn::ATTRIBUTE)?;
    232 			n += fmt::fprintf(out, "@symbol(")?;
    233 			n += render(out, syn::STRING)?;
    234 			n += fmt::fprintf(out, `"{}"`, f.symbol)?;
    235 			n += render(out, syn::ATTRIBUTE)?;
    236 			n += fmt::fprintf(out, ") ")?;
    237 			n += render(out, syn::NORMAL)?;
    238 		};
    239 		n += render(out, syn::KEYWORD)?;
    240 		n += fmt::fprint(out, "fn ")?;
    241 		n += render(out, syn::FUNCTION)?;
    242 		n += unparse::ident(out, f.ident)?;
    243 		n += fmt::fprint(out, "\x1b[0m")?;
    244 		n += prototype_tty(out, 0,
    245 			f.prototype.repr as ast::func_type)?;
    246 	};
    247 	n += render(out, syn::PUNCTUATION)?;
    248 	n += fmt::fprint(out, ";")?;
    249 	return n;
    250 };
    251 
    252 fn prototype_tty(
    253 	out: io::handle,
    254 	indent: size,
    255 	t: ast::func_type,
    256 ) (size | io::error) = {
    257 	let n = 0z;
    258 	n += render(out, syn::PUNCTUATION)?;
    259 	n += fmt::fprint(out, "(")?;
    260 
    261 	let typenames: []str = [];
    262 	// TODO: https://todo.sr.ht/~sircmpwn/hare/581
    263 	if (len(t.params) > 0) {
    264 		typenames = alloc([""...], len(t.params));
    265 	};
    266 	defer strings::freeall(typenames);
    267 	let retname = "";
    268 	defer free(retname);
    269 
    270 	// estimate length of prototype to determine if it should span multiple
    271 	// lines
    272 	const linelen = if (len(t.params) == 0) {
    273 		let strm = memio::dynamic();
    274 		defer io::close(&strm)!;
    275 		type_tty(&strm, indent, *t.result)?;
    276 		retname = strings::dup(memio::string(&strm)!);
    277 		yield 0z; // only use one line if there's no parameters
    278 	} else {
    279 		let strm = memio::dynamic();
    280 		defer io::close(&strm)!;
    281 		let linelen = indent * 8 + 5;
    282 		linelen += if (len(t.params) != 0) len(t.params) * 3 - 1 else 0;
    283 		for (let i = 0z; i < len(t.params); i += 1) {
    284 			const param = t.params[i];
    285 			linelen += unparse::_type(&strm, indent, *param._type)?;
    286 			typenames[i] = strings::dup(memio::string(&strm)!);
    287 			linelen += if (param.name == "") 1 else len(param.name);
    288 			memio::reset(&strm);
    289 		};
    290 		switch (t.variadism) {
    291 		case variadism::NONE => void;
    292 		case variadism::HARE =>
    293 			linelen += 3;
    294 		case variadism::C =>
    295 			linelen += 5;
    296 		};
    297 		linelen += type_tty(&strm, indent, *t.result)?;
    298 		retname = strings::dup(memio::string(&strm)!);
    299 		yield linelen;
    300 	};
    301 
    302 	// use 72 instead of 80 to give a bit of leeway for preceding text
    303 	if (linelen > 72) {
    304 		indent += 1;
    305 		for (let i = 0z; i < len(t.params); i += 1) {
    306 			const param = t.params[i];
    307 			n += newline(out, indent)?;
    308 			n += render(out, syn::SECONDARY)?;
    309 			n += fmt::fprint(out,
    310 				if (param.name == "") "_" else param.name)?;
    311 			n += render(out, syn::PUNCTUATION)?;
    312 			n += fmt::fprint(out, ": ")?;
    313 			n += render(out, syn::TYPE)?;
    314 			n += fmt::fprint(out, typenames[i])?;
    315 			if (i + 1 == len(t.params)
    316 					&& t.variadism == variadism::HARE) {
    317 				n += render(out, syn::OPERATOR)?;
    318 				n += fmt::fprint(out, "...")?;
    319 			} else {
    320 				n += render(out, syn::PUNCTUATION)?;
    321 				n += fmt::fprint(out, ",")?;
    322 			};
    323 		};
    324 		if (t.variadism == variadism::C) {
    325 			n += newline(out, indent)?;
    326 			n += render(out, syn::OPERATOR)?;
    327 			n += fmt::fprint(out, "...")?;
    328 		};
    329 		indent -= 1;
    330 		n += newline(out, indent)?;
    331 	} else for (let i = 0z; i < len(t.params); i += 1) {
    332 		const param = t.params[i];
    333 		n += render(out, syn::SECONDARY)?;
    334 		n += fmt::fprint(out,
    335 			if (param.name == "") "_" else param.name)?;
    336 		n += render(out, syn::PUNCTUATION)?;
    337 		n += fmt::fprint(out, ": ")?;
    338 		n += render(out, syn::TYPE)?;
    339 		n += fmt::fprint(out, typenames[i])?;
    340 		if (i + 1 == len(t.params)) {
    341 			switch (t.variadism) {
    342 			case variadism::NONE => void;
    343 			case variadism::HARE =>
    344 				n += render(out, syn::OPERATOR)?;
    345 				n += fmt::fprint(out, "...")?;
    346 			case variadism::C =>
    347 				n += render(out, syn::PUNCTUATION)?;
    348 				n += fmt::fprint(out, ", ")?;
    349 				n += render(out, syn::OPERATOR)?;
    350 				n += fmt::fprint(out, "...")?;
    351 			};
    352 		} else {
    353 			n += render(out, syn::PUNCTUATION)?;
    354 			n += fmt::fprint(out, ", ")?;
    355 		};
    356 	};
    357 
    358 	n += render(out, syn::PUNCTUATION)?;
    359 	n += fmt::fprint(out, ")", retname)?;
    360 	return n;
    361 };
    362 
    363 // Forked from [[hare::unparse]]
    364 fn struct_union_type_tty(
    365 	out: io::handle,
    366 	indent: size,
    367 	t: ast::_type,
    368 ) (size | io::error) = {
    369 	let n = 0z;
    370 	let membs = match (t.repr) {
    371 	case let st: ast::struct_type =>
    372 		n += render(out, syn::TYPE)?;
    373 		n += fmt::fprint(out, "struct")?;
    374 		if (st.packed) {
    375 			n += render(out, syn::ATTRIBUTE)?;
    376 			n += fmt::fprint(out, " @packed")?;
    377 		};
    378 		n += render(out, syn::PUNCTUATION)?;
    379 		n += fmt::fprint(out, " {")?;
    380 		yield st.members: []ast::struct_member;
    381 	case let ut: ast::union_type =>
    382 		n += render(out, syn::TYPE)?;
    383 		n += fmt::fprint(out, "union")?;
    384 		n += render(out, syn::PUNCTUATION)?;
    385 		n += fmt::fprint(out, " {")?;
    386 		yield ut: []ast::struct_member;
    387 	};
    388 
    389 	indent += 1z;
    390 	for (let i = 0z; i < len(membs); i += 1) {
    391 		n += newline(out, indent)?;
    392 		if (membs[i].docs != "") {
    393 			n += docs_tty(out, membs[i].docs, indent)?;
    394 		};
    395 
    396 		match (membs[i]._offset) {
    397 		case null => void;
    398 		case let ex: *ast::expr =>
    399 			n += render(out, syn::ATTRIBUTE)?;
    400 			n += fmt::fprint(out, "@offset(")?;
    401 			n += render(out, syn::NUMBER)?;
    402 			n += unparse::expr(out, indent, *ex)?;
    403 			n += render(out, syn::ATTRIBUTE)?;
    404 			n += fmt::fprint(out, ")")?;
    405 			n += render(out, syn::NORMAL)?;
    406 		};
    407 
    408 		match (membs[i].member) {
    409 		case let se: ast::struct_embedded =>
    410 			n += type_tty(out, indent, *se)?;
    411 		case let sa: ast::struct_alias =>
    412 			n += unparse::ident(out, sa)?;
    413 		case let sf: ast::struct_field =>
    414 			n += render(out, syn::SECONDARY)?;
    415 			n += fmt::fprint(out, sf.name)?;
    416 			n += render(out, syn::PUNCTUATION)?;
    417 			n += fmt::fprint(out, ": ")?;
    418 			n += type_tty(out, indent, *sf._type)?;
    419 		};
    420 
    421 		n += render(out, syn::PUNCTUATION)?;
    422 		n += fmt::fprint(out, ",")?;
    423 	};
    424 
    425 	indent -= 1;
    426 	n += newline(out, indent)?;
    427 	n += render(out, syn::PUNCTUATION)?;
    428 	n += fmt::fprint(out, "}")?;
    429 	return n;
    430 };
    431 
    432 // Forked from [[hare::unparse]]
    433 fn type_tty(
    434 	out: io::handle,
    435 	indent: size,
    436 	t: ast::_type,
    437 ) (size | io::error) = {
    438 	let n = 0z;
    439 	if (t.flags & ast::type_flag::CONST != 0
    440 			&& !(t.repr is ast::func_type)) {
    441 		n += render(out, syn::TYPE)?;
    442 		n += fmt::fprint(out, "const ")?;
    443 	};
    444 	if (t.flags & ast::type_flag::ERROR != 0) {
    445 		n += render(out, syn::OPERATOR)?;
    446 		n += fmt::fprint(out, "!")?;
    447 	};
    448 
    449 	match (t.repr) {
    450 	case let a: ast::alias_type =>
    451 		if (a.unwrap) {
    452 			n += render(out, syn::OPERATOR)?;
    453 			n += fmt::fprint(out, "...")?;
    454 		};
    455 		n += render(out, syn::TYPE)?;
    456 		n += unparse::ident(out, a.ident)?;
    457 	case let b: ast::builtin_type =>
    458 		n += render(out, syn::TYPE)?;
    459 		n += fmt::fprintf(out, "{}", unparse::builtin_type(b))?;
    460 	case let e: ast::enum_type =>
    461 		n += render(out, syn::TYPE)?;
    462 		n += fmt::fprint(out, "enum ")?;
    463 		if (e.storage != ast::builtin_type::INT) {
    464 			n += fmt::fprintf(out,
    465 				"{} ", unparse::builtin_type(e.storage))?;
    466 		};
    467 		n += render(out, syn::PUNCTUATION)?;
    468 		n += fmt::fprintln(out, "{")?;
    469 		indent += 1;
    470 		for (let i = 0z; i < len(e.values); i += 1) {
    471 			for (let i = 0z; i < indent; i += 1) {
    472 				n += fmt::fprint(out, "\t")?;
    473 			};
    474 			let value = e.values[i];
    475 			let wrotedocs = false;
    476 			if (value.docs != "") {
    477 				// Check if comment should go above or next to
    478 				// field
    479 				if (multiline_comment(value.docs)) {
    480 					n += docs_tty(out, value.docs, indent)?;
    481 					wrotedocs = true;
    482 				};
    483 			};
    484 			n += render(out, syn::SECONDARY)?;
    485 			n += fmt::fprint(out, value.name)?;
    486 			match (value.value) {
    487 			case null => void;
    488 			case let e: *ast::expr =>
    489 				n += render(out, syn::OPERATOR)?;
    490 				n += fmt::fprint(out, " = ")?;
    491 				n += render(out, syn::NORMAL)?;
    492 				n += unparse::expr(out, indent, *e)?;
    493 			};
    494 			n += render(out, syn::PUNCTUATION)?;
    495 			n += fmt::fprint(out, ",")?;
    496 			if (value.docs != "" && !wrotedocs) {
    497 				n += fmt::fprint(out, " ")?;
    498 				n += docs_tty(out, value.docs, 0)?;
    499 			} else {
    500 				n += fmt::fprintln(out)?;
    501 			};
    502 		};
    503 		indent -= 1;
    504 		for (let i = 0z; i < indent; i += 1) {
    505 			n += fmt::fprint(out, "\t")?;
    506 		};
    507 		n += render(out, syn::PUNCTUATION)?;
    508 		n += fmt::fprint(out, "}")?;
    509 	case let f: ast::func_type =>
    510 		if (f.attrs & ast::func_attrs::NORETURN != 0) {
    511 			n += render(out, syn::ATTRIBUTE)?;
    512 			n += fmt::fprint(out, "@noreturn ")?;
    513 		};
    514 		n += render(out, syn::TYPE)?;
    515 		n += fmt::fprint(out, "fn")?;
    516 		n += prototype_tty(out, indent, f)?;
    517 	case let l: ast::list_type =>
    518 		n += render(out, syn::OPERATOR)?;
    519 		n += fmt::fprint(out, "[")?;
    520 		match (l.length) {
    521 		case ast::len_slice => void;
    522 		case ast::len_unbounded =>
    523 			n += fmt::fprint(out, "*")?;
    524 		case ast::len_contextual =>
    525 			n += fmt::fprint(out, "_")?;
    526 		case let e: *ast::expr =>
    527 			n += unparse::expr(out, indent, *e)?;
    528 		};
    529 		n += render(out, syn::OPERATOR)?;
    530 		n += fmt::fprint(out, "]")?;
    531 		n += type_tty(out, indent, *l.members)?;
    532 	case let p: ast::pointer_type =>
    533 		if (p.flags & ast::pointer_flag::NULLABLE != 0) {
    534 			n += render(out, syn::TYPE)?;
    535 			n += fmt::fprint(out, "nullable ")?;
    536 		};
    537 		n += render(out, syn::OPERATOR)?;
    538 		n += fmt::fprint(out, "*")?;
    539 		n += type_tty(out, indent, *p.referent)?;
    540 	case ast::struct_type =>
    541 		n += struct_union_type_tty(out, indent, t)?;
    542 	case ast::union_type =>
    543 		n += struct_union_type_tty(out, indent, t)?;
    544 	case let t: ast::tagged_type =>
    545 		// rough estimate of current line length
    546 		let linelen: size = n + (indent + 1) * 8;
    547 		n = 0;
    548 		n += render(out, syn::PUNCTUATION)?;
    549 		linelen += fmt::fprint(out, "(")?;
    550 		for (let i = 0z; i < len(t); i += 1) {
    551 			linelen += type_tty(out, indent, *t[i])?;
    552 			if (i + 1 == len(t)) break;
    553 			n += render(out, syn::PUNCTUATION)?;
    554 			linelen += fmt::fprint(out, " |")?;
    555 			// use 72 instead of 80 to give a bit of leeway for long
    556 			// type names
    557 			if (linelen > 72) {
    558 				n += linelen;
    559 				linelen = (indent + 1) * 8;
    560 				n += fmt::fprintln(out)?;
    561 				for (let i = 0z; i <= indent; i += 1) {
    562 					n += fmt::fprint(out, "\t")?;
    563 				};
    564 			} else {
    565 				linelen += fmt::fprint(out, " ")?;
    566 			};
    567 		};
    568 		n += linelen;
    569 		n += render(out, syn::PUNCTUATION)?;
    570 		n += fmt::fprint(out, ")")?;
    571 	case let t: ast::tuple_type =>
    572 		// rough estimate of current line length
    573 		let linelen: size = n + (indent + 1) * 8;
    574 		n = 0;
    575 		n += render(out, syn::PUNCTUATION)?;
    576 		linelen += fmt::fprint(out, "(")?;
    577 		for (let i = 0z; i < len(t); i += 1) {
    578 			linelen += type_tty(out, indent, *t[i])?;
    579 			if (i + 1 == len(t)) break;
    580 			n += render(out, syn::PUNCTUATION)?;
    581 			linelen += fmt::fprint(out, ",")?;
    582 			// use 72 instead of 80 to give a bit of leeway for long
    583 			// type names
    584 			if (linelen > 72) {
    585 				n += linelen;
    586 				linelen = (indent + 1) * 8;
    587 				n += fmt::fprintln(out)?;
    588 				for (let i = 0z; i <= indent; i += 1) {
    589 					n += fmt::fprint(out, "\t")?;
    590 				};
    591 			} else {
    592 				linelen += fmt::fprint(out, " ")?;
    593 			};
    594 		};
    595 		n += linelen;
    596 		n += render(out, syn::PUNCTUATION)?;
    597 		n += fmt::fprint(out, ")")?;
    598 	};
    599 	return n;
    600 };