| 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,7 +36,7 @@ pub fn build(b: *std.Build) !void { |
| 36 | 36 | ||
| 37 | const docgen_exe = b.addExecutable(.{ | 37 | const docgen_exe = b.addExecutable(.{ |
| 38 | .name = "docgen", | 38 | .name = "docgen", |
| 39 | .root_source_file = .{ .path = "doc/docgen.zig" }, | 39 | .root_source_file = .{ .path = "tools/docgen.zig" }, |
| 40 | .target = .{}, | 40 | .target = .{}, |
| 41 | .optimize = .Debug, | 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 | } | ||