Something went wrong. Try again.
javascript for 🐜's, a tiny runtime with big ambitions antjs.org
Something went wrong. Try again.
Zig
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403const std = @import("std");const builtin = @import("builtin");
pub const MAGIC: u32 = 0x504B474C;pub const VERSION: u32 = 1;
pub const StringRef = extern struct { offset: u32, len: u32,
pub fn slice(self: StringRef, string_table: []const u8) []const u8 { const offset: usize = self.offset; const len: usize = self.len; if (offset >= string_table.len) return ""; const end = @min(offset + len, string_table.len); return string_table[offset..end]; }
pub const empty: StringRef = .{ .offset = 0, .len = 0 };};
pub const Header = extern struct { magic: u32 = MAGIC, version: u32 = VERSION, package_count: u32 = 0, dependency_count: u32 = 0, string_table_offset: u32 = 0, string_table_size: u32 = 0, package_array_offset: u32 = 0, dependency_array_offset: u32 = 0, hash_table_offset: u32 = 0, hash_table_size: u32 = 0, _reserved: [24]u8 = [_]u8{0} ** 24,
pub fn validate(self: *const Header) bool { return self.magic == MAGIC and self.version == VERSION; }};
pub const PackageFlags = packed struct(u8) { dev: bool = false, optional: bool = false, peer: bool = false, bundled: bool = false, has_bin: bool = false, has_scripts: bool = false, direct: bool = false, _reserved: u1 = 0,};
pub const Package = extern struct { name: StringRef, version_major: u64, version_minor: u64, version_patch: u64, prerelease: StringRef, integrity: [64]u8, tarball_url: StringRef, parent_path: StringRef, deps_start: u32, deps_count: u32, flags: PackageFlags, _padding: [3]u8 = .{ 0, 0, 0 },
comptime { std.debug.assert(@sizeOf(Package) == 136); }
pub fn versionString(self: *const Package, allocator: std.mem.Allocator, string_table: []const u8) ![]u8 { const prerelease_str = self.prerelease.slice(string_table); if (prerelease_str.len > 0) { return std.fmt.allocPrint(allocator, "{d}.{d}.{d}-{s}", .{ self.version_major, self.version_minor, self.version_patch, prerelease_str, }); } return std.fmt.allocPrint(allocator, "{d}.{d}.{d}", .{ self.version_major, self.version_minor, self.version_patch, }); }
pub fn integrityHex(self: *const Package) [128]u8 { return std.fmt.bytesToHex(self.integrity, .lower); }
pub fn installPath(self: *const Package, allocator: std.mem.Allocator, string_table: []const u8) ![]u8 { const parent = self.parent_path.slice(string_table); const name = self.name.slice(string_table); if (parent.len > 0) return std.fmt.allocPrint(allocator, "{s}/node_modules/{s}", .{ parent, name }); return allocator.dupe(u8, name); }
pub fn isNested(self: *const Package) bool { return self.parent_path.len > 0; }};
pub const DependencyFlags = packed struct(u8) { peer: bool = false, dev: bool = false, optional: bool = false, _reserved: u5 = 0,};
pub const Dependency = extern struct { package_index: u32, constraint: StringRef, flags: DependencyFlags = .{}, _padding: [3]u8 = .{ 0, 0, 0 },};
pub const HashBucket = extern struct { name_hash: u32, package_index: u32, pub const empty: HashBucket = .{ .name_hash = 0, .package_index = std.math.maxInt(u32) };};
pub fn djb2Hash(str: []const u8) u32 { var hash: u32 = 5381; for (str) |c| hash = ((hash << 5) +% hash) +% c; return hash;}
pub const Lockfile = struct { data: if (builtin.os.tag == .windows) []align(@alignOf(Header)) u8 else []align(std.heap.page_size_min) const u8, header: *const Header, string_table: []const u8, packages: []const Package, dependencies: []const Dependency, hash_table: []const HashBucket,
pub fn open(path: []const u8) !Lockfile { const file = try std.fs.cwd().openFile(path, .{}); defer file.close(); const stat = try file.stat(); if (stat.size < @sizeOf(Header)) { return error.InvalidLockfile; } if (comptime builtin.os.tag == .windows) { const data = try std.heap.c_allocator.alignedAlloc(u8, std.mem.Alignment.fromByteUnits(@alignOf(Header)), stat.size); errdefer std.heap.c_allocator.free(data); const bytes_read = try file.readAll(data); if (bytes_read != stat.size) { std.heap.c_allocator.free(data); return error.InvalidLockfile; } return initFromDataWindows(data); } else { const data = try std.posix.mmap( null, stat.size, std.posix.PROT.READ, .{ .TYPE = .PRIVATE }, file.handle, 0, ); return initFromData(data); } }
fn initFromDataWindows(data: []align(@alignOf(Header)) u8) !Lockfile { if (data.len < @sizeOf(Header)) return error.InvalidLockfile;
const header: *const Header = @ptrCast(@alignCast(data.ptr)); if (!header.validate()) return error.InvalidLockfile;
if (header.string_table_offset + header.string_table_size > data.len or header.package_array_offset + header.package_count * @sizeOf(Package) > data.len or header.dependency_array_offset + header.dependency_count * @sizeOf(Dependency) > data.len or header.hash_table_offset + header.hash_table_size * @sizeOf(HashBucket) > data.len) { return error.InvalidLockfile; }
return .{ .data = data, .header = header, .string_table = data[header.string_table_offset..][0..header.string_table_size], .packages = @as([*]const Package, @ptrCast(@alignCast(data.ptr + header.package_array_offset)))[0..header.package_count], .dependencies = @as([*]const Dependency, @ptrCast(@alignCast(data.ptr + header.dependency_array_offset)))[0..header.dependency_count], .hash_table = @as([*]const HashBucket, @ptrCast(@alignCast(data.ptr + header.hash_table_offset)))[0..header.hash_table_size], }; }
pub fn initFromData(data: []align(std.heap.page_size_min) const u8) !Lockfile { if (data.len < @sizeOf(Header)) { return error.InvalidLockfile; }
const header: *const Header = @ptrCast(@alignCast(data.ptr)); if (!header.validate()) { return error.InvalidLockfile; }
if (header.string_table_offset + header.string_table_size > data.len or header.package_array_offset + header.package_count * @sizeOf(Package) > data.len or header.dependency_array_offset + header.dependency_count * @sizeOf(Dependency) > data.len or header.hash_table_offset + header.hash_table_size * @sizeOf(HashBucket) > data.len) { return error.InvalidLockfile; }
const string_table = data[header.string_table_offset..][0..header.string_table_size];
const packages_ptr: [*]const Package = @ptrCast(@alignCast(data.ptr + header.package_array_offset)); const packages = packages_ptr[0..header.package_count];
const deps_ptr: [*]const Dependency = @ptrCast(@alignCast(data.ptr + header.dependency_array_offset)); const dependencies = deps_ptr[0..header.dependency_count];
const hash_ptr: [*]const HashBucket = @ptrCast(@alignCast(data.ptr + header.hash_table_offset)); const hash_table = hash_ptr[0..header.hash_table_size];
return .{ .data = data, .header = header, .string_table = string_table, .packages = packages, .dependencies = dependencies, .hash_table = hash_table, }; }
pub fn close(self: *Lockfile) void { if (comptime builtin.os.tag == .windows) { std.heap.c_allocator.free(self.data); } else std.posix.munmap(self.data); self.* = undefined; }
pub fn lookupPackage(self: *const Lockfile, name: []const u8) ?*const Package { if (self.hash_table.len == 0) return null;
const hash = djb2Hash(name); var index = hash % @as(u32, @intCast(self.hash_table.len)); var probes: u32 = 0;
while (probes < self.hash_table.len) : (probes += 1) { const bucket = &self.hash_table[index]; if (bucket.package_index == std.math.maxInt(u32)) return null; if (bucket.name_hash == hash) { const pkg = &self.packages[bucket.package_index]; const pkg_name = pkg.name.slice(self.string_table); if (std.mem.eql(u8, pkg_name, name)) return pkg; } index = (index + 1) % @as(u32, @intCast(self.hash_table.len)); } return null; }
pub fn getPackageDeps(self: *const Lockfile, pkg: *const Package) []const Dependency { if (pkg.deps_count == 0) return &[_]Dependency{}; if (pkg.deps_start >= self.dependencies.len or pkg.deps_start + pkg.deps_count > self.dependencies.len) return &[_]Dependency{}; return self.dependencies[pkg.deps_start..][0..pkg.deps_count]; }};
pub const LockfileWriter = struct { allocator: std.mem.Allocator, packages: std.ArrayListUnmanaged(Package), dependencies: std.ArrayListUnmanaged(Dependency), string_builder: std.ArrayListUnmanaged(u8), string_offsets: std.StringHashMap(StringRef),
pub fn init(allocator: std.mem.Allocator) LockfileWriter { return .{ .allocator = allocator, .packages = .{}, .dependencies = .{}, .string_builder = .{}, .string_offsets = std.StringHashMap(StringRef).init(allocator), }; }
pub fn deinit(self: *LockfileWriter) void { self.packages.deinit(self.allocator); self.dependencies.deinit(self.allocator); self.string_builder.deinit(self.allocator); var key_iter = self.string_offsets.keyIterator(); while (key_iter.next()) |key| { self.allocator.free(key.*); } self.string_offsets.deinit(); }
pub fn internString(self: *LockfileWriter, str: []const u8) !StringRef { if (str.len == 0) return StringRef.empty; if (self.string_offsets.get(str)) |ref| return ref;
if (self.string_builder.items.len > std.math.maxInt(u32)) return error.StringTableTooLarge; if (str.len > std.math.maxInt(u32)) return error.StringTooLarge;
const offset: u32 = @intCast(self.string_builder.items.len); try self.string_builder.appendSlice(self.allocator, str);
const ref = StringRef{ .offset = offset, .len = @intCast(str.len) }; const stored_str = try self.allocator.dupe(u8, str); errdefer self.allocator.free(stored_str); try self.string_offsets.put(stored_str, ref);
return ref; }
pub fn addPackage(self: *LockfileWriter, pkg: Package) !u32 { const index: u32 = @intCast(self.packages.items.len); try self.packages.append(self.allocator, pkg); return index; }
pub fn addDependency(self: *LockfileWriter, dep: Dependency) !void { try self.dependencies.append(self.allocator, dep); }
fn alignOffset(offset: u32, alignment: u32) u32 { const rem = offset % alignment; return if (rem == 0) offset else offset + (alignment - rem); }
pub fn write(self: *LockfileWriter, path: []const u8) !void { const file = try std.fs.cwd().createFile(path, .{}); defer file.close();
const header_size: u32 = @sizeOf(Header); const string_table_offset = header_size; const string_table_size: u32 = @intCast(self.string_builder.items.len);
const package_array_offset = alignOffset(string_table_offset + string_table_size, @alignOf(Package)); const package_pad_size = package_array_offset - (string_table_offset + string_table_size); const package_array_size: u32 = @intCast(self.packages.items.len * @sizeOf(Package));
const dependency_array_offset = alignOffset(package_array_offset + package_array_size, @alignOf(Dependency)); const dep_pad_size = dependency_array_offset - (package_array_offset + package_array_size); const dependency_array_size: u32 = @intCast(self.dependencies.items.len * @sizeOf(Dependency));
const hash_table_size: u32 = @intCast(@max(1, self.packages.items.len * 10 / 7)); var hash_table = try self.allocator.alloc(HashBucket, hash_table_size); defer self.allocator.free(hash_table);
for (hash_table) |*bucket| { bucket.* = HashBucket.empty; }
for (self.packages.items, 0..) |pkg, i| { const name = pkg.name.slice(self.string_builder.items); const hash = djb2Hash(name); var index = hash % hash_table_size; var probes: u32 = 0;
while (hash_table[index].package_index != std.math.maxInt(u32) and probes < hash_table_size) : (probes += 1) { index = (index + 1) % hash_table_size; } if (probes >= hash_table_size) return error.HashTableFull;
hash_table[index] = .{ .name_hash = hash, .package_index = @intCast(i), }; }
const hash_table_offset = alignOffset(dependency_array_offset + dependency_array_size, @alignOf(HashBucket)); const hash_pad_size = hash_table_offset - (dependency_array_offset + dependency_array_size);
const header = Header{ .package_count = @intCast(self.packages.items.len), .dependency_count = @intCast(self.dependencies.items.len), .string_table_offset = string_table_offset, .string_table_size = string_table_size, .package_array_offset = package_array_offset, .dependency_array_offset = dependency_array_offset, .hash_table_offset = hash_table_offset, .hash_table_size = hash_table_size, };
try file.writeAll(std.mem.asBytes(&header)); try file.writeAll(self.string_builder.items); if (package_pad_size > 0) { const padding = [_]u8{0} ** 8; try file.writeAll(padding[0..package_pad_size]); } try file.writeAll(std.mem.sliceAsBytes(self.packages.items)); if (dep_pad_size > 0) { const padding = [_]u8{0} ** 8; try file.writeAll(padding[0..dep_pad_size]); } try file.writeAll(std.mem.sliceAsBytes(self.dependencies.items)); if (hash_pad_size > 0) { const padding = [_]u8{0} ** 8; try file.writeAll(padding[0..hash_pad_size]); } try file.writeAll(std.mem.sliceAsBytes(hash_table)); }};