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 };