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