Merge pull request #19208 from ziglang/rework-autodoc

Redesign How Autodoc Works
This commit is contained in:
Andrew Kelley 2024-03-11 01:37:50 -07:00 committed by GitHub
commit d0c06ca712
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 7465 additions and 25280 deletions

5
.github/CODEOWNERS vendored
View File

@ -1,8 +1,3 @@
# Autodoc
/src/Autodoc.zig @kristoff-it
/src/autodoc/* @kristoff-it
/lib/docs/* @kristoff-it
# std.json
/lib/std/json* @thejoshwolfe

View File

@ -1,12 +0,0 @@
---
name: Autodoc Issue
about: Issues with automatically generated docs, including stdlib docs.
title: 'Autodoc: {your issue}'
labels: autodoc
assignees: kristoff-it
---
Autodoc is still work in progress and as such many bugs and missing features are already known.
# Please report only <ins>regressions</ins>, i.e. things that worked in a previous build of new Autodoc (orange banner) that now don't work any more.

View File

@ -907,8 +907,6 @@ else()
endif()
# -Dno-langref is currently hardcoded because building the langref takes too damn long
# -Dno-autodocs is currently hardcoded because the C backend generates a miscompilation
# that prevents it from working.
# To obtain these two forms of documentation, run zig build against stage3 rather than stage2.
set(ZIG_BUILD_ARGS
--zig-lib-dir "${CMAKE_SOURCE_DIR}/lib"
@ -918,7 +916,6 @@ set(ZIG_BUILD_ARGS
${ZIG_STATIC_ARG}
${ZIG_NO_LIB_ARG}
"-Dno-langref"
"-Dno-autodocs"
${ZIG_SINGLE_THREADED_ARG}
${ZIG_PIE_ARG}
"-Dtarget=${ZIG_TARGET_TRIPLE}"

View File

@ -31,7 +31,7 @@ pub fn build(b: *std.Build) !void {
const test_step = b.step("test", "Run all the tests");
const skip_install_lib_files = b.option(bool, "no-lib", "skip copying of lib/ files and langref to installation prefix. Useful for development") orelse false;
const skip_install_langref = b.option(bool, "no-langref", "skip copying of langref to the installation prefix") orelse skip_install_lib_files;
const skip_install_autodocs = b.option(bool, "no-autodocs", "skip copying of standard library autodocs to the installation prefix") orelse skip_install_lib_files;
const std_docs = b.option(bool, "std-docs", "include standard library autodocs") orelse false;
const no_bin = b.option(bool, "no-bin", "skip emitting compiler binary") orelse false;
const docgen_exe = b.addExecutable(.{
@ -55,17 +55,19 @@ pub fn build(b: *std.Build) !void {
b.getInstallStep().dependOn(&install_langref.step);
}
const autodoc_test = b.addTest(.{
const autodoc_test = b.addObject(.{
.name = "std",
.root_source_file = .{ .path = "lib/std/std.zig" },
.target = target,
.zig_lib_dir = .{ .path = "lib" },
.optimize = .Debug,
});
const install_std_docs = b.addInstallDirectory(.{
.source_dir = autodoc_test.getEmittedDocs(),
.install_dir = .prefix,
.install_subdir = "doc/std",
});
if (!skip_install_autodocs) {
if (std_docs) {
b.getInstallStep().dependOn(&install_std_docs.step);
}

384
lib/compiler/std-docs.zig Normal file
View File

@ -0,0 +1,384 @@
const builtin = @import("builtin");
const std = @import("std");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
pub fn main() !void {
var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena_instance.deinit();
const arena = arena_instance.allocator();
var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .{};
const gpa = general_purpose_allocator.allocator();
const args = try std.process.argsAlloc(arena);
const zig_lib_directory = args[1];
const zig_exe_path = args[2];
const global_cache_path = args[3];
var lib_dir = try std.fs.cwd().openDir(zig_lib_directory, .{});
defer lib_dir.close();
const listen_port: u16 = 0;
const address = std.net.Address.parseIp("127.0.0.1", listen_port) catch unreachable;
var http_server = try address.listen(.{});
const port = http_server.listen_address.in.getPort();
const url = try std.fmt.allocPrint(arena, "http://127.0.0.1:{d}/\n", .{port});
std.io.getStdOut().writeAll(url) catch {};
openBrowserTab(gpa, url[0 .. url.len - 1 :'\n']) catch |err| {
std.log.err("unable to open browser: {s}", .{@errorName(err)});
};
var context: Context = .{
.gpa = gpa,
.zig_exe_path = zig_exe_path,
.global_cache_path = global_cache_path,
.lib_dir = lib_dir,
.zig_lib_directory = zig_lib_directory,
};
while (true) {
const connection = try http_server.accept();
_ = std.Thread.spawn(.{}, accept, .{ &context, connection }) catch |err| {
std.log.err("unable to accept connection: {s}", .{@errorName(err)});
connection.stream.close();
continue;
};
}
}
fn accept(context: *Context, connection: std.net.Server.Connection) void {
defer connection.stream.close();
var read_buffer: [8000]u8 = undefined;
var server = std.http.Server.init(connection, &read_buffer);
while (server.state == .ready) {
var request = server.receiveHead() catch |err| switch (err) {
error.HttpConnectionClosing => return,
else => {
std.log.err("closing http connection: {s}", .{@errorName(err)});
return;
},
};
serveRequest(&request, context) catch |err| {
std.log.err("unable to serve {s}: {s}", .{ request.head.target, @errorName(err) });
return;
};
}
}
const Context = struct {
gpa: Allocator,
lib_dir: std.fs.Dir,
zig_lib_directory: []const u8,
zig_exe_path: []const u8,
global_cache_path: []const u8,
};
fn serveRequest(request: *std.http.Server.Request, context: *Context) !void {
if (std.mem.eql(u8, request.head.target, "/") or
std.mem.eql(u8, request.head.target, "/debug/"))
{
try serveDocsFile(request, context, "docs/index.html", "text/html");
} else if (std.mem.eql(u8, request.head.target, "/main.js") or
std.mem.eql(u8, request.head.target, "/debug/main.js"))
{
try serveDocsFile(request, context, "docs/main.js", "application/javascript");
} else if (std.mem.eql(u8, request.head.target, "/main.wasm")) {
try serveWasm(request, context, .ReleaseFast);
} else if (std.mem.eql(u8, request.head.target, "/debug/main.wasm")) {
try serveWasm(request, context, .Debug);
} else if (std.mem.eql(u8, request.head.target, "/sources.tar") or
std.mem.eql(u8, request.head.target, "/debug/sources.tar"))
{
try serveSourcesTar(request, context);
} else {
try request.respond("not found", .{
.status = .not_found,
.extra_headers = &.{
.{ .name = "content-type", .value = "text/plain" },
},
});
}
}
const cache_control_header: std.http.Header = .{
.name = "cache-control",
.value = "max-age=0, must-revalidate",
};
fn serveDocsFile(
request: *std.http.Server.Request,
context: *Context,
name: []const u8,
content_type: []const u8,
) !void {
const gpa = context.gpa;
// The desired API is actually sendfile, which will require enhancing std.http.Server.
// We load the file with every request so that the user can make changes to the file
// and refresh the HTML page without restarting this server.
const file_contents = try context.lib_dir.readFileAlloc(gpa, name, 10 * 1024 * 1024);
defer gpa.free(file_contents);
try request.respond(file_contents, .{
.extra_headers = &.{
.{ .name = "content-type", .value = content_type },
cache_control_header,
},
});
}
fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void {
const gpa = context.gpa;
var send_buffer: [0x4000]u8 = undefined;
var response = request.respondStreaming(.{
.send_buffer = &send_buffer,
.respond_options = .{
.extra_headers = &.{
.{ .name = "content-type", .value = "application/x-tar" },
cache_control_header,
},
},
});
const w = response.writer();
var std_dir = try context.lib_dir.openDir("std", .{ .iterate = true });
defer std_dir.close();
var walker = try std_dir.walk(gpa);
defer walker.deinit();
while (try walker.next()) |entry| {
switch (entry.kind) {
.file => {
if (!std.mem.endsWith(u8, entry.basename, ".zig"))
continue;
if (std.mem.endsWith(u8, entry.basename, "test.zig"))
continue;
},
else => continue,
}
var file = try std_dir.openFile(entry.path, .{});
defer file.close();
const stat = try file.stat();
const padding = p: {
const remainder = stat.size % 512;
break :p if (remainder > 0) 512 - remainder else 0;
};
var file_header = std.tar.output.Header.init();
file_header.typeflag = .regular;
try file_header.setPath("std", entry.path);
try file_header.setSize(stat.size);
try file_header.updateChecksum();
try w.writeAll(std.mem.asBytes(&file_header));
try w.writeFile(file);
try w.writeByteNTimes(0, padding);
}
// intentionally omitting the pointless trailer
//try w.writeByteNTimes(0, 512 * 2);
try response.end();
}
fn serveWasm(
request: *std.http.Server.Request,
context: *Context,
optimize_mode: std.builtin.OptimizeMode,
) !void {
const gpa = context.gpa;
var arena_instance = std.heap.ArenaAllocator.init(gpa);
defer arena_instance.deinit();
const arena = arena_instance.allocator();
// Do the compilation every request, so that the user can edit the files
// and see the changes without restarting the server.
const wasm_binary_path = try buildWasmBinary(arena, context, optimize_mode);
// std.http.Server does not have a sendfile API yet.
const file_contents = try std.fs.cwd().readFileAlloc(gpa, wasm_binary_path, 10 * 1024 * 1024);
defer gpa.free(file_contents);
try request.respond(file_contents, .{
.extra_headers = &.{
.{ .name = "content-type", .value = "application/wasm" },
cache_control_header,
},
});
}
fn buildWasmBinary(
arena: Allocator,
context: *Context,
optimize_mode: std.builtin.OptimizeMode,
) ![]const u8 {
const gpa = context.gpa;
const main_src_path = try std.fs.path.join(arena, &.{
context.zig_lib_directory, "docs", "wasm", "main.zig",
});
var argv: std.ArrayListUnmanaged([]const u8) = .{};
try argv.appendSlice(arena, &.{
context.zig_exe_path,
"build-exe",
"-fno-entry",
"-O",
@tagName(optimize_mode),
"-target",
"wasm32-freestanding",
"-mcpu",
"baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext",
"--cache-dir",
context.global_cache_path,
"--global-cache-dir",
context.global_cache_path,
"--name",
"autodoc",
"-rdynamic",
main_src_path,
"--listen=-",
});
var child = std.ChildProcess.init(argv.items, gpa);
child.stdin_behavior = .Pipe;
child.stdout_behavior = .Pipe;
child.stderr_behavior = .Pipe;
try child.spawn();
var poller = std.io.poll(gpa, enum { stdout, stderr }, .{
.stdout = child.stdout.?,
.stderr = child.stderr.?,
});
defer poller.deinit();
try sendMessage(child.stdin.?, .update);
try sendMessage(child.stdin.?, .exit);
const Header = std.zig.Server.Message.Header;
var result: ?[]const u8 = null;
var result_error_bundle = std.zig.ErrorBundle.empty;
const stdout = poller.fifo(.stdout);
poll: while (true) {
while (stdout.readableLength() < @sizeOf(Header)) {
if (!(try poller.poll())) break :poll;
}
const header = stdout.reader().readStruct(Header) catch unreachable;
while (stdout.readableLength() < header.bytes_len) {
if (!(try poller.poll())) break :poll;
}
const body = stdout.readableSliceOfLen(header.bytes_len);
switch (header.tag) {
.zig_version => {
if (!std.mem.eql(u8, builtin.zig_version_string, body)) {
return error.ZigProtocolVersionMismatch;
}
},
.error_bundle => {
const EbHdr = std.zig.Server.Message.ErrorBundle;
const eb_hdr = @as(*align(1) const EbHdr, @ptrCast(body));
const extra_bytes =
body[@sizeOf(EbHdr)..][0 .. @sizeOf(u32) * eb_hdr.extra_len];
const string_bytes =
body[@sizeOf(EbHdr) + extra_bytes.len ..][0..eb_hdr.string_bytes_len];
// TODO: use @ptrCast when the compiler supports it
const unaligned_extra = std.mem.bytesAsSlice(u32, extra_bytes);
const extra_array = try arena.alloc(u32, unaligned_extra.len);
@memcpy(extra_array, unaligned_extra);
result_error_bundle = .{
.string_bytes = try arena.dupe(u8, string_bytes),
.extra = extra_array,
};
},
.emit_bin_path => {
const EbpHdr = std.zig.Server.Message.EmitBinPath;
const ebp_hdr = @as(*align(1) const EbpHdr, @ptrCast(body));
if (!ebp_hdr.flags.cache_hit) {
std.log.info("source changes detected; rebuilt wasm component", .{});
}
result = try arena.dupe(u8, body[@sizeOf(EbpHdr)..]);
},
else => {}, // ignore other messages
}
stdout.discard(body.len);
}
const stderr = poller.fifo(.stderr);
if (stderr.readableLength() > 0) {
const owned_stderr = try stderr.toOwnedSlice();
defer gpa.free(owned_stderr);
std.debug.print("{s}", .{owned_stderr});
}
// Send EOF to stdin.
child.stdin.?.close();
child.stdin = null;
switch (try child.wait()) {
.Exited => |code| {
if (code != 0) {
std.log.err(
"the following command exited with error code {d}:\n{s}",
.{ code, try std.Build.Step.allocPrintCmd(arena, null, argv.items) },
);
return error.WasmCompilationFailed;
}
},
.Signal, .Stopped, .Unknown => {
std.log.err(
"the following command terminated unexpectedly:\n{s}",
.{try std.Build.Step.allocPrintCmd(arena, null, argv.items)},
);
return error.WasmCompilationFailed;
},
}
if (result_error_bundle.errorMessageCount() > 0) {
const color = std.zig.Color.auto;
result_error_bundle.renderToStdErr(color.renderOptions());
std.log.err("the following command failed with {d} compilation errors:\n{s}", .{
result_error_bundle.errorMessageCount(),
try std.Build.Step.allocPrintCmd(arena, null, argv.items),
});
return error.WasmCompilationFailed;
}
return result orelse {
std.log.err("child process failed to report result\n{s}", .{
try std.Build.Step.allocPrintCmd(arena, null, argv.items),
});
return error.WasmCompilationFailed;
};
}
fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void {
const header: std.zig.Client.Message.Header = .{
.tag = tag,
.bytes_len = 0,
};
try file.writeAll(std.mem.asBytes(&header));
}
fn openBrowserTab(gpa: Allocator, url: []const u8) !void {
// Until https://github.com/ziglang/zig/issues/19205 is implemented, we
// spawn a thread for this child process.
_ = try std.Thread.spawn(.{}, openBrowserTabThread, .{ gpa, url });
}
fn openBrowserTabThread(gpa: Allocator, url: []const u8) !void {
const main_exe = switch (builtin.os.tag) {
.windows => "explorer",
else => "xdg-open",
};
var child = std.ChildProcess.init(&.{ main_exe, url }, gpa);
child.stdin_behavior = .Ignore;
child.stdout_behavior = .Ignore;
child.stderr_behavior = .Ignore;
try child.spawn();
_ = try child.wait();
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

226
lib/docs/wasm/Decl.zig Normal file
View File

@ -0,0 +1,226 @@
ast_node: Ast.Node.Index,
file: Walk.File.Index,
/// The decl whose namespace this is in.
parent: Index,
pub const ExtraInfo = struct {
is_pub: bool,
name: []const u8,
/// This might not be a doc_comment token in which case there are no doc comments.
first_doc_comment: Ast.TokenIndex,
};
pub const Index = enum(u32) {
none = std.math.maxInt(u32),
_,
pub fn get(i: Index) *Decl {
return &Walk.decls.items[@intFromEnum(i)];
}
};
pub fn is_pub(d: *const Decl) bool {
return d.extra_info().is_pub;
}
pub fn extra_info(d: *const Decl) ExtraInfo {
const ast = d.file.get_ast();
const token_tags = ast.tokens.items(.tag);
const node_tags = ast.nodes.items(.tag);
switch (node_tags[d.ast_node]) {
.root => return .{
.name = "",
.is_pub = true,
.first_doc_comment = if (token_tags[0] == .container_doc_comment)
0
else
token_tags.len - 1,
},
.global_var_decl,
.local_var_decl,
.simple_var_decl,
.aligned_var_decl,
=> {
const var_decl = ast.fullVarDecl(d.ast_node).?;
const name_token = var_decl.ast.mut_token + 1;
assert(token_tags[name_token] == .identifier);
const ident_name = ast.tokenSlice(name_token);
return .{
.name = ident_name,
.is_pub = var_decl.visib_token != null,
.first_doc_comment = findFirstDocComment(ast, var_decl.firstToken()),
};
},
.fn_proto,
.fn_proto_multi,
.fn_proto_one,
.fn_proto_simple,
.fn_decl,
=> {
var buf: [1]Ast.Node.Index = undefined;
const fn_proto = ast.fullFnProto(&buf, d.ast_node).?;
const name_token = fn_proto.name_token.?;
assert(token_tags[name_token] == .identifier);
const ident_name = ast.tokenSlice(name_token);
return .{
.name = ident_name,
.is_pub = fn_proto.visib_token != null,
.first_doc_comment = findFirstDocComment(ast, fn_proto.firstToken()),
};
},
else => |t| {
log.debug("hit '{s}'", .{@tagName(t)});
unreachable;
},
}
}
pub fn value_node(d: *const Decl) ?Ast.Node.Index {
const ast = d.file.get_ast();
const node_tags = ast.nodes.items(.tag);
const token_tags = ast.tokens.items(.tag);
return switch (node_tags[d.ast_node]) {
.fn_proto,
.fn_proto_multi,
.fn_proto_one,
.fn_proto_simple,
.fn_decl,
.root,
=> d.ast_node,
.global_var_decl,
.local_var_decl,
.simple_var_decl,
.aligned_var_decl,
=> {
const var_decl = ast.fullVarDecl(d.ast_node).?;
if (token_tags[var_decl.ast.mut_token] == .keyword_const)
return var_decl.ast.init_node;
return null;
},
else => null,
};
}
pub fn categorize(decl: *const Decl) Walk.Category {
return decl.file.categorize_decl(decl.ast_node);
}
/// Looks up a direct child of `decl` by name.
pub fn get_child(decl: *const Decl, name: []const u8) ?Decl.Index {
switch (decl.categorize()) {
.alias => |aliasee| return aliasee.get().get_child(name),
.namespace => |node| {
const file = decl.file.get();
const scope = file.scopes.get(node) orelse return null;
const child_node = scope.get_child(name) orelse return null;
return file.node_decls.get(child_node);
},
else => return null,
}
}
/// Looks up a decl by name accessible in `decl`'s namespace.
pub fn lookup(decl: *const Decl, name: []const u8) ?Decl.Index {
const namespace_node = switch (decl.categorize()) {
.namespace => |node| node,
else => decl.parent.get().ast_node,
};
const file = decl.file.get();
const scope = file.scopes.get(namespace_node) orelse return null;
const resolved_node = scope.lookup(&file.ast, name) orelse return null;
return file.node_decls.get(resolved_node);
}
/// Appends the fully qualified name to `out`.
pub fn fqn(decl: *const Decl, out: *std.ArrayListUnmanaged(u8)) Oom!void {
try decl.append_path(out);
if (decl.parent != .none) {
try append_parent_ns(out, decl.parent);
try out.appendSlice(gpa, decl.extra_info().name);
} else {
out.items.len -= 1; // remove the trailing '.'
}
}
pub fn reset_with_path(decl: *const Decl, list: *std.ArrayListUnmanaged(u8)) Oom!void {
list.clearRetainingCapacity();
try append_path(decl, list);
}
pub fn append_path(decl: *const Decl, list: *std.ArrayListUnmanaged(u8)) Oom!void {
const start = list.items.len;
// Prefer the module name alias.
for (Walk.modules.keys(), Walk.modules.values()) |pkg_name, pkg_file| {
if (pkg_file == decl.file) {
try list.ensureUnusedCapacity(gpa, pkg_name.len + 1);
list.appendSliceAssumeCapacity(pkg_name);
list.appendAssumeCapacity('.');
return;
}
}
const file_path = decl.file.path();
try list.ensureUnusedCapacity(gpa, file_path.len + 1);
list.appendSliceAssumeCapacity(file_path);
for (list.items[start..]) |*byte| switch (byte.*) {
'/' => byte.* = '.',
else => continue,
};
if (std.mem.endsWith(u8, list.items, ".zig")) {
list.items.len -= 3;
} else {
list.appendAssumeCapacity('.');
}
}
pub fn append_parent_ns(list: *std.ArrayListUnmanaged(u8), parent: Decl.Index) Oom!void {
assert(parent != .none);
const decl = parent.get();
if (decl.parent != .none) {
try append_parent_ns(list, decl.parent);
try list.appendSlice(gpa, decl.extra_info().name);
try list.append(gpa, '.');
}
}
pub fn findFirstDocComment(ast: *const Ast, token: Ast.TokenIndex) Ast.TokenIndex {
const token_tags = ast.tokens.items(.tag);
var it = token;
while (it > 0) {
it -= 1;
if (token_tags[it] != .doc_comment) {
return it + 1;
}
}
return it;
}
/// Successively looks up each component.
pub fn find(search_string: []const u8) Decl.Index {
var path_components = std.mem.splitScalar(u8, search_string, '.');
const file = Walk.modules.get(path_components.first()) orelse return .none;
var current_decl_index = file.findRootDecl();
while (path_components.next()) |component| {
while (true) switch (current_decl_index.get().categorize()) {
.alias => |aliasee| current_decl_index = aliasee,
else => break,
};
current_decl_index = current_decl_index.get().get_child(component) orelse return .none;
}
return current_decl_index;
}
const Decl = @This();
const std = @import("std");
const Ast = std.zig.Ast;
const Walk = @import("Walk.zig");
const gpa = std.heap.wasm_allocator;
const assert = std.debug.assert;
const log = std.log;
const Oom = error{OutOfMemory};

1122
lib/docs/wasm/Walk.zig Normal file

File diff suppressed because it is too large Load Diff

1259
lib/docs/wasm/main.zig Normal file

File diff suppressed because it is too large Load Diff

940
lib/docs/wasm/markdown.zig Normal file
View File

@ -0,0 +1,940 @@
//! Markdown parsing and rendering support.
//!
//! A Markdown document consists of a series of blocks. Depending on its type,
//! each block may contain other blocks, inline content, or nothing. The
//! supported blocks are as follows:
//!
//! - **List** - a sequence of list items of the same type.
//!
//! - **List item** - unordered list items start with `-`, `*`, or `+` followed
//! by a space. Ordered list items start with a number between 0 and
//! 999,999,999, followed by a `.` or `)` and a space. The number of an
//! ordered list item only matters for the first item in the list (to
//! determine the starting number of the list). All subsequent ordered list
//! items will have sequentially increasing numbers.
//!
//! All list items may contain block content. Any content indented at least as
//! far as the end of the list item marker (including the space after it) is
//! considered part of the list item.
//!
//! Lists which have no blank lines between items or between direct children
//! of items are considered _tight_, and direct child paragraphs of tight list
//! items are rendered without `<p>` tags.
//!
//! - **Table** - a sequence of adjacent table row lines, where each line starts
//! and ends with a `|`, and cells within the row are delimited by `|`s.
//!
//! The first or second row of a table may be a _header delimiter row_, which
//! is a row consisting of cells of the pattern `---` (for unset column
//! alignment), `:--` (for left alignment), `:-:` (for center alignment), or
//! `--:` (for right alignment). The number of `-`s must be at least one, but
//! is otherwise arbitrary. If there is a row just before the header delimiter
//! row, it becomes the header row for the table (a table need not have a
//! header row at all).
//!
//! - **Heading** - a sequence of between 1 and 6 `#` characters, followed by a
//! space and further inline content on the same line.
//!
//! - **Code block** - a sequence of at least 3 `` ` `` characters (a _fence_),
//! optionally followed by a "tag" on the same line, and continuing until a
//! line consisting only of a closing fence whose length matches the opening
//! fence, or until the end of the containing block.
//!
//! The content of a code block is not parsed as inline content. It is
//! included verbatim in the output document (minus leading indentation up to
//! the position of the opening fence).
//!
//! - **Blockquote** - a sequence of lines preceded by `>` characters.
//!
//! - **Paragraph** - ordinary text, parsed as inline content, ending with a
//! blank line or the end of the containing block.
//!
//! Paragraphs which are part of another block may be "lazily" continued by
//! subsequent paragraph lines even if those lines would not ordinarily be
//! considered part of the containing block. For example, this is a single
//! list item, not a list item followed by a paragraph:
//!
//! ```markdown
//! - First line of content.
//! This content is still part of the paragraph,
//! even though it isn't indented far enough.
//! ```
//!
//! - **Thematic break** - a line consisting of at least three matching `-`,
//! `_`, or `*` characters and, optionally, spaces.
//!
//! Indentation may consist of spaces and tabs. The use of tabs is not
//! recommended: a tab is treated the same as a single space for the purpose of
//! determining the indentation level, and is not recognized as a space for
//! block starters which require one (for example, `-` followed by a tab is not
//! a valid list item).
//!
//! The supported inlines are as follows:
//!
//! - **Link** - of the format `[text](target)`. `text` may contain inline
//! content. `target` may contain `\`-escaped characters and balanced
//! parentheses.
//!
//! - **Image** - a link directly preceded by a `!`. The link text is
//! interpreted as the alt text of the image.
//!
//! - **Emphasis** - a run of `*` or `_` characters may be an emphasis opener,
//! closer, or both. For `*` characters, the run may be an opener as long as
//! it is not directly followed by a whitespace character (or the end of the
//! inline content) and a closer as long as it is not directly preceded by
//! one. For `_` characters, this rule is strengthened by requiring that the
//! run also be preceded by a whitespace or punctuation character (for
//! openers) or followed by one (for closers), to avoid mangling `snake_case`
//! words.
//!
//! The rule for emphasis handling is greedy: any run that can close existing
//! emphasis will do so, otherwise it will open emphasis. A single run may
//! serve both functions: the middle `**` in the following example both closes
//! the initial emphasis and opens a new one:
//!
//! ```markdown
//! *one**two*
//! ```
//!
//! A single `*` or `_` is used for normal emphasis (HTML `<em>`), and a
//! double `**` or `__` is used for strong emphasis (HTML `<strong>`). Even
//! longer runs may be used to produce further nested emphasis (though only
//! `***` and `___` to produce `<em><strong>` is really useful).
//!
//! - **Code span** - a run of `` ` `` characters, terminated by a matching run
//! or the end of inline content. The content of a code span is not parsed
//! further.
//!
//! - **Text** - normal text is interpreted as-is, except that `\` may be used
//! to escape any punctuation character, preventing it from being interpreted
//! according to other syntax rules. A `\` followed by a line break within a
//! paragraph is interpreted as a hard line break.
//!
//! Any null bytes or invalid UTF-8 bytes within text are replaced with Unicode
//! replacement characters, `U+FFFD`.
const std = @import("std");
const testing = std.testing;
pub const Document = @import("markdown/Document.zig");
pub const Parser = @import("markdown/Parser.zig");
pub const Renderer = @import("markdown/renderer.zig").Renderer;
pub const renderNodeInlineText = @import("markdown/renderer.zig").renderNodeInlineText;
pub const fmtHtml = @import("markdown/renderer.zig").fmtHtml;
// Avoid exposing main to other files merely importing this one.
pub const main = if (@import("root") == @This())
mainImpl
else
@compileError("only available as root source file");
fn mainImpl() !void {
const gpa = std.heap.c_allocator;
var parser = try Parser.init(gpa);
defer parser.deinit();
var stdin_buf = std.io.bufferedReader(std.io.getStdIn().reader());
var line_buf = std.ArrayList(u8).init(gpa);
defer line_buf.deinit();
while (stdin_buf.reader().streamUntilDelimiter(line_buf.writer(), '\n', null)) {
if (line_buf.getLastOrNull() == '\r') _ = line_buf.pop();
try parser.feedLine(line_buf.items);
line_buf.clearRetainingCapacity();
} else |err| switch (err) {
error.EndOfStream => {},
else => |e| return e,
}
var doc = try parser.endInput();
defer doc.deinit(gpa);
var stdout_buf = std.io.bufferedWriter(std.io.getStdOut().writer());
try doc.render(stdout_buf.writer());
try stdout_buf.flush();
}
test "empty document" {
try testRender("", "");
try testRender(" ", "");
try testRender("\n \n\t\n \n", "");
}
test "unordered lists" {
try testRender(
\\- Spam
\\- Spam
\\- Spam
\\- Eggs
\\- Bacon
\\- Spam
\\
\\* Spam
\\* Spam
\\* Spam
\\* Eggs
\\* Bacon
\\* Spam
\\
\\+ Spam
\\+ Spam
\\+ Spam
\\+ Eggs
\\+ Bacon
\\+ Spam
\\
,
\\<ul>
\\<li>Spam</li>
\\<li>Spam</li>
\\<li>Spam</li>
\\<li>Eggs</li>
\\<li>Bacon</li>
\\<li>Spam</li>
\\</ul>
\\<ul>
\\<li>Spam</li>
\\<li>Spam</li>
\\<li>Spam</li>
\\<li>Eggs</li>
\\<li>Bacon</li>
\\<li>Spam</li>
\\</ul>
\\<ul>
\\<li>Spam</li>
\\<li>Spam</li>
\\<li>Spam</li>
\\<li>Eggs</li>
\\<li>Bacon</li>
\\<li>Spam</li>
\\</ul>
\\
);
}
test "ordered lists" {
try testRender(
\\1. Breakfast
\\2. Second breakfast
\\3. Lunch
\\2. Afternoon snack
\\1. Dinner
\\6. Dessert
\\7. Midnight snack
\\
\\1) Breakfast
\\2) Second breakfast
\\3) Lunch
\\2) Afternoon snack
\\1) Dinner
\\6) Dessert
\\7) Midnight snack
\\
\\1001. Breakfast
\\2. Second breakfast
\\3. Lunch
\\2. Afternoon snack
\\1. Dinner
\\6. Dessert
\\7. Midnight snack
\\
\\1001) Breakfast
\\2) Second breakfast
\\3) Lunch
\\2) Afternoon snack
\\1) Dinner
\\6) Dessert
\\7) Midnight snack
\\
,
\\<ol>
\\<li>Breakfast</li>
\\<li>Second breakfast</li>
\\<li>Lunch</li>
\\<li>Afternoon snack</li>
\\<li>Dinner</li>
\\<li>Dessert</li>
\\<li>Midnight snack</li>
\\</ol>
\\<ol>
\\<li>Breakfast</li>
\\<li>Second breakfast</li>
\\<li>Lunch</li>
\\<li>Afternoon snack</li>
\\<li>Dinner</li>
\\<li>Dessert</li>
\\<li>Midnight snack</li>
\\</ol>
\\<ol start="1001">
\\<li>Breakfast</li>
\\<li>Second breakfast</li>
\\<li>Lunch</li>
\\<li>Afternoon snack</li>
\\<li>Dinner</li>
\\<li>Dessert</li>
\\<li>Midnight snack</li>
\\</ol>
\\<ol start="1001">
\\<li>Breakfast</li>
\\<li>Second breakfast</li>
\\<li>Lunch</li>
\\<li>Afternoon snack</li>
\\<li>Dinner</li>
\\<li>Dessert</li>
\\<li>Midnight snack</li>
\\</ol>
\\
);
}
test "nested lists" {
try testRender(
\\- - Item 1.
\\ - Item 2.
\\Item 2 continued.
\\ * New list.
\\
,
\\<ul>
\\<li><ul>
\\<li>Item 1.</li>
\\<li>Item 2.
\\Item 2 continued.</li>
\\</ul>
\\<ul>
\\<li>New list.</li>
\\</ul>
\\</li>
\\</ul>
\\
);
}
test "lists with block content" {
try testRender(
\\1. Item 1.
\\2. Item 2.
\\
\\ This one has another paragraph.
\\3. Item 3.
\\
\\- > Blockquote.
\\- - Sub-list.
\\ - Sub-list continued.
\\ * Different sub-list.
\\- ## Heading.
\\
\\ Some contents below the heading.
\\ 1. Item 1.
\\ 2. Item 2.
\\ 3. Item 3.
\\
,
\\<ol>
\\<li><p>Item 1.</p>
\\</li>
\\<li><p>Item 2.</p>
\\<p>This one has another paragraph.</p>
\\</li>
\\<li><p>Item 3.</p>
\\</li>
\\</ol>
\\<ul>
\\<li><blockquote>
\\<p>Blockquote.</p>
\\</blockquote>
\\</li>
\\<li><ul>
\\<li>Sub-list.</li>
\\<li>Sub-list continued.</li>
\\</ul>
\\<ul>
\\<li>Different sub-list.</li>
\\</ul>
\\</li>
\\<li><h2>Heading.</h2>
\\<p>Some contents below the heading.</p>
\\<ol>
\\<li>Item 1.</li>
\\<li>Item 2.</li>
\\<li>Item 3.</li>
\\</ol>
\\</li>
\\</ul>
\\
);
}
test "tables" {
try testRender(
\\| Operator | Meaning |
\\| :------: | ---------------- |
\\| `+` | Add |
\\| `-` | Subtract |
\\| `*` | Multiply |
\\| `/` | Divide |
\\| `??` | **Not sure yet** |
\\
\\| Item 1 | Value 1 |
\\| Item 2 | Value 2 |
\\| Item 3 | Value 3 |
\\| Item 4 | Value 4 |
\\
\\| :--- | :----: | ----: |
\\| Left | Center | Right |
\\
,
\\<table>
\\<tr>
\\<th style="text-align: center">Operator</th>
\\<th>Meaning</th>
\\</tr>
\\<tr>
\\<td style="text-align: center"><code>+</code></td>
\\<td>Add</td>
\\</tr>
\\<tr>
\\<td style="text-align: center"><code>-</code></td>
\\<td>Subtract</td>
\\</tr>
\\<tr>
\\<td style="text-align: center"><code>*</code></td>
\\<td>Multiply</td>
\\</tr>
\\<tr>
\\<td style="text-align: center"><code>/</code></td>
\\<td>Divide</td>
\\</tr>
\\<tr>
\\<td style="text-align: center"><code>??</code></td>
\\<td><strong>Not sure yet</strong></td>
\\</tr>
\\</table>
\\<table>
\\<tr>
\\<td>Item 1</td>
\\<td>Value 1</td>
\\</tr>
\\<tr>
\\<td>Item 2</td>
\\<td>Value 2</td>
\\</tr>
\\<tr>
\\<td>Item 3</td>
\\<td>Value 3</td>
\\</tr>
\\<tr>
\\<td>Item 4</td>
\\<td>Value 4</td>
\\</tr>
\\</table>
\\<table>
\\<tr>
\\<td style="text-align: left">Left</td>
\\<td style="text-align: center">Center</td>
\\<td style="text-align: right">Right</td>
\\</tr>
\\</table>
\\
);
}
test "table with uneven number of columns" {
try testRender(
\\| One |
\\| :-- | :--: |
\\| One | Two | Three |
\\
,
\\<table>
\\<tr>
\\<th style="text-align: left">One</th>
\\</tr>
\\<tr>
\\<td style="text-align: left">One</td>
\\<td style="text-align: center">Two</td>
\\<td>Three</td>
\\</tr>
\\</table>
\\
);
}
test "table with escaped pipes" {
try testRender(
\\| One \| Two |
\\| --- | --- |
\\| One \| Two |
\\
,
\\<table>
\\<tr>
\\<th>One | Two</th>
\\</tr>
\\<tr>
\\<td>One | Two</td>
\\</tr>
\\</table>
\\
);
}
test "table with pipes in code spans" {
try testRender(
\\| `|` | Bitwise _OR_ |
\\| `||` | Combines error sets |
\\| `` `||` `` | Escaped version |
\\| ` ``||`` ` | Another escaped version |
\\| `Oops unterminated code span |
\\
,
\\<table>
\\<tr>
\\<td><code>|</code></td>
\\<td>Bitwise <em>OR</em></td>
\\</tr>
\\<tr>
\\<td><code>||</code></td>
\\<td>Combines error sets</td>
\\</tr>
\\<tr>
\\<td><code>`||`</code></td>
\\<td>Escaped version</td>
\\</tr>
\\<tr>
\\<td><code>``||``</code></td>
\\<td>Another escaped version</td>
\\</tr>
\\</table>
\\<p>| <code>Oops unterminated code span |</code></p>
\\
);
}
test "tables require leading and trailing pipes" {
try testRender(
\\Not | a | table
\\
\\| But | this | is |
\\
\\Also not a table:
\\|
\\ |
\\
,
\\<p>Not | a | table</p>
\\<table>
\\<tr>
\\<td>But</td>
\\<td>this</td>
\\<td>is</td>
\\</tr>
\\</table>
\\<p>Also not a table:
\\|
\\|</p>
\\
);
}
test "headings" {
try testRender(
\\# Level one
\\## Level two
\\### Level three
\\#### Level four
\\##### Level five
\\###### Level six
\\####### Not a heading
\\
,
\\<h1>Level one</h1>
\\<h2>Level two</h2>
\\<h3>Level three</h3>
\\<h4>Level four</h4>
\\<h5>Level five</h5>
\\<h6>Level six</h6>
\\<p>####### Not a heading</p>
\\
);
}
test "headings with inline content" {
try testRender(
\\# Outline of `std.zig`
\\## **Important** notes
\\### ***Nested* inline content**
\\
,
\\<h1>Outline of <code>std.zig</code></h1>
\\<h2><strong>Important</strong> notes</h2>
\\<h3><strong><em>Nested</em> inline content</strong></h3>
\\
);
}
test "code blocks" {
try testRender(
\\```
\\Hello, world!
\\This is some code.
\\```
\\``` zig test
\\const std = @import("std");
\\
\\test {
\\ try std.testing.expect(2 + 2 == 4);
\\}
\\```
\\
,
\\<pre><code>Hello, world!
\\This is some code.
\\</code></pre>
\\<pre><code>const std = @import(&quot;std&quot;);
\\
\\test {
\\ try std.testing.expect(2 + 2 == 4);
\\}
\\</code></pre>
\\
);
}
test "blockquotes" {
try testRender(
\\> > You miss 100% of the shots you don't take.
\\> >
\\> > ~ Wayne Gretzky
\\>
\\> ~ Michael Scott
\\
,
\\<blockquote>
\\<blockquote>
\\<p>You miss 100% of the shots you don't take.</p>
\\<p>~ Wayne Gretzky</p>
\\</blockquote>
\\<p>~ Michael Scott</p>
\\</blockquote>
\\
);
}
test "blockquote lazy continuation lines" {
try testRender(
\\>>>>Deeply nested blockquote
\\>>which continues on another line
\\and then yet another one.
\\>>
\\>> But now two of them have been closed.
\\
\\And then there were none.
\\
,
\\<blockquote>
\\<blockquote>
\\<blockquote>
\\<blockquote>
\\<p>Deeply nested blockquote
\\which continues on another line
\\and then yet another one.</p>
\\</blockquote>
\\</blockquote>
\\<p>But now two of them have been closed.</p>
\\</blockquote>
\\</blockquote>
\\<p>And then there were none.</p>
\\
);
}
test "paragraphs" {
try testRender(
\\Paragraph one.
\\
\\Paragraph two.
\\Still in the paragraph.
\\ So is this.
\\
\\
\\
\\
\\ Last paragraph.
\\
,
\\<p>Paragraph one.</p>
\\<p>Paragraph two.
\\Still in the paragraph.
\\So is this.</p>
\\<p>Last paragraph.</p>
\\
);
}
test "thematic breaks" {
try testRender(
\\---
\\***
\\___
\\ ---
\\ - - - - - - - - - - -
\\
,
\\<hr />
\\<hr />
\\<hr />
\\<hr />
\\<hr />
\\
);
}
test "links" {
try testRender(
\\[Link](https://example.com)
\\[Link *with inlines*](https://example.com)
\\[Nested parens](https://example.com/nested(parens(inside)))
\\[Escaped parens](https://example.com/\)escaped\()
\\[Line break in target](test\
\\target)
\\
,
\\<p><a href="https://example.com">Link</a>
\\<a href="https://example.com">Link <em>with inlines</em></a>
\\<a href="https://example.com/nested(parens(inside))">Nested parens</a>
\\<a href="https://example.com/)escaped(">Escaped parens</a>
\\<a href="test\
\\target">Line break in target</a></p>
\\
);
}
test "images" {
try testRender(
\\![Alt text](https://example.com/image.png)
\\![Alt text *with inlines*](https://example.com/image.png)
\\![Nested parens](https://example.com/nested(parens(inside)).png)
\\![Escaped parens](https://example.com/\)escaped\(.png)
\\![Line break in target](test\
\\target)
\\
,
\\<p><img src="https://example.com/image.png" alt="Alt text" />
\\<img src="https://example.com/image.png" alt="Alt text with inlines" />
\\<img src="https://example.com/nested(parens(inside)).png" alt="Nested parens" />
\\<img src="https://example.com/)escaped(.png" alt="Escaped parens" />
\\<img src="test\
\\target" alt="Line break in target" /></p>
\\
);
}
test "emphasis" {
try testRender(
\\*Emphasis.*
\\**Strong.**
\\***Strong emphasis.***
\\****More...****
\\*****MORE...*****
\\******Even more...******
\\*******OK, this is enough.*******
\\
,
\\<p><em>Emphasis.</em>
\\<strong>Strong.</strong>
\\<em><strong>Strong emphasis.</strong></em>
\\<em><strong><em>More...</em></strong></em>
\\<em><strong><strong>MORE...</strong></strong></em>
\\<em><strong><em><strong>Even more...</strong></em></strong></em>
\\<em><strong><em><strong><em>OK, this is enough.</em></strong></em></strong></em></p>
\\
);
try testRender(
\\_Emphasis._
\\__Strong.__
\\___Strong emphasis.___
\\____More...____
\\_____MORE..._____
\\______Even more...______
\\_______OK, this is enough._______
\\
,
\\<p><em>Emphasis.</em>
\\<strong>Strong.</strong>
\\<em><strong>Strong emphasis.</strong></em>
\\<em><strong><em>More...</em></strong></em>
\\<em><strong><strong>MORE...</strong></strong></em>
\\<em><strong><em><strong>Even more...</strong></em></strong></em>
\\<em><strong><em><strong><em>OK, this is enough.</em></strong></em></strong></em></p>
\\
);
}
test "nested emphasis" {
try testRender(
\\**Hello, *world!***
\\*Hello, **world!***
\\**Hello, _world!_**
\\_Hello, **world!**_
\\*Hello, **nested** *world!**
\\***Hello,* world!**
\\__**Hello, world!**__
\\****Hello,** world!**
\\__Hello,_ world!_
\\*Test**123*
\\__Test____123__
\\
,
\\<p><strong>Hello, <em>world!</em></strong>
\\<em>Hello, <strong>world!</strong></em>
\\<strong>Hello, <em>world!</em></strong>
\\<em>Hello, <strong>world!</strong></em>
\\<em>Hello, <strong>nested</strong> <em>world!</em></em>
\\<strong><em>Hello,</em> world!</strong>
\\<strong><strong>Hello, world!</strong></strong>
\\<strong><strong>Hello,</strong> world!</strong>
\\<em><em>Hello,</em> world!</em>
\\<em>Test</em><em>123</em>
\\<strong>Test____123</strong></p>
\\
);
}
test "emphasis precedence" {
try testRender(
\\*First one _wins*_.
\\_*No other __rule matters.*_
\\
,
\\<p><em>First one _wins</em>_.
\\<em><em>No other __rule matters.</em></em></p>
\\
);
}
test "emphasis open and close" {
try testRender(
\\Cannot open: *
\\Cannot open: _
\\*Cannot close: *
\\_Cannot close: _
\\
\\foo*bar*baz
\\foo_bar_baz
\\foo**bar**baz
\\foo__bar__baz
\\
,
\\<p>Cannot open: *
\\Cannot open: _
\\*Cannot close: *
\\_Cannot close: _</p>
\\<p>foo<em>bar</em>baz
\\foo_bar_baz
\\foo<strong>bar</strong>baz
\\foo__bar__baz</p>
\\
);
}
test "code spans" {
try testRender(
\\`Hello, world!`
\\```Multiple `backticks` can be used.```
\\`**This** does not produce emphasis.`
\\`` `Backtick enclosed string.` ``
\\`Delimiter lengths ```must``` match.`
\\
\\Unterminated ``code...
\\
\\Weird empty code span: `
\\
\\**Very important code: `hi`**
\\
,
\\<p><code>Hello, world!</code>
\\<code>Multiple `backticks` can be used.</code>
\\<code>**This** does not produce emphasis.</code>
\\<code>`Backtick enclosed string.`</code>
\\<code>Delimiter lengths ```must``` match.</code></p>
\\<p>Unterminated <code>code...</code></p>
\\<p>Weird empty code span: <code></code></p>
\\<p><strong>Very important code: <code>hi</code></strong></p>
\\
);
}
test "backslash escapes" {
try testRender(
\\Not \*emphasized\*.
\\Literal \\backslashes\\.
\\Not code: \`hi\`.
\\\# Not a title.
\\#\# Also not a title.
\\\> Not a blockquote.
\\\- Not a list item.
\\\| Not a table. |
\\| Also not a table. \|
\\Any \punctuation\ characte\r can be escaped:
\\\!\"\#\$\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\@\[\\\]\^\_\`\{\|\}\~
\\
,
\\<p>Not *emphasized*.
\\Literal \backslashes\.
\\Not code: `hi`.
\\# Not a title.
\\## Also not a title.
\\&gt; Not a blockquote.
\\- Not a list item.
\\| Not a table. |
\\| Also not a table. |
\\Any \punctuation\ characte\r can be escaped:
\\!&quot;#$%&amp;'()*+,-./:;&lt;=&gt;?@[\]^_`{|}~</p>
\\
);
}
test "hard line breaks" {
try testRender(
\\The iguana sits\
\\Perched atop a short desk chair\
\\Writing code in Zig
\\
,
\\<p>The iguana sits<br />
\\Perched atop a short desk chair<br />
\\Writing code in Zig</p>
\\
);
}
test "Unicode handling" {
// Null bytes must be replaced.
try testRender("\x00\x00\x00", "<p>\u{FFFD}\u{FFFD}\u{FFFD}</p>\n");
// Invalid UTF-8 must be replaced.
try testRender("\xC0\x80\xE0\x80\x80\xF0\x80\x80\x80", "<p>\u{FFFD}\u{FFFD}\u{FFFD}</p>\n");
try testRender("\xED\xA0\x80\xED\xBF\xBF", "<p>\u{FFFD}\u{FFFD}</p>\n");
// Incomplete UTF-8 must be replaced.
try testRender("\xE2\x82", "<p>\u{FFFD}</p>\n");
}
fn testRender(input: []const u8, expected: []const u8) !void {
var parser = try Parser.init(testing.allocator);
defer parser.deinit();
var lines = std.mem.split(u8, input, "\n");
while (lines.next()) |line| {
try parser.feedLine(line);
}
var doc = try parser.endInput();
defer doc.deinit(testing.allocator);
var actual = std.ArrayList(u8).init(testing.allocator);
defer actual.deinit();
try doc.render(actual.writer());
try testing.expectEqualStrings(expected, actual.items);
}

View File

@ -0,0 +1,192 @@
//! An abstract tree representation of a Markdown document.
const std = @import("std");
const builtin = @import("builtin");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const Renderer = @import("renderer.zig").Renderer;
nodes: Node.List.Slice,
extra: []u32,
string_bytes: []u8,
const Document = @This();
pub const Node = struct {
tag: Tag,
data: Data,
pub const Index = enum(u32) {
root = 0,
_,
};
pub const List = std.MultiArrayList(Node);
pub const Tag = enum {
/// Data is `container`.
root,
// Blocks
/// Data is `list`.
list,
/// Data is `list_item`.
list_item,
/// Data is `container`.
table,
/// Data is `container`.
table_row,
/// Data is `table_cell`.
table_cell,
/// Data is `heading`.
heading,
/// Data is `code_block`.
code_block,
/// Data is `container`.
blockquote,
/// Data is `container`.
paragraph,
/// Data is `none`.
thematic_break,
// Inlines
/// Data is `link`.
link,
/// Data is `link`.
image,
/// Data is `container`.
strong,
/// Data is `container`.
emphasis,
/// Data is `text`.
code_span,
/// Data is `text`.
text,
/// Data is `none`.
line_break,
};
pub const Data = union {
none: void,
container: struct {
children: ExtraIndex,
},
text: struct {
content: StringIndex,
},
list: struct {
start: ListStart,
children: ExtraIndex,
},
list_item: struct {
tight: bool,
children: ExtraIndex,
},
table_cell: struct {
info: packed struct {
alignment: TableCellAlignment,
header: bool,
},
children: ExtraIndex,
},
heading: struct {
/// Between 1 and 6, inclusive.
level: u3,
children: ExtraIndex,
},
code_block: struct {
tag: StringIndex,
content: StringIndex,
},
link: struct {
target: StringIndex,
children: ExtraIndex,
},
comptime {
// In Debug and ReleaseSafe builds, there may be hidden extra fields
// included for safety checks. Without such safety checks enabled,
// we always want this union to be 8 bytes.
if (builtin.mode != .Debug and builtin.mode != .ReleaseSafe) {
assert(@sizeOf(Data) == 8);
}
}
};
/// The starting number of a list. This is either a number between 0 and
/// 999,999,999, inclusive, or `unordered` to indicate an unordered list.
pub const ListStart = enum(u30) {
// When https://github.com/ziglang/zig/issues/104 is implemented, this
// type can be more naturally expressed as ?u30. As it is, we want
// values to fit within 4 bytes, so ?u30 does not yet suffice for
// storage.
unordered = std.math.maxInt(u30),
_,
pub fn asNumber(start: ListStart) ?u30 {
if (start == .unordered) return null;
assert(@intFromEnum(start) <= 999_999_999);
return @intFromEnum(start);
}
};
pub const TableCellAlignment = enum {
unset,
left,
center,
right,
};
/// Trailing: `len` times `Node.Index`
pub const Children = struct {
len: u32,
};
};
pub const ExtraIndex = enum(u32) { _ };
/// The index of a null-terminated string in `string_bytes`.
pub const StringIndex = enum(u32) {
empty = 0,
_,
};
pub fn deinit(doc: *Document, allocator: Allocator) void {
doc.nodes.deinit(allocator);
allocator.free(doc.extra);
allocator.free(doc.string_bytes);
doc.* = undefined;
}
/// Renders a document directly to a writer using the default renderer.
pub fn render(doc: Document, writer: anytype) @TypeOf(writer).Error!void {
const renderer: Renderer(@TypeOf(writer), void) = .{ .context = {} };
try renderer.render(doc, writer);
}
pub fn ExtraData(comptime T: type) type {
return struct { data: T, end: usize };
}
pub fn extraData(doc: Document, comptime T: type, index: ExtraIndex) ExtraData(T) {
const fields = @typeInfo(T).Struct.fields;
var i: usize = @intFromEnum(index);
var result: T = undefined;
inline for (fields) |field| {
@field(result, field.name) = switch (field.type) {
u32 => doc.extra[i],
else => @compileError("bad field type"),
};
i += 1;
}
return .{ .data = result, .end = i };
}
pub fn extraChildren(doc: Document, index: ExtraIndex) []const Node.Index {
const children = doc.extraData(Node.Children, index);
return @ptrCast(doc.extra[children.end..][0..children.data.len]);
}
pub fn string(doc: Document, index: StringIndex) [:0]const u8 {
const start = @intFromEnum(index);
return std.mem.span(@as([*:0]u8, @ptrCast(doc.string_bytes[start..].ptr)));
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,249 @@
const std = @import("std");
const Document = @import("Document.zig");
const Node = Document.Node;
/// A Markdown document renderer.
///
/// Each concrete `Renderer` type has a `renderDefault` function, with the
/// intention that custom `renderFn` implementations can call `renderDefault`
/// for node types for which they require no special rendering.
pub fn Renderer(comptime Writer: type, comptime Context: type) type {
return struct {
renderFn: *const fn (
r: Self,
doc: Document,
node: Node.Index,
writer: Writer,
) Writer.Error!void = renderDefault,
context: Context,
const Self = @This();
pub fn render(r: Self, doc: Document, writer: Writer) Writer.Error!void {
try r.renderFn(r, doc, .root, writer);
}
pub fn renderDefault(
r: Self,
doc: Document,
node: Node.Index,
writer: Writer,
) Writer.Error!void {
const data = doc.nodes.items(.data)[@intFromEnum(node)];
switch (doc.nodes.items(.tag)[@intFromEnum(node)]) {
.root => {
for (doc.extraChildren(data.container.children)) |child| {
try r.renderFn(r, doc, child, writer);
}
},
.list => {
if (data.list.start.asNumber()) |start| {
if (start == 1) {
try writer.writeAll("<ol>\n");
} else {
try writer.print("<ol start=\"{}\">\n", .{start});
}
} else {
try writer.writeAll("<ul>\n");
}
for (doc.extraChildren(data.list.children)) |child| {
try r.renderFn(r, doc, child, writer);
}
if (data.list.start.asNumber() != null) {
try writer.writeAll("</ol>\n");
} else {
try writer.writeAll("</ul>\n");
}
},
.list_item => {
try writer.writeAll("<li>");
for (doc.extraChildren(data.list_item.children)) |child| {
if (data.list_item.tight and doc.nodes.items(.tag)[@intFromEnum(child)] == .paragraph) {
const para_data = doc.nodes.items(.data)[@intFromEnum(child)];
for (doc.extraChildren(para_data.container.children)) |para_child| {
try r.renderFn(r, doc, para_child, writer);
}
} else {
try r.renderFn(r, doc, child, writer);
}
}
try writer.writeAll("</li>\n");
},
.table => {
try writer.writeAll("<table>\n");
for (doc.extraChildren(data.container.children)) |child| {
try r.renderFn(r, doc, child, writer);
}
try writer.writeAll("</table>\n");
},
.table_row => {
try writer.writeAll("<tr>\n");
for (doc.extraChildren(data.container.children)) |child| {
try r.renderFn(r, doc, child, writer);
}
try writer.writeAll("</tr>\n");
},
.table_cell => {
if (data.table_cell.info.header) {
try writer.writeAll("<th");
} else {
try writer.writeAll("<td");
}
switch (data.table_cell.info.alignment) {
.unset => try writer.writeAll(">"),
else => |a| try writer.print(" style=\"text-align: {s}\">", .{@tagName(a)}),
}
for (doc.extraChildren(data.table_cell.children)) |child| {
try r.renderFn(r, doc, child, writer);
}
if (data.table_cell.info.header) {
try writer.writeAll("</th>\n");
} else {
try writer.writeAll("</td>\n");
}
},
.heading => {
try writer.print("<h{}>", .{data.heading.level});
for (doc.extraChildren(data.heading.children)) |child| {
try r.renderFn(r, doc, child, writer);
}
try writer.print("</h{}>\n", .{data.heading.level});
},
.code_block => {
const content = doc.string(data.code_block.content);
try writer.print("<pre><code>{}</code></pre>\n", .{fmtHtml(content)});
},
.blockquote => {
try writer.writeAll("<blockquote>\n");
for (doc.extraChildren(data.container.children)) |child| {
try r.renderFn(r, doc, child, writer);
}
try writer.writeAll("</blockquote>\n");
},
.paragraph => {
try writer.writeAll("<p>");
for (doc.extraChildren(data.container.children)) |child| {
try r.renderFn(r, doc, child, writer);
}
try writer.writeAll("</p>\n");
},
.thematic_break => {
try writer.writeAll("<hr />\n");
},
.link => {
const target = doc.string(data.link.target);
try writer.print("<a href=\"{}\">", .{fmtHtml(target)});
for (doc.extraChildren(data.link.children)) |child| {
try r.renderFn(r, doc, child, writer);
}
try writer.writeAll("</a>");
},
.image => {
const target = doc.string(data.link.target);
try writer.print("<img src=\"{}\" alt=\"", .{fmtHtml(target)});
for (doc.extraChildren(data.link.children)) |child| {
try renderInlineNodeText(doc, child, writer);
}
try writer.writeAll("\" />");
},
.strong => {
try writer.writeAll("<strong>");
for (doc.extraChildren(data.container.children)) |child| {
try r.renderFn(r, doc, child, writer);
}
try writer.writeAll("</strong>");
},
.emphasis => {
try writer.writeAll("<em>");
for (doc.extraChildren(data.container.children)) |child| {
try r.renderFn(r, doc, child, writer);
}
try writer.writeAll("</em>");
},
.code_span => {
const content = doc.string(data.text.content);
try writer.print("<code>{}</code>", .{fmtHtml(content)});
},
.text => {
const content = doc.string(data.text.content);
try writer.print("{}", .{fmtHtml(content)});
},
.line_break => {
try writer.writeAll("<br />\n");
},
}
}
};
}
/// Renders an inline node as plain text. Asserts that the node is an inline and
/// has no non-inline children.
pub fn renderInlineNodeText(
doc: Document,
node: Node.Index,
writer: anytype,
) @TypeOf(writer).Error!void {
const data = doc.nodes.items(.data)[@intFromEnum(node)];
switch (doc.nodes.items(.tag)[@intFromEnum(node)]) {
.root,
.list,
.list_item,
.table,
.table_row,
.table_cell,
.heading,
.code_block,
.blockquote,
.paragraph,
.thematic_break,
=> unreachable, // Blocks
.link, .image => {
for (doc.extraChildren(data.link.children)) |child| {
try renderInlineNodeText(doc, child, writer);
}
},
.strong => {
for (doc.extraChildren(data.container.children)) |child| {
try renderInlineNodeText(doc, child, writer);
}
},
.emphasis => {
for (doc.extraChildren(data.container.children)) |child| {
try renderInlineNodeText(doc, child, writer);
}
},
.code_span, .text => {
const content = doc.string(data.text.content);
try writer.print("{}", .{fmtHtml(content)});
},
.line_break => {
try writer.writeAll("\n");
},
}
}
pub fn fmtHtml(bytes: []const u8) std.fmt.Formatter(formatHtml) {
return .{ .data = bytes };
}
fn formatHtml(
bytes: []const u8,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = fmt;
_ = options;
for (bytes) |b| {
switch (b) {
'<' => try writer.writeAll("&lt;"),
'>' => try writer.writeAll("&gt;"),
'&' => try writer.writeAll("&amp;"),
'"' => try writer.writeAll("&quot;"),
else => try writer.writeByte(b),
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,4 @@
const builtin = @import("builtin");
const std = @import("std");
const assert = std.debug.assert;
const WaitGroup = @This();
@ -43,3 +44,24 @@ pub fn isDone(wg: *WaitGroup) bool {
return (state / one_pending) == 0;
}
// Spawns a new thread for the task. This is appropriate when the callee
// delegates all work.
pub fn spawnManager(
wg: *WaitGroup,
comptime func: anytype,
args: anytype,
) void {
if (builtin.single_threaded) {
@call(.auto, func, args);
return;
}
const Manager = struct {
fn run(wg_inner: *WaitGroup, args_inner: @TypeOf(args)) void {
defer wg_inner.finish();
@call(.auto, func, args_inner);
}
};
wg.start();
_ = std.Thread.spawn(.{}, Manager.run, .{ wg, args }) catch Manager.run(wg, args);
}

View File

@ -1,3 +1,5 @@
//! Base64 encoding/decoding.
const std = @import("std.zig");
const assert = std.debug.assert;
const builtin = @import("builtin");

View File

@ -1,3 +1,5 @@
//! Types and values provided by the Zig language.
const builtin = @import("builtin");
/// `explicit_subsystem` is missing when the subsystem is automatically detected,

View File

@ -1,3 +1,5 @@
//! Compression algorithms.
const std = @import("std.zig");
pub const flate = @import("compress/flate.zig");

View File

@ -1,3 +1,5 @@
//! Cryptography.
const root = @import("root");
/// Authenticated Encryption with Associated Data

View File

@ -1,3 +1,5 @@
//! DWARF debugging data format.
const builtin = @import("builtin");
const std = @import("std.zig");
const debug = std.debug;

View File

@ -1,3 +1,5 @@
//! Executable and Linkable Format.
const std = @import("std.zig");
const math = std.math;
const mem = std.mem;

View File

@ -1,3 +1,5 @@
//! String formatting and parsing.
const std = @import("std.zig");
const builtin = @import("builtin");

View File

@ -1,3 +1,5 @@
//! File System.
const std = @import("std.zig");
const builtin = @import("builtin");
const root = @import("root");

View File

@ -58,3 +58,14 @@ pub fn writeStruct(self: Self, value: anytype) anyerror!void {
comptime assert(@typeInfo(@TypeOf(value)).Struct.layout != .Auto);
return self.writeAll(mem.asBytes(&value));
}
pub fn writeFile(self: Self, file: std.fs.File) anyerror!void {
// TODO: figure out how to adjust std lib abstractions so that this ends up
// doing sendfile or maybe even copy_file_range under the right conditions.
var buf: [4000]u8 = undefined;
while (true) {
const n = try file.readAll(&buf);
try self.writeAll(buf[0..n]);
if (n < buf.len) return;
}
}

View File

@ -1,3 +1,5 @@
//! Cross-platform networking abstractions.
const std = @import("std.zig");
const builtin = @import("builtin");
const assert = std.debug.assert;

View File

@ -1,7 +1,9 @@
//! This module provides functions for working conveniently with SIMD (Single Instruction; Multiple Data),
//! which may offer a potential boost in performance on some targets by performing the same operations on
//! multiple elements at once.
//! Please be aware that some functions are known to not work on MIPS.
//! SIMD (Single Instruction; Multiple Data) convenience functions.
//!
//! May offer a potential boost in performance on some targets by performing
//! the same operations on multiple elements at once.
//!
//! Some functions are known to not work on MIPS.
const std = @import("std");
const builtin = @import("builtin");

View File

@ -55,149 +55,56 @@ pub const Tz = tz.Tz;
pub const Uri = @import("Uri.zig");
pub const array_hash_map = @import("array_hash_map.zig");
/// Memory ordering, atomic data structures, and operations.
pub const atomic = @import("atomic.zig");
/// Base64 encoding/decoding.
pub const base64 = @import("base64.zig");
/// Bit manipulation data structures.
pub const bit_set = @import("bit_set.zig");
/// Comptime-available information about the build environment, such as the target and optimize mode.
pub const builtin = @import("builtin.zig");
pub const c = @import("c.zig");
/// COFF format.
pub const coff = @import("coff.zig");
/// Compression algorithms such as zlib, zstd, etc.
pub const compress = @import("compress.zig");
pub const comptime_string_map = @import("comptime_string_map.zig");
/// Cryptography.
pub const crypto = @import("crypto.zig");
/// Debug printing, allocation and other debug helpers.
pub const debug = @import("debug.zig");
/// DWARF debugging data format.
pub const dwarf = @import("dwarf.zig");
/// ELF format.
pub const elf = @import("elf.zig");
/// Enum-related metaprogramming helpers.
pub const enums = @import("enums.zig");
/// First in, first out data structures.
pub const fifo = @import("fifo.zig");
/// String formatting and parsing (e.g. parsing numbers out of strings).
pub const fmt = @import("fmt.zig");
/// File system-related functionality.
pub const fs = @import("fs.zig");
/// GPU programming helpers.
pub const gpu = @import("gpu.zig");
/// Fast hashing functions (i.e. not cryptographically secure).
pub const hash = @import("hash.zig");
pub const hash_map = @import("hash_map.zig");
/// Allocator implementations.
pub const heap = @import("heap.zig");
/// HTTP client and server.
pub const http = @import("http.zig");
/// I/O streams, reader/writer interfaces and common helpers.
pub const io = @import("io.zig");
/// JSON parsing and serialization.
pub const json = @import("json.zig");
/// LEB128 encoding.
pub const leb = @import("leb128.zig");
/// A standardized interface for logging.
pub const log = @import("log.zig");
/// Mach-O format.
pub const macho = @import("macho.zig");
/// Mathematical constants and operations.
pub const math = @import("math.zig");
/// Functions for comparing, searching, and manipulating memory.
pub const mem = @import("mem.zig");
/// Metaprogramming helpers.
pub const meta = @import("meta.zig");
/// Networking.
pub const net = @import("net.zig");
/// POSIX-like API layer.
pub const posix = @import("os.zig");
/// Non-portable Operating System-specific API.
pub const os = @import("os.zig");
pub const once = @import("once.zig").once;
/// A set of array and slice types that bit-pack integer elements.
pub const packed_int_array = @import("packed_int_array.zig");
/// PDB file format.
pub const pdb = @import("pdb.zig");
/// Accessors for process-related info (e.g. command line arguments)
/// and spawning of child processes.
pub const process = @import("process.zig");
/// Deprecated: use `Random` instead.
pub const rand = Random;
/// Sorting.
pub const sort = @import("sort.zig");
/// Single Instruction Multiple Data (SIMD) helpers.
pub const simd = @import("simd.zig");
/// ASCII text processing.
pub const ascii = @import("ascii.zig");
/// Tar archive format compression/decompression.
pub const tar = @import("tar.zig");
/// Testing allocator, testing assertions, and other helpers for testing code.
pub const testing = @import("testing.zig");
/// Sleep, obtaining the current time, conversion constants, and more.
pub const time = @import("time.zig");
/// Time zones.
pub const tz = @import("tz.zig");
/// UTF-8 and UTF-16LE encoding/decoding.
pub const unicode = @import("unicode.zig");
/// Helpers for integrating with Valgrind.
pub const valgrind = @import("valgrind.zig");
/// Constants and types representing the Wasm binary format.
pub const wasm = @import("wasm.zig");
/// Builds of the Zig compiler are distributed partly in source form. That
/// source lives here. These APIs are provided as-is and have absolutely no API
/// guarantees whatsoever.
pub const zig = @import("zig.zig");
pub const start = @import("start.zig");
const root = @import("root");

View File

@ -1,23 +1,25 @@
/// Tar archive is single ordinary file which can contain many files (or
/// directories, symlinks, ...). It's build by series of blocks each size of 512
/// bytes. First block of each entry is header which defines type, name, size
/// permissions and other attributes. Header is followed by series of blocks of
/// file content, if any that entry has content. Content is padded to the block
/// size, so next header always starts at block boundary.
///
/// This simple format is extended by GNU and POSIX pax extensions to support
/// file names longer than 256 bytes and additional attributes.
///
/// This is not comprehensive tar parser. Here we are only file types needed to
/// support Zig package manager; normal file, directory, symbolic link. And
/// subset of attributes: name, size, permissions.
///
/// GNU tar reference: https://www.gnu.org/software/tar/manual/html_node/Standard.html
/// pax reference: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13
///
//! Tar archive is single ordinary file which can contain many files (or
//! directories, symlinks, ...). It's build by series of blocks each size of 512
//! bytes. First block of each entry is header which defines type, name, size
//! permissions and other attributes. Header is followed by series of blocks of
//! file content, if any that entry has content. Content is padded to the block
//! size, so next header always starts at block boundary.
//!
//! This simple format is extended by GNU and POSIX pax extensions to support
//! file names longer than 256 bytes and additional attributes.
//!
//! This is not comprehensive tar parser. Here we are only file types needed to
//! support Zig package manager; normal file, directory, symbolic link. And
//! subset of attributes: name, size, permissions.
//!
//! GNU tar reference: https://www.gnu.org/software/tar/manual/html_node/Standard.html
//! pax reference: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13
const std = @import("std.zig");
const assert = std.debug.assert;
pub const output = @import("tar/output.zig");
pub const Options = struct {
/// Number of directory levels to skip when extracting files.
strip_components: u32 = 0,

85
lib/std/tar/output.zig Normal file
View File

@ -0,0 +1,85 @@
/// A struct that is exactly 512 bytes and matches tar file format. This is
/// intended to be used for outputting tar files; for parsing there is
/// `std.tar.Header`.
pub const Header = extern struct {
// This struct was originally copied from
// https://github.com/mattnite/tar/blob/main/src/main.zig which is MIT
// licensed.
name: [100]u8,
mode: [7:0]u8,
uid: [7:0]u8,
gid: [7:0]u8,
size: [11:0]u8,
mtime: [11:0]u8,
checksum: [7:0]u8,
typeflag: FileType,
linkname: [100]u8,
magic: [5:0]u8,
version: [2]u8,
uname: [31:0]u8,
gname: [31:0]u8,
devmajor: [7:0]u8,
devminor: [7:0]u8,
prefix: [155]u8,
pad: [12]u8,
pub const FileType = enum(u8) {
regular = '0',
hard_link = '1',
symbolic_link = '2',
character = '3',
block = '4',
directory = '5',
fifo = '6',
reserved = '7',
pax_global = 'g',
extended = 'x',
_,
};
pub fn init() Header {
var ret = std.mem.zeroes(Header);
ret.magic = [_:0]u8{ 'u', 's', 't', 'a', 'r' };
ret.version = [_:0]u8{ '0', '0' };
return ret;
}
pub fn setPath(self: *Header, prefix: []const u8, path: []const u8) !void {
if (prefix.len + 1 + path.len > 100) {
var i: usize = 0;
while (i < path.len and path.len - i > 100) {
while (path[i] != '/') : (i += 1) {}
}
_ = try std.fmt.bufPrint(&self.prefix, "{s}/{s}", .{ prefix, path[0..i] });
_ = try std.fmt.bufPrint(&self.name, "{s}", .{path[i + 1 ..]});
} else {
_ = try std.fmt.bufPrint(&self.name, "{s}/{s}", .{ prefix, path });
}
}
pub fn setSize(self: *Header, size: u64) !void {
_ = try std.fmt.bufPrint(&self.size, "{o:0>11}", .{size});
}
pub fn updateChecksum(self: *Header) !void {
const offset = @offsetOf(Header, "checksum");
var checksum: usize = 0;
for (std.mem.asBytes(self), 0..) |val, i| {
checksum += if (i >= offset and i < offset + @sizeOf(@TypeOf(self.checksum)))
' '
else
val;
}
_ = try std.fmt.bufPrint(&self.checksum, "{o:0>7}", .{checksum});
}
comptime {
assert(@sizeOf(Header) == 512);
}
};
const std = @import("../std.zig");
const assert = std.debug.assert;

View File

@ -1,3 +1,7 @@
//! Builds of the Zig compiler are distributed partly in source form. That
//! source lives here. These APIs are provided as-is and have absolutely no API
//! guarantees whatsoever.
pub const ErrorBundle = @import("zig/ErrorBundle.zig");
pub const Server = @import("zig/Server.zig");
pub const Client = @import("zig/Client.zig");

File diff suppressed because it is too large Load Diff

View File

@ -36,7 +36,6 @@ const Cache = std.Build.Cache;
const c_codegen = @import("codegen/c.zig");
const libtsan = @import("libtsan.zig");
const Zir = std.zig.Zir;
const Autodoc = @import("Autodoc.zig");
const resinator = @import("resinator.zig");
const Builtin = @import("Builtin.zig");
const LlvmObject = @import("codegen/llvm.zig").Object;
@ -734,6 +733,8 @@ pub const MiscTask = enum {
compiler_rt,
zig_libc,
analyze_mod,
docs_copy,
docs_wasm,
@"musl crti.o",
@"musl crtn.o",
@ -2347,10 +2348,6 @@ fn flush(comp: *Compilation, arena: Allocator, prog_node: *std.Progress.Node) !v
try emitLlvmObject(comp, arena, default_emit, null, llvm_object, prog_node);
}
}
if (comp.totalErrorCount() == 0) {
try maybeGenerateAutodocs(comp, prog_node);
}
}
/// This function is called by the frontend before flush(). It communicates that
@ -2401,26 +2398,6 @@ fn renameTmpIntoCache(
}
}
fn maybeGenerateAutodocs(comp: *Compilation, prog_node: *std.Progress.Node) !void {
const mod = comp.module orelse return;
// TODO: do this in a separate job during performAllTheWork(). The
// file copies at the end of generate() can also be extracted to
// separate jobs
if (!build_options.only_c and !build_options.only_core_functionality) {
if (comp.docs_emit) |emit| {
var dir = try emit.directory.handle.makeOpenPath(emit.sub_path, .{});
defer dir.close();
var sub_prog_node = prog_node.start("Generating documentation", 0);
sub_prog_node.activate();
sub_prog_node.context.refresh();
defer sub_prog_node.end();
try Autodoc.generate(mod, dir);
}
}
}
/// Communicate the output binary location to parent Compilations.
fn wholeCacheModeSetBinFilePath(
comp: *Compilation,
@ -3346,6 +3323,9 @@ pub fn performAllTheWork(
var zir_prog_node = main_progress_node.start("AST Lowering", 0);
defer zir_prog_node.end();
var wasm_prog_node = main_progress_node.start("Compile Autodocs", 0);
defer wasm_prog_node.end();
var c_obj_prog_node = main_progress_node.start("Compile C Objects", comp.c_source_files.len);
defer c_obj_prog_node.end();
@ -3355,6 +3335,13 @@ pub fn performAllTheWork(
comp.work_queue_wait_group.reset();
defer comp.work_queue_wait_group.wait();
if (!build_options.only_c and !build_options.only_core_functionality) {
if (comp.docs_emit != null) {
try taskDocsCopy(comp, &comp.work_queue_wait_group);
comp.work_queue_wait_group.spawnManager(workerDocsWasm, .{ comp, &wasm_prog_node });
}
}
{
const astgen_frame = tracy.namedFrame("astgen");
defer astgen_frame.end();
@ -3769,6 +3756,255 @@ fn processOneJob(comp: *Compilation, job: Job, prog_node: *std.Progress.Node) !v
}
}
fn taskDocsCopy(comp: *Compilation, wg: *WaitGroup) !void {
wg.start();
errdefer wg.finish();
try comp.thread_pool.spawn(workerDocsCopy, .{ comp, wg });
}
fn workerDocsCopy(comp: *Compilation, wg: *WaitGroup) void {
defer wg.finish();
docsCopyFallible(comp) catch |err| {
return comp.lockAndSetMiscFailure(
.docs_copy,
"unable to copy autodocs artifacts: {s}",
.{@errorName(err)},
);
};
}
fn docsCopyFallible(comp: *Compilation) anyerror!void {
const emit = comp.docs_emit.?;
var out_dir = emit.directory.handle.makeOpenPath(emit.sub_path, .{}) catch |err| {
return comp.lockAndSetMiscFailure(
.docs_copy,
"unable to create output directory '{}{s}': {s}",
.{ emit.directory, emit.sub_path, @errorName(err) },
);
};
defer out_dir.close();
for (&[_][]const u8{ "docs/main.js", "docs/index.html" }) |sub_path| {
const basename = std.fs.path.basename(sub_path);
comp.zig_lib_directory.handle.copyFile(sub_path, out_dir, basename, .{}) catch |err| {
comp.lockAndSetMiscFailure(.docs_copy, "unable to copy {s}: {s}", .{
sub_path,
@errorName(err),
});
return;
};
}
var tar_file = out_dir.createFile("sources.tar", .{}) catch |err| {
return comp.lockAndSetMiscFailure(
.docs_copy,
"unable to create '{}{s}/sources.tar': {s}",
.{ emit.directory, emit.sub_path, @errorName(err) },
);
};
defer tar_file.close();
const root = comp.root_mod.root;
const sub_path = if (root.sub_path.len == 0) "." else root.sub_path;
var mod_dir = root.root_dir.handle.openDir(sub_path, .{ .iterate = true }) catch |err| {
return comp.lockAndSetMiscFailure(.docs_copy, "unable to open directory '{}': {s}", .{
root, @errorName(err),
});
};
defer mod_dir.close();
var walker = try mod_dir.walk(comp.gpa);
defer walker.deinit();
const padding_buffer = [1]u8{0} ** 512;
while (try walker.next()) |entry| {
switch (entry.kind) {
.file => {
if (!std.mem.endsWith(u8, entry.basename, ".zig")) continue;
if (std.mem.eql(u8, entry.basename, "test.zig")) continue;
if (std.mem.endsWith(u8, entry.basename, "_test.zig")) continue;
},
else => continue,
}
var file = mod_dir.openFile(entry.path, .{}) catch |err| {
return comp.lockAndSetMiscFailure(.docs_copy, "unable to open '{}{s}': {s}", .{
root, entry.path, @errorName(err),
});
};
defer file.close();
const stat = file.stat() catch |err| {
return comp.lockAndSetMiscFailure(.docs_copy, "unable to stat '{}{s}': {s}", .{
root, entry.path, @errorName(err),
});
};
var file_header = std.tar.output.Header.init();
file_header.typeflag = .regular;
try file_header.setPath(comp.root_name, entry.path);
try file_header.setSize(stat.size);
try file_header.updateChecksum();
const header_bytes = std.mem.asBytes(&file_header);
const padding = p: {
const remainder: u16 = @intCast(stat.size % 512);
const n = if (remainder > 0) 512 - remainder else 0;
break :p padding_buffer[0..n];
};
var header_and_trailer: [2]std.os.iovec_const = .{
.{ .iov_base = header_bytes.ptr, .iov_len = header_bytes.len },
.{ .iov_base = padding.ptr, .iov_len = padding.len },
};
try tar_file.writeFileAll(file, .{
.in_len = stat.size,
.headers_and_trailers = &header_and_trailer,
.header_count = 1,
});
}
}
fn workerDocsWasm(comp: *Compilation, prog_node: *std.Progress.Node) void {
workerDocsWasmFallible(comp, prog_node) catch |err| {
comp.lockAndSetMiscFailure(.docs_wasm, "unable to build autodocs: {s}", .{
@errorName(err),
});
};
}
fn workerDocsWasmFallible(comp: *Compilation, prog_node: *std.Progress.Node) anyerror!void {
const gpa = comp.gpa;
var arena_allocator = std.heap.ArenaAllocator.init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
const optimize_mode = std.builtin.OptimizeMode.ReleaseSmall;
const output_mode = std.builtin.OutputMode.Exe;
const resolved_target: Package.Module.ResolvedTarget = .{
.result = std.zig.system.resolveTargetQuery(.{
.cpu_arch = .wasm32,
.os_tag = .freestanding,
.cpu_features_add = std.Target.wasm.featureSet(&.{
.atomics,
.bulk_memory,
// .extended_const, not supported by Safari
.multivalue,
.mutable_globals,
.nontrapping_fptoint,
.reference_types,
//.relaxed_simd, not supported by Firefox or Safari
.sign_ext,
// observed to cause Error occured during wast conversion :
// Unknown operator: 0xfd058 in Firefox 117
//.simd128,
// .tail_call, not supported by Safari
}),
}) catch unreachable,
.is_native_os = false,
.is_native_abi = false,
};
const config = try Config.resolve(.{
.output_mode = output_mode,
.resolved_target = resolved_target,
.is_test = false,
.have_zcu = true,
.emit_bin = true,
.root_optimize_mode = optimize_mode,
.link_libc = false,
.rdynamic = true,
});
const src_basename = "main.zig";
const root_name = std.fs.path.stem(src_basename);
const root_mod = try Package.Module.create(arena, .{
.global_cache_directory = comp.global_cache_directory,
.paths = .{
.root = .{
.root_dir = comp.zig_lib_directory,
.sub_path = "docs/wasm",
},
.root_src_path = src_basename,
},
.fully_qualified_name = root_name,
.inherited = .{
.resolved_target = resolved_target,
.optimize_mode = optimize_mode,
},
.global = config,
.cc_argv = &.{},
.parent = null,
.builtin_mod = null,
.builtin_modules = null, // there is only one module in this compilation
});
const bin_basename = try std.zig.binNameAlloc(arena, .{
.root_name = root_name,
.target = resolved_target.result,
.output_mode = output_mode,
});
const sub_compilation = try Compilation.create(gpa, arena, .{
.global_cache_directory = comp.global_cache_directory,
.local_cache_directory = comp.global_cache_directory,
.zig_lib_directory = comp.zig_lib_directory,
.self_exe_path = comp.self_exe_path,
.config = config,
.root_mod = root_mod,
.entry = .disabled,
.cache_mode = .whole,
.root_name = root_name,
.thread_pool = comp.thread_pool,
.libc_installation = comp.libc_installation,
.emit_bin = .{
.directory = null, // Put it in the cache directory.
.basename = bin_basename,
},
.verbose_cc = comp.verbose_cc,
.verbose_link = comp.verbose_link,
.verbose_air = comp.verbose_air,
.verbose_intern_pool = comp.verbose_intern_pool,
.verbose_generic_instances = comp.verbose_intern_pool,
.verbose_llvm_ir = comp.verbose_llvm_ir,
.verbose_llvm_bc = comp.verbose_llvm_bc,
.verbose_cimport = comp.verbose_cimport,
.verbose_llvm_cpu_features = comp.verbose_llvm_cpu_features,
});
defer sub_compilation.destroy();
try comp.updateSubCompilation(sub_compilation, .docs_wasm, prog_node);
const emit = comp.docs_emit.?;
var out_dir = emit.directory.handle.makeOpenPath(emit.sub_path, .{}) catch |err| {
return comp.lockAndSetMiscFailure(
.docs_copy,
"unable to create output directory '{}{s}': {s}",
.{ emit.directory, emit.sub_path, @errorName(err) },
);
};
defer out_dir.close();
sub_compilation.local_cache_directory.handle.copyFile(
sub_compilation.cache_use.whole.bin_sub_path.?,
out_dir,
"main.wasm",
.{},
) catch |err| {
return comp.lockAndSetMiscFailure(.docs_copy, "unable to copy '{}{s}' to '{}{s}': {s}", .{
sub_compilation.local_cache_directory,
sub_compilation.cache_use.whole.bin_sub_path.?,
emit.directory,
emit.sub_path,
@errorName(err),
});
};
}
const AstGenSrc = union(enum) {
root,
import: struct {

View File

@ -1,435 +0,0 @@
const std = @import("std");
const builtin = @import("builtin");
const io = std.io;
const fs = std.fs;
const process = std.process;
const ChildProcess = std.ChildProcess;
const Progress = std.Progress;
const print = std.debug.print;
const mem = std.mem;
const testing = std.testing;
const Allocator = std.mem.Allocator;
const Module = @import("../Module.zig");
pub fn genHtml(
allocator: Allocator,
src: *Module.File,
out: anytype,
) !void {
try out.writeAll(
\\<!doctype html>
\\<html lang="en">
\\<head>
\\ <meta charset="utf-8">
\\ <meta name="viewport" content="width=device-width, initial-scale=1.0">
);
try out.print(" <title>{s} - source view</title>\n", .{src.sub_file_path});
try out.writeAll(
\\ <link rel="icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAgklEQVR4AWMYWuD7EllJIM4G4g4g5oIJ/odhOJ8wToOxSTXgNxDHoeiBMfA4+wGShjyYOCkG/IGqWQziEzYAoUAeiF9D5U+DxEg14DRU7jWIT5IBIOdCxf+A+CQZAAoopEB7QJwBCBwHiip8UYmRdrAlDpIMgApwQZNnNii5Dq0MBgCxxycBnwEd+wAAAABJRU5ErkJggg==">
\\ <link rel="icon" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNTMgMTQwIj48ZyBmaWxsPSIjRjdBNDFEIj48Zz48cG9seWdvbiBwb2ludHM9IjQ2LDIyIDI4LDQ0IDE5LDMwIi8+PHBvbHlnb24gcG9pbnRzPSI0NiwyMiAzMywzMyAyOCw0NCAyMiw0NCAyMiw5NSAzMSw5NSAyMCwxMDAgMTIsMTE3IDAsMTE3IDAsMjIiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyIvPjxwb2x5Z29uIHBvaW50cz0iMzEsOTUgMTIsMTE3IDQsMTA2Ii8+PC9nPjxnPjxwb2x5Z29uIHBvaW50cz0iNTYsMjIgNjIsMzYgMzcsNDQiLz48cG9seWdvbiBwb2ludHM9IjU2LDIyIDExMSwyMiAxMTEsNDQgMzcsNDQgNTYsMzIiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyIvPjxwb2x5Z29uIHBvaW50cz0iMTE2LDk1IDk3LDExNyA5MCwxMDQiLz48cG9seWdvbiBwb2ludHM9IjExNiw5NSAxMDAsMTA0IDk3LDExNyA0MiwxMTcgNDIsOTUiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyIvPjxwb2x5Z29uIHBvaW50cz0iMTUwLDAgNTIsMTE3IDMsMTQwIDEwMSwyMiIvPjwvZz48Zz48cG9seWdvbiBwb2ludHM9IjE0MSwyMiAxNDAsNDAgMTIyLDQ1Ii8+PHBvbHlnb24gcG9pbnRzPSIxNTMsMjIgMTUzLDExNyAxMDYsMTE3IDEyMCwxMDUgMTI1LDk1IDEzMSw5NSAxMzEsNDUgMTIyLDQ1IDEzMiwzNiAxNDEsMjIiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyIvPjxwb2x5Z29uIHBvaW50cz0iMTI1LDk1IDEzMCwxMTAgMTA2LDExNyIvPjwvZz48L2c+PC9zdmc+">
\\ <style>
\\ body{
\\ font-family: system-ui, -apple-system, Roboto, "Segoe UI", sans-serif;
\\ margin: 0;
\\ line-height: 1.5;
\\ }
\\
\\ pre > code {
\\ display: block;
\\ overflow: auto;
\\ line-height: normal;
\\ margin: 0em;
\\ }
\\ .tok-kw {
\\ color: #333;
\\ font-weight: bold;
\\ }
\\ .tok-str {
\\ color: #d14;
\\ }
\\ .tok-builtin {
\\ color: #005C7A;
\\ }
\\ .tok-comment {
\\ color: #545454;
\\ font-style: italic;
\\ }
\\ .tok-fn {
\\ color: #900;
\\ font-weight: bold;
\\ }
\\ .tok-null {
\\ color: #005C5C;
\\ }
\\ .tok-number {
\\ color: #005C5C;
\\ }
\\ .tok-type {
\\ color: #458;
\\ font-weight: bold;
\\ }
\\ pre {
\\ counter-reset: line;
\\ }
\\ pre .line:before {
\\ counter-increment: line;
\\ content: counter(line);
\\ display: inline-block;
\\ padding-right: 1em;
\\ width: 2em;
\\ text-align: right;
\\ color: #999;
\\ }
\\
\\ .line {
\\ width: 100%;
\\ display: inline-block;
\\ }
\\ .line:target {
\\ border-top: 1px solid #ccc;
\\ border-bottom: 1px solid #ccc;
\\ background: #fafafa;
\\ }
\\
\\ @media (prefers-color-scheme: dark) {
\\ body{
\\ background:#222;
\\ color: #ccc;
\\ }
\\ pre > code {
\\ color: #ccc;
\\ background: #222;
\\ border: unset;
\\ }
\\ .line:target {
\\ border-top: 1px solid #444;
\\ border-bottom: 1px solid #444;
\\ background: #333;
\\ }
\\ .tok-kw {
\\ color: #eee;
\\ }
\\ .tok-str {
\\ color: #2e5;
\\ }
\\ .tok-builtin {
\\ color: #ff894c;
\\ }
\\ .tok-comment {
\\ color: #aa7;
\\ }
\\ .tok-fn {
\\ color: #B1A0F8;
\\ }
\\ .tok-null {
\\ color: #ff8080;
\\ }
\\ .tok-number {
\\ color: #ff8080;
\\ }
\\ .tok-type {
\\ color: #68f;
\\ }
\\ }
\\ </style>
\\</head>
\\<body>
\\
);
const source = try src.getSource(allocator);
try tokenizeAndPrintRaw(out, source.bytes);
try out.writeAll(
\\</body>
\\</html>
);
}
const start_line = "<span class=\"line\" id=\"L{d}\">";
const end_line = "</span>\n";
var line_counter: usize = 1;
pub fn tokenizeAndPrintRaw(
out: anytype,
src: [:0]const u8,
) !void {
line_counter = 1;
try out.print("<pre><code>" ++ start_line, .{line_counter});
var tokenizer = std.zig.Tokenizer.init(src);
var index: usize = 0;
var next_tok_is_fn = false;
while (true) {
const prev_tok_was_fn = next_tok_is_fn;
next_tok_is_fn = false;
const token = tokenizer.next();
if (mem.indexOf(u8, src[index..token.loc.start], "//")) |comment_start_off| {
// render one comment
const comment_start = index + comment_start_off;
const comment_end_off = mem.indexOf(u8, src[comment_start..token.loc.start], "\n");
const comment_end = if (comment_end_off) |o| comment_start + o else token.loc.start;
try writeEscapedLines(out, src[index..comment_start]);
try out.writeAll("<span class=\"tok-comment\">");
try writeEscaped(out, src[comment_start..comment_end]);
try out.writeAll("</span>\n");
index = comment_end;
tokenizer.index = index;
continue;
}
try writeEscapedLines(out, src[index..token.loc.start]);
switch (token.tag) {
.eof => break,
.keyword_addrspace,
.keyword_align,
.keyword_and,
.keyword_asm,
.keyword_async,
.keyword_await,
.keyword_break,
.keyword_catch,
.keyword_comptime,
.keyword_const,
.keyword_continue,
.keyword_defer,
.keyword_else,
.keyword_enum,
.keyword_errdefer,
.keyword_error,
.keyword_export,
.keyword_extern,
.keyword_for,
.keyword_if,
.keyword_inline,
.keyword_noalias,
.keyword_noinline,
.keyword_nosuspend,
.keyword_opaque,
.keyword_or,
.keyword_orelse,
.keyword_packed,
.keyword_anyframe,
.keyword_pub,
.keyword_resume,
.keyword_return,
.keyword_linksection,
.keyword_callconv,
.keyword_struct,
.keyword_suspend,
.keyword_switch,
.keyword_test,
.keyword_threadlocal,
.keyword_try,
.keyword_union,
.keyword_unreachable,
.keyword_usingnamespace,
.keyword_var,
.keyword_volatile,
.keyword_allowzero,
.keyword_while,
.keyword_anytype,
=> {
try out.writeAll("<span class=\"tok-kw\">");
try writeEscaped(out, src[token.loc.start..token.loc.end]);
try out.writeAll("</span>");
},
.keyword_fn => {
try out.writeAll("<span class=\"tok-kw\">");
try writeEscaped(out, src[token.loc.start..token.loc.end]);
try out.writeAll("</span>");
next_tok_is_fn = true;
},
.string_literal,
.char_literal,
=> {
try out.writeAll("<span class=\"tok-str\">");
try writeEscaped(out, src[token.loc.start..token.loc.end]);
try out.writeAll("</span>");
},
.multiline_string_literal_line => {
if (src[token.loc.end - 1] == '\n') {
try out.writeAll("<span class=\"tok-str\">");
try writeEscaped(out, src[token.loc.start .. token.loc.end - 1]);
line_counter += 1;
try out.print("</span>" ++ end_line ++ "\n" ++ start_line, .{line_counter});
} else {
try out.writeAll("<span class=\"tok-str\">");
try writeEscaped(out, src[token.loc.start..token.loc.end]);
try out.writeAll("</span>");
}
},
.builtin => {
try out.writeAll("<span class=\"tok-builtin\">");
try writeEscaped(out, src[token.loc.start..token.loc.end]);
try out.writeAll("</span>");
},
.doc_comment,
.container_doc_comment,
=> {
try out.writeAll("<span class=\"tok-comment\">");
try writeEscaped(out, src[token.loc.start..token.loc.end]);
try out.writeAll("</span>");
},
.identifier => {
const tok_bytes = src[token.loc.start..token.loc.end];
if (mem.eql(u8, tok_bytes, "undefined") or
mem.eql(u8, tok_bytes, "null") or
mem.eql(u8, tok_bytes, "true") or
mem.eql(u8, tok_bytes, "false"))
{
try out.writeAll("<span class=\"tok-null\">");
try writeEscaped(out, tok_bytes);
try out.writeAll("</span>");
} else if (prev_tok_was_fn) {
try out.writeAll("<span class=\"tok-fn\">");
try writeEscaped(out, tok_bytes);
try out.writeAll("</span>");
} else {
const is_int = blk: {
if (src[token.loc.start] != 'i' and src[token.loc.start] != 'u')
break :blk false;
var i = token.loc.start + 1;
if (i == token.loc.end)
break :blk false;
while (i != token.loc.end) : (i += 1) {
if (src[i] < '0' or src[i] > '9')
break :blk false;
}
break :blk true;
};
if (is_int or isType(tok_bytes)) {
try out.writeAll("<span class=\"tok-type\">");
try writeEscaped(out, tok_bytes);
try out.writeAll("</span>");
} else {
try writeEscaped(out, tok_bytes);
}
}
},
.number_literal => {
try out.writeAll("<span class=\"tok-number\">");
try writeEscaped(out, src[token.loc.start..token.loc.end]);
try out.writeAll("</span>");
},
.bang,
.pipe,
.pipe_pipe,
.pipe_equal,
.equal,
.equal_equal,
.equal_angle_bracket_right,
.bang_equal,
.l_paren,
.r_paren,
.semicolon,
.percent,
.percent_equal,
.l_brace,
.r_brace,
.l_bracket,
.r_bracket,
.period,
.period_asterisk,
.ellipsis2,
.ellipsis3,
.caret,
.caret_equal,
.plus,
.plus_plus,
.plus_equal,
.plus_percent,
.plus_percent_equal,
.plus_pipe,
.plus_pipe_equal,
.minus,
.minus_equal,
.minus_percent,
.minus_percent_equal,
.minus_pipe,
.minus_pipe_equal,
.asterisk,
.asterisk_equal,
.asterisk_asterisk,
.asterisk_percent,
.asterisk_percent_equal,
.asterisk_pipe,
.asterisk_pipe_equal,
.arrow,
.colon,
.slash,
.slash_equal,
.comma,
.ampersand,
.ampersand_equal,
.question_mark,
.angle_bracket_left,
.angle_bracket_left_equal,
.angle_bracket_angle_bracket_left,
.angle_bracket_angle_bracket_left_equal,
.angle_bracket_angle_bracket_left_pipe,
.angle_bracket_angle_bracket_left_pipe_equal,
.angle_bracket_right,
.angle_bracket_right_equal,
.angle_bracket_angle_bracket_right,
.angle_bracket_angle_bracket_right_equal,
.tilde,
=> try writeEscaped(out, src[token.loc.start..token.loc.end]),
.invalid, .invalid_periodasterisks => return error.ParseError,
}
index = token.loc.end;
}
try out.writeAll(end_line ++ "</code></pre>");
}
fn writeEscapedLines(out: anytype, text: []const u8) !void {
for (text) |char| {
if (char == '\n') {
try out.writeAll(end_line);
line_counter += 1;
try out.print(start_line, .{line_counter});
} else {
try writeEscaped(out, &[_]u8{char});
}
}
}
fn writeEscaped(out: anytype, input: []const u8) !void {
for (input) |c| {
try switch (c) {
'&' => out.writeAll("&amp;"),
'<' => out.writeAll("&lt;"),
'>' => out.writeAll("&gt;"),
'"' => out.writeAll("&quot;"),
else => out.writeByte(c),
};
}
}
const builtin_types = [_][]const u8{
"f16", "f32", "f64", "f80", "f128",
"c_longdouble", "c_short", "c_ushort", "c_int", "c_uint",
"c_long", "c_ulong", "c_longlong", "c_ulonglong", "c_char",
"anyopaque", "void", "bool", "isize", "usize",
"noreturn", "type", "anyerror", "comptime_int", "comptime_float",
};
fn isType(name: []const u8) bool {
for (builtin_types) |t| {
if (mem.eql(u8, t, name))
return true;
}
return false;
}

View File

@ -98,6 +98,7 @@ const normal_usage =
\\
\\ env Print lib path, std path, cache directory, and version
\\ help Print this help and exit
\\ std View standard library documentation in a browser
\\ libc Display native libc paths file or validate one
\\ targets List available compilation targets
\\ version Print version number and exit
@ -309,6 +310,14 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
.root_src_path = "libc.zig",
.prepend_zig_lib_dir_path = true,
});
} else if (mem.eql(u8, cmd, "std")) {
return jitCmd(gpa, arena, cmd_args, .{
.cmd_name = "std",
.root_src_path = "std-docs.zig",
.prepend_zig_lib_dir_path = true,
.prepend_zig_exe_path = true,
.prepend_global_cache_path = true,
});
} else if (mem.eql(u8, cmd, "init")) {
return cmdInit(gpa, arena, cmd_args);
} else if (mem.eql(u8, cmd, "targets")) {
@ -5556,6 +5565,8 @@ const JitCmdOptions = struct {
cmd_name: []const u8,
root_src_path: []const u8,
prepend_zig_lib_dir_path: bool = false,
prepend_global_cache_path: bool = false,
prepend_zig_exe_path: bool = false,
depend_on_aro: bool = false,
capture: ?*[]u8 = null,
};
@ -5714,6 +5725,10 @@ fn jitCmd(
if (options.prepend_zig_lib_dir_path)
child_argv.appendAssumeCapacity(zig_lib_directory.path.?);
if (options.prepend_zig_exe_path)
child_argv.appendAssumeCapacity(self_exe_path);
if (options.prepend_global_cache_path)
child_argv.appendAssumeCapacity(global_cache_directory.path.?);
child_argv.appendSliceAssumeCapacity(args);