const std = @import("std"); const ctx = @import("../AppContext.zig"); /// Interned string ID. A string can be converted to a stable integer constant, /// called an *atom*. The value of an atom for a given string is guaranteed to /// be stable throughout a program's runtime, but not across different runs. /// There can be no more than 2¹⁶ atoms. pub const Atom = enum(u16) { // VOLATILE When modifying the list of explicitly defined atoms (i.e. any // explicit enum value), we need to update `Atoms.init` implementation. /// Atom representing an empty string, i.e. `""`. empty, _, /// Cast an integer into an atom. This can produce an invalid atom. pub fn fromInt(value: u16) Atom { return @enumFromInt(value); } /// Cast an index into an atom. This can produce an invalid atom. The caller /// asserts that the index is not greater than the max atom value. pub fn fromIndex(index: usize) Atom { return @enumFromInt(@as(u16, @intCast(index))); } /// Cast an index into an atom. This can produce an invalid atom. Returns an /// error if the index is greater than the max atom value. pub fn fromIndexSafe(index: usize) error{Overflow}!Atom { return @enumFromInt(std.math.cast(u16, index) orelse return error.Overflow); } /// Turn a string into an atom. Returns either an existing atom or makes a /// new one, if necessary. This will always produce a valid atom. Will not /// return any error if the atom already exists. pub fn fromString(string: []const u8) error{ OutOfMemory, OutOfAtoms }!Atom { const allocator_general = ctx.allocator_general; const allocator_persistent = ctx.allocator_persistent; const io = ctx.io; const atoms = ctx.atoms; atoms.mutex.lockUncancelable(io); defer atoms.mutex.unlock(io); const entry = try atoms.map.getOrPut(allocator_general, string); if (entry.found_existing) { return entry.value_ptr.*; } else { errdefer _ = atoms.map.remove(string); const atom = Atom.fromIndexSafe(atoms.array.items.len) catch |err| switch (err) { error.Overflow => return error.OutOfAtoms, }; try atoms.array.ensureUnusedCapacity(allocator_general, 1); const owned_string = try allocator_persistent.dupeZ(u8, string); entry.key_ptr.* = owned_string; entry.value_ptr.* = atom; atoms.array.appendAssumeCapacity(owned_string); return atom; } } /// Turn a string into an atom, if the string has been already registered as /// an atom. Returns `null` otherwise. This will always produce a valid /// atom. pub fn fromStringIfExists(string: []const u8) ?Atom { const io = ctx.io; const atoms = ctx.atoms; atoms.mutex.lockUncancelable(io); defer atoms.mutex.unlock(io); return atoms.map.get(string); } /// Cast an atom into an integer. pub fn toInt(self: Atom) u16 { return @intFromEnum(self); } /// Cast an atom into a string. The caller asserts that the atom is valid. pub fn toString(self: Atom) [:0]const u8 { const io = ctx.io; const atoms = ctx.atoms; atoms.mutex.lockUncancelable(io); defer atoms.mutex.unlock(io); return atoms.array.items[self.toInt()]; } };