main.zig (33691B) - Raw
1 const std = @import("std"); 2 const log = std.log; 3 const assert = std.debug.assert; 4 const Ast = std.zig.Ast; 5 const Walk = @import("Walk"); 6 const markdown = @import("markdown.zig"); 7 const Decl = Walk.Decl; 8 9 const fileSourceHtml = @import("html_render.zig").fileSourceHtml; 10 const appendEscaped = @import("html_render.zig").appendEscaped; 11 const resolveDeclLink = @import("html_render.zig").resolveDeclLink; 12 const missing_feature_url_escape = @import("html_render.zig").missing_feature_url_escape; 13 14 const gpa = std.heap.wasm_allocator; 15 16 const js = struct { 17 /// Keep in sync with the `LOG_` constants in `main.js`. 18 const LogLevel = enum(u8) { 19 err, 20 warn, 21 info, 22 debug, 23 }; 24 25 extern "js" fn log(level: LogLevel, ptr: [*]const u8, len: usize) void; 26 }; 27 28 pub const std_options: std.Options = .{ 29 .logFn = logFn, 30 //.log_level = .debug, 31 }; 32 33 pub fn panic(msg: []const u8, st: ?*std.builtin.StackTrace, addr: ?usize) noreturn { 34 _ = st; 35 _ = addr; 36 log.err("panic: {s}", .{msg}); 37 @trap(); 38 } 39 40 fn logFn( 41 comptime message_level: log.Level, 42 comptime scope: @TypeOf(.enum_literal), 43 comptime format: []const u8, 44 args: anytype, 45 ) void { 46 const prefix = if (scope == .default) "" else @tagName(scope) ++ ": "; 47 var buf: [500]u8 = undefined; 48 const line = std.fmt.bufPrint(&buf, prefix ++ format, args) catch l: { 49 buf[buf.len - 3 ..][0..3].* = "...".*; 50 break :l &buf; 51 }; 52 js.log(@field(js.LogLevel, @tagName(message_level)), line.ptr, line.len); 53 } 54 55 export fn alloc(n: usize) [*]u8 { 56 const slice = gpa.alloc(u8, n) catch @panic("OOM"); 57 return slice.ptr; 58 } 59 60 export fn unpack(tar_ptr: [*]u8, tar_len: usize) void { 61 const tar_bytes = tar_ptr[0..tar_len]; 62 //log.debug("received {d} bytes of tar file", .{tar_bytes.len}); 63 64 unpackInner(tar_bytes) catch |err| { 65 std.debug.panic("unable to unpack tar: {s}", .{@errorName(err)}); 66 }; 67 } 68 69 var query_string: std.ArrayListUnmanaged(u8) = .empty; 70 var query_results: std.ArrayListUnmanaged(Decl.Index) = .empty; 71 72 /// Resizes the query string to be the correct length; returns the pointer to 73 /// the query string. 74 export fn query_begin(query_string_len: usize) [*]u8 { 75 query_string.resize(gpa, query_string_len) catch @panic("OOM"); 76 return query_string.items.ptr; 77 } 78 79 /// Executes the query. Returns the pointer to the query results which is an 80 /// array of u32. 81 /// The first element is the length of the array. 82 /// Subsequent elements are Decl.Index values which are all public 83 /// declarations. 84 export fn query_exec(ignore_case: bool) [*]Decl.Index { 85 const query = query_string.items; 86 log.debug("querying '{s}'", .{query}); 87 query_exec_fallible(query, ignore_case) catch |err| switch (err) { 88 error.OutOfMemory => @panic("OOM"), 89 }; 90 query_results.items[0] = @enumFromInt(query_results.items.len - 1); 91 return query_results.items.ptr; 92 } 93 94 const max_matched_items = 1000; 95 96 fn query_exec_fallible(query: []const u8, ignore_case: bool) !void { 97 const Score = packed struct(u32) { 98 points: u16, 99 segments: u16, 100 }; 101 const g = struct { 102 var full_path_search_text: std.ArrayListUnmanaged(u8) = .empty; 103 var full_path_search_text_lower: std.ArrayListUnmanaged(u8) = .empty; 104 var doc_search_text: std.ArrayListUnmanaged(u8) = .empty; 105 /// Each element matches a corresponding query_results element. 106 var scores: std.ArrayListUnmanaged(Score) = .empty; 107 }; 108 109 // First element stores the size of the list. 110 try query_results.resize(gpa, 1); 111 // Corresponding point value is meaningless and therefore undefined. 112 try g.scores.resize(gpa, 1); 113 114 decl_loop: for (Walk.decls.items, 0..) |*decl, decl_index| { 115 const info = decl.extra_info(); 116 if (!info.is_pub) continue; 117 118 try decl.reset_with_path(&g.full_path_search_text); 119 if (decl.parent != .none) 120 try Decl.append_parent_ns(&g.full_path_search_text, decl.parent); 121 try g.full_path_search_text.appendSlice(gpa, info.name); 122 123 try g.full_path_search_text_lower.resize(gpa, g.full_path_search_text.items.len); 124 @memcpy(g.full_path_search_text_lower.items, g.full_path_search_text.items); 125 126 const ast = decl.file.get_ast(); 127 if (info.first_doc_comment.unwrap()) |first_doc_comment| { 128 try collect_docs(&g.doc_search_text, ast, first_doc_comment); 129 } 130 131 if (ignore_case) { 132 ascii_lower(g.full_path_search_text_lower.items); 133 ascii_lower(g.doc_search_text.items); 134 } 135 136 var it = std.mem.tokenizeScalar(u8, query, ' '); 137 var points: u16 = 0; 138 var bypass_limit = false; 139 while (it.next()) |term| { 140 // exact, case sensitive match of full decl path 141 if (std.mem.eql(u8, g.full_path_search_text.items, term)) { 142 points += 4; 143 bypass_limit = true; 144 continue; 145 } 146 // exact, case sensitive match of just decl name 147 if (std.mem.eql(u8, info.name, term)) { 148 points += 3; 149 bypass_limit = true; 150 continue; 151 } 152 // substring, case insensitive match of full decl path 153 if (std.mem.indexOf(u8, g.full_path_search_text_lower.items, term) != null) { 154 points += 2; 155 continue; 156 } 157 if (std.mem.indexOf(u8, g.doc_search_text.items, term) != null) { 158 points += 1; 159 continue; 160 } 161 continue :decl_loop; 162 } 163 164 if (query_results.items.len < max_matched_items or bypass_limit) { 165 try query_results.append(gpa, @enumFromInt(decl_index)); 166 try g.scores.append(gpa, .{ 167 .points = points, 168 .segments = @intCast(count_scalar(g.full_path_search_text.items, '.')), 169 }); 170 } 171 } 172 173 const sort_context: struct { 174 pub fn swap(sc: @This(), a_index: usize, b_index: usize) void { 175 _ = sc; 176 std.mem.swap(Score, &g.scores.items[a_index], &g.scores.items[b_index]); 177 std.mem.swap(Decl.Index, &query_results.items[a_index], &query_results.items[b_index]); 178 } 179 180 pub fn lessThan(sc: @This(), a_index: usize, b_index: usize) bool { 181 _ = sc; 182 const a_score = g.scores.items[a_index]; 183 const b_score = g.scores.items[b_index]; 184 if (b_score.points < a_score.points) { 185 return true; 186 } else if (b_score.points > a_score.points) { 187 return false; 188 } else if (a_score.segments < b_score.segments) { 189 return true; 190 } else if (a_score.segments > b_score.segments) { 191 return false; 192 } else { 193 const a_decl = query_results.items[a_index]; 194 const b_decl = query_results.items[b_index]; 195 const a_file_path = a_decl.get().file.path(); 196 const b_file_path = b_decl.get().file.path(); 197 // This neglects to check the local namespace inside the file. 198 return std.mem.lessThan(u8, b_file_path, a_file_path); 199 } 200 } 201 } = .{}; 202 203 std.mem.sortUnstableContext(1, query_results.items.len, sort_context); 204 205 if (query_results.items.len > max_matched_items) 206 query_results.shrinkRetainingCapacity(max_matched_items); 207 } 208 209 const String = Slice(u8); 210 211 fn Slice(T: type) type { 212 return packed struct(u64) { 213 ptr: u32, 214 len: u32, 215 216 fn init(s: []const T) @This() { 217 return .{ 218 .ptr = @intFromPtr(s.ptr), 219 .len = s.len, 220 }; 221 } 222 }; 223 } 224 225 const ErrorIdentifier = packed struct(u64) { 226 token_index: Ast.TokenIndex, 227 decl_index: Decl.Index, 228 229 fn hasDocs(ei: ErrorIdentifier) bool { 230 const decl_index = ei.decl_index; 231 const ast = decl_index.get().file.get_ast(); 232 const token_index = ei.token_index; 233 if (token_index == 0) return false; 234 return ast.tokenTag(token_index - 1) == .doc_comment; 235 } 236 237 fn html(ei: ErrorIdentifier, base_decl: Decl.Index, out: *std.ArrayListUnmanaged(u8)) Oom!void { 238 const decl_index = ei.decl_index; 239 const ast = decl_index.get().file.get_ast(); 240 const name = ast.tokenSlice(ei.token_index); 241 const has_link = base_decl != decl_index; 242 243 try out.appendSlice(gpa, "<dt>"); 244 try out.appendSlice(gpa, name); 245 if (has_link) { 246 try out.appendSlice(gpa, " <a href=\"#"); 247 _ = missing_feature_url_escape; 248 try decl_index.get().fqn(out); 249 try out.appendSlice(gpa, "\">"); 250 try out.appendSlice(gpa, decl_index.get().extra_info().name); 251 try out.appendSlice(gpa, "</a>"); 252 } 253 try out.appendSlice(gpa, "</dt>"); 254 255 if (Decl.findFirstDocComment(ast, ei.token_index).unwrap()) |first_doc_comment| { 256 try out.appendSlice(gpa, "<dd>"); 257 try render_docs(out, decl_index, first_doc_comment, false); 258 try out.appendSlice(gpa, "</dd>"); 259 } 260 } 261 }; 262 263 var string_result: std.ArrayListUnmanaged(u8) = .empty; 264 var error_set_result: std.StringArrayHashMapUnmanaged(ErrorIdentifier) = .empty; 265 266 export fn decl_error_set(decl_index: Decl.Index) Slice(ErrorIdentifier) { 267 return Slice(ErrorIdentifier).init(decl_error_set_fallible(decl_index) catch @panic("OOM")); 268 } 269 270 export fn error_set_node_list(base_decl: Decl.Index, node: Ast.Node.Index) Slice(ErrorIdentifier) { 271 error_set_result.clearRetainingCapacity(); 272 addErrorsFromExpr(base_decl, &error_set_result, node) catch @panic("OOM"); 273 sort_error_set_result(); 274 return Slice(ErrorIdentifier).init(error_set_result.values()); 275 } 276 277 export fn fn_error_set_decl(decl_index: Decl.Index, node: Ast.Node.Index) Decl.Index { 278 return switch (decl_index.get().file.categorize_expr(node)) { 279 .alias => |aliasee| fn_error_set_decl(aliasee, aliasee.get().ast_node), 280 else => decl_index, 281 }; 282 } 283 284 fn decl_error_set_fallible(decl_index: Decl.Index) Oom![]ErrorIdentifier { 285 error_set_result.clearRetainingCapacity(); 286 try addErrorsFromDecl(decl_index, &error_set_result); 287 sort_error_set_result(); 288 return error_set_result.values(); 289 } 290 291 fn sort_error_set_result() void { 292 const sort_context: struct { 293 pub fn lessThan(sc: @This(), a_index: usize, b_index: usize) bool { 294 _ = sc; 295 const a_name = error_set_result.keys()[a_index]; 296 const b_name = error_set_result.keys()[b_index]; 297 return std.mem.lessThan(u8, a_name, b_name); 298 } 299 } = .{}; 300 error_set_result.sortUnstable(sort_context); 301 } 302 303 fn addErrorsFromDecl( 304 decl_index: Decl.Index, 305 out: *std.StringArrayHashMapUnmanaged(ErrorIdentifier), 306 ) Oom!void { 307 switch (decl_index.get().categorize()) { 308 .error_set => |node| try addErrorsFromExpr(decl_index, out, node), 309 .alias => |aliasee| try addErrorsFromDecl(aliasee, out), 310 else => |cat| log.debug("unable to addErrorsFromDecl: {any}", .{cat}), 311 } 312 } 313 314 fn addErrorsFromExpr( 315 decl_index: Decl.Index, 316 out: *std.StringArrayHashMapUnmanaged(ErrorIdentifier), 317 node: Ast.Node.Index, 318 ) Oom!void { 319 const decl = decl_index.get(); 320 const ast = decl.file.get_ast(); 321 322 switch (decl.file.categorize_expr(node)) { 323 .error_set => |n| switch (ast.nodeTag(n)) { 324 .error_set_decl => { 325 try addErrorsFromNode(decl_index, out, node); 326 }, 327 .merge_error_sets => { 328 const lhs, const rhs = ast.nodeData(n).node_and_node; 329 try addErrorsFromExpr(decl_index, out, lhs); 330 try addErrorsFromExpr(decl_index, out, rhs); 331 }, 332 else => unreachable, 333 }, 334 .alias => |aliasee| { 335 try addErrorsFromDecl(aliasee, out); 336 }, 337 else => return, 338 } 339 } 340 341 fn addErrorsFromNode( 342 decl_index: Decl.Index, 343 out: *std.StringArrayHashMapUnmanaged(ErrorIdentifier), 344 node: Ast.Node.Index, 345 ) Oom!void { 346 const decl = decl_index.get(); 347 const ast = decl.file.get_ast(); 348 const error_token = ast.nodeMainToken(node); 349 var tok_i = error_token + 2; 350 while (true) : (tok_i += 1) switch (ast.tokenTag(tok_i)) { 351 .doc_comment, .comma => {}, 352 .identifier => { 353 const name = ast.tokenSlice(tok_i); 354 const gop = try out.getOrPut(gpa, name); 355 // If there are more than one, take the one with doc comments. 356 // If they both have doc comments, prefer the existing one. 357 const new: ErrorIdentifier = .{ 358 .token_index = tok_i, 359 .decl_index = decl_index, 360 }; 361 if (!gop.found_existing or 362 (!gop.value_ptr.hasDocs() and new.hasDocs())) 363 { 364 gop.value_ptr.* = new; 365 } 366 }, 367 .r_brace => break, 368 else => unreachable, 369 }; 370 } 371 372 export fn type_fn_fields(decl_index: Decl.Index) Slice(Ast.Node.Index) { 373 return decl_fields(decl_index); 374 } 375 376 export fn decl_fields(decl_index: Decl.Index) Slice(Ast.Node.Index) { 377 return Slice(Ast.Node.Index).init(decl_fields_fallible(decl_index) catch @panic("OOM")); 378 } 379 380 export fn decl_params(decl_index: Decl.Index) Slice(Ast.Node.Index) { 381 return Slice(Ast.Node.Index).init(decl_params_fallible(decl_index) catch @panic("OOM")); 382 } 383 384 fn decl_fields_fallible(decl_index: Decl.Index) ![]Ast.Node.Index { 385 const decl = decl_index.get(); 386 const ast = decl.file.get_ast(); 387 388 switch (decl.categorize()) { 389 .type_function => { 390 // If the type function returns a reference to another type function, get the fields from there 391 if (decl.get_type_fn_return_type_fn()) |function_decl| { 392 return decl_fields_fallible(function_decl); 393 } 394 // If the type function returns a container, such as a `struct`, read that container's fields 395 if (decl.get_type_fn_return_expr()) |return_expr| { 396 switch (ast.nodeTag(return_expr)) { 397 .container_decl, .container_decl_trailing, .container_decl_two, .container_decl_two_trailing, .container_decl_arg, .container_decl_arg_trailing => { 398 return ast_decl_fields_fallible(ast, return_expr); 399 }, 400 else => {}, 401 } 402 } 403 return &.{}; 404 }, 405 else => { 406 const value_node = decl.value_node() orelse return &.{}; 407 return ast_decl_fields_fallible(ast, value_node); 408 }, 409 } 410 } 411 412 fn ast_decl_fields_fallible(ast: *Ast, ast_index: Ast.Node.Index) ![]Ast.Node.Index { 413 const g = struct { 414 var result: std.ArrayListUnmanaged(Ast.Node.Index) = .empty; 415 }; 416 g.result.clearRetainingCapacity(); 417 var buf: [2]Ast.Node.Index = undefined; 418 const container_decl = ast.fullContainerDecl(&buf, ast_index) orelse return &.{}; 419 for (container_decl.ast.members) |member_node| switch (ast.nodeTag(member_node)) { 420 .container_field_init, 421 .container_field_align, 422 .container_field, 423 => try g.result.append(gpa, member_node), 424 425 else => continue, 426 }; 427 return g.result.items; 428 } 429 430 fn decl_params_fallible(decl_index: Decl.Index) ![]Ast.Node.Index { 431 const g = struct { 432 var result: std.ArrayListUnmanaged(Ast.Node.Index) = .empty; 433 }; 434 g.result.clearRetainingCapacity(); 435 const decl = decl_index.get(); 436 const ast = decl.file.get_ast(); 437 const value_node = decl.value_node() orelse return &.{}; 438 var buf: [1]Ast.Node.Index = undefined; 439 const fn_proto = ast.fullFnProto(&buf, value_node) orelse return &.{}; 440 try g.result.appendSlice(gpa, fn_proto.ast.params); 441 return g.result.items; 442 } 443 444 export fn error_html(base_decl: Decl.Index, error_identifier: ErrorIdentifier) String { 445 string_result.clearRetainingCapacity(); 446 error_identifier.html(base_decl, &string_result) catch @panic("OOM"); 447 return String.init(string_result.items); 448 } 449 450 export fn decl_field_html(decl_index: Decl.Index, field_node: Ast.Node.Index) String { 451 string_result.clearRetainingCapacity(); 452 decl_field_html_fallible(&string_result, decl_index, field_node) catch @panic("OOM"); 453 return String.init(string_result.items); 454 } 455 456 export fn decl_param_html(decl_index: Decl.Index, param_node: Ast.Node.Index) String { 457 string_result.clearRetainingCapacity(); 458 decl_param_html_fallible(&string_result, decl_index, param_node) catch @panic("OOM"); 459 return String.init(string_result.items); 460 } 461 462 fn decl_field_html_fallible( 463 out: *std.ArrayListUnmanaged(u8), 464 decl_index: Decl.Index, 465 field_node: Ast.Node.Index, 466 ) !void { 467 const decl = decl_index.get(); 468 const ast = decl.file.get_ast(); 469 try out.appendSlice(gpa, "<pre><code>"); 470 try fileSourceHtml(decl.file, out, field_node, .{}); 471 try out.appendSlice(gpa, "</code></pre>"); 472 473 const field = ast.fullContainerField(field_node).?; 474 475 if (Decl.findFirstDocComment(ast, field.firstToken()).unwrap()) |first_doc_comment| { 476 try out.appendSlice(gpa, "<div class=\"fieldDocs\">"); 477 try render_docs(out, decl_index, first_doc_comment, false); 478 try out.appendSlice(gpa, "</div>"); 479 } 480 } 481 482 fn decl_param_html_fallible( 483 out: *std.ArrayListUnmanaged(u8), 484 decl_index: Decl.Index, 485 param_node: Ast.Node.Index, 486 ) !void { 487 const decl = decl_index.get(); 488 const ast = decl.file.get_ast(); 489 const colon = ast.firstToken(param_node) - 1; 490 const name_token = colon - 1; 491 const first_doc_comment = f: { 492 var it = ast.firstToken(param_node); 493 while (it > 0) { 494 it -= 1; 495 switch (ast.tokenTag(it)) { 496 .doc_comment, .colon, .identifier, .keyword_comptime, .keyword_noalias => {}, 497 else => break, 498 } 499 } 500 break :f it + 1; 501 }; 502 const name = ast.tokenSlice(name_token); 503 504 try out.appendSlice(gpa, "<pre><code>"); 505 try appendEscaped(out, name); 506 try out.appendSlice(gpa, ": "); 507 try fileSourceHtml(decl.file, out, param_node, .{}); 508 try out.appendSlice(gpa, "</code></pre>"); 509 510 if (ast.tokenTag(first_doc_comment) == .doc_comment) { 511 try out.appendSlice(gpa, "<div class=\"fieldDocs\">"); 512 try render_docs(out, decl_index, first_doc_comment, false); 513 try out.appendSlice(gpa, "</div>"); 514 } 515 } 516 517 export fn decl_fn_proto_html(decl_index: Decl.Index, linkify_fn_name: bool) String { 518 const decl = decl_index.get(); 519 const ast = decl.file.get_ast(); 520 const proto_node = switch (ast.nodeTag(decl.ast_node)) { 521 .fn_decl => ast.nodeData(decl.ast_node).node_and_node[0], 522 523 .fn_proto, 524 .fn_proto_one, 525 .fn_proto_simple, 526 .fn_proto_multi, 527 => decl.ast_node, 528 529 else => unreachable, 530 }; 531 532 string_result.clearRetainingCapacity(); 533 fileSourceHtml(decl.file, &string_result, proto_node, .{ 534 .skip_doc_comments = true, 535 .skip_comments = true, 536 .collapse_whitespace = true, 537 .fn_link = if (linkify_fn_name) decl_index else .none, 538 }) catch |err| { 539 std.debug.panic("unable to render source: {s}", .{@errorName(err)}); 540 }; 541 return String.init(string_result.items); 542 } 543 544 export fn decl_source_html(decl_index: Decl.Index) String { 545 const decl = decl_index.get(); 546 547 string_result.clearRetainingCapacity(); 548 fileSourceHtml(decl.file, &string_result, decl.ast_node, .{}) catch |err| { 549 std.debug.panic("unable to render source: {s}", .{@errorName(err)}); 550 }; 551 return String.init(string_result.items); 552 } 553 554 export fn decl_doctest_html(decl_index: Decl.Index) String { 555 const decl = decl_index.get(); 556 const doctest_ast_node = decl.file.get().doctests.get(decl.ast_node) orelse 557 return String.init(""); 558 559 string_result.clearRetainingCapacity(); 560 fileSourceHtml(decl.file, &string_result, doctest_ast_node, .{}) catch |err| { 561 std.debug.panic("unable to render source: {s}", .{@errorName(err)}); 562 }; 563 return String.init(string_result.items); 564 } 565 566 export fn decl_fqn(decl_index: Decl.Index) String { 567 const decl = decl_index.get(); 568 string_result.clearRetainingCapacity(); 569 decl.fqn(&string_result) catch @panic("OOM"); 570 return String.init(string_result.items); 571 } 572 573 export fn decl_parent(decl_index: Decl.Index) Decl.Index { 574 const decl = decl_index.get(); 575 return decl.parent; 576 } 577 578 export fn fn_error_set(decl_index: Decl.Index) Ast.Node.OptionalIndex { 579 const decl = decl_index.get(); 580 const ast = decl.file.get_ast(); 581 var buf: [1]Ast.Node.Index = undefined; 582 const full = ast.fullFnProto(&buf, decl.ast_node).?; 583 const return_type = full.ast.return_type.unwrap().?; 584 return switch (ast.nodeTag(return_type)) { 585 .error_set_decl => return_type.toOptional(), 586 .error_union => ast.nodeData(return_type).node_and_node[0].toOptional(), 587 else => .none, 588 }; 589 } 590 591 export fn decl_file_path(decl_index: Decl.Index) String { 592 string_result.clearRetainingCapacity(); 593 string_result.appendSlice(gpa, decl_index.get().file.path()) catch @panic("OOM"); 594 return String.init(string_result.items); 595 } 596 597 export fn decl_category_name(decl_index: Decl.Index) String { 598 const decl = decl_index.get(); 599 const ast = decl.file.get_ast(); 600 const name = switch (decl.categorize()) { 601 .namespace, .container => |node| { 602 if (ast.nodeTag(decl.ast_node) == .root) 603 return String.init("struct"); 604 string_result.clearRetainingCapacity(); 605 var buf: [2]Ast.Node.Index = undefined; 606 const container_decl = ast.fullContainerDecl(&buf, node).?; 607 if (container_decl.layout_token) |t| { 608 if (ast.tokenTag(t) == .keyword_extern) { 609 string_result.appendSlice(gpa, "extern ") catch @panic("OOM"); 610 } 611 } 612 const main_token_tag = ast.tokenTag(container_decl.ast.main_token); 613 string_result.appendSlice(gpa, main_token_tag.lexeme().?) catch @panic("OOM"); 614 return String.init(string_result.items); 615 }, 616 .global_variable => "Global Variable", 617 .function => "Function", 618 .type_function => "Type Function", 619 .type, .type_type => "Type", 620 .error_set => "Error Set", 621 .global_const => "Constant", 622 .primitive => "Primitive Value", 623 .alias => "Alias", 624 }; 625 return String.init(name); 626 } 627 628 export fn decl_name(decl_index: Decl.Index) String { 629 const decl = decl_index.get(); 630 string_result.clearRetainingCapacity(); 631 const name = n: { 632 if (decl.parent == .none) { 633 // Then it is the root struct of a file. 634 break :n std.fs.path.stem(decl.file.path()); 635 } 636 break :n decl.extra_info().name; 637 }; 638 string_result.appendSlice(gpa, name) catch @panic("OOM"); 639 return String.init(string_result.items); 640 } 641 642 export fn decl_docs_html(decl_index: Decl.Index, short: bool) String { 643 const decl = decl_index.get(); 644 string_result.clearRetainingCapacity(); 645 if (decl.extra_info().first_doc_comment.unwrap()) |first_doc_comment| { 646 render_docs(&string_result, decl_index, first_doc_comment, short) catch @panic("OOM"); 647 } 648 return String.init(string_result.items); 649 } 650 651 fn collect_docs( 652 list: *std.ArrayListUnmanaged(u8), 653 ast: *const Ast, 654 first_doc_comment: Ast.TokenIndex, 655 ) Oom!void { 656 list.clearRetainingCapacity(); 657 var it = first_doc_comment; 658 while (true) : (it += 1) switch (ast.tokenTag(it)) { 659 .doc_comment, .container_doc_comment => { 660 // It is tempting to trim this string but think carefully about how 661 // that will affect the markdown parser. 662 const line = ast.tokenSlice(it)[3..]; 663 try list.appendSlice(gpa, line); 664 }, 665 else => break, 666 }; 667 } 668 669 fn render_docs( 670 out: *std.ArrayListUnmanaged(u8), 671 decl_index: Decl.Index, 672 first_doc_comment: Ast.TokenIndex, 673 short: bool, 674 ) Oom!void { 675 const decl = decl_index.get(); 676 const ast = decl.file.get_ast(); 677 678 var parser = try markdown.Parser.init(gpa); 679 defer parser.deinit(); 680 var it = first_doc_comment; 681 while (true) : (it += 1) switch (ast.tokenTag(it)) { 682 .doc_comment, .container_doc_comment => { 683 const line = ast.tokenSlice(it)[3..]; 684 if (short and line.len == 0) break; 685 try parser.feedLine(line); 686 }, 687 else => break, 688 }; 689 690 var parsed_doc = try parser.endInput(); 691 defer parsed_doc.deinit(gpa); 692 693 const g = struct { 694 var link_buffer: std.ArrayListUnmanaged(u8) = .empty; 695 }; 696 697 const Writer = std.ArrayListUnmanaged(u8).Writer; 698 const Renderer = markdown.Renderer(Writer, Decl.Index); 699 const renderer: Renderer = .{ 700 .context = decl_index, 701 .renderFn = struct { 702 fn render( 703 r: Renderer, 704 doc: markdown.Document, 705 node: markdown.Document.Node.Index, 706 writer: Writer, 707 ) !void { 708 const data = doc.nodes.items(.data)[@intFromEnum(node)]; 709 switch (doc.nodes.items(.tag)[@intFromEnum(node)]) { 710 .code_span => { 711 try writer.writeAll("<code>"); 712 const content = doc.string(data.text.content); 713 if (resolve_decl_path(r.context, content)) |resolved_decl_index| { 714 g.link_buffer.clearRetainingCapacity(); 715 try resolveDeclLink(resolved_decl_index, &g.link_buffer); 716 717 try writer.writeAll("<a href=\"#"); 718 _ = missing_feature_url_escape; 719 try writer.writeAll(g.link_buffer.items); 720 try writer.print("\">{f}</a>", .{markdown.fmtHtml(content)}); 721 } else { 722 try writer.print("{f}", .{markdown.fmtHtml(content)}); 723 } 724 725 try writer.writeAll("</code>"); 726 }, 727 728 else => try Renderer.renderDefault(r, doc, node, writer), 729 } 730 } 731 }.render, 732 }; 733 try renderer.render(parsed_doc, out.writer(gpa)); 734 } 735 736 fn resolve_decl_path(decl_index: Decl.Index, path: []const u8) ?Decl.Index { 737 var path_components = std.mem.splitScalar(u8, path, '.'); 738 var current_decl_index = decl_index.get().lookup(path_components.first()) orelse return null; 739 while (path_components.next()) |component| { 740 switch (current_decl_index.get().categorize()) { 741 .alias => |aliasee| current_decl_index = aliasee, 742 else => {}, 743 } 744 current_decl_index = current_decl_index.get().get_child(component) orelse return null; 745 } 746 return current_decl_index; 747 } 748 749 export fn decl_type_html(decl_index: Decl.Index) String { 750 const decl = decl_index.get(); 751 const ast = decl.file.get_ast(); 752 string_result.clearRetainingCapacity(); 753 t: { 754 // If there is an explicit type, use it. 755 if (ast.fullVarDecl(decl.ast_node)) |var_decl| { 756 if (var_decl.ast.type_node.unwrap()) |type_node| { 757 string_result.appendSlice(gpa, "<code>") catch @panic("OOM"); 758 fileSourceHtml(decl.file, &string_result, type_node, .{ 759 .skip_comments = true, 760 .collapse_whitespace = true, 761 }) catch |e| { 762 std.debug.panic("unable to render html: {s}", .{@errorName(e)}); 763 }; 764 string_result.appendSlice(gpa, "</code>") catch @panic("OOM"); 765 break :t; 766 } 767 } 768 } 769 return String.init(string_result.items); 770 } 771 772 const Oom = error{OutOfMemory}; 773 774 fn unpackInner(tar_bytes: []u8) !void { 775 var reader: std.Io.Reader = .fixed(tar_bytes); 776 var file_name_buffer: [1024]u8 = undefined; 777 var link_name_buffer: [1024]u8 = undefined; 778 var it: std.tar.Iterator = .init(&reader, .{ 779 .file_name_buffer = &file_name_buffer, 780 .link_name_buffer = &link_name_buffer, 781 }); 782 while (try it.next()) |tar_file| { 783 switch (tar_file.kind) { 784 .file => { 785 if (tar_file.size == 0 and tar_file.name.len == 0) break; 786 if (std.mem.endsWith(u8, tar_file.name, ".zig")) { 787 log.debug("found file: '{s}'", .{tar_file.name}); 788 const file_name = try gpa.dupe(u8, tar_file.name); 789 if (std.mem.indexOfScalar(u8, file_name, '/')) |pkg_name_end| { 790 const pkg_name = file_name[0..pkg_name_end]; 791 const gop = try Walk.modules.getOrPut(gpa, pkg_name); 792 const file: Walk.File.Index = @enumFromInt(Walk.files.entries.len); 793 if (!gop.found_existing or 794 std.mem.eql(u8, file_name[pkg_name_end..], "/root.zig") or 795 std.mem.eql(u8, file_name[pkg_name_end + 1 .. file_name.len - ".zig".len], pkg_name)) 796 { 797 gop.value_ptr.* = file; 798 } 799 const file_bytes = tar_bytes[reader.seek..][0..@intCast(tar_file.size)]; 800 assert(file == try Walk.add_file(file_name, file_bytes)); 801 } 802 } else { 803 log.warn("skipping: '{s}' - the tar creation should have done that", .{ 804 tar_file.name, 805 }); 806 } 807 }, 808 else => continue, 809 } 810 } 811 } 812 813 fn ascii_lower(bytes: []u8) void { 814 for (bytes) |*b| b.* = std.ascii.toLower(b.*); 815 } 816 817 export fn module_name(index: u32) String { 818 const names = Walk.modules.keys(); 819 return String.init(if (index >= names.len) "" else names[index]); 820 } 821 822 export fn find_module_root(pkg: Walk.ModuleIndex) Decl.Index { 823 const root_file = Walk.modules.values()[@intFromEnum(pkg)]; 824 const result = root_file.findRootDecl(); 825 assert(result != .none); 826 return result; 827 } 828 829 /// Set by `set_input_string`. 830 var input_string: std.ArrayListUnmanaged(u8) = .empty; 831 832 export fn set_input_string(len: usize) [*]u8 { 833 input_string.resize(gpa, len) catch @panic("OOM"); 834 return input_string.items.ptr; 835 } 836 837 /// Looks up the root struct decl corresponding to a file by path. 838 /// Uses `input_string`. 839 export fn find_file_root() Decl.Index { 840 const file: Walk.File.Index = @enumFromInt(Walk.files.getIndex(input_string.items) orelse return .none); 841 return file.findRootDecl(); 842 } 843 844 /// Uses `input_string`. 845 /// Tries to look up the Decl component-wise but then falls back to a file path 846 /// based scan. 847 export fn find_decl() Decl.Index { 848 const result = Decl.find(input_string.items); 849 if (result != .none) return result; 850 851 const g = struct { 852 var match_fqn: std.ArrayListUnmanaged(u8) = .empty; 853 }; 854 for (Walk.decls.items, 0..) |*decl, decl_index| { 855 g.match_fqn.clearRetainingCapacity(); 856 decl.fqn(&g.match_fqn) catch @panic("OOM"); 857 if (std.mem.eql(u8, g.match_fqn.items, input_string.items)) { 858 //const path = @as(Decl.Index, @enumFromInt(decl_index)).get().file.path(); 859 //log.debug("find_decl '{s}' found in {s}", .{ input_string.items, path }); 860 return @enumFromInt(decl_index); 861 } 862 } 863 return .none; 864 } 865 866 /// Set only by `categorize_decl`; read only by `get_aliasee`, valid only 867 /// when `categorize_decl` returns `.alias`. 868 var global_aliasee: Decl.Index = .none; 869 870 export fn get_aliasee() Decl.Index { 871 return global_aliasee; 872 } 873 export fn categorize_decl(decl_index: Decl.Index, resolve_alias_count: usize) Walk.Category.Tag { 874 global_aliasee = .none; 875 var chase_alias_n = resolve_alias_count; 876 var decl = decl_index.get(); 877 while (true) { 878 const result = decl.categorize(); 879 switch (result) { 880 .alias => |new_index| { 881 assert(new_index != .none); 882 global_aliasee = new_index; 883 if (chase_alias_n > 0) { 884 chase_alias_n -= 1; 885 decl = new_index.get(); 886 continue; 887 } 888 }, 889 else => {}, 890 } 891 return result; 892 } 893 } 894 895 export fn type_fn_members(parent: Decl.Index, include_private: bool) Slice(Decl.Index) { 896 const decl = parent.get(); 897 898 // If the type function returns another type function, get the members of that function 899 if (decl.get_type_fn_return_type_fn()) |function_decl| { 900 return namespace_members(function_decl, include_private); 901 } 902 903 return namespace_members(parent, include_private); 904 } 905 906 export fn namespace_members(parent: Decl.Index, include_private: bool) Slice(Decl.Index) { 907 const g = struct { 908 var members: std.ArrayListUnmanaged(Decl.Index) = .empty; 909 }; 910 911 g.members.clearRetainingCapacity(); 912 913 for (Walk.decls.items, 0..) |*decl, i| { 914 if (decl.parent == parent) { 915 if (include_private or decl.is_pub()) { 916 g.members.append(gpa, @enumFromInt(i)) catch @panic("OOM"); 917 } 918 } 919 } 920 921 return Slice(Decl.Index).init(g.members.items); 922 } 923 924 fn count_scalar(haystack: []const u8, needle: u8) usize { 925 var total: usize = 0; 926 for (haystack) |elem| { 927 if (elem == needle) 928 total += 1; 929 } 930 return total; 931 }