| author | |
| committer | |
| log | 107b27276602d1935a90ae97c57f640b090088b5 |
| tree | 4606427ffab08b476973028265ee9d6c967304c6 |
| parent | e0ffac4e3c2271a617616760f3084f5f01fb0785 |
9 files changed, 598 insertions(+), 434 deletions(-)
lib/compiler/std-docs.zig+4-5| ... | @@ -275,10 +275,6 @@ fn buildWasmBinary( | ... | @@ -275,10 +275,6 @@ fn buildWasmBinary( |
| 275 | ) ![]const u8 { | 275 | ) ![]const u8 { |
| 276 | const gpa = context.gpa; | 276 | const gpa = context.gpa; |
| 277 | 277 | ||
| 278 | const main_src_path = try std.fs.path.join(arena, &.{ | ||
| 279 | context.zig_lib_directory, "docs", "wasm", "main.zig", | ||
| 280 | }); | ||
| 281 | |||
| 282 | var argv: std.ArrayListUnmanaged([]const u8) = .{}; | 278 | var argv: std.ArrayListUnmanaged([]const u8) = .{}; |
| 283 | 279 | ||
| 284 | try argv.appendSlice(arena, &.{ | 280 | try argv.appendSlice(arena, &.{ |
| ... | @@ -298,7 +294,10 @@ fn buildWasmBinary( | ... | @@ -298,7 +294,10 @@ fn buildWasmBinary( |
| 298 | "--name", | 294 | "--name", |
| 299 | "autodoc", | 295 | "autodoc", |
| 300 | "-rdynamic", | 296 | "-rdynamic", |
| 301 | main_src_path, | 297 | "--dep", |
| 298 | "Walk", | ||
| 299 | try std.fmt.allocPrint(arena, "-Mroot={s}/docs/wasm/main.zig", .{context.zig_lib_directory}), | ||
| 300 | try std.fmt.allocPrint(arena, "-MWalk={s}/docs/wasm/Walk.zig", .{context.zig_lib_directory}), | ||
| 302 | "--listen=-", | 301 | "--listen=-", |
| 303 | }); | 302 | }); |
| 304 | 303 |
lib/docs/wasm/Decl.zig+9-9| ... | @@ -1,3 +1,12 @@ | ... | @@ -1,3 +1,12 @@ |
| 1 | const Decl = @This(); | ||
| 2 | const std = @import("std"); | ||
| 3 | const Ast = std.zig.Ast; | ||
| 4 | const Walk = @import("Walk.zig"); | ||
| 5 | const gpa = std.heap.wasm_allocator; | ||
| 6 | const assert = std.debug.assert; | ||
| 7 | const log = std.log; | ||
| 8 | const Oom = error{OutOfMemory}; | ||
| 9 | |||
| 1 | ast_node: Ast.Node.Index, | 10 | ast_node: Ast.Node.Index, |
| 2 | file: Walk.File.Index, | 11 | file: Walk.File.Index, |
| 3 | /// The decl whose namespace this is in. | 12 | /// The decl whose namespace this is in. |
| ... | @@ -215,12 +224,3 @@ pub fn find(search_string: []const u8) Decl.Index { | ... | @@ -215,12 +224,3 @@ pub fn find(search_string: []const u8) Decl.Index { |
| 215 | } | 224 | } |
| 216 | return current_decl_index; | 225 | return current_decl_index; |
| 217 | } | 226 | } |
| 218 | |||
| 219 | const Decl = @This(); | ||
| 220 | const std = @import("std"); | ||
| 221 | const Ast = std.zig.Ast; | ||
| 222 | const Walk = @import("Walk.zig"); | ||
| 223 | const gpa = std.heap.wasm_allocator; | ||
| 224 | const assert = std.debug.assert; | ||
| 225 | const log = std.log; | ||
| 226 | const Oom = error{OutOfMemory}; |
lib/docs/wasm/Walk.zig+11-9| ... | @@ -1,4 +1,15 @@ | ... | @@ -1,4 +1,15 @@ |
| 1 | //! Find and annotate identifiers with links to their declarations. | 1 | //! Find and annotate identifiers with links to their declarations. |
| 2 | |||
| 3 | const Walk = @This(); | ||
| 4 | const std = @import("std"); | ||
| 5 | const Ast = std.zig.Ast; | ||
| 6 | const assert = std.debug.assert; | ||
| 7 | const log = std.log; | ||
| 8 | const gpa = std.heap.wasm_allocator; | ||
| 9 | const Oom = error{OutOfMemory}; | ||
| 10 | |||
| 11 | pub const Decl = @import("Decl.zig"); | ||
| 12 | |||
| 2 | pub var files: std.StringArrayHashMapUnmanaged(File) = .{}; | 13 | pub var files: std.StringArrayHashMapUnmanaged(File) = .{}; |
| 3 | pub var decls: std.ArrayListUnmanaged(Decl) = .{}; | 14 | pub var decls: std.ArrayListUnmanaged(Decl) = .{}; |
| 4 | pub var modules: std.StringArrayHashMapUnmanaged(File.Index) = .{}; | 15 | pub var modules: std.StringArrayHashMapUnmanaged(File.Index) = .{}; |
| ... | @@ -1120,15 +1131,6 @@ pub fn isPrimitiveNonType(name: []const u8) bool { | ... | @@ -1120,15 +1131,6 @@ pub fn isPrimitiveNonType(name: []const u8) bool { |
| 1120 | // try w.root(); | 1131 | // try w.root(); |
| 1121 | //} | 1132 | //} |
| 1122 | 1133 | ||
| 1123 | const Walk = @This(); | ||
| 1124 | const std = @import("std"); | ||
| 1125 | const Ast = std.zig.Ast; | ||
| 1126 | const assert = std.debug.assert; | ||
| 1127 | const Decl = @import("Decl.zig"); | ||
| 1128 | const log = std.log; | ||
| 1129 | const gpa = std.heap.wasm_allocator; | ||
| 1130 | const Oom = error{OutOfMemory}; | ||
| 1131 | |||
| 1132 | fn shrinkToFit(m: anytype) void { | 1134 | fn shrinkToFit(m: anytype) void { |
| 1133 | m.shrinkAndFree(gpa, m.entries.len); | 1135 | m.shrinkAndFree(gpa, m.entries.len); |
| 1134 | } | 1136 | } |
lib/docs/wasm/html_render.zig created+388| ... | @@ -0,0 +1,388 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const Ast = std.zig.Ast; | ||
| 3 | const assert = std.debug.assert; | ||
| 4 | |||
| 5 | const Walk = @import("Walk"); | ||
| 6 | const Decl = Walk.Decl; | ||
| 7 | |||
| 8 | const gpa = std.heap.wasm_allocator; | ||
| 9 | const Oom = error{OutOfMemory}; | ||
| 10 | |||
| 11 | /// Delete this to find out where URL escaping needs to be added. | ||
| 12 | pub const missing_feature_url_escape = true; | ||
| 13 | |||
| 14 | pub const RenderSourceOptions = struct { | ||
| 15 | skip_doc_comments: bool = false, | ||
| 16 | skip_comments: bool = false, | ||
| 17 | collapse_whitespace: bool = false, | ||
| 18 | fn_link: Decl.Index = .none, | ||
| 19 | }; | ||
| 20 | |||
| 21 | pub fn fileSourceHtml( | ||
| 22 | file_index: Walk.File.Index, | ||
| 23 | out: *std.ArrayListUnmanaged(u8), | ||
| 24 | root_node: Ast.Node.Index, | ||
| 25 | options: RenderSourceOptions, | ||
| 26 | ) !void { | ||
| 27 | const ast = file_index.get_ast(); | ||
| 28 | const file = file_index.get(); | ||
| 29 | |||
| 30 | const g = struct { | ||
| 31 | var field_access_buffer: std.ArrayListUnmanaged(u8) = .{}; | ||
| 32 | }; | ||
| 33 | |||
| 34 | const token_tags = ast.tokens.items(.tag); | ||
| 35 | const token_starts = ast.tokens.items(.start); | ||
| 36 | const main_tokens = ast.nodes.items(.main_token); | ||
| 37 | |||
| 38 | const start_token = ast.firstToken(root_node); | ||
| 39 | const end_token = ast.lastToken(root_node) + 1; | ||
| 40 | |||
| 41 | var cursor: usize = token_starts[start_token]; | ||
| 42 | |||
| 43 | var indent: usize = 0; | ||
| 44 | if (std.mem.lastIndexOf(u8, ast.source[0..cursor], "\n")) |newline_index| { | ||
| 45 | for (ast.source[newline_index + 1 .. cursor]) |c| { | ||
| 46 | if (c == ' ') { | ||
| 47 | indent += 1; | ||
| 48 | } else { | ||
| 49 | break; | ||
| 50 | } | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | for ( | ||
| 55 | token_tags[start_token..end_token], | ||
| 56 | token_starts[start_token..end_token], | ||
| 57 | start_token.., | ||
| 58 | ) |tag, start, token_index| { | ||
| 59 | const between = ast.source[cursor..start]; | ||
| 60 | if (std.mem.trim(u8, between, " \t\r\n").len > 0) { | ||
| 61 | if (!options.skip_comments) { | ||
| 62 | try out.appendSlice(gpa, "<span class=\"tok-comment\">"); | ||
| 63 | try appendUnindented(out, between, indent); | ||
| 64 | try out.appendSlice(gpa, "</span>"); | ||
| 65 | } | ||
| 66 | } else if (between.len > 0) { | ||
| 67 | if (options.collapse_whitespace) { | ||
| 68 | if (out.items.len > 0 and out.items[out.items.len - 1] != ' ') | ||
| 69 | try out.append(gpa, ' '); | ||
| 70 | } else { | ||
| 71 | try appendUnindented(out, between, indent); | ||
| 72 | } | ||
| 73 | } | ||
| 74 | if (tag == .eof) break; | ||
| 75 | const slice = ast.tokenSlice(token_index); | ||
| 76 | cursor = start + slice.len; | ||
| 77 | switch (tag) { | ||
| 78 | .eof => unreachable, | ||
| 79 | |||
| 80 | .keyword_addrspace, | ||
| 81 | .keyword_align, | ||
| 82 | .keyword_and, | ||
| 83 | .keyword_asm, | ||
| 84 | .keyword_async, | ||
| 85 | .keyword_await, | ||
| 86 | .keyword_break, | ||
| 87 | .keyword_catch, | ||
| 88 | .keyword_comptime, | ||
| 89 | .keyword_const, | ||
| 90 | .keyword_continue, | ||
| 91 | .keyword_defer, | ||
| 92 | .keyword_else, | ||
| 93 | .keyword_enum, | ||
| 94 | .keyword_errdefer, | ||
| 95 | .keyword_error, | ||
| 96 | .keyword_export, | ||
| 97 | .keyword_extern, | ||
| 98 | .keyword_for, | ||
| 99 | .keyword_if, | ||
| 100 | .keyword_inline, | ||
| 101 | .keyword_noalias, | ||
| 102 | .keyword_noinline, | ||
| 103 | .keyword_nosuspend, | ||
| 104 | .keyword_opaque, | ||
| 105 | .keyword_or, | ||
| 106 | .keyword_orelse, | ||
| 107 | .keyword_packed, | ||
| 108 | .keyword_anyframe, | ||
| 109 | .keyword_pub, | ||
| 110 | .keyword_resume, | ||
| 111 | .keyword_return, | ||
| 112 | .keyword_linksection, | ||
| 113 | .keyword_callconv, | ||
| 114 | .keyword_struct, | ||
| 115 | .keyword_suspend, | ||
| 116 | .keyword_switch, | ||
| 117 | .keyword_test, | ||
| 118 | .keyword_threadlocal, | ||
| 119 | .keyword_try, | ||
| 120 | .keyword_union, | ||
| 121 | .keyword_unreachable, | ||
| 122 | .keyword_usingnamespace, | ||
| 123 | .keyword_var, | ||
| 124 | .keyword_volatile, | ||
| 125 | .keyword_allowzero, | ||
| 126 | .keyword_while, | ||
| 127 | .keyword_anytype, | ||
| 128 | .keyword_fn, | ||
| 129 | => { | ||
| 130 | try out.appendSlice(gpa, "<span class=\"tok-kw\">"); | ||
| 131 | try appendEscaped(out, slice); | ||
| 132 | try out.appendSlice(gpa, "</span>"); | ||
| 133 | }, | ||
| 134 | |||
| 135 | .string_literal, | ||
| 136 | .char_literal, | ||
| 137 | .multiline_string_literal_line, | ||
| 138 | => { | ||
| 139 | try out.appendSlice(gpa, "<span class=\"tok-str\">"); | ||
| 140 | try appendEscaped(out, slice); | ||
| 141 | try out.appendSlice(gpa, "</span>"); | ||
| 142 | }, | ||
| 143 | |||
| 144 | .builtin => { | ||
| 145 | try out.appendSlice(gpa, "<span class=\"tok-builtin\">"); | ||
| 146 | try appendEscaped(out, slice); | ||
| 147 | try out.appendSlice(gpa, "</span>"); | ||
| 148 | }, | ||
| 149 | |||
| 150 | .doc_comment, | ||
| 151 | .container_doc_comment, | ||
| 152 | => { | ||
| 153 | if (!options.skip_doc_comments) { | ||
| 154 | try out.appendSlice(gpa, "<span class=\"tok-comment\">"); | ||
| 155 | try appendEscaped(out, slice); | ||
| 156 | try out.appendSlice(gpa, "</span>"); | ||
| 157 | } | ||
| 158 | }, | ||
| 159 | |||
| 160 | .identifier => i: { | ||
| 161 | if (options.fn_link != .none) { | ||
| 162 | const fn_link = options.fn_link.get(); | ||
| 163 | const fn_token = main_tokens[fn_link.ast_node]; | ||
| 164 | if (token_index == fn_token + 1) { | ||
| 165 | try out.appendSlice(gpa, "<a class=\"tok-fn\" href=\"#"); | ||
| 166 | _ = missing_feature_url_escape; | ||
| 167 | try fn_link.fqn(out); | ||
| 168 | try out.appendSlice(gpa, "\">"); | ||
| 169 | try appendEscaped(out, slice); | ||
| 170 | try out.appendSlice(gpa, "</a>"); | ||
| 171 | break :i; | ||
| 172 | } | ||
| 173 | } | ||
| 174 | |||
| 175 | if (token_index > 0 and token_tags[token_index - 1] == .keyword_fn) { | ||
| 176 | try out.appendSlice(gpa, "<span class=\"tok-fn\">"); | ||
| 177 | try appendEscaped(out, slice); | ||
| 178 | try out.appendSlice(gpa, "</span>"); | ||
| 179 | break :i; | ||
| 180 | } | ||
| 181 | |||
| 182 | if (Walk.isPrimitiveNonType(slice)) { | ||
| 183 | try out.appendSlice(gpa, "<span class=\"tok-null\">"); | ||
| 184 | try appendEscaped(out, slice); | ||
| 185 | try out.appendSlice(gpa, "</span>"); | ||
| 186 | break :i; | ||
| 187 | } | ||
| 188 | |||
| 189 | if (std.zig.primitives.isPrimitive(slice)) { | ||
| 190 | try out.appendSlice(gpa, "<span class=\"tok-type\">"); | ||
| 191 | try appendEscaped(out, slice); | ||
| 192 | try out.appendSlice(gpa, "</span>"); | ||
| 193 | break :i; | ||
| 194 | } | ||
| 195 | |||
| 196 | if (file.token_parents.get(token_index)) |field_access_node| { | ||
| 197 | g.field_access_buffer.clearRetainingCapacity(); | ||
| 198 | try walkFieldAccesses(file_index, &g.field_access_buffer, field_access_node); | ||
| 199 | if (g.field_access_buffer.items.len > 0) { | ||
| 200 | try out.appendSlice(gpa, "<a href=\"#"); | ||
| 201 | _ = missing_feature_url_escape; | ||
| 202 | try out.appendSlice(gpa, g.field_access_buffer.items); | ||
| 203 | try out.appendSlice(gpa, "\">"); | ||
| 204 | try appendEscaped(out, slice); | ||
| 205 | try out.appendSlice(gpa, "</a>"); | ||
| 206 | } else { | ||
| 207 | try appendEscaped(out, slice); | ||
| 208 | } | ||
| 209 | break :i; | ||
| 210 | } | ||
| 211 | |||
| 212 | { | ||
| 213 | g.field_access_buffer.clearRetainingCapacity(); | ||
| 214 | try resolveIdentLink(file_index, &g.field_access_buffer, token_index); | ||
| 215 | if (g.field_access_buffer.items.len > 0) { | ||
| 216 | try out.appendSlice(gpa, "<a href=\"#"); | ||
| 217 | _ = missing_feature_url_escape; | ||
| 218 | try out.appendSlice(gpa, g.field_access_buffer.items); | ||
| 219 | try out.appendSlice(gpa, "\">"); | ||
| 220 | try appendEscaped(out, slice); | ||
| 221 | try out.appendSlice(gpa, "</a>"); | ||
| 222 | break :i; | ||
| 223 | } | ||
| 224 | } | ||
| 225 | |||
| 226 | try appendEscaped(out, slice); | ||
| 227 | }, | ||
| 228 | |||
| 229 | .number_literal => { | ||
| 230 | try out.appendSlice(gpa, "<span class=\"tok-number\">"); | ||
| 231 | try appendEscaped(out, slice); | ||
| 232 | try out.appendSlice(gpa, "</span>"); | ||
| 233 | }, | ||
| 234 | |||
| 235 | .bang, | ||
| 236 | .pipe, | ||
| 237 | .pipe_pipe, | ||
| 238 | .pipe_equal, | ||
| 239 | .equal, | ||
| 240 | .equal_equal, | ||
| 241 | .equal_angle_bracket_right, | ||
| 242 | .bang_equal, | ||
| 243 | .l_paren, | ||
| 244 | .r_paren, | ||
| 245 | .semicolon, | ||
| 246 | .percent, | ||
| 247 | .percent_equal, | ||
| 248 | .l_brace, | ||
| 249 | .r_brace, | ||
| 250 | .l_bracket, | ||
| 251 | .r_bracket, | ||
| 252 | .period, | ||
| 253 | .period_asterisk, | ||
| 254 | .ellipsis2, | ||
| 255 | .ellipsis3, | ||
| 256 | .caret, | ||
| 257 | .caret_equal, | ||
| 258 | .plus, | ||
| 259 | .plus_plus, | ||
| 260 | .plus_equal, | ||
| 261 | .plus_percent, | ||
| 262 | .plus_percent_equal, | ||
| 263 | .plus_pipe, | ||
| 264 | .plus_pipe_equal, | ||
| 265 | .minus, | ||
| 266 | .minus_equal, | ||
| 267 | .minus_percent, | ||
| 268 | .minus_percent_equal, | ||
| 269 | .minus_pipe, | ||
| 270 | .minus_pipe_equal, | ||
| 271 | .asterisk, | ||
| 272 | .asterisk_equal, | ||
| 273 | .asterisk_asterisk, | ||
| 274 | .asterisk_percent, | ||
| 275 | .asterisk_percent_equal, | ||
| 276 | .asterisk_pipe, | ||
| 277 | .asterisk_pipe_equal, | ||
| 278 | .arrow, | ||
| 279 | .colon, | ||
| 280 | .slash, | ||
| 281 | .slash_equal, | ||
| 282 | .comma, | ||
| 283 | .ampersand, | ||
| 284 | .ampersand_equal, | ||
| 285 | .question_mark, | ||
| 286 | .angle_bracket_left, | ||
| 287 | .angle_bracket_left_equal, | ||
| 288 | .angle_bracket_angle_bracket_left, | ||
| 289 | .angle_bracket_angle_bracket_left_equal, | ||
| 290 | .angle_bracket_angle_bracket_left_pipe, | ||
| 291 | .angle_bracket_angle_bracket_left_pipe_equal, | ||
| 292 | .angle_bracket_right, | ||
| 293 | .angle_bracket_right_equal, | ||
| 294 | .angle_bracket_angle_bracket_right, | ||
| 295 | .angle_bracket_angle_bracket_right_equal, | ||
| 296 | .tilde, | ||
| 297 | => try appendEscaped(out, slice), | ||
| 298 | |||
| 299 | .invalid, .invalid_periodasterisks => return error.InvalidToken, | ||
| 300 | } | ||
| 301 | } | ||
| 302 | } | ||
| 303 | |||
| 304 | fn appendUnindented(out: *std.ArrayListUnmanaged(u8), s: []const u8, indent: usize) !void { | ||
| 305 | var it = std.mem.splitScalar(u8, s, '\n'); | ||
| 306 | var is_first_line = true; | ||
| 307 | while (it.next()) |line| { | ||
| 308 | if (is_first_line) { | ||
| 309 | try appendEscaped(out, line); | ||
| 310 | is_first_line = false; | ||
| 311 | } else { | ||
| 312 | try out.appendSlice(gpa, "\n"); | ||
| 313 | try appendEscaped(out, unindent(line, indent)); | ||
| 314 | } | ||
| 315 | } | ||
| 316 | } | ||
| 317 | |||
| 318 | pub fn appendEscaped(out: *std.ArrayListUnmanaged(u8), s: []const u8) !void { | ||
| 319 | for (s) |c| { | ||
| 320 | try out.ensureUnusedCapacity(gpa, 6); | ||
| 321 | switch (c) { | ||
| 322 | '&' => out.appendSliceAssumeCapacity("&amp;"), | ||
| 323 | '<' => out.appendSliceAssumeCapacity("&lt;"), | ||
| 324 | '>' => out.appendSliceAssumeCapacity("&gt;"), | ||
| 325 | '"' => out.appendSliceAssumeCapacity("&quot;"), | ||
| 326 | else => out.appendAssumeCapacity(c), | ||
| 327 | } | ||
| 328 | } | ||
| 329 | } | ||
| 330 | |||
| 331 | fn walkFieldAccesses( | ||
| 332 | file_index: Walk.File.Index, | ||
| 333 | out: *std.ArrayListUnmanaged(u8), | ||
| 334 | node: Ast.Node.Index, | ||
| 335 | ) Oom!void { | ||
| 336 | const ast = file_index.get_ast(); | ||
| 337 | const node_tags = ast.nodes.items(.tag); | ||
| 338 | assert(node_tags[node] == .field_access); | ||
| 339 | const node_datas = ast.nodes.items(.data); | ||
| 340 | const main_tokens = ast.nodes.items(.main_token); | ||
| 341 | const object_node = node_datas[node].lhs; | ||
| 342 | const dot_token = main_tokens[node]; | ||
| 343 | const field_ident = dot_token + 1; | ||
| 344 | switch (node_tags[object_node]) { | ||
| 345 | .identifier => { | ||
| 346 | const lhs_ident = main_tokens[object_node]; | ||
| 347 | try resolveIdentLink(file_index, out, lhs_ident); | ||
| 348 | }, | ||
| 349 | .field_access => { | ||
| 350 | try walkFieldAccesses(file_index, out, object_node); | ||
| 351 | }, | ||
| 352 | else => {}, | ||
| 353 | } | ||
| 354 | if (out.items.len > 0) { | ||
| 355 | try out.append(gpa, '.'); | ||
| 356 | try out.appendSlice(gpa, ast.tokenSlice(field_ident)); | ||
| 357 | } | ||
| 358 | } | ||
| 359 | |||
| 360 | fn resolveIdentLink( | ||
| 361 | file_index: Walk.File.Index, | ||
| 362 | out: *std.ArrayListUnmanaged(u8), | ||
| 363 | ident_token: Ast.TokenIndex, | ||
| 364 | ) Oom!void { | ||
| 365 | const decl_index = file_index.get().lookup_token(ident_token); | ||
| 366 | if (decl_index == .none) return; | ||
| 367 | try resolveDeclLink(decl_index, out); | ||
| 368 | } | ||
| 369 | |||
| 370 | fn unindent(s: []const u8, indent: usize) []const u8 { | ||
| 371 | var indent_idx: usize = 0; | ||
| 372 | for (s) |c| { | ||
| 373 | if (c == ' ' and indent_idx < indent) { | ||
| 374 | indent_idx += 1; | ||
| 375 | } else { | ||
| 376 | break; | ||
| 377 | } | ||
| 378 | } | ||
| 379 | return s[indent_idx..]; | ||
| 380 | } | ||
| 381 | |||
| 382 | pub fn resolveDeclLink(decl_index: Decl.Index, out: *std.ArrayListUnmanaged(u8)) Oom!void { | ||
| 383 | const decl = decl_index.get(); | ||
| 384 | switch (decl.categorize()) { | ||
| 385 | .alias => |alias_decl| try alias_decl.get().fqn(out), | ||
| 386 | else => try decl.fqn(out), | ||
| 387 | } | ||
| 388 | } | ||
lib/docs/wasm/main.zig+16-390| ... | @@ -1,15 +1,17 @@ | ... | @@ -1,15 +1,17 @@ |
| 1 | /// Delete this to find out where URL escaping needs to be added. | ||
| 2 | const missing_feature_url_escape = true; | ||
| 3 | |||
| 4 | const gpa = std.heap.wasm_allocator; | ||
| 5 | |||
| 6 | const std = @import("std"); | 1 | const std = @import("std"); |
| 7 | const log = std.log; | 2 | const log = std.log; |
| 8 | const assert = std.debug.assert; | 3 | const assert = std.debug.assert; |
| 9 | const Ast = std.zig.Ast; | 4 | const Ast = std.zig.Ast; |
| 10 | const Walk = @import("Walk.zig"); | 5 | const Walk = @import("Walk"); |
| 11 | const markdown = @import("markdown.zig"); | 6 | const markdown = @import("markdown.zig"); |
| 12 | const Decl = @import("Decl.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; | ||
| 13 | 15 | ||
| 14 | const js = struct { | 16 | const js = struct { |
| 15 | extern "js" fn log(ptr: [*]const u8, len: usize) void; | 17 | extern "js" fn log(ptr: [*]const u8, len: usize) void; |
| ... | @@ -439,7 +441,7 @@ fn decl_field_html_fallible( | ... | @@ -439,7 +441,7 @@ fn decl_field_html_fallible( |
| 439 | const decl = decl_index.get(); | 441 | const decl = decl_index.get(); |
| 440 | const ast = decl.file.get_ast(); | 442 | const ast = decl.file.get_ast(); |
| 441 | try out.appendSlice(gpa, "<pre><code>"); | 443 | try out.appendSlice(gpa, "<pre><code>"); |
| 442 | try file_source_html(decl.file, out, field_node, .{}); | 444 | try fileSourceHtml(decl.file, out, field_node, .{}); |
| 443 | try out.appendSlice(gpa, "</code></pre>"); | 445 | try out.appendSlice(gpa, "</code></pre>"); |
| 444 | 446 | ||
| 445 | const field = ast.fullContainerField(field_node).?; | 447 | const field = ast.fullContainerField(field_node).?; |
| ... | @@ -478,7 +480,7 @@ fn decl_param_html_fallible( | ... | @@ -478,7 +480,7 @@ fn decl_param_html_fallible( |
| 478 | try out.appendSlice(gpa, "<pre><code>"); | 480 | try out.appendSlice(gpa, "<pre><code>"); |
| 479 | try appendEscaped(out, name); | 481 | try appendEscaped(out, name); |
| 480 | try out.appendSlice(gpa, ": "); | 482 | try out.appendSlice(gpa, ": "); |
| 481 | try file_source_html(decl.file, out, param_node, .{}); | 483 | try fileSourceHtml(decl.file, out, param_node, .{}); |
| 482 | try out.appendSlice(gpa, "</code></pre>"); | 484 | try out.appendSlice(gpa, "</code></pre>"); |
| 483 | 485 | ||
| 484 | if (ast.tokens.items(.tag)[first_doc_comment] == .doc_comment) { | 486 | 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 | ... | @@ -506,7 +508,7 @@ export fn decl_fn_proto_html(decl_index: Decl.Index, linkify_fn_name: bool) Stri |
| 506 | }; | 508 | }; |
| 507 | 509 | ||
| 508 | string_result.clearRetainingCapacity(); | 510 | string_result.clearRetainingCapacity(); |
| 509 | file_source_html(decl.file, &string_result, proto_node, .{ | 511 | fileSourceHtml(decl.file, &string_result, proto_node, .{ |
| 510 | .skip_doc_comments = true, | 512 | .skip_doc_comments = true, |
| 511 | .skip_comments = true, | 513 | .skip_comments = true, |
| 512 | .collapse_whitespace = true, | 514 | .collapse_whitespace = true, |
| ... | @@ -521,7 +523,7 @@ export fn decl_source_html(decl_index: Decl.Index) String { | ... | @@ -521,7 +523,7 @@ export fn decl_source_html(decl_index: Decl.Index) String { |
| 521 | const decl = decl_index.get(); | 523 | const decl = decl_index.get(); |
| 522 | 524 | ||
| 523 | string_result.clearRetainingCapacity(); | 525 | string_result.clearRetainingCapacity(); |
| 524 | file_source_html(decl.file, &string_result, decl.ast_node, .{}) catch |err| { | 526 | fileSourceHtml(decl.file, &string_result, decl.ast_node, .{}) catch |err| { |
| 525 | fatal("unable to render source: {s}", .{@errorName(err)}); | 527 | fatal("unable to render source: {s}", .{@errorName(err)}); |
| 526 | }; | 528 | }; |
| 527 | return String.init(string_result.items); | 529 | return String.init(string_result.items); |
| ... | @@ -533,7 +535,7 @@ export fn decl_doctest_html(decl_index: Decl.Index) String { | ... | @@ -533,7 +535,7 @@ export fn decl_doctest_html(decl_index: Decl.Index) String { |
| 533 | return String.init(""); | 535 | return String.init(""); |
| 534 | 536 | ||
| 535 | string_result.clearRetainingCapacity(); | 537 | string_result.clearRetainingCapacity(); |
| 536 | file_source_html(decl.file, &string_result, doctest_ast_node, .{}) catch |err| { | 538 | fileSourceHtml(decl.file, &string_result, doctest_ast_node, .{}) catch |err| { |
| 537 | fatal("unable to render source: {s}", .{@errorName(err)}); | 539 | fatal("unable to render source: {s}", .{@errorName(err)}); |
| 538 | }; | 540 | }; |
| 539 | return String.init(string_result.items); | 541 | return String.init(string_result.items); |
| ... | @@ -691,7 +693,7 @@ fn render_docs( | ... | @@ -691,7 +693,7 @@ fn render_docs( |
| 691 | const content = doc.string(data.text.content); | 693 | const content = doc.string(data.text.content); |
| 692 | if (resolve_decl_path(r.context, content)) |resolved_decl_index| { | 694 | if (resolve_decl_path(r.context, content)) |resolved_decl_index| { |
| 693 | g.link_buffer.clearRetainingCapacity(); | 695 | g.link_buffer.clearRetainingCapacity(); |
| 694 | try resolve_decl_link(resolved_decl_index, &g.link_buffer); | 696 | try resolveDeclLink(resolved_decl_index, &g.link_buffer); |
| 695 | 697 | ||
| 696 | try writer.writeAll("<a href=\"#"); | 698 | try writer.writeAll("<a href=\"#"); |
| 697 | _ = missing_feature_url_escape; | 699 | _ = missing_feature_url_escape; |
| ... | @@ -734,7 +736,7 @@ export fn decl_type_html(decl_index: Decl.Index) String { | ... | @@ -734,7 +736,7 @@ export fn decl_type_html(decl_index: Decl.Index) String { |
| 734 | if (ast.fullVarDecl(decl.ast_node)) |var_decl| { | 736 | if (ast.fullVarDecl(decl.ast_node)) |var_decl| { |
| 735 | if (var_decl.ast.type_node != 0) { | 737 | if (var_decl.ast.type_node != 0) { |
| 736 | string_result.appendSlice(gpa, "<code>") catch @panic("OOM"); | 738 | string_result.appendSlice(gpa, "<code>") catch @panic("OOM"); |
| 737 | file_source_html(decl.file, &string_result, var_decl.ast.type_node, .{ | 739 | fileSourceHtml(decl.file, &string_result, var_decl.ast.type_node, .{ |
| 738 | .skip_comments = true, | 740 | .skip_comments = true, |
| 739 | .collapse_whitespace = true, | 741 | .collapse_whitespace = true, |
| 740 | }) catch |e| { | 742 | }) catch |e| { |
| ... | @@ -902,382 +904,6 @@ export fn namespace_members(parent: Decl.Index, include_private: bool) Slice(Dec | ... | @@ -902,382 +904,6 @@ export fn namespace_members(parent: Decl.Index, include_private: bool) Slice(Dec |
| 902 | return Slice(Decl.Index).init(g.members.items); | 904 | return Slice(Decl.Index).init(g.members.items); |
| 903 | } | 905 | } |
| 904 | 906 | ||
| 905 | const RenderSourceOptions = struct { | ||
| 906 | skip_doc_comments: bool = false, | ||
| 907 | skip_comments: bool = false, | ||
| 908 | collapse_whitespace: bool = false, | ||
| 909 | fn_link: Decl.Index = .none, | ||
| 910 | }; | ||
| 911 | |||
| 912 | fn file_source_html( | ||
| 913 | file_index: Walk.File.Index, | ||
| 914 | out: *std.ArrayListUnmanaged(u8), | ||
| 915 | root_node: Ast.Node.Index, | ||
| 916 | options: RenderSourceOptions, | ||
| 917 | ) !void { | ||
| 918 | const ast = file_index.get_ast(); | ||
| 919 | const file = file_index.get(); | ||
| 920 | |||
| 921 | const g = struct { | ||
| 922 | var field_access_buffer: std.ArrayListUnmanaged(u8) = .{}; | ||
| 923 | }; | ||
| 924 | |||
| 925 | const token_tags = ast.tokens.items(.tag); | ||
| 926 | const token_starts = ast.tokens.items(.start); | ||
| 927 | const main_tokens = ast.nodes.items(.main_token); | ||
| 928 | |||
| 929 | const start_token = ast.firstToken(root_node); | ||
| 930 | const end_token = ast.lastToken(root_node) + 1; | ||
| 931 | |||
| 932 | var cursor: usize = token_starts[start_token]; | ||
| 933 | |||
| 934 | var indent: usize = 0; | ||
| 935 | if (std.mem.lastIndexOf(u8, ast.source[0..cursor], "\n")) |newline_index| { | ||
| 936 | for (ast.source[newline_index + 1 .. cursor]) |c| { | ||
| 937 | if (c == ' ') { | ||
| 938 | indent += 1; | ||
| 939 | } else { | ||
| 940 | break; | ||
| 941 | } | ||
| 942 | } | ||
| 943 | } | ||
| 944 | |||
| 945 | for ( | ||
| 946 | token_tags[start_token..end_token], | ||
| 947 | token_starts[start_token..end_token], | ||
| 948 | start_token.., | ||
| 949 | ) |tag, start, token_index| { | ||
| 950 | const between = ast.source[cursor..start]; | ||
| 951 | if (std.mem.trim(u8, between, " \t\r\n").len > 0) { | ||
| 952 | if (!options.skip_comments) { | ||
| 953 | try out.appendSlice(gpa, "<span class=\"tok-comment\">"); | ||
| 954 | try appendUnindented(out, between, indent); | ||
| 955 | try out.appendSlice(gpa, "</span>"); | ||
| 956 | } | ||
| 957 | } else if (between.len > 0) { | ||
| 958 | if (options.collapse_whitespace) { | ||
| 959 | if (out.items.len > 0 and out.items[out.items.len - 1] != ' ') | ||
| 960 | try out.append(gpa, ' '); | ||
| 961 | } else { | ||
| 962 | try appendUnindented(out, between, indent); | ||
| 963 | } | ||
| 964 | } | ||
| 965 | if (tag == .eof) break; | ||
| 966 | const slice = ast.tokenSlice(token_index); | ||
| 967 | cursor = start + slice.len; | ||
| 968 | switch (tag) { | ||
| 969 | .eof => unreachable, | ||
| 970 | |||
| 971 | .keyword_addrspace, | ||
| 972 | .keyword_align, | ||
| 973 | .keyword_and, | ||
| 974 | .keyword_asm, | ||
| 975 | .keyword_async, | ||
| 976 | .keyword_await, | ||
| 977 | .keyword_break, | ||
| 978 | .keyword_catch, | ||
| 979 | .keyword_comptime, | ||
| 980 | .keyword_const, | ||
| 981 | .keyword_continue, | ||
| 982 | .keyword_defer, | ||
| 983 | .keyword_else, | ||
| 984 | .keyword_enum, | ||
| 985 | .keyword_errdefer, | ||
| 986 | .keyword_error, | ||
| 987 | .keyword_export, | ||
| 988 | .keyword_extern, | ||
| 989 | .keyword_for, | ||
| 990 | .keyword_if, | ||
| 991 | .keyword_inline, | ||
| 992 | .keyword_noalias, | ||
| 993 | .keyword_noinline, | ||
| 994 | .keyword_nosuspend, | ||
| 995 | .keyword_opaque, | ||
| 996 | .keyword_or, | ||
| 997 | .keyword_orelse, | ||
| 998 | .keyword_packed, | ||
| 999 | .keyword_anyframe, | ||
| 1000 | .keyword_pub, | ||
| 1001 | .keyword_resume, | ||
| 1002 | .keyword_return, | ||
| 1003 | .keyword_linksection, | ||
| 1004 | .keyword_callconv, | ||
| 1005 | .keyword_struct, | ||
| 1006 | .keyword_suspend, | ||
| 1007 | .keyword_switch, | ||
| 1008 | .keyword_test, | ||
| 1009 | .keyword_threadlocal, | ||
| 1010 | .keyword_try, | ||
| 1011 | .keyword_union, | ||
| 1012 | .keyword_unreachable, | ||
| 1013 | .keyword_usingnamespace, | ||
| 1014 | .keyword_var, | ||
| 1015 | .keyword_volatile, | ||
| 1016 | .keyword_allowzero, | ||
| 1017 | .keyword_while, | ||
| 1018 | .keyword_anytype, | ||
| 1019 | .keyword_fn, | ||
| 1020 | => { | ||
| 1021 | try out.appendSlice(gpa, "<span class=\"tok-kw\">"); | ||
| 1022 | try appendEscaped(out, slice); | ||
| 1023 | try out.appendSlice(gpa, "</span>"); | ||
| 1024 | }, | ||
| 1025 | |||
| 1026 | .string_literal, | ||
| 1027 | .char_literal, | ||
| 1028 | .multiline_string_literal_line, | ||
| 1029 | => { | ||
| 1030 | try out.appendSlice(gpa, "<span class=\"tok-str\">"); | ||
| 1031 | try appendEscaped(out, slice); | ||
| 1032 | try out.appendSlice(gpa, "</span>"); | ||
| 1033 | }, | ||
| 1034 | |||
| 1035 | .builtin => { | ||
| 1036 | try out.appendSlice(gpa, "<span class=\"tok-builtin\">"); | ||
| 1037 | try appendEscaped(out, slice); | ||
| 1038 | try out.appendSlice(gpa, "</span>"); | ||
| 1039 | }, | ||
| 1040 | |||
| 1041 | .doc_comment, | ||
| 1042 | .container_doc_comment, | ||
| 1043 | => { | ||
| 1044 | if (!options.skip_doc_comments) { | ||
| 1045 | try out.appendSlice(gpa, "<span class=\"tok-comment\">"); | ||
| 1046 | try appendEscaped(out, slice); | ||
| 1047 | try out.appendSlice(gpa, "</span>"); | ||
| 1048 | } | ||
| 1049 | }, | ||
| 1050 | |||
| 1051 | .identifier => i: { | ||
| 1052 | if (options.fn_link != .none) { | ||
| 1053 | const fn_link = options.fn_link.get(); | ||
| 1054 | const fn_token = main_tokens[fn_link.ast_node]; | ||
| 1055 | if (token_index == fn_token + 1) { | ||
| 1056 | try out.appendSlice(gpa, "<a class=\"tok-fn\" href=\"#"); | ||
| 1057 | _ = missing_feature_url_escape; | ||
| 1058 | try fn_link.fqn(out); | ||
| 1059 | try out.appendSlice(gpa, "\">"); | ||
| 1060 | try appendEscaped(out, slice); | ||
| 1061 | try out.appendSlice(gpa, "</a>"); | ||
| 1062 | break :i; | ||
| 1063 | } | ||
| 1064 | } | ||
| 1065 | |||
| 1066 | if (token_index > 0 and token_tags[token_index - 1] == .keyword_fn) { | ||
| 1067 | try out.appendSlice(gpa, "<span class=\"tok-fn\">"); | ||
| 1068 | try appendEscaped(out, slice); | ||
| 1069 | try out.appendSlice(gpa, "</span>"); | ||
| 1070 | break :i; | ||
| 1071 | } | ||
| 1072 | |||
| 1073 | if (Walk.isPrimitiveNonType(slice)) { | ||
| 1074 | try out.appendSlice(gpa, "<span class=\"tok-null\">"); | ||
| 1075 | try appendEscaped(out, slice); | ||
| 1076 | try out.appendSlice(gpa, "</span>"); | ||
| 1077 | break :i; | ||
| 1078 | } | ||
| 1079 | |||
| 1080 | if (std.zig.primitives.isPrimitive(slice)) { | ||
| 1081 | try out.appendSlice(gpa, "<span class=\"tok-type\">"); | ||
| 1082 | try appendEscaped(out, slice); | ||
| 1083 | try out.appendSlice(gpa, "</span>"); | ||
| 1084 | break :i; | ||
| 1085 | } | ||
| 1086 | |||
| 1087 | if (file.token_parents.get(token_index)) |field_access_node| { | ||
| 1088 | g.field_access_buffer.clearRetainingCapacity(); | ||
| 1089 | try walk_field_accesses(file_index, &g.field_access_buffer, field_access_node); | ||
| 1090 | if (g.field_access_buffer.items.len > 0) { | ||
| 1091 | try out.appendSlice(gpa, "<a href=\"#"); | ||
| 1092 | _ = missing_feature_url_escape; | ||
| 1093 | try out.appendSlice(gpa, g.field_access_buffer.items); | ||
| 1094 | try out.appendSlice(gpa, "\">"); | ||
| 1095 | try appendEscaped(out, slice); | ||
| 1096 | try out.appendSlice(gpa, "</a>"); | ||
| 1097 | } else { | ||
| 1098 | try appendEscaped(out, slice); | ||
| 1099 | } | ||
| 1100 | break :i; | ||
| 1101 | } | ||
| 1102 | |||
| 1103 | { | ||
| 1104 | g.field_access_buffer.clearRetainingCapacity(); | ||
| 1105 | try resolve_ident_link(file_index, &g.field_access_buffer, token_index); | ||
| 1106 | if (g.field_access_buffer.items.len > 0) { | ||
| 1107 | try out.appendSlice(gpa, "<a href=\"#"); | ||
| 1108 | _ = missing_feature_url_escape; | ||
| 1109 | try out.appendSlice(gpa, g.field_access_buffer.items); | ||
| 1110 | try out.appendSlice(gpa, "\">"); | ||
| 1111 | try appendEscaped(out, slice); | ||
| 1112 | try out.appendSlice(gpa, "</a>"); | ||
| 1113 | break :i; | ||
| 1114 | } | ||
| 1115 | } | ||
| 1116 | |||
| 1117 | try appendEscaped(out, slice); | ||
| 1118 | }, | ||
| 1119 | |||
| 1120 | .number_literal => { | ||
| 1121 | try out.appendSlice(gpa, "<span class=\"tok-number\">"); | ||
| 1122 | try appendEscaped(out, slice); | ||
| 1123 | try out.appendSlice(gpa, "</span>"); | ||
| 1124 | }, | ||
| 1125 | |||
| 1126 | .bang, | ||
| 1127 | .pipe, | ||
| 1128 | .pipe_pipe, | ||
| 1129 | .pipe_equal, | ||
| 1130 | .equal, | ||
| 1131 | .equal_equal, | ||
| 1132 | .equal_angle_bracket_right, | ||
| 1133 | .bang_equal, | ||
| 1134 | .l_paren, | ||
| 1135 | .r_paren, | ||
| 1136 | .semicolon, | ||
| 1137 | .percent, | ||
| 1138 | .percent_equal, | ||
| 1139 | .l_brace, | ||
| 1140 | .r_brace, | ||
| 1141 | .l_bracket, | ||
| 1142 | .r_bracket, | ||
| 1143 | .period, | ||
| 1144 | .period_asterisk, | ||
| 1145 | .ellipsis2, | ||
| 1146 | .ellipsis3, | ||
| 1147 | .caret, | ||
| 1148 | .caret_equal, | ||
| 1149 | .plus, | ||
| 1150 | .plus_plus, | ||
| 1151 | .plus_equal, | ||
| 1152 | .plus_percent, | ||
| 1153 | .plus_percent_equal, | ||
| 1154 | .plus_pipe, | ||
| 1155 | .plus_pipe_equal, | ||
| 1156 | .minus, | ||
| 1157 | .minus_equal, | ||
| 1158 | .minus_percent, | ||
| 1159 | .minus_percent_equal, | ||
| 1160 | .minus_pipe, | ||
| 1161 | .minus_pipe_equal, | ||
| 1162 | .asterisk, | ||
| 1163 | .asterisk_equal, | ||
| 1164 | .asterisk_asterisk, | ||
| 1165 | .asterisk_percent, | ||
| 1166 | .asterisk_percent_equal, | ||
| 1167 | .asterisk_pipe, | ||
| 1168 | .asterisk_pipe_equal, | ||
| 1169 | .arrow, | ||
| 1170 | .colon, | ||
| 1171 | .slash, | ||
| 1172 | .slash_equal, | ||
| 1173 | .comma, | ||
| 1174 | .ampersand, | ||
| 1175 | .ampersand_equal, | ||
| 1176 | .question_mark, | ||
| 1177 | .angle_bracket_left, | ||
| 1178 | .angle_bracket_left_equal, | ||
| 1179 | .angle_bracket_angle_bracket_left, | ||
| 1180 | .angle_bracket_angle_bracket_left_equal, | ||
| 1181 | .angle_bracket_angle_bracket_left_pipe, | ||
| 1182 | .angle_bracket_angle_bracket_left_pipe_equal, | ||
| 1183 | .angle_bracket_right, | ||
| 1184 | .angle_bracket_right_equal, | ||
| 1185 | .angle_bracket_angle_bracket_right, | ||
| 1186 | .angle_bracket_angle_bracket_right_equal, | ||
| 1187 | .tilde, | ||
| 1188 | => try appendEscaped(out, slice), | ||
| 1189 | |||
| 1190 | .invalid, .invalid_periodasterisks => return error.InvalidToken, | ||
| 1191 | } | ||
| 1192 | } | ||
| 1193 | } | ||
| 1194 | |||
| 1195 | fn unindent(s: []const u8, indent: usize) []const u8 { | ||
| 1196 | var indent_idx: usize = 0; | ||
| 1197 | for (s) |c| { | ||
| 1198 | if (c == ' ' and indent_idx < indent) { | ||
| 1199 | indent_idx += 1; | ||
| 1200 | } else { | ||
| 1201 | break; | ||
| 1202 | } | ||
| 1203 | } | ||
| 1204 | return s[indent_idx..]; | ||
| 1205 | } | ||
| 1206 | |||
| 1207 | fn appendUnindented(out: *std.ArrayListUnmanaged(u8), s: []const u8, indent: usize) !void { | ||
| 1208 | var it = std.mem.splitScalar(u8, s, '\n'); | ||
| 1209 | var is_first_line = true; | ||
| 1210 | while (it.next()) |line| { | ||
| 1211 | if (is_first_line) { | ||
| 1212 | try appendEscaped(out, line); | ||
| 1213 | is_first_line = false; | ||
| 1214 | } else { | ||
| 1215 | try out.appendSlice(gpa, "\n"); | ||
| 1216 | try appendEscaped(out, unindent(line, indent)); | ||
| 1217 | } | ||
| 1218 | } | ||
| 1219 | } | ||
| 1220 | |||
| 1221 | fn resolve_ident_link( | ||
| 1222 | file_index: Walk.File.Index, | ||
| 1223 | out: *std.ArrayListUnmanaged(u8), | ||
| 1224 | ident_token: Ast.TokenIndex, | ||
| 1225 | ) Oom!void { | ||
| 1226 | const decl_index = file_index.get().lookup_token(ident_token); | ||
| 1227 | if (decl_index == .none) return; | ||
| 1228 | try resolve_decl_link(decl_index, out); | ||
| 1229 | } | ||
| 1230 | |||
| 1231 | fn resolve_decl_link(decl_index: Decl.Index, out: *std.ArrayListUnmanaged(u8)) Oom!void { | ||
| 1232 | const decl = decl_index.get(); | ||
| 1233 | switch (decl.categorize()) { | ||
| 1234 | .alias => |alias_decl| try alias_decl.get().fqn(out), | ||
| 1235 | else => try decl.fqn(out), | ||
| 1236 | } | ||
| 1237 | } | ||
| 1238 | |||
| 1239 | fn walk_field_accesses( | ||
| 1240 | file_index: Walk.File.Index, | ||
| 1241 | out: *std.ArrayListUnmanaged(u8), | ||
| 1242 | node: Ast.Node.Index, | ||
| 1243 | ) Oom!void { | ||
| 1244 | const ast = file_index.get_ast(); | ||
| 1245 | const node_tags = ast.nodes.items(.tag); | ||
| 1246 | assert(node_tags[node] == .field_access); | ||
| 1247 | const node_datas = ast.nodes.items(.data); | ||
| 1248 | const main_tokens = ast.nodes.items(.main_token); | ||
| 1249 | const object_node = node_datas[node].lhs; | ||
| 1250 | const dot_token = main_tokens[node]; | ||
| 1251 | const field_ident = dot_token + 1; | ||
| 1252 | switch (node_tags[object_node]) { | ||
| 1253 | .identifier => { | ||
| 1254 | const lhs_ident = main_tokens[object_node]; | ||
| 1255 | try resolve_ident_link(file_index, out, lhs_ident); | ||
| 1256 | }, | ||
| 1257 | .field_access => { | ||
| 1258 | try walk_field_accesses(file_index, out, object_node); | ||
| 1259 | }, | ||
| 1260 | else => {}, | ||
| 1261 | } | ||
| 1262 | if (out.items.len > 0) { | ||
| 1263 | try out.append(gpa, '.'); | ||
| 1264 | try out.appendSlice(gpa, ast.tokenSlice(field_ident)); | ||
| 1265 | } | ||
| 1266 | } | ||
| 1267 | |||
| 1268 | fn appendEscaped(out: *std.ArrayListUnmanaged(u8), s: []const u8) !void { | ||
| 1269 | for (s) |c| { | ||
| 1270 | try out.ensureUnusedCapacity(gpa, 6); | ||
| 1271 | switch (c) { | ||
| 1272 | '&' => out.appendSliceAssumeCapacity("&amp;"), | ||
| 1273 | '<' => out.appendSliceAssumeCapacity("&lt;"), | ||
| 1274 | '>' => out.appendSliceAssumeCapacity("&gt;"), | ||
| 1275 | '"' => out.appendSliceAssumeCapacity("&quot;"), | ||
| 1276 | else => out.appendAssumeCapacity(c), | ||
| 1277 | } | ||
| 1278 | } | ||
| 1279 | } | ||
| 1280 | |||
| 1281 | fn count_scalar(haystack: []const u8, needle: u8) usize { | 907 | fn count_scalar(haystack: []const u8, needle: u8) usize { |
| 1282 | var total: usize = 0; | 908 | var total: usize = 0; |
| 1283 | for (haystack) |elem| { | 909 | for (haystack) |elem| { |
lib/fuzzer/index.html+59-1| ... | @@ -2,12 +2,56 @@ | ... | @@ -2,12 +2,56 @@ |
| 2 | <html> | 2 | <html> |
| 3 | <head> | 3 | <head> |
| 4 | <meta charset="utf-8"> | 4 | <meta charset="utf-8"> |
| 5 | <title>Zig Documentation</title> | 5 | <title>Zig Build System Interface</title> |
| 6 | <style type="text/css"> | 6 | <style type="text/css"> |
| 7 | body { | 7 | body { |
| 8 | font-family: system-ui, -apple-system, Roboto, "Segoe UI", sans-serif; | 8 | font-family: system-ui, -apple-system, Roboto, "Segoe UI", sans-serif; |
| 9 | color: #000000; | 9 | color: #000000; |
| 10 | } | 10 | } |
| 11 | .hidden { | ||
| 12 | display: none; | ||
| 13 | } | ||
| 14 | table { | ||
| 15 | width: 100%; | ||
| 16 | } | ||
| 17 | a { | ||
| 18 | color: #2A6286; | ||
| 19 | } | ||
| 20 | pre{ | ||
| 21 | font-family:"Source Code Pro",monospace; | ||
| 22 | font-size:1em; | ||
| 23 | background-color:#F5F5F5; | ||
| 24 | padding: 1em; | ||
| 25 | margin: 0; | ||
| 26 | overflow-x: auto; | ||
| 27 | } | ||
| 28 | :not(pre) > code { | ||
| 29 | white-space: break-spaces; | ||
| 30 | } | ||
| 31 | code { | ||
| 32 | font-family:"Source Code Pro",monospace; | ||
| 33 | font-size: 0.9em; | ||
| 34 | } | ||
| 35 | code a { | ||
| 36 | color: #000000; | ||
| 37 | } | ||
| 38 | kbd { | ||
| 39 | color: #000; | ||
| 40 | background-color: #fafbfc; | ||
| 41 | border-color: #d1d5da; | ||
| 42 | border-bottom-color: #c6cbd1; | ||
| 43 | box-shadow-color: #c6cbd1; | ||
| 44 | display: inline-block; | ||
| 45 | padding: 0.3em 0.2em; | ||
| 46 | font: 1.2em monospace; | ||
| 47 | line-height: 0.8em; | ||
| 48 | vertical-align: middle; | ||
| 49 | border: solid 1px; | ||
| 50 | border-radius: 3px; | ||
| 51 | box-shadow: inset 0 -1px 0; | ||
| 52 | cursor: default; | ||
| 53 | } | ||
| 54 | |||
| 11 | .tok-kw { | 55 | .tok-kw { |
| 12 | color: #333; | 56 | color: #333; |
| 13 | font-weight: bold; | 57 | font-weight: bold; |
| ... | @@ -42,6 +86,16 @@ | ... | @@ -42,6 +86,16 @@ |
| 42 | background-color: #111; | 86 | background-color: #111; |
| 43 | color: #bbb; | 87 | color: #bbb; |
| 44 | } | 88 | } |
| 89 | pre { | ||
| 90 | background-color: #222; | ||
| 91 | color: #ccc; | ||
| 92 | } | ||
| 93 | a { | ||
| 94 | color: #88f; | ||
| 95 | } | ||
| 96 | code a { | ||
| 97 | color: #ccc; | ||
| 98 | } | ||
| 45 | .tok-kw { | 99 | .tok-kw { |
| 46 | color: #eee; | 100 | color: #eee; |
| 47 | } | 101 | } |
| ... | @@ -70,6 +124,10 @@ | ... | @@ -70,6 +124,10 @@ |
| 70 | </style> | 124 | </style> |
| 71 | </head> | 125 | </head> |
| 72 | <body> | 126 | <body> |
| 127 | <div id="sectSource" class="hidden"> | ||
| 128 | <h2>Source Code</h2> | ||
| 129 | <pre><code id="sourceText"></code></pre> | ||
| 130 | </div> | ||
| 73 | <script src="main.js"></script> | 131 | <script src="main.js"></script> |
| 74 | </body> | 132 | </body> |
| 75 | </html> | 133 | </html> |
lib/fuzzer/main.js+48| ... | @@ -1,4 +1,7 @@ | ... | @@ -1,4 +1,7 @@ |
| 1 | (function() { | 1 | (function() { |
| 2 | const domSectSource = document.getElementById("sectSource"); | ||
| 3 | const domSourceText = document.getElementById("sourceText"); | ||
| 4 | |||
| 2 | let wasm_promise = fetch("main.wasm"); | 5 | let wasm_promise = fetch("main.wasm"); |
| 3 | let sources_promise = fetch("sources.tar").then(function(response) { | 6 | let sources_promise = fetch("sources.tar").then(function(response) { |
| 4 | if (!response.ok) throw new Error("unable to download sources"); | 7 | if (!response.ok) throw new Error("unable to download sources"); |
| ... | @@ -30,11 +33,56 @@ | ... | @@ -30,11 +33,56 @@ |
| 30 | const wasm_array = new Uint8Array(wasm_exports.memory.buffer, ptr, js_array.length); | 33 | const wasm_array = new Uint8Array(wasm_exports.memory.buffer, ptr, js_array.length); |
| 31 | wasm_array.set(js_array); | 34 | wasm_array.set(js_array); |
| 32 | wasm_exports.unpack(ptr, js_array.length); | 35 | wasm_exports.unpack(ptr, js_array.length); |
| 36 | |||
| 37 | render(); | ||
| 33 | }); | 38 | }); |
| 34 | }); | 39 | }); |
| 35 | 40 | ||
| 41 | function render() { | ||
| 42 | domSectSource.classList.add("hidden"); | ||
| 43 | |||
| 44 | // TODO this is temporary debugging data | ||
| 45 | renderSource("/home/andy/dev/zig/lib/std/zig/tokenizer.zig"); | ||
| 46 | } | ||
| 47 | |||
| 48 | function renderSource(path) { | ||
| 49 | const decl_index = findFileRoot(path); | ||
| 50 | if (decl_index == null) throw new Error("file not found: " + path); | ||
| 51 | |||
| 52 | const h2 = domSectSource.children[0]; | ||
| 53 | h2.innerText = path; | ||
| 54 | domSourceText.innerHTML = declSourceHtml(decl_index); | ||
| 55 | |||
| 56 | domSectSource.classList.remove("hidden"); | ||
| 57 | } | ||
| 58 | |||
| 59 | function findFileRoot(path) { | ||
| 60 | setInputString(path); | ||
| 61 | const result = wasm_exports.find_file_root(); | ||
| 62 | if (result === -1) return null; | ||
| 63 | return result; | ||
| 64 | } | ||
| 65 | |||
| 36 | function decodeString(ptr, len) { | 66 | function decodeString(ptr, len) { |
| 37 | if (len === 0) return ""; | 67 | if (len === 0) return ""; |
| 38 | return text_decoder.decode(new Uint8Array(wasm_exports.memory.buffer, ptr, len)); | 68 | return text_decoder.decode(new Uint8Array(wasm_exports.memory.buffer, ptr, len)); |
| 39 | } | 69 | } |
| 70 | |||
| 71 | function setInputString(s) { | ||
| 72 | const jsArray = text_encoder.encode(s); | ||
| 73 | const len = jsArray.length; | ||
| 74 | const ptr = wasm_exports.set_input_string(len); | ||
| 75 | const wasmArray = new Uint8Array(wasm_exports.memory.buffer, ptr, len); | ||
| 76 | wasmArray.set(jsArray); | ||
| 77 | } | ||
| 78 | |||
| 79 | function declSourceHtml(decl_index) { | ||
| 80 | return unwrapString(wasm_exports.decl_source_html(decl_index)); | ||
| 81 | } | ||
| 82 | |||
| 83 | function unwrapString(bigint) { | ||
| 84 | const ptr = Number(bigint & 0xffffffffn); | ||
| 85 | const len = Number(bigint >> 32n); | ||
| 86 | return decodeString(ptr, len); | ||
| 87 | } | ||
| 40 | })(); | 88 | })(); |
lib/fuzzer/wasm/main.zig+44| ... | @@ -2,6 +2,8 @@ const std = @import("std"); | ... | @@ -2,6 +2,8 @@ const std = @import("std"); |
| 2 | const assert = std.debug.assert; | 2 | const assert = std.debug.assert; |
| 3 | 3 | ||
| 4 | const Walk = @import("Walk"); | 4 | const Walk = @import("Walk"); |
| 5 | const Decl = Walk.Decl; | ||
| 6 | const html_render = @import("html_render"); | ||
| 5 | 7 | ||
| 6 | const gpa = std.heap.wasm_allocator; | 8 | const gpa = std.heap.wasm_allocator; |
| 7 | const log = std.log; | 9 | const log = std.log; |
| ... | @@ -52,6 +54,48 @@ export fn unpack(tar_ptr: [*]u8, tar_len: usize) void { | ... | @@ -52,6 +54,48 @@ export fn unpack(tar_ptr: [*]u8, tar_len: usize) void { |
| 52 | }; | 54 | }; |
| 53 | } | 55 | } |
| 54 | 56 | ||
| 57 | /// Set by `set_input_string`. | ||
| 58 | var input_string: std.ArrayListUnmanaged(u8) = .{}; | ||
| 59 | var string_result: std.ArrayListUnmanaged(u8) = .{}; | ||
| 60 | |||
| 61 | export fn set_input_string(len: usize) [*]u8 { | ||
| 62 | input_string.resize(gpa, len) catch @panic("OOM"); | ||
| 63 | return input_string.items.ptr; | ||
| 64 | } | ||
| 65 | |||
| 66 | /// Looks up the root struct decl corresponding to a file by path. | ||
| 67 | /// Uses `input_string`. | ||
| 68 | export fn find_file_root() Decl.Index { | ||
| 69 | const file: Walk.File.Index = @enumFromInt(Walk.files.getIndex(input_string.items) orelse return .none); | ||
| 70 | return file.findRootDecl(); | ||
| 71 | } | ||
| 72 | |||
| 73 | export fn decl_source_html(decl_index: Decl.Index) String { | ||
| 74 | const decl = decl_index.get(); | ||
| 75 | |||
| 76 | string_result.clearRetainingCapacity(); | ||
| 77 | html_render.fileSourceHtml(decl.file, &string_result, decl.ast_node, .{}) catch |err| { | ||
| 78 | fatal("unable to render source: {s}", .{@errorName(err)}); | ||
| 79 | }; | ||
| 80 | return String.init(string_result.items); | ||
| 81 | } | ||
| 82 | |||
| 83 | const String = Slice(u8); | ||
| 84 | |||
| 85 | fn Slice(T: type) type { | ||
| 86 | return packed struct(u64) { | ||
| 87 | ptr: u32, | ||
| 88 | len: u32, | ||
| 89 | |||
| 90 | fn init(s: []const T) @This() { | ||
| 91 | return .{ | ||
| 92 | .ptr = @intFromPtr(s.ptr), | ||
| 93 | .len = s.len, | ||
| 94 | }; | ||
| 95 | } | ||
| 96 | }; | ||
| 97 | } | ||
| 98 | |||
| 55 | fn unpackInner(tar_bytes: []u8) !void { | 99 | fn unpackInner(tar_bytes: []u8) !void { |
| 56 | var fbs = std.io.fixedBufferStream(tar_bytes); | 100 | var fbs = std.io.fixedBufferStream(tar_bytes); |
| 57 | var file_name_buffer: [1024]u8 = undefined; | 101 | var file_name_buffer: [1024]u8 = undefined; |
lib/std/Build/Fuzz.zig+19-20| ... | @@ -235,30 +235,29 @@ pub const WebServer = struct { | ... | @@ -235,30 +235,29 @@ pub const WebServer = struct { |
| 235 | .root_dir = ws.zig_lib_directory, | 235 | .root_dir = ws.zig_lib_directory, |
| 236 | .sub_path = "docs/wasm/Walk.zig", | 236 | .sub_path = "docs/wasm/Walk.zig", |
| 237 | }; | 237 | }; |
| 238 | const html_render_src_path: Build.Cache.Path = .{ | ||
| 239 | .root_dir = ws.zig_lib_directory, | ||
| 240 | .sub_path = "docs/wasm/html_render.zig", | ||
| 241 | }; | ||
| 238 | 242 | ||
| 239 | var argv: std.ArrayListUnmanaged([]const u8) = .{}; | 243 | var argv: std.ArrayListUnmanaged([]const u8) = .{}; |
| 240 | 244 | ||
| 241 | try argv.appendSlice(arena, &.{ | 245 | try argv.appendSlice(arena, &.{ |
| 242 | ws.zig_exe_path, | 246 | ws.zig_exe_path, "build-exe", // |
| 243 | "build-exe", | 247 | "-fno-entry", // |
| 244 | "-fno-entry", | 248 | "-O", @tagName(optimize_mode), // |
| 245 | "-O", | 249 | "-target", "wasm32-freestanding", // |
| 246 | @tagName(optimize_mode), | 250 | "-mcpu", "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext", // |
| 247 | "-target", | 251 | "--cache-dir", ws.global_cache_directory.path orelse ".", // |
| 248 | "wasm32-freestanding", | 252 | "--global-cache-dir", ws.global_cache_directory.path orelse ".", // |
| 249 | "-mcpu", | 253 | "--name", "fuzzer", // |
| 250 | "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext", | 254 | "-rdynamic", // |
| 251 | "--cache-dir", | 255 | "--dep", "Walk", // |
| 252 | ws.global_cache_directory.path orelse ".", | 256 | "--dep", "html_render", // |
| 253 | "--global-cache-dir", | 257 | try std.fmt.allocPrint(arena, "-Mroot={}", .{main_src_path}), // |
| 254 | ws.global_cache_directory.path orelse ".", | 258 | try std.fmt.allocPrint(arena, "-MWalk={}", .{walk_src_path}), // |
| 255 | "--name", | 259 | "--dep", "Walk", // |
| 256 | "fuzzer", | 260 | try std.fmt.allocPrint(arena, "-Mhtml_render={}", .{html_render_src_path}), // |
| 257 | "-rdynamic", | ||
| 258 | "--dep", | ||
| 259 | "Walk", | ||
| 260 | try std.fmt.allocPrint(arena, "-Mroot={}", .{main_src_path}), | ||
| 261 | try std.fmt.allocPrint(arena, "-MWalk={}", .{walk_src_path}), | ||
| 262 | "--listen=-", | 261 | "--listen=-", |
| 263 | }); | 262 | }); |
| 264 | 263 |