| author | |
| committer | |
| log | acbb6418c3899eb79aea98e7f5d3173298716377 |
| tree | eaf6206e2038cbf7af17098162458b78cac91058 |
| parent | 25a9487caa05c16f9f87cb6931340e1a1051a7f3 |
3 files changed, 2279 insertions(+), 2279 deletions(-)
build.zig+1-1| ... | ... | @@ -36,7 +36,7 @@ pub fn build(b: *std.Build) !void { |
| 36 | 36 | |
| 37 | 37 | const docgen_exe = b.addExecutable(.{ |
| 38 | 38 | .name = "docgen", |
| 39 | .root_source_file = .{ .path = "doc/docgen.zig" }, | |
| 39 | .root_source_file = .{ .path = "tools/docgen.zig" }, | |
| 40 | 40 | .target = .{}, |
| 41 | 41 | .optimize = .Debug, |
| 42 | 42 | }); |
doc/docgen.zig deleted-2278| ... | ... | @@ -1,2278 +0,0 @@ |
| 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 | ||
| 13 | const max_doc_file_size = 10 * 1024 * 1024; | |
| 14 | ||
| 15 | const exe_ext = @as(std.zig.CrossTarget, .{}).exeFileExt(); | |
| 16 | const obj_ext = builtin.object_format.fileExt(builtin.cpu.arch); | |
| 17 | const tmp_dir_name = "docgen_tmp"; | |
| 18 | const test_out_path = tmp_dir_name ++ fs.path.sep_str ++ "test" ++ exe_ext; | |
| 19 | ||
| 20 | const usage = | |
| 21 | \\Usage: docgen [--zig] [--skip-code-tests] input output" | |
| 22 | \\ | |
| 23 | \\ Generates an HTML document from a docgen template. | |
| 24 | \\ | |
| 25 | \\Options: | |
| 26 | \\ -h, --help Print this help and exit | |
| 27 | \\ --skip-code-tests Skip the doctests | |
| 28 | \\ | |
| 29 | ; | |
| 30 | ||
| 31 | fn fatal(comptime format: []const u8, args: anytype) noreturn { | |
| 32 | const stderr = io.getStdErr().writer(); | |
| 33 | ||
| 34 | stderr.print("error: " ++ format ++ "\n", args) catch {}; | |
| 35 | process.exit(1); | |
| 36 | } | |
| 37 | ||
| 38 | pub fn main() !void { | |
| 39 | var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); | |
| 40 | defer arena.deinit(); | |
| 41 | ||
| 42 | const allocator = arena.allocator(); | |
| 43 | ||
| 44 | var args_it = try process.argsWithAllocator(allocator); | |
| 45 | if (!args_it.skip()) @panic("expected self arg"); | |
| 46 | ||
| 47 | var zig_exe: []const u8 = "zig"; | |
| 48 | var opt_zig_lib_dir: ?[]const u8 = null; | |
| 49 | var do_code_tests = true; | |
| 50 | var files = [_][]const u8{ "", "" }; | |
| 51 | ||
| 52 | var i: usize = 0; | |
| 53 | while (args_it.next()) |arg| { | |
| 54 | if (mem.startsWith(u8, arg, "-")) { | |
| 55 | if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { | |
| 56 | const stdout = io.getStdOut().writer(); | |
| 57 | try stdout.writeAll(usage); | |
| 58 | process.exit(0); | |
| 59 | } else if (mem.eql(u8, arg, "--zig")) { | |
| 60 | if (args_it.next()) |param| { | |
| 61 | zig_exe = param; | |
| 62 | } else { | |
| 63 | fatal("expected parameter after --zig", .{}); | |
| 64 | } | |
| 65 | } else if (mem.eql(u8, arg, "--zig-lib-dir")) { | |
| 66 | if (args_it.next()) |param| { | |
| 67 | // Convert relative to absolute because this will be passed | |
| 68 | // to a child process with a different cwd. | |
| 69 | opt_zig_lib_dir = try fs.realpathAlloc(allocator, param); | |
| 70 | } else { | |
| 71 | fatal("expected parameter after --zig-lib-dir", .{}); | |
| 72 | } | |
| 73 | } else if (mem.eql(u8, arg, "--skip-code-tests")) { | |
| 74 | do_code_tests = false; | |
| 75 | } else { | |
| 76 | fatal("unrecognized option: '{s}'", .{arg}); | |
| 77 | } | |
| 78 | } else { | |
| 79 | if (i > 1) { | |
| 80 | fatal("too many arguments", .{}); | |
| 81 | } | |
| 82 | files[i] = arg; | |
| 83 | i += 1; | |
| 84 | } | |
| 85 | } | |
| 86 | if (i < 2) { | |
| 87 | fatal("not enough arguments", .{}); | |
| 88 | } | |
| 89 | ||
| 90 | var in_file = try fs.cwd().openFile(files[0], .{ .mode = .read_only }); | |
| 91 | defer in_file.close(); | |
| 92 | ||
| 93 | var out_file = try fs.cwd().createFile(files[1], .{}); | |
| 94 | defer out_file.close(); | |
| 95 | ||
| 96 | const input_file_bytes = try in_file.reader().readAllAlloc(allocator, max_doc_file_size); | |
| 97 | ||
| 98 | var buffered_writer = io.bufferedWriter(out_file.writer()); | |
| 99 | ||
| 100 | var tokenizer = Tokenizer.init(files[0], input_file_bytes); | |
| 101 | var toc = try genToc(allocator, &tokenizer); | |
| 102 | ||
| 103 | try fs.cwd().makePath(tmp_dir_name); | |
| 104 | defer fs.cwd().deleteTree(tmp_dir_name) catch {}; | |
| 105 | ||
| 106 | try genHtml(allocator, &tokenizer, &toc, buffered_writer.writer(), zig_exe, opt_zig_lib_dir, do_code_tests); | |
| 107 | try buffered_writer.flush(); | |
| 108 | } | |
| 109 | ||
| 110 | const Token = struct { | |
| 111 | id: Id, | |
| 112 | start: usize, | |
| 113 | end: usize, | |
| 114 | ||
| 115 | const Id = enum { | |
| 116 | invalid, | |
| 117 | content, | |
| 118 | bracket_open, | |
| 119 | tag_content, | |
| 120 | separator, | |
| 121 | bracket_close, | |
| 122 | eof, | |
| 123 | }; | |
| 124 | }; | |
| 125 | ||
| 126 | const Tokenizer = struct { | |
| 127 | buffer: []const u8, | |
| 128 | index: usize, | |
| 129 | state: State, | |
| 130 | source_file_name: []const u8, | |
| 131 | code_node_count: usize, | |
| 132 | ||
| 133 | const State = enum { | |
| 134 | start, | |
| 135 | l_bracket, | |
| 136 | hash, | |
| 137 | tag_name, | |
| 138 | eof, | |
| 139 | }; | |
| 140 | ||
| 141 | fn init(source_file_name: []const u8, buffer: []const u8) Tokenizer { | |
| 142 | return Tokenizer{ | |
| 143 | .buffer = buffer, | |
| 144 | .index = 0, | |
| 145 | .state = .start, | |
| 146 | .source_file_name = source_file_name, | |
| 147 | .code_node_count = 0, | |
| 148 | }; | |
| 149 | } | |
| 150 | ||
| 151 | fn next(self: *Tokenizer) Token { | |
| 152 | var result = Token{ | |
| 153 | .id = .eof, | |
| 154 | .start = self.index, | |
| 155 | .end = undefined, | |
| 156 | }; | |
| 157 | while (self.index < self.buffer.len) : (self.index += 1) { | |
| 158 | const c = self.buffer[self.index]; | |
| 159 | switch (self.state) { | |
| 160 | .start => switch (c) { | |
| 161 | '{' => { | |
| 162 | self.state = .l_bracket; | |
| 163 | }, | |
| 164 | else => { | |
| 165 | result.id = .content; | |
| 166 | }, | |
| 167 | }, | |
| 168 | .l_bracket => switch (c) { | |
| 169 | '#' => { | |
| 170 | if (result.id != .eof) { | |
| 171 | self.index -= 1; | |
| 172 | self.state = .start; | |
| 173 | break; | |
| 174 | } else { | |
| 175 | result.id = .bracket_open; | |
| 176 | self.index += 1; | |
| 177 | self.state = .tag_name; | |
| 178 | break; | |
| 179 | } | |
| 180 | }, | |
| 181 | else => { | |
| 182 | result.id = .content; | |
| 183 | self.state = .start; | |
| 184 | }, | |
| 185 | }, | |
| 186 | .tag_name => switch (c) { | |
| 187 | '|' => { | |
| 188 | if (result.id != .eof) { | |
| 189 | break; | |
| 190 | } else { | |
| 191 | result.id = .separator; | |
| 192 | self.index += 1; | |
| 193 | break; | |
| 194 | } | |
| 195 | }, | |
| 196 | '#' => { | |
| 197 | self.state = .hash; | |
| 198 | }, | |
| 199 | else => { | |
| 200 | result.id = .tag_content; | |
| 201 | }, | |
| 202 | }, | |
| 203 | .hash => switch (c) { | |
| 204 | '}' => { | |
| 205 | if (result.id != .eof) { | |
| 206 | self.index -= 1; | |
| 207 | self.state = .tag_name; | |
| 208 | break; | |
| 209 | } else { | |
| 210 | result.id = .bracket_close; | |
| 211 | self.index += 1; | |
| 212 | self.state = .start; | |
| 213 | break; | |
| 214 | } | |
| 215 | }, | |
| 216 | else => { | |
| 217 | result.id = .tag_content; | |
| 218 | self.state = .tag_name; | |
| 219 | }, | |
| 220 | }, | |
| 221 | .eof => unreachable, | |
| 222 | } | |
| 223 | } else { | |
| 224 | switch (self.state) { | |
| 225 | .start, .l_bracket, .eof => {}, | |
| 226 | else => { | |
| 227 | result.id = .invalid; | |
| 228 | }, | |
| 229 | } | |
| 230 | self.state = .eof; | |
| 231 | } | |
| 232 | result.end = self.index; | |
| 233 | return result; | |
| 234 | } | |
| 235 | ||
| 236 | const Location = struct { | |
| 237 | line: usize, | |
| 238 | column: usize, | |
| 239 | line_start: usize, | |
| 240 | line_end: usize, | |
| 241 | }; | |
| 242 | ||
| 243 | fn getTokenLocation(self: *Tokenizer, token: Token) Location { | |
| 244 | var loc = Location{ | |
| 245 | .line = 0, | |
| 246 | .column = 0, | |
| 247 | .line_start = 0, | |
| 248 | .line_end = 0, | |
| 249 | }; | |
| 250 | for (self.buffer, 0..) |c, i| { | |
| 251 | if (i == token.start) { | |
| 252 | loc.line_end = i; | |
| 253 | while (loc.line_end < self.buffer.len and self.buffer[loc.line_end] != '\n') : (loc.line_end += 1) {} | |
| 254 | return loc; | |
| 255 | } | |
| 256 | if (c == '\n') { | |
| 257 | loc.line += 1; | |
| 258 | loc.column = 0; | |
| 259 | loc.line_start = i + 1; | |
| 260 | } else { | |
| 261 | loc.column += 1; | |
| 262 | } | |
| 263 | } | |
| 264 | return loc; | |
| 265 | } | |
| 266 | }; | |
| 267 | ||
| 268 | fn parseError(tokenizer: *Tokenizer, token: Token, comptime fmt: []const u8, args: anytype) anyerror { | |
| 269 | const loc = tokenizer.getTokenLocation(token); | |
| 270 | const args_prefix = .{ tokenizer.source_file_name, loc.line + 1, loc.column + 1 }; | |
| 271 | print("{s}:{d}:{d}: error: " ++ fmt ++ "\n", args_prefix ++ args); | |
| 272 | if (loc.line_start <= loc.line_end) { | |
| 273 | print("{s}\n", .{tokenizer.buffer[loc.line_start..loc.line_end]}); | |
| 274 | { | |
| 275 | var i: usize = 0; | |
| 276 | while (i < loc.column) : (i += 1) { | |
| 277 | print(" ", .{}); | |
| 278 | } | |
| 279 | } | |
| 280 | { | |
| 281 | const caret_count = @min(token.end, loc.line_end) - token.start; | |
| 282 | var i: usize = 0; | |
| 283 | while (i < caret_count) : (i += 1) { | |
| 284 | print("~", .{}); | |
| 285 | } | |
| 286 | } | |
| 287 | print("\n", .{}); | |
| 288 | } | |
| 289 | return error.ParseError; | |
| 290 | } | |
| 291 | ||
| 292 | fn assertToken(tokenizer: *Tokenizer, token: Token, id: Token.Id) !void { | |
| 293 | if (token.id != id) { | |
| 294 | return parseError(tokenizer, token, "expected {s}, found {s}", .{ @tagName(id), @tagName(token.id) }); | |
| 295 | } | |
| 296 | } | |
| 297 | ||
| 298 | fn eatToken(tokenizer: *Tokenizer, id: Token.Id) !Token { | |
| 299 | const token = tokenizer.next(); | |
| 300 | try assertToken(tokenizer, token, id); | |
| 301 | return token; | |
| 302 | } | |
| 303 | ||
| 304 | const HeaderOpen = struct { | |
| 305 | name: []const u8, | |
| 306 | url: []const u8, | |
| 307 | n: usize, | |
| 308 | }; | |
| 309 | ||
| 310 | const SeeAlsoItem = struct { | |
| 311 | name: []const u8, | |
| 312 | token: Token, | |
| 313 | }; | |
| 314 | ||
| 315 | const ExpectedOutcome = enum { | |
| 316 | succeed, | |
| 317 | fail, | |
| 318 | build_fail, | |
| 319 | }; | |
| 320 | ||
| 321 | const Code = struct { | |
| 322 | id: Id, | |
| 323 | name: []const u8, | |
| 324 | source_token: Token, | |
| 325 | just_check_syntax: bool, | |
| 326 | mode: std.builtin.Mode, | |
| 327 | link_objects: []const []const u8, | |
| 328 | target_str: ?[]const u8, | |
| 329 | link_libc: bool, | |
| 330 | link_mode: ?std.builtin.LinkMode, | |
| 331 | disable_cache: bool, | |
| 332 | verbose_cimport: bool, | |
| 333 | additional_options: []const []const u8, | |
| 334 | ||
| 335 | const Id = union(enum) { | |
| 336 | @"test", | |
| 337 | test_error: []const u8, | |
| 338 | test_safety: []const u8, | |
| 339 | exe: ExpectedOutcome, | |
| 340 | obj: ?[]const u8, | |
| 341 | lib, | |
| 342 | }; | |
| 343 | }; | |
| 344 | ||
| 345 | const Link = struct { | |
| 346 | url: []const u8, | |
| 347 | name: []const u8, | |
| 348 | token: Token, | |
| 349 | }; | |
| 350 | ||
| 351 | const SyntaxBlock = struct { | |
| 352 | source_type: SourceType, | |
| 353 | name: []const u8, | |
| 354 | source_token: Token, | |
| 355 | ||
| 356 | const SourceType = enum { | |
| 357 | zig, | |
| 358 | c, | |
| 359 | peg, | |
| 360 | javascript, | |
| 361 | }; | |
| 362 | }; | |
| 363 | ||
| 364 | const Node = union(enum) { | |
| 365 | Content: []const u8, | |
| 366 | Nav, | |
| 367 | Builtin: Token, | |
| 368 | HeaderOpen: HeaderOpen, | |
| 369 | SeeAlso: []const SeeAlsoItem, | |
| 370 | Code: Code, | |
| 371 | Link: Link, | |
| 372 | InlineSyntax: Token, | |
| 373 | Shell: Token, | |
| 374 | SyntaxBlock: SyntaxBlock, | |
| 375 | }; | |
| 376 | ||
| 377 | const Toc = struct { | |
| 378 | nodes: []Node, | |
| 379 | toc: []u8, | |
| 380 | urls: std.StringHashMap(Token), | |
| 381 | }; | |
| 382 | ||
| 383 | const Action = enum { | |
| 384 | open, | |
| 385 | close, | |
| 386 | }; | |
| 387 | ||
| 388 | fn genToc(allocator: Allocator, tokenizer: *Tokenizer) !Toc { | |
| 389 | var urls = std.StringHashMap(Token).init(allocator); | |
| 390 | errdefer urls.deinit(); | |
| 391 | ||
| 392 | var header_stack_size: usize = 0; | |
| 393 | var last_action: Action = .open; | |
| 394 | var last_columns: ?u8 = null; | |
| 395 | ||
| 396 | var toc_buf = std.ArrayList(u8).init(allocator); | |
| 397 | defer toc_buf.deinit(); | |
| 398 | ||
| 399 | var toc = toc_buf.writer(); | |
| 400 | ||
| 401 | var nodes = std.ArrayList(Node).init(allocator); | |
| 402 | defer nodes.deinit(); | |
| 403 | ||
| 404 | try toc.writeByte('\n'); | |
| 405 | ||
| 406 | while (true) { | |
| 407 | const token = tokenizer.next(); | |
| 408 | switch (token.id) { | |
| 409 | .eof => { | |
| 410 | if (header_stack_size != 0) { | |
| 411 | return parseError(tokenizer, token, "unbalanced headers", .{}); | |
| 412 | } | |
| 413 | try toc.writeAll(" </ul>\n"); | |
| 414 | break; | |
| 415 | }, | |
| 416 | .content => { | |
| 417 | try nodes.append(Node{ .Content = tokenizer.buffer[token.start..token.end] }); | |
| 418 | }, | |
| 419 | .bracket_open => { | |
| 420 | const tag_token = try eatToken(tokenizer, .tag_content); | |
| 421 | const tag_name = tokenizer.buffer[tag_token.start..tag_token.end]; | |
| 422 | ||
| 423 | if (mem.eql(u8, tag_name, "nav")) { | |
| 424 | _ = try eatToken(tokenizer, .bracket_close); | |
| 425 | ||
| 426 | try nodes.append(Node.Nav); | |
| 427 | } else if (mem.eql(u8, tag_name, "builtin")) { | |
| 428 | _ = try eatToken(tokenizer, .bracket_close); | |
| 429 | try nodes.append(Node{ .Builtin = tag_token }); | |
| 430 | } else if (mem.eql(u8, tag_name, "header_open")) { | |
| 431 | _ = try eatToken(tokenizer, .separator); | |
| 432 | const content_token = try eatToken(tokenizer, .tag_content); | |
| 433 | const content = tokenizer.buffer[content_token.start..content_token.end]; | |
| 434 | var columns: ?u8 = null; | |
| 435 | while (true) { | |
| 436 | const bracket_tok = tokenizer.next(); | |
| 437 | switch (bracket_tok.id) { | |
| 438 | .bracket_close => break, | |
| 439 | .separator => continue, | |
| 440 | .tag_content => { | |
| 441 | const param = tokenizer.buffer[bracket_tok.start..bracket_tok.end]; | |
| 442 | if (mem.eql(u8, param, "2col")) { | |
| 443 | columns = 2; | |
| 444 | } else { | |
| 445 | return parseError( | |
| 446 | tokenizer, | |
| 447 | bracket_tok, | |
| 448 | "unrecognized header_open param: {s}", | |
| 449 | .{param}, | |
| 450 | ); | |
| 451 | } | |
| 452 | }, | |
| 453 | else => return parseError(tokenizer, bracket_tok, "invalid header_open token", .{}), | |
| 454 | } | |
| 455 | } | |
| 456 | ||
| 457 | header_stack_size += 1; | |
| 458 | ||
| 459 | const urlized = try urlize(allocator, content); | |
| 460 | try nodes.append(Node{ | |
| 461 | .HeaderOpen = HeaderOpen{ | |
| 462 | .name = content, | |
| 463 | .url = urlized, | |
| 464 | .n = header_stack_size + 1, // highest-level section headers start at h2 | |
| 465 | }, | |
| 466 | }); | |
| 467 | if (try urls.fetchPut(urlized, tag_token)) |kv| { | |
| 468 | parseError(tokenizer, tag_token, "duplicate header url: #{s}", .{urlized}) catch {}; | |
| 469 | parseError(tokenizer, kv.value, "other tag here", .{}) catch {}; | |
| 470 | return error.ParseError; | |
| 471 | } | |
| 472 | if (last_action == .open) { | |
| 473 | try toc.writeByte('\n'); | |
| 474 | try toc.writeByteNTimes(' ', header_stack_size * 4); | |
| 475 | if (last_columns) |n| { | |
| 476 | try toc.print("<ul style=\"columns: {}\">\n", .{n}); | |
| 477 | } else { | |
| 478 | try toc.writeAll("<ul>\n"); | |
| 479 | } | |
| 480 | } else { | |
| 481 | last_action = .open; | |
| 482 | } | |
| 483 | last_columns = columns; | |
| 484 | try toc.writeByteNTimes(' ', 4 + header_stack_size * 4); | |
| 485 | try toc.print("<li><a id=\"toc-{s}\" href=\"#{s}\">{s}</a>", .{ urlized, urlized, content }); | |
| 486 | } else if (mem.eql(u8, tag_name, "header_close")) { | |
| 487 | if (header_stack_size == 0) { | |
| 488 | return parseError(tokenizer, tag_token, "unbalanced close header", .{}); | |
| 489 | } | |
| 490 | header_stack_size -= 1; | |
| 491 | _ = try eatToken(tokenizer, .bracket_close); | |
| 492 | ||
| 493 | if (last_action == .close) { | |
| 494 | try toc.writeByteNTimes(' ', 8 + header_stack_size * 4); | |
| 495 | try toc.writeAll("</ul></li>\n"); | |
| 496 | } else { | |
| 497 | try toc.writeAll("</li>\n"); | |
| 498 | last_action = .close; | |
| 499 | } | |
| 500 | } else if (mem.eql(u8, tag_name, "see_also")) { | |
| 501 | var list = std.ArrayList(SeeAlsoItem).init(allocator); | |
| 502 | errdefer list.deinit(); | |
| 503 | ||
| 504 | while (true) { | |
| 505 | const see_also_tok = tokenizer.next(); | |
| 506 | switch (see_also_tok.id) { | |
| 507 | .tag_content => { | |
| 508 | const content = tokenizer.buffer[see_also_tok.start..see_also_tok.end]; | |
| 509 | try list.append(SeeAlsoItem{ | |
| 510 | .name = content, | |
| 511 | .token = see_also_tok, | |
| 512 | }); | |
| 513 | }, | |
| 514 | .separator => {}, | |
| 515 | .bracket_close => { | |
| 516 | try nodes.append(Node{ .SeeAlso = try list.toOwnedSlice() }); | |
| 517 | break; | |
| 518 | }, | |
| 519 | else => return parseError(tokenizer, see_also_tok, "invalid see_also token", .{}), | |
| 520 | } | |
| 521 | } | |
| 522 | } else if (mem.eql(u8, tag_name, "link")) { | |
| 523 | _ = try eatToken(tokenizer, .separator); | |
| 524 | const name_tok = try eatToken(tokenizer, .tag_content); | |
| 525 | const name = tokenizer.buffer[name_tok.start..name_tok.end]; | |
| 526 | ||
| 527 | const url_name = blk: { | |
| 528 | const tok = tokenizer.next(); | |
| 529 | switch (tok.id) { | |
| 530 | .bracket_close => break :blk name, | |
| 531 | .separator => { | |
| 532 | const explicit_text = try eatToken(tokenizer, .tag_content); | |
| 533 | _ = try eatToken(tokenizer, .bracket_close); | |
| 534 | break :blk tokenizer.buffer[explicit_text.start..explicit_text.end]; | |
| 535 | }, | |
| 536 | else => return parseError(tokenizer, tok, "invalid link token", .{}), | |
| 537 | } | |
| 538 | }; | |
| 539 | ||
| 540 | try nodes.append(Node{ | |
| 541 | .Link = Link{ | |
| 542 | .url = try urlize(allocator, url_name), | |
| 543 | .name = name, | |
| 544 | .token = name_tok, | |
| 545 | }, | |
| 546 | }); | |
| 547 | } else if (mem.eql(u8, tag_name, "code_begin")) { | |
| 548 | _ = try eatToken(tokenizer, .separator); | |
| 549 | const code_kind_tok = try eatToken(tokenizer, .tag_content); | |
| 550 | _ = try eatToken(tokenizer, .separator); | |
| 551 | const name_tok = try eatToken(tokenizer, .tag_content); | |
| 552 | const name = tokenizer.buffer[name_tok.start..name_tok.end]; | |
| 553 | var error_str: []const u8 = ""; | |
| 554 | const maybe_sep = tokenizer.next(); | |
| 555 | switch (maybe_sep.id) { | |
| 556 | .separator => { | |
| 557 | const error_tok = try eatToken(tokenizer, .tag_content); | |
| 558 | error_str = tokenizer.buffer[error_tok.start..error_tok.end]; | |
| 559 | _ = try eatToken(tokenizer, .bracket_close); | |
| 560 | }, | |
| 561 | .bracket_close => {}, | |
| 562 | else => return parseError(tokenizer, token, "invalid token", .{}), | |
| 563 | } | |
| 564 | const code_kind_str = tokenizer.buffer[code_kind_tok.start..code_kind_tok.end]; | |
| 565 | var code_kind_id: Code.Id = undefined; | |
| 566 | var just_check_syntax = false; | |
| 567 | if (mem.eql(u8, code_kind_str, "exe")) { | |
| 568 | code_kind_id = Code.Id{ .exe = .succeed }; | |
| 569 | } else if (mem.eql(u8, code_kind_str, "exe_err")) { | |
| 570 | code_kind_id = Code.Id{ .exe = .fail }; | |
| 571 | } else if (mem.eql(u8, code_kind_str, "exe_build_err")) { | |
| 572 | code_kind_id = Code.Id{ .exe = .build_fail }; | |
| 573 | } else if (mem.eql(u8, code_kind_str, "test")) { | |
| 574 | code_kind_id = .@"test"; | |
| 575 | } else if (mem.eql(u8, code_kind_str, "test_err")) { | |
| 576 | code_kind_id = Code.Id{ .test_error = error_str }; | |
| 577 | } else if (mem.eql(u8, code_kind_str, "test_safety")) { | |
| 578 | code_kind_id = Code.Id{ .test_safety = error_str }; | |
| 579 | } else if (mem.eql(u8, code_kind_str, "obj")) { | |
| 580 | code_kind_id = Code.Id{ .obj = null }; | |
| 581 | } else if (mem.eql(u8, code_kind_str, "obj_err")) { | |
| 582 | code_kind_id = Code.Id{ .obj = error_str }; | |
| 583 | } else if (mem.eql(u8, code_kind_str, "lib")) { | |
| 584 | code_kind_id = Code.Id.lib; | |
| 585 | } else if (mem.eql(u8, code_kind_str, "syntax")) { | |
| 586 | code_kind_id = Code.Id{ .obj = null }; | |
| 587 | just_check_syntax = true; | |
| 588 | } else { | |
| 589 | return parseError(tokenizer, code_kind_tok, "unrecognized code kind: {s}", .{code_kind_str}); | |
| 590 | } | |
| 591 | ||
| 592 | var mode: std.builtin.Mode = .Debug; | |
| 593 | var link_objects = std.ArrayList([]const u8).init(allocator); | |
| 594 | defer link_objects.deinit(); | |
| 595 | var target_str: ?[]const u8 = null; | |
| 596 | var link_libc = false; | |
| 597 | var link_mode: ?std.builtin.LinkMode = null; | |
| 598 | var disable_cache = false; | |
| 599 | var verbose_cimport = false; | |
| 600 | var additional_options = std.ArrayList([]const u8).init(allocator); | |
| 601 | defer additional_options.deinit(); | |
| 602 | ||
| 603 | const source_token = while (true) { | |
| 604 | const content_tok = try eatToken(tokenizer, .content); | |
| 605 | _ = try eatToken(tokenizer, .bracket_open); | |
| 606 | const end_code_tag = try eatToken(tokenizer, .tag_content); | |
| 607 | const end_tag_name = tokenizer.buffer[end_code_tag.start..end_code_tag.end]; | |
| 608 | if (mem.eql(u8, end_tag_name, "code_release_fast")) { | |
| 609 | mode = .ReleaseFast; | |
| 610 | } else if (mem.eql(u8, end_tag_name, "code_release_safe")) { | |
| 611 | mode = .ReleaseSafe; | |
| 612 | } else if (mem.eql(u8, end_tag_name, "code_disable_cache")) { | |
| 613 | disable_cache = true; | |
| 614 | } else if (mem.eql(u8, end_tag_name, "code_verbose_cimport")) { | |
| 615 | verbose_cimport = true; | |
| 616 | } else if (mem.eql(u8, end_tag_name, "code_link_object")) { | |
| 617 | _ = try eatToken(tokenizer, .separator); | |
| 618 | const obj_tok = try eatToken(tokenizer, .tag_content); | |
| 619 | try link_objects.append(tokenizer.buffer[obj_tok.start..obj_tok.end]); | |
| 620 | } else if (mem.eql(u8, end_tag_name, "target_windows")) { | |
| 621 | target_str = "x86_64-windows"; | |
| 622 | } else if (mem.eql(u8, end_tag_name, "target_linux_x86_64")) { | |
| 623 | target_str = "x86_64-linux"; | |
| 624 | } else if (mem.eql(u8, end_tag_name, "target_linux_riscv64")) { | |
| 625 | target_str = "riscv64-linux"; | |
| 626 | } else if (mem.eql(u8, end_tag_name, "target_wasm")) { | |
| 627 | target_str = "wasm32-freestanding"; | |
| 628 | } else if (mem.eql(u8, end_tag_name, "target_wasi")) { | |
| 629 | target_str = "wasm32-wasi"; | |
| 630 | } else if (mem.eql(u8, end_tag_name, "link_libc")) { | |
| 631 | link_libc = true; | |
| 632 | } else if (mem.eql(u8, end_tag_name, "link_mode_dynamic")) { | |
| 633 | link_mode = .Dynamic; | |
| 634 | } else if (mem.eql(u8, end_tag_name, "additonal_option")) { | |
| 635 | _ = try eatToken(tokenizer, .separator); | |
| 636 | const option = try eatToken(tokenizer, .tag_content); | |
| 637 | try additional_options.append(tokenizer.buffer[option.start..option.end]); | |
| 638 | } else if (mem.eql(u8, end_tag_name, "code_end")) { | |
| 639 | _ = try eatToken(tokenizer, .bracket_close); | |
| 640 | break content_tok; | |
| 641 | } else { | |
| 642 | return parseError( | |
| 643 | tokenizer, | |
| 644 | end_code_tag, | |
| 645 | "invalid token inside code_begin: {s}", | |
| 646 | .{end_tag_name}, | |
| 647 | ); | |
| 648 | } | |
| 649 | _ = try eatToken(tokenizer, .bracket_close); | |
| 650 | } else unreachable; // TODO issue #707 | |
| 651 | try nodes.append(Node{ | |
| 652 | .Code = Code{ | |
| 653 | .id = code_kind_id, | |
| 654 | .name = name, | |
| 655 | .source_token = source_token, | |
| 656 | .just_check_syntax = just_check_syntax, | |
| 657 | .mode = mode, | |
| 658 | .link_objects = try link_objects.toOwnedSlice(), | |
| 659 | .target_str = target_str, | |
| 660 | .link_libc = link_libc, | |
| 661 | .link_mode = link_mode, | |
| 662 | .disable_cache = disable_cache, | |
| 663 | .verbose_cimport = verbose_cimport, | |
| 664 | .additional_options = try additional_options.toOwnedSlice(), | |
| 665 | }, | |
| 666 | }); | |
| 667 | tokenizer.code_node_count += 1; | |
| 668 | } else if (mem.eql(u8, tag_name, "syntax")) { | |
| 669 | _ = try eatToken(tokenizer, .bracket_close); | |
| 670 | const content_tok = try eatToken(tokenizer, .content); | |
| 671 | _ = try eatToken(tokenizer, .bracket_open); | |
| 672 | const end_syntax_tag = try eatToken(tokenizer, .tag_content); | |
| 673 | const end_tag_name = tokenizer.buffer[end_syntax_tag.start..end_syntax_tag.end]; | |
| 674 | if (!mem.eql(u8, end_tag_name, "endsyntax")) { | |
| 675 | return parseError( | |
| 676 | tokenizer, | |
| 677 | end_syntax_tag, | |
| 678 | "invalid token inside syntax: {s}", | |
| 679 | .{end_tag_name}, | |
| 680 | ); | |
| 681 | } | |
| 682 | _ = try eatToken(tokenizer, .bracket_close); | |
| 683 | try nodes.append(Node{ .InlineSyntax = content_tok }); | |
| 684 | } else if (mem.eql(u8, tag_name, "shell_samp")) { | |
| 685 | _ = try eatToken(tokenizer, .bracket_close); | |
| 686 | const content_tok = try eatToken(tokenizer, .content); | |
| 687 | _ = try eatToken(tokenizer, .bracket_open); | |
| 688 | const end_syntax_tag = try eatToken(tokenizer, .tag_content); | |
| 689 | const end_tag_name = tokenizer.buffer[end_syntax_tag.start..end_syntax_tag.end]; | |
| 690 | if (!mem.eql(u8, end_tag_name, "end_shell_samp")) { | |
| 691 | return parseError( | |
| 692 | tokenizer, | |
| 693 | end_syntax_tag, | |
| 694 | "invalid token inside syntax: {s}", | |
| 695 | .{end_tag_name}, | |
| 696 | ); | |
| 697 | } | |
| 698 | _ = try eatToken(tokenizer, .bracket_close); | |
| 699 | try nodes.append(Node{ .Shell = content_tok }); | |
| 700 | } else if (mem.eql(u8, tag_name, "syntax_block")) { | |
| 701 | _ = try eatToken(tokenizer, .separator); | |
| 702 | const source_type_tok = try eatToken(tokenizer, .tag_content); | |
| 703 | var name: []const u8 = "sample_code"; | |
| 704 | const maybe_sep = tokenizer.next(); | |
| 705 | switch (maybe_sep.id) { | |
| 706 | .separator => { | |
| 707 | const name_tok = try eatToken(tokenizer, .tag_content); | |
| 708 | name = tokenizer.buffer[name_tok.start..name_tok.end]; | |
| 709 | _ = try eatToken(tokenizer, .bracket_close); | |
| 710 | }, | |
| 711 | .bracket_close => {}, | |
| 712 | else => return parseError(tokenizer, token, "invalid token", .{}), | |
| 713 | } | |
| 714 | const source_type_str = tokenizer.buffer[source_type_tok.start..source_type_tok.end]; | |
| 715 | var source_type: SyntaxBlock.SourceType = undefined; | |
| 716 | if (mem.eql(u8, source_type_str, "zig")) { | |
| 717 | source_type = SyntaxBlock.SourceType.zig; | |
| 718 | } else if (mem.eql(u8, source_type_str, "c")) { | |
| 719 | source_type = SyntaxBlock.SourceType.c; | |
| 720 | } else if (mem.eql(u8, source_type_str, "peg")) { | |
| 721 | source_type = SyntaxBlock.SourceType.peg; | |
| 722 | } else if (mem.eql(u8, source_type_str, "javascript")) { | |
| 723 | source_type = SyntaxBlock.SourceType.javascript; | |
| 724 | } else { | |
| 725 | return parseError(tokenizer, source_type_tok, "unrecognized code kind: {s}", .{source_type_str}); | |
| 726 | } | |
| 727 | const source_token = while (true) { | |
| 728 | const content_tok = try eatToken(tokenizer, .content); | |
| 729 | _ = try eatToken(tokenizer, .bracket_open); | |
| 730 | const end_code_tag = try eatToken(tokenizer, .tag_content); | |
| 731 | const end_tag_name = tokenizer.buffer[end_code_tag.start..end_code_tag.end]; | |
| 732 | if (mem.eql(u8, end_tag_name, "end_syntax_block")) { | |
| 733 | _ = try eatToken(tokenizer, .bracket_close); | |
| 734 | break content_tok; | |
| 735 | } else { | |
| 736 | return parseError( | |
| 737 | tokenizer, | |
| 738 | end_code_tag, | |
| 739 | "invalid token inside code_begin: {s}", | |
| 740 | .{end_tag_name}, | |
| 741 | ); | |
| 742 | } | |
| 743 | _ = try eatToken(tokenizer, .bracket_close); | |
| 744 | }; | |
| 745 | try nodes.append(Node{ .SyntaxBlock = SyntaxBlock{ .source_type = source_type, .name = name, .source_token = source_token } }); | |
| 746 | } else { | |
| 747 | return parseError(tokenizer, tag_token, "unrecognized tag name: {s}", .{tag_name}); | |
| 748 | } | |
| 749 | }, | |
| 750 | else => return parseError(tokenizer, token, "invalid token", .{}), | |
| 751 | } | |
| 752 | } | |
| 753 | ||
| 754 | return Toc{ | |
| 755 | .nodes = try nodes.toOwnedSlice(), | |
| 756 | .toc = try toc_buf.toOwnedSlice(), | |
| 757 | .urls = urls, | |
| 758 | }; | |
| 759 | } | |
| 760 | ||
| 761 | fn urlize(allocator: Allocator, input: []const u8) ![]u8 { | |
| 762 | var buf = std.ArrayList(u8).init(allocator); | |
| 763 | defer buf.deinit(); | |
| 764 | ||
| 765 | const out = buf.writer(); | |
| 766 | for (input) |c| { | |
| 767 | switch (c) { | |
| 768 | 'a'...'z', 'A'...'Z', '_', '-', '0'...'9' => { | |
| 769 | try out.writeByte(c); | |
| 770 | }, | |
| 771 | ' ' => { | |
| 772 | try out.writeByte('-'); | |
| 773 | }, | |
| 774 | else => {}, | |
| 775 | } | |
| 776 | } | |
| 777 | return try buf.toOwnedSlice(); | |
| 778 | } | |
| 779 | ||
| 780 | fn escapeHtml(allocator: Allocator, input: []const u8) ![]u8 { | |
| 781 | var buf = std.ArrayList(u8).init(allocator); | |
| 782 | defer buf.deinit(); | |
| 783 | ||
| 784 | const out = buf.writer(); | |
| 785 | try writeEscaped(out, input); | |
| 786 | return try buf.toOwnedSlice(); | |
| 787 | } | |
| 788 | ||
| 789 | fn writeEscaped(out: anytype, input: []const u8) !void { | |
| 790 | for (input) |c| { | |
| 791 | try switch (c) { | |
| 792 | '&' => out.writeAll("&amp;"), | |
| 793 | '<' => out.writeAll("&lt;"), | |
| 794 | '>' => out.writeAll("&gt;"), | |
| 795 | '"' => out.writeAll("&quot;"), | |
| 796 | else => out.writeByte(c), | |
| 797 | }; | |
| 798 | } | |
| 799 | } | |
| 800 | ||
| 801 | // Returns true if number is in slice. | |
| 802 | fn in(slice: []const u8, number: u8) bool { | |
| 803 | for (slice) |n| { | |
| 804 | if (number == n) return true; | |
| 805 | } | |
| 806 | return false; | |
| 807 | } | |
| 808 | ||
| 809 | fn termColor(allocator: Allocator, input: []const u8) ![]u8 { | |
| 810 | // The SRG sequences generates by the Zig compiler are in the format: | |
| 811 | // ESC [ <foreground-color> ; <n> m | |
| 812 | // or | |
| 813 | // ESC [ <n> m | |
| 814 | // | |
| 815 | // where | |
| 816 | // foreground-color is 31 (red), 32 (green), 36 (cyan) | |
| 817 | // n is 0 (reset), 1 (bold), 2 (dim) | |
| 818 | // | |
| 819 | // Note that 37 (white) is currently not used by the compiler. | |
| 820 | // | |
| 821 | // See std.debug.TTY.Color. | |
| 822 | const supported_sgr_colors = [_]u8{ 31, 32, 36 }; | |
| 823 | const supported_sgr_numbers = [_]u8{ 0, 1, 2 }; | |
| 824 | ||
| 825 | var buf = std.ArrayList(u8).init(allocator); | |
| 826 | defer buf.deinit(); | |
| 827 | ||
| 828 | var out = buf.writer(); | |
| 829 | var sgr_param_start_index: usize = undefined; | |
| 830 | var sgr_num: u8 = undefined; | |
| 831 | var sgr_color: u8 = undefined; | |
| 832 | var i: usize = 0; | |
| 833 | var state: enum { | |
| 834 | start, | |
| 835 | escape, | |
| 836 | lbracket, | |
| 837 | number, | |
| 838 | after_number, | |
| 839 | arg, | |
| 840 | arg_number, | |
| 841 | expect_end, | |
| 842 | } = .start; | |
| 843 | var last_new_line: usize = 0; | |
| 844 | var open_span_count: usize = 0; | |
| 845 | while (i < input.len) : (i += 1) { | |
| 846 | const c = input[i]; | |
| 847 | switch (state) { | |
| 848 | .start => switch (c) { | |
| 849 | '\x1b' => state = .escape, | |
| 850 | '\n' => { | |
| 851 | try out.writeByte(c); | |
| 852 | last_new_line = buf.items.len; | |
| 853 | }, | |
| 854 | else => try out.writeByte(c), | |
| 855 | }, | |
| 856 | .escape => switch (c) { | |
| 857 | '[' => state = .lbracket, | |
| 858 | else => return error.UnsupportedEscape, | |
| 859 | }, | |
| 860 | .lbracket => switch (c) { | |
| 861 | '0'...'9' => { | |
| 862 | sgr_param_start_index = i; | |
| 863 | state = .number; | |
| 864 | }, | |
| 865 | else => return error.UnsupportedEscape, | |
| 866 | }, | |
| 867 | .number => switch (c) { | |
| 868 | '0'...'9' => {}, | |
| 869 | else => { | |
| 870 | sgr_num = try std.fmt.parseInt(u8, input[sgr_param_start_index..i], 10); | |
| 871 | sgr_color = 0; | |
| 872 | state = .after_number; | |
| 873 | i -= 1; | |
| 874 | }, | |
| 875 | }, | |
| 876 | .after_number => switch (c) { | |
| 877 | ';' => state = .arg, | |
| 878 | 'D' => state = .start, | |
| 879 | 'K' => { | |
| 880 | buf.items.len = last_new_line; | |
| 881 | state = .start; | |
| 882 | }, | |
| 883 | else => { | |
| 884 | state = .expect_end; | |
| 885 | i -= 1; | |
| 886 | }, | |
| 887 | }, | |
| 888 | .arg => switch (c) { | |
| 889 | '0'...'9' => { | |
| 890 | sgr_param_start_index = i; | |
| 891 | state = .arg_number; | |
| 892 | }, | |
| 893 | else => return error.UnsupportedEscape, | |
| 894 | }, | |
| 895 | .arg_number => switch (c) { | |
| 896 | '0'...'9' => {}, | |
| 897 | else => { | |
| 898 | // Keep the sequence consistent, foreground color first. | |
| 899 | // 32;1m is equivalent to 1;32m, but the latter will | |
| 900 | // generate an incorrect HTML class without notice. | |
| 901 | sgr_color = sgr_num; | |
| 902 | if (!in(&supported_sgr_colors, sgr_color)) return error.UnsupportedForegroundColor; | |
| 903 | ||
| 904 | sgr_num = try std.fmt.parseInt(u8, input[sgr_param_start_index..i], 10); | |
| 905 | if (!in(&supported_sgr_numbers, sgr_num)) return error.UnsupportedNumber; | |
| 906 | ||
| 907 | state = .expect_end; | |
| 908 | i -= 1; | |
| 909 | }, | |
| 910 | }, | |
| 911 | .expect_end => switch (c) { | |
| 912 | 'm' => { | |
| 913 | state = .start; | |
| 914 | while (open_span_count != 0) : (open_span_count -= 1) { | |
| 915 | try out.writeAll("</span>"); | |
| 916 | } | |
| 917 | if (sgr_num == 0) { | |
| 918 | if (sgr_color != 0) return error.UnsupportedColor; | |
| 919 | continue; | |
| 920 | } | |
| 921 | if (sgr_color != 0) { | |
| 922 | try out.print("<span class=\"sgr-{d}_{d}m\">", .{ sgr_color, sgr_num }); | |
| 923 | } else { | |
| 924 | try out.print("<span class=\"sgr-{d}m\">", .{sgr_num}); | |
| 925 | } | |
| 926 | open_span_count += 1; | |
| 927 | }, | |
| 928 | else => return error.UnsupportedEscape, | |
| 929 | }, | |
| 930 | } | |
| 931 | } | |
| 932 | return try buf.toOwnedSlice(); | |
| 933 | } | |
| 934 | ||
| 935 | const builtin_types = [_][]const u8{ | |
| 936 | "f16", "f32", "f64", "f80", "f128", | |
| 937 | "c_longdouble", "c_short", "c_ushort", "c_int", "c_uint", | |
| 938 | "c_long", "c_ulong", "c_longlong", "c_ulonglong", "c_char", | |
| 939 | "anyopaque", "void", "bool", "isize", "usize", | |
| 940 | "noreturn", "type", "anyerror", "comptime_int", "comptime_float", | |
| 941 | }; | |
| 942 | ||
| 943 | fn isType(name: []const u8) bool { | |
| 944 | for (builtin_types) |t| { | |
| 945 | if (mem.eql(u8, t, name)) | |
| 946 | return true; | |
| 947 | } | |
| 948 | return false; | |
| 949 | } | |
| 950 | ||
| 951 | const start_line = "<span class=\"line\">"; | |
| 952 | const end_line = "</span>"; | |
| 953 | ||
| 954 | fn writeEscapedLines(out: anytype, text: []const u8) !void { | |
| 955 | for (text) |char| { | |
| 956 | if (char == '\n') { | |
| 957 | try out.writeAll(end_line); | |
| 958 | try out.writeAll("\n"); | |
| 959 | try out.writeAll(start_line); | |
| 960 | } else { | |
| 961 | try writeEscaped(out, &[_]u8{char}); | |
| 962 | } | |
| 963 | } | |
| 964 | } | |
| 965 | ||
| 966 | fn tokenizeAndPrintRaw( | |
| 967 | allocator: Allocator, | |
| 968 | docgen_tokenizer: *Tokenizer, | |
| 969 | out: anytype, | |
| 970 | source_token: Token, | |
| 971 | raw_src: []const u8, | |
| 972 | ) !void { | |
| 973 | const src_non_terminated = mem.trim(u8, raw_src, " \n"); | |
| 974 | const src = try allocator.dupeZ(u8, src_non_terminated); | |
| 975 | ||
| 976 | try out.writeAll("<code>" ++ start_line); | |
| 977 | var tokenizer = std.zig.Tokenizer.init(src); | |
| 978 | var index: usize = 0; | |
| 979 | var next_tok_is_fn = false; | |
| 980 | while (true) { | |
| 981 | const prev_tok_was_fn = next_tok_is_fn; | |
| 982 | next_tok_is_fn = false; | |
| 983 | ||
| 984 | const token = tokenizer.next(); | |
| 985 | if (mem.indexOf(u8, src[index..token.loc.start], "//")) |comment_start_off| { | |
| 986 | // render one comment | |
| 987 | const comment_start = index + comment_start_off; | |
| 988 | const comment_end_off = mem.indexOf(u8, src[comment_start..token.loc.start], "\n"); | |
| 989 | const comment_end = if (comment_end_off) |o| comment_start + o else token.loc.start; | |
| 990 | ||
| 991 | try writeEscapedLines(out, src[index..comment_start]); | |
| 992 | try out.writeAll("<span class=\"tok-comment\">"); | |
| 993 | try writeEscaped(out, src[comment_start..comment_end]); | |
| 994 | try out.writeAll("</span>"); | |
| 995 | index = comment_end; | |
| 996 | tokenizer.index = index; | |
| 997 | continue; | |
| 998 | } | |
| 999 | ||
| 1000 | try writeEscapedLines(out, src[index..token.loc.start]); | |
| 1001 | switch (token.tag) { | |
| 1002 | .eof => break, | |
| 1003 | ||
| 1004 | .keyword_addrspace, | |
| 1005 | .keyword_align, | |
| 1006 | .keyword_and, | |
| 1007 | .keyword_asm, | |
| 1008 | .keyword_async, | |
| 1009 | .keyword_await, | |
| 1010 | .keyword_break, | |
| 1011 | .keyword_catch, | |
| 1012 | .keyword_comptime, | |
| 1013 | .keyword_const, | |
| 1014 | .keyword_continue, | |
| 1015 | .keyword_defer, | |
| 1016 | .keyword_else, | |
| 1017 | .keyword_enum, | |
| 1018 | .keyword_errdefer, | |
| 1019 | .keyword_error, | |
| 1020 | .keyword_export, | |
| 1021 | .keyword_extern, | |
| 1022 | .keyword_for, | |
| 1023 | .keyword_if, | |
| 1024 | .keyword_inline, | |
| 1025 | .keyword_noalias, | |
| 1026 | .keyword_noinline, | |
| 1027 | .keyword_nosuspend, | |
| 1028 | .keyword_opaque, | |
| 1029 | .keyword_or, | |
| 1030 | .keyword_orelse, | |
| 1031 | .keyword_packed, | |
| 1032 | .keyword_anyframe, | |
| 1033 | .keyword_pub, | |
| 1034 | .keyword_resume, | |
| 1035 | .keyword_return, | |
| 1036 | .keyword_linksection, | |
| 1037 | .keyword_callconv, | |
| 1038 | .keyword_struct, | |
| 1039 | .keyword_suspend, | |
| 1040 | .keyword_switch, | |
| 1041 | .keyword_test, | |
| 1042 | .keyword_threadlocal, | |
| 1043 | .keyword_try, | |
| 1044 | .keyword_union, | |
| 1045 | .keyword_unreachable, | |
| 1046 | .keyword_usingnamespace, | |
| 1047 | .keyword_var, | |
| 1048 | .keyword_volatile, | |
| 1049 | .keyword_allowzero, | |
| 1050 | .keyword_while, | |
| 1051 | .keyword_anytype, | |
| 1052 | => { | |
| 1053 | try out.writeAll("<span class=\"tok-kw\">"); | |
| 1054 | try writeEscaped(out, src[token.loc.start..token.loc.end]); | |
| 1055 | try out.writeAll("</span>"); | |
| 1056 | }, | |
| 1057 | ||
| 1058 | .keyword_fn => { | |
| 1059 | try out.writeAll("<span class=\"tok-kw\">"); | |
| 1060 | try writeEscaped(out, src[token.loc.start..token.loc.end]); | |
| 1061 | try out.writeAll("</span>"); | |
| 1062 | next_tok_is_fn = true; | |
| 1063 | }, | |
| 1064 | ||
| 1065 | .string_literal, | |
| 1066 | .char_literal, | |
| 1067 | => { | |
| 1068 | try out.writeAll("<span class=\"tok-str\">"); | |
| 1069 | try writeEscaped(out, src[token.loc.start..token.loc.end]); | |
| 1070 | try out.writeAll("</span>"); | |
| 1071 | }, | |
| 1072 | ||
| 1073 | .multiline_string_literal_line => { | |
| 1074 | if (src[token.loc.end - 1] == '\n') { | |
| 1075 | try out.writeAll("<span class=\"tok-str\">"); | |
| 1076 | try writeEscaped(out, src[token.loc.start .. token.loc.end - 1]); | |
| 1077 | try out.writeAll("</span>" ++ end_line ++ "\n" ++ start_line); | |
| 1078 | } else { | |
| 1079 | try out.writeAll("<span class=\"tok-str\">"); | |
| 1080 | try writeEscaped(out, src[token.loc.start..token.loc.end]); | |
| 1081 | try out.writeAll("</span>"); | |
| 1082 | } | |
| 1083 | }, | |
| 1084 | ||
| 1085 | .builtin => { | |
| 1086 | try out.writeAll("<span class=\"tok-builtin\">"); | |
| 1087 | try writeEscaped(out, src[token.loc.start..token.loc.end]); | |
| 1088 | try out.writeAll("</span>"); | |
| 1089 | }, | |
| 1090 | ||
| 1091 | .doc_comment, | |
| 1092 | .container_doc_comment, | |
| 1093 | => { | |
| 1094 | try out.writeAll("<span class=\"tok-comment\">"); | |
| 1095 | try writeEscaped(out, src[token.loc.start..token.loc.end]); | |
| 1096 | try out.writeAll("</span>"); | |
| 1097 | }, | |
| 1098 | ||
| 1099 | .identifier => { | |
| 1100 | const tok_bytes = src[token.loc.start..token.loc.end]; | |
| 1101 | if (mem.eql(u8, tok_bytes, "undefined") or | |
| 1102 | mem.eql(u8, tok_bytes, "null") or | |
| 1103 | mem.eql(u8, tok_bytes, "true") or | |
| 1104 | mem.eql(u8, tok_bytes, "false")) | |
| 1105 | { | |
| 1106 | try out.writeAll("<span class=\"tok-null\">"); | |
| 1107 | try writeEscaped(out, tok_bytes); | |
| 1108 | try out.writeAll("</span>"); | |
| 1109 | } else if (prev_tok_was_fn) { | |
| 1110 | try out.writeAll("<span class=\"tok-fn\">"); | |
| 1111 | try writeEscaped(out, tok_bytes); | |
| 1112 | try out.writeAll("</span>"); | |
| 1113 | } else { | |
| 1114 | const is_int = blk: { | |
| 1115 | if (src[token.loc.start] != 'i' and src[token.loc.start] != 'u') | |
| 1116 | break :blk false; | |
| 1117 | var i = token.loc.start + 1; | |
| 1118 | if (i == token.loc.end) | |
| 1119 | break :blk false; | |
| 1120 | while (i != token.loc.end) : (i += 1) { | |
| 1121 | if (src[i] < '0' or src[i] > '9') | |
| 1122 | break :blk false; | |
| 1123 | } | |
| 1124 | break :blk true; | |
| 1125 | }; | |
| 1126 | if (is_int or isType(tok_bytes)) { | |
| 1127 | try out.writeAll("<span class=\"tok-type\">"); | |
| 1128 | try writeEscaped(out, tok_bytes); | |
| 1129 | try out.writeAll("</span>"); | |
| 1130 | } else { | |
| 1131 | try writeEscaped(out, tok_bytes); | |
| 1132 | } | |
| 1133 | } | |
| 1134 | }, | |
| 1135 | ||
| 1136 | .number_literal => { | |
| 1137 | try out.writeAll("<span class=\"tok-number\">"); | |
| 1138 | try writeEscaped(out, src[token.loc.start..token.loc.end]); | |
| 1139 | try out.writeAll("</span>"); | |
| 1140 | }, | |
| 1141 | ||
| 1142 | .bang, | |
| 1143 | .pipe, | |
| 1144 | .pipe_pipe, | |
| 1145 | .pipe_equal, | |
| 1146 | .equal, | |
| 1147 | .equal_equal, | |
| 1148 | .equal_angle_bracket_right, | |
| 1149 | .bang_equal, | |
| 1150 | .l_paren, | |
| 1151 | .r_paren, | |
| 1152 | .semicolon, | |
| 1153 | .percent, | |
| 1154 | .percent_equal, | |
| 1155 | .l_brace, | |
| 1156 | .r_brace, | |
| 1157 | .l_bracket, | |
| 1158 | .r_bracket, | |
| 1159 | .period, | |
| 1160 | .period_asterisk, | |
| 1161 | .ellipsis2, | |
| 1162 | .ellipsis3, | |
| 1163 | .caret, | |
| 1164 | .caret_equal, | |
| 1165 | .plus, | |
| 1166 | .plus_plus, | |
| 1167 | .plus_equal, | |
| 1168 | .plus_percent, | |
| 1169 | .plus_percent_equal, | |
| 1170 | .plus_pipe, | |
| 1171 | .plus_pipe_equal, | |
| 1172 | .minus, | |
| 1173 | .minus_equal, | |
| 1174 | .minus_percent, | |
| 1175 | .minus_percent_equal, | |
| 1176 | .minus_pipe, | |
| 1177 | .minus_pipe_equal, | |
| 1178 | .asterisk, | |
| 1179 | .asterisk_equal, | |
| 1180 | .asterisk_asterisk, | |
| 1181 | .asterisk_percent, | |
| 1182 | .asterisk_percent_equal, | |
| 1183 | .asterisk_pipe, | |
| 1184 | .asterisk_pipe_equal, | |
| 1185 | .arrow, | |
| 1186 | .colon, | |
| 1187 | .slash, | |
| 1188 | .slash_equal, | |
| 1189 | .comma, | |
| 1190 | .ampersand, | |
| 1191 | .ampersand_equal, | |
| 1192 | .question_mark, | |
| 1193 | .angle_bracket_left, | |
| 1194 | .angle_bracket_left_equal, | |
| 1195 | .angle_bracket_angle_bracket_left, | |
| 1196 | .angle_bracket_angle_bracket_left_equal, | |
| 1197 | .angle_bracket_angle_bracket_left_pipe, | |
| 1198 | .angle_bracket_angle_bracket_left_pipe_equal, | |
| 1199 | .angle_bracket_right, | |
| 1200 | .angle_bracket_right_equal, | |
| 1201 | .angle_bracket_angle_bracket_right, | |
| 1202 | .angle_bracket_angle_bracket_right_equal, | |
| 1203 | .tilde, | |
| 1204 | => try writeEscaped(out, src[token.loc.start..token.loc.end]), | |
| 1205 | ||
| 1206 | .invalid, .invalid_periodasterisks => return parseError( | |
| 1207 | docgen_tokenizer, | |
| 1208 | source_token, | |
| 1209 | "syntax error", | |
| 1210 | .{}, | |
| 1211 | ), | |
| 1212 | } | |
| 1213 | index = token.loc.end; | |
| 1214 | } | |
| 1215 | try out.writeAll(end_line ++ "</code>"); | |
| 1216 | } | |
| 1217 | ||
| 1218 | fn tokenizeAndPrint( | |
| 1219 | allocator: Allocator, | |
| 1220 | docgen_tokenizer: *Tokenizer, | |
| 1221 | out: anytype, | |
| 1222 | source_token: Token, | |
| 1223 | ) !void { | |
| 1224 | const raw_src = docgen_tokenizer.buffer[source_token.start..source_token.end]; | |
| 1225 | return tokenizeAndPrintRaw(allocator, docgen_tokenizer, out, source_token, raw_src); | |
| 1226 | } | |
| 1227 | ||
| 1228 | fn printSourceBlock(allocator: Allocator, docgen_tokenizer: *Tokenizer, out: anytype, syntax_block: SyntaxBlock) !void { | |
| 1229 | const source_type = @tagName(syntax_block.source_type); | |
| 1230 | ||
| 1231 | try out.print("<figure><figcaption class=\"{s}-cap\"><cite class=\"file\">{s}</cite></figcaption><pre>", .{ source_type, syntax_block.name }); | |
| 1232 | switch (syntax_block.source_type) { | |
| 1233 | .zig => try tokenizeAndPrint(allocator, docgen_tokenizer, out, syntax_block.source_token), | |
| 1234 | else => { | |
| 1235 | const raw_source = docgen_tokenizer.buffer[syntax_block.source_token.start..syntax_block.source_token.end]; | |
| 1236 | const trimmed_raw_source = mem.trim(u8, raw_source, " \n"); | |
| 1237 | ||
| 1238 | try out.writeAll("<code>" ++ start_line); | |
| 1239 | try writeEscapedLines(out, trimmed_raw_source); | |
| 1240 | try out.writeAll(end_line ++ "</code>"); | |
| 1241 | }, | |
| 1242 | } | |
| 1243 | try out.writeAll("</pre></figure>"); | |
| 1244 | } | |
| 1245 | ||
| 1246 | fn printShell(out: anytype, shell_content: []const u8, escape: bool) !void { | |
| 1247 | const trimmed_shell_content = mem.trim(u8, shell_content, " \n"); | |
| 1248 | try out.writeAll("<figure><figcaption class=\"shell-cap\">Shell</figcaption><pre><samp>"); | |
| 1249 | var cmd_cont: bool = false; | |
| 1250 | var iter = std.mem.splitScalar(u8, trimmed_shell_content, '\n'); | |
| 1251 | while (iter.next()) |orig_line| { | |
| 1252 | const line = mem.trimRight(u8, orig_line, " "); | |
| 1253 | if (!cmd_cont and line.len > 1 and mem.eql(u8, line[0..2], "$ ") and line[line.len - 1] != '\\') { | |
| 1254 | try out.writeAll("$ <kbd>"); | |
| 1255 | const s = std.mem.trimLeft(u8, line[1..], " "); | |
| 1256 | if (escape) { | |
| 1257 | try writeEscaped(out, s); | |
| 1258 | } else { | |
| 1259 | try out.writeAll(s); | |
| 1260 | } | |
| 1261 | try out.writeAll("</kbd>" ++ "\n"); | |
| 1262 | } else if (!cmd_cont and line.len > 1 and mem.eql(u8, line[0..2], "$ ") and line[line.len - 1] == '\\') { | |
| 1263 | try out.writeAll("$ <kbd>"); | |
| 1264 | const s = std.mem.trimLeft(u8, line[1..], " "); | |
| 1265 | if (escape) { | |
| 1266 | try writeEscaped(out, s); | |
| 1267 | } else { | |
| 1268 | try out.writeAll(s); | |
| 1269 | } | |
| 1270 | try out.writeAll("\n"); | |
| 1271 | cmd_cont = true; | |
| 1272 | } else if (line.len > 0 and line[line.len - 1] != '\\' and cmd_cont) { | |
| 1273 | if (escape) { | |
| 1274 | try writeEscaped(out, line); | |
| 1275 | } else { | |
| 1276 | try out.writeAll(line); | |
| 1277 | } | |
| 1278 | try out.writeAll("</kbd>" ++ "\n"); | |
| 1279 | cmd_cont = false; | |
| 1280 | } else { | |
| 1281 | if (escape) { | |
| 1282 | try writeEscaped(out, line); | |
| 1283 | } else { | |
| 1284 | try out.writeAll(line); | |
| 1285 | } | |
| 1286 | try out.writeAll("\n"); | |
| 1287 | } | |
| 1288 | } | |
| 1289 | ||
| 1290 | try out.writeAll("</samp></pre></figure>"); | |
| 1291 | } | |
| 1292 | ||
| 1293 | // Override this to skip to later tests | |
| 1294 | const debug_start_line = 0; | |
| 1295 | ||
| 1296 | fn genHtml( | |
| 1297 | allocator: Allocator, | |
| 1298 | tokenizer: *Tokenizer, | |
| 1299 | toc: *Toc, | |
| 1300 | out: anytype, | |
| 1301 | zig_exe: []const u8, | |
| 1302 | opt_zig_lib_dir: ?[]const u8, | |
| 1303 | do_code_tests: bool, | |
| 1304 | ) !void { | |
| 1305 | var progress = Progress{ .dont_print_on_dumb = true }; | |
| 1306 | const root_node = progress.start("Generating docgen examples", toc.nodes.len); | |
| 1307 | defer root_node.end(); | |
| 1308 | ||
| 1309 | var env_map = try process.getEnvMap(allocator); | |
| 1310 | try env_map.put("YES_COLOR", "1"); | |
| 1311 | ||
| 1312 | const host = try std.zig.system.NativeTargetInfo.detect(.{}); | |
| 1313 | const builtin_code = try getBuiltinCode(allocator, &env_map, zig_exe, opt_zig_lib_dir); | |
| 1314 | ||
| 1315 | for (toc.nodes) |node| { | |
| 1316 | defer root_node.completeOne(); | |
| 1317 | switch (node) { | |
| 1318 | .Content => |data| { | |
| 1319 | try out.writeAll(data); | |
| 1320 | }, | |
| 1321 | .Link => |info| { | |
| 1322 | if (!toc.urls.contains(info.url)) { | |
| 1323 | return parseError(tokenizer, info.token, "url not found: {s}", .{info.url}); | |
| 1324 | } | |
| 1325 | try out.print("<a href=\"#{s}\">{s}</a>", .{ info.url, info.name }); | |
| 1326 | }, | |
| 1327 | .Nav => { | |
| 1328 | try out.writeAll(toc.toc); | |
| 1329 | }, | |
| 1330 | .Builtin => |tok| { | |
| 1331 | try out.writeAll("<figure><figcaption class=\"zig-cap\"><cite>@import(\"builtin\")</cite></figcaption><pre>"); | |
| 1332 | try tokenizeAndPrintRaw(allocator, tokenizer, out, tok, builtin_code); | |
| 1333 | try out.writeAll("</pre></figure>"); | |
| 1334 | }, | |
| 1335 | .HeaderOpen => |info| { | |
| 1336 | try out.print( | |
| 1337 | "<h{d} id=\"{s}\"><a href=\"#toc-{s}\">{s}</a> <a class=\"hdr\" href=\"#{s}\">§</a></h{d}>\n", | |
| 1338 | .{ info.n, info.url, info.url, info.name, info.url, info.n }, | |
| 1339 | ); | |
| 1340 | }, | |
| 1341 | .SeeAlso => |items| { | |
| 1342 | try out.writeAll("<p>See also:</p><ul>\n"); | |
| 1343 | for (items) |item| { | |
| 1344 | const url = try urlize(allocator, item.name); | |
| 1345 | if (!toc.urls.contains(url)) { | |
| 1346 | return parseError(tokenizer, item.token, "url not found: {s}", .{url}); | |
| 1347 | } | |
| 1348 | try out.print("<li><a href=\"#{s}\">{s}</a></li>\n", .{ url, item.name }); | |
| 1349 | } | |
| 1350 | try out.writeAll("</ul>\n"); | |
| 1351 | }, | |
| 1352 | .InlineSyntax => |content_tok| { | |
| 1353 | try tokenizeAndPrint(allocator, tokenizer, out, content_tok); | |
| 1354 | }, | |
| 1355 | .Shell => |content_tok| { | |
| 1356 | const raw_shell_content = tokenizer.buffer[content_tok.start..content_tok.end]; | |
| 1357 | try printShell(out, raw_shell_content, true); | |
| 1358 | }, | |
| 1359 | .SyntaxBlock => |syntax_block| { | |
| 1360 | try printSourceBlock(allocator, tokenizer, out, syntax_block); | |
| 1361 | }, | |
| 1362 | .Code => |code| { | |
| 1363 | const name_plus_ext = try std.fmt.allocPrint(allocator, "{s}.zig", .{code.name}); | |
| 1364 | const syntax_block = SyntaxBlock{ | |
| 1365 | .source_type = .zig, | |
| 1366 | .name = name_plus_ext, | |
| 1367 | .source_token = code.source_token, | |
| 1368 | }; | |
| 1369 | ||
| 1370 | try printSourceBlock(allocator, tokenizer, out, syntax_block); | |
| 1371 | ||
| 1372 | if (!do_code_tests) { | |
| 1373 | continue; | |
| 1374 | } | |
| 1375 | ||
| 1376 | if (debug_start_line > 0) { | |
| 1377 | const loc = tokenizer.getTokenLocation(code.source_token); | |
| 1378 | if (debug_start_line > loc.line) { | |
| 1379 | continue; | |
| 1380 | } | |
| 1381 | } | |
| 1382 | ||
| 1383 | const raw_source = tokenizer.buffer[code.source_token.start..code.source_token.end]; | |
| 1384 | const trimmed_raw_source = mem.trim(u8, raw_source, " \n"); | |
| 1385 | const tmp_source_file_name = try fs.path.join( | |
| 1386 | allocator, | |
| 1387 | &[_][]const u8{ tmp_dir_name, name_plus_ext }, | |
| 1388 | ); | |
| 1389 | try fs.cwd().writeFile(tmp_source_file_name, trimmed_raw_source); | |
| 1390 | ||
| 1391 | var shell_buffer = std.ArrayList(u8).init(allocator); | |
| 1392 | defer shell_buffer.deinit(); | |
| 1393 | var shell_out = shell_buffer.writer(); | |
| 1394 | ||
| 1395 | switch (code.id) { | |
| 1396 | .exe => |expected_outcome| code_block: { | |
| 1397 | var build_args = std.ArrayList([]const u8).init(allocator); | |
| 1398 | defer build_args.deinit(); | |
| 1399 | try build_args.appendSlice(&[_][]const u8{ | |
| 1400 | zig_exe, "build-exe", | |
| 1401 | "--name", code.name, | |
| 1402 | "--color", "on", | |
| 1403 | name_plus_ext, | |
| 1404 | }); | |
| 1405 | if (opt_zig_lib_dir) |zig_lib_dir| { | |
| 1406 | try build_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir }); | |
| 1407 | } | |
| 1408 | ||
| 1409 | try shell_out.print("$ zig build-exe {s} ", .{name_plus_ext}); | |
| 1410 | ||
| 1411 | switch (code.mode) { | |
| 1412 | .Debug => {}, | |
| 1413 | else => { | |
| 1414 | try build_args.appendSlice(&[_][]const u8{ "-O", @tagName(code.mode) }); | |
| 1415 | try shell_out.print("-O {s} ", .{@tagName(code.mode)}); | |
| 1416 | }, | |
| 1417 | } | |
| 1418 | for (code.link_objects) |link_object| { | |
| 1419 | const name_with_ext = try std.fmt.allocPrint(allocator, "{s}{s}", .{ link_object, obj_ext }); | |
| 1420 | try build_args.append(name_with_ext); | |
| 1421 | try shell_out.print("{s} ", .{name_with_ext}); | |
| 1422 | } | |
| 1423 | if (code.link_libc) { | |
| 1424 | try build_args.append("-lc"); | |
| 1425 | try shell_out.print("-lc ", .{}); | |
| 1426 | } | |
| 1427 | const target = try std.zig.CrossTarget.parse(.{ | |
| 1428 | .arch_os_abi = code.target_str orelse "native", | |
| 1429 | }); | |
| 1430 | if (code.target_str) |triple| { | |
| 1431 | try build_args.appendSlice(&[_][]const u8{ "-target", triple }); | |
| 1432 | try shell_out.print("-target {s} ", .{triple}); | |
| 1433 | } | |
| 1434 | if (code.verbose_cimport) { | |
| 1435 | try build_args.append("--verbose-cimport"); | |
| 1436 | try shell_out.print("--verbose-cimport ", .{}); | |
| 1437 | } | |
| 1438 | for (code.additional_options) |option| { | |
| 1439 | try build_args.append(option); | |
| 1440 | try shell_out.print("{s} ", .{option}); | |
| 1441 | } | |
| 1442 | ||
| 1443 | try shell_out.print("\n", .{}); | |
| 1444 | ||
| 1445 | if (expected_outcome == .build_fail) { | |
| 1446 | const result = try ChildProcess.exec(.{ | |
| 1447 | .allocator = allocator, | |
| 1448 | .argv = build_args.items, | |
| 1449 | .cwd = tmp_dir_name, | |
| 1450 | .env_map = &env_map, | |
| 1451 | .max_output_bytes = max_doc_file_size, | |
| 1452 | }); | |
| 1453 | switch (result.term) { | |
| 1454 | .Exited => |exit_code| { | |
| 1455 | if (exit_code == 0) { | |
| 1456 | progress.log("", .{}); | |
| 1457 | print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr}); | |
| 1458 | dumpArgs(build_args.items); | |
| 1459 | return parseError(tokenizer, code.source_token, "example incorrectly compiled", .{}); | |
| 1460 | } | |
| 1461 | }, | |
| 1462 | else => { | |
| 1463 | progress.log("", .{}); | |
| 1464 | print("{s}\nThe following command crashed:\n", .{result.stderr}); | |
| 1465 | dumpArgs(build_args.items); | |
| 1466 | return parseError(tokenizer, code.source_token, "example compile crashed", .{}); | |
| 1467 | }, | |
| 1468 | } | |
| 1469 | const escaped_stderr = try escapeHtml(allocator, result.stderr); | |
| 1470 | const colored_stderr = try termColor(allocator, escaped_stderr); | |
| 1471 | try shell_out.writeAll(colored_stderr); | |
| 1472 | break :code_block; | |
| 1473 | } | |
| 1474 | const exec_result = exec(allocator, &env_map, tmp_dir_name, build_args.items) catch | |
| 1475 | return parseError(tokenizer, code.source_token, "example failed to compile", .{}); | |
| 1476 | ||
| 1477 | if (code.verbose_cimport) { | |
| 1478 | const escaped_build_stderr = try escapeHtml(allocator, exec_result.stderr); | |
| 1479 | try shell_out.writeAll(escaped_build_stderr); | |
| 1480 | } | |
| 1481 | ||
| 1482 | if (code.target_str) |triple| { | |
| 1483 | if (mem.startsWith(u8, triple, "wasm32") or | |
| 1484 | mem.startsWith(u8, triple, "riscv64-linux") or | |
| 1485 | (mem.startsWith(u8, triple, "x86_64-linux") and | |
| 1486 | builtin.os.tag != .linux or builtin.cpu.arch != .x86_64)) | |
| 1487 | { | |
| 1488 | // skip execution | |
| 1489 | break :code_block; | |
| 1490 | } | |
| 1491 | } | |
| 1492 | ||
| 1493 | const path_to_exe = try std.fmt.allocPrint(allocator, "./{s}{s}", .{ | |
| 1494 | code.name, | |
| 1495 | target.exeFileExt(), | |
| 1496 | }); | |
| 1497 | const run_args = &[_][]const u8{path_to_exe}; | |
| 1498 | ||
| 1499 | var exited_with_signal = false; | |
| 1500 | ||
| 1501 | const result = if (expected_outcome == .fail) blk: { | |
| 1502 | const result = try ChildProcess.exec(.{ | |
| 1503 | .allocator = allocator, | |
| 1504 | .argv = run_args, | |
| 1505 | .env_map = &env_map, | |
| 1506 | .cwd = tmp_dir_name, | |
| 1507 | .max_output_bytes = max_doc_file_size, | |
| 1508 | }); | |
| 1509 | switch (result.term) { | |
| 1510 | .Exited => |exit_code| { | |
| 1511 | if (exit_code == 0) { | |
| 1512 | progress.log("", .{}); | |
| 1513 | print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr}); | |
| 1514 | dumpArgs(run_args); | |
| 1515 | return parseError(tokenizer, code.source_token, "example incorrectly compiled", .{}); | |
| 1516 | } | |
| 1517 | }, | |
| 1518 | .Signal => exited_with_signal = true, | |
| 1519 | else => {}, | |
| 1520 | } | |
| 1521 | break :blk result; | |
| 1522 | } else blk: { | |
| 1523 | break :blk exec(allocator, &env_map, tmp_dir_name, run_args) catch return parseError(tokenizer, code.source_token, "example crashed", .{}); | |
| 1524 | }; | |
| 1525 | ||
| 1526 | const escaped_stderr = try escapeHtml(allocator, result.stderr); | |
| 1527 | const escaped_stdout = try escapeHtml(allocator, result.stdout); | |
| 1528 | ||
| 1529 | const colored_stderr = try termColor(allocator, escaped_stderr); | |
| 1530 | const colored_stdout = try termColor(allocator, escaped_stdout); | |
| 1531 | ||
| 1532 | try shell_out.print("$ ./{s}\n{s}{s}", .{ code.name, colored_stdout, colored_stderr }); | |
| 1533 | if (exited_with_signal) { | |
| 1534 | try shell_out.print("(process terminated by signal)", .{}); | |
| 1535 | } | |
| 1536 | try shell_out.writeAll("\n"); | |
| 1537 | }, | |
| 1538 | .@"test" => { | |
| 1539 | var test_args = std.ArrayList([]const u8).init(allocator); | |
| 1540 | defer test_args.deinit(); | |
| 1541 | ||
| 1542 | try test_args.appendSlice(&[_][]const u8{ | |
| 1543 | zig_exe, "test", | |
| 1544 | tmp_source_file_name, | |
| 1545 | }); | |
| 1546 | if (opt_zig_lib_dir) |zig_lib_dir| { | |
| 1547 | try test_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir }); | |
| 1548 | } | |
| 1549 | try shell_out.print("$ zig test {s}.zig ", .{code.name}); | |
| 1550 | ||
| 1551 | switch (code.mode) { | |
| 1552 | .Debug => {}, | |
| 1553 | else => { | |
| 1554 | try test_args.appendSlice(&[_][]const u8{ | |
| 1555 | "-O", @tagName(code.mode), | |
| 1556 | }); | |
| 1557 | try shell_out.print("-O {s} ", .{@tagName(code.mode)}); | |
| 1558 | }, | |
| 1559 | } | |
| 1560 | if (code.link_libc) { | |
| 1561 | try test_args.append("-lc"); | |
| 1562 | try shell_out.print("-lc ", .{}); | |
| 1563 | } | |
| 1564 | if (code.target_str) |triple| { | |
| 1565 | try test_args.appendSlice(&[_][]const u8{ "-target", triple }); | |
| 1566 | try shell_out.print("-target {s} ", .{triple}); | |
| 1567 | ||
| 1568 | const cross_target = try std.zig.CrossTarget.parse(.{ | |
| 1569 | .arch_os_abi = triple, | |
| 1570 | }); | |
| 1571 | const target_info = try std.zig.system.NativeTargetInfo.detect( | |
| 1572 | cross_target, | |
| 1573 | ); | |
| 1574 | switch (host.getExternalExecutor(target_info, .{ | |
| 1575 | .link_libc = code.link_libc, | |
| 1576 | })) { | |
| 1577 | .native => {}, | |
| 1578 | else => { | |
| 1579 | try test_args.appendSlice(&[_][]const u8{"--test-no-exec"}); | |
| 1580 | try shell_out.writeAll("--test-no-exec"); | |
| 1581 | }, | |
| 1582 | } | |
| 1583 | } | |
| 1584 | const result = exec(allocator, &env_map, null, test_args.items) catch | |
| 1585 | return parseError(tokenizer, code.source_token, "test failed", .{}); | |
| 1586 | const escaped_stderr = try escapeHtml(allocator, result.stderr); | |
| 1587 | const escaped_stdout = try escapeHtml(allocator, result.stdout); | |
| 1588 | try shell_out.print("\n{s}{s}\n", .{ escaped_stderr, escaped_stdout }); | |
| 1589 | }, | |
| 1590 | .test_error => |error_match| { | |
| 1591 | var test_args = std.ArrayList([]const u8).init(allocator); | |
| 1592 | defer test_args.deinit(); | |
| 1593 | ||
| 1594 | try test_args.appendSlice(&[_][]const u8{ | |
| 1595 | zig_exe, "test", | |
| 1596 | "--color", "on", | |
| 1597 | tmp_source_file_name, | |
| 1598 | }); | |
| 1599 | if (opt_zig_lib_dir) |zig_lib_dir| { | |
| 1600 | try test_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir }); | |
| 1601 | } | |
| 1602 | try shell_out.print("$ zig test {s}.zig ", .{code.name}); | |
| 1603 | ||
| 1604 | switch (code.mode) { | |
| 1605 | .Debug => {}, | |
| 1606 | else => { | |
| 1607 | try test_args.appendSlice(&[_][]const u8{ "-O", @tagName(code.mode) }); | |
| 1608 | try shell_out.print("-O {s} ", .{@tagName(code.mode)}); | |
| 1609 | }, | |
| 1610 | } | |
| 1611 | if (code.link_libc) { | |
| 1612 | try test_args.append("-lc"); | |
| 1613 | try shell_out.print("-lc ", .{}); | |
| 1614 | } | |
| 1615 | const result = try ChildProcess.exec(.{ | |
| 1616 | .allocator = allocator, | |
| 1617 | .argv = test_args.items, | |
| 1618 | .env_map = &env_map, | |
| 1619 | .max_output_bytes = max_doc_file_size, | |
| 1620 | }); | |
| 1621 | switch (result.term) { | |
| 1622 | .Exited => |exit_code| { | |
| 1623 | if (exit_code == 0) { | |
| 1624 | progress.log("", .{}); | |
| 1625 | print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr}); | |
| 1626 | dumpArgs(test_args.items); | |
| 1627 | return parseError(tokenizer, code.source_token, "example incorrectly compiled", .{}); | |
| 1628 | } | |
| 1629 | }, | |
| 1630 | else => { | |
| 1631 | progress.log("", .{}); | |
| 1632 | print("{s}\nThe following command crashed:\n", .{result.stderr}); | |
| 1633 | dumpArgs(test_args.items); | |
| 1634 | return parseError(tokenizer, code.source_token, "example compile crashed", .{}); | |
| 1635 | }, | |
| 1636 | } | |
| 1637 | if (mem.indexOf(u8, result.stderr, error_match) == null) { | |
| 1638 | progress.log("", .{}); | |
| 1639 | print("{s}\nExpected to find '{s}' in stderr\n", .{ result.stderr, error_match }); | |
| 1640 | return parseError(tokenizer, code.source_token, "example did not have expected compile error", .{}); | |
| 1641 | } | |
| 1642 | const escaped_stderr = try escapeHtml(allocator, result.stderr); | |
| 1643 | const colored_stderr = try termColor(allocator, escaped_stderr); | |
| 1644 | try shell_out.print("\n{s}\n", .{colored_stderr}); | |
| 1645 | }, | |
| 1646 | .test_safety => |error_match| { | |
| 1647 | var test_args = std.ArrayList([]const u8).init(allocator); | |
| 1648 | defer test_args.deinit(); | |
| 1649 | ||
| 1650 | try test_args.appendSlice(&[_][]const u8{ | |
| 1651 | zig_exe, "test", | |
| 1652 | tmp_source_file_name, | |
| 1653 | }); | |
| 1654 | if (opt_zig_lib_dir) |zig_lib_dir| { | |
| 1655 | try test_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir }); | |
| 1656 | } | |
| 1657 | var mode_arg: []const u8 = ""; | |
| 1658 | switch (code.mode) { | |
| 1659 | .Debug => {}, | |
| 1660 | .ReleaseSafe => { | |
| 1661 | try test_args.append("-OReleaseSafe"); | |
| 1662 | mode_arg = "-OReleaseSafe"; | |
| 1663 | }, | |
| 1664 | .ReleaseFast => { | |
| 1665 | try test_args.append("-OReleaseFast"); | |
| 1666 | mode_arg = "-OReleaseFast"; | |
| 1667 | }, | |
| 1668 | .ReleaseSmall => { | |
| 1669 | try test_args.append("-OReleaseSmall"); | |
| 1670 | mode_arg = "-OReleaseSmall"; | |
| 1671 | }, | |
| 1672 | } | |
| 1673 | ||
| 1674 | const result = try ChildProcess.exec(.{ | |
| 1675 | .allocator = allocator, | |
| 1676 | .argv = test_args.items, | |
| 1677 | .env_map = &env_map, | |
| 1678 | .max_output_bytes = max_doc_file_size, | |
| 1679 | }); | |
| 1680 | switch (result.term) { | |
| 1681 | .Exited => |exit_code| { | |
| 1682 | if (exit_code == 0) { | |
| 1683 | progress.log("", .{}); | |
| 1684 | print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr}); | |
| 1685 | dumpArgs(test_args.items); | |
| 1686 | return parseError(tokenizer, code.source_token, "example test incorrectly succeeded", .{}); | |
| 1687 | } | |
| 1688 | }, | |
| 1689 | else => { | |
| 1690 | progress.log("", .{}); | |
| 1691 | print("{s}\nThe following command crashed:\n", .{result.stderr}); | |
| 1692 | dumpArgs(test_args.items); | |
| 1693 | return parseError(tokenizer, code.source_token, "example compile crashed", .{}); | |
| 1694 | }, | |
| 1695 | } | |
| 1696 | if (mem.indexOf(u8, result.stderr, error_match) == null) { | |
| 1697 | progress.log("", .{}); | |
| 1698 | print("{s}\nExpected to find '{s}' in stderr\n", .{ result.stderr, error_match }); | |
| 1699 | return parseError(tokenizer, code.source_token, "example did not have expected runtime safety error message", .{}); | |
| 1700 | } | |
| 1701 | const escaped_stderr = try escapeHtml(allocator, result.stderr); | |
| 1702 | const colored_stderr = try termColor(allocator, escaped_stderr); | |
| 1703 | try shell_out.print("$ zig test {s}.zig {s}\n{s}\n", .{ | |
| 1704 | code.name, | |
| 1705 | mode_arg, | |
| 1706 | colored_stderr, | |
| 1707 | }); | |
| 1708 | }, | |
| 1709 | .obj => |maybe_error_match| { | |
| 1710 | const name_plus_obj_ext = try std.fmt.allocPrint(allocator, "{s}{s}", .{ code.name, obj_ext }); | |
| 1711 | var build_args = std.ArrayList([]const u8).init(allocator); | |
| 1712 | defer build_args.deinit(); | |
| 1713 | ||
| 1714 | try build_args.appendSlice(&[_][]const u8{ | |
| 1715 | zig_exe, "build-obj", | |
| 1716 | "--color", "on", | |
| 1717 | "--name", code.name, | |
| 1718 | tmp_source_file_name, | |
| 1719 | try std.fmt.allocPrint(allocator, "-femit-bin={s}{c}{s}", .{ | |
| 1720 | tmp_dir_name, fs.path.sep, name_plus_obj_ext, | |
| 1721 | }), | |
| 1722 | }); | |
| 1723 | if (opt_zig_lib_dir) |zig_lib_dir| { | |
| 1724 | try build_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir }); | |
| 1725 | } | |
| 1726 | ||
| 1727 | try shell_out.print("$ zig build-obj {s}.zig ", .{code.name}); | |
| 1728 | ||
| 1729 | switch (code.mode) { | |
| 1730 | .Debug => {}, | |
| 1731 | else => { | |
| 1732 | try build_args.appendSlice(&[_][]const u8{ "-O", @tagName(code.mode) }); | |
| 1733 | try shell_out.print("-O {s} ", .{@tagName(code.mode)}); | |
| 1734 | }, | |
| 1735 | } | |
| 1736 | ||
| 1737 | if (code.target_str) |triple| { | |
| 1738 | try build_args.appendSlice(&[_][]const u8{ "-target", triple }); | |
| 1739 | try shell_out.print("-target {s} ", .{triple}); | |
| 1740 | } | |
| 1741 | for (code.additional_options) |option| { | |
| 1742 | try build_args.append(option); | |
| 1743 | try shell_out.print("{s} ", .{option}); | |
| 1744 | } | |
| 1745 | ||
| 1746 | if (maybe_error_match) |error_match| { | |
| 1747 | const result = try ChildProcess.exec(.{ | |
| 1748 | .allocator = allocator, | |
| 1749 | .argv = build_args.items, | |
| 1750 | .env_map = &env_map, | |
| 1751 | .max_output_bytes = max_doc_file_size, | |
| 1752 | }); | |
| 1753 | switch (result.term) { | |
| 1754 | .Exited => |exit_code| { | |
| 1755 | if (exit_code == 0) { | |
| 1756 | progress.log("", .{}); | |
| 1757 | print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr}); | |
| 1758 | dumpArgs(build_args.items); | |
| 1759 | return parseError(tokenizer, code.source_token, "example build incorrectly succeeded", .{}); | |
| 1760 | } | |
| 1761 | }, | |
| 1762 | else => { | |
| 1763 | progress.log("", .{}); | |
| 1764 | print("{s}\nThe following command crashed:\n", .{result.stderr}); | |
| 1765 | dumpArgs(build_args.items); | |
| 1766 | return parseError(tokenizer, code.source_token, "example compile crashed", .{}); | |
| 1767 | }, | |
| 1768 | } | |
| 1769 | if (mem.indexOf(u8, result.stderr, error_match) == null) { | |
| 1770 | progress.log("", .{}); | |
| 1771 | print("{s}\nExpected to find '{s}' in stderr\n", .{ result.stderr, error_match }); | |
| 1772 | return parseError(tokenizer, code.source_token, "example did not have expected compile error message", .{}); | |
| 1773 | } | |
| 1774 | const escaped_stderr = try escapeHtml(allocator, result.stderr); | |
| 1775 | const colored_stderr = try termColor(allocator, escaped_stderr); | |
| 1776 | try shell_out.print("\n{s} ", .{colored_stderr}); | |
| 1777 | } else { | |
| 1778 | _ = exec(allocator, &env_map, null, build_args.items) catch return parseError(tokenizer, code.source_token, "example failed to compile", .{}); | |
| 1779 | } | |
| 1780 | try shell_out.writeAll("\n"); | |
| 1781 | }, | |
| 1782 | .lib => { | |
| 1783 | const bin_basename = try std.zig.binNameAlloc(allocator, .{ | |
| 1784 | .root_name = code.name, | |
| 1785 | .target = builtin.target, | |
| 1786 | .output_mode = .Lib, | |
| 1787 | }); | |
| 1788 | ||
| 1789 | var test_args = std.ArrayList([]const u8).init(allocator); | |
| 1790 | defer test_args.deinit(); | |
| 1791 | ||
| 1792 | try test_args.appendSlice(&[_][]const u8{ | |
| 1793 | zig_exe, "build-lib", | |
| 1794 | tmp_source_file_name, | |
| 1795 | try std.fmt.allocPrint(allocator, "-femit-bin={s}{s}{s}", .{ | |
| 1796 | tmp_dir_name, fs.path.sep_str, bin_basename, | |
| 1797 | }), | |
| 1798 | }); | |
| 1799 | if (opt_zig_lib_dir) |zig_lib_dir| { | |
| 1800 | try test_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir }); | |
| 1801 | } | |
| 1802 | try shell_out.print("$ zig build-lib {s}.zig ", .{code.name}); | |
| 1803 | ||
| 1804 | switch (code.mode) { | |
| 1805 | .Debug => {}, | |
| 1806 | else => { | |
| 1807 | try test_args.appendSlice(&[_][]const u8{ "-O", @tagName(code.mode) }); | |
| 1808 | try shell_out.print("-O {s} ", .{@tagName(code.mode)}); | |
| 1809 | }, | |
| 1810 | } | |
| 1811 | if (code.target_str) |triple| { | |
| 1812 | try test_args.appendSlice(&[_][]const u8{ "-target", triple }); | |
| 1813 | try shell_out.print("-target {s} ", .{triple}); | |
| 1814 | } | |
| 1815 | if (code.link_mode) |link_mode| { | |
| 1816 | switch (link_mode) { | |
| 1817 | .Static => { | |
| 1818 | try test_args.append("-static"); | |
| 1819 | try shell_out.print("-static ", .{}); | |
| 1820 | }, | |
| 1821 | .Dynamic => { | |
| 1822 | try test_args.append("-dynamic"); | |
| 1823 | try shell_out.print("-dynamic ", .{}); | |
| 1824 | }, | |
| 1825 | } | |
| 1826 | } | |
| 1827 | for (code.additional_options) |option| { | |
| 1828 | try test_args.append(option); | |
| 1829 | try shell_out.print("{s} ", .{option}); | |
| 1830 | } | |
| 1831 | const result = exec(allocator, &env_map, null, test_args.items) catch return parseError(tokenizer, code.source_token, "test failed", .{}); | |
| 1832 | const escaped_stderr = try escapeHtml(allocator, result.stderr); | |
| 1833 | const escaped_stdout = try escapeHtml(allocator, result.stdout); | |
| 1834 | try shell_out.print("\n{s}{s}\n", .{ escaped_stderr, escaped_stdout }); | |
| 1835 | }, | |
| 1836 | } | |
| 1837 | ||
| 1838 | if (!code.just_check_syntax) { | |
| 1839 | try printShell(out, shell_buffer.items, false); | |
| 1840 | } | |
| 1841 | }, | |
| 1842 | } | |
| 1843 | } | |
| 1844 | } | |
| 1845 | ||
| 1846 | fn exec( | |
| 1847 | allocator: Allocator, | |
| 1848 | env_map: *process.EnvMap, | |
| 1849 | cwd: ?[]const u8, | |
| 1850 | args: []const []const u8, | |
| 1851 | ) !ChildProcess.ExecResult { | |
| 1852 | const result = try ChildProcess.exec(.{ | |
| 1853 | .allocator = allocator, | |
| 1854 | .argv = args, | |
| 1855 | .env_map = env_map, | |
| 1856 | .cwd = cwd, | |
| 1857 | .max_output_bytes = max_doc_file_size, | |
| 1858 | }); | |
| 1859 | switch (result.term) { | |
| 1860 | .Exited => |exit_code| { | |
| 1861 | if (exit_code != 0) { | |
| 1862 | print("{s}\nThe following command exited with code {}:\n", .{ result.stderr, exit_code }); | |
| 1863 | dumpArgs(args); | |
| 1864 | return error.ChildExitError; | |
| 1865 | } | |
| 1866 | }, | |
| 1867 | else => { | |
| 1868 | print("{s}\nThe following command crashed:\n", .{result.stderr}); | |
| 1869 | dumpArgs(args); | |
| 1870 | return error.ChildCrashed; | |
| 1871 | }, | |
| 1872 | } | |
| 1873 | return result; | |
| 1874 | } | |
| 1875 | ||
| 1876 | fn getBuiltinCode( | |
| 1877 | allocator: Allocator, | |
| 1878 | env_map: *process.EnvMap, | |
| 1879 | zig_exe: []const u8, | |
| 1880 | opt_zig_lib_dir: ?[]const u8, | |
| 1881 | ) ![]const u8 { | |
| 1882 | if (opt_zig_lib_dir) |zig_lib_dir| { | |
| 1883 | const result = try exec(allocator, env_map, null, &.{ | |
| 1884 | zig_exe, "build-obj", "--show-builtin", "--zig-lib-dir", zig_lib_dir, | |
| 1885 | }); | |
| 1886 | return result.stdout; | |
| 1887 | } else { | |
| 1888 | const result = try exec(allocator, env_map, null, &.{ | |
| 1889 | zig_exe, "build-obj", "--show-builtin", | |
| 1890 | }); | |
| 1891 | return result.stdout; | |
| 1892 | } | |
| 1893 | } | |
| 1894 | ||
| 1895 | fn dumpArgs(args: []const []const u8) void { | |
| 1896 | for (args) |arg| | |
| 1897 | print("{s} ", .{arg}) | |
| 1898 | else | |
| 1899 | print("\n", .{}); | |
| 1900 | } | |
| 1901 | ||
| 1902 | test "term supported colors" { | |
| 1903 | const test_allocator = testing.allocator; | |
| 1904 | ||
| 1905 | { | |
| 1906 | const input = "A\x1b[31;1mred\x1b[0mB"; | |
| 1907 | const expect = "A<span class=\"sgr-31_1m\">red</span>B"; | |
| 1908 | ||
| 1909 | const result = try termColor(test_allocator, input); | |
| 1910 | defer test_allocator.free(result); | |
| 1911 | try testing.expectEqualSlices(u8, expect, result); | |
| 1912 | } | |
| 1913 | ||
| 1914 | { | |
| 1915 | const input = "A\x1b[32;1mgreen\x1b[0mB"; | |
| 1916 | const expect = "A<span class=\"sgr-32_1m\">green</span>B"; | |
| 1917 | ||
| 1918 | const result = try termColor(test_allocator, input); | |
| 1919 | defer test_allocator.free(result); | |
| 1920 | try testing.expectEqualSlices(u8, expect, result); | |
| 1921 | } | |
| 1922 | ||
| 1923 | { | |
| 1924 | const input = "A\x1b[36;1mcyan\x1b[0mB"; | |
| 1925 | const expect = "A<span class=\"sgr-36_1m\">cyan</span>B"; | |
| 1926 | ||
| 1927 | const result = try termColor(test_allocator, input); | |
| 1928 | defer test_allocator.free(result); | |
| 1929 | try testing.expectEqualSlices(u8, expect, result); | |
| 1930 | } | |
| 1931 | ||
| 1932 | { | |
| 1933 | const input = "A\x1b[1mbold\x1b[0mB"; | |
| 1934 | const expect = "A<span class=\"sgr-1m\">bold</span>B"; | |
| 1935 | ||
| 1936 | const result = try termColor(test_allocator, input); | |
| 1937 | defer test_allocator.free(result); | |
| 1938 | try testing.expectEqualSlices(u8, expect, result); | |
| 1939 | } | |
| 1940 | ||
| 1941 | { | |
| 1942 | const input = "A\x1b[2mdim\x1b[0mB"; | |
| 1943 | const expect = "A<span class=\"sgr-2m\">dim</span>B"; | |
| 1944 | ||
| 1945 | const result = try termColor(test_allocator, input); | |
| 1946 | defer test_allocator.free(result); | |
| 1947 | try testing.expectEqualSlices(u8, expect, result); | |
| 1948 | } | |
| 1949 | } | |
| 1950 | ||
| 1951 | test "term output from zig" { | |
| 1952 | // Use data generated by https://github.com/perillo/zig-tty-test-data, | |
| 1953 | // with zig version 0.11.0-dev.1898+36d47dd19. | |
| 1954 | const test_allocator = testing.allocator; | |
| 1955 | ||
| 1956 | { | |
| 1957 | // 1.1-with-build-progress.out | |
| 1958 | const input = "Semantic Analysis [1324] \x1b[25D\x1b[0KLLVM Emit Object... \x1b[20D\x1b[0KLLVM Emit Object... \x1b[20D\x1b[0KLLD Link... \x1b[12D\x1b[0K"; | |
| 1959 | const expect = ""; | |
| 1960 | ||
| 1961 | const result = try termColor(test_allocator, input); | |
| 1962 | defer test_allocator.free(result); | |
| 1963 | try testing.expectEqualSlices(u8, expect, result); | |
| 1964 | } | |
| 1965 | ||
| 1966 | { | |
| 1967 | // 2.1-with-reference-traces.out | |
| 1968 | const input = "\x1b[1msrc/2.1-with-reference-traces.zig:3:7: \x1b[31;1merror: \x1b[0m\x1b[1mcannot assign to constant\n\x1b[0m x += 1;\n \x1b[32;1m~~^~~~\n\x1b[0m\x1b[0m\x1b[2mreferenced by:\n main: src/2.1-with-reference-traces.zig:7:5\n callMain: /usr/local/lib/zig/lib/std/start.zig:607:17\n remaining reference traces hidden; use '-freference-trace' to see all reference traces\n\n\x1b[0m"; | |
| 1969 | const expect = | |
| 1970 | \\<span class="sgr-1m">src/2.1-with-reference-traces.zig:3:7: </span><span class="sgr-31_1m">error: </span><span class="sgr-1m">cannot assign to constant | |
| 1971 | \\</span> x += 1; | |
| 1972 | \\ <span class="sgr-32_1m">~~^~~~ | |
| 1973 | \\</span><span class="sgr-2m">referenced by: | |
| 1974 | \\ main: src/2.1-with-reference-traces.zig:7:5 | |
| 1975 | \\ callMain: /usr/local/lib/zig/lib/std/start.zig:607:17 | |
| 1976 | \\ remaining reference traces hidden; use '-freference-trace' to see all reference traces | |
| 1977 | \\ | |
| 1978 | \\</span> | |
| 1979 | ; | |
| 1980 | ||
| 1981 | const result = try termColor(test_allocator, input); | |
| 1982 | defer test_allocator.free(result); | |
| 1983 | try testing.expectEqualSlices(u8, expect, result); | |
| 1984 | } | |
| 1985 | ||
| 1986 | { | |
| 1987 | // 2.2-without-reference-traces.out | |
| 1988 | const input = "\x1b[1m/usr/local/lib/zig/lib/std/io/fixed_buffer_stream.zig:128:29: \x1b[31;1merror: \x1b[0m\x1b[1minvalid type given to fixedBufferStream\n\x1b[0m else => @compileError(\"invalid type given to fixedBufferStream\"),\n \x1b[32;1m^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\x1b[0m\x1b[1m/usr/local/lib/zig/lib/std/io/fixed_buffer_stream.zig:116:66: \x1b[36;1mnote: \x1b[0m\x1b[1mcalled from here\n\x1b[0mpub fn fixedBufferStream(buffer: anytype) FixedBufferStream(Slice(@TypeOf(buffer))) {\n; \x1b[32;1m~~~~~^~~~~~~~~~~~~~~~~\n\x1b[0m"; | |
| 1989 | const expect = | |
| 1990 | \\<span class="sgr-1m">/usr/local/lib/zig/lib/std/io/fixed_buffer_stream.zig:128:29: </span><span class="sgr-31_1m">error: </span><span class="sgr-1m">invalid type given to fixedBufferStream | |
| 1991 | \\</span> else => @compileError("invalid type given to fixedBufferStream"), | |
| 1992 | \\ <span class="sgr-32_1m">^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| 1993 | \\</span><span class="sgr-1m">/usr/local/lib/zig/lib/std/io/fixed_buffer_stream.zig:116:66: </span><span class="sgr-36_1m">note: </span><span class="sgr-1m">called from here | |
| 1994 | \\</span>pub fn fixedBufferStream(buffer: anytype) FixedBufferStream(Slice(@TypeOf(buffer))) { | |
| 1995 | \\; <span class="sgr-32_1m">~~~~~^~~~~~~~~~~~~~~~~ | |
| 1996 | \\</span> | |
| 1997 | ; | |
| 1998 | ||
| 1999 | const result = try termColor(test_allocator, input); | |
| 2000 | defer test_allocator.free(result); | |
| 2001 | try testing.expectEqualSlices(u8, expect, result); | |
| 2002 | } | |
| 2003 | ||
| 2004 | { | |
| 2005 | // 2.3-with-notes.out | |
| 2006 | const input = "\x1b[1msrc/2.3-with-notes.zig:6:9: \x1b[31;1merror: \x1b[0m\x1b[1mexpected type '*2.3-with-notes.Derp', found '*2.3-with-notes.Wat'\n\x1b[0m bar(w);\n \x1b[32;1m^\n\x1b[0m\x1b[1msrc/2.3-with-notes.zig:6:9: \x1b[36;1mnote: \x1b[0m\x1b[1mpointer type child '2.3-with-notes.Wat' cannot cast into pointer type child '2.3-with-notes.Derp'\n\x1b[0m\x1b[1msrc/2.3-with-notes.zig:2:13: \x1b[36;1mnote: \x1b[0m\x1b[1mopaque declared here\n\x1b[0mconst Wat = opaque {};\n \x1b[32;1m^~~~~~~~~\n\x1b[0m\x1b[1msrc/2.3-with-notes.zig:1:14: \x1b[36;1mnote: \x1b[0m\x1b[1mopaque declared here\n\x1b[0mconst Derp = opaque {};\n \x1b[32;1m^~~~~~~~~\n\x1b[0m\x1b[1msrc/2.3-with-notes.zig:4:18: \x1b[36;1mnote: \x1b[0m\x1b[1mparameter type declared here\n\x1b[0mextern fn bar(d: *Derp) void;\n \x1b[32;1m^~~~~\n\x1b[0m\x1b[0m\x1b[2mreferenced by:\n main: src/2.3-with-notes.zig:10:5\n callMain: /usr/local/lib/zig/lib/std/start.zig:607:17\n remaining reference traces hidden; use '-freference-trace' to see all reference traces\n\n\x1b[0m"; | |
| 2007 | const expect = | |
| 2008 | \\<span class="sgr-1m">src/2.3-with-notes.zig:6:9: </span><span class="sgr-31_1m">error: </span><span class="sgr-1m">expected type '*2.3-with-notes.Derp', found '*2.3-with-notes.Wat' | |
| 2009 | \\</span> bar(w); | |
| 2010 | \\ <span class="sgr-32_1m">^ | |
| 2011 | \\</span><span class="sgr-1m">src/2.3-with-notes.zig:6:9: </span><span class="sgr-36_1m">note: </span><span class="sgr-1m">pointer type child '2.3-with-notes.Wat' cannot cast into pointer type child '2.3-with-notes.Derp' | |
| 2012 | \\</span><span class="sgr-1m">src/2.3-with-notes.zig:2:13: </span><span class="sgr-36_1m">note: </span><span class="sgr-1m">opaque declared here | |
| 2013 | \\</span>const Wat = opaque {}; | |
| 2014 | \\ <span class="sgr-32_1m">^~~~~~~~~ | |
| 2015 | \\</span><span class="sgr-1m">src/2.3-with-notes.zig:1:14: </span><span class="sgr-36_1m">note: </span><span class="sgr-1m">opaque declared here | |
| 2016 | \\</span>const Derp = opaque {}; | |
| 2017 | \\ <span class="sgr-32_1m">^~~~~~~~~ | |
| 2018 | \\</span><span class="sgr-1m">src/2.3-with-notes.zig:4:18: </span><span class="sgr-36_1m">note: </span><span class="sgr-1m">parameter type declared here | |
| 2019 | \\</span>extern fn bar(d: *Derp) void; | |
| 2020 | \\ <span class="sgr-32_1m">^~~~~ | |
| 2021 | \\</span><span class="sgr-2m">referenced by: | |
| 2022 | \\ main: src/2.3-with-notes.zig:10:5 | |
| 2023 | \\ callMain: /usr/local/lib/zig/lib/std/start.zig:607:17 | |
| 2024 | \\ remaining reference traces hidden; use '-freference-trace' to see all reference traces | |
| 2025 | \\ | |
| 2026 | \\</span> | |
| 2027 | ; | |
| 2028 | ||
| 2029 | const result = try termColor(test_allocator, input); | |
| 2030 | defer test_allocator.free(result); | |
| 2031 | try testing.expectEqualSlices(u8, expect, result); | |
| 2032 | } | |
| 2033 | ||
| 2034 | { | |
| 2035 | // 3.1-with-error-return-traces.out | |
| 2036 | ||
| 2037 | const input = "error: Error\n\x1b[1m/home/zig/src/3.1-with-error-return-traces.zig:5:5\x1b[0m: \x1b[2m0x20b008 in callee (3.1-with-error-return-traces)\x1b[0m\n return error.Error;\n \x1b[32;1m^\x1b[0m\n\x1b[1m/home/zig/src/3.1-with-error-return-traces.zig:9:5\x1b[0m: \x1b[2m0x20b113 in caller (3.1-with-error-return-traces)\x1b[0m\n try callee();\n \x1b[32;1m^\x1b[0m\n\x1b[1m/home/zig/src/3.1-with-error-return-traces.zig:13:5\x1b[0m: \x1b[2m0x20b153 in main (3.1-with-error-return-traces)\x1b[0m\n try caller();\n \x1b[32;1m^\x1b[0m\n"; | |
| 2038 | const expect = | |
| 2039 | \\error: Error | |
| 2040 | \\<span class="sgr-1m">/home/zig/src/3.1-with-error-return-traces.zig:5:5</span>: <span class="sgr-2m">0x20b008 in callee (3.1-with-error-return-traces)</span> | |
| 2041 | \\ return error.Error; | |
| 2042 | \\ <span class="sgr-32_1m">^</span> | |
| 2043 | \\<span class="sgr-1m">/home/zig/src/3.1-with-error-return-traces.zig:9:5</span>: <span class="sgr-2m">0x20b113 in caller (3.1-with-error-return-traces)</span> | |
| 2044 | \\ try callee(); | |
| 2045 | \\ <span class="sgr-32_1m">^</span> | |
| 2046 | \\<span class="sgr-1m">/home/zig/src/3.1-with-error-return-traces.zig:13:5</span>: <span class="sgr-2m">0x20b153 in main (3.1-with-error-return-traces)</span> | |
| 2047 | \\ try caller(); | |
| 2048 | \\ <span class="sgr-32_1m">^</span> | |
| 2049 | \\ | |
| 2050 | ; | |
| 2051 | ||
| 2052 | const result = try termColor(test_allocator, input); | |
| 2053 | defer test_allocator.free(result); | |
| 2054 | try testing.expectEqualSlices(u8, expect, result); | |
| 2055 | } | |
| 2056 | ||
| 2057 | { | |
| 2058 | // 3.2-with-stack-trace.out | |
| 2059 | const input = "\x1b[1m/usr/local/lib/zig/lib/std/debug.zig:561:19\x1b[0m: \x1b[2m0x22a107 in writeCurrentStackTrace__anon_5898 (3.2-with-stack-trace)\x1b[0m\n while (it.next()) |return_address| {\n \x1b[32;1m^\x1b[0m\n\x1b[1m/usr/local/lib/zig/lib/std/debug.zig:157:80\x1b[0m: \x1b[2m0x20bb23 in dumpCurrentStackTrace (3.2-with-stack-trace)\x1b[0m\n writeCurrentStackTrace(stderr, debug_info, detectTTYConfig(io.getStdErr()), start_addr) catch |err| {\n \x1b[32;1m^\x1b[0m\n\x1b[1m/home/zig/src/3.2-with-stack-trace.zig:5:36\x1b[0m: \x1b[2m0x20d3b2 in foo (3.2-with-stack-trace)\x1b[0m\n std.debug.dumpCurrentStackTrace(null);\n \x1b[32;1m^\x1b[0m\n\x1b[1m/home/zig/src/3.2-with-stack-trace.zig:9:8\x1b[0m: \x1b[2m0x20b458 in main (3.2-with-stack-trace)\x1b[0m\n foo();\n \x1b[32;1m^\x1b[0m\n\x1b[1m/usr/local/lib/zig/lib/std/start.zig:607:22\x1b[0m: \x1b[2m0x20a965 in posixCallMainAndExit (3.2-with-stack-trace)\x1b[0m\n root.main();\n \x1b[32;1m^\x1b[0m\n\x1b[1m/usr/local/lib/zig/lib/std/start.zig:376:5\x1b[0m: \x1b[2m0x20a411 in _start (3.2-with-stack-trace)\x1b[0m\n @call(.never_inline, posixCallMainAndExit, .{});\n \x1b[32;1m^\x1b[0m\n"; | |
| 2060 | const expect = | |
| 2061 | \\<span class="sgr-1m">/usr/local/lib/zig/lib/std/debug.zig:561:19</span>: <span class="sgr-2m">0x22a107 in writeCurrentStackTrace__anon_5898 (3.2-with-stack-trace)</span> | |
| 2062 | \\ while (it.next()) |return_address| { | |
| 2063 | \\ <span class="sgr-32_1m">^</span> | |
| 2064 | \\<span class="sgr-1m">/usr/local/lib/zig/lib/std/debug.zig:157:80</span>: <span class="sgr-2m">0x20bb23 in dumpCurrentStackTrace (3.2-with-stack-trace)</span> | |
| 2065 | \\ writeCurrentStackTrace(stderr, debug_info, detectTTYConfig(io.getStdErr()), start_addr) catch |err| { | |
| 2066 | \\ <span class="sgr-32_1m">^</span> | |
| 2067 | \\<span class="sgr-1m">/home/zig/src/3.2-with-stack-trace.zig:5:36</span>: <span class="sgr-2m">0x20d3b2 in foo (3.2-with-stack-trace)</span> | |
| 2068 | \\ std.debug.dumpCurrentStackTrace(null); | |
| 2069 | \\ <span class="sgr-32_1m">^</span> | |
| 2070 | \\<span class="sgr-1m">/home/zig/src/3.2-with-stack-trace.zig:9:8</span>: <span class="sgr-2m">0x20b458 in main (3.2-with-stack-trace)</span> | |
| 2071 | \\ foo(); | |
| 2072 | \\ <span class="sgr-32_1m">^</span> | |
| 2073 | \\<span class="sgr-1m">/usr/local/lib/zig/lib/std/start.zig:607:22</span>: <span class="sgr-2m">0x20a965 in posixCallMainAndExit (3.2-with-stack-trace)</span> | |
| 2074 | \\ root.main(); | |
| 2075 | \\ <span class="sgr-32_1m">^</span> | |
| 2076 | \\<span class="sgr-1m">/usr/local/lib/zig/lib/std/start.zig:376:5</span>: <span class="sgr-2m">0x20a411 in _start (3.2-with-stack-trace)</span> | |
| 2077 | \\ @call(.never_inline, posixCallMainAndExit, .{}); | |
| 2078 | \\ <span class="sgr-32_1m">^</span> | |
| 2079 | \\ | |
| 2080 | ; | |
| 2081 | ||
| 2082 | const result = try termColor(test_allocator, input); | |
| 2083 | defer test_allocator.free(result); | |
| 2084 | try testing.expectEqualSlices(u8, expect, result); | |
| 2085 | } | |
| 2086 | } | |
| 2087 | ||
| 2088 | test "printShell" { | |
| 2089 | const test_allocator = std.testing.allocator; | |
| 2090 | ||
| 2091 | { | |
| 2092 | const shell_out = | |
| 2093 | \\$ zig build test.zig | |
| 2094 | ; | |
| 2095 | const expected = | |
| 2096 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd> | |
| 2097 | \\</samp></pre></figure> | |
| 2098 | ; | |
| 2099 | ||
| 2100 | var buffer = std.ArrayList(u8).init(test_allocator); | |
| 2101 | defer buffer.deinit(); | |
| 2102 | ||
| 2103 | try printShell(buffer.writer(), shell_out, false); | |
| 2104 | try testing.expectEqualSlices(u8, expected, buffer.items); | |
| 2105 | } | |
| 2106 | { | |
| 2107 | const shell_out = | |
| 2108 | \\$ zig build test.zig | |
| 2109 | \\build output | |
| 2110 | ; | |
| 2111 | const expected = | |
| 2112 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd> | |
| 2113 | \\build output | |
| 2114 | \\</samp></pre></figure> | |
| 2115 | ; | |
| 2116 | ||
| 2117 | var buffer = std.ArrayList(u8).init(test_allocator); | |
| 2118 | defer buffer.deinit(); | |
| 2119 | ||
| 2120 | try printShell(buffer.writer(), shell_out, false); | |
| 2121 | try testing.expectEqualSlices(u8, expected, buffer.items); | |
| 2122 | } | |
| 2123 | { | |
| 2124 | const shell_out = | |
| 2125 | \\$ zig build test.zig | |
| 2126 | \\build output | |
| 2127 | \\$ ./test | |
| 2128 | ; | |
| 2129 | const expected = | |
| 2130 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd> | |
| 2131 | \\build output | |
| 2132 | \\$ <kbd>./test</kbd> | |
| 2133 | \\</samp></pre></figure> | |
| 2134 | ; | |
| 2135 | ||
| 2136 | var buffer = std.ArrayList(u8).init(test_allocator); | |
| 2137 | defer buffer.deinit(); | |
| 2138 | ||
| 2139 | try printShell(buffer.writer(), shell_out, false); | |
| 2140 | try testing.expectEqualSlices(u8, expected, buffer.items); | |
| 2141 | } | |
| 2142 | { | |
| 2143 | const shell_out = | |
| 2144 | \\$ zig build test.zig | |
| 2145 | \\ | |
| 2146 | \\$ ./test | |
| 2147 | \\output | |
| 2148 | ; | |
| 2149 | const expected = | |
| 2150 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd> | |
| 2151 | \\ | |
| 2152 | \\$ <kbd>./test</kbd> | |
| 2153 | \\output | |
| 2154 | \\</samp></pre></figure> | |
| 2155 | ; | |
| 2156 | ||
| 2157 | var buffer = std.ArrayList(u8).init(test_allocator); | |
| 2158 | defer buffer.deinit(); | |
| 2159 | ||
| 2160 | try printShell(buffer.writer(), shell_out, false); | |
| 2161 | try testing.expectEqualSlices(u8, expected, buffer.items); | |
| 2162 | } | |
| 2163 | { | |
| 2164 | const shell_out = | |
| 2165 | \\$ zig build test.zig | |
| 2166 | \\$ ./test | |
| 2167 | \\output | |
| 2168 | ; | |
| 2169 | const expected = | |
| 2170 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd> | |
| 2171 | \\$ <kbd>./test</kbd> | |
| 2172 | \\output | |
| 2173 | \\</samp></pre></figure> | |
| 2174 | ; | |
| 2175 | ||
| 2176 | var buffer = std.ArrayList(u8).init(test_allocator); | |
| 2177 | defer buffer.deinit(); | |
| 2178 | ||
| 2179 | try printShell(buffer.writer(), shell_out, false); | |
| 2180 | try testing.expectEqualSlices(u8, expected, buffer.items); | |
| 2181 | } | |
| 2182 | { | |
| 2183 | const shell_out = | |
| 2184 | \\$ zig build test.zig \ | |
| 2185 | \\ --build-option | |
| 2186 | \\build output | |
| 2187 | \\$ ./test | |
| 2188 | \\output | |
| 2189 | ; | |
| 2190 | const expected = | |
| 2191 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig \ | |
| 2192 | \\ --build-option</kbd> | |
| 2193 | \\build output | |
| 2194 | \\$ <kbd>./test</kbd> | |
| 2195 | \\output | |
| 2196 | \\</samp></pre></figure> | |
| 2197 | ; | |
| 2198 | ||
| 2199 | var buffer = std.ArrayList(u8).init(test_allocator); | |
| 2200 | defer buffer.deinit(); | |
| 2201 | ||
| 2202 | try printShell(buffer.writer(), shell_out, false); | |
| 2203 | try testing.expectEqualSlices(u8, expected, buffer.items); | |
| 2204 | } | |
| 2205 | { | |
| 2206 | // intentional space after "--build-option1 \" | |
| 2207 | const shell_out = | |
| 2208 | \\$ zig build test.zig \ | |
| 2209 | \\ --build-option1 \ | |
| 2210 | \\ --build-option2 | |
| 2211 | \\$ ./test | |
| 2212 | ; | |
| 2213 | const expected = | |
| 2214 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig \ | |
| 2215 | \\ --build-option1 \ | |
| 2216 | \\ --build-option2</kbd> | |
| 2217 | \\$ <kbd>./test</kbd> | |
| 2218 | \\</samp></pre></figure> | |
| 2219 | ; | |
| 2220 | ||
| 2221 | var buffer = std.ArrayList(u8).init(test_allocator); | |
| 2222 | defer buffer.deinit(); | |
| 2223 | ||
| 2224 | try printShell(buffer.writer(), shell_out, false); | |
| 2225 | try testing.expectEqualSlices(u8, expected, buffer.items); | |
| 2226 | } | |
| 2227 | { | |
| 2228 | const shell_out = | |
| 2229 | \\$ zig build test.zig \ | |
| 2230 | \\$ ./test | |
| 2231 | ; | |
| 2232 | const expected = | |
| 2233 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig \ | |
| 2234 | \\$ ./test</kbd> | |
| 2235 | \\</samp></pre></figure> | |
| 2236 | ; | |
| 2237 | ||
| 2238 | var buffer = std.ArrayList(u8).init(test_allocator); | |
| 2239 | defer buffer.deinit(); | |
| 2240 | ||
| 2241 | try printShell(buffer.writer(), shell_out, false); | |
| 2242 | try testing.expectEqualSlices(u8, expected, buffer.items); | |
| 2243 | } | |
| 2244 | { | |
| 2245 | const shell_out = | |
| 2246 | \\$ zig build test.zig | |
| 2247 | \\$ ./test | |
| 2248 | \\$1 | |
| 2249 | ; | |
| 2250 | const expected = | |
| 2251 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd> | |
| 2252 | \\$ <kbd>./test</kbd> | |
| 2253 | \\$1 | |
| 2254 | \\</samp></pre></figure> | |
| 2255 | ; | |
| 2256 | ||
| 2257 | var buffer = std.ArrayList(u8).init(test_allocator); | |
| 2258 | defer buffer.deinit(); | |
| 2259 | ||
| 2260 | try printShell(buffer.writer(), shell_out, false); | |
| 2261 | try testing.expectEqualSlices(u8, expected, buffer.items); | |
| 2262 | } | |
| 2263 | { | |
| 2264 | const shell_out = | |
| 2265 | \\$zig build test.zig | |
| 2266 | ; | |
| 2267 | const expected = | |
| 2268 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$zig build test.zig | |
| 2269 | \\</samp></pre></figure> | |
| 2270 | ; | |
| 2271 | ||
| 2272 | var buffer = std.ArrayList(u8).init(test_allocator); | |
| 2273 | defer buffer.deinit(); | |
| 2274 | ||
| 2275 | try printShell(buffer.writer(), shell_out, false); | |
| 2276 | try testing.expectEqualSlices(u8, expected, buffer.items); | |
| 2277 | } | |
| 2278 | } |
tools/docgen.zig created+2278| ... | ... | @@ -0,0 +1,2278 @@ |
| 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 | ||
| 13 | const max_doc_file_size = 10 * 1024 * 1024; | |
| 14 | ||
| 15 | const exe_ext = @as(std.zig.CrossTarget, .{}).exeFileExt(); | |
| 16 | const obj_ext = builtin.object_format.fileExt(builtin.cpu.arch); | |
| 17 | const tmp_dir_name = "docgen_tmp"; | |
| 18 | const test_out_path = tmp_dir_name ++ fs.path.sep_str ++ "test" ++ exe_ext; | |
| 19 | ||
| 20 | const usage = | |
| 21 | \\Usage: docgen [--zig] [--skip-code-tests] input output" | |
| 22 | \\ | |
| 23 | \\ Generates an HTML document from a docgen template. | |
| 24 | \\ | |
| 25 | \\Options: | |
| 26 | \\ -h, --help Print this help and exit | |
| 27 | \\ --skip-code-tests Skip the doctests | |
| 28 | \\ | |
| 29 | ; | |
| 30 | ||
| 31 | fn fatal(comptime format: []const u8, args: anytype) noreturn { | |
| 32 | const stderr = io.getStdErr().writer(); | |
| 33 | ||
| 34 | stderr.print("error: " ++ format ++ "\n", args) catch {}; | |
| 35 | process.exit(1); | |
| 36 | } | |
| 37 | ||
| 38 | pub fn main() !void { | |
| 39 | var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); | |
| 40 | defer arena.deinit(); | |
| 41 | ||
| 42 | const allocator = arena.allocator(); | |
| 43 | ||
| 44 | var args_it = try process.argsWithAllocator(allocator); | |
| 45 | if (!args_it.skip()) @panic("expected self arg"); | |
| 46 | ||
| 47 | var zig_exe: []const u8 = "zig"; | |
| 48 | var opt_zig_lib_dir: ?[]const u8 = null; | |
| 49 | var do_code_tests = true; | |
| 50 | var files = [_][]const u8{ "", "" }; | |
| 51 | ||
| 52 | var i: usize = 0; | |
| 53 | while (args_it.next()) |arg| { | |
| 54 | if (mem.startsWith(u8, arg, "-")) { | |
| 55 | if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { | |
| 56 | const stdout = io.getStdOut().writer(); | |
| 57 | try stdout.writeAll(usage); | |
| 58 | process.exit(0); | |
| 59 | } else if (mem.eql(u8, arg, "--zig")) { | |
| 60 | if (args_it.next()) |param| { | |
| 61 | zig_exe = param; | |
| 62 | } else { | |
| 63 | fatal("expected parameter after --zig", .{}); | |
| 64 | } | |
| 65 | } else if (mem.eql(u8, arg, "--zig-lib-dir")) { | |
| 66 | if (args_it.next()) |param| { | |
| 67 | // Convert relative to absolute because this will be passed | |
| 68 | // to a child process with a different cwd. | |
| 69 | opt_zig_lib_dir = try fs.realpathAlloc(allocator, param); | |
| 70 | } else { | |
| 71 | fatal("expected parameter after --zig-lib-dir", .{}); | |
| 72 | } | |
| 73 | } else if (mem.eql(u8, arg, "--skip-code-tests")) { | |
| 74 | do_code_tests = false; | |
| 75 | } else { | |
| 76 | fatal("unrecognized option: '{s}'", .{arg}); | |
| 77 | } | |
| 78 | } else { | |
| 79 | if (i > 1) { | |
| 80 | fatal("too many arguments", .{}); | |
| 81 | } | |
| 82 | files[i] = arg; | |
| 83 | i += 1; | |
| 84 | } | |
| 85 | } | |
| 86 | if (i < 2) { | |
| 87 | fatal("not enough arguments", .{}); | |
| 88 | } | |
| 89 | ||
| 90 | var in_file = try fs.cwd().openFile(files[0], .{ .mode = .read_only }); | |
| 91 | defer in_file.close(); | |
| 92 | ||
| 93 | var out_file = try fs.cwd().createFile(files[1], .{}); | |
| 94 | defer out_file.close(); | |
| 95 | ||
| 96 | const input_file_bytes = try in_file.reader().readAllAlloc(allocator, max_doc_file_size); | |
| 97 | ||
| 98 | var buffered_writer = io.bufferedWriter(out_file.writer()); | |
| 99 | ||
| 100 | var tokenizer = Tokenizer.init(files[0], input_file_bytes); | |
| 101 | var toc = try genToc(allocator, &tokenizer); | |
| 102 | ||
| 103 | try fs.cwd().makePath(tmp_dir_name); | |
| 104 | defer fs.cwd().deleteTree(tmp_dir_name) catch {}; | |
| 105 | ||
| 106 | try genHtml(allocator, &tokenizer, &toc, buffered_writer.writer(), zig_exe, opt_zig_lib_dir, do_code_tests); | |
| 107 | try buffered_writer.flush(); | |
| 108 | } | |
| 109 | ||
| 110 | const Token = struct { | |
| 111 | id: Id, | |
| 112 | start: usize, | |
| 113 | end: usize, | |
| 114 | ||
| 115 | const Id = enum { | |
| 116 | invalid, | |
| 117 | content, | |
| 118 | bracket_open, | |
| 119 | tag_content, | |
| 120 | separator, | |
| 121 | bracket_close, | |
| 122 | eof, | |
| 123 | }; | |
| 124 | }; | |
| 125 | ||
| 126 | const Tokenizer = struct { | |
| 127 | buffer: []const u8, | |
| 128 | index: usize, | |
| 129 | state: State, | |
| 130 | source_file_name: []const u8, | |
| 131 | code_node_count: usize, | |
| 132 | ||
| 133 | const State = enum { | |
| 134 | start, | |
| 135 | l_bracket, | |
| 136 | hash, | |
| 137 | tag_name, | |
| 138 | eof, | |
| 139 | }; | |
| 140 | ||
| 141 | fn init(source_file_name: []const u8, buffer: []const u8) Tokenizer { | |
| 142 | return Tokenizer{ | |
| 143 | .buffer = buffer, | |
| 144 | .index = 0, | |
| 145 | .state = .start, | |
| 146 | .source_file_name = source_file_name, | |
| 147 | .code_node_count = 0, | |
| 148 | }; | |
| 149 | } | |
| 150 | ||
| 151 | fn next(self: *Tokenizer) Token { | |
| 152 | var result = Token{ | |
| 153 | .id = .eof, | |
| 154 | .start = self.index, | |
| 155 | .end = undefined, | |
| 156 | }; | |
| 157 | while (self.index < self.buffer.len) : (self.index += 1) { | |
| 158 | const c = self.buffer[self.index]; | |
| 159 | switch (self.state) { | |
| 160 | .start => switch (c) { | |
| 161 | '{' => { | |
| 162 | self.state = .l_bracket; | |
| 163 | }, | |
| 164 | else => { | |
| 165 | result.id = .content; | |
| 166 | }, | |
| 167 | }, | |
| 168 | .l_bracket => switch (c) { | |
| 169 | '#' => { | |
| 170 | if (result.id != .eof) { | |
| 171 | self.index -= 1; | |
| 172 | self.state = .start; | |
| 173 | break; | |
| 174 | } else { | |
| 175 | result.id = .bracket_open; | |
| 176 | self.index += 1; | |
| 177 | self.state = .tag_name; | |
| 178 | break; | |
| 179 | } | |
| 180 | }, | |
| 181 | else => { | |
| 182 | result.id = .content; | |
| 183 | self.state = .start; | |
| 184 | }, | |
| 185 | }, | |
| 186 | .tag_name => switch (c) { | |
| 187 | '|' => { | |
| 188 | if (result.id != .eof) { | |
| 189 | break; | |
| 190 | } else { | |
| 191 | result.id = .separator; | |
| 192 | self.index += 1; | |
| 193 | break; | |
| 194 | } | |
| 195 | }, | |
| 196 | '#' => { | |
| 197 | self.state = .hash; | |
| 198 | }, | |
| 199 | else => { | |
| 200 | result.id = .tag_content; | |
| 201 | }, | |
| 202 | }, | |
| 203 | .hash => switch (c) { | |
| 204 | '}' => { | |
| 205 | if (result.id != .eof) { | |
| 206 | self.index -= 1; | |
| 207 | self.state = .tag_name; | |
| 208 | break; | |
| 209 | } else { | |
| 210 | result.id = .bracket_close; | |
| 211 | self.index += 1; | |
| 212 | self.state = .start; | |
| 213 | break; | |
| 214 | } | |
| 215 | }, | |
| 216 | else => { | |
| 217 | result.id = .tag_content; | |
| 218 | self.state = .tag_name; | |
| 219 | }, | |
| 220 | }, | |
| 221 | .eof => unreachable, | |
| 222 | } | |
| 223 | } else { | |
| 224 | switch (self.state) { | |
| 225 | .start, .l_bracket, .eof => {}, | |
| 226 | else => { | |
| 227 | result.id = .invalid; | |
| 228 | }, | |
| 229 | } | |
| 230 | self.state = .eof; | |
| 231 | } | |
| 232 | result.end = self.index; | |
| 233 | return result; | |
| 234 | } | |
| 235 | ||
| 236 | const Location = struct { | |
| 237 | line: usize, | |
| 238 | column: usize, | |
| 239 | line_start: usize, | |
| 240 | line_end: usize, | |
| 241 | }; | |
| 242 | ||
| 243 | fn getTokenLocation(self: *Tokenizer, token: Token) Location { | |
| 244 | var loc = Location{ | |
| 245 | .line = 0, | |
| 246 | .column = 0, | |
| 247 | .line_start = 0, | |
| 248 | .line_end = 0, | |
| 249 | }; | |
| 250 | for (self.buffer, 0..) |c, i| { | |
| 251 | if (i == token.start) { | |
| 252 | loc.line_end = i; | |
| 253 | while (loc.line_end < self.buffer.len and self.buffer[loc.line_end] != '\n') : (loc.line_end += 1) {} | |
| 254 | return loc; | |
| 255 | } | |
| 256 | if (c == '\n') { | |
| 257 | loc.line += 1; | |
| 258 | loc.column = 0; | |
| 259 | loc.line_start = i + 1; | |
| 260 | } else { | |
| 261 | loc.column += 1; | |
| 262 | } | |
| 263 | } | |
| 264 | return loc; | |
| 265 | } | |
| 266 | }; | |
| 267 | ||
| 268 | fn parseError(tokenizer: *Tokenizer, token: Token, comptime fmt: []const u8, args: anytype) anyerror { | |
| 269 | const loc = tokenizer.getTokenLocation(token); | |
| 270 | const args_prefix = .{ tokenizer.source_file_name, loc.line + 1, loc.column + 1 }; | |
| 271 | print("{s}:{d}:{d}: error: " ++ fmt ++ "\n", args_prefix ++ args); | |
| 272 | if (loc.line_start <= loc.line_end) { | |
| 273 | print("{s}\n", .{tokenizer.buffer[loc.line_start..loc.line_end]}); | |
| 274 | { | |
| 275 | var i: usize = 0; | |
| 276 | while (i < loc.column) : (i += 1) { | |
| 277 | print(" ", .{}); | |
| 278 | } | |
| 279 | } | |
| 280 | { | |
| 281 | const caret_count = @min(token.end, loc.line_end) - token.start; | |
| 282 | var i: usize = 0; | |
| 283 | while (i < caret_count) : (i += 1) { | |
| 284 | print("~", .{}); | |
| 285 | } | |
| 286 | } | |
| 287 | print("\n", .{}); | |
| 288 | } | |
| 289 | return error.ParseError; | |
| 290 | } | |
| 291 | ||
| 292 | fn assertToken(tokenizer: *Tokenizer, token: Token, id: Token.Id) !void { | |
| 293 | if (token.id != id) { | |
| 294 | return parseError(tokenizer, token, "expected {s}, found {s}", .{ @tagName(id), @tagName(token.id) }); | |
| 295 | } | |
| 296 | } | |
| 297 | ||
| 298 | fn eatToken(tokenizer: *Tokenizer, id: Token.Id) !Token { | |
| 299 | const token = tokenizer.next(); | |
| 300 | try assertToken(tokenizer, token, id); | |
| 301 | return token; | |
| 302 | } | |
| 303 | ||
| 304 | const HeaderOpen = struct { | |
| 305 | name: []const u8, | |
| 306 | url: []const u8, | |
| 307 | n: usize, | |
| 308 | }; | |
| 309 | ||
| 310 | const SeeAlsoItem = struct { | |
| 311 | name: []const u8, | |
| 312 | token: Token, | |
| 313 | }; | |
| 314 | ||
| 315 | const ExpectedOutcome = enum { | |
| 316 | succeed, | |
| 317 | fail, | |
| 318 | build_fail, | |
| 319 | }; | |
| 320 | ||
| 321 | const Code = struct { | |
| 322 | id: Id, | |
| 323 | name: []const u8, | |
| 324 | source_token: Token, | |
| 325 | just_check_syntax: bool, | |
| 326 | mode: std.builtin.Mode, | |
| 327 | link_objects: []const []const u8, | |
| 328 | target_str: ?[]const u8, | |
| 329 | link_libc: bool, | |
| 330 | link_mode: ?std.builtin.LinkMode, | |
| 331 | disable_cache: bool, | |
| 332 | verbose_cimport: bool, | |
| 333 | additional_options: []const []const u8, | |
| 334 | ||
| 335 | const Id = union(enum) { | |
| 336 | @"test", | |
| 337 | test_error: []const u8, | |
| 338 | test_safety: []const u8, | |
| 339 | exe: ExpectedOutcome, | |
| 340 | obj: ?[]const u8, | |
| 341 | lib, | |
| 342 | }; | |
| 343 | }; | |
| 344 | ||
| 345 | const Link = struct { | |
| 346 | url: []const u8, | |
| 347 | name: []const u8, | |
| 348 | token: Token, | |
| 349 | }; | |
| 350 | ||
| 351 | const SyntaxBlock = struct { | |
| 352 | source_type: SourceType, | |
| 353 | name: []const u8, | |
| 354 | source_token: Token, | |
| 355 | ||
| 356 | const SourceType = enum { | |
| 357 | zig, | |
| 358 | c, | |
| 359 | peg, | |
| 360 | javascript, | |
| 361 | }; | |
| 362 | }; | |
| 363 | ||
| 364 | const Node = union(enum) { | |
| 365 | Content: []const u8, | |
| 366 | Nav, | |
| 367 | Builtin: Token, | |
| 368 | HeaderOpen: HeaderOpen, | |
| 369 | SeeAlso: []const SeeAlsoItem, | |
| 370 | Code: Code, | |
| 371 | Link: Link, | |
| 372 | InlineSyntax: Token, | |
| 373 | Shell: Token, | |
| 374 | SyntaxBlock: SyntaxBlock, | |
| 375 | }; | |
| 376 | ||
| 377 | const Toc = struct { | |
| 378 | nodes: []Node, | |
| 379 | toc: []u8, | |
| 380 | urls: std.StringHashMap(Token), | |
| 381 | }; | |
| 382 | ||
| 383 | const Action = enum { | |
| 384 | open, | |
| 385 | close, | |
| 386 | }; | |
| 387 | ||
| 388 | fn genToc(allocator: Allocator, tokenizer: *Tokenizer) !Toc { | |
| 389 | var urls = std.StringHashMap(Token).init(allocator); | |
| 390 | errdefer urls.deinit(); | |
| 391 | ||
| 392 | var header_stack_size: usize = 0; | |
| 393 | var last_action: Action = .open; | |
| 394 | var last_columns: ?u8 = null; | |
| 395 | ||
| 396 | var toc_buf = std.ArrayList(u8).init(allocator); | |
| 397 | defer toc_buf.deinit(); | |
| 398 | ||
| 399 | var toc = toc_buf.writer(); | |
| 400 | ||
| 401 | var nodes = std.ArrayList(Node).init(allocator); | |
| 402 | defer nodes.deinit(); | |
| 403 | ||
| 404 | try toc.writeByte('\n'); | |
| 405 | ||
| 406 | while (true) { | |
| 407 | const token = tokenizer.next(); | |
| 408 | switch (token.id) { | |
| 409 | .eof => { | |
| 410 | if (header_stack_size != 0) { | |
| 411 | return parseError(tokenizer, token, "unbalanced headers", .{}); | |
| 412 | } | |
| 413 | try toc.writeAll(" </ul>\n"); | |
| 414 | break; | |
| 415 | }, | |
| 416 | .content => { | |
| 417 | try nodes.append(Node{ .Content = tokenizer.buffer[token.start..token.end] }); | |
| 418 | }, | |
| 419 | .bracket_open => { | |
| 420 | const tag_token = try eatToken(tokenizer, .tag_content); | |
| 421 | const tag_name = tokenizer.buffer[tag_token.start..tag_token.end]; | |
| 422 | ||
| 423 | if (mem.eql(u8, tag_name, "nav")) { | |
| 424 | _ = try eatToken(tokenizer, .bracket_close); | |
| 425 | ||
| 426 | try nodes.append(Node.Nav); | |
| 427 | } else if (mem.eql(u8, tag_name, "builtin")) { | |
| 428 | _ = try eatToken(tokenizer, .bracket_close); | |
| 429 | try nodes.append(Node{ .Builtin = tag_token }); | |
| 430 | } else if (mem.eql(u8, tag_name, "header_open")) { | |
| 431 | _ = try eatToken(tokenizer, .separator); | |
| 432 | const content_token = try eatToken(tokenizer, .tag_content); | |
| 433 | const content = tokenizer.buffer[content_token.start..content_token.end]; | |
| 434 | var columns: ?u8 = null; | |
| 435 | while (true) { | |
| 436 | const bracket_tok = tokenizer.next(); | |
| 437 | switch (bracket_tok.id) { | |
| 438 | .bracket_close => break, | |
| 439 | .separator => continue, | |
| 440 | .tag_content => { | |
| 441 | const param = tokenizer.buffer[bracket_tok.start..bracket_tok.end]; | |
| 442 | if (mem.eql(u8, param, "2col")) { | |
| 443 | columns = 2; | |
| 444 | } else { | |
| 445 | return parseError( | |
| 446 | tokenizer, | |
| 447 | bracket_tok, | |
| 448 | "unrecognized header_open param: {s}", | |
| 449 | .{param}, | |
| 450 | ); | |
| 451 | } | |
| 452 | }, | |
| 453 | else => return parseError(tokenizer, bracket_tok, "invalid header_open token", .{}), | |
| 454 | } | |
| 455 | } | |
| 456 | ||
| 457 | header_stack_size += 1; | |
| 458 | ||
| 459 | const urlized = try urlize(allocator, content); | |
| 460 | try nodes.append(Node{ | |
| 461 | .HeaderOpen = HeaderOpen{ | |
| 462 | .name = content, | |
| 463 | .url = urlized, | |
| 464 | .n = header_stack_size + 1, // highest-level section headers start at h2 | |
| 465 | }, | |
| 466 | }); | |
| 467 | if (try urls.fetchPut(urlized, tag_token)) |kv| { | |
| 468 | parseError(tokenizer, tag_token, "duplicate header url: #{s}", .{urlized}) catch {}; | |
| 469 | parseError(tokenizer, kv.value, "other tag here", .{}) catch {}; | |
| 470 | return error.ParseError; | |
| 471 | } | |
| 472 | if (last_action == .open) { | |
| 473 | try toc.writeByte('\n'); | |
| 474 | try toc.writeByteNTimes(' ', header_stack_size * 4); | |
| 475 | if (last_columns) |n| { | |
| 476 | try toc.print("<ul style=\"columns: {}\">\n", .{n}); | |
| 477 | } else { | |
| 478 | try toc.writeAll("<ul>\n"); | |
| 479 | } | |
| 480 | } else { | |
| 481 | last_action = .open; | |
| 482 | } | |
| 483 | last_columns = columns; | |
| 484 | try toc.writeByteNTimes(' ', 4 + header_stack_size * 4); | |
| 485 | try toc.print("<li><a id=\"toc-{s}\" href=\"#{s}\">{s}</a>", .{ urlized, urlized, content }); | |
| 486 | } else if (mem.eql(u8, tag_name, "header_close")) { | |
| 487 | if (header_stack_size == 0) { | |
| 488 | return parseError(tokenizer, tag_token, "unbalanced close header", .{}); | |
| 489 | } | |
| 490 | header_stack_size -= 1; | |
| 491 | _ = try eatToken(tokenizer, .bracket_close); | |
| 492 | ||
| 493 | if (last_action == .close) { | |
| 494 | try toc.writeByteNTimes(' ', 8 + header_stack_size * 4); | |
| 495 | try toc.writeAll("</ul></li>\n"); | |
| 496 | } else { | |
| 497 | try toc.writeAll("</li>\n"); | |
| 498 | last_action = .close; | |
| 499 | } | |
| 500 | } else if (mem.eql(u8, tag_name, "see_also")) { | |
| 501 | var list = std.ArrayList(SeeAlsoItem).init(allocator); | |
| 502 | errdefer list.deinit(); | |
| 503 | ||
| 504 | while (true) { | |
| 505 | const see_also_tok = tokenizer.next(); | |
| 506 | switch (see_also_tok.id) { | |
| 507 | .tag_content => { | |
| 508 | const content = tokenizer.buffer[see_also_tok.start..see_also_tok.end]; | |
| 509 | try list.append(SeeAlsoItem{ | |
| 510 | .name = content, | |
| 511 | .token = see_also_tok, | |
| 512 | }); | |
| 513 | }, | |
| 514 | .separator => {}, | |
| 515 | .bracket_close => { | |
| 516 | try nodes.append(Node{ .SeeAlso = try list.toOwnedSlice() }); | |
| 517 | break; | |
| 518 | }, | |
| 519 | else => return parseError(tokenizer, see_also_tok, "invalid see_also token", .{}), | |
| 520 | } | |
| 521 | } | |
| 522 | } else if (mem.eql(u8, tag_name, "link")) { | |
| 523 | _ = try eatToken(tokenizer, .separator); | |
| 524 | const name_tok = try eatToken(tokenizer, .tag_content); | |
| 525 | const name = tokenizer.buffer[name_tok.start..name_tok.end]; | |
| 526 | ||
| 527 | const url_name = blk: { | |
| 528 | const tok = tokenizer.next(); | |
| 529 | switch (tok.id) { | |
| 530 | .bracket_close => break :blk name, | |
| 531 | .separator => { | |
| 532 | const explicit_text = try eatToken(tokenizer, .tag_content); | |
| 533 | _ = try eatToken(tokenizer, .bracket_close); | |
| 534 | break :blk tokenizer.buffer[explicit_text.start..explicit_text.end]; | |
| 535 | }, | |
| 536 | else => return parseError(tokenizer, tok, "invalid link token", .{}), | |
| 537 | } | |
| 538 | }; | |
| 539 | ||
| 540 | try nodes.append(Node{ | |
| 541 | .Link = Link{ | |
| 542 | .url = try urlize(allocator, url_name), | |
| 543 | .name = name, | |
| 544 | .token = name_tok, | |
| 545 | }, | |
| 546 | }); | |
| 547 | } else if (mem.eql(u8, tag_name, "code_begin")) { | |
| 548 | _ = try eatToken(tokenizer, .separator); | |
| 549 | const code_kind_tok = try eatToken(tokenizer, .tag_content); | |
| 550 | _ = try eatToken(tokenizer, .separator); | |
| 551 | const name_tok = try eatToken(tokenizer, .tag_content); | |
| 552 | const name = tokenizer.buffer[name_tok.start..name_tok.end]; | |
| 553 | var error_str: []const u8 = ""; | |
| 554 | const maybe_sep = tokenizer.next(); | |
| 555 | switch (maybe_sep.id) { | |
| 556 | .separator => { | |
| 557 | const error_tok = try eatToken(tokenizer, .tag_content); | |
| 558 | error_str = tokenizer.buffer[error_tok.start..error_tok.end]; | |
| 559 | _ = try eatToken(tokenizer, .bracket_close); | |
| 560 | }, | |
| 561 | .bracket_close => {}, | |
| 562 | else => return parseError(tokenizer, token, "invalid token", .{}), | |
| 563 | } | |
| 564 | const code_kind_str = tokenizer.buffer[code_kind_tok.start..code_kind_tok.end]; | |
| 565 | var code_kind_id: Code.Id = undefined; | |
| 566 | var just_check_syntax = false; | |
| 567 | if (mem.eql(u8, code_kind_str, "exe")) { | |
| 568 | code_kind_id = Code.Id{ .exe = .succeed }; | |
| 569 | } else if (mem.eql(u8, code_kind_str, "exe_err")) { | |
| 570 | code_kind_id = Code.Id{ .exe = .fail }; | |
| 571 | } else if (mem.eql(u8, code_kind_str, "exe_build_err")) { | |
| 572 | code_kind_id = Code.Id{ .exe = .build_fail }; | |
| 573 | } else if (mem.eql(u8, code_kind_str, "test")) { | |
| 574 | code_kind_id = .@"test"; | |
| 575 | } else if (mem.eql(u8, code_kind_str, "test_err")) { | |
| 576 | code_kind_id = Code.Id{ .test_error = error_str }; | |
| 577 | } else if (mem.eql(u8, code_kind_str, "test_safety")) { | |
| 578 | code_kind_id = Code.Id{ .test_safety = error_str }; | |
| 579 | } else if (mem.eql(u8, code_kind_str, "obj")) { | |
| 580 | code_kind_id = Code.Id{ .obj = null }; | |
| 581 | } else if (mem.eql(u8, code_kind_str, "obj_err")) { | |
| 582 | code_kind_id = Code.Id{ .obj = error_str }; | |
| 583 | } else if (mem.eql(u8, code_kind_str, "lib")) { | |
| 584 | code_kind_id = Code.Id.lib; | |
| 585 | } else if (mem.eql(u8, code_kind_str, "syntax")) { | |
| 586 | code_kind_id = Code.Id{ .obj = null }; | |
| 587 | just_check_syntax = true; | |
| 588 | } else { | |
| 589 | return parseError(tokenizer, code_kind_tok, "unrecognized code kind: {s}", .{code_kind_str}); | |
| 590 | } | |
| 591 | ||
| 592 | var mode: std.builtin.Mode = .Debug; | |
| 593 | var link_objects = std.ArrayList([]const u8).init(allocator); | |
| 594 | defer link_objects.deinit(); | |
| 595 | var target_str: ?[]const u8 = null; | |
| 596 | var link_libc = false; | |
| 597 | var link_mode: ?std.builtin.LinkMode = null; | |
| 598 | var disable_cache = false; | |
| 599 | var verbose_cimport = false; | |
| 600 | var additional_options = std.ArrayList([]const u8).init(allocator); | |
| 601 | defer additional_options.deinit(); | |
| 602 | ||
| 603 | const source_token = while (true) { | |
| 604 | const content_tok = try eatToken(tokenizer, .content); | |
| 605 | _ = try eatToken(tokenizer, .bracket_open); | |
| 606 | const end_code_tag = try eatToken(tokenizer, .tag_content); | |
| 607 | const end_tag_name = tokenizer.buffer[end_code_tag.start..end_code_tag.end]; | |
| 608 | if (mem.eql(u8, end_tag_name, "code_release_fast")) { | |
| 609 | mode = .ReleaseFast; | |
| 610 | } else if (mem.eql(u8, end_tag_name, "code_release_safe")) { | |
| 611 | mode = .ReleaseSafe; | |
| 612 | } else if (mem.eql(u8, end_tag_name, "code_disable_cache")) { | |
| 613 | disable_cache = true; | |
| 614 | } else if (mem.eql(u8, end_tag_name, "code_verbose_cimport")) { | |
| 615 | verbose_cimport = true; | |
| 616 | } else if (mem.eql(u8, end_tag_name, "code_link_object")) { | |
| 617 | _ = try eatToken(tokenizer, .separator); | |
| 618 | const obj_tok = try eatToken(tokenizer, .tag_content); | |
| 619 | try link_objects.append(tokenizer.buffer[obj_tok.start..obj_tok.end]); | |
| 620 | } else if (mem.eql(u8, end_tag_name, "target_windows")) { | |
| 621 | target_str = "x86_64-windows"; | |
| 622 | } else if (mem.eql(u8, end_tag_name, "target_linux_x86_64")) { | |
| 623 | target_str = "x86_64-linux"; | |
| 624 | } else if (mem.eql(u8, end_tag_name, "target_linux_riscv64")) { | |
| 625 | target_str = "riscv64-linux"; | |
| 626 | } else if (mem.eql(u8, end_tag_name, "target_wasm")) { | |
| 627 | target_str = "wasm32-freestanding"; | |
| 628 | } else if (mem.eql(u8, end_tag_name, "target_wasi")) { | |
| 629 | target_str = "wasm32-wasi"; | |
| 630 | } else if (mem.eql(u8, end_tag_name, "link_libc")) { | |
| 631 | link_libc = true; | |
| 632 | } else if (mem.eql(u8, end_tag_name, "link_mode_dynamic")) { | |
| 633 | link_mode = .Dynamic; | |
| 634 | } else if (mem.eql(u8, end_tag_name, "additonal_option")) { | |
| 635 | _ = try eatToken(tokenizer, .separator); | |
| 636 | const option = try eatToken(tokenizer, .tag_content); | |
| 637 | try additional_options.append(tokenizer.buffer[option.start..option.end]); | |
| 638 | } else if (mem.eql(u8, end_tag_name, "code_end")) { | |
| 639 | _ = try eatToken(tokenizer, .bracket_close); | |
| 640 | break content_tok; | |
| 641 | } else { | |
| 642 | return parseError( | |
| 643 | tokenizer, | |
| 644 | end_code_tag, | |
| 645 | "invalid token inside code_begin: {s}", | |
| 646 | .{end_tag_name}, | |
| 647 | ); | |
| 648 | } | |
| 649 | _ = try eatToken(tokenizer, .bracket_close); | |
| 650 | } else unreachable; // TODO issue #707 | |
| 651 | try nodes.append(Node{ | |
| 652 | .Code = Code{ | |
| 653 | .id = code_kind_id, | |
| 654 | .name = name, | |
| 655 | .source_token = source_token, | |
| 656 | .just_check_syntax = just_check_syntax, | |
| 657 | .mode = mode, | |
| 658 | .link_objects = try link_objects.toOwnedSlice(), | |
| 659 | .target_str = target_str, | |
| 660 | .link_libc = link_libc, | |
| 661 | .link_mode = link_mode, | |
| 662 | .disable_cache = disable_cache, | |
| 663 | .verbose_cimport = verbose_cimport, | |
| 664 | .additional_options = try additional_options.toOwnedSlice(), | |
| 665 | }, | |
| 666 | }); | |
| 667 | tokenizer.code_node_count += 1; | |
| 668 | } else if (mem.eql(u8, tag_name, "syntax")) { | |
| 669 | _ = try eatToken(tokenizer, .bracket_close); | |
| 670 | const content_tok = try eatToken(tokenizer, .content); | |
| 671 | _ = try eatToken(tokenizer, .bracket_open); | |
| 672 | const end_syntax_tag = try eatToken(tokenizer, .tag_content); | |
| 673 | const end_tag_name = tokenizer.buffer[end_syntax_tag.start..end_syntax_tag.end]; | |
| 674 | if (!mem.eql(u8, end_tag_name, "endsyntax")) { | |
| 675 | return parseError( | |
| 676 | tokenizer, | |
| 677 | end_syntax_tag, | |
| 678 | "invalid token inside syntax: {s}", | |
| 679 | .{end_tag_name}, | |
| 680 | ); | |
| 681 | } | |
| 682 | _ = try eatToken(tokenizer, .bracket_close); | |
| 683 | try nodes.append(Node{ .InlineSyntax = content_tok }); | |
| 684 | } else if (mem.eql(u8, tag_name, "shell_samp")) { | |
| 685 | _ = try eatToken(tokenizer, .bracket_close); | |
| 686 | const content_tok = try eatToken(tokenizer, .content); | |
| 687 | _ = try eatToken(tokenizer, .bracket_open); | |
| 688 | const end_syntax_tag = try eatToken(tokenizer, .tag_content); | |
| 689 | const end_tag_name = tokenizer.buffer[end_syntax_tag.start..end_syntax_tag.end]; | |
| 690 | if (!mem.eql(u8, end_tag_name, "end_shell_samp")) { | |
| 691 | return parseError( | |
| 692 | tokenizer, | |
| 693 | end_syntax_tag, | |
| 694 | "invalid token inside syntax: {s}", | |
| 695 | .{end_tag_name}, | |
| 696 | ); | |
| 697 | } | |
| 698 | _ = try eatToken(tokenizer, .bracket_close); | |
| 699 | try nodes.append(Node{ .Shell = content_tok }); | |
| 700 | } else if (mem.eql(u8, tag_name, "syntax_block")) { | |
| 701 | _ = try eatToken(tokenizer, .separator); | |
| 702 | const source_type_tok = try eatToken(tokenizer, .tag_content); | |
| 703 | var name: []const u8 = "sample_code"; | |
| 704 | const maybe_sep = tokenizer.next(); | |
| 705 | switch (maybe_sep.id) { | |
| 706 | .separator => { | |
| 707 | const name_tok = try eatToken(tokenizer, .tag_content); | |
| 708 | name = tokenizer.buffer[name_tok.start..name_tok.end]; | |
| 709 | _ = try eatToken(tokenizer, .bracket_close); | |
| 710 | }, | |
| 711 | .bracket_close => {}, | |
| 712 | else => return parseError(tokenizer, token, "invalid token", .{}), | |
| 713 | } | |
| 714 | const source_type_str = tokenizer.buffer[source_type_tok.start..source_type_tok.end]; | |
| 715 | var source_type: SyntaxBlock.SourceType = undefined; | |
| 716 | if (mem.eql(u8, source_type_str, "zig")) { | |
| 717 | source_type = SyntaxBlock.SourceType.zig; | |
| 718 | } else if (mem.eql(u8, source_type_str, "c")) { | |
| 719 | source_type = SyntaxBlock.SourceType.c; | |
| 720 | } else if (mem.eql(u8, source_type_str, "peg")) { | |
| 721 | source_type = SyntaxBlock.SourceType.peg; | |
| 722 | } else if (mem.eql(u8, source_type_str, "javascript")) { | |
| 723 | source_type = SyntaxBlock.SourceType.javascript; | |
| 724 | } else { | |
| 725 | return parseError(tokenizer, source_type_tok, "unrecognized code kind: {s}", .{source_type_str}); | |
| 726 | } | |
| 727 | const source_token = while (true) { | |
| 728 | const content_tok = try eatToken(tokenizer, .content); | |
| 729 | _ = try eatToken(tokenizer, .bracket_open); | |
| 730 | const end_code_tag = try eatToken(tokenizer, .tag_content); | |
| 731 | const end_tag_name = tokenizer.buffer[end_code_tag.start..end_code_tag.end]; | |
| 732 | if (mem.eql(u8, end_tag_name, "end_syntax_block")) { | |
| 733 | _ = try eatToken(tokenizer, .bracket_close); | |
| 734 | break content_tok; | |
| 735 | } else { | |
| 736 | return parseError( | |
| 737 | tokenizer, | |
| 738 | end_code_tag, | |
| 739 | "invalid token inside code_begin: {s}", | |
| 740 | .{end_tag_name}, | |
| 741 | ); | |
| 742 | } | |
| 743 | _ = try eatToken(tokenizer, .bracket_close); | |
| 744 | }; | |
| 745 | try nodes.append(Node{ .SyntaxBlock = SyntaxBlock{ .source_type = source_type, .name = name, .source_token = source_token } }); | |
| 746 | } else { | |
| 747 | return parseError(tokenizer, tag_token, "unrecognized tag name: {s}", .{tag_name}); | |
| 748 | } | |
| 749 | }, | |
| 750 | else => return parseError(tokenizer, token, "invalid token", .{}), | |
| 751 | } | |
| 752 | } | |
| 753 | ||
| 754 | return Toc{ | |
| 755 | .nodes = try nodes.toOwnedSlice(), | |
| 756 | .toc = try toc_buf.toOwnedSlice(), | |
| 757 | .urls = urls, | |
| 758 | }; | |
| 759 | } | |
| 760 | ||
| 761 | fn urlize(allocator: Allocator, input: []const u8) ![]u8 { | |
| 762 | var buf = std.ArrayList(u8).init(allocator); | |
| 763 | defer buf.deinit(); | |
| 764 | ||
| 765 | const out = buf.writer(); | |
| 766 | for (input) |c| { | |
| 767 | switch (c) { | |
| 768 | 'a'...'z', 'A'...'Z', '_', '-', '0'...'9' => { | |
| 769 | try out.writeByte(c); | |
| 770 | }, | |
| 771 | ' ' => { | |
| 772 | try out.writeByte('-'); | |
| 773 | }, | |
| 774 | else => {}, | |
| 775 | } | |
| 776 | } | |
| 777 | return try buf.toOwnedSlice(); | |
| 778 | } | |
| 779 | ||
| 780 | fn escapeHtml(allocator: Allocator, input: []const u8) ![]u8 { | |
| 781 | var buf = std.ArrayList(u8).init(allocator); | |
| 782 | defer buf.deinit(); | |
| 783 | ||
| 784 | const out = buf.writer(); | |
| 785 | try writeEscaped(out, input); | |
| 786 | return try buf.toOwnedSlice(); | |
| 787 | } | |
| 788 | ||
| 789 | fn writeEscaped(out: anytype, input: []const u8) !void { | |
| 790 | for (input) |c| { | |
| 791 | try switch (c) { | |
| 792 | '&' => out.writeAll("&amp;"), | |
| 793 | '<' => out.writeAll("&lt;"), | |
| 794 | '>' => out.writeAll("&gt;"), | |
| 795 | '"' => out.writeAll("&quot;"), | |
| 796 | else => out.writeByte(c), | |
| 797 | }; | |
| 798 | } | |
| 799 | } | |
| 800 | ||
| 801 | // Returns true if number is in slice. | |
| 802 | fn in(slice: []const u8, number: u8) bool { | |
| 803 | for (slice) |n| { | |
| 804 | if (number == n) return true; | |
| 805 | } | |
| 806 | return false; | |
| 807 | } | |
| 808 | ||
| 809 | fn termColor(allocator: Allocator, input: []const u8) ![]u8 { | |
| 810 | // The SRG sequences generates by the Zig compiler are in the format: | |
| 811 | // ESC [ <foreground-color> ; <n> m | |
| 812 | // or | |
| 813 | // ESC [ <n> m | |
| 814 | // | |
| 815 | // where | |
| 816 | // foreground-color is 31 (red), 32 (green), 36 (cyan) | |
| 817 | // n is 0 (reset), 1 (bold), 2 (dim) | |
| 818 | // | |
| 819 | // Note that 37 (white) is currently not used by the compiler. | |
| 820 | // | |
| 821 | // See std.debug.TTY.Color. | |
| 822 | const supported_sgr_colors = [_]u8{ 31, 32, 36 }; | |
| 823 | const supported_sgr_numbers = [_]u8{ 0, 1, 2 }; | |
| 824 | ||
| 825 | var buf = std.ArrayList(u8).init(allocator); | |
| 826 | defer buf.deinit(); | |
| 827 | ||
| 828 | var out = buf.writer(); | |
| 829 | var sgr_param_start_index: usize = undefined; | |
| 830 | var sgr_num: u8 = undefined; | |
| 831 | var sgr_color: u8 = undefined; | |
| 832 | var i: usize = 0; | |
| 833 | var state: enum { | |
| 834 | start, | |
| 835 | escape, | |
| 836 | lbracket, | |
| 837 | number, | |
| 838 | after_number, | |
| 839 | arg, | |
| 840 | arg_number, | |
| 841 | expect_end, | |
| 842 | } = .start; | |
| 843 | var last_new_line: usize = 0; | |
| 844 | var open_span_count: usize = 0; | |
| 845 | while (i < input.len) : (i += 1) { | |
| 846 | const c = input[i]; | |
| 847 | switch (state) { | |
| 848 | .start => switch (c) { | |
| 849 | '\x1b' => state = .escape, | |
| 850 | '\n' => { | |
| 851 | try out.writeByte(c); | |
| 852 | last_new_line = buf.items.len; | |
| 853 | }, | |
| 854 | else => try out.writeByte(c), | |
| 855 | }, | |
| 856 | .escape => switch (c) { | |
| 857 | '[' => state = .lbracket, | |
| 858 | else => return error.UnsupportedEscape, | |
| 859 | }, | |
| 860 | .lbracket => switch (c) { | |
| 861 | '0'...'9' => { | |
| 862 | sgr_param_start_index = i; | |
| 863 | state = .number; | |
| 864 | }, | |
| 865 | else => return error.UnsupportedEscape, | |
| 866 | }, | |
| 867 | .number => switch (c) { | |
| 868 | '0'...'9' => {}, | |
| 869 | else => { | |
| 870 | sgr_num = try std.fmt.parseInt(u8, input[sgr_param_start_index..i], 10); | |
| 871 | sgr_color = 0; | |
| 872 | state = .after_number; | |
| 873 | i -= 1; | |
| 874 | }, | |
| 875 | }, | |
| 876 | .after_number => switch (c) { | |
| 877 | ';' => state = .arg, | |
| 878 | 'D' => state = .start, | |
| 879 | 'K' => { | |
| 880 | buf.items.len = last_new_line; | |
| 881 | state = .start; | |
| 882 | }, | |
| 883 | else => { | |
| 884 | state = .expect_end; | |
| 885 | i -= 1; | |
| 886 | }, | |
| 887 | }, | |
| 888 | .arg => switch (c) { | |
| 889 | '0'...'9' => { | |
| 890 | sgr_param_start_index = i; | |
| 891 | state = .arg_number; | |
| 892 | }, | |
| 893 | else => return error.UnsupportedEscape, | |
| 894 | }, | |
| 895 | .arg_number => switch (c) { | |
| 896 | '0'...'9' => {}, | |
| 897 | else => { | |
| 898 | // Keep the sequence consistent, foreground color first. | |
| 899 | // 32;1m is equivalent to 1;32m, but the latter will | |
| 900 | // generate an incorrect HTML class without notice. | |
| 901 | sgr_color = sgr_num; | |
| 902 | if (!in(&supported_sgr_colors, sgr_color)) return error.UnsupportedForegroundColor; | |
| 903 | ||
| 904 | sgr_num = try std.fmt.parseInt(u8, input[sgr_param_start_index..i], 10); | |
| 905 | if (!in(&supported_sgr_numbers, sgr_num)) return error.UnsupportedNumber; | |
| 906 | ||
| 907 | state = .expect_end; | |
| 908 | i -= 1; | |
| 909 | }, | |
| 910 | }, | |
| 911 | .expect_end => switch (c) { | |
| 912 | 'm' => { | |
| 913 | state = .start; | |
| 914 | while (open_span_count != 0) : (open_span_count -= 1) { | |
| 915 | try out.writeAll("</span>"); | |
| 916 | } | |
| 917 | if (sgr_num == 0) { | |
| 918 | if (sgr_color != 0) return error.UnsupportedColor; | |
| 919 | continue; | |
| 920 | } | |
| 921 | if (sgr_color != 0) { | |
| 922 | try out.print("<span class=\"sgr-{d}_{d}m\">", .{ sgr_color, sgr_num }); | |
| 923 | } else { | |
| 924 | try out.print("<span class=\"sgr-{d}m\">", .{sgr_num}); | |
| 925 | } | |
| 926 | open_span_count += 1; | |
| 927 | }, | |
| 928 | else => return error.UnsupportedEscape, | |
| 929 | }, | |
| 930 | } | |
| 931 | } | |
| 932 | return try buf.toOwnedSlice(); | |
| 933 | } | |
| 934 | ||
| 935 | const builtin_types = [_][]const u8{ | |
| 936 | "f16", "f32", "f64", "f80", "f128", | |
| 937 | "c_longdouble", "c_short", "c_ushort", "c_int", "c_uint", | |
| 938 | "c_long", "c_ulong", "c_longlong", "c_ulonglong", "c_char", | |
| 939 | "anyopaque", "void", "bool", "isize", "usize", | |
| 940 | "noreturn", "type", "anyerror", "comptime_int", "comptime_float", | |
| 941 | }; | |
| 942 | ||
| 943 | fn isType(name: []const u8) bool { | |
| 944 | for (builtin_types) |t| { | |
| 945 | if (mem.eql(u8, t, name)) | |
| 946 | return true; | |
| 947 | } | |
| 948 | return false; | |
| 949 | } | |
| 950 | ||
| 951 | const start_line = "<span class=\"line\">"; | |
| 952 | const end_line = "</span>"; | |
| 953 | ||
| 954 | fn writeEscapedLines(out: anytype, text: []const u8) !void { | |
| 955 | for (text) |char| { | |
| 956 | if (char == '\n') { | |
| 957 | try out.writeAll(end_line); | |
| 958 | try out.writeAll("\n"); | |
| 959 | try out.writeAll(start_line); | |
| 960 | } else { | |
| 961 | try writeEscaped(out, &[_]u8{char}); | |
| 962 | } | |
| 963 | } | |
| 964 | } | |
| 965 | ||
| 966 | fn tokenizeAndPrintRaw( | |
| 967 | allocator: Allocator, | |
| 968 | docgen_tokenizer: *Tokenizer, | |
| 969 | out: anytype, | |
| 970 | source_token: Token, | |
| 971 | raw_src: []const u8, | |
| 972 | ) !void { | |
| 973 | const src_non_terminated = mem.trim(u8, raw_src, " \n"); | |
| 974 | const src = try allocator.dupeZ(u8, src_non_terminated); | |
| 975 | ||
| 976 | try out.writeAll("<code>" ++ start_line); | |
| 977 | var tokenizer = std.zig.Tokenizer.init(src); | |
| 978 | var index: usize = 0; | |
| 979 | var next_tok_is_fn = false; | |
| 980 | while (true) { | |
| 981 | const prev_tok_was_fn = next_tok_is_fn; | |
| 982 | next_tok_is_fn = false; | |
| 983 | ||
| 984 | const token = tokenizer.next(); | |
| 985 | if (mem.indexOf(u8, src[index..token.loc.start], "//")) |comment_start_off| { | |
| 986 | // render one comment | |
| 987 | const comment_start = index + comment_start_off; | |
| 988 | const comment_end_off = mem.indexOf(u8, src[comment_start..token.loc.start], "\n"); | |
| 989 | const comment_end = if (comment_end_off) |o| comment_start + o else token.loc.start; | |
| 990 | ||
| 991 | try writeEscapedLines(out, src[index..comment_start]); | |
| 992 | try out.writeAll("<span class=\"tok-comment\">"); | |
| 993 | try writeEscaped(out, src[comment_start..comment_end]); | |
| 994 | try out.writeAll("</span>"); | |
| 995 | index = comment_end; | |
| 996 | tokenizer.index = index; | |
| 997 | continue; | |
| 998 | } | |
| 999 | ||
| 1000 | try writeEscapedLines(out, src[index..token.loc.start]); | |
| 1001 | switch (token.tag) { | |
| 1002 | .eof => break, | |
| 1003 | ||
| 1004 | .keyword_addrspace, | |
| 1005 | .keyword_align, | |
| 1006 | .keyword_and, | |
| 1007 | .keyword_asm, | |
| 1008 | .keyword_async, | |
| 1009 | .keyword_await, | |
| 1010 | .keyword_break, | |
| 1011 | .keyword_catch, | |
| 1012 | .keyword_comptime, | |
| 1013 | .keyword_const, | |
| 1014 | .keyword_continue, | |
| 1015 | .keyword_defer, | |
| 1016 | .keyword_else, | |
| 1017 | .keyword_enum, | |
| 1018 | .keyword_errdefer, | |
| 1019 | .keyword_error, | |
| 1020 | .keyword_export, | |
| 1021 | .keyword_extern, | |
| 1022 | .keyword_for, | |
| 1023 | .keyword_if, | |
| 1024 | .keyword_inline, | |
| 1025 | .keyword_noalias, | |
| 1026 | .keyword_noinline, | |
| 1027 | .keyword_nosuspend, | |
| 1028 | .keyword_opaque, | |
| 1029 | .keyword_or, | |
| 1030 | .keyword_orelse, | |
| 1031 | .keyword_packed, | |
| 1032 | .keyword_anyframe, | |
| 1033 | .keyword_pub, | |
| 1034 | .keyword_resume, | |
| 1035 | .keyword_return, | |
| 1036 | .keyword_linksection, | |
| 1037 | .keyword_callconv, | |
| 1038 | .keyword_struct, | |
| 1039 | .keyword_suspend, | |
| 1040 | .keyword_switch, | |
| 1041 | .keyword_test, | |
| 1042 | .keyword_threadlocal, | |
| 1043 | .keyword_try, | |
| 1044 | .keyword_union, | |
| 1045 | .keyword_unreachable, | |
| 1046 | .keyword_usingnamespace, | |
| 1047 | .keyword_var, | |
| 1048 | .keyword_volatile, | |
| 1049 | .keyword_allowzero, | |
| 1050 | .keyword_while, | |
| 1051 | .keyword_anytype, | |
| 1052 | => { | |
| 1053 | try out.writeAll("<span class=\"tok-kw\">"); | |
| 1054 | try writeEscaped(out, src[token.loc.start..token.loc.end]); | |
| 1055 | try out.writeAll("</span>"); | |
| 1056 | }, | |
| 1057 | ||
| 1058 | .keyword_fn => { | |
| 1059 | try out.writeAll("<span class=\"tok-kw\">"); | |
| 1060 | try writeEscaped(out, src[token.loc.start..token.loc.end]); | |
| 1061 | try out.writeAll("</span>"); | |
| 1062 | next_tok_is_fn = true; | |
| 1063 | }, | |
| 1064 | ||
| 1065 | .string_literal, | |
| 1066 | .char_literal, | |
| 1067 | => { | |
| 1068 | try out.writeAll("<span class=\"tok-str\">"); | |
| 1069 | try writeEscaped(out, src[token.loc.start..token.loc.end]); | |
| 1070 | try out.writeAll("</span>"); | |
| 1071 | }, | |
| 1072 | ||
| 1073 | .multiline_string_literal_line => { | |
| 1074 | if (src[token.loc.end - 1] == '\n') { | |
| 1075 | try out.writeAll("<span class=\"tok-str\">"); | |
| 1076 | try writeEscaped(out, src[token.loc.start .. token.loc.end - 1]); | |
| 1077 | try out.writeAll("</span>" ++ end_line ++ "\n" ++ start_line); | |
| 1078 | } else { | |
| 1079 | try out.writeAll("<span class=\"tok-str\">"); | |
| 1080 | try writeEscaped(out, src[token.loc.start..token.loc.end]); | |
| 1081 | try out.writeAll("</span>"); | |
| 1082 | } | |
| 1083 | }, | |
| 1084 | ||
| 1085 | .builtin => { | |
| 1086 | try out.writeAll("<span class=\"tok-builtin\">"); | |
| 1087 | try writeEscaped(out, src[token.loc.start..token.loc.end]); | |
| 1088 | try out.writeAll("</span>"); | |
| 1089 | }, | |
| 1090 | ||
| 1091 | .doc_comment, | |
| 1092 | .container_doc_comment, | |
| 1093 | => { | |
| 1094 | try out.writeAll("<span class=\"tok-comment\">"); | |
| 1095 | try writeEscaped(out, src[token.loc.start..token.loc.end]); | |
| 1096 | try out.writeAll("</span>"); | |
| 1097 | }, | |
| 1098 | ||
| 1099 | .identifier => { | |
| 1100 | const tok_bytes = src[token.loc.start..token.loc.end]; | |
| 1101 | if (mem.eql(u8, tok_bytes, "undefined") or | |
| 1102 | mem.eql(u8, tok_bytes, "null") or | |
| 1103 | mem.eql(u8, tok_bytes, "true") or | |
| 1104 | mem.eql(u8, tok_bytes, "false")) | |
| 1105 | { | |
| 1106 | try out.writeAll("<span class=\"tok-null\">"); | |
| 1107 | try writeEscaped(out, tok_bytes); | |
| 1108 | try out.writeAll("</span>"); | |
| 1109 | } else if (prev_tok_was_fn) { | |
| 1110 | try out.writeAll("<span class=\"tok-fn\">"); | |
| 1111 | try writeEscaped(out, tok_bytes); | |
| 1112 | try out.writeAll("</span>"); | |
| 1113 | } else { | |
| 1114 | const is_int = blk: { | |
| 1115 | if (src[token.loc.start] != 'i' and src[token.loc.start] != 'u') | |
| 1116 | break :blk false; | |
| 1117 | var i = token.loc.start + 1; | |
| 1118 | if (i == token.loc.end) | |
| 1119 | break :blk false; | |
| 1120 | while (i != token.loc.end) : (i += 1) { | |
| 1121 | if (src[i] < '0' or src[i] > '9') | |
| 1122 | break :blk false; | |
| 1123 | } | |
| 1124 | break :blk true; | |
| 1125 | }; | |
| 1126 | if (is_int or isType(tok_bytes)) { | |
| 1127 | try out.writeAll("<span class=\"tok-type\">"); | |
| 1128 | try writeEscaped(out, tok_bytes); | |
| 1129 | try out.writeAll("</span>"); | |
| 1130 | } else { | |
| 1131 | try writeEscaped(out, tok_bytes); | |
| 1132 | } | |
| 1133 | } | |
| 1134 | }, | |
| 1135 | ||
| 1136 | .number_literal => { | |
| 1137 | try out.writeAll("<span class=\"tok-number\">"); | |
| 1138 | try writeEscaped(out, src[token.loc.start..token.loc.end]); | |
| 1139 | try out.writeAll("</span>"); | |
| 1140 | }, | |
| 1141 | ||
| 1142 | .bang, | |
| 1143 | .pipe, | |
| 1144 | .pipe_pipe, | |
| 1145 | .pipe_equal, | |
| 1146 | .equal, | |
| 1147 | .equal_equal, | |
| 1148 | .equal_angle_bracket_right, | |
| 1149 | .bang_equal, | |
| 1150 | .l_paren, | |
| 1151 | .r_paren, | |
| 1152 | .semicolon, | |
| 1153 | .percent, | |
| 1154 | .percent_equal, | |
| 1155 | .l_brace, | |
| 1156 | .r_brace, | |
| 1157 | .l_bracket, | |
| 1158 | .r_bracket, | |
| 1159 | .period, | |
| 1160 | .period_asterisk, | |
| 1161 | .ellipsis2, | |
| 1162 | .ellipsis3, | |
| 1163 | .caret, | |
| 1164 | .caret_equal, | |
| 1165 | .plus, | |
| 1166 | .plus_plus, | |
| 1167 | .plus_equal, | |
| 1168 | .plus_percent, | |
| 1169 | .plus_percent_equal, | |
| 1170 | .plus_pipe, | |
| 1171 | .plus_pipe_equal, | |
| 1172 | .minus, | |
| 1173 | .minus_equal, | |
| 1174 | .minus_percent, | |
| 1175 | .minus_percent_equal, | |
| 1176 | .minus_pipe, | |
| 1177 | .minus_pipe_equal, | |
| 1178 | .asterisk, | |
| 1179 | .asterisk_equal, | |
| 1180 | .asterisk_asterisk, | |
| 1181 | .asterisk_percent, | |
| 1182 | .asterisk_percent_equal, | |
| 1183 | .asterisk_pipe, | |
| 1184 | .asterisk_pipe_equal, | |
| 1185 | .arrow, | |
| 1186 | .colon, | |
| 1187 | .slash, | |
| 1188 | .slash_equal, | |
| 1189 | .comma, | |
| 1190 | .ampersand, | |
| 1191 | .ampersand_equal, | |
| 1192 | .question_mark, | |
| 1193 | .angle_bracket_left, | |
| 1194 | .angle_bracket_left_equal, | |
| 1195 | .angle_bracket_angle_bracket_left, | |
| 1196 | .angle_bracket_angle_bracket_left_equal, | |
| 1197 | .angle_bracket_angle_bracket_left_pipe, | |
| 1198 | .angle_bracket_angle_bracket_left_pipe_equal, | |
| 1199 | .angle_bracket_right, | |
| 1200 | .angle_bracket_right_equal, | |
| 1201 | .angle_bracket_angle_bracket_right, | |
| 1202 | .angle_bracket_angle_bracket_right_equal, | |
| 1203 | .tilde, | |
| 1204 | => try writeEscaped(out, src[token.loc.start..token.loc.end]), | |
| 1205 | ||
| 1206 | .invalid, .invalid_periodasterisks => return parseError( | |
| 1207 | docgen_tokenizer, | |
| 1208 | source_token, | |
| 1209 | "syntax error", | |
| 1210 | .{}, | |
| 1211 | ), | |
| 1212 | } | |
| 1213 | index = token.loc.end; | |
| 1214 | } | |
| 1215 | try out.writeAll(end_line ++ "</code>"); | |
| 1216 | } | |
| 1217 | ||
| 1218 | fn tokenizeAndPrint( | |
| 1219 | allocator: Allocator, | |
| 1220 | docgen_tokenizer: *Tokenizer, | |
| 1221 | out: anytype, | |
| 1222 | source_token: Token, | |
| 1223 | ) !void { | |
| 1224 | const raw_src = docgen_tokenizer.buffer[source_token.start..source_token.end]; | |
| 1225 | return tokenizeAndPrintRaw(allocator, docgen_tokenizer, out, source_token, raw_src); | |
| 1226 | } | |
| 1227 | ||
| 1228 | fn printSourceBlock(allocator: Allocator, docgen_tokenizer: *Tokenizer, out: anytype, syntax_block: SyntaxBlock) !void { | |
| 1229 | const source_type = @tagName(syntax_block.source_type); | |
| 1230 | ||
| 1231 | try out.print("<figure><figcaption class=\"{s}-cap\"><cite class=\"file\">{s}</cite></figcaption><pre>", .{ source_type, syntax_block.name }); | |
| 1232 | switch (syntax_block.source_type) { | |
| 1233 | .zig => try tokenizeAndPrint(allocator, docgen_tokenizer, out, syntax_block.source_token), | |
| 1234 | else => { | |
| 1235 | const raw_source = docgen_tokenizer.buffer[syntax_block.source_token.start..syntax_block.source_token.end]; | |
| 1236 | const trimmed_raw_source = mem.trim(u8, raw_source, " \n"); | |
| 1237 | ||
| 1238 | try out.writeAll("<code>" ++ start_line); | |
| 1239 | try writeEscapedLines(out, trimmed_raw_source); | |
| 1240 | try out.writeAll(end_line ++ "</code>"); | |
| 1241 | }, | |
| 1242 | } | |
| 1243 | try out.writeAll("</pre></figure>"); | |
| 1244 | } | |
| 1245 | ||
| 1246 | fn printShell(out: anytype, shell_content: []const u8, escape: bool) !void { | |
| 1247 | const trimmed_shell_content = mem.trim(u8, shell_content, " \n"); | |
| 1248 | try out.writeAll("<figure><figcaption class=\"shell-cap\">Shell</figcaption><pre><samp>"); | |
| 1249 | var cmd_cont: bool = false; | |
| 1250 | var iter = std.mem.splitScalar(u8, trimmed_shell_content, '\n'); | |
| 1251 | while (iter.next()) |orig_line| { | |
| 1252 | const line = mem.trimRight(u8, orig_line, " "); | |
| 1253 | if (!cmd_cont and line.len > 1 and mem.eql(u8, line[0..2], "$ ") and line[line.len - 1] != '\\') { | |
| 1254 | try out.writeAll("$ <kbd>"); | |
| 1255 | const s = std.mem.trimLeft(u8, line[1..], " "); | |
| 1256 | if (escape) { | |
| 1257 | try writeEscaped(out, s); | |
| 1258 | } else { | |
| 1259 | try out.writeAll(s); | |
| 1260 | } | |
| 1261 | try out.writeAll("</kbd>" ++ "\n"); | |
| 1262 | } else if (!cmd_cont and line.len > 1 and mem.eql(u8, line[0..2], "$ ") and line[line.len - 1] == '\\') { | |
| 1263 | try out.writeAll("$ <kbd>"); | |
| 1264 | const s = std.mem.trimLeft(u8, line[1..], " "); | |
| 1265 | if (escape) { | |
| 1266 | try writeEscaped(out, s); | |
| 1267 | } else { | |
| 1268 | try out.writeAll(s); | |
| 1269 | } | |
| 1270 | try out.writeAll("\n"); | |
| 1271 | cmd_cont = true; | |
| 1272 | } else if (line.len > 0 and line[line.len - 1] != '\\' and cmd_cont) { | |
| 1273 | if (escape) { | |
| 1274 | try writeEscaped(out, line); | |
| 1275 | } else { | |
| 1276 | try out.writeAll(line); | |
| 1277 | } | |
| 1278 | try out.writeAll("</kbd>" ++ "\n"); | |
| 1279 | cmd_cont = false; | |
| 1280 | } else { | |
| 1281 | if (escape) { | |
| 1282 | try writeEscaped(out, line); | |
| 1283 | } else { | |
| 1284 | try out.writeAll(line); | |
| 1285 | } | |
| 1286 | try out.writeAll("\n"); | |
| 1287 | } | |
| 1288 | } | |
| 1289 | ||
| 1290 | try out.writeAll("</samp></pre></figure>"); | |
| 1291 | } | |
| 1292 | ||
| 1293 | // Override this to skip to later tests | |
| 1294 | const debug_start_line = 0; | |
| 1295 | ||
| 1296 | fn genHtml( | |
| 1297 | allocator: Allocator, | |
| 1298 | tokenizer: *Tokenizer, | |
| 1299 | toc: *Toc, | |
| 1300 | out: anytype, | |
| 1301 | zig_exe: []const u8, | |
| 1302 | opt_zig_lib_dir: ?[]const u8, | |
| 1303 | do_code_tests: bool, | |
| 1304 | ) !void { | |
| 1305 | var progress = Progress{ .dont_print_on_dumb = true }; | |
| 1306 | const root_node = progress.start("Generating docgen examples", toc.nodes.len); | |
| 1307 | defer root_node.end(); | |
| 1308 | ||
| 1309 | var env_map = try process.getEnvMap(allocator); | |
| 1310 | try env_map.put("YES_COLOR", "1"); | |
| 1311 | ||
| 1312 | const host = try std.zig.system.NativeTargetInfo.detect(.{}); | |
| 1313 | const builtin_code = try getBuiltinCode(allocator, &env_map, zig_exe, opt_zig_lib_dir); | |
| 1314 | ||
| 1315 | for (toc.nodes) |node| { | |
| 1316 | defer root_node.completeOne(); | |
| 1317 | switch (node) { | |
| 1318 | .Content => |data| { | |
| 1319 | try out.writeAll(data); | |
| 1320 | }, | |
| 1321 | .Link => |info| { | |
| 1322 | if (!toc.urls.contains(info.url)) { | |
| 1323 | return parseError(tokenizer, info.token, "url not found: {s}", .{info.url}); | |
| 1324 | } | |
| 1325 | try out.print("<a href=\"#{s}\">{s}</a>", .{ info.url, info.name }); | |
| 1326 | }, | |
| 1327 | .Nav => { | |
| 1328 | try out.writeAll(toc.toc); | |
| 1329 | }, | |
| 1330 | .Builtin => |tok| { | |
| 1331 | try out.writeAll("<figure><figcaption class=\"zig-cap\"><cite>@import(\"builtin\")</cite></figcaption><pre>"); | |
| 1332 | try tokenizeAndPrintRaw(allocator, tokenizer, out, tok, builtin_code); | |
| 1333 | try out.writeAll("</pre></figure>"); | |
| 1334 | }, | |
| 1335 | .HeaderOpen => |info| { | |
| 1336 | try out.print( | |
| 1337 | "<h{d} id=\"{s}\"><a href=\"#toc-{s}\">{s}</a> <a class=\"hdr\" href=\"#{s}\">§</a></h{d}>\n", | |
| 1338 | .{ info.n, info.url, info.url, info.name, info.url, info.n }, | |
| 1339 | ); | |
| 1340 | }, | |
| 1341 | .SeeAlso => |items| { | |
| 1342 | try out.writeAll("<p>See also:</p><ul>\n"); | |
| 1343 | for (items) |item| { | |
| 1344 | const url = try urlize(allocator, item.name); | |
| 1345 | if (!toc.urls.contains(url)) { | |
| 1346 | return parseError(tokenizer, item.token, "url not found: {s}", .{url}); | |
| 1347 | } | |
| 1348 | try out.print("<li><a href=\"#{s}\">{s}</a></li>\n", .{ url, item.name }); | |
| 1349 | } | |
| 1350 | try out.writeAll("</ul>\n"); | |
| 1351 | }, | |
| 1352 | .InlineSyntax => |content_tok| { | |
| 1353 | try tokenizeAndPrint(allocator, tokenizer, out, content_tok); | |
| 1354 | }, | |
| 1355 | .Shell => |content_tok| { | |
| 1356 | const raw_shell_content = tokenizer.buffer[content_tok.start..content_tok.end]; | |
| 1357 | try printShell(out, raw_shell_content, true); | |
| 1358 | }, | |
| 1359 | .SyntaxBlock => |syntax_block| { | |
| 1360 | try printSourceBlock(allocator, tokenizer, out, syntax_block); | |
| 1361 | }, | |
| 1362 | .Code => |code| { | |
| 1363 | const name_plus_ext = try std.fmt.allocPrint(allocator, "{s}.zig", .{code.name}); | |
| 1364 | const syntax_block = SyntaxBlock{ | |
| 1365 | .source_type = .zig, | |
| 1366 | .name = name_plus_ext, | |
| 1367 | .source_token = code.source_token, | |
| 1368 | }; | |
| 1369 | ||
| 1370 | try printSourceBlock(allocator, tokenizer, out, syntax_block); | |
| 1371 | ||
| 1372 | if (!do_code_tests) { | |
| 1373 | continue; | |
| 1374 | } | |
| 1375 | ||
| 1376 | if (debug_start_line > 0) { | |
| 1377 | const loc = tokenizer.getTokenLocation(code.source_token); | |
| 1378 | if (debug_start_line > loc.line) { | |
| 1379 | continue; | |
| 1380 | } | |
| 1381 | } | |
| 1382 | ||
| 1383 | const raw_source = tokenizer.buffer[code.source_token.start..code.source_token.end]; | |
| 1384 | const trimmed_raw_source = mem.trim(u8, raw_source, " \n"); | |
| 1385 | const tmp_source_file_name = try fs.path.join( | |
| 1386 | allocator, | |
| 1387 | &[_][]const u8{ tmp_dir_name, name_plus_ext }, | |
| 1388 | ); | |
| 1389 | try fs.cwd().writeFile(tmp_source_file_name, trimmed_raw_source); | |
| 1390 | ||
| 1391 | var shell_buffer = std.ArrayList(u8).init(allocator); | |
| 1392 | defer shell_buffer.deinit(); | |
| 1393 | var shell_out = shell_buffer.writer(); | |
| 1394 | ||
| 1395 | switch (code.id) { | |
| 1396 | .exe => |expected_outcome| code_block: { | |
| 1397 | var build_args = std.ArrayList([]const u8).init(allocator); | |
| 1398 | defer build_args.deinit(); | |
| 1399 | try build_args.appendSlice(&[_][]const u8{ | |
| 1400 | zig_exe, "build-exe", | |
| 1401 | "--name", code.name, | |
| 1402 | "--color", "on", | |
| 1403 | name_plus_ext, | |
| 1404 | }); | |
| 1405 | if (opt_zig_lib_dir) |zig_lib_dir| { | |
| 1406 | try build_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir }); | |
| 1407 | } | |
| 1408 | ||
| 1409 | try shell_out.print("$ zig build-exe {s} ", .{name_plus_ext}); | |
| 1410 | ||
| 1411 | switch (code.mode) { | |
| 1412 | .Debug => {}, | |
| 1413 | else => { | |
| 1414 | try build_args.appendSlice(&[_][]const u8{ "-O", @tagName(code.mode) }); | |
| 1415 | try shell_out.print("-O {s} ", .{@tagName(code.mode)}); | |
| 1416 | }, | |
| 1417 | } | |
| 1418 | for (code.link_objects) |link_object| { | |
| 1419 | const name_with_ext = try std.fmt.allocPrint(allocator, "{s}{s}", .{ link_object, obj_ext }); | |
| 1420 | try build_args.append(name_with_ext); | |
| 1421 | try shell_out.print("{s} ", .{name_with_ext}); | |
| 1422 | } | |
| 1423 | if (code.link_libc) { | |
| 1424 | try build_args.append("-lc"); | |
| 1425 | try shell_out.print("-lc ", .{}); | |
| 1426 | } | |
| 1427 | const target = try std.zig.CrossTarget.parse(.{ | |
| 1428 | .arch_os_abi = code.target_str orelse "native", | |
| 1429 | }); | |
| 1430 | if (code.target_str) |triple| { | |
| 1431 | try build_args.appendSlice(&[_][]const u8{ "-target", triple }); | |
| 1432 | try shell_out.print("-target {s} ", .{triple}); | |
| 1433 | } | |
| 1434 | if (code.verbose_cimport) { | |
| 1435 | try build_args.append("--verbose-cimport"); | |
| 1436 | try shell_out.print("--verbose-cimport ", .{}); | |
| 1437 | } | |
| 1438 | for (code.additional_options) |option| { | |
| 1439 | try build_args.append(option); | |
| 1440 | try shell_out.print("{s} ", .{option}); | |
| 1441 | } | |
| 1442 | ||
| 1443 | try shell_out.print("\n", .{}); | |
| 1444 | ||
| 1445 | if (expected_outcome == .build_fail) { | |
| 1446 | const result = try ChildProcess.exec(.{ | |
| 1447 | .allocator = allocator, | |
| 1448 | .argv = build_args.items, | |
| 1449 | .cwd = tmp_dir_name, | |
| 1450 | .env_map = &env_map, | |
| 1451 | .max_output_bytes = max_doc_file_size, | |
| 1452 | }); | |
| 1453 | switch (result.term) { | |
| 1454 | .Exited => |exit_code| { | |
| 1455 | if (exit_code == 0) { | |
| 1456 | progress.log("", .{}); | |
| 1457 | print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr}); | |
| 1458 | dumpArgs(build_args.items); | |
| 1459 | return parseError(tokenizer, code.source_token, "example incorrectly compiled", .{}); | |
| 1460 | } | |
| 1461 | }, | |
| 1462 | else => { | |
| 1463 | progress.log("", .{}); | |
| 1464 | print("{s}\nThe following command crashed:\n", .{result.stderr}); | |
| 1465 | dumpArgs(build_args.items); | |
| 1466 | return parseError(tokenizer, code.source_token, "example compile crashed", .{}); | |
| 1467 | }, | |
| 1468 | } | |
| 1469 | const escaped_stderr = try escapeHtml(allocator, result.stderr); | |
| 1470 | const colored_stderr = try termColor(allocator, escaped_stderr); | |
| 1471 | try shell_out.writeAll(colored_stderr); | |
| 1472 | break :code_block; | |
| 1473 | } | |
| 1474 | const exec_result = exec(allocator, &env_map, tmp_dir_name, build_args.items) catch | |
| 1475 | return parseError(tokenizer, code.source_token, "example failed to compile", .{}); | |
| 1476 | ||
| 1477 | if (code.verbose_cimport) { | |
| 1478 | const escaped_build_stderr = try escapeHtml(allocator, exec_result.stderr); | |
| 1479 | try shell_out.writeAll(escaped_build_stderr); | |
| 1480 | } | |
| 1481 | ||
| 1482 | if (code.target_str) |triple| { | |
| 1483 | if (mem.startsWith(u8, triple, "wasm32") or | |
| 1484 | mem.startsWith(u8, triple, "riscv64-linux") or | |
| 1485 | (mem.startsWith(u8, triple, "x86_64-linux") and | |
| 1486 | builtin.os.tag != .linux or builtin.cpu.arch != .x86_64)) | |
| 1487 | { | |
| 1488 | // skip execution | |
| 1489 | break :code_block; | |
| 1490 | } | |
| 1491 | } | |
| 1492 | ||
| 1493 | const path_to_exe = try std.fmt.allocPrint(allocator, "./{s}{s}", .{ | |
| 1494 | code.name, | |
| 1495 | target.exeFileExt(), | |
| 1496 | }); | |
| 1497 | const run_args = &[_][]const u8{path_to_exe}; | |
| 1498 | ||
| 1499 | var exited_with_signal = false; | |
| 1500 | ||
| 1501 | const result = if (expected_outcome == .fail) blk: { | |
| 1502 | const result = try ChildProcess.exec(.{ | |
| 1503 | .allocator = allocator, | |
| 1504 | .argv = run_args, | |
| 1505 | .env_map = &env_map, | |
| 1506 | .cwd = tmp_dir_name, | |
| 1507 | .max_output_bytes = max_doc_file_size, | |
| 1508 | }); | |
| 1509 | switch (result.term) { | |
| 1510 | .Exited => |exit_code| { | |
| 1511 | if (exit_code == 0) { | |
| 1512 | progress.log("", .{}); | |
| 1513 | print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr}); | |
| 1514 | dumpArgs(run_args); | |
| 1515 | return parseError(tokenizer, code.source_token, "example incorrectly compiled", .{}); | |
| 1516 | } | |
| 1517 | }, | |
| 1518 | .Signal => exited_with_signal = true, | |
| 1519 | else => {}, | |
| 1520 | } | |
| 1521 | break :blk result; | |
| 1522 | } else blk: { | |
| 1523 | break :blk exec(allocator, &env_map, tmp_dir_name, run_args) catch return parseError(tokenizer, code.source_token, "example crashed", .{}); | |
| 1524 | }; | |
| 1525 | ||
| 1526 | const escaped_stderr = try escapeHtml(allocator, result.stderr); | |
| 1527 | const escaped_stdout = try escapeHtml(allocator, result.stdout); | |
| 1528 | ||
| 1529 | const colored_stderr = try termColor(allocator, escaped_stderr); | |
| 1530 | const colored_stdout = try termColor(allocator, escaped_stdout); | |
| 1531 | ||
| 1532 | try shell_out.print("$ ./{s}\n{s}{s}", .{ code.name, colored_stdout, colored_stderr }); | |
| 1533 | if (exited_with_signal) { | |
| 1534 | try shell_out.print("(process terminated by signal)", .{}); | |
| 1535 | } | |
| 1536 | try shell_out.writeAll("\n"); | |
| 1537 | }, | |
| 1538 | .@"test" => { | |
| 1539 | var test_args = std.ArrayList([]const u8).init(allocator); | |
| 1540 | defer test_args.deinit(); | |
| 1541 | ||
| 1542 | try test_args.appendSlice(&[_][]const u8{ | |
| 1543 | zig_exe, "test", | |
| 1544 | tmp_source_file_name, | |
| 1545 | }); | |
| 1546 | if (opt_zig_lib_dir) |zig_lib_dir| { | |
| 1547 | try test_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir }); | |
| 1548 | } | |
| 1549 | try shell_out.print("$ zig test {s}.zig ", .{code.name}); | |
| 1550 | ||
| 1551 | switch (code.mode) { | |
| 1552 | .Debug => {}, | |
| 1553 | else => { | |
| 1554 | try test_args.appendSlice(&[_][]const u8{ | |
| 1555 | "-O", @tagName(code.mode), | |
| 1556 | }); | |
| 1557 | try shell_out.print("-O {s} ", .{@tagName(code.mode)}); | |
| 1558 | }, | |
| 1559 | } | |
| 1560 | if (code.link_libc) { | |
| 1561 | try test_args.append("-lc"); | |
| 1562 | try shell_out.print("-lc ", .{}); | |
| 1563 | } | |
| 1564 | if (code.target_str) |triple| { | |
| 1565 | try test_args.appendSlice(&[_][]const u8{ "-target", triple }); | |
| 1566 | try shell_out.print("-target {s} ", .{triple}); | |
| 1567 | ||
| 1568 | const cross_target = try std.zig.CrossTarget.parse(.{ | |
| 1569 | .arch_os_abi = triple, | |
| 1570 | }); | |
| 1571 | const target_info = try std.zig.system.NativeTargetInfo.detect( | |
| 1572 | cross_target, | |
| 1573 | ); | |
| 1574 | switch (host.getExternalExecutor(target_info, .{ | |
| 1575 | .link_libc = code.link_libc, | |
| 1576 | })) { | |
| 1577 | .native => {}, | |
| 1578 | else => { | |
| 1579 | try test_args.appendSlice(&[_][]const u8{"--test-no-exec"}); | |
| 1580 | try shell_out.writeAll("--test-no-exec"); | |
| 1581 | }, | |
| 1582 | } | |
| 1583 | } | |
| 1584 | const result = exec(allocator, &env_map, null, test_args.items) catch | |
| 1585 | return parseError(tokenizer, code.source_token, "test failed", .{}); | |
| 1586 | const escaped_stderr = try escapeHtml(allocator, result.stderr); | |
| 1587 | const escaped_stdout = try escapeHtml(allocator, result.stdout); | |
| 1588 | try shell_out.print("\n{s}{s}\n", .{ escaped_stderr, escaped_stdout }); | |
| 1589 | }, | |
| 1590 | .test_error => |error_match| { | |
| 1591 | var test_args = std.ArrayList([]const u8).init(allocator); | |
| 1592 | defer test_args.deinit(); | |
| 1593 | ||
| 1594 | try test_args.appendSlice(&[_][]const u8{ | |
| 1595 | zig_exe, "test", | |
| 1596 | "--color", "on", | |
| 1597 | tmp_source_file_name, | |
| 1598 | }); | |
| 1599 | if (opt_zig_lib_dir) |zig_lib_dir| { | |
| 1600 | try test_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir }); | |
| 1601 | } | |
| 1602 | try shell_out.print("$ zig test {s}.zig ", .{code.name}); | |
| 1603 | ||
| 1604 | switch (code.mode) { | |
| 1605 | .Debug => {}, | |
| 1606 | else => { | |
| 1607 | try test_args.appendSlice(&[_][]const u8{ "-O", @tagName(code.mode) }); | |
| 1608 | try shell_out.print("-O {s} ", .{@tagName(code.mode)}); | |
| 1609 | }, | |
| 1610 | } | |
| 1611 | if (code.link_libc) { | |
| 1612 | try test_args.append("-lc"); | |
| 1613 | try shell_out.print("-lc ", .{}); | |
| 1614 | } | |
| 1615 | const result = try ChildProcess.exec(.{ | |
| 1616 | .allocator = allocator, | |
| 1617 | .argv = test_args.items, | |
| 1618 | .env_map = &env_map, | |
| 1619 | .max_output_bytes = max_doc_file_size, | |
| 1620 | }); | |
| 1621 | switch (result.term) { | |
| 1622 | .Exited => |exit_code| { | |
| 1623 | if (exit_code == 0) { | |
| 1624 | progress.log("", .{}); | |
| 1625 | print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr}); | |
| 1626 | dumpArgs(test_args.items); | |
| 1627 | return parseError(tokenizer, code.source_token, "example incorrectly compiled", .{}); | |
| 1628 | } | |
| 1629 | }, | |
| 1630 | else => { | |
| 1631 | progress.log("", .{}); | |
| 1632 | print("{s}\nThe following command crashed:\n", .{result.stderr}); | |
| 1633 | dumpArgs(test_args.items); | |
| 1634 | return parseError(tokenizer, code.source_token, "example compile crashed", .{}); | |
| 1635 | }, | |
| 1636 | } | |
| 1637 | if (mem.indexOf(u8, result.stderr, error_match) == null) { | |
| 1638 | progress.log("", .{}); | |
| 1639 | print("{s}\nExpected to find '{s}' in stderr\n", .{ result.stderr, error_match }); | |
| 1640 | return parseError(tokenizer, code.source_token, "example did not have expected compile error", .{}); | |
| 1641 | } | |
| 1642 | const escaped_stderr = try escapeHtml(allocator, result.stderr); | |
| 1643 | const colored_stderr = try termColor(allocator, escaped_stderr); | |
| 1644 | try shell_out.print("\n{s}\n", .{colored_stderr}); | |
| 1645 | }, | |
| 1646 | .test_safety => |error_match| { | |
| 1647 | var test_args = std.ArrayList([]const u8).init(allocator); | |
| 1648 | defer test_args.deinit(); | |
| 1649 | ||
| 1650 | try test_args.appendSlice(&[_][]const u8{ | |
| 1651 | zig_exe, "test", | |
| 1652 | tmp_source_file_name, | |
| 1653 | }); | |
| 1654 | if (opt_zig_lib_dir) |zig_lib_dir| { | |
| 1655 | try test_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir }); | |
| 1656 | } | |
| 1657 | var mode_arg: []const u8 = ""; | |
| 1658 | switch (code.mode) { | |
| 1659 | .Debug => {}, | |
| 1660 | .ReleaseSafe => { | |
| 1661 | try test_args.append("-OReleaseSafe"); | |
| 1662 | mode_arg = "-OReleaseSafe"; | |
| 1663 | }, | |
| 1664 | .ReleaseFast => { | |
| 1665 | try test_args.append("-OReleaseFast"); | |
| 1666 | mode_arg = "-OReleaseFast"; | |
| 1667 | }, | |
| 1668 | .ReleaseSmall => { | |
| 1669 | try test_args.append("-OReleaseSmall"); | |
| 1670 | mode_arg = "-OReleaseSmall"; | |
| 1671 | }, | |
| 1672 | } | |
| 1673 | ||
| 1674 | const result = try ChildProcess.exec(.{ | |
| 1675 | .allocator = allocator, | |
| 1676 | .argv = test_args.items, | |
| 1677 | .env_map = &env_map, | |
| 1678 | .max_output_bytes = max_doc_file_size, | |
| 1679 | }); | |
| 1680 | switch (result.term) { | |
| 1681 | .Exited => |exit_code| { | |
| 1682 | if (exit_code == 0) { | |
| 1683 | progress.log("", .{}); | |
| 1684 | print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr}); | |
| 1685 | dumpArgs(test_args.items); | |
| 1686 | return parseError(tokenizer, code.source_token, "example test incorrectly succeeded", .{}); | |
| 1687 | } | |
| 1688 | }, | |
| 1689 | else => { | |
| 1690 | progress.log("", .{}); | |
| 1691 | print("{s}\nThe following command crashed:\n", .{result.stderr}); | |
| 1692 | dumpArgs(test_args.items); | |
| 1693 | return parseError(tokenizer, code.source_token, "example compile crashed", .{}); | |
| 1694 | }, | |
| 1695 | } | |
| 1696 | if (mem.indexOf(u8, result.stderr, error_match) == null) { | |
| 1697 | progress.log("", .{}); | |
| 1698 | print("{s}\nExpected to find '{s}' in stderr\n", .{ result.stderr, error_match }); | |
| 1699 | return parseError(tokenizer, code.source_token, "example did not have expected runtime safety error message", .{}); | |
| 1700 | } | |
| 1701 | const escaped_stderr = try escapeHtml(allocator, result.stderr); | |
| 1702 | const colored_stderr = try termColor(allocator, escaped_stderr); | |
| 1703 | try shell_out.print("$ zig test {s}.zig {s}\n{s}\n", .{ | |
| 1704 | code.name, | |
| 1705 | mode_arg, | |
| 1706 | colored_stderr, | |
| 1707 | }); | |
| 1708 | }, | |
| 1709 | .obj => |maybe_error_match| { | |
| 1710 | const name_plus_obj_ext = try std.fmt.allocPrint(allocator, "{s}{s}", .{ code.name, obj_ext }); | |
| 1711 | var build_args = std.ArrayList([]const u8).init(allocator); | |
| 1712 | defer build_args.deinit(); | |
| 1713 | ||
| 1714 | try build_args.appendSlice(&[_][]const u8{ | |
| 1715 | zig_exe, "build-obj", | |
| 1716 | "--color", "on", | |
| 1717 | "--name", code.name, | |
| 1718 | tmp_source_file_name, | |
| 1719 | try std.fmt.allocPrint(allocator, "-femit-bin={s}{c}{s}", .{ | |
| 1720 | tmp_dir_name, fs.path.sep, name_plus_obj_ext, | |
| 1721 | }), | |
| 1722 | }); | |
| 1723 | if (opt_zig_lib_dir) |zig_lib_dir| { | |
| 1724 | try build_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir }); | |
| 1725 | } | |
| 1726 | ||
| 1727 | try shell_out.print("$ zig build-obj {s}.zig ", .{code.name}); | |
| 1728 | ||
| 1729 | switch (code.mode) { | |
| 1730 | .Debug => {}, | |
| 1731 | else => { | |
| 1732 | try build_args.appendSlice(&[_][]const u8{ "-O", @tagName(code.mode) }); | |
| 1733 | try shell_out.print("-O {s} ", .{@tagName(code.mode)}); | |
| 1734 | }, | |
| 1735 | } | |
| 1736 | ||
| 1737 | if (code.target_str) |triple| { | |
| 1738 | try build_args.appendSlice(&[_][]const u8{ "-target", triple }); | |
| 1739 | try shell_out.print("-target {s} ", .{triple}); | |
| 1740 | } | |
| 1741 | for (code.additional_options) |option| { | |
| 1742 | try build_args.append(option); | |
| 1743 | try shell_out.print("{s} ", .{option}); | |
| 1744 | } | |
| 1745 | ||
| 1746 | if (maybe_error_match) |error_match| { | |
| 1747 | const result = try ChildProcess.exec(.{ | |
| 1748 | .allocator = allocator, | |
| 1749 | .argv = build_args.items, | |
| 1750 | .env_map = &env_map, | |
| 1751 | .max_output_bytes = max_doc_file_size, | |
| 1752 | }); | |
| 1753 | switch (result.term) { | |
| 1754 | .Exited => |exit_code| { | |
| 1755 | if (exit_code == 0) { | |
| 1756 | progress.log("", .{}); | |
| 1757 | print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr}); | |
| 1758 | dumpArgs(build_args.items); | |
| 1759 | return parseError(tokenizer, code.source_token, "example build incorrectly succeeded", .{}); | |
| 1760 | } | |
| 1761 | }, | |
| 1762 | else => { | |
| 1763 | progress.log("", .{}); | |
| 1764 | print("{s}\nThe following command crashed:\n", .{result.stderr}); | |
| 1765 | dumpArgs(build_args.items); | |
| 1766 | return parseError(tokenizer, code.source_token, "example compile crashed", .{}); | |
| 1767 | }, | |
| 1768 | } | |
| 1769 | if (mem.indexOf(u8, result.stderr, error_match) == null) { | |
| 1770 | progress.log("", .{}); | |
| 1771 | print("{s}\nExpected to find '{s}' in stderr\n", .{ result.stderr, error_match }); | |
| 1772 | return parseError(tokenizer, code.source_token, "example did not have expected compile error message", .{}); | |
| 1773 | } | |
| 1774 | const escaped_stderr = try escapeHtml(allocator, result.stderr); | |
| 1775 | const colored_stderr = try termColor(allocator, escaped_stderr); | |
| 1776 | try shell_out.print("\n{s} ", .{colored_stderr}); | |
| 1777 | } else { | |
| 1778 | _ = exec(allocator, &env_map, null, build_args.items) catch return parseError(tokenizer, code.source_token, "example failed to compile", .{}); | |
| 1779 | } | |
| 1780 | try shell_out.writeAll("\n"); | |
| 1781 | }, | |
| 1782 | .lib => { | |
| 1783 | const bin_basename = try std.zig.binNameAlloc(allocator, .{ | |
| 1784 | .root_name = code.name, | |
| 1785 | .target = builtin.target, | |
| 1786 | .output_mode = .Lib, | |
| 1787 | }); | |
| 1788 | ||
| 1789 | var test_args = std.ArrayList([]const u8).init(allocator); | |
| 1790 | defer test_args.deinit(); | |
| 1791 | ||
| 1792 | try test_args.appendSlice(&[_][]const u8{ | |
| 1793 | zig_exe, "build-lib", | |
| 1794 | tmp_source_file_name, | |
| 1795 | try std.fmt.allocPrint(allocator, "-femit-bin={s}{s}{s}", .{ | |
| 1796 | tmp_dir_name, fs.path.sep_str, bin_basename, | |
| 1797 | }), | |
| 1798 | }); | |
| 1799 | if (opt_zig_lib_dir) |zig_lib_dir| { | |
| 1800 | try test_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir }); | |
| 1801 | } | |
| 1802 | try shell_out.print("$ zig build-lib {s}.zig ", .{code.name}); | |
| 1803 | ||
| 1804 | switch (code.mode) { | |
| 1805 | .Debug => {}, | |
| 1806 | else => { | |
| 1807 | try test_args.appendSlice(&[_][]const u8{ "-O", @tagName(code.mode) }); | |
| 1808 | try shell_out.print("-O {s} ", .{@tagName(code.mode)}); | |
| 1809 | }, | |
| 1810 | } | |
| 1811 | if (code.target_str) |triple| { | |
| 1812 | try test_args.appendSlice(&[_][]const u8{ "-target", triple }); | |
| 1813 | try shell_out.print("-target {s} ", .{triple}); | |
| 1814 | } | |
| 1815 | if (code.link_mode) |link_mode| { | |
| 1816 | switch (link_mode) { | |
| 1817 | .Static => { | |
| 1818 | try test_args.append("-static"); | |
| 1819 | try shell_out.print("-static ", .{}); | |
| 1820 | }, | |
| 1821 | .Dynamic => { | |
| 1822 | try test_args.append("-dynamic"); | |
| 1823 | try shell_out.print("-dynamic ", .{}); | |
| 1824 | }, | |
| 1825 | } | |
| 1826 | } | |
| 1827 | for (code.additional_options) |option| { | |
| 1828 | try test_args.append(option); | |
| 1829 | try shell_out.print("{s} ", .{option}); | |
| 1830 | } | |
| 1831 | const result = exec(allocator, &env_map, null, test_args.items) catch return parseError(tokenizer, code.source_token, "test failed", .{}); | |
| 1832 | const escaped_stderr = try escapeHtml(allocator, result.stderr); | |
| 1833 | const escaped_stdout = try escapeHtml(allocator, result.stdout); | |
| 1834 | try shell_out.print("\n{s}{s}\n", .{ escaped_stderr, escaped_stdout }); | |
| 1835 | }, | |
| 1836 | } | |
| 1837 | ||
| 1838 | if (!code.just_check_syntax) { | |
| 1839 | try printShell(out, shell_buffer.items, false); | |
| 1840 | } | |
| 1841 | }, | |
| 1842 | } | |
| 1843 | } | |
| 1844 | } | |
| 1845 | ||
| 1846 | fn exec( | |
| 1847 | allocator: Allocator, | |
| 1848 | env_map: *process.EnvMap, | |
| 1849 | cwd: ?[]const u8, | |
| 1850 | args: []const []const u8, | |
| 1851 | ) !ChildProcess.ExecResult { | |
| 1852 | const result = try ChildProcess.exec(.{ | |
| 1853 | .allocator = allocator, | |
| 1854 | .argv = args, | |
| 1855 | .env_map = env_map, | |
| 1856 | .cwd = cwd, | |
| 1857 | .max_output_bytes = max_doc_file_size, | |
| 1858 | }); | |
| 1859 | switch (result.term) { | |
| 1860 | .Exited => |exit_code| { | |
| 1861 | if (exit_code != 0) { | |
| 1862 | print("{s}\nThe following command exited with code {}:\n", .{ result.stderr, exit_code }); | |
| 1863 | dumpArgs(args); | |
| 1864 | return error.ChildExitError; | |
| 1865 | } | |
| 1866 | }, | |
| 1867 | else => { | |
| 1868 | print("{s}\nThe following command crashed:\n", .{result.stderr}); | |
| 1869 | dumpArgs(args); | |
| 1870 | return error.ChildCrashed; | |
| 1871 | }, | |
| 1872 | } | |
| 1873 | return result; | |
| 1874 | } | |
| 1875 | ||
| 1876 | fn getBuiltinCode( | |
| 1877 | allocator: Allocator, | |
| 1878 | env_map: *process.EnvMap, | |
| 1879 | zig_exe: []const u8, | |
| 1880 | opt_zig_lib_dir: ?[]const u8, | |
| 1881 | ) ![]const u8 { | |
| 1882 | if (opt_zig_lib_dir) |zig_lib_dir| { | |
| 1883 | const result = try exec(allocator, env_map, null, &.{ | |
| 1884 | zig_exe, "build-obj", "--show-builtin", "--zig-lib-dir", zig_lib_dir, | |
| 1885 | }); | |
| 1886 | return result.stdout; | |
| 1887 | } else { | |
| 1888 | const result = try exec(allocator, env_map, null, &.{ | |
| 1889 | zig_exe, "build-obj", "--show-builtin", | |
| 1890 | }); | |
| 1891 | return result.stdout; | |
| 1892 | } | |
| 1893 | } | |
| 1894 | ||
| 1895 | fn dumpArgs(args: []const []const u8) void { | |
| 1896 | for (args) |arg| | |
| 1897 | print("{s} ", .{arg}) | |
| 1898 | else | |
| 1899 | print("\n", .{}); | |
| 1900 | } | |
| 1901 | ||
| 1902 | test "term supported colors" { | |
| 1903 | const test_allocator = testing.allocator; | |
| 1904 | ||
| 1905 | { | |
| 1906 | const input = "A\x1b[31;1mred\x1b[0mB"; | |
| 1907 | const expect = "A<span class=\"sgr-31_1m\">red</span>B"; | |
| 1908 | ||
| 1909 | const result = try termColor(test_allocator, input); | |
| 1910 | defer test_allocator.free(result); | |
| 1911 | try testing.expectEqualSlices(u8, expect, result); | |
| 1912 | } | |
| 1913 | ||
| 1914 | { | |
| 1915 | const input = "A\x1b[32;1mgreen\x1b[0mB"; | |
| 1916 | const expect = "A<span class=\"sgr-32_1m\">green</span>B"; | |
| 1917 | ||
| 1918 | const result = try termColor(test_allocator, input); | |
| 1919 | defer test_allocator.free(result); | |
| 1920 | try testing.expectEqualSlices(u8, expect, result); | |
| 1921 | } | |
| 1922 | ||
| 1923 | { | |
| 1924 | const input = "A\x1b[36;1mcyan\x1b[0mB"; | |
| 1925 | const expect = "A<span class=\"sgr-36_1m\">cyan</span>B"; | |
| 1926 | ||
| 1927 | const result = try termColor(test_allocator, input); | |
| 1928 | defer test_allocator.free(result); | |
| 1929 | try testing.expectEqualSlices(u8, expect, result); | |
| 1930 | } | |
| 1931 | ||
| 1932 | { | |
| 1933 | const input = "A\x1b[1mbold\x1b[0mB"; | |
| 1934 | const expect = "A<span class=\"sgr-1m\">bold</span>B"; | |
| 1935 | ||
| 1936 | const result = try termColor(test_allocator, input); | |
| 1937 | defer test_allocator.free(result); | |
| 1938 | try testing.expectEqualSlices(u8, expect, result); | |
| 1939 | } | |
| 1940 | ||
| 1941 | { | |
| 1942 | const input = "A\x1b[2mdim\x1b[0mB"; | |
| 1943 | const expect = "A<span class=\"sgr-2m\">dim</span>B"; | |
| 1944 | ||
| 1945 | const result = try termColor(test_allocator, input); | |
| 1946 | defer test_allocator.free(result); | |
| 1947 | try testing.expectEqualSlices(u8, expect, result); | |
| 1948 | } | |
| 1949 | } | |
| 1950 | ||
| 1951 | test "term output from zig" { | |
| 1952 | // Use data generated by https://github.com/perillo/zig-tty-test-data, | |
| 1953 | // with zig version 0.11.0-dev.1898+36d47dd19. | |
| 1954 | const test_allocator = testing.allocator; | |
| 1955 | ||
| 1956 | { | |
| 1957 | // 1.1-with-build-progress.out | |
| 1958 | const input = "Semantic Analysis [1324] \x1b[25D\x1b[0KLLVM Emit Object... \x1b[20D\x1b[0KLLVM Emit Object... \x1b[20D\x1b[0KLLD Link... \x1b[12D\x1b[0K"; | |
| 1959 | const expect = ""; | |
| 1960 | ||
| 1961 | const result = try termColor(test_allocator, input); | |
| 1962 | defer test_allocator.free(result); | |
| 1963 | try testing.expectEqualSlices(u8, expect, result); | |
| 1964 | } | |
| 1965 | ||
| 1966 | { | |
| 1967 | // 2.1-with-reference-traces.out | |
| 1968 | const input = "\x1b[1msrc/2.1-with-reference-traces.zig:3:7: \x1b[31;1merror: \x1b[0m\x1b[1mcannot assign to constant\n\x1b[0m x += 1;\n \x1b[32;1m~~^~~~\n\x1b[0m\x1b[0m\x1b[2mreferenced by:\n main: src/2.1-with-reference-traces.zig:7:5\n callMain: /usr/local/lib/zig/lib/std/start.zig:607:17\n remaining reference traces hidden; use '-freference-trace' to see all reference traces\n\n\x1b[0m"; | |
| 1969 | const expect = | |
| 1970 | \\<span class="sgr-1m">src/2.1-with-reference-traces.zig:3:7: </span><span class="sgr-31_1m">error: </span><span class="sgr-1m">cannot assign to constant | |
| 1971 | \\</span> x += 1; | |
| 1972 | \\ <span class="sgr-32_1m">~~^~~~ | |
| 1973 | \\</span><span class="sgr-2m">referenced by: | |
| 1974 | \\ main: src/2.1-with-reference-traces.zig:7:5 | |
| 1975 | \\ callMain: /usr/local/lib/zig/lib/std/start.zig:607:17 | |
| 1976 | \\ remaining reference traces hidden; use '-freference-trace' to see all reference traces | |
| 1977 | \\ | |
| 1978 | \\</span> | |
| 1979 | ; | |
| 1980 | ||
| 1981 | const result = try termColor(test_allocator, input); | |
| 1982 | defer test_allocator.free(result); | |
| 1983 | try testing.expectEqualSlices(u8, expect, result); | |
| 1984 | } | |
| 1985 | ||
| 1986 | { | |
| 1987 | // 2.2-without-reference-traces.out | |
| 1988 | const input = "\x1b[1m/usr/local/lib/zig/lib/std/io/fixed_buffer_stream.zig:128:29: \x1b[31;1merror: \x1b[0m\x1b[1minvalid type given to fixedBufferStream\n\x1b[0m else => @compileError(\"invalid type given to fixedBufferStream\"),\n \x1b[32;1m^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\x1b[0m\x1b[1m/usr/local/lib/zig/lib/std/io/fixed_buffer_stream.zig:116:66: \x1b[36;1mnote: \x1b[0m\x1b[1mcalled from here\n\x1b[0mpub fn fixedBufferStream(buffer: anytype) FixedBufferStream(Slice(@TypeOf(buffer))) {\n; \x1b[32;1m~~~~~^~~~~~~~~~~~~~~~~\n\x1b[0m"; | |
| 1989 | const expect = | |
| 1990 | \\<span class="sgr-1m">/usr/local/lib/zig/lib/std/io/fixed_buffer_stream.zig:128:29: </span><span class="sgr-31_1m">error: </span><span class="sgr-1m">invalid type given to fixedBufferStream | |
| 1991 | \\</span> else => @compileError("invalid type given to fixedBufferStream"), | |
| 1992 | \\ <span class="sgr-32_1m">^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| 1993 | \\</span><span class="sgr-1m">/usr/local/lib/zig/lib/std/io/fixed_buffer_stream.zig:116:66: </span><span class="sgr-36_1m">note: </span><span class="sgr-1m">called from here | |
| 1994 | \\</span>pub fn fixedBufferStream(buffer: anytype) FixedBufferStream(Slice(@TypeOf(buffer))) { | |
| 1995 | \\; <span class="sgr-32_1m">~~~~~^~~~~~~~~~~~~~~~~ | |
| 1996 | \\</span> | |
| 1997 | ; | |
| 1998 | ||
| 1999 | const result = try termColor(test_allocator, input); | |
| 2000 | defer test_allocator.free(result); | |
| 2001 | try testing.expectEqualSlices(u8, expect, result); | |
| 2002 | } | |
| 2003 | ||
| 2004 | { | |
| 2005 | // 2.3-with-notes.out | |
| 2006 | const input = "\x1b[1msrc/2.3-with-notes.zig:6:9: \x1b[31;1merror: \x1b[0m\x1b[1mexpected type '*2.3-with-notes.Derp', found '*2.3-with-notes.Wat'\n\x1b[0m bar(w);\n \x1b[32;1m^\n\x1b[0m\x1b[1msrc/2.3-with-notes.zig:6:9: \x1b[36;1mnote: \x1b[0m\x1b[1mpointer type child '2.3-with-notes.Wat' cannot cast into pointer type child '2.3-with-notes.Derp'\n\x1b[0m\x1b[1msrc/2.3-with-notes.zig:2:13: \x1b[36;1mnote: \x1b[0m\x1b[1mopaque declared here\n\x1b[0mconst Wat = opaque {};\n \x1b[32;1m^~~~~~~~~\n\x1b[0m\x1b[1msrc/2.3-with-notes.zig:1:14: \x1b[36;1mnote: \x1b[0m\x1b[1mopaque declared here\n\x1b[0mconst Derp = opaque {};\n \x1b[32;1m^~~~~~~~~\n\x1b[0m\x1b[1msrc/2.3-with-notes.zig:4:18: \x1b[36;1mnote: \x1b[0m\x1b[1mparameter type declared here\n\x1b[0mextern fn bar(d: *Derp) void;\n \x1b[32;1m^~~~~\n\x1b[0m\x1b[0m\x1b[2mreferenced by:\n main: src/2.3-with-notes.zig:10:5\n callMain: /usr/local/lib/zig/lib/std/start.zig:607:17\n remaining reference traces hidden; use '-freference-trace' to see all reference traces\n\n\x1b[0m"; | |
| 2007 | const expect = | |
| 2008 | \\<span class="sgr-1m">src/2.3-with-notes.zig:6:9: </span><span class="sgr-31_1m">error: </span><span class="sgr-1m">expected type '*2.3-with-notes.Derp', found '*2.3-with-notes.Wat' | |
| 2009 | \\</span> bar(w); | |
| 2010 | \\ <span class="sgr-32_1m">^ | |
| 2011 | \\</span><span class="sgr-1m">src/2.3-with-notes.zig:6:9: </span><span class="sgr-36_1m">note: </span><span class="sgr-1m">pointer type child '2.3-with-notes.Wat' cannot cast into pointer type child '2.3-with-notes.Derp' | |
| 2012 | \\</span><span class="sgr-1m">src/2.3-with-notes.zig:2:13: </span><span class="sgr-36_1m">note: </span><span class="sgr-1m">opaque declared here | |
| 2013 | \\</span>const Wat = opaque {}; | |
| 2014 | \\ <span class="sgr-32_1m">^~~~~~~~~ | |
| 2015 | \\</span><span class="sgr-1m">src/2.3-with-notes.zig:1:14: </span><span class="sgr-36_1m">note: </span><span class="sgr-1m">opaque declared here | |
| 2016 | \\</span>const Derp = opaque {}; | |
| 2017 | \\ <span class="sgr-32_1m">^~~~~~~~~ | |
| 2018 | \\</span><span class="sgr-1m">src/2.3-with-notes.zig:4:18: </span><span class="sgr-36_1m">note: </span><span class="sgr-1m">parameter type declared here | |
| 2019 | \\</span>extern fn bar(d: *Derp) void; | |
| 2020 | \\ <span class="sgr-32_1m">^~~~~ | |
| 2021 | \\</span><span class="sgr-2m">referenced by: | |
| 2022 | \\ main: src/2.3-with-notes.zig:10:5 | |
| 2023 | \\ callMain: /usr/local/lib/zig/lib/std/start.zig:607:17 | |
| 2024 | \\ remaining reference traces hidden; use '-freference-trace' to see all reference traces | |
| 2025 | \\ | |
| 2026 | \\</span> | |
| 2027 | ; | |
| 2028 | ||
| 2029 | const result = try termColor(test_allocator, input); | |
| 2030 | defer test_allocator.free(result); | |
| 2031 | try testing.expectEqualSlices(u8, expect, result); | |
| 2032 | } | |
| 2033 | ||
| 2034 | { | |
| 2035 | // 3.1-with-error-return-traces.out | |
| 2036 | ||
| 2037 | const input = "error: Error\n\x1b[1m/home/zig/src/3.1-with-error-return-traces.zig:5:5\x1b[0m: \x1b[2m0x20b008 in callee (3.1-with-error-return-traces)\x1b[0m\n return error.Error;\n \x1b[32;1m^\x1b[0m\n\x1b[1m/home/zig/src/3.1-with-error-return-traces.zig:9:5\x1b[0m: \x1b[2m0x20b113 in caller (3.1-with-error-return-traces)\x1b[0m\n try callee();\n \x1b[32;1m^\x1b[0m\n\x1b[1m/home/zig/src/3.1-with-error-return-traces.zig:13:5\x1b[0m: \x1b[2m0x20b153 in main (3.1-with-error-return-traces)\x1b[0m\n try caller();\n \x1b[32;1m^\x1b[0m\n"; | |
| 2038 | const expect = | |
| 2039 | \\error: Error | |
| 2040 | \\<span class="sgr-1m">/home/zig/src/3.1-with-error-return-traces.zig:5:5</span>: <span class="sgr-2m">0x20b008 in callee (3.1-with-error-return-traces)</span> | |
| 2041 | \\ return error.Error; | |
| 2042 | \\ <span class="sgr-32_1m">^</span> | |
| 2043 | \\<span class="sgr-1m">/home/zig/src/3.1-with-error-return-traces.zig:9:5</span>: <span class="sgr-2m">0x20b113 in caller (3.1-with-error-return-traces)</span> | |
| 2044 | \\ try callee(); | |
| 2045 | \\ <span class="sgr-32_1m">^</span> | |
| 2046 | \\<span class="sgr-1m">/home/zig/src/3.1-with-error-return-traces.zig:13:5</span>: <span class="sgr-2m">0x20b153 in main (3.1-with-error-return-traces)</span> | |
| 2047 | \\ try caller(); | |
| 2048 | \\ <span class="sgr-32_1m">^</span> | |
| 2049 | \\ | |
| 2050 | ; | |
| 2051 | ||
| 2052 | const result = try termColor(test_allocator, input); | |
| 2053 | defer test_allocator.free(result); | |
| 2054 | try testing.expectEqualSlices(u8, expect, result); | |
| 2055 | } | |
| 2056 | ||
| 2057 | { | |
| 2058 | // 3.2-with-stack-trace.out | |
| 2059 | const input = "\x1b[1m/usr/local/lib/zig/lib/std/debug.zig:561:19\x1b[0m: \x1b[2m0x22a107 in writeCurrentStackTrace__anon_5898 (3.2-with-stack-trace)\x1b[0m\n while (it.next()) |return_address| {\n \x1b[32;1m^\x1b[0m\n\x1b[1m/usr/local/lib/zig/lib/std/debug.zig:157:80\x1b[0m: \x1b[2m0x20bb23 in dumpCurrentStackTrace (3.2-with-stack-trace)\x1b[0m\n writeCurrentStackTrace(stderr, debug_info, detectTTYConfig(io.getStdErr()), start_addr) catch |err| {\n \x1b[32;1m^\x1b[0m\n\x1b[1m/home/zig/src/3.2-with-stack-trace.zig:5:36\x1b[0m: \x1b[2m0x20d3b2 in foo (3.2-with-stack-trace)\x1b[0m\n std.debug.dumpCurrentStackTrace(null);\n \x1b[32;1m^\x1b[0m\n\x1b[1m/home/zig/src/3.2-with-stack-trace.zig:9:8\x1b[0m: \x1b[2m0x20b458 in main (3.2-with-stack-trace)\x1b[0m\n foo();\n \x1b[32;1m^\x1b[0m\n\x1b[1m/usr/local/lib/zig/lib/std/start.zig:607:22\x1b[0m: \x1b[2m0x20a965 in posixCallMainAndExit (3.2-with-stack-trace)\x1b[0m\n root.main();\n \x1b[32;1m^\x1b[0m\n\x1b[1m/usr/local/lib/zig/lib/std/start.zig:376:5\x1b[0m: \x1b[2m0x20a411 in _start (3.2-with-stack-trace)\x1b[0m\n @call(.never_inline, posixCallMainAndExit, .{});\n \x1b[32;1m^\x1b[0m\n"; | |
| 2060 | const expect = | |
| 2061 | \\<span class="sgr-1m">/usr/local/lib/zig/lib/std/debug.zig:561:19</span>: <span class="sgr-2m">0x22a107 in writeCurrentStackTrace__anon_5898 (3.2-with-stack-trace)</span> | |
| 2062 | \\ while (it.next()) |return_address| { | |
| 2063 | \\ <span class="sgr-32_1m">^</span> | |
| 2064 | \\<span class="sgr-1m">/usr/local/lib/zig/lib/std/debug.zig:157:80</span>: <span class="sgr-2m">0x20bb23 in dumpCurrentStackTrace (3.2-with-stack-trace)</span> | |
| 2065 | \\ writeCurrentStackTrace(stderr, debug_info, detectTTYConfig(io.getStdErr()), start_addr) catch |err| { | |
| 2066 | \\ <span class="sgr-32_1m">^</span> | |
| 2067 | \\<span class="sgr-1m">/home/zig/src/3.2-with-stack-trace.zig:5:36</span>: <span class="sgr-2m">0x20d3b2 in foo (3.2-with-stack-trace)</span> | |
| 2068 | \\ std.debug.dumpCurrentStackTrace(null); | |
| 2069 | \\ <span class="sgr-32_1m">^</span> | |
| 2070 | \\<span class="sgr-1m">/home/zig/src/3.2-with-stack-trace.zig:9:8</span>: <span class="sgr-2m">0x20b458 in main (3.2-with-stack-trace)</span> | |
| 2071 | \\ foo(); | |
| 2072 | \\ <span class="sgr-32_1m">^</span> | |
| 2073 | \\<span class="sgr-1m">/usr/local/lib/zig/lib/std/start.zig:607:22</span>: <span class="sgr-2m">0x20a965 in posixCallMainAndExit (3.2-with-stack-trace)</span> | |
| 2074 | \\ root.main(); | |
| 2075 | \\ <span class="sgr-32_1m">^</span> | |
| 2076 | \\<span class="sgr-1m">/usr/local/lib/zig/lib/std/start.zig:376:5</span>: <span class="sgr-2m">0x20a411 in _start (3.2-with-stack-trace)</span> | |
| 2077 | \\ @call(.never_inline, posixCallMainAndExit, .{}); | |
| 2078 | \\ <span class="sgr-32_1m">^</span> | |
| 2079 | \\ | |
| 2080 | ; | |
| 2081 | ||
| 2082 | const result = try termColor(test_allocator, input); | |
| 2083 | defer test_allocator.free(result); | |
| 2084 | try testing.expectEqualSlices(u8, expect, result); | |
| 2085 | } | |
| 2086 | } | |
| 2087 | ||
| 2088 | test "printShell" { | |
| 2089 | const test_allocator = std.testing.allocator; | |
| 2090 | ||
| 2091 | { | |
| 2092 | const shell_out = | |
| 2093 | \\$ zig build test.zig | |
| 2094 | ; | |
| 2095 | const expected = | |
| 2096 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd> | |
| 2097 | \\</samp></pre></figure> | |
| 2098 | ; | |
| 2099 | ||
| 2100 | var buffer = std.ArrayList(u8).init(test_allocator); | |
| 2101 | defer buffer.deinit(); | |
| 2102 | ||
| 2103 | try printShell(buffer.writer(), shell_out, false); | |
| 2104 | try testing.expectEqualSlices(u8, expected, buffer.items); | |
| 2105 | } | |
| 2106 | { | |
| 2107 | const shell_out = | |
| 2108 | \\$ zig build test.zig | |
| 2109 | \\build output | |
| 2110 | ; | |
| 2111 | const expected = | |
| 2112 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd> | |
| 2113 | \\build output | |
| 2114 | \\</samp></pre></figure> | |
| 2115 | ; | |
| 2116 | ||
| 2117 | var buffer = std.ArrayList(u8).init(test_allocator); | |
| 2118 | defer buffer.deinit(); | |
| 2119 | ||
| 2120 | try printShell(buffer.writer(), shell_out, false); | |
| 2121 | try testing.expectEqualSlices(u8, expected, buffer.items); | |
| 2122 | } | |
| 2123 | { | |
| 2124 | const shell_out = | |
| 2125 | \\$ zig build test.zig | |
| 2126 | \\build output | |
| 2127 | \\$ ./test | |
| 2128 | ; | |
| 2129 | const expected = | |
| 2130 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd> | |
| 2131 | \\build output | |
| 2132 | \\$ <kbd>./test</kbd> | |
| 2133 | \\</samp></pre></figure> | |
| 2134 | ; | |
| 2135 | ||
| 2136 | var buffer = std.ArrayList(u8).init(test_allocator); | |
| 2137 | defer buffer.deinit(); | |
| 2138 | ||
| 2139 | try printShell(buffer.writer(), shell_out, false); | |
| 2140 | try testing.expectEqualSlices(u8, expected, buffer.items); | |
| 2141 | } | |
| 2142 | { | |
| 2143 | const shell_out = | |
| 2144 | \\$ zig build test.zig | |
| 2145 | \\ | |
| 2146 | \\$ ./test | |
| 2147 | \\output | |
| 2148 | ; | |
| 2149 | const expected = | |
| 2150 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd> | |
| 2151 | \\ | |
| 2152 | \\$ <kbd>./test</kbd> | |
| 2153 | \\output | |
| 2154 | \\</samp></pre></figure> | |
| 2155 | ; | |
| 2156 | ||
| 2157 | var buffer = std.ArrayList(u8).init(test_allocator); | |
| 2158 | defer buffer.deinit(); | |
| 2159 | ||
| 2160 | try printShell(buffer.writer(), shell_out, false); | |
| 2161 | try testing.expectEqualSlices(u8, expected, buffer.items); | |
| 2162 | } | |
| 2163 | { | |
| 2164 | const shell_out = | |
| 2165 | \\$ zig build test.zig | |
| 2166 | \\$ ./test | |
| 2167 | \\output | |
| 2168 | ; | |
| 2169 | const expected = | |
| 2170 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd> | |
| 2171 | \\$ <kbd>./test</kbd> | |
| 2172 | \\output | |
| 2173 | \\</samp></pre></figure> | |
| 2174 | ; | |
| 2175 | ||
| 2176 | var buffer = std.ArrayList(u8).init(test_allocator); | |
| 2177 | defer buffer.deinit(); | |
| 2178 | ||
| 2179 | try printShell(buffer.writer(), shell_out, false); | |
| 2180 | try testing.expectEqualSlices(u8, expected, buffer.items); | |
| 2181 | } | |
| 2182 | { | |
| 2183 | const shell_out = | |
| 2184 | \\$ zig build test.zig \ | |
| 2185 | \\ --build-option | |
| 2186 | \\build output | |
| 2187 | \\$ ./test | |
| 2188 | \\output | |
| 2189 | ; | |
| 2190 | const expected = | |
| 2191 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig \ | |
| 2192 | \\ --build-option</kbd> | |
| 2193 | \\build output | |
| 2194 | \\$ <kbd>./test</kbd> | |
| 2195 | \\output | |
| 2196 | \\</samp></pre></figure> | |
| 2197 | ; | |
| 2198 | ||
| 2199 | var buffer = std.ArrayList(u8).init(test_allocator); | |
| 2200 | defer buffer.deinit(); | |
| 2201 | ||
| 2202 | try printShell(buffer.writer(), shell_out, false); | |
| 2203 | try testing.expectEqualSlices(u8, expected, buffer.items); | |
| 2204 | } | |
| 2205 | { | |
| 2206 | // intentional space after "--build-option1 \" | |
| 2207 | const shell_out = | |
| 2208 | \\$ zig build test.zig \ | |
| 2209 | \\ --build-option1 \ | |
| 2210 | \\ --build-option2 | |
| 2211 | \\$ ./test | |
| 2212 | ; | |
| 2213 | const expected = | |
| 2214 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig \ | |
| 2215 | \\ --build-option1 \ | |
| 2216 | \\ --build-option2</kbd> | |
| 2217 | \\$ <kbd>./test</kbd> | |
| 2218 | \\</samp></pre></figure> | |
| 2219 | ; | |
| 2220 | ||
| 2221 | var buffer = std.ArrayList(u8).init(test_allocator); | |
| 2222 | defer buffer.deinit(); | |
| 2223 | ||
| 2224 | try printShell(buffer.writer(), shell_out, false); | |
| 2225 | try testing.expectEqualSlices(u8, expected, buffer.items); | |
| 2226 | } | |
| 2227 | { | |
| 2228 | const shell_out = | |
| 2229 | \\$ zig build test.zig \ | |
| 2230 | \\$ ./test | |
| 2231 | ; | |
| 2232 | const expected = | |
| 2233 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig \ | |
| 2234 | \\$ ./test</kbd> | |
| 2235 | \\</samp></pre></figure> | |
| 2236 | ; | |
| 2237 | ||
| 2238 | var buffer = std.ArrayList(u8).init(test_allocator); | |
| 2239 | defer buffer.deinit(); | |
| 2240 | ||
| 2241 | try printShell(buffer.writer(), shell_out, false); | |
| 2242 | try testing.expectEqualSlices(u8, expected, buffer.items); | |
| 2243 | } | |
| 2244 | { | |
| 2245 | const shell_out = | |
| 2246 | \\$ zig build test.zig | |
| 2247 | \\$ ./test | |
| 2248 | \\$1 | |
| 2249 | ; | |
| 2250 | const expected = | |
| 2251 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd> | |
| 2252 | \\$ <kbd>./test</kbd> | |
| 2253 | \\$1 | |
| 2254 | \\</samp></pre></figure> | |
| 2255 | ; | |
| 2256 | ||
| 2257 | var buffer = std.ArrayList(u8).init(test_allocator); | |
| 2258 | defer buffer.deinit(); | |
| 2259 | ||
| 2260 | try printShell(buffer.writer(), shell_out, false); | |
| 2261 | try testing.expectEqualSlices(u8, expected, buffer.items); | |
| 2262 | } | |
| 2263 | { | |
| 2264 | const shell_out = | |
| 2265 | \\$zig build test.zig | |
| 2266 | ; | |
| 2267 | const expected = | |
| 2268 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$zig build test.zig | |
| 2269 | \\</samp></pre></figure> | |
| 2270 | ; | |
| 2271 | ||
| 2272 | var buffer = std.ArrayList(u8).init(test_allocator); | |
| 2273 | defer buffer.deinit(); | |
| 2274 | ||
| 2275 | try printShell(buffer.writer(), shell_out, false); | |
| 2276 | try testing.expectEqualSlices(u8, expected, buffer.items); | |
| 2277 | } | |
| 2278 | } |