| ... | ... | @@ -0,0 +1,424 @@ |
| 1 | const std = @import("std"); |
| 2 | const builtin = @import("builtin"); |
| 3 | const io = std.io; |
| 4 | const fs = std.fs; |
| 5 | const process = std.process; |
| 6 | const ChildProcess = std.ChildProcess; |
| 7 | const Progress = std.Progress; |
| 8 | const print = std.debug.print; |
| 9 | const mem = std.mem; |
| 10 | const testing = std.testing; |
| 11 | const Allocator = std.mem.Allocator; |
| 12 | const Module = @import("../Module.zig"); |
| 13 | |
| 14 | pub fn genHtml( |
| 15 | allocator: Allocator, |
| 16 | src: *Module.File, |
| 17 | out: anytype, |
| 18 | ) !void { |
| 19 | try out.writeAll( |
| 20 | \\<!doctype html> |
| 21 | \\<html lang="en"> |
| 22 | \\<head> |
| 23 | \\ <meta charset="utf-8"> |
| 24 | \\ <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 25 | ); |
| 26 | try out.print(" <title>{s} - source view</title>\n", .{src.sub_file_path}); |
| 27 | try out.writeAll( |
| 28 | \\ <link rel="icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAgklEQVR4AWMYWuD7EllJIM4G4g4g5oIJ/odhOJ8wToOxSTXgNxDHoeiBMfA4+wGShjyYOCkG/IGqWQziEzYAoUAeiF9D5U+DxEg14DRU7jWIT5IBIOdCxf+A+CQZAAoopEB7QJwBCBwHiip8UYmRdrAlDpIMgApwQZNnNii5Dq0MBgCxxycBnwEd+wAAAABJRU5ErkJggg=="/> |
| 29 | \\ <style> |
| 30 | \\ body{ |
| 31 | \\ font-family: system-ui, -apple-system, Roboto, "Segoe UI", sans-serif; |
| 32 | \\ margin: 0; |
| 33 | \\ line-height: 1.5; |
| 34 | \\ } |
| 35 | \\ |
| 36 | \\ pre > code { |
| 37 | \\ display: block; |
| 38 | \\ overflow: auto; |
| 39 | \\ line-height: normal; |
| 40 | \\ margin: 0em; |
| 41 | \\ } |
| 42 | \\ .tok-kw { |
| 43 | \\ color: #333; |
| 44 | \\ font-weight: bold; |
| 45 | \\ } |
| 46 | \\ .tok-str { |
| 47 | \\ color: #d14; |
| 48 | \\ } |
| 49 | \\ .tok-builtin { |
| 50 | \\ color: #005C7A; |
| 51 | \\ } |
| 52 | \\ .tok-comment { |
| 53 | \\ color: #545454; |
| 54 | \\ font-style: italic; |
| 55 | \\ } |
| 56 | \\ .tok-fn { |
| 57 | \\ color: #900; |
| 58 | \\ font-weight: bold; |
| 59 | \\ } |
| 60 | \\ .tok-null { |
| 61 | \\ color: #005C5C; |
| 62 | \\ } |
| 63 | \\ .tok-number { |
| 64 | \\ color: #005C5C; |
| 65 | \\ } |
| 66 | \\ .tok-type { |
| 67 | \\ color: #458; |
| 68 | \\ font-weight: bold; |
| 69 | \\ } |
| 70 | \\ pre { |
| 71 | \\ counter-reset: line; |
| 72 | \\ } |
| 73 | \\ pre .line:before { |
| 74 | \\ counter-increment: line; |
| 75 | \\ content: counter(line); |
| 76 | \\ display: inline-block; |
| 77 | \\ padding-right: 1em; |
| 78 | \\ width: 2em; |
| 79 | \\ text-align: right; |
| 80 | \\ color: #999; |
| 81 | \\ } |
| 82 | \\ |
| 83 | \\ @media (prefers-color-scheme: dark) { |
| 84 | \\ body{ |
| 85 | \\ background:#222; |
| 86 | \\ color: #ccc; |
| 87 | \\ } |
| 88 | \\ pre > code { |
| 89 | \\ color: #ccc; |
| 90 | \\ background: #222; |
| 91 | \\ border: unset; |
| 92 | \\ } |
| 93 | \\ .tok-kw { |
| 94 | \\ color: #eee; |
| 95 | \\ } |
| 96 | \\ .tok-str { |
| 97 | \\ color: #2e5; |
| 98 | \\ } |
| 99 | \\ .tok-builtin { |
| 100 | \\ color: #ff894c; |
| 101 | \\ } |
| 102 | \\ .tok-comment { |
| 103 | \\ color: #aa7; |
| 104 | \\ } |
| 105 | \\ .tok-fn { |
| 106 | \\ color: #B1A0F8; |
| 107 | \\ } |
| 108 | \\ .tok-null { |
| 109 | \\ color: #ff8080; |
| 110 | \\ } |
| 111 | \\ .tok-number { |
| 112 | \\ color: #ff8080; |
| 113 | \\ } |
| 114 | \\ .tok-type { |
| 115 | \\ color: #68f; |
| 116 | \\ } |
| 117 | \\ } |
| 118 | \\ </style> |
| 119 | \\</head> |
| 120 | \\<body> |
| 121 | \\ |
| 122 | ); |
| 123 | |
| 124 | const source = try src.getSource(allocator); |
| 125 | try tokenizeAndPrintRaw(allocator, out, source.bytes); |
| 126 | try out.writeAll( |
| 127 | \\</body> |
| 128 | \\</html> |
| 129 | ); |
| 130 | } |
| 131 | |
| 132 | const start_line = "<span class=\"line\" id=\"L{d}\">"; |
| 133 | const end_line = "</span>\n"; |
| 134 | |
| 135 | var line_counter: usize = 1; |
| 136 | |
| 137 | pub fn tokenizeAndPrintRaw( |
| 138 | allocator: Allocator, |
| 139 | out: anytype, |
| 140 | raw_src: [:0]const u8, |
| 141 | ) !void { |
| 142 | const src = try allocator.dupeZ(u8, raw_src); |
| 143 | defer allocator.free(src); |
| 144 | |
| 145 | line_counter = 1; |
| 146 | |
| 147 | try out.print("<pre><code>" ++ start_line, .{line_counter}); |
| 148 | var tokenizer = std.zig.Tokenizer.init(src); |
| 149 | var index: usize = 0; |
| 150 | var next_tok_is_fn = false; |
| 151 | while (true) { |
| 152 | const prev_tok_was_fn = next_tok_is_fn; |
| 153 | next_tok_is_fn = false; |
| 154 | |
| 155 | const token = tokenizer.next(); |
| 156 | if (mem.indexOf(u8, src[index..token.loc.start], "//")) |comment_start_off| { |
| 157 | // render one comment |
| 158 | const comment_start = index + comment_start_off; |
| 159 | const comment_end_off = mem.indexOf(u8, src[comment_start..token.loc.start], "\n"); |
| 160 | const comment_end = if (comment_end_off) |o| comment_start + o else token.loc.start; |
| 161 | |
| 162 | try writeEscapedLines(out, src[index..comment_start]); |
| 163 | try out.writeAll("<span class=\"tok-comment\">"); |
| 164 | try writeEscaped(out, src[comment_start..comment_end]); |
| 165 | try out.writeAll("</span>\n"); |
| 166 | index = comment_end; |
| 167 | tokenizer.index = index; |
| 168 | continue; |
| 169 | } |
| 170 | |
| 171 | try writeEscapedLines(out, src[index..token.loc.start]); |
| 172 | switch (token.tag) { |
| 173 | .eof => break, |
| 174 | |
| 175 | .keyword_addrspace, |
| 176 | .keyword_align, |
| 177 | .keyword_and, |
| 178 | .keyword_asm, |
| 179 | .keyword_async, |
| 180 | .keyword_await, |
| 181 | .keyword_break, |
| 182 | .keyword_catch, |
| 183 | .keyword_comptime, |
| 184 | .keyword_const, |
| 185 | .keyword_continue, |
| 186 | .keyword_defer, |
| 187 | .keyword_else, |
| 188 | .keyword_enum, |
| 189 | .keyword_errdefer, |
| 190 | .keyword_error, |
| 191 | .keyword_export, |
| 192 | .keyword_extern, |
| 193 | .keyword_for, |
| 194 | .keyword_if, |
| 195 | .keyword_inline, |
| 196 | .keyword_noalias, |
| 197 | .keyword_noinline, |
| 198 | .keyword_nosuspend, |
| 199 | .keyword_opaque, |
| 200 | .keyword_or, |
| 201 | .keyword_orelse, |
| 202 | .keyword_packed, |
| 203 | .keyword_anyframe, |
| 204 | .keyword_pub, |
| 205 | .keyword_resume, |
| 206 | .keyword_return, |
| 207 | .keyword_linksection, |
| 208 | .keyword_callconv, |
| 209 | .keyword_struct, |
| 210 | .keyword_suspend, |
| 211 | .keyword_switch, |
| 212 | .keyword_test, |
| 213 | .keyword_threadlocal, |
| 214 | .keyword_try, |
| 215 | .keyword_union, |
| 216 | .keyword_unreachable, |
| 217 | .keyword_usingnamespace, |
| 218 | .keyword_var, |
| 219 | .keyword_volatile, |
| 220 | .keyword_allowzero, |
| 221 | .keyword_while, |
| 222 | .keyword_anytype, |
| 223 | => { |
| 224 | try out.writeAll("<span class=\"tok-kw\">"); |
| 225 | try writeEscaped(out, src[token.loc.start..token.loc.end]); |
| 226 | try out.writeAll("</span>"); |
| 227 | }, |
| 228 | |
| 229 | .keyword_fn => { |
| 230 | try out.writeAll("<span class=\"tok-kw\">"); |
| 231 | try writeEscaped(out, src[token.loc.start..token.loc.end]); |
| 232 | try out.writeAll("</span>"); |
| 233 | next_tok_is_fn = true; |
| 234 | }, |
| 235 | |
| 236 | .string_literal, |
| 237 | .char_literal, |
| 238 | => { |
| 239 | try out.writeAll("<span class=\"tok-str\">"); |
| 240 | try writeEscaped(out, src[token.loc.start..token.loc.end]); |
| 241 | try out.writeAll("</span>"); |
| 242 | }, |
| 243 | |
| 244 | .multiline_string_literal_line => { |
| 245 | if (src[token.loc.end - 1] == '\n') { |
| 246 | try out.writeAll("<span class=\"tok-str\">"); |
| 247 | try writeEscaped(out, src[token.loc.start .. token.loc.end - 1]); |
| 248 | line_counter += 1; |
| 249 | try out.print("</span>" ++ end_line ++ "\n" ++ start_line, .{line_counter}); |
| 250 | } else { |
| 251 | try out.writeAll("<span class=\"tok-str\">"); |
| 252 | try writeEscaped(out, src[token.loc.start..token.loc.end]); |
| 253 | try out.writeAll("</span>"); |
| 254 | } |
| 255 | }, |
| 256 | |
| 257 | .builtin => { |
| 258 | try out.writeAll("<span class=\"tok-builtin\">"); |
| 259 | try writeEscaped(out, src[token.loc.start..token.loc.end]); |
| 260 | try out.writeAll("</span>"); |
| 261 | }, |
| 262 | |
| 263 | .doc_comment, |
| 264 | .container_doc_comment, |
| 265 | => { |
| 266 | try out.writeAll("<span class=\"tok-comment\">"); |
| 267 | try writeEscaped(out, src[token.loc.start..token.loc.end]); |
| 268 | try out.writeAll("</span>"); |
| 269 | }, |
| 270 | |
| 271 | .identifier => { |
| 272 | const tok_bytes = src[token.loc.start..token.loc.end]; |
| 273 | if (mem.eql(u8, tok_bytes, "undefined") or |
| 274 | mem.eql(u8, tok_bytes, "null") or |
| 275 | mem.eql(u8, tok_bytes, "true") or |
| 276 | mem.eql(u8, tok_bytes, "false")) |
| 277 | { |
| 278 | try out.writeAll("<span class=\"tok-null\">"); |
| 279 | try writeEscaped(out, tok_bytes); |
| 280 | try out.writeAll("</span>"); |
| 281 | } else if (prev_tok_was_fn) { |
| 282 | try out.writeAll("<span class=\"tok-fn\">"); |
| 283 | try writeEscaped(out, tok_bytes); |
| 284 | try out.writeAll("</span>"); |
| 285 | } else { |
| 286 | const is_int = blk: { |
| 287 | if (src[token.loc.start] != 'i' and src[token.loc.start] != 'u') |
| 288 | break :blk false; |
| 289 | var i = token.loc.start + 1; |
| 290 | if (i == token.loc.end) |
| 291 | break :blk false; |
| 292 | while (i != token.loc.end) : (i += 1) { |
| 293 | if (src[i] < '0' or src[i] > '9') |
| 294 | break :blk false; |
| 295 | } |
| 296 | break :blk true; |
| 297 | }; |
| 298 | if (is_int or isType(tok_bytes)) { |
| 299 | try out.writeAll("<span class=\"tok-type\">"); |
| 300 | try writeEscaped(out, tok_bytes); |
| 301 | try out.writeAll("</span>"); |
| 302 | } else { |
| 303 | try writeEscaped(out, tok_bytes); |
| 304 | } |
| 305 | } |
| 306 | }, |
| 307 | |
| 308 | .integer_literal, |
| 309 | .float_literal, |
| 310 | => { |
| 311 | try out.writeAll("<span class=\"tok-number\">"); |
| 312 | try writeEscaped(out, src[token.loc.start..token.loc.end]); |
| 313 | try out.writeAll("</span>"); |
| 314 | }, |
| 315 | |
| 316 | .bang, |
| 317 | .pipe, |
| 318 | .pipe_pipe, |
| 319 | .pipe_equal, |
| 320 | .equal, |
| 321 | .equal_equal, |
| 322 | .equal_angle_bracket_right, |
| 323 | .bang_equal, |
| 324 | .l_paren, |
| 325 | .r_paren, |
| 326 | .semicolon, |
| 327 | .percent, |
| 328 | .percent_equal, |
| 329 | .l_brace, |
| 330 | .r_brace, |
| 331 | .l_bracket, |
| 332 | .r_bracket, |
| 333 | .period, |
| 334 | .period_asterisk, |
| 335 | .ellipsis2, |
| 336 | .ellipsis3, |
| 337 | .caret, |
| 338 | .caret_equal, |
| 339 | .plus, |
| 340 | .plus_plus, |
| 341 | .plus_equal, |
| 342 | .plus_percent, |
| 343 | .plus_percent_equal, |
| 344 | .plus_pipe, |
| 345 | .plus_pipe_equal, |
| 346 | .minus, |
| 347 | .minus_equal, |
| 348 | .minus_percent, |
| 349 | .minus_percent_equal, |
| 350 | .minus_pipe, |
| 351 | .minus_pipe_equal, |
| 352 | .asterisk, |
| 353 | .asterisk_equal, |
| 354 | .asterisk_asterisk, |
| 355 | .asterisk_percent, |
| 356 | .asterisk_percent_equal, |
| 357 | .asterisk_pipe, |
| 358 | .asterisk_pipe_equal, |
| 359 | .arrow, |
| 360 | .colon, |
| 361 | .slash, |
| 362 | .slash_equal, |
| 363 | .comma, |
| 364 | .ampersand, |
| 365 | .ampersand_equal, |
| 366 | .question_mark, |
| 367 | .angle_bracket_left, |
| 368 | .angle_bracket_left_equal, |
| 369 | .angle_bracket_angle_bracket_left, |
| 370 | .angle_bracket_angle_bracket_left_equal, |
| 371 | .angle_bracket_angle_bracket_left_pipe, |
| 372 | .angle_bracket_angle_bracket_left_pipe_equal, |
| 373 | .angle_bracket_right, |
| 374 | .angle_bracket_right_equal, |
| 375 | .angle_bracket_angle_bracket_right, |
| 376 | .angle_bracket_angle_bracket_right_equal, |
| 377 | .tilde, |
| 378 | => try writeEscaped(out, src[token.loc.start..token.loc.end]), |
| 379 | |
| 380 | .invalid, .invalid_periodasterisks => return error.ParseError, |
| 381 | } |
| 382 | index = token.loc.end; |
| 383 | } |
| 384 | try out.writeAll(end_line ++ "</code></pre>"); |
| 385 | } |
| 386 | |
| 387 | fn writeEscapedLines(out: anytype, text: []const u8) !void { |
| 388 | for (text) |char| { |
| 389 | if (char == '\n') { |
| 390 | try out.writeAll(end_line); |
| 391 | line_counter += 1; |
| 392 | try out.print(start_line, .{line_counter}); |
| 393 | } else { |
| 394 | try writeEscaped(out, &[_]u8{char}); |
| 395 | } |
| 396 | } |
| 397 | } |
| 398 | |
| 399 | fn writeEscaped(out: anytype, input: []const u8) !void { |
| 400 | for (input) |c| { |
| 401 | try switch (c) { |
| 402 | '&' => out.writeAll("&amp;"), |
| 403 | '<' => out.writeAll("&lt;"), |
| 404 | '>' => out.writeAll("&gt;"), |
| 405 | '"' => out.writeAll("&quot;"), |
| 406 | else => out.writeByte(c), |
| 407 | }; |
| 408 | } |
| 409 | } |
| 410 | |
| 411 | const builtin_types = [_][]const u8{ |
| 412 | "f16", "f32", "f64", "f128", "c_longdouble", "c_short", |
| 413 | "c_ushort", "c_int", "c_uint", "c_long", "c_ulong", "c_longlong", |
| 414 | "c_ulonglong", "c_char", "anyopaque", "void", "bool", "isize", |
| 415 | "usize", "noreturn", "type", "anyerror", "comptime_int", "comptime_float", |
| 416 | }; |
| 417 | |
| 418 | fn isType(name: []const u8) bool { |
| 419 | for (builtin_types) |t| { |
| 420 | if (mem.eql(u8, t, name)) |
| 421 | return true; |
| 422 | } |
| 423 | return false; |
| 424 | } |