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.
parsedEmbeddedClone: Returns a function that copies a payload that is aValuein its own heap cell into a new cell.parsedEmbeddedDeinit: Returns a function that frees a payload that is aValuein its own heap cell: it callsdeiniton the value and then frees the cell.parsedEmbeddedOps: Returns one table whose equality, hash and order read each payload as a pointer to aValueand calleql,hashandcompareon it.
Types and contracts
Public types and contracts.
AnyEmbedded: An embedded value that points to a payload of the host program, with optional functions to compare, hash, free and copy it.SemanticOps: One table of equality, hash and order functions over untyped payload pointers.
Source
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
| Definitions | 3 |
|---|---|
| Public names | 3 |
| Members | 0 |
| Version | 26.7.0 |
| Revision | daab053ee433 |