Skip to documentation
SLOP

tiny.preserves.embedded_mod

Reference tiny.preserves embedded_mod

Defined in tiny.preserves.

An embedded value points to a payload of the host program and carries optional functions to compare, hash, free and copy it.

API (5)

Actions

Public operations.

Types and contracts

Public types and contracts.

No direct callersNo direct callstiny.preservesembedded mod
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Called byCallsprivate sourcelib.preserves.src.embeddedcheckParsedEmbeddedBindCloneAllocatio...test sourcelib.preserves.src.embeddedtest: parsed embedded clone and deini...private sourcelib.sql.src.versioncloneValueembedded_modparsedEmbeddedClone
Static calls · unresolved targets: 13 · external targets: 15.
Called byCallsNo direct callsprivate sourcelib.preserves.src.embeddedcheckParsedEmbeddedBindCloneAllocatio...test sourcelib.preserves.src.embeddedtest: parsed embedded clone and deini...embedded_modparsedEmbeddedDeinit
Static calls · unresolved targets: 2 · external targets: 1.

Source: lib/preserves/src/embedded.zig

zig
//! An embedded value points to a payload of the host program and carries optional functions to//! compare, hash, free and copy it. Values that hold such payloads still need equality, a total//! order and a hash that agrees with equality, so sets, dictionaries and hash maps work over them.//! Freeing or copying a tree has to free or copy each payload the tree owns.//!//! The package never sees the payload's type, so it has no way to compare, hash, free or copy the//! payload by itself. Equality from one source and hashing from another can disagree, and then two//! equal payloads land in different hash-map slots. Some payloads belong to the tree and die with//! it, and others belong to the host program and outlive it.//!//! The [Preserves](https://preserves.dev/) data language lets a host program place its own values//! inside Preserves data as embedded values, and the package keeps them.//!//! The package's value type uses a struct as the type of its embedded values (*domain*) for the//! text parser, the JSON codec and every function at the package root. The struct (`AnyEmbedded`)//! holds an untyped pointer to the payload and three optional parts: a table of functions, a free//! function and a copy function. Equality, hash and order come together as one table of functions//! (`SemanticOps`), so one owner supplies all three. Two payloads are equal only when they share//! one table and that table's equality holds, and payloads with different tables order by the//! table's address. The table's order must be total, its equality must hold exactly when the order//! returns `.eq`, and equal payloads must hash equally. A payload with no table compares, orders//! and hashes by its address, and it orders before every payload with a table. Freeing calls the//! free function when there is one, and a payload without one is borrowed and left alone. Copying//! calls the copy function. A payload with neither function stays shared with the copy. Copying//! panics on a payload that has a free function and no copy function, because two trees would then//! free one payload.//!//! Three functions (`parsedEmbeddedOps`, `parsedEmbeddedDeinit` and `parsedEmbeddedClone`) supply//! the table and the free and copy functions for a payload that is itself a value in its own heap//! cell. The text parser builds such a payload from `#:v`, the JSON reader from an `__embedded__`//! member, and the function `observeRecord` from an observer yet to be embedded. The function//! `toText` prints such a payload as `#:` followed by the value, and any other payload as `#:`//! followed by its address in decimal. The binary reader rejects every embedded value, and the//! binary writer rejects one whose type declares no `encodePacked` function, as this struct//! declares none.const std = @import("std");const Allocator = std.mem.Allocator;/// One table of equality, hash and order functions over untyped payload pointers. Code that embeds/// its own objects fills one table per object kind, so equality, order and hash agree. Two embedded/// values use the table only when both point to the same table. The table's order must be total,/// its equality must hold exactly when the order returns `.eq`, and equal payloads must hash/// equally. Tables are compared by address, so one table serves every payload of one kind.pub const SemanticOps = struct {    /// Returns whether two payloads are equal. The function must return `true` exactly when `order`    /// returns `.eq`.    eql: *const fn (*anyopaque, *anyopaque) bool,    /// Returns the 64-bit hash of one payload. Equal payloads must hash equally.    hash: *const fn (*anyopaque) u64,    /// Returns the order of one payload against another. The order must be total.    order: *const fn (*anyopaque, *anyopaque) std.math.Order,};/// An embedded value that points to a payload of the host program, with optional functions to/// compare, hash, free and copy it. Code that places host objects inside values uses this type, as/// the text parser, the JSON codec and every function at the package root do. Every optional part/// defaults to `null`, so `.{ .value = ptr }` is a borrowed payload that compares by address.pub const AnyEmbedded = struct {    /// An untyped pointer to the payload.    value: *anyopaque,    /// The payload's table of equality, hash and order functions, or `null` to compare, order and    /// hash by address.    semantic_ops: ?*const SemanticOps = null,    /// The function that frees the payload, or `null` for a payload the tree borrows. `deinit`    /// calls it with the payload and the allocator.    deinit_fn: ?*const fn (*anyopaque, Allocator) void = null,    /// The function that copies the payload with an allocator and returns a pointer to the copy, or    /// `null`. A payload with a free function and no copy function makes `clone` panic.    clone_fn: ?*const fn (*anyopaque, Allocator) Allocator.Error!*anyopaque = null,    /// Returns whether two embedded values are equal. `Value.eql` calls it for two embedded values.    /// Two values with no table are equal when they point to the same payload. A value with a table    /// never equals one without a table or one with a different table. Two values with the same    /// table are equal when the table's equality says so.    pub fn eql(a: AnyEmbedded, b: AnyEmbedded) bool {        const a_ops = a.semantic_ops orelse {            if (b.semantic_ops != null) return false;            return a.value == b.value;        };        const b_ops = b.semantic_ops orelse return false;        if (a_ops != b_ops) return false;        return a_ops.eql(a.value, b.value);    }    /// Returns the order of one embedded value against another. `Value.compare` calls it for two    /// embedded values. A value with no table orders before one with a table, and two values with    /// no table order by payload address. Values with different tables order by the tables'    /// addresses, and values with the same table order by that table's order. The order is total    /// whenever each table's order is.    pub fn order(a: AnyEmbedded, b: AnyEmbedded) std.math.Order {        const a_ops = a.semantic_ops orelse {            if (b.semantic_ops != null) return .lt;            return std.math.order(@intFromPtr(a.value), @intFromPtr(b.value));        };        const b_ops = b.semantic_ops orelse return .gt;        if (a_ops != b_ops) {            return std.math.order(@intFromPtr(a_ops), @intFromPtr(b_ops));        }        return a_ops.order(a.value, b.value);    }    /// Returns the table's hash of the payload, or the payload's address when there is no table.    /// `Value.hash` calls it for an embedded value.    pub fn hash(self: AnyEmbedded) u64 {        if (self.semantic_ops) |ops| return ops.hash(self.value);        return @intFromPtr(self.value);    }    /// Calls the free function with the payload and `allocator` when there is one, and does nothing    /// otherwise. `Value.deinit` calls it for an embedded value.    pub fn deinit(self: *AnyEmbedded, allocator: Allocator) void {        if (self.deinit_fn) |deinit_payload| deinit_payload(self.value, allocator);    }    /// Returns a copy that keeps the same table and functions. `cloneValueDeep` calls it for an    /// embedded value. With a copy function, the copy points to a new payload from that function.    /// With no copy function and no free function, the copy points to the same payload. With a free    /// function and no copy function, the call panics, because two trees would then free one    /// payload.    pub fn clone(self: AnyEmbedded, allocator: Allocator) Allocator.Error!AnyEmbedded {        var cloned = self;        if (self.clone_fn) |clone_payload| {            cloned.value = try clone_payload(self.value, allocator);            return cloned;        }        if (self.deinit_fn != null) @panic("owned embedded value missing clone hook");        return cloned;    }};/// Returns one table whose equality, hash and order read each payload as a pointer to a `Value` and/// call `eql`, `hash` and `compare` on it. The text parser, the JSON reader and `observeRecord`/// call it when they wrap a value as an embedded payload. The table is one constant per `Value`/// type, so payloads built by the text parser, the JSON reader and `observeRecord` compare with/// each other. `toText` checks for this table to print the payload as a value.pub fn parsedEmbeddedOps(comptime Value: type) *const SemanticOps {    const Impl = struct {        fn eql(a: *anyopaque, b: *anyopaque) bool {            const va: *const Value = @ptrCast(@alignCast(a));            const vb: *const Value = @ptrCast(@alignCast(b));            return va.*.eql(vb.*);        }        fn hash(ptr: *anyopaque) u64 {            const v: *const Value = @ptrCast(@alignCast(ptr));            return v.*.hash();        }        fn order(a: *anyopaque, b: *anyopaque) std.math.Order {            const va: *const Value = @ptrCast(@alignCast(a));            const vb: *const Value = @ptrCast(@alignCast(b));            return va.*.compare(vb.*);        }        const ops: SemanticOps = .{            .eql = &eql,            .hash = &hash,            .order = &order,        };    };    return &Impl.ops;}/// Returns a function that frees a payload that is a `Value` in its own heap cell: it calls/// `deinit` on the value and then frees the cell. The text parser, the JSON reader and/// `observeRecord` call it for the free function of a payload they wrap. The value has to own every/// byte, and the cell and the value have to come from the allocator the function is given.pub fn parsedEmbeddedDeinit(comptime Value: type) *const fn (*anyopaque, Allocator) void {    const Impl = struct {        fn call(ptr: *anyopaque, allocator: Allocator) void {            const value: *Value = @ptrCast(@alignCast(ptr));            value.deinit(allocator);            allocator.destroy(value);        }    };    return &Impl.call;}/// Returns a function that copies a payload that is a `Value` in its own heap cell into a new cell./// The text parser, the JSON reader and `observeRecord` call it for the copy function of a payload/// they wrap. The copy owns every byte: atom bytes, integer digits, bind names, compound storage,/// and nested payloads through their own copy functions. On `error.OutOfMemory` the function frees/// every partial copy.pub fn parsedEmbeddedClone(comptime Value: type) *const fn (*anyopaque, Allocator) Allocator.Error!*anyopaque {    const Impl = struct {        fn cloneSlice(allocator: Allocator, values: []const Value) Allocator.Error![]Value {            const cloned = try allocator.alloc(Value, values.len);            var index: usize = 0;            errdefer {                for (cloned[0..index]) |*value| value.deinit(allocator);                allocator.free(cloned);            }            while (index < values.len) : (index += 1) {                cloned[index] = try cloneValue(allocator, values[index]);            }            return cloned;        }        fn cloneValue(allocator: Allocator, value: Value) Allocator.Error!Value {            return switch (value) {                .boolean => |v| Value.initBoolean(v),                .double => |v| Value.initDouble(v),                .signed_integer => |v| Value.initSignedInteger(try v.clone(allocator)),                .string => |v| try Value.initString(allocator, v),                .byte_string => |v| try Value.initByteString(allocator, v),                .symbol => |v| try Value.initSymbol(allocator, v),                .record => |record| blk: {                    const label = try cloneValue(allocator, record.label.*);                    errdefer {                        var owned = label;                        owned.deinit(allocator);                    }                    const fields = try cloneSlice(allocator, record.fields);                    errdefer {                        for (fields) |*field| field.deinit(allocator);                        allocator.free(fields);                    }                    break :blk try Value.initRecord(allocator, label, fields);                },                .sequence => |items| Value.initSequence(try cloneSlice(allocator, items)),                .set => |items| Value.initSet(try cloneSlice(allocator, items)),                .dictionary => |entries| blk: {                    const cloned = try allocator.alloc(Value.DictionaryEntry, entries.len);                    var index: usize = 0;                    errdefer {                        for (cloned[0..index]) |*entry| {                            entry.key.deinit(allocator);                            entry.value.deinit(allocator);                        }                        allocator.free(cloned);                    }                    while (index < entries.len) : (index += 1) {                        var key = try cloneValue(allocator, entries[index].key);                        var key_owned = true;                        errdefer if (key_owned) key.deinit(allocator);                        cloned[index] = .{                            .key = key,                            .value = try cloneValue(allocator, entries[index].value),                        };                        key_owned = false;                    }                    break :blk Value.initDictionary(cloned);                },                .embedded => |embedded| Value.initEmbedded(try embedded.clone(allocator)),                .discard => .{ .discard = {} },                .capture => |inner| blk: {                    const cloned = try allocator.create(Value);                    errdefer allocator.destroy(cloned);                    cloned.* = try cloneValue(allocator, inner.*);                    break :blk .{ .capture = cloned };                },                .bind => |bind| blk: {                    const pattern = try allocator.create(Value);                    errdefer allocator.destroy(pattern);                    pattern.* = try cloneValue(allocator, bind.pattern.*);                    errdefer pattern.deinit(allocator);                    const name = try allocator.dupe(u8, bind.name);                    break :blk .{ .bind = .{ .name = name, .pattern = pattern } };                },                .rest_pattern => |rest| blk: {                    const prefix = try cloneSlice(allocator, rest.prefix);                    errdefer {                        for (prefix) |*item| item.deinit(allocator);                        allocator.free(prefix);                    }                    const rest_clone = try allocator.create(Value);                    errdefer allocator.destroy(rest_clone);                    rest_clone.* = try cloneValue(allocator, rest.rest.*);                    break :blk .{ .rest_pattern = .{ .prefix = prefix, .rest = rest_clone } };                },            };        }        fn call(ptr: *anyopaque, allocator: Allocator) Allocator.Error!*anyopaque {            const original: *const Value = @ptrCast(@alignCast(ptr));            const cloned = try allocator.create(Value);            errdefer allocator.destroy(cloned);            cloned.* = try cloneValue(allocator, original.*);            return @ptrCast(cloned);        }    };    return &Impl.call;}const domain_mod = @import("domain.zig");const value_mod = @import("value.zig");const containers_mod = @import("containers.zig");const ExactU32 = struct {    fn eql(a: *anyopaque, b: *anyopaque) bool {        const left: *const u32 = @ptrCast(@alignCast(a));        const right: *const u32 = @ptrCast(@alignCast(b));        return left.* == right.*;    }    fn hash(value: *anyopaque) u64 {        const payload: *const u32 = @ptrCast(@alignCast(value));        return payload.*;    }    fn order(a: *anyopaque, b: *anyopaque) std.math.Order {        const left: *const u32 = @ptrCast(@alignCast(a));        const right: *const u32 = @ptrCast(@alignCast(b));        return std.math.order(left.*, right.*);    }    const ops: SemanticOps = .{        .eql = &eql,        .hash = &hash,        .order = &order,    };};const ParityU32 = struct {    fn eql(a: *anyopaque, b: *anyopaque) bool {        const left: *const u32 = @ptrCast(@alignCast(a));        const right: *const u32 = @ptrCast(@alignCast(b));        return left.* % 2 == right.* % 2;    }    fn hash(value: *anyopaque) u64 {        const payload: *const u32 = @ptrCast(@alignCast(value));        return payload.* % 2;    }    fn order(a: *anyopaque, b: *anyopaque) std.math.Order {        const left: *const u32 = @ptrCast(@alignCast(a));        const right: *const u32 = @ptrCast(@alignCast(b));        return std.math.order(left.* % 2, right.* % 2);    }    const ops: SemanticOps = .{        .eql = &eql,        .hash = &hash,        .order = &order,    };};test "AnyEmbedded satisfies the Domain contract" {    comptime domain_mod.assertIsDomain(AnyEmbedded);}test "AnyEmbedded falls back to pointer identity" {    var a: u32 = 1;    var b: u32 = 2;    const ea: AnyEmbedded = .{ .value = &a };    const eb: AnyEmbedded = .{ .value = &b };    const ea2: AnyEmbedded = .{ .value = &a };    try std.testing.expect(ea.eql(ea2));    try std.testing.expect(!ea.eql(eb));    try std.testing.expectEqual(ea.hash(), ea2.hash());    try std.testing.expect(ea.hash() != eb.hash());    const ord = ea.order(eb);    try std.testing.expect(ord != .eq);}test "AnyEmbedded semantic operations are structurally bundled" {    try std.testing.expect(@hasField(AnyEmbedded, "semantic_ops"));    try std.testing.expect(!@hasField(AnyEmbedded, "eql_fn"));    try std.testing.expect(!@hasField(AnyEmbedded, "hash_fn"));    try std.testing.expect(!@hasField(AnyEmbedded, "order_fn"));    try std.testing.expect(@hasField(SemanticOps, "eql"));    try std.testing.expect(@hasField(SemanticOps, "hash"));    try std.testing.expect(@hasField(SemanticOps, "order"));}test "AnyEmbedded dispatches through one semantic bundle" {    var payload_a: u32 = 5;    var payload_b: u32 = 5;    const ea: AnyEmbedded = .{        .value = &payload_a,        .semantic_ops = &ExactU32.ops,    };    const eb: AnyEmbedded = .{        .value = &payload_b,        .semantic_ops = &ExactU32.ops,    };    try std.testing.expect(ea.eql(eb));    try std.testing.expectEqual(@as(u64, 5), ea.hash());    try std.testing.expectEqual(ea.hash(), eb.hash());    try std.testing.expectEqual(std.math.Order.eq, ea.order(eb));}test "AnyEmbedded keeps different semantic bundles disjoint and symmetric" {    var exact_payload: u32 = 2;    var parity_payload: u32 = 4;    const exact = AnyEmbedded{ .value = &exact_payload, .semantic_ops = &ExactU32.ops };    const parity = AnyEmbedded{ .value = &parity_payload, .semantic_ops = &ParityU32.ops };    try std.testing.expect(!exact.eql(parity));    try std.testing.expect(!parity.eql(exact));    const forward = exact.order(parity);    const reverse = parity.order(exact);    try std.testing.expect(forward != .eq);    try std.testing.expect(reverse != .eq);    try std.testing.expectEqual(forward == .lt, reverse == .gt);    const opaque_value = AnyEmbedded{ .value = &exact_payload };    try std.testing.expect(!opaque_value.eql(exact));    try std.testing.expect(!exact.eql(opaque_value));    try std.testing.expectEqual(std.math.Order.lt, opaque_value.order(exact));    try std.testing.expectEqual(std.math.Order.gt, exact.order(opaque_value));}test "parsed embedded clone and deinit own nested values" {    const V = value_mod.Value(AnyEmbedded);    const allocator = std.testing.allocator;    const original_payload = try allocator.create(V);    original_payload.* = try V.initString(allocator, "payload");    var embedded = AnyEmbedded{        .value = @ptrCast(original_payload),        .semantic_ops = parsedEmbeddedOps(V),        .deinit_fn = parsedEmbeddedDeinit(V),        .clone_fn = parsedEmbeddedClone(V),    };    defer embedded.deinit(allocator);    var cloned = try embedded.clone(allocator);    defer cloned.deinit(allocator);    try std.testing.expect(embedded.value != cloned.value);    const cloned_payload: *const V = @ptrCast(@alignCast(cloned.value));    try std.testing.expect(cloned_payload.* == .string);    try std.testing.expectEqualStrings("payload", cloned_payload.string);}fn checkParsedEmbeddedBindCloneAllocationFailures(allocator: Allocator) !void {    const V = value_mod.Value(AnyEmbedded);    var pattern = V{ .string = "pattern" };    var source_value = V{ .bind = .{ .name = "name", .pattern = &pattern } };    const source = AnyEmbedded{        .value = &source_value,        .semantic_ops = parsedEmbeddedOps(V),        .deinit_fn = parsedEmbeddedDeinit(V),        .clone_fn = parsedEmbeddedClone(V),    };    var cloned = try source.clone(allocator);    defer cloned.deinit(allocator);    const cloned_value: *const V = @ptrCast(@alignCast(cloned.value));    try std.testing.expect(cloned_value.* == .bind);    try std.testing.expectEqualStrings("name", cloned_value.bind.name);    try std.testing.expectEqualStrings("pattern", cloned_value.bind.pattern.string);}test "parsed embedded bind clone releases every allocation failure path" {    try std.testing.checkAllAllocationFailures(        std.testing.allocator,        checkParsedEmbeddedBindCloneAllocationFailures,        .{},    );}test "semantic embedded values remain lawful through nested sets and maps" {    const V = value_mod.Value(AnyEmbedded);    const Map = containers_mod.ValueHashMap(AnyEmbedded);    const allocator = std.testing.allocator;    var left_payloads = [_]u32{ 1, 2 };    var right_payloads = [_]u32{ 2, 1 };    var left_items = [_]V{        V.initEmbedded(.{ .value = &left_payloads[0], .semantic_ops = &ExactU32.ops }),        V.initEmbedded(.{ .value = &left_payloads[1], .semantic_ops = &ExactU32.ops }),    };    var right_items = [_]V{        V.initEmbedded(.{ .value = &right_payloads[0], .semantic_ops = &ExactU32.ops }),        V.initEmbedded(.{ .value = &right_payloads[1], .semantic_ops = &ExactU32.ops }),    };    const left = V.initSet(&left_items);    const right = V.initSet(&right_items);    try std.testing.expect(left.eql(right));    try std.testing.expectEqual(std.math.Order.eq, left.compare(right));    try std.testing.expectEqual(left.hash(), right.hash());    var map: Map = .{};    defer map.deinit(allocator);    try map.put(allocator, left, V.initBoolean(true));    try std.testing.expect(map.get(right).?.eql(V.initBoolean(true)));}

Source: lib/preserves/src/root.zig:110

zig
pub const embedded_mod = @import("embedded.zig");

Audit

Definitions3
Public names3
Members0
Version26.7.0
Revisiondaab053ee433