Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
zig-out/
.zig-cache/
/result*
10 changes: 10 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,19 @@ pub fn build(b: *std.Build) void {
});
const run_build_readme_tests = b.addRunArtifact(build_readme_tests);

const symbol_resolution_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("src/test_symbol_resolution.zig"),
.target = target,
.optimize = optimize,
}),
});
const run_symbol_resolution_tests = b.addRunArtifact(symbol_resolution_tests);

const test_step = b.step("test", "Run tests");
test_step.dependOn(&run_exe_tests.step);
test_step.dependOn(&run_build_readme_tests.step);
test_step.dependOn(&run_symbol_resolution_tests.step);

const fmt_step = b.step("fmt", "Check code formatting");
const fmt_check = b.addFmt(.{
Expand Down
4 changes: 2 additions & 2 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
.minimum_zig_version = "0.16.0",
.dependencies = .{
.ziglint = .{
.url = "git+https://github.com/EugOT/ziglint.git?ref=fix/0.16-build-compat#40f26f1283468032ec993c93fca921bd3c106adb",
.hash = "ziglint-0.5.2-t0bwLz2XBQCgnA0PTY0KM9YgrmdjLU7wwjXablNijOuq",
.url = "git+https://github.com/EugOT/ziglint.git?ref=main#a2fa56aa6a26181f9d6f586203fe65fc6c4c11c9",
.hash = "ziglint-0.5.2-t0bwLzCwBQA69qkf9xumLCiu4N7jqxuzjBy8Abpga7v9",
},
},
.paths = .{
Expand Down
27 changes: 27 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

69 changes: 69 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
description = "zigdoc";

inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";

outputs =
{
self,
nixpkgs,
}:
let
systems = [
"aarch64-darwin"
"aarch64-linux"
"x86_64-darwin"
"x86_64-linux"
];
forAllSystems = nixpkgs.lib.genAttrs systems;
pkgsFor = system: import nixpkgs { inherit system; };
in
{
devShells = forAllSystems (
system:
let
pkgs = pkgsFor system;
zig = pkgs.zig_0_16 or pkgs.zig;
in
{
default = pkgs.mkShell {
packages = [
zig
];
};
}
);

packages = forAllSystems (
system:
let
pkgs = pkgsFor system;
zig = pkgs.zig_0_16 or pkgs.zig;
in
{
default = pkgs.stdenv.mkDerivation {
pname = "zigdoc";
version = "0.0.0";
src = self;

nativeBuildInputs = [
zig.hook
];

zigBuildFlags = [
"-Dcpu=baseline"
"-Doptimize=ReleaseFast"
];

installPhase = ''
runHook preInstall
install -Dm755 zig-out/bin/zigdoc $out/bin/zigdoc
runHook postInstall
'';
};
}
);

formatter = forAllSystems (system: (pkgsFor system).nixfmt);
};
}
2 changes: 2 additions & 0 deletions mise.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[tools]
zig = "0.16"
117 changes: 101 additions & 16 deletions src/Walk.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ const Ast = std.zig.Ast;
const assert = std.debug.assert;
const log = std.log;
var gpa: std.mem.Allocator = undefined;
var io_global: std.Io = undefined;
var io: std.Io = undefined;

pub fn init(allocator: std.mem.Allocator, io: std.Io) void {
pub fn init(allocator: std.mem.Allocator, io_arg: std.Io) void {
gpa = allocator;
io_global = io;
io = io_arg;
}

const Oom = error{OutOfMemory};
Expand All @@ -21,6 +21,21 @@ pub var files: std.array_hash_map.String(File) = .empty;
pub var decls: std.ArrayList(Decl) = .empty;
pub var modules: std.array_hash_map.String(File.Index) = .empty;

pub fn deinit() void {
for (files.values()) |*file| {
file.deinit();
}
for (files.keys()) |key| {
gpa.free(key);
}
files.deinit(gpa);
decls.deinit(gpa);
modules.deinit(gpa);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
files = .empty;
decls = .empty;
modules = .empty;
}

file: File.Index,

/// keep in sync with "CAT_" constants in main.js
Expand Down Expand Up @@ -69,6 +84,23 @@ pub const File = struct {
/// struct/union/enum/opaque decl node => its namespace scope
/// local var decl node => its local variable scope
scopes: std.AutoArrayHashMapUnmanaged(Ast.Node.Index, *Scope) = .empty,
top_scope: ?*Scope = null,

fn deinit(file: *File) void {
file.ast.deinit(gpa);
file.ident_decls.deinit(gpa);
file.token_parents.deinit(gpa);
file.node_decls.deinit(gpa);
file.doctests.deinit(gpa);
for (file.scopes.values()) |scope| {
scope.destroy();
}
if (file.top_scope) |scope| {
scope.destroy();
}
file.scopes.deinit(gpa);
file.* = undefined;
}

pub fn lookupToken(file: *File, token: Ast.TokenIndex) Decl.Index {
const decl_node = file.ident_decls.get(token) orelse return .none;
Expand Down Expand Up @@ -339,19 +371,22 @@ pub const File = struct {
return Category.makeAlias(File.Index.findRootDecl(imported_file_index), node);
}

const resolved_path = if (std.fs.path.isAbsolute(file_path))
std.Io.Dir.cwd().realPathFileAlloc(io_global, file_path, gpa) catch file_path
else blk: {
// Uniform ownership: `resolved_path` is always a fresh plain
// []u8. The realpath result is a [:0]u8 sentinel slice that
// must be freed with its exact type (see addFile), so re-dupe
// it instead of storing/freeing the coerced slice.
const resolved_path = if (std.fs.path.isAbsolute(file_path)) blk: {
const real = std.Io.Dir.realPathFileAbsoluteAlloc(io, file_path, gpa) catch
break :blk gpa.dupe(u8, file_path) catch @panic("OOM");
defer gpa.free(real);
break :blk gpa.dupe(u8, real) catch @panic("OOM");
} else blk: {
const base_path = file_index.path();
break :blk std.fs.path.resolve(gpa, &.{
base_path, "..", file_path,
}) catch @panic("OOM");
};
defer {
if (!std.fs.path.isAbsolute(file_path) or resolved_path.ptr != file_path.ptr) {
gpa.free(resolved_path);
}
}
defer gpa.free(resolved_path);

log.debug("from '{s}' @import '{s}' resolved='{s}'", .{
file_index.path(), file_path, resolved_path,
Expand All @@ -363,7 +398,7 @@ pub const File = struct {
);
} else {
const import_content = std.Io.Dir.cwd().readFileAlloc(
io_global,
io,
resolved_path,
gpa,
.limited(10 * 1024 * 1024),
Expand Down Expand Up @@ -434,21 +469,44 @@ pub fn addFile(file_name: []const u8, bytes: []u8) !File.Index {
const ast = try parse(file_name, bytes);
assert(ast.errors.len == 0);

const normalized_path = std.Io.Dir.cwd().realPathFileAlloc(io_global, file_name, gpa) catch file_name;
// realPathFile*Alloc return sentinel slices ([:0]u8 — dupeZ allocates
// N+1 bytes). Re-dupe to a plain []u8 and free the sentinel original
// with its exact type, so `deinit` can later free every key with the
// correct allocation size (a [:0] key freed as []const u8 trips the
// DebugAllocator size check).
const normalized_path = blk: {
if (std.fs.path.isAbsolute(file_name)) {
if (std.Io.Dir.realPathFileAbsoluteAlloc(io, file_name, gpa)) |real| {
defer gpa.free(real);
break :blk try gpa.dupe(u8, real);
} else |_| {}
} else {
if (std.Io.Dir.cwd().realPathFileAlloc(io, file_name, gpa)) |real| {
defer gpa.free(real);
break :blk try gpa.dupe(u8, real);
} else |_| {}
}
break :blk try gpa.dupe(u8, file_name);
};

// Check if this file already exists to avoid duplicate entries
if (files.getIndex(normalized_path)) |existing_index| {
gpa.free(normalized_path);
return @enumFromInt(existing_index);
}

const file_index: File.Index = @enumFromInt(files.entries.len);
try files.put(gpa, normalized_path, .{ .ast = ast });
files.put(gpa, normalized_path, .{ .ast = ast }) catch |err| {
gpa.free(normalized_path);
return err;
};

var w: Walk = .{
.file = file_index,
};
const scope = try gpa.create(Scope);
scope.* = .{ .tag = .top };
file_index.get().top_scope = scope;

const decl_index = try file_index.addDecl(.root, .none);
try structDecl(&w, scope, decl_index, .root, ast.containerDeclRoot());
Expand Down Expand Up @@ -518,11 +576,38 @@ pub const Scope = struct {
const Namespace = struct {
base: Scope = .{ .tag = .namespace },
parent: *Scope,
names: std.array_hash_map.String(Ast.Node.Index) = .empty,
doctests: std.array_hash_map.String(Ast.Node.Index) = .empty,
names: std.StringArrayHashMapUnmanaged(Ast.Node.Index) = .empty,
doctests: std.StringArrayHashMapUnmanaged(Ast.Node.Index) = .empty,
decl_index: Decl.Index,

fn deinit(namespace: *Namespace) void {
namespace.names.deinit(gpa);
namespace.doctests.deinit(gpa);
namespace.* = undefined;
}
};

fn destroy(self: *Scope) void {
switch (self.tag) {
.top => {
self.* = undefined;
gpa.destroy(self);
},
.local => {
const local: *Local = @alignCast(@fieldParentPtr("base", self));
self.* = undefined;
local.* = undefined;
gpa.destroy(local);
},
.namespace => {
const namespace: *Namespace = @alignCast(@fieldParentPtr("base", self));
namespace.deinit();
self.* = undefined;
gpa.destroy(namespace);
},
}
}

fn getNamespaceDecl(start_scope: *Scope) Decl.Index {
var it: *Scope = start_scope;
while (true) switch (it.tag) {
Expand Down
Loading