mirror of
https://github.com/ziglang/zig.git
synced 2026-02-21 00:35:10 +00:00
Merge pull request #19208 from ziglang/rework-autodoc
Redesign How Autodoc Works
This commit is contained in:
commit
d0c06ca712
5
.github/CODEOWNERS
vendored
5
.github/CODEOWNERS
vendored
@ -1,8 +1,3 @@
|
||||
# Autodoc
|
||||
/src/Autodoc.zig @kristoff-it
|
||||
/src/autodoc/* @kristoff-it
|
||||
/lib/docs/* @kristoff-it
|
||||
|
||||
# std.json
|
||||
/lib/std/json* @thejoshwolfe
|
||||
|
||||
|
||||
12
.github/ISSUE_TEMPLATE/autodoc-issue.md
vendored
12
.github/ISSUE_TEMPLATE/autodoc-issue.md
vendored
@ -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.
|
||||
@ -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}"
|
||||
|
||||
@ -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
384
lib/compiler/std-docs.zig
Normal 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();
|
||||
}
|
||||
10270
lib/docs/commonmark.js
10270
lib/docs/commonmark.js
File diff suppressed because it is too large
Load Diff
1364
lib/docs/index.html
1364
lib/docs/index.html
File diff suppressed because it is too large
Load Diff
6013
lib/docs/main.js
6013
lib/docs/main.js
File diff suppressed because it is too large
Load Diff
226
lib/docs/wasm/Decl.zig
Normal file
226
lib/docs/wasm/Decl.zig
Normal 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
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
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
940
lib/docs/wasm/markdown.zig
Normal 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("std");
|
||||
\\
|
||||
\\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(
|
||||
\\
|
||||
\\
|
||||
\\).png)
|
||||
\\escaped\(.png)
|
||||
\\
|
||||
\\
|
||||
,
|
||||
\\<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.
|
||||
\\> Not a blockquote.
|
||||
\\- Not a list item.
|
||||
\\| Not a table. |
|
||||
\\| Also not a table. |
|
||||
\\Any \punctuation\ characte\r can be escaped:
|
||||
\\!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~</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);
|
||||
}
|
||||
192
lib/docs/wasm/markdown/Document.zig
Normal file
192
lib/docs/wasm/markdown/Document.zig
Normal 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)));
|
||||
}
|
||||
1501
lib/docs/wasm/markdown/Parser.zig
Normal file
1501
lib/docs/wasm/markdown/Parser.zig
Normal file
File diff suppressed because it is too large
Load Diff
249
lib/docs/wasm/markdown/renderer.zig
Normal file
249
lib/docs/wasm/markdown/renderer.zig
Normal 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("<"),
|
||||
'>' => try writer.writeAll(">"),
|
||||
'&' => try writer.writeAll("&"),
|
||||
'"' => try writer.writeAll("""),
|
||||
else => try writer.writeByte(b),
|
||||
}
|
||||
}
|
||||
}
|
||||
2147
lib/docs/ziglexer.js
2147
lib/docs/ziglexer.js
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
//! Base64 encoding/decoding.
|
||||
|
||||
const std = @import("std.zig");
|
||||
const assert = std.debug.assert;
|
||||
const builtin = @import("builtin");
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
//! Compression algorithms.
|
||||
|
||||
const std = @import("std.zig");
|
||||
|
||||
pub const flate = @import("compress/flate.zig");
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
//! Cryptography.
|
||||
|
||||
const root = @import("root");
|
||||
|
||||
/// Authenticated Encryption with Associated Data
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
//! DWARF debugging data format.
|
||||
|
||||
const builtin = @import("builtin");
|
||||
const std = @import("std.zig");
|
||||
const debug = std.debug;
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
//! Executable and Linkable Format.
|
||||
|
||||
const std = @import("std.zig");
|
||||
const math = std.math;
|
||||
const mem = std.mem;
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
//! String formatting and parsing.
|
||||
|
||||
const std = @import("std.zig");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
//! File System.
|
||||
|
||||
const std = @import("std.zig");
|
||||
const builtin = @import("builtin");
|
||||
const root = @import("root");
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
//! Cross-platform networking abstractions.
|
||||
|
||||
const std = @import("std.zig");
|
||||
const builtin = @import("builtin");
|
||||
const assert = std.debug.assert;
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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
85
lib/std/tar/output.zig
Normal 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;
|
||||
@ -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");
|
||||
|
||||
6035
src/Autodoc.zig
6035
src/Autodoc.zig
File diff suppressed because it is too large
Load Diff
@ -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 {
|
||||
|
||||
@ -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("&"),
|
||||
'<' => out.writeAll("<"),
|
||||
'>' => out.writeAll(">"),
|
||||
'"' => out.writeAll("""),
|
||||
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;
|
||||
}
|
||||
15
src/main.zig
15
src/main.zig
@ -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);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user