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