self-hosted: watch files and trigger a rebuild
This commit is contained in:
109
std/build.zig
109
std/build.zig
@@ -424,60 +424,69 @@ pub const Builder = struct {
|
||||
return mode;
|
||||
}
|
||||
|
||||
pub fn addUserInputOption(self: *Builder, name: []const u8, value: []const u8) bool {
|
||||
if (self.user_input_options.put(name, UserInputOption{
|
||||
.name = name,
|
||||
.value = UserValue{ .Scalar = value },
|
||||
.used = false,
|
||||
}) catch unreachable) |*prev_value| {
|
||||
// option already exists
|
||||
switch (prev_value.value) {
|
||||
UserValue.Scalar => |s| {
|
||||
// turn it into a list
|
||||
var list = ArrayList([]const u8).init(self.allocator);
|
||||
list.append(s) catch unreachable;
|
||||
list.append(value) catch unreachable;
|
||||
_ = self.user_input_options.put(name, UserInputOption{
|
||||
.name = name,
|
||||
.value = UserValue{ .List = list },
|
||||
.used = false,
|
||||
}) catch unreachable;
|
||||
},
|
||||
UserValue.List => |*list| {
|
||||
// append to the list
|
||||
list.append(value) catch unreachable;
|
||||
_ = self.user_input_options.put(name, UserInputOption{
|
||||
.name = name,
|
||||
.value = UserValue{ .List = list.* },
|
||||
.used = false,
|
||||
}) catch unreachable;
|
||||
},
|
||||
UserValue.Flag => {
|
||||
warn("Option '-D{}={}' conflicts with flag '-D{}'.\n", name, value, name);
|
||||
return true;
|
||||
},
|
||||
}
|
||||
pub fn addUserInputOption(self: *Builder, name: []const u8, value: []const u8) !bool {
|
||||
const gop = try self.user_input_options.getOrPut(name);
|
||||
if (!gop.found_existing) {
|
||||
gop.kv.value = UserInputOption{
|
||||
.name = name,
|
||||
.value = UserValue{ .Scalar = value },
|
||||
.used = false,
|
||||
};
|
||||
return false;
|
||||
}
|
||||
|
||||
// option already exists
|
||||
switch (gop.kv.value.value) {
|
||||
UserValue.Scalar => |s| {
|
||||
// turn it into a list
|
||||
var list = ArrayList([]const u8).init(self.allocator);
|
||||
list.append(s) catch unreachable;
|
||||
list.append(value) catch unreachable;
|
||||
_ = self.user_input_options.put(name, UserInputOption{
|
||||
.name = name,
|
||||
.value = UserValue{ .List = list },
|
||||
.used = false,
|
||||
}) catch unreachable;
|
||||
},
|
||||
UserValue.List => |*list| {
|
||||
// append to the list
|
||||
list.append(value) catch unreachable;
|
||||
_ = self.user_input_options.put(name, UserInputOption{
|
||||
.name = name,
|
||||
.value = UserValue{ .List = list.* },
|
||||
.used = false,
|
||||
}) catch unreachable;
|
||||
},
|
||||
UserValue.Flag => {
|
||||
warn("Option '-D{}={}' conflicts with flag '-D{}'.\n", name, value, name);
|
||||
return true;
|
||||
},
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn addUserInputFlag(self: *Builder, name: []const u8) bool {
|
||||
if (self.user_input_options.put(name, UserInputOption{
|
||||
.name = name,
|
||||
.value = UserValue{ .Flag = {} },
|
||||
.used = false,
|
||||
}) catch unreachable) |*prev_value| {
|
||||
switch (prev_value.value) {
|
||||
UserValue.Scalar => |s| {
|
||||
warn("Flag '-D{}' conflicts with option '-D{}={}'.\n", name, name, s);
|
||||
return true;
|
||||
},
|
||||
UserValue.List => {
|
||||
warn("Flag '-D{}' conflicts with multiple options of the same name.\n", name);
|
||||
return true;
|
||||
},
|
||||
UserValue.Flag => {},
|
||||
}
|
||||
pub fn addUserInputFlag(self: *Builder, name: []const u8) !bool {
|
||||
const gop = try self.user_input_options.getOrPut(name);
|
||||
if (!gop.found_existing) {
|
||||
gop.kv.value = UserInputOption{
|
||||
.name = name,
|
||||
.value = UserValue{ .Flag = {} },
|
||||
.used = false,
|
||||
};
|
||||
return false;
|
||||
}
|
||||
|
||||
// option already exists
|
||||
switch (gop.kv.value.value) {
|
||||
UserValue.Scalar => |s| {
|
||||
warn("Flag '-D{}' conflicts with option '-D{}={}'.\n", name, name, s);
|
||||
return true;
|
||||
},
|
||||
UserValue.List => {
|
||||
warn("Flag '-D{}' conflicts with multiple options of the same name.\n", name);
|
||||
return true;
|
||||
},
|
||||
UserValue.Flag => {},
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
264
std/event/fs.zig
264
std/event/fs.zig
@@ -367,109 +367,193 @@ pub async fn readFile(loop: *event.Loop, file_path: []const u8, max_size: usize)
|
||||
}
|
||||
}
|
||||
|
||||
pub const Watch = struct {
|
||||
channel: *event.Channel(Event),
|
||||
putter: promise,
|
||||
pub fn Watch(comptime V: type) type {
|
||||
return struct {
|
||||
channel: *event.Channel(Event),
|
||||
putter: promise,
|
||||
wd_table: WdTable,
|
||||
table_lock: event.Lock,
|
||||
inotify_fd: i32,
|
||||
|
||||
pub const Event = union(enum) {
|
||||
CloseWrite,
|
||||
Err: Error,
|
||||
};
|
||||
const WdTable = std.AutoHashMap(i32, Dir);
|
||||
const FileTable = std.AutoHashMap([]const u8, V);
|
||||
|
||||
pub const Error = error{
|
||||
UserResourceLimitReached,
|
||||
SystemResources,
|
||||
};
|
||||
const Self = this;
|
||||
|
||||
pub fn destroy(self: *Watch) void {
|
||||
// TODO https://github.com/ziglang/zig/issues/1261
|
||||
cancel self.putter;
|
||||
}
|
||||
};
|
||||
const Dir = struct {
|
||||
dirname: []const u8,
|
||||
file_table: FileTable,
|
||||
};
|
||||
|
||||
pub fn watchFile(loop: *event.Loop, file_path: []const u8) !*Watch {
|
||||
const path_with_null = try std.cstr.addNullByte(loop.allocator, file_path);
|
||||
defer loop.allocator.free(path_with_null);
|
||||
pub const Event = union(enum) {
|
||||
CloseWrite: V,
|
||||
Err: Error,
|
||||
|
||||
const inotify_fd = try os.linuxINotifyInit1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC);
|
||||
errdefer os.close(inotify_fd);
|
||||
pub const Error = error{
|
||||
UserResourceLimitReached,
|
||||
SystemResources,
|
||||
};
|
||||
};
|
||||
|
||||
const wd = try os.linuxINotifyAddWatchC(inotify_fd, path_with_null.ptr, os.linux.IN_CLOSE_WRITE);
|
||||
errdefer os.close(wd);
|
||||
pub fn create(loop: *event.Loop, event_buf_count: usize) !*Self {
|
||||
const inotify_fd = try os.linuxINotifyInit1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC);
|
||||
errdefer os.close(inotify_fd);
|
||||
|
||||
const channel = try event.Channel(Watch.Event).create(loop, 0);
|
||||
errdefer channel.destroy();
|
||||
const channel = try event.Channel(Self.Event).create(loop, event_buf_count);
|
||||
errdefer channel.destroy();
|
||||
|
||||
var result: *Watch = undefined;
|
||||
_ = try async<loop.allocator> watchEventPutter(inotify_fd, wd, channel, &result);
|
||||
return result;
|
||||
}
|
||||
var result: *Self = undefined;
|
||||
_ = try async<loop.allocator> eventPutter(inotify_fd, channel, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
async fn watchEventPutter(inotify_fd: i32, wd: i32, channel: *event.Channel(Watch.Event), out_watch: **Watch) void {
|
||||
// TODO https://github.com/ziglang/zig/issues/1194
|
||||
suspend {
|
||||
resume @handle();
|
||||
}
|
||||
pub fn destroy(self: *Self) void {
|
||||
cancel self.putter;
|
||||
}
|
||||
|
||||
var watch = Watch{
|
||||
.putter = @handle(),
|
||||
.channel = channel,
|
||||
};
|
||||
out_watch.* = &watch;
|
||||
pub async fn addFile(self: *Self, file_path: []const u8, value: V) !?V {
|
||||
const dirname = os.path.dirname(file_path) orelse ".";
|
||||
const dirname_with_null = try std.cstr.addNullByte(self.channel.loop.allocator, dirname);
|
||||
var dirname_with_null_consumed = false;
|
||||
defer if (!dirname_with_null_consumed) self.channel.loop.allocator.free(dirname_with_null);
|
||||
|
||||
const loop = channel.loop;
|
||||
loop.beginOneEvent();
|
||||
const basename = os.path.basename(file_path);
|
||||
const basename_with_null = try std.cstr.addNullByte(self.channel.loop.allocator, basename);
|
||||
var basename_with_null_consumed = false;
|
||||
defer if (!basename_with_null_consumed) self.channel.loop.allocator.free(basename_with_null);
|
||||
|
||||
defer {
|
||||
channel.destroy();
|
||||
os.close(wd);
|
||||
os.close(inotify_fd);
|
||||
loop.finishOneEvent();
|
||||
}
|
||||
const wd = try os.linuxINotifyAddWatchC(
|
||||
self.inotify_fd,
|
||||
dirname_with_null.ptr,
|
||||
os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_EXCL_UNLINK,
|
||||
);
|
||||
// wd is either a newly created watch or an existing one.
|
||||
|
||||
var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined;
|
||||
const held = await (async self.table_lock.acquire() catch unreachable);
|
||||
defer held.release();
|
||||
|
||||
while (true) {
|
||||
const rc = os.linux.read(inotify_fd, &event_buf, event_buf.len);
|
||||
const errno = os.linux.getErrno(rc);
|
||||
switch (errno) {
|
||||
0 => {
|
||||
// can't use @bytesToSlice because of the special variable length name field
|
||||
var ptr = event_buf[0..].ptr;
|
||||
const end_ptr = ptr + event_buf.len;
|
||||
var ev: *os.linux.inotify_event = undefined;
|
||||
while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += @sizeOf(os.linux.inotify_event) + ev.len) {
|
||||
ev = @ptrCast(*os.linux.inotify_event, ptr);
|
||||
if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) {
|
||||
await (async channel.put(Watch.Event.CloseWrite) catch unreachable);
|
||||
const gop = try self.wd_table.getOrPut(wd);
|
||||
if (!gop.found_existing) {
|
||||
gop.kv.value = Dir{
|
||||
.dirname = dirname_with_null,
|
||||
.file_table = FileTable.init(self.channel.loop.allocator),
|
||||
};
|
||||
dirname_with_null_consumed = true;
|
||||
}
|
||||
const dir = &gop.kv.value;
|
||||
|
||||
const file_table_gop = try dir.file_table.getOrPut(basename_with_null);
|
||||
if (file_table_gop.found_existing) {
|
||||
const prev_value = file_table_gop.kv.value;
|
||||
file_table_gop.kv.value = value;
|
||||
return prev_value;
|
||||
} else {
|
||||
file_table_gop.kv.value = value;
|
||||
basename_with_null_consumed = true;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn removeFile(self: *Self, file_path: []const u8) ?V {
|
||||
@panic("TODO");
|
||||
}
|
||||
|
||||
async fn eventPutter(inotify_fd: i32, channel: *event.Channel(Event), out_watch: **Self) void {
|
||||
// TODO https://github.com/ziglang/zig/issues/1194
|
||||
suspend {
|
||||
resume @handle();
|
||||
}
|
||||
|
||||
const loop = channel.loop;
|
||||
|
||||
var watch = Self{
|
||||
.putter = @handle(),
|
||||
.channel = channel,
|
||||
.wd_table = WdTable.init(loop.allocator),
|
||||
.table_lock = event.Lock.init(loop),
|
||||
.inotify_fd = inotify_fd,
|
||||
};
|
||||
out_watch.* = &watch;
|
||||
|
||||
loop.beginOneEvent();
|
||||
|
||||
defer {
|
||||
watch.table_lock.deinit();
|
||||
{
|
||||
var wd_it = watch.wd_table.iterator();
|
||||
while (wd_it.next()) |wd_entry| {
|
||||
var file_it = wd_entry.value.file_table.iterator();
|
||||
while (file_it.next()) |file_entry| {
|
||||
loop.allocator.free(file_entry.key);
|
||||
}
|
||||
loop.allocator.free(wd_entry.value.dirname);
|
||||
}
|
||||
}
|
||||
},
|
||||
os.linux.EINTR => continue,
|
||||
os.linux.EINVAL => unreachable,
|
||||
os.linux.EFAULT => unreachable,
|
||||
os.linux.EAGAIN => {
|
||||
(await (async loop.linuxWaitFd(
|
||||
inotify_fd,
|
||||
os.linux.EPOLLET | os.linux.EPOLLIN,
|
||||
) catch unreachable)) catch |err| {
|
||||
const transformed_err = switch (err) {
|
||||
error.InvalidFileDescriptor => unreachable,
|
||||
error.FileDescriptorAlreadyPresentInSet => unreachable,
|
||||
error.InvalidSyscall => unreachable,
|
||||
error.OperationCausesCircularLoop => unreachable,
|
||||
error.FileDescriptorNotRegistered => unreachable,
|
||||
error.SystemResources => error.SystemResources,
|
||||
error.UserResourceLimitReached => error.UserResourceLimitReached,
|
||||
error.FileDescriptorIncompatibleWithEpoll => unreachable,
|
||||
error.Unexpected => unreachable,
|
||||
};
|
||||
await (async channel.put(Watch.Event{ .Err = transformed_err }) catch unreachable);
|
||||
};
|
||||
},
|
||||
else => unreachable,
|
||||
loop.finishOneEvent();
|
||||
os.close(inotify_fd);
|
||||
channel.destroy();
|
||||
}
|
||||
|
||||
var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined;
|
||||
|
||||
while (true) {
|
||||
const rc = os.linux.read(inotify_fd, &event_buf, event_buf.len);
|
||||
const errno = os.linux.getErrno(rc);
|
||||
switch (errno) {
|
||||
0 => {
|
||||
// can't use @bytesToSlice because of the special variable length name field
|
||||
var ptr = event_buf[0..].ptr;
|
||||
const end_ptr = ptr + event_buf.len;
|
||||
var ev: *os.linux.inotify_event = undefined;
|
||||
while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += @sizeOf(os.linux.inotify_event) + ev.len) {
|
||||
ev = @ptrCast(*os.linux.inotify_event, ptr);
|
||||
if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) {
|
||||
const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);
|
||||
const basename_with_null = basename_ptr[0 .. std.cstr.len(basename_ptr) + 1];
|
||||
const user_value = blk: {
|
||||
const held = await (async watch.table_lock.acquire() catch unreachable);
|
||||
defer held.release();
|
||||
|
||||
const dir = &watch.wd_table.get(ev.wd).?.value;
|
||||
if (dir.file_table.get(basename_with_null)) |entry| {
|
||||
break :blk entry.value;
|
||||
} else {
|
||||
break :blk null;
|
||||
}
|
||||
};
|
||||
if (user_value) |v| {
|
||||
await (async channel.put(Self.Event{ .CloseWrite = v }) catch unreachable);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
os.linux.EINTR => continue,
|
||||
os.linux.EINVAL => unreachable,
|
||||
os.linux.EFAULT => unreachable,
|
||||
os.linux.EAGAIN => {
|
||||
(await (async loop.linuxWaitFd(
|
||||
inotify_fd,
|
||||
os.linux.EPOLLET | os.linux.EPOLLIN,
|
||||
) catch unreachable)) catch |err| {
|
||||
const transformed_err = switch (err) {
|
||||
error.InvalidFileDescriptor => unreachable,
|
||||
error.FileDescriptorAlreadyPresentInSet => unreachable,
|
||||
error.InvalidSyscall => unreachable,
|
||||
error.OperationCausesCircularLoop => unreachable,
|
||||
error.FileDescriptorNotRegistered => unreachable,
|
||||
error.SystemResources => error.SystemResources,
|
||||
error.UserResourceLimitReached => error.UserResourceLimitReached,
|
||||
error.FileDescriptorIncompatibleWithEpoll => unreachable,
|
||||
error.Unexpected => unreachable,
|
||||
};
|
||||
await (async channel.put(Self.Event{ .Err = transformed_err }) catch unreachable);
|
||||
};
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const test_tmp_dir = "std_event_fs_test";
|
||||
@@ -517,9 +601,11 @@ async fn testFsWatch(loop: *event.Loop) !void {
|
||||
assert(mem.eql(u8, read_contents, contents));
|
||||
|
||||
// now watch the file
|
||||
var watch = try watchFile(loop, file_path);
|
||||
var watch = try Watch(void).create(loop, 0);
|
||||
defer watch.destroy();
|
||||
|
||||
assert((try await try async watch.addFile(file_path, {})) == null);
|
||||
|
||||
const ev = try async watch.channel.get();
|
||||
var ev_consumed = false;
|
||||
defer if (!ev_consumed) cancel ev;
|
||||
@@ -534,8 +620,8 @@ async fn testFsWatch(loop: *event.Loop) !void {
|
||||
|
||||
ev_consumed = true;
|
||||
switch (await ev) {
|
||||
Watch.Event.CloseWrite => {},
|
||||
Watch.Event.Err => |err| return err,
|
||||
Watch(void).Event.CloseWrite => {},
|
||||
Watch(void).Event.Err => |err| return err,
|
||||
}
|
||||
|
||||
const contents_updated = try await try async readFile(loop, file_path, 1024 * 1024);
|
||||
|
||||
@@ -10,6 +10,8 @@ const Loop = std.event.Loop;
|
||||
/// Does not make any syscalls - coroutines which are waiting for the lock are suspended, and
|
||||
/// are resumed when the lock is released, in order.
|
||||
/// Many readers can hold the lock at the same time; however locking for writing is exclusive.
|
||||
/// When a read lock is held, it will not be released until the reader queue is empty.
|
||||
/// When a write lock is held, it will not be released until the writer queue is empty.
|
||||
pub const RwLock = struct {
|
||||
loop: *Loop,
|
||||
shared_state: u8, // TODO make this an enum
|
||||
|
||||
300
std/hash_map.zig
300
std/hash_map.zig
@@ -9,6 +9,10 @@ const builtin = @import("builtin");
|
||||
const want_modification_safety = builtin.mode != builtin.Mode.ReleaseFast;
|
||||
const debug_u32 = if (want_modification_safety) u32 else void;
|
||||
|
||||
pub fn AutoHashMap(comptime K: type, comptime V: type) type {
|
||||
return HashMap(K, V, getAutoHashFn(K), getAutoEqlFn(K));
|
||||
}
|
||||
|
||||
pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u32, comptime eql: fn (a: K, b: K) bool) type {
|
||||
return struct {
|
||||
entries: []Entry,
|
||||
@@ -20,13 +24,22 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3
|
||||
|
||||
const Self = this;
|
||||
|
||||
pub const Entry = struct {
|
||||
used: bool,
|
||||
distance_from_start_index: usize,
|
||||
pub const KV = struct {
|
||||
key: K,
|
||||
value: V,
|
||||
};
|
||||
|
||||
const Entry = struct {
|
||||
used: bool,
|
||||
distance_from_start_index: usize,
|
||||
kv: KV,
|
||||
};
|
||||
|
||||
pub const GetOrPutResult = struct {
|
||||
kv: *KV,
|
||||
found_existing: bool,
|
||||
};
|
||||
|
||||
pub const Iterator = struct {
|
||||
hm: *const Self,
|
||||
// how many items have we returned
|
||||
@@ -36,7 +49,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3
|
||||
// used to detect concurrent modification
|
||||
initial_modification_count: debug_u32,
|
||||
|
||||
pub fn next(it: *Iterator) ?*Entry {
|
||||
pub fn next(it: *Iterator) ?*KV {
|
||||
if (want_modification_safety) {
|
||||
assert(it.initial_modification_count == it.hm.modification_count); // concurrent modification
|
||||
}
|
||||
@@ -46,7 +59,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3
|
||||
if (entry.used) {
|
||||
it.index += 1;
|
||||
it.count += 1;
|
||||
return entry;
|
||||
return &entry.kv;
|
||||
}
|
||||
}
|
||||
unreachable; // no next item
|
||||
@@ -71,7 +84,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(hm: *const Self) void {
|
||||
pub fn deinit(hm: Self) void {
|
||||
hm.allocator.free(hm.entries);
|
||||
}
|
||||
|
||||
@@ -84,34 +97,65 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3
|
||||
hm.incrementModificationCount();
|
||||
}
|
||||
|
||||
pub fn count(hm: *const Self) usize {
|
||||
return hm.size;
|
||||
pub fn count(self: Self) usize {
|
||||
return self.size;
|
||||
}
|
||||
|
||||
/// Returns the value that was already there.
|
||||
pub fn put(hm: *Self, key: K, value: *const V) !?V {
|
||||
if (hm.entries.len == 0) {
|
||||
try hm.initCapacity(16);
|
||||
/// If key exists this function cannot fail.
|
||||
/// If there is an existing item with `key`, then the result
|
||||
/// kv pointer points to it, and found_existing is true.
|
||||
/// Otherwise, puts a new item with undefined value, and
|
||||
/// the kv pointer points to it. Caller should then initialize
|
||||
/// the data.
|
||||
pub fn getOrPut(self: *Self, key: K) !GetOrPutResult {
|
||||
// TODO this implementation can be improved - we should only
|
||||
// have to hash once and find the entry once.
|
||||
if (self.get(key)) |kv| {
|
||||
return GetOrPutResult{
|
||||
.kv = kv,
|
||||
.found_existing = true,
|
||||
};
|
||||
}
|
||||
self.incrementModificationCount();
|
||||
try self.ensureCapacity();
|
||||
const put_result = self.internalPut(key);
|
||||
assert(put_result.old_kv == null);
|
||||
return GetOrPutResult{
|
||||
.kv = &put_result.new_entry.kv,
|
||||
.found_existing = false,
|
||||
};
|
||||
}
|
||||
|
||||
fn ensureCapacity(self: *Self) !void {
|
||||
if (self.entries.len == 0) {
|
||||
return self.initCapacity(16);
|
||||
}
|
||||
hm.incrementModificationCount();
|
||||
|
||||
// if we get too full (60%), double the capacity
|
||||
if (hm.size * 5 >= hm.entries.len * 3) {
|
||||
const old_entries = hm.entries;
|
||||
try hm.initCapacity(hm.entries.len * 2);
|
||||
if (self.size * 5 >= self.entries.len * 3) {
|
||||
const old_entries = self.entries;
|
||||
try self.initCapacity(self.entries.len * 2);
|
||||
// dump all of the old elements into the new table
|
||||
for (old_entries) |*old_entry| {
|
||||
if (old_entry.used) {
|
||||
_ = hm.internalPut(old_entry.key, old_entry.value);
|
||||
self.internalPut(old_entry.kv.key).new_entry.kv.value = old_entry.kv.value;
|
||||
}
|
||||
}
|
||||
hm.allocator.free(old_entries);
|
||||
self.allocator.free(old_entries);
|
||||
}
|
||||
|
||||
return hm.internalPut(key, value);
|
||||
}
|
||||
|
||||
pub fn get(hm: *const Self, key: K) ?*Entry {
|
||||
/// Returns the kv pair that was already there.
|
||||
pub fn put(self: *Self, key: K, value: V) !?KV {
|
||||
self.incrementModificationCount();
|
||||
try self.ensureCapacity();
|
||||
|
||||
const put_result = self.internalPut(key);
|
||||
put_result.new_entry.kv.value = value;
|
||||
return put_result.old_kv;
|
||||
}
|
||||
|
||||
pub fn get(hm: *const Self, key: K) ?*KV {
|
||||
if (hm.entries.len == 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -122,7 +166,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3
|
||||
return hm.get(key) != null;
|
||||
}
|
||||
|
||||
pub fn remove(hm: *Self, key: K) ?*Entry {
|
||||
pub fn remove(hm: *Self, key: K) ?*KV {
|
||||
if (hm.entries.len == 0) return null;
|
||||
hm.incrementModificationCount();
|
||||
const start_index = hm.keyToIndex(key);
|
||||
@@ -134,7 +178,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3
|
||||
|
||||
if (!entry.used) return null;
|
||||
|
||||
if (!eql(entry.key, key)) continue;
|
||||
if (!eql(entry.kv.key, key)) continue;
|
||||
|
||||
while (roll_over < hm.entries.len) : (roll_over += 1) {
|
||||
const next_index = (start_index + roll_over + 1) % hm.entries.len;
|
||||
@@ -142,7 +186,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3
|
||||
if (!next_entry.used or next_entry.distance_from_start_index == 0) {
|
||||
entry.used = false;
|
||||
hm.size -= 1;
|
||||
return entry;
|
||||
return &entry.kv;
|
||||
}
|
||||
entry.* = next_entry.*;
|
||||
entry.distance_from_start_index -= 1;
|
||||
@@ -168,7 +212,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3
|
||||
try other.initCapacity(self.entries.len);
|
||||
var it = self.iterator();
|
||||
while (it.next()) |entry| {
|
||||
try other.put(entry.key, entry.value);
|
||||
assert((try other.put(entry.key, entry.value)) == null);
|
||||
}
|
||||
return other;
|
||||
}
|
||||
@@ -188,60 +232,81 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value that was already there.
|
||||
fn internalPut(hm: *Self, orig_key: K, orig_value: *const V) ?V {
|
||||
const InternalPutResult = struct {
|
||||
new_entry: *Entry,
|
||||
old_kv: ?KV,
|
||||
};
|
||||
|
||||
/// Returns a pointer to the new entry.
|
||||
/// Asserts that there is enough space for the new item.
|
||||
fn internalPut(self: *Self, orig_key: K) InternalPutResult {
|
||||
var key = orig_key;
|
||||
var value = orig_value.*;
|
||||
const start_index = hm.keyToIndex(key);
|
||||
var value: V = undefined;
|
||||
const start_index = self.keyToIndex(key);
|
||||
var roll_over: usize = 0;
|
||||
var distance_from_start_index: usize = 0;
|
||||
while (roll_over < hm.entries.len) : ({
|
||||
var got_result_entry = false;
|
||||
var result = InternalPutResult{
|
||||
.new_entry = undefined,
|
||||
.old_kv = null,
|
||||
};
|
||||
while (roll_over < self.entries.len) : ({
|
||||
roll_over += 1;
|
||||
distance_from_start_index += 1;
|
||||
}) {
|
||||
const index = (start_index + roll_over) % hm.entries.len;
|
||||
const entry = &hm.entries[index];
|
||||
const index = (start_index + roll_over) % self.entries.len;
|
||||
const entry = &self.entries[index];
|
||||
|
||||
if (entry.used and !eql(entry.key, key)) {
|
||||
if (entry.used and !eql(entry.kv.key, key)) {
|
||||
if (entry.distance_from_start_index < distance_from_start_index) {
|
||||
// robin hood to the rescue
|
||||
const tmp = entry.*;
|
||||
hm.max_distance_from_start_index = math.max(hm.max_distance_from_start_index, distance_from_start_index);
|
||||
self.max_distance_from_start_index = math.max(self.max_distance_from_start_index, distance_from_start_index);
|
||||
if (!got_result_entry) {
|
||||
got_result_entry = true;
|
||||
result.new_entry = entry;
|
||||
}
|
||||
entry.* = Entry{
|
||||
.used = true,
|
||||
.distance_from_start_index = distance_from_start_index,
|
||||
.key = key,
|
||||
.value = value,
|
||||
.kv = KV{
|
||||
.key = key,
|
||||
.value = value,
|
||||
},
|
||||
};
|
||||
key = tmp.key;
|
||||
value = tmp.value;
|
||||
key = tmp.kv.key;
|
||||
value = tmp.kv.value;
|
||||
distance_from_start_index = tmp.distance_from_start_index;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var result: ?V = null;
|
||||
if (entry.used) {
|
||||
result = entry.value;
|
||||
result.old_kv = entry.kv;
|
||||
} else {
|
||||
// adding an entry. otherwise overwriting old value with
|
||||
// same key
|
||||
hm.size += 1;
|
||||
self.size += 1;
|
||||
}
|
||||
|
||||
hm.max_distance_from_start_index = math.max(distance_from_start_index, hm.max_distance_from_start_index);
|
||||
self.max_distance_from_start_index = math.max(distance_from_start_index, self.max_distance_from_start_index);
|
||||
if (!got_result_entry) {
|
||||
result.new_entry = entry;
|
||||
}
|
||||
entry.* = Entry{
|
||||
.used = true,
|
||||
.distance_from_start_index = distance_from_start_index,
|
||||
.key = key,
|
||||
.value = value,
|
||||
.kv = KV{
|
||||
.key = key,
|
||||
.value = value,
|
||||
},
|
||||
};
|
||||
return result;
|
||||
}
|
||||
unreachable; // put into a full map
|
||||
}
|
||||
|
||||
fn internalGet(hm: *const Self, key: K) ?*Entry {
|
||||
fn internalGet(hm: Self, key: K) ?*KV {
|
||||
const start_index = hm.keyToIndex(key);
|
||||
{
|
||||
var roll_over: usize = 0;
|
||||
@@ -250,13 +315,13 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3
|
||||
const entry = &hm.entries[index];
|
||||
|
||||
if (!entry.used) return null;
|
||||
if (eql(entry.key, key)) return entry;
|
||||
if (eql(entry.kv.key, key)) return &entry.kv;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn keyToIndex(hm: *const Self, key: K) usize {
|
||||
fn keyToIndex(hm: Self, key: K) usize {
|
||||
return usize(hash(key)) % hm.entries.len;
|
||||
}
|
||||
};
|
||||
@@ -266,7 +331,7 @@ test "basic hash map usage" {
|
||||
var direct_allocator = std.heap.DirectAllocator.init();
|
||||
defer direct_allocator.deinit();
|
||||
|
||||
var map = HashMap(i32, i32, hash_i32, eql_i32).init(&direct_allocator.allocator);
|
||||
var map = AutoHashMap(i32, i32).init(&direct_allocator.allocator);
|
||||
defer map.deinit();
|
||||
|
||||
assert((try map.put(1, 11)) == null);
|
||||
@@ -275,8 +340,19 @@ test "basic hash map usage" {
|
||||
assert((try map.put(4, 44)) == null);
|
||||
assert((try map.put(5, 55)) == null);
|
||||
|
||||
assert((try map.put(5, 66)).? == 55);
|
||||
assert((try map.put(5, 55)).? == 66);
|
||||
assert((try map.put(5, 66)).?.value == 55);
|
||||
assert((try map.put(5, 55)).?.value == 66);
|
||||
|
||||
const gop1 = try map.getOrPut(5);
|
||||
assert(gop1.found_existing == true);
|
||||
assert(gop1.kv.value == 55);
|
||||
gop1.kv.value = 77;
|
||||
assert(map.get(5).?.value == 77);
|
||||
|
||||
const gop2 = try map.getOrPut(99);
|
||||
assert(gop2.found_existing == false);
|
||||
gop2.kv.value = 42;
|
||||
assert(map.get(99).?.value == 42);
|
||||
|
||||
assert(map.contains(2));
|
||||
assert(map.get(2).?.value == 22);
|
||||
@@ -289,7 +365,7 @@ test "iterator hash map" {
|
||||
var direct_allocator = std.heap.DirectAllocator.init();
|
||||
defer direct_allocator.deinit();
|
||||
|
||||
var reset_map = HashMap(i32, i32, hash_i32, eql_i32).init(&direct_allocator.allocator);
|
||||
var reset_map = AutoHashMap(i32, i32).init(&direct_allocator.allocator);
|
||||
defer reset_map.deinit();
|
||||
|
||||
assert((try reset_map.put(1, 11)) == null);
|
||||
@@ -332,10 +408,124 @@ test "iterator hash map" {
|
||||
assert(entry.value == values[0]);
|
||||
}
|
||||
|
||||
fn hash_i32(x: i32) u32 {
|
||||
return @bitCast(u32, x);
|
||||
pub fn getAutoHashFn(comptime K: type) (fn (K) u32) {
|
||||
return struct {
|
||||
fn hash(key: K) u32 {
|
||||
comptime var rng = comptime std.rand.DefaultPrng.init(0);
|
||||
return autoHash(key, &rng.random, u32);
|
||||
}
|
||||
}.hash;
|
||||
}
|
||||
|
||||
fn eql_i32(a: i32, b: i32) bool {
|
||||
return a == b;
|
||||
pub fn getAutoEqlFn(comptime K: type) (fn (K, K) bool) {
|
||||
return struct {
|
||||
fn eql(a: K, b: K) bool {
|
||||
return autoEql(a, b);
|
||||
}
|
||||
}.eql;
|
||||
}
|
||||
|
||||
// TODO improve these hash functions
|
||||
pub fn autoHash(key: var, comptime rng: *std.rand.Random, comptime HashInt: type) HashInt {
|
||||
switch (@typeInfo(@typeOf(key))) {
|
||||
builtin.TypeId.NoReturn,
|
||||
builtin.TypeId.Opaque,
|
||||
builtin.TypeId.Undefined,
|
||||
builtin.TypeId.ArgTuple,
|
||||
=> @compileError("cannot hash this type"),
|
||||
|
||||
builtin.TypeId.Void,
|
||||
builtin.TypeId.Null,
|
||||
=> return 0,
|
||||
|
||||
builtin.TypeId.Int => |info| {
|
||||
const unsigned_x = @bitCast(@IntType(false, info.bits), key);
|
||||
if (info.bits <= HashInt.bit_count) {
|
||||
return HashInt(unsigned_x) *% comptime rng.scalar(HashInt);
|
||||
} else {
|
||||
return @truncate(HashInt, unsigned_x *% comptime rng.scalar(@typeOf(unsigned_x)));
|
||||
}
|
||||
},
|
||||
|
||||
builtin.TypeId.Float => |info| {
|
||||
return autoHash(@bitCast(@IntType(false, info.bits), key), rng);
|
||||
},
|
||||
builtin.TypeId.Bool => return autoHash(@boolToInt(key), rng),
|
||||
builtin.TypeId.Enum => return autoHash(@enumToInt(key), rng),
|
||||
builtin.TypeId.ErrorSet => return autoHash(@errorToInt(key), rng),
|
||||
builtin.TypeId.Promise, builtin.TypeId.Fn => return autoHash(@ptrToInt(key), rng),
|
||||
|
||||
builtin.TypeId.Namespace,
|
||||
builtin.TypeId.Block,
|
||||
builtin.TypeId.BoundFn,
|
||||
builtin.TypeId.ComptimeFloat,
|
||||
builtin.TypeId.ComptimeInt,
|
||||
builtin.TypeId.Type,
|
||||
=> return 0,
|
||||
|
||||
builtin.TypeId.Pointer => |info| switch (info.size) {
|
||||
builtin.TypeInfo.Pointer.Size.One => @compileError("TODO auto hash for single item pointers"),
|
||||
builtin.TypeInfo.Pointer.Size.Many => @compileError("TODO auto hash for many item pointers"),
|
||||
builtin.TypeInfo.Pointer.Size.Slice => {
|
||||
const interval = std.math.max(1, key.len / 256);
|
||||
var i: usize = 0;
|
||||
var h = comptime rng.scalar(HashInt);
|
||||
while (i < key.len) : (i += interval) {
|
||||
h ^= autoHash(key[i], rng, HashInt);
|
||||
}
|
||||
return h;
|
||||
},
|
||||
},
|
||||
|
||||
builtin.TypeId.Optional => @compileError("TODO auto hash for optionals"),
|
||||
builtin.TypeId.Array => @compileError("TODO auto hash for arrays"),
|
||||
builtin.TypeId.Struct => @compileError("TODO auto hash for structs"),
|
||||
builtin.TypeId.Union => @compileError("TODO auto hash for unions"),
|
||||
builtin.TypeId.ErrorUnion => @compileError("TODO auto hash for unions"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn autoEql(a: var, b: @typeOf(a)) bool {
|
||||
switch (@typeInfo(@typeOf(a))) {
|
||||
builtin.TypeId.NoReturn,
|
||||
builtin.TypeId.Opaque,
|
||||
builtin.TypeId.Undefined,
|
||||
builtin.TypeId.ArgTuple,
|
||||
=> @compileError("cannot test equality of this type"),
|
||||
builtin.TypeId.Void,
|
||||
builtin.TypeId.Null,
|
||||
=> return true,
|
||||
builtin.TypeId.Bool,
|
||||
builtin.TypeId.Int,
|
||||
builtin.TypeId.Float,
|
||||
builtin.TypeId.ComptimeFloat,
|
||||
builtin.TypeId.ComptimeInt,
|
||||
builtin.TypeId.Namespace,
|
||||
builtin.TypeId.Block,
|
||||
builtin.TypeId.Promise,
|
||||
builtin.TypeId.Enum,
|
||||
builtin.TypeId.BoundFn,
|
||||
builtin.TypeId.Fn,
|
||||
builtin.TypeId.ErrorSet,
|
||||
builtin.TypeId.Type,
|
||||
=> return a == b,
|
||||
|
||||
builtin.TypeId.Pointer => |info| switch (info.size) {
|
||||
builtin.TypeInfo.Pointer.Size.One => @compileError("TODO auto eql for single item pointers"),
|
||||
builtin.TypeInfo.Pointer.Size.Many => @compileError("TODO auto eql for many item pointers"),
|
||||
builtin.TypeInfo.Pointer.Size.Slice => {
|
||||
if (a.len != b.len) return false;
|
||||
for (a) |a_item, i| {
|
||||
if (!autoEql(a_item, b[i])) return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
|
||||
builtin.TypeId.Optional => @compileError("TODO auto eql for optionals"),
|
||||
builtin.TypeId.Array => @compileError("TODO auto eql for arrays"),
|
||||
builtin.TypeId.Struct => @compileError("TODO auto eql for structs"),
|
||||
builtin.TypeId.Union => @compileError("TODO auto eql for unions"),
|
||||
builtin.TypeId.ErrorUnion => @compileError("TODO auto eql for unions"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ pub const BufSet = @import("buf_set.zig").BufSet;
|
||||
pub const Buffer = @import("buffer.zig").Buffer;
|
||||
pub const BufferOutStream = @import("buffer.zig").BufferOutStream;
|
||||
pub const HashMap = @import("hash_map.zig").HashMap;
|
||||
pub const AutoHashMap = @import("hash_map.zig").AutoHashMap;
|
||||
pub const LinkedList = @import("linked_list.zig").LinkedList;
|
||||
pub const SegmentedList = @import("segmented_list.zig").SegmentedList;
|
||||
pub const DynLib = @import("dynamic_library.zig").DynLib;
|
||||
|
||||
@@ -1318,7 +1318,7 @@ pub const Parser = struct {
|
||||
_ = p.stack.pop();
|
||||
|
||||
var object = &p.stack.items[p.stack.len - 1].Object;
|
||||
_ = try object.put(key, value);
|
||||
_ = try object.put(key, value.*);
|
||||
p.state = State.ObjectKey;
|
||||
},
|
||||
// Array Parent -> [ ..., <array>, value ]
|
||||
|
||||
@@ -72,10 +72,10 @@ pub fn main() !void {
|
||||
if (mem.indexOfScalar(u8, option_contents, '=')) |name_end| {
|
||||
const option_name = option_contents[0..name_end];
|
||||
const option_value = option_contents[name_end + 1 ..];
|
||||
if (builder.addUserInputOption(option_name, option_value))
|
||||
if (try builder.addUserInputOption(option_name, option_value))
|
||||
return usageAndErr(&builder, false, try stderr_stream);
|
||||
} else {
|
||||
if (builder.addUserInputFlag(option_contents))
|
||||
if (try builder.addUserInputFlag(option_contents))
|
||||
return usageAndErr(&builder, false, try stderr_stream);
|
||||
}
|
||||
} else if (mem.startsWith(u8, arg, "-")) {
|
||||
|
||||
Reference in New Issue
Block a user