diff --git a/lib/compiler/std-docs.zig b/lib/compiler/std-docs.zig index eaabcfa936..c11665101c 100644 --- a/lib/compiler/std-docs.zig +++ b/lib/compiler/std-docs.zig @@ -275,10 +275,6 @@ fn buildWasmBinary( ) ![]const u8 { const gpa = context.gpa; - const main_src_path = try std.fs.path.join(arena, &.{ - context.zig_lib_directory, "docs", "wasm", "main.zig", - }); - var argv: std.ArrayListUnmanaged([]const u8) = .{}; try argv.appendSlice(arena, &.{ @@ -298,7 +294,10 @@ fn buildWasmBinary( "--name", "autodoc", "-rdynamic", - main_src_path, + "--dep", + "Walk", + try std.fmt.allocPrint(arena, "-Mroot={s}/docs/wasm/main.zig", .{context.zig_lib_directory}), + try std.fmt.allocPrint(arena, "-MWalk={s}/docs/wasm/Walk.zig", .{context.zig_lib_directory}), "--listen=-", }); diff --git a/lib/docs/wasm/Decl.zig b/lib/docs/wasm/Decl.zig index 0260ce0285..2546355987 100644 --- a/lib/docs/wasm/Decl.zig +++ b/lib/docs/wasm/Decl.zig @@ -1,3 +1,12 @@ +const Decl = @This(); +const std = @import("std"); +const Ast = std.zig.Ast; +const Walk = @import("Walk.zig"); +const gpa = std.heap.wasm_allocator; +const assert = std.debug.assert; +const log = std.log; +const Oom = error{OutOfMemory}; + ast_node: Ast.Node.Index, file: Walk.File.Index, /// The decl whose namespace this is in. @@ -215,12 +224,3 @@ pub fn find(search_string: []const u8) Decl.Index { } return current_decl_index; } - -const Decl = @This(); -const std = @import("std"); -const Ast = std.zig.Ast; -const Walk = @import("Walk.zig"); -const gpa = std.heap.wasm_allocator; -const assert = std.debug.assert; -const log = std.log; -const Oom = error{OutOfMemory}; diff --git a/lib/docs/wasm/Walk.zig b/lib/docs/wasm/Walk.zig index a22da861a8..ae924b8c38 100644 --- a/lib/docs/wasm/Walk.zig +++ b/lib/docs/wasm/Walk.zig @@ -1,4 +1,15 @@ //! Find and annotate identifiers with links to their declarations. + +const Walk = @This(); +const std = @import("std"); +const Ast = std.zig.Ast; +const assert = std.debug.assert; +const log = std.log; +const gpa = std.heap.wasm_allocator; +const Oom = error{OutOfMemory}; + +pub const Decl = @import("Decl.zig"); + pub var files: std.StringArrayHashMapUnmanaged(File) = .{}; pub var decls: std.ArrayListUnmanaged(Decl) = .{}; pub var modules: std.StringArrayHashMapUnmanaged(File.Index) = .{}; @@ -1120,15 +1131,6 @@ pub fn isPrimitiveNonType(name: []const u8) bool { // try w.root(); //} -const Walk = @This(); -const std = @import("std"); -const Ast = std.zig.Ast; -const assert = std.debug.assert; -const Decl = @import("Decl.zig"); -const log = std.log; -const gpa = std.heap.wasm_allocator; -const Oom = error{OutOfMemory}; - fn shrinkToFit(m: anytype) void { m.shrinkAndFree(gpa, m.entries.len); } diff --git a/lib/docs/wasm/html_render.zig b/lib/docs/wasm/html_render.zig new file mode 100644 index 0000000000..cce201049d --- /dev/null +++ b/lib/docs/wasm/html_render.zig @@ -0,0 +1,388 @@ +const std = @import("std"); +const Ast = std.zig.Ast; +const assert = std.debug.assert; + +const Walk = @import("Walk"); +const Decl = Walk.Decl; + +const gpa = std.heap.wasm_allocator; +const Oom = error{OutOfMemory}; + +/// Delete this to find out where URL escaping needs to be added. +pub const missing_feature_url_escape = true; + +pub const RenderSourceOptions = struct { + skip_doc_comments: bool = false, + skip_comments: bool = false, + collapse_whitespace: bool = false, + fn_link: Decl.Index = .none, +}; + +pub fn fileSourceHtml( + file_index: Walk.File.Index, + out: *std.ArrayListUnmanaged(u8), + root_node: Ast.Node.Index, + options: RenderSourceOptions, +) !void { + const ast = file_index.get_ast(); + const file = file_index.get(); + + const g = struct { + var field_access_buffer: std.ArrayListUnmanaged(u8) = .{}; + }; + + const token_tags = ast.tokens.items(.tag); + const token_starts = ast.tokens.items(.start); + const main_tokens = ast.nodes.items(.main_token); + + const start_token = ast.firstToken(root_node); + const end_token = ast.lastToken(root_node) + 1; + + var cursor: usize = token_starts[start_token]; + + var indent: usize = 0; + if (std.mem.lastIndexOf(u8, ast.source[0..cursor], "\n")) |newline_index| { + for (ast.source[newline_index + 1 .. cursor]) |c| { + if (c == ' ') { + indent += 1; + } else { + break; + } + } + } + + for ( + token_tags[start_token..end_token], + token_starts[start_token..end_token], + start_token.., + ) |tag, start, token_index| { + const between = ast.source[cursor..start]; + if (std.mem.trim(u8, between, " \t\r\n").len > 0) { + if (!options.skip_comments) { + try out.appendSlice(gpa, ""); + try appendUnindented(out, between, indent); + try out.appendSlice(gpa, ""); + } + } else if (between.len > 0) { + if (options.collapse_whitespace) { + if (out.items.len > 0 and out.items[out.items.len - 1] != ' ') + try out.append(gpa, ' '); + } else { + try appendUnindented(out, between, indent); + } + } + if (tag == .eof) break; + const slice = ast.tokenSlice(token_index); + cursor = start + slice.len; + switch (tag) { + .eof => unreachable, + + .keyword_addrspace, + .keyword_align, + .keyword_and, + .keyword_asm, + .keyword_async, + .keyword_await, + .keyword_break, + .keyword_catch, + .keyword_comptime, + .keyword_const, + .keyword_continue, + .keyword_defer, + .keyword_else, + .keyword_enum, + .keyword_errdefer, + .keyword_error, + .keyword_export, + .keyword_extern, + .keyword_for, + .keyword_if, + .keyword_inline, + .keyword_noalias, + .keyword_noinline, + .keyword_nosuspend, + .keyword_opaque, + .keyword_or, + .keyword_orelse, + .keyword_packed, + .keyword_anyframe, + .keyword_pub, + .keyword_resume, + .keyword_return, + .keyword_linksection, + .keyword_callconv, + .keyword_struct, + .keyword_suspend, + .keyword_switch, + .keyword_test, + .keyword_threadlocal, + .keyword_try, + .keyword_union, + .keyword_unreachable, + .keyword_usingnamespace, + .keyword_var, + .keyword_volatile, + .keyword_allowzero, + .keyword_while, + .keyword_anytype, + .keyword_fn, + => { + try out.appendSlice(gpa, ""); + try appendEscaped(out, slice); + try out.appendSlice(gpa, ""); + }, + + .string_literal, + .char_literal, + .multiline_string_literal_line, + => { + try out.appendSlice(gpa, ""); + try appendEscaped(out, slice); + try out.appendSlice(gpa, ""); + }, + + .builtin => { + try out.appendSlice(gpa, ""); + try appendEscaped(out, slice); + try out.appendSlice(gpa, ""); + }, + + .doc_comment, + .container_doc_comment, + => { + if (!options.skip_doc_comments) { + try out.appendSlice(gpa, ""); + try appendEscaped(out, slice); + try out.appendSlice(gpa, ""); + } + }, + + .identifier => i: { + if (options.fn_link != .none) { + const fn_link = options.fn_link.get(); + const fn_token = main_tokens[fn_link.ast_node]; + if (token_index == fn_token + 1) { + try out.appendSlice(gpa, ""); + try appendEscaped(out, slice); + try out.appendSlice(gpa, ""); + break :i; + } + } + + if (token_index > 0 and token_tags[token_index - 1] == .keyword_fn) { + try out.appendSlice(gpa, ""); + try appendEscaped(out, slice); + try out.appendSlice(gpa, ""); + break :i; + } + + if (Walk.isPrimitiveNonType(slice)) { + try out.appendSlice(gpa, ""); + try appendEscaped(out, slice); + try out.appendSlice(gpa, ""); + break :i; + } + + if (std.zig.primitives.isPrimitive(slice)) { + try out.appendSlice(gpa, ""); + try appendEscaped(out, slice); + try out.appendSlice(gpa, ""); + break :i; + } + + if (file.token_parents.get(token_index)) |field_access_node| { + g.field_access_buffer.clearRetainingCapacity(); + try walkFieldAccesses(file_index, &g.field_access_buffer, field_access_node); + if (g.field_access_buffer.items.len > 0) { + try out.appendSlice(gpa, ""); + try appendEscaped(out, slice); + try out.appendSlice(gpa, ""); + } else { + try appendEscaped(out, slice); + } + break :i; + } + + { + g.field_access_buffer.clearRetainingCapacity(); + try resolveIdentLink(file_index, &g.field_access_buffer, token_index); + if (g.field_access_buffer.items.len > 0) { + try out.appendSlice(gpa, ""); + try appendEscaped(out, slice); + try out.appendSlice(gpa, ""); + break :i; + } + } + + try appendEscaped(out, slice); + }, + + .number_literal => { + try out.appendSlice(gpa, ""); + try appendEscaped(out, slice); + try out.appendSlice(gpa, ""); + }, + + .bang, + .pipe, + .pipe_pipe, + .pipe_equal, + .equal, + .equal_equal, + .equal_angle_bracket_right, + .bang_equal, + .l_paren, + .r_paren, + .semicolon, + .percent, + .percent_equal, + .l_brace, + .r_brace, + .l_bracket, + .r_bracket, + .period, + .period_asterisk, + .ellipsis2, + .ellipsis3, + .caret, + .caret_equal, + .plus, + .plus_plus, + .plus_equal, + .plus_percent, + .plus_percent_equal, + .plus_pipe, + .plus_pipe_equal, + .minus, + .minus_equal, + .minus_percent, + .minus_percent_equal, + .minus_pipe, + .minus_pipe_equal, + .asterisk, + .asterisk_equal, + .asterisk_asterisk, + .asterisk_percent, + .asterisk_percent_equal, + .asterisk_pipe, + .asterisk_pipe_equal, + .arrow, + .colon, + .slash, + .slash_equal, + .comma, + .ampersand, + .ampersand_equal, + .question_mark, + .angle_bracket_left, + .angle_bracket_left_equal, + .angle_bracket_angle_bracket_left, + .angle_bracket_angle_bracket_left_equal, + .angle_bracket_angle_bracket_left_pipe, + .angle_bracket_angle_bracket_left_pipe_equal, + .angle_bracket_right, + .angle_bracket_right_equal, + .angle_bracket_angle_bracket_right, + .angle_bracket_angle_bracket_right_equal, + .tilde, + => try appendEscaped(out, slice), + + .invalid, .invalid_periodasterisks => return error.InvalidToken, + } + } +} + +fn appendUnindented(out: *std.ArrayListUnmanaged(u8), s: []const u8, indent: usize) !void { + var it = std.mem.splitScalar(u8, s, '\n'); + var is_first_line = true; + while (it.next()) |line| { + if (is_first_line) { + try appendEscaped(out, line); + is_first_line = false; + } else { + try out.appendSlice(gpa, "\n"); + try appendEscaped(out, unindent(line, indent)); + } + } +} + +pub fn appendEscaped(out: *std.ArrayListUnmanaged(u8), s: []const u8) !void { + for (s) |c| { + try out.ensureUnusedCapacity(gpa, 6); + switch (c) { + '&' => out.appendSliceAssumeCapacity("&"), + '<' => out.appendSliceAssumeCapacity("<"), + '>' => out.appendSliceAssumeCapacity(">"), + '"' => out.appendSliceAssumeCapacity("""), + else => out.appendAssumeCapacity(c), + } + } +} + +fn walkFieldAccesses( + file_index: Walk.File.Index, + out: *std.ArrayListUnmanaged(u8), + node: Ast.Node.Index, +) Oom!void { + const ast = file_index.get_ast(); + const node_tags = ast.nodes.items(.tag); + assert(node_tags[node] == .field_access); + const node_datas = ast.nodes.items(.data); + const main_tokens = ast.nodes.items(.main_token); + const object_node = node_datas[node].lhs; + const dot_token = main_tokens[node]; + const field_ident = dot_token + 1; + switch (node_tags[object_node]) { + .identifier => { + const lhs_ident = main_tokens[object_node]; + try resolveIdentLink(file_index, out, lhs_ident); + }, + .field_access => { + try walkFieldAccesses(file_index, out, object_node); + }, + else => {}, + } + if (out.items.len > 0) { + try out.append(gpa, '.'); + try out.appendSlice(gpa, ast.tokenSlice(field_ident)); + } +} + +fn resolveIdentLink( + file_index: Walk.File.Index, + out: *std.ArrayListUnmanaged(u8), + ident_token: Ast.TokenIndex, +) Oom!void { + const decl_index = file_index.get().lookup_token(ident_token); + if (decl_index == .none) return; + try resolveDeclLink(decl_index, out); +} + +fn unindent(s: []const u8, indent: usize) []const u8 { + var indent_idx: usize = 0; + for (s) |c| { + if (c == ' ' and indent_idx < indent) { + indent_idx += 1; + } else { + break; + } + } + return s[indent_idx..]; +} + +pub fn resolveDeclLink(decl_index: Decl.Index, out: *std.ArrayListUnmanaged(u8)) Oom!void { + const decl = decl_index.get(); + switch (decl.categorize()) { + .alias => |alias_decl| try alias_decl.get().fqn(out), + else => try decl.fqn(out), + } +} diff --git a/lib/docs/wasm/main.zig b/lib/docs/wasm/main.zig index 214f28c24b..55882aaf7d 100644 --- a/lib/docs/wasm/main.zig +++ b/lib/docs/wasm/main.zig @@ -1,15 +1,17 @@ -/// Delete this to find out where URL escaping needs to be added. -const missing_feature_url_escape = true; - -const gpa = std.heap.wasm_allocator; - const std = @import("std"); const log = std.log; const assert = std.debug.assert; const Ast = std.zig.Ast; -const Walk = @import("Walk.zig"); +const Walk = @import("Walk"); const markdown = @import("markdown.zig"); -const Decl = @import("Decl.zig"); +const Decl = Walk.Decl; + +const fileSourceHtml = @import("html_render.zig").fileSourceHtml; +const appendEscaped = @import("html_render.zig").appendEscaped; +const resolveDeclLink = @import("html_render.zig").resolveDeclLink; +const missing_feature_url_escape = @import("html_render.zig").missing_feature_url_escape; + +const gpa = std.heap.wasm_allocator; const js = struct { extern "js" fn log(ptr: [*]const u8, len: usize) void; @@ -439,7 +441,7 @@ fn decl_field_html_fallible( const decl = decl_index.get(); const ast = decl.file.get_ast(); try out.appendSlice(gpa, "
");
-    try file_source_html(decl.file, out, field_node, .{});
+    try fileSourceHtml(decl.file, out, field_node, .{});
     try out.appendSlice(gpa, "
"); const field = ast.fullContainerField(field_node).?; @@ -478,7 +480,7 @@ fn decl_param_html_fallible( try out.appendSlice(gpa, "
");
     try appendEscaped(out, name);
     try out.appendSlice(gpa, ": ");
-    try file_source_html(decl.file, out, param_node, .{});
+    try fileSourceHtml(decl.file, out, param_node, .{});
     try out.appendSlice(gpa, "
"); if (ast.tokens.items(.tag)[first_doc_comment] == .doc_comment) { @@ -506,7 +508,7 @@ export fn decl_fn_proto_html(decl_index: Decl.Index, linkify_fn_name: bool) Stri }; string_result.clearRetainingCapacity(); - file_source_html(decl.file, &string_result, proto_node, .{ + fileSourceHtml(decl.file, &string_result, proto_node, .{ .skip_doc_comments = true, .skip_comments = true, .collapse_whitespace = true, @@ -521,7 +523,7 @@ export fn decl_source_html(decl_index: Decl.Index) String { const decl = decl_index.get(); string_result.clearRetainingCapacity(); - file_source_html(decl.file, &string_result, decl.ast_node, .{}) catch |err| { + fileSourceHtml(decl.file, &string_result, decl.ast_node, .{}) catch |err| { fatal("unable to render source: {s}", .{@errorName(err)}); }; return String.init(string_result.items); @@ -533,7 +535,7 @@ export fn decl_doctest_html(decl_index: Decl.Index) String { return String.init(""); string_result.clearRetainingCapacity(); - file_source_html(decl.file, &string_result, doctest_ast_node, .{}) catch |err| { + fileSourceHtml(decl.file, &string_result, doctest_ast_node, .{}) catch |err| { fatal("unable to render source: {s}", .{@errorName(err)}); }; return String.init(string_result.items); @@ -691,7 +693,7 @@ fn render_docs( const content = doc.string(data.text.content); if (resolve_decl_path(r.context, content)) |resolved_decl_index| { g.link_buffer.clearRetainingCapacity(); - try resolve_decl_link(resolved_decl_index, &g.link_buffer); + try resolveDeclLink(resolved_decl_index, &g.link_buffer); try writer.writeAll("") catch @panic("OOM"); - file_source_html(decl.file, &string_result, var_decl.ast.type_node, .{ + fileSourceHtml(decl.file, &string_result, var_decl.ast.type_node, .{ .skip_comments = true, .collapse_whitespace = true, }) catch |e| { @@ -902,382 +904,6 @@ export fn namespace_members(parent: Decl.Index, include_private: bool) Slice(Dec return Slice(Decl.Index).init(g.members.items); } -const RenderSourceOptions = struct { - skip_doc_comments: bool = false, - skip_comments: bool = false, - collapse_whitespace: bool = false, - fn_link: Decl.Index = .none, -}; - -fn file_source_html( - file_index: Walk.File.Index, - out: *std.ArrayListUnmanaged(u8), - root_node: Ast.Node.Index, - options: RenderSourceOptions, -) !void { - const ast = file_index.get_ast(); - const file = file_index.get(); - - const g = struct { - var field_access_buffer: std.ArrayListUnmanaged(u8) = .{}; - }; - - const token_tags = ast.tokens.items(.tag); - const token_starts = ast.tokens.items(.start); - const main_tokens = ast.nodes.items(.main_token); - - const start_token = ast.firstToken(root_node); - const end_token = ast.lastToken(root_node) + 1; - - var cursor: usize = token_starts[start_token]; - - var indent: usize = 0; - if (std.mem.lastIndexOf(u8, ast.source[0..cursor], "\n")) |newline_index| { - for (ast.source[newline_index + 1 .. cursor]) |c| { - if (c == ' ') { - indent += 1; - } else { - break; - } - } - } - - for ( - token_tags[start_token..end_token], - token_starts[start_token..end_token], - start_token.., - ) |tag, start, token_index| { - const between = ast.source[cursor..start]; - if (std.mem.trim(u8, between, " \t\r\n").len > 0) { - if (!options.skip_comments) { - try out.appendSlice(gpa, ""); - try appendUnindented(out, between, indent); - try out.appendSlice(gpa, ""); - } - } else if (between.len > 0) { - if (options.collapse_whitespace) { - if (out.items.len > 0 and out.items[out.items.len - 1] != ' ') - try out.append(gpa, ' '); - } else { - try appendUnindented(out, between, indent); - } - } - if (tag == .eof) break; - const slice = ast.tokenSlice(token_index); - cursor = start + slice.len; - switch (tag) { - .eof => unreachable, - - .keyword_addrspace, - .keyword_align, - .keyword_and, - .keyword_asm, - .keyword_async, - .keyword_await, - .keyword_break, - .keyword_catch, - .keyword_comptime, - .keyword_const, - .keyword_continue, - .keyword_defer, - .keyword_else, - .keyword_enum, - .keyword_errdefer, - .keyword_error, - .keyword_export, - .keyword_extern, - .keyword_for, - .keyword_if, - .keyword_inline, - .keyword_noalias, - .keyword_noinline, - .keyword_nosuspend, - .keyword_opaque, - .keyword_or, - .keyword_orelse, - .keyword_packed, - .keyword_anyframe, - .keyword_pub, - .keyword_resume, - .keyword_return, - .keyword_linksection, - .keyword_callconv, - .keyword_struct, - .keyword_suspend, - .keyword_switch, - .keyword_test, - .keyword_threadlocal, - .keyword_try, - .keyword_union, - .keyword_unreachable, - .keyword_usingnamespace, - .keyword_var, - .keyword_volatile, - .keyword_allowzero, - .keyword_while, - .keyword_anytype, - .keyword_fn, - => { - try out.appendSlice(gpa, ""); - try appendEscaped(out, slice); - try out.appendSlice(gpa, ""); - }, - - .string_literal, - .char_literal, - .multiline_string_literal_line, - => { - try out.appendSlice(gpa, ""); - try appendEscaped(out, slice); - try out.appendSlice(gpa, ""); - }, - - .builtin => { - try out.appendSlice(gpa, ""); - try appendEscaped(out, slice); - try out.appendSlice(gpa, ""); - }, - - .doc_comment, - .container_doc_comment, - => { - if (!options.skip_doc_comments) { - try out.appendSlice(gpa, ""); - try appendEscaped(out, slice); - try out.appendSlice(gpa, ""); - } - }, - - .identifier => i: { - if (options.fn_link != .none) { - const fn_link = options.fn_link.get(); - const fn_token = main_tokens[fn_link.ast_node]; - if (token_index == fn_token + 1) { - try out.appendSlice(gpa, ""); - try appendEscaped(out, slice); - try out.appendSlice(gpa, ""); - break :i; - } - } - - if (token_index > 0 and token_tags[token_index - 1] == .keyword_fn) { - try out.appendSlice(gpa, ""); - try appendEscaped(out, slice); - try out.appendSlice(gpa, ""); - break :i; - } - - if (Walk.isPrimitiveNonType(slice)) { - try out.appendSlice(gpa, ""); - try appendEscaped(out, slice); - try out.appendSlice(gpa, ""); - break :i; - } - - if (std.zig.primitives.isPrimitive(slice)) { - try out.appendSlice(gpa, ""); - try appendEscaped(out, slice); - try out.appendSlice(gpa, ""); - break :i; - } - - if (file.token_parents.get(token_index)) |field_access_node| { - g.field_access_buffer.clearRetainingCapacity(); - try walk_field_accesses(file_index, &g.field_access_buffer, field_access_node); - if (g.field_access_buffer.items.len > 0) { - try out.appendSlice(gpa, ""); - try appendEscaped(out, slice); - try out.appendSlice(gpa, ""); - } else { - try appendEscaped(out, slice); - } - break :i; - } - - { - g.field_access_buffer.clearRetainingCapacity(); - try resolve_ident_link(file_index, &g.field_access_buffer, token_index); - if (g.field_access_buffer.items.len > 0) { - try out.appendSlice(gpa, ""); - try appendEscaped(out, slice); - try out.appendSlice(gpa, ""); - break :i; - } - } - - try appendEscaped(out, slice); - }, - - .number_literal => { - try out.appendSlice(gpa, ""); - try appendEscaped(out, slice); - try out.appendSlice(gpa, ""); - }, - - .bang, - .pipe, - .pipe_pipe, - .pipe_equal, - .equal, - .equal_equal, - .equal_angle_bracket_right, - .bang_equal, - .l_paren, - .r_paren, - .semicolon, - .percent, - .percent_equal, - .l_brace, - .r_brace, - .l_bracket, - .r_bracket, - .period, - .period_asterisk, - .ellipsis2, - .ellipsis3, - .caret, - .caret_equal, - .plus, - .plus_plus, - .plus_equal, - .plus_percent, - .plus_percent_equal, - .plus_pipe, - .plus_pipe_equal, - .minus, - .minus_equal, - .minus_percent, - .minus_percent_equal, - .minus_pipe, - .minus_pipe_equal, - .asterisk, - .asterisk_equal, - .asterisk_asterisk, - .asterisk_percent, - .asterisk_percent_equal, - .asterisk_pipe, - .asterisk_pipe_equal, - .arrow, - .colon, - .slash, - .slash_equal, - .comma, - .ampersand, - .ampersand_equal, - .question_mark, - .angle_bracket_left, - .angle_bracket_left_equal, - .angle_bracket_angle_bracket_left, - .angle_bracket_angle_bracket_left_equal, - .angle_bracket_angle_bracket_left_pipe, - .angle_bracket_angle_bracket_left_pipe_equal, - .angle_bracket_right, - .angle_bracket_right_equal, - .angle_bracket_angle_bracket_right, - .angle_bracket_angle_bracket_right_equal, - .tilde, - => try appendEscaped(out, slice), - - .invalid, .invalid_periodasterisks => return error.InvalidToken, - } - } -} - -fn unindent(s: []const u8, indent: usize) []const u8 { - var indent_idx: usize = 0; - for (s) |c| { - if (c == ' ' and indent_idx < indent) { - indent_idx += 1; - } else { - break; - } - } - return s[indent_idx..]; -} - -fn appendUnindented(out: *std.ArrayListUnmanaged(u8), s: []const u8, indent: usize) !void { - var it = std.mem.splitScalar(u8, s, '\n'); - var is_first_line = true; - while (it.next()) |line| { - if (is_first_line) { - try appendEscaped(out, line); - is_first_line = false; - } else { - try out.appendSlice(gpa, "\n"); - try appendEscaped(out, unindent(line, indent)); - } - } -} - -fn resolve_ident_link( - file_index: Walk.File.Index, - out: *std.ArrayListUnmanaged(u8), - ident_token: Ast.TokenIndex, -) Oom!void { - const decl_index = file_index.get().lookup_token(ident_token); - if (decl_index == .none) return; - try resolve_decl_link(decl_index, out); -} - -fn resolve_decl_link(decl_index: Decl.Index, out: *std.ArrayListUnmanaged(u8)) Oom!void { - const decl = decl_index.get(); - switch (decl.categorize()) { - .alias => |alias_decl| try alias_decl.get().fqn(out), - else => try decl.fqn(out), - } -} - -fn walk_field_accesses( - file_index: Walk.File.Index, - out: *std.ArrayListUnmanaged(u8), - node: Ast.Node.Index, -) Oom!void { - const ast = file_index.get_ast(); - const node_tags = ast.nodes.items(.tag); - assert(node_tags[node] == .field_access); - const node_datas = ast.nodes.items(.data); - const main_tokens = ast.nodes.items(.main_token); - const object_node = node_datas[node].lhs; - const dot_token = main_tokens[node]; - const field_ident = dot_token + 1; - switch (node_tags[object_node]) { - .identifier => { - const lhs_ident = main_tokens[object_node]; - try resolve_ident_link(file_index, out, lhs_ident); - }, - .field_access => { - try walk_field_accesses(file_index, out, object_node); - }, - else => {}, - } - if (out.items.len > 0) { - try out.append(gpa, '.'); - try out.appendSlice(gpa, ast.tokenSlice(field_ident)); - } -} - -fn appendEscaped(out: *std.ArrayListUnmanaged(u8), s: []const u8) !void { - for (s) |c| { - try out.ensureUnusedCapacity(gpa, 6); - switch (c) { - '&' => out.appendSliceAssumeCapacity("&"), - '<' => out.appendSliceAssumeCapacity("<"), - '>' => out.appendSliceAssumeCapacity(">"), - '"' => out.appendSliceAssumeCapacity("""), - else => out.appendAssumeCapacity(c), - } - } -} - fn count_scalar(haystack: []const u8, needle: u8) usize { var total: usize = 0; for (haystack) |elem| { diff --git a/lib/fuzzer/index.html b/lib/fuzzer/index.html index c1ef059ad6..dadc2f91d3 100644 --- a/lib/fuzzer/index.html +++ b/lib/fuzzer/index.html @@ -2,12 +2,56 @@ - Zig Documentation + Zig Build System Interface + diff --git a/lib/fuzzer/main.js b/lib/fuzzer/main.js index 9b0d4cd8c3..71e6b5fa54 100644 --- a/lib/fuzzer/main.js +++ b/lib/fuzzer/main.js @@ -1,4 +1,7 @@ (function() { + const domSectSource = document.getElementById("sectSource"); + const domSourceText = document.getElementById("sourceText"); + let wasm_promise = fetch("main.wasm"); let sources_promise = fetch("sources.tar").then(function(response) { if (!response.ok) throw new Error("unable to download sources"); @@ -30,11 +33,56 @@ const wasm_array = new Uint8Array(wasm_exports.memory.buffer, ptr, js_array.length); wasm_array.set(js_array); wasm_exports.unpack(ptr, js_array.length); + + render(); }); }); + function render() { + domSectSource.classList.add("hidden"); + + // TODO this is temporary debugging data + renderSource("/home/andy/dev/zig/lib/std/zig/tokenizer.zig"); + } + + function renderSource(path) { + const decl_index = findFileRoot(path); + if (decl_index == null) throw new Error("file not found: " + path); + + const h2 = domSectSource.children[0]; + h2.innerText = path; + domSourceText.innerHTML = declSourceHtml(decl_index); + + domSectSource.classList.remove("hidden"); + } + + function findFileRoot(path) { + setInputString(path); + const result = wasm_exports.find_file_root(); + if (result === -1) return null; + return result; + } + function decodeString(ptr, len) { if (len === 0) return ""; return text_decoder.decode(new Uint8Array(wasm_exports.memory.buffer, ptr, len)); } + + function setInputString(s) { + const jsArray = text_encoder.encode(s); + const len = jsArray.length; + const ptr = wasm_exports.set_input_string(len); + const wasmArray = new Uint8Array(wasm_exports.memory.buffer, ptr, len); + wasmArray.set(jsArray); + } + + function declSourceHtml(decl_index) { + return unwrapString(wasm_exports.decl_source_html(decl_index)); + } + + function unwrapString(bigint) { + const ptr = Number(bigint & 0xffffffffn); + const len = Number(bigint >> 32n); + return decodeString(ptr, len); + } })(); diff --git a/lib/fuzzer/wasm/main.zig b/lib/fuzzer/wasm/main.zig index 09b9d81068..5045f784cc 100644 --- a/lib/fuzzer/wasm/main.zig +++ b/lib/fuzzer/wasm/main.zig @@ -2,6 +2,8 @@ const std = @import("std"); const assert = std.debug.assert; const Walk = @import("Walk"); +const Decl = Walk.Decl; +const html_render = @import("html_render"); const gpa = std.heap.wasm_allocator; const log = std.log; @@ -52,6 +54,48 @@ export fn unpack(tar_ptr: [*]u8, tar_len: usize) void { }; } +/// Set by `set_input_string`. +var input_string: std.ArrayListUnmanaged(u8) = .{}; +var string_result: std.ArrayListUnmanaged(u8) = .{}; + +export fn set_input_string(len: usize) [*]u8 { + input_string.resize(gpa, len) catch @panic("OOM"); + return input_string.items.ptr; +} + +/// Looks up the root struct decl corresponding to a file by path. +/// Uses `input_string`. +export fn find_file_root() Decl.Index { + const file: Walk.File.Index = @enumFromInt(Walk.files.getIndex(input_string.items) orelse return .none); + return file.findRootDecl(); +} + +export fn decl_source_html(decl_index: Decl.Index) String { + const decl = decl_index.get(); + + string_result.clearRetainingCapacity(); + html_render.fileSourceHtml(decl.file, &string_result, decl.ast_node, .{}) catch |err| { + fatal("unable to render source: {s}", .{@errorName(err)}); + }; + return String.init(string_result.items); +} + +const String = Slice(u8); + +fn Slice(T: type) type { + return packed struct(u64) { + ptr: u32, + len: u32, + + fn init(s: []const T) @This() { + return .{ + .ptr = @intFromPtr(s.ptr), + .len = s.len, + }; + } + }; +} + fn unpackInner(tar_bytes: []u8) !void { var fbs = std.io.fixedBufferStream(tar_bytes); var file_name_buffer: [1024]u8 = undefined; diff --git a/lib/std/Build/Fuzz.zig b/lib/std/Build/Fuzz.zig index e26f587eac..46d9bfc8fd 100644 --- a/lib/std/Build/Fuzz.zig +++ b/lib/std/Build/Fuzz.zig @@ -235,30 +235,29 @@ pub const WebServer = struct { .root_dir = ws.zig_lib_directory, .sub_path = "docs/wasm/Walk.zig", }; + const html_render_src_path: Build.Cache.Path = .{ + .root_dir = ws.zig_lib_directory, + .sub_path = "docs/wasm/html_render.zig", + }; var argv: std.ArrayListUnmanaged([]const u8) = .{}; try argv.appendSlice(arena, &.{ - ws.zig_exe_path, - "build-exe", - "-fno-entry", - "-O", - @tagName(optimize_mode), - "-target", - "wasm32-freestanding", - "-mcpu", - "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext", - "--cache-dir", - ws.global_cache_directory.path orelse ".", - "--global-cache-dir", - ws.global_cache_directory.path orelse ".", - "--name", - "fuzzer", - "-rdynamic", - "--dep", - "Walk", - try std.fmt.allocPrint(arena, "-Mroot={}", .{main_src_path}), - try std.fmt.allocPrint(arena, "-MWalk={}", .{walk_src_path}), + ws.zig_exe_path, "build-exe", // + "-fno-entry", // + "-O", @tagName(optimize_mode), // + "-target", "wasm32-freestanding", // + "-mcpu", "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext", // + "--cache-dir", ws.global_cache_directory.path orelse ".", // + "--global-cache-dir", ws.global_cache_directory.path orelse ".", // + "--name", "fuzzer", // + "-rdynamic", // + "--dep", "Walk", // + "--dep", "html_render", // + try std.fmt.allocPrint(arena, "-Mroot={}", .{main_src_path}), // + try std.fmt.allocPrint(arena, "-MWalk={}", .{walk_src_path}), // + "--dep", "Walk", // + try std.fmt.allocPrint(arena, "-Mhtml_render={}", .{html_render_src_path}), // "--listen=-", });