tiny.preserves.atom
Defined in tiny.preserves.
A copy of one atom, taken out of a value, records whether its holder owns the bytes behind it.
API (6)
Types and contracts
Public types and contracts.
Atom: A copy of one atom, tagged by its kind.AtomClass: The six kinds of atom, in the orderValueranks them: boolean, double, integer, string, byte string and symbol.CowBytes: A byte slice with a tag that says whether its holder owns the bytes.CowSignedInteger: An integer with a tag that says whether its holder owns the integer's heap digits.Ownership: Whether the holder of some bytes owns them, and frees them with its owndeinit, or borrows them from an owner that keeps them alive.SignedInteger: A signed integer of any size, stored in the smallest of three representations that holds it.
Source
Source: lib/preserves/src/atom.zig
zig
//! A copy of one atom, taken out of a value, records whether its holder owns the bytes behind it.//! An atom is a boolean, a double, an integer, a string, a byte string or a symbol.//!//! A program that keeps atoms apart from the tree they came from has to know, for each one, whether//! freeing its bytes is the program's job.//!//! Atom bytes come from two places: memory that some other owner keeps alive, and memory the holder//! allocated itself. A value tree stores its text as bare slices and records neither case, so one//! free call over the tree is wrong for one of the two. A Lean model of parser results proves that//! when a borrowed symbol and an owned symbol look the same, no single cleanup frees exactly the//! owned bytes of both.//!//! The six atom kinds are the atoms of the [Preserves](https://preserves.dev/) data language, which//! the package keeps along with its compounds and embedded values.//!//! Each copy carries a two-state tag (`Ownership`). The tag says whether the holder owns the copy's//! bytes or borrows them. A byte slice with that tag (`CowBytes`) holds the text of a string, byte//! string or symbol. An integer with that tag (`CowSignedInteger`) holds a number whose digits may//! live on the heap. The union of all six kinds (`Atom`) holds booleans and doubles directly, since//! they own no storage. Freeing a copy frees only what it owns, and promoting a borrowed copy//! copies its bytes into storage its holder owns. Two copies of one atom compare equal whatever//! their ownership tags. `Value.asAtom` returns a borrowed copy of one atom of a value, and `null`//! for any other kind. The codecs and the free calls of the package never read these types, and a//! value never stores them.const std = @import("std");const Allocator = std.mem.Allocator;const integer = @import("integer.zig");pub const SignedInteger = integer.SignedInteger;/// The six kinds of atom, in the order `Value` ranks them: boolean, double, integer, string, byte/// string and symbol. Code that switches on an atom's kind reads this, from `Value.atomClass` or/// `Atom.class`. `Value.atomClass` returns it for an atom and `null` for any other value. It tags/// the union `Atom`.pub const AtomClass = enum(u8) { boolean, double, signed_integer, string, byte_string, symbol,};/// Whether the holder of some bytes owns them, and frees them with its own `deinit`, or borrows/// them from an owner that keeps them alive. Code that holds atom bytes apart from their value/// reads this to decide whether `deinit` frees them.pub const Ownership = enum { borrowed, owned };/// A byte slice with a tag that says whether its holder owns the bytes. Code that keeps a string,/// byte string or symbol apart from its value holds it in this type, so its ownership travels with/// it. A new one borrows its bytes unless its builder says otherwise.pub const CowBytes = struct { /// The bytes of the string, byte string or symbol. bytes: []const u8, /// Whether the holder owns `bytes`. It defaults to borrowed, so a struct written by hand never /// frees bytes it did not allocate. ownership: Ownership = .borrowed, /// Wraps `bytes` as borrowed. Code that wraps bytes it keeps alive elsewhere calls it. `deinit` /// leaves the bytes alone, so they have to outlive the result. pub fn borrow(bytes: []const u8) CowBytes { return .{ .bytes = bytes, .ownership = .borrowed }; } /// Wraps `bytes` as owned. Code that wraps bytes it allocated calls it. `deinit` frees them /// with the allocator it is given, so they have to come from that allocator. pub fn own(bytes: []const u8) CowBytes { return .{ .bytes = bytes, .ownership = .owned }; } /// Frees the bytes with `allocator` when they are owned, and leaves them when they are /// borrowed. Code that holds the bytes calls it when it is done with them. The struct is /// undefined afterward. pub fn deinit(self: *CowBytes, allocator: Allocator) void { if (self.ownership == .owned) allocator.free(self.bytes); self.* = undefined; } /// Returns a copy that owns its bytes. Code that must keep the bytes after their owner goes /// calls it. Borrowed bytes are copied into storage from `allocator`. Owned bytes come back as /// the same struct with no copy, so the result and the original share them and only one of the /// two may be freed. The call can fail only with `error.OutOfMemory`. pub fn intoOwned(self: CowBytes, allocator: Allocator) !CowBytes { return switch (self.ownership) { .owned => self, .borrowed => CowBytes.own(try allocator.dupe(u8, self.bytes)), }; }};/// An integer with a tag that says whether its holder owns the integer's heap digits. Code that/// keeps an integer apart from its value holds it in this type, so the ownership of its digits/// travels with it. A new one borrows unless its builder says otherwise.pub const CowSignedInteger = struct { /// The integer. value: SignedInteger, /// Whether the holder owns the digits of `value`. It defaults to borrowed, so a struct written /// by hand never frees digits it did not allocate. ownership: Ownership = .borrowed, /// Wraps `value` as borrowed. Code that wraps an integer whose digits live elsewhere calls it. /// `deinit` leaves its digits alone. pub fn borrow(value: SignedInteger) CowSignedInteger { return .{ .value = value, .ownership = .borrowed }; } /// Wraps `value` as owned. Code that wraps an integer it built calls it. `deinit` frees its /// digits with the allocator it is given, so they have to come from that allocator. pub fn own(value: SignedInteger) CowSignedInteger { return .{ .value = value, .ownership = .owned }; } /// Frees the integer's heap digits with `allocator` when they are owned, and leaves them when /// they are borrowed. Code that holds the integer calls it when it is done with it. Only an /// integer too large for 128 bits has heap digits. The struct is undefined afterward. pub fn deinit(self: *CowSignedInteger, allocator: Allocator) void { if (self.ownership == .owned) self.value.deinit(allocator); self.* = undefined; } /// Returns a copy that owns its digits. Code that must keep the integer after its owner goes /// calls it. A borrowed integer is copied into storage from `allocator`. An owned integer comes /// back as the same struct with no copy, so the result and the original share its digits and /// only one of the two may be freed. pub fn intoOwned(self: CowSignedInteger, allocator: Allocator) !CowSignedInteger { return switch (self.ownership) { .owned => self, .borrowed => CowSignedInteger.own(try self.value.clone(allocator)), }; }};/// A copy of one atom, tagged by its kind. Code that takes one atom out of a value holds it in this/// type, as `Value.asAtom` returns it. Booleans and doubles are stored directly. Strings, byte/// strings and symbols are stored as bytes with an ownership tag, and integers as an integer with/// one. `Value.asAtom` builds one that borrows from a value.pub const Atom = union(AtomClass) { /// The boolean. boolean: bool, /// The double. double: f64, /// The integer, with whether its holder owns its digits. signed_integer: CowSignedInteger, /// The string's bytes, with whether its holder owns them. string: CowBytes, /// The byte string's bytes, with whether its holder owns them. byte_string: CowBytes, /// The symbol's name bytes, with whether its holder owns them. symbol: CowBytes, /// Returns the atom's kind. Code that switches on an atom's kind calls it. pub fn class(self: Atom) AtomClass { return @as(AtomClass, self); } /// Makes a boolean atom from `v`. `Value.asAtom` calls it for a boolean atom. pub fn fromBool(v: bool) Atom { return .{ .boolean = v }; } /// Makes a double atom from `v`. `Value.asAtom` calls it for a double atom. pub fn fromDouble(v: f64) Atom { return .{ .double = v }; } /// Makes an integer atom that borrows `v`'s digits. `Value.asAtom` calls it for an integer atom /// over the value's own digits. pub fn fromSignedIntegerBorrowed(v: SignedInteger) Atom { return .{ .signed_integer = CowSignedInteger.borrow(v) }; } /// Makes an integer atom that owns `v`'s digits, so its `deinit` frees them. Code that hands an /// integer's digits to the atom calls it. pub fn fromSignedIntegerOwned(v: SignedInteger) Atom { return .{ .signed_integer = CowSignedInteger.own(v) }; } /// Makes a string atom that borrows `bytes`. `Value.asAtom` calls it for a string atom over the /// value's own bytes. pub fn fromStringBorrowed(bytes: []const u8) Atom { return .{ .string = CowBytes.borrow(bytes) }; } /// Makes a string atom that owns `bytes`, so its `deinit` frees them. Code that hands a /// string's bytes to the atom calls it. pub fn fromStringOwned(bytes: []const u8) Atom { return .{ .string = CowBytes.own(bytes) }; } /// Makes a byte-string atom that borrows `bytes`. `Value.asAtom` calls it for a byte-string /// atom over the value's own bytes. pub fn fromByteStringBorrowed(bytes: []const u8) Atom { return .{ .byte_string = CowBytes.borrow(bytes) }; } /// Makes a byte-string atom that owns `bytes`, so its `deinit` frees them. Code that hands a /// byte string's bytes to the atom calls it. pub fn fromByteStringOwned(bytes: []const u8) Atom { return .{ .byte_string = CowBytes.own(bytes) }; } /// Makes a symbol atom that borrows `bytes`. `Value.asAtom` calls it for a symbol atom over the /// value's own bytes. pub fn fromSymbolBorrowed(bytes: []const u8) Atom { return .{ .symbol = CowBytes.borrow(bytes) }; } /// Makes a symbol atom that owns `bytes`, so its `deinit` frees them. Code that hands a /// symbol's bytes to the atom calls it. pub fn fromSymbolOwned(bytes: []const u8) Atom { return .{ .symbol = CowBytes.own(bytes) }; } /// Frees the atom's bytes or digits with `allocator` when it owns them. Code that holds the /// atom calls it when it is done with it. Booleans and doubles need nothing. The atom is /// undefined afterward. pub fn deinit(self: *Atom, allocator: Allocator) void { switch (self.*) { .boolean, .double => {}, .signed_integer => |*c| c.deinit(allocator), .string => |*c| c.deinit(allocator), .byte_string => |*c| c.deinit(allocator), .symbol => |*c| c.deinit(allocator), } self.* = undefined; } /// Returns a copy of the atom that owns its bytes or digits, copying borrowed ones into storage /// from `allocator`. Code that keeps an atom calls it so the atom outlives the value it came /// from. Parts that are already owned come back with no copy, so the result shares them with /// the original and only one of the two may be freed. pub fn intoOwned(self: Atom, allocator: Allocator) !Atom { return switch (self) { .boolean, .double => self, .signed_integer => |c| .{ .signed_integer = try c.intoOwned(allocator) }, .string => |c| .{ .string = try c.intoOwned(allocator) }, .byte_string => |c| .{ .byte_string = try c.intoOwned(allocator) }, .symbol => |c| .{ .symbol = try c.intoOwned(allocator) }, }; } /// Returns whether two atoms have the same kind and the same contents, whatever their ownership /// tags. Code that compares atoms from different owners calls it, so ownership plays no part. /// Doubles are equal when their bit patterns are equal, so a `NaN` equals a `NaN` with the same /// bits and `0.0` differs from `-0.0`. Integers compare with `SignedInteger.eql`. pub fn eql(a: Atom, b: Atom) bool { if (a.class() != b.class()) return false; return switch (a) { .boolean => |v| v == b.boolean, .double => |v| @as(u64, @bitCast(v)) == @as(u64, @bitCast(b.double)), .signed_integer => |c| c.value.eql(b.signed_integer.value), .string => |c| std.mem.eql(u8, c.bytes, b.string.bytes), .byte_string => |c| std.mem.eql(u8, c.bytes, b.byte_string.bytes), .symbol => |c| std.mem.eql(u8, c.bytes, b.symbol.bytes), }; }};test "Atom borrowed slice does not free on deinit" { var bytes = [_]u8{ 'h', 'i' }; var atom = Atom.fromStringBorrowed(&bytes); atom.deinit(std.testing.allocator);}test "Atom owned slice frees on deinit" { const allocator = std.testing.allocator; const buf = try allocator.dupe(u8, "hello"); var atom = Atom.fromStringOwned(buf); atom.deinit(allocator);}test "Atom intoOwned promotes borrowed payload" { const allocator = std.testing.allocator; const literal = "abc"; const borrowed = Atom.fromSymbolBorrowed(literal); var owned = try borrowed.intoOwned(allocator); defer owned.deinit(allocator); try std.testing.expectEqual(Ownership.owned, owned.symbol.ownership); try std.testing.expect(std.mem.eql(u8, owned.symbol.bytes, literal));}test "Atom eql compares across ownership tags" { var buf = [_]u8{'x'}; const borrowed = Atom.fromStringBorrowed(&buf); const allocator = std.testing.allocator; const owned_bytes = try allocator.dupe(u8, "x"); var owned = Atom.fromStringOwned(owned_bytes); defer owned.deinit(allocator); try std.testing.expect(Atom.eql(borrowed, owned));}Source: lib/preserves/src/root.zig:106
zig
pub const atom = @import("atom.zig");Audit
| Definitions | 1 |
|---|---|
| Public names | 1 |
| Members | 0 |
| Version | 26.7.0 |
| Revision | daab053ee433 |