Skip to documentation
SLOP

tiny.preserves.patterns_mod

Reference tiny.preserves patterns_mod

Defined in tiny.preserves.

Converts patterns between their in-memory form and a wire form made of plain records, reads the pattern and the observer out of a record that asks to observe a pattern, and walks the embedded values in a value.

API (14)

Actions

Public operations.

Types and contracts

Public types and contracts.

Values and defaults

Public values and defaults.

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

Source

Called byCallsNo direct callstest sourcelib.preserves.src.patternstest: observeObserver and observePatt...patterns_modobserveObserver
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callstest sourcelib.preserves.src.patternstest: observeObserver and observePatt...patterns_modobservePattern
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callstest sourcelib.preserves.src.patternstest: patternToPreserves capture → ...test sourcelib.preserves.src.patternstest: patternToPreserves discard → ...test sourcelib.preserves.src.patternstest: patternToPreserves literal → ...test sourcelib.preserves.src.patternstest: preservesToPattern round-trip t...test sourcelib.preserves.src.patternstest: preservesToPattern round-trip t...patterns_modpatternToPreserves
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callstest sourcelib.preserves.src.patternstest: preserve is identitypatterns_modpreserve
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callstest sourcelib.preserves.src.patterns.test_preservePatte...discardtest sourcelib.preserves.src.patterns.test_preservePatte...capturepatterns_modpreservePattern
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callstest sourcelib.preserves.src.patternstest: preservesToPattern lowers recur...test sourcelib.preserves.src.patternstest: preservesToPattern round-trip t...test sourcelib.preserves.src.patternstest: preservesToPattern round-trip t...patterns_modpreservesToPattern
Static calls · unresolved targets: 0 · external targets: 2.

Source: lib/preserves/src/patterns.zig

zig
//! Converts patterns between their in-memory form and a wire form made of plain records, reads the//! pattern and the observer out of a record that asks to observe a pattern, and walks the embedded//! values in a value.//!//! A pattern that travels between peers has to be written as a plain value and read back as the//! same pattern. A caller also needs to know which parts of a converted value it frees. The binary//! syntax has no tag for an in-memory pattern, and the packed writer refuses one.//!//! The package keeps the patterns of the [Preserves](https://preserves.dev/) data language, which//! comes from the Syndicate ecosystem. Each in-memory pattern becomes a record: `<_>` for the//! discard, `<bind P>` for a capture, `<lit v>` for an atom or set, and `<group <rec L> {…}>`,//! `<group <arr> {…}>` or `<group <dict> {…}>` for a record, sequence or dictionary whose items are//! patterns. `patternToPreserves` writes that wire form, `preservesToPattern` reads it back, and//! `preservePattern` reads it too and never reports a failure. The conversions allocate new//! records, sequences, dictionaries and pattern cells, and leave strings, symbols, byte strings and//! bind names pointing into their input. A bind loses its name on the wire, because//! `patternToPreserves` writes it as `<bind P>`, which reads back as a capture.const std = @import("std");const Allocator = std.mem.Allocator;const value_mod = @import("value.zig");const domain_mod = @import("domain.zig");const symbols_mod = @import("symbols.zig");const constructors_mod = @import("constructors.zig");const ownership = @import("ownership.zig");const predicates_mod = @import("predicates.zig");const embedded_mod = @import("embedded.zig");pub const Value = value_mod.Value;pub const NoEmbedded = domain_mod.NoEmbedded;pub const AnyEmbedded = embedded_mod.AnyEmbedded;/// Returns a namespace of pattern conversions for `Value(D)`. Code whose values hold embedded/// values of one type calls it once at compile time, for every pattern conversion over those/// values. The type `D` has to provide `eql`, `order`, `deinit` and `clone`, or the call is a/// compile error. `conversions` and `any_conversions` are its two instances.pub fn Conversions(comptime D: type) type {    domain_mod.assertIsDomain(D);    const V = Value(D);    const H = constructors_mod.Constructors(D);    return struct {        const Self = @This();        /// Returns `value` unchanged. The call allocates nothing.        pub fn preserve(alloc: Allocator, value: V) V {            _ = alloc;            return value;        }        /// Returns the in-memory pattern that the wire value `pattern` spells, allocated with        /// `alloc`. Code receiving a pattern in wire form calls it for an in-memory pattern. The        /// symbol `_` and the record `<_>` become the discard pattern. `<bind P>` becomes a capture        /// of `P`, and `<lit v>` becomes `v` itself. `<group <rec L> {…}>` becomes a record labeled        /// `L`, and `<group <arr> {…}>` a sequence, each item taken in key order from the        /// dictionary. `<group <dict> {…}>` becomes a dictionary with the same keys. Other records,        /// sequences and dictionaries keep their shape, and their parts are converted in turn. The        /// result shares atoms and `<lit>` contents with `pattern`, so `pattern` has to outlive it.        /// When an allocation fails, the call returns the unconverted input at that level and keeps        /// what it already allocated. The package's tests build its result in an arena and free the        /// arena whole.        pub fn preservePattern(alloc: Allocator, pattern: V) V {            switch (pattern) {                .discard => return pattern,                .capture => |inner| {                    const new = alloc.create(V) catch return pattern;                    new.* = Self.preservePattern(alloc, inner.*);                    return .{ .capture = new };                },                .bind => |b| {                    const new_pat = alloc.create(V) catch return pattern;                    new_pat.* = Self.preservePattern(alloc, b.pattern.*);                    return .{ .bind = .{ .name = b.name, .pattern = new_pat } };                },                .rest_pattern => |rp| {                    const new_prefix = alloc.alloc(V, rp.prefix.len) catch return pattern;                    for (rp.prefix, 0..) |item, i| {                        new_prefix[i] = Self.preservePattern(alloc, item);                    }                    const new_rest = alloc.create(V) catch return pattern;                    new_rest.* = Self.preservePattern(alloc, rp.rest.*);                    return .{ .rest_pattern = .{ .prefix = new_prefix, .rest = new_rest } };                },                .record => |r| return Self.preservePatternRecord(alloc, pattern, r),                .symbol => |s| {                    if (std.mem.eql(u8, s, symbols_mod.SYM_DISCARD.name)) return .{ .discard = {} };                    return pattern;                },                .dictionary => |d| {                    const new_entries = alloc.alloc(V.DictionaryEntry, d.len) catch return pattern;                    for (d, 0..) |entry, i| {                        new_entries[i] = .{                            .key = Self.preservePattern(alloc, entry.key),                            .value = Self.preservePattern(alloc, entry.value),                        };                    }                    return .{ .dictionary = new_entries };                },                .sequence => |s| {                    const new_items = alloc.alloc(V, s.len) catch return pattern;                    for (s, 0..) |item, i| {                        new_items[i] = Self.preservePattern(alloc, item);                    }                    return .{ .sequence = new_items };                },                else => return pattern,            }        }        fn preservePatternRecord(alloc: Allocator, fallback: V, r: V.Record) V {            if (r.label.* == .symbol) {                const lname = r.label.*.symbol;                if (std.mem.eql(u8, lname, symbols_mod.SYM_DISCARD.name) and r.fields.len == 0) {                    return .{ .discard = {} };                }                if (std.mem.eql(u8, lname, symbols_mod.SYM_BIND_PAT.name) and r.fields.len == 1) {                    const inner = alloc.create(V) catch return fallback;                    inner.* = Self.preservePattern(alloc, r.fields[0]);                    return .{ .capture = inner };                }                if (std.mem.eql(u8, lname, symbols_mod.SYM_LIT.name) and r.fields.len == 1) {                    return r.fields[0];                }                if (std.mem.eql(u8, lname, symbols_mod.SYM_GROUP.name) and r.fields.len == 2) {                    if (Self.preserveGroup(alloc, r.fields[0], r.fields[1], fallback)) |g| return g;                }            }            const new_label = alloc.create(V) catch return fallback;            new_label.* = Self.preserve(alloc, r.label.*);            const new_fields = alloc.alloc(V, r.fields.len) catch return fallback;            for (r.fields, 0..) |field, i| {                new_fields[i] = Self.preservePattern(alloc, field);            }            return .{ .record = .{ .label = new_label, .fields = new_fields } };        }        fn preserveGroup(alloc: Allocator, group_type: V, entries_val: V, fallback: V) ?V {            const gt = switch (group_type) {                .record => |gtr| gtr,                else => return null,            };            if (gt.label.* != .symbol) return null;            const gs = gt.label.*.symbol;            if (std.mem.eql(u8, gs, symbols_mod.SYM_REC.name)) {                const rec_label = if (gt.fields.len > 0) gt.fields[0] else V{ .symbol = "" };                const fields = Self.entriesToIndexed(alloc, entries_val) catch return fallback;                defer alloc.free(fields);                const new_fields = alloc.alloc(V, fields.len) catch return fallback;                for (fields, 0..) |f, i| {                    new_fields[i] = Self.preservePattern(alloc, f);                }                const new_label = alloc.create(V) catch return fallback;                new_label.* = rec_label;                return V{ .record = .{ .label = new_label, .fields = new_fields } };            }            if (std.mem.eql(u8, gs, symbols_mod.SYM_ARR.name)) {                const fields = Self.entriesToIndexed(alloc, entries_val) catch return fallback;                defer alloc.free(fields);                const new_items = alloc.alloc(V, fields.len) catch return fallback;                for (fields, 0..) |f, i| {                    new_items[i] = Self.preservePattern(alloc, f);                }                return V{ .sequence = new_items };            }            if (std.mem.eql(u8, gs, symbols_mod.SYM_DICT.name)) {                switch (entries_val) {                    .dictionary => |d| {                        const new_entries = alloc.alloc(V.DictionaryEntry, d.len) catch return fallback;                        for (d, 0..) |entry, i| {                            new_entries[i] = .{                                .key = entry.key,                                .value = Self.preservePattern(alloc, entry.value),                            };                        }                        return V{ .dictionary = new_entries };                    },                    else => {},                }            }            return null;        }        /// Returns the wire form of `pattern`, allocated with `alloc`. The text formatter calls it        /// for the wire spelling of captures and binds, and code sending a pattern calls it, for a        /// plain value the codecs can write. The discard pattern becomes `<_>`, and a capture or        /// bind becomes `<bind P>` around its converted pattern. An atom or a set becomes        /// `<lit v>`, and an embedded value stays as it is. A record becomes        /// `<group <rec L> {0: …, 1: …}>`, and a sequence `<group <arr> {…}>`, keyed by position. A        /// dictionary becomes `<group <dict> {…}>` with the same keys. A rest pattern becomes an        /// array group of its prefix, then `<lit .>`, then its rest. The result shares atoms,        /// labels and dictionary keys with `pattern`, so `pattern` has to outlive it. A bind's name        /// does not appear in the result. The only error is running out of memory, and the call        /// does not free what it built before the failure.        pub fn patternToPreserves(alloc: Allocator, pattern: V) !V {            return switch (pattern) {                .discard => H.record(alloc, V{ .symbol = symbols_mod.SYM_DISCARD.name }, &.{}),                .capture => |inner| blk: {                    const converted = try Self.patternToPreserves(alloc, inner.*);                    break :blk H.record(alloc, V{ .symbol = symbols_mod.SYM_BIND_PAT.name }, &.{converted});                },                .bind => |b| blk: {                    const converted = try Self.patternToPreserves(alloc, b.pattern.*);                    break :blk H.record(alloc, V{ .symbol = symbols_mod.SYM_BIND_PAT.name }, &.{converted});                },                .boolean,                .double,                .signed_integer,                .string,                .byte_string,                .symbol,                => H.record(alloc, V{ .symbol = symbols_mod.SYM_LIT.name }, &.{pattern}),                .embedded => pattern,                .record => |r| blk: {                    const group_type = try H.record(alloc, V{ .symbol = symbols_mod.SYM_REC.name }, &.{r.label.*});                    const entries = try alloc.alloc(V.DictionaryEntry, r.fields.len);                    for (r.fields, 0..) |field, i| {                        entries[i] = .{                            .key = V.initI128(@as(i128, @intCast(i))),                            .value = try Self.patternToPreserves(alloc, field),                        };                    }                    const dict = V{ .dictionary = entries };                    break :blk H.record(alloc, V{ .symbol = symbols_mod.SYM_GROUP.name }, &.{ group_type, dict });                },                .sequence => |items| blk: {                    const group_type = try H.record(alloc, V{ .symbol = symbols_mod.SYM_ARR.name }, &.{});                    const entries = try alloc.alloc(V.DictionaryEntry, items.len);                    for (items, 0..) |item, i| {                        entries[i] = .{                            .key = V.initI128(@as(i128, @intCast(i))),                            .value = try Self.patternToPreserves(alloc, item),                        };                    }                    const dict = V{ .dictionary = entries };                    break :blk H.record(alloc, V{ .symbol = symbols_mod.SYM_GROUP.name }, &.{ group_type, dict });                },                .dictionary => |d| blk: {                    const group_type = try H.record(alloc, V{ .symbol = symbols_mod.SYM_DICT.name }, &.{});                    const entries = try alloc.alloc(V.DictionaryEntry, d.len);                    for (d, 0..) |entry, i| {                        entries[i] = .{                            .key = entry.key,                            .value = try Self.patternToPreserves(alloc, entry.value),                        };                    }                    const dict = V{ .dictionary = entries };                    break :blk H.record(alloc, V{ .symbol = symbols_mod.SYM_GROUP.name }, &.{ group_type, dict });                },                .rest_pattern => |rp| blk: {                    const items = try alloc.alloc(V, rp.prefix.len + 2);                    defer alloc.free(items);                    for (rp.prefix, 0..) |item, i| {                        items[i] = item;                    }                    items[rp.prefix.len] = V{ .symbol = "." };                    items[rp.prefix.len + 1] = rp.rest.*;                    break :blk try Self.patternToPreserves(alloc, V{ .sequence = items });                },                .set => H.record(alloc, V{ .symbol = symbols_mod.SYM_LIT.name }, &.{pattern}),            };        }        /// Returns the in-memory pattern that the wire value `value` spells, allocated with        /// `alloc`. Code receiving a pattern in wire form calls it for an in-memory pattern, with        /// allocation failure reported. The call reads the same spellings as `preservePattern`: `_`        /// and `<_>`, `<bind P>`, `<lit v>` and the three `<group>` forms. A `<lit v>` gets a copy        /// of `v`'s compound storage. Other records, sequences, dictionaries and in-memory patterns        /// keep their shape, and their parts are converted in turn. Strings, symbols, byte strings        /// and bind names in the result point into `value`, and sets, integers and embedded values        /// come back as they are, sharing storage with `value`. The package's test frees a result        /// with `freeValueDeep`, which frees shared sets and integers too. The call returns        /// `error.OutOfMemory` when an allocation fails. On failure, most arms free what they        /// built, and the `<group>` arms and the `<bind P>` arm leak it.        pub fn preservesToPattern(alloc: Allocator, value: V) Allocator.Error!V {            switch (value) {                .symbol => |s| {                    if (std.mem.eql(u8, s, symbols_mod.SYM_DISCARD.name)) {                        return .{ .discard = {} };                    }                },                .record => |r| {                    switch (r.label.*) {                        .symbol => |s| {                            if (std.mem.eql(u8, s, symbols_mod.SYM_DISCARD.name) and r.fields.len == 0) {                                return .{ .discard = {} };                            }                            if (std.mem.eql(u8, s, symbols_mod.SYM_BIND_PAT.name) and r.fields.len == 1) {                                const inner = try Self.preservesToPattern(alloc, r.fields[0]);                                return H.capture(alloc, inner);                            }                            if (std.mem.eql(u8, s, symbols_mod.SYM_LIT.name) and r.fields.len == 1) {                                return try Self.cloneValueOwned(alloc, r.fields[0]);                            }                            if (std.mem.eql(u8, s, symbols_mod.SYM_GROUP.name) and r.fields.len == 2) {                                if (try Self.preservesToGroup(alloc, r.fields[0], r.fields[1])) |g| return g;                            }                        },                        else => {},                    }                    return try Self.plainRecordToPattern(alloc, r);                },                .sequence => |items| {                    const converted = try alloc.alloc(V, items.len);                    var filled: usize = 0;                    errdefer {                        for (converted[0..filled]) |item| {                            ownership.freeValueDeep(D, alloc, item);                        }                        alloc.free(converted);                    }                    while (filled < items.len) : (filled += 1) {                        converted[filled] = try Self.preservesToPattern(alloc, items[filled]);                    }                    return .{ .sequence = converted };                },                .dictionary => |entries| {                    const converted = try alloc.alloc(V.DictionaryEntry, entries.len);                    var filled: usize = 0;                    errdefer {                        for (converted[0..filled]) |entry| {                            ownership.freeValueDeep(D, alloc, entry.key);                            ownership.freeValueDeep(D, alloc, entry.value);                        }                        alloc.free(converted);                    }                    while (filled < entries.len) : (filled += 1) {                        const key = try Self.cloneValueOwned(alloc, entries[filled].key);                        errdefer ownership.freeValueDeep(D, alloc, key);                        converted[filled] = .{                            .key = key,                            .value = try Self.preservesToPattern(alloc, entries[filled].value),                        };                    }                    return .{ .dictionary = converted };                },                .capture => |inner| {                    const converted = try Self.preservesToPattern(alloc, inner.*);                    return H.capture(alloc, converted);                },                .bind => |binding| {                    const pattern = try alloc.create(V);                    errdefer alloc.destroy(pattern);                    pattern.* = try Self.preservesToPattern(alloc, binding.pattern.*);                    return .{ .bind = .{                        .name = binding.name,                        .pattern = pattern,                    } };                },                .rest_pattern => |rest| {                    const prefix = try alloc.alloc(V, rest.prefix.len);                    var filled: usize = 0;                    errdefer {                        for (prefix[0..filled]) |item| {                            ownership.freeValueDeep(D, alloc, item);                        }                        alloc.free(prefix);                    }                    while (filled < rest.prefix.len) : (filled += 1) {                        prefix[filled] = try Self.preservesToPattern(alloc, rest.prefix[filled]);                    }                    const rest_ptr = try alloc.create(V);                    errdefer alloc.destroy(rest_ptr);                    rest_ptr.* = try Self.preservesToPattern(alloc, rest.rest.*);                    return .{ .rest_pattern = .{ .prefix = prefix, .rest = rest_ptr } };                },                else => {},            }            return value;        }        fn plainRecordToPattern(alloc: Allocator, r: V.Record) Allocator.Error!V {            const label = try alloc.create(V);            errdefer alloc.destroy(label);            label.* = try Self.cloneValueOwned(alloc, r.label.*);            errdefer ownership.freeValueDeep(D, alloc, label.*);            const fields = try alloc.alloc(V, r.fields.len);            var filled: usize = 0;            errdefer {                for (fields[0..filled]) |field| {                    ownership.freeValueDeep(D, alloc, field);                }                alloc.free(fields);            }            while (filled < r.fields.len) : (filled += 1) {                fields[filled] = try Self.preservesToPattern(alloc, r.fields[filled]);            }            return .{ .record = .{                .label = label,                .fields = fields,            } };        }        fn cloneValueOwned(alloc: Allocator, value: V) Allocator.Error!V {            return switch (value) {                .boolean, .double, .string, .byte_string, .symbol, .discard => value,                .signed_integer => |si| .{ .signed_integer = try si.clone(alloc) },                .record => |record| blk: {                    const label = try alloc.create(V);                    errdefer alloc.destroy(label);                    label.* = try Self.cloneValueOwned(alloc, record.label.*);                    errdefer ownership.freeValueDeep(D, alloc, label.*);                    break :blk .{ .record = .{                        .label = label,                        .fields = try Self.cloneValueSliceOwned(alloc, record.fields),                    } };                },                .sequence => |items| .{ .sequence = try Self.cloneValueSliceOwned(alloc, items) },                .set => |items| .{ .set = try Self.cloneValueSliceOwned(alloc, items) },                .dictionary => |entries| .{ .dictionary = try Self.cloneDictionaryOwned(alloc, entries) },                .embedded => |embedded| .{ .embedded = try embedded.clone(alloc) },                .capture => |inner| blk: {                    const cloned = try alloc.create(V);                    errdefer alloc.destroy(cloned);                    cloned.* = try Self.cloneValueOwned(alloc, inner.*);                    break :blk .{ .capture = cloned };                },                .bind => |binding| blk: {                    const cloned = try alloc.create(V);                    errdefer alloc.destroy(cloned);                    cloned.* = try Self.cloneValueOwned(alloc, binding.pattern.*);                    break :blk .{ .bind = .{                        .name = binding.name,                        .pattern = cloned,                    } };                },                .rest_pattern => |rest| blk: {                    const prefix = try Self.cloneValueSliceOwned(alloc, rest.prefix);                    errdefer {                        for (prefix) |item| ownership.freeValueDeep(D, alloc, item);                        alloc.free(prefix);                    }                    const rest_ptr = try alloc.create(V);                    errdefer alloc.destroy(rest_ptr);                    rest_ptr.* = try Self.cloneValueOwned(alloc, rest.rest.*);                    break :blk .{ .rest_pattern = .{ .prefix = prefix, .rest = rest_ptr } };                },            };        }        fn cloneValueSliceOwned(alloc: Allocator, items: []const V) Allocator.Error![]V {            const cloned = try alloc.alloc(V, items.len);            var filled: usize = 0;            errdefer {                for (cloned[0..filled]) |item| {                    ownership.freeValueDeep(D, alloc, item);                }                alloc.free(cloned);            }            while (filled < items.len) : (filled += 1) {                cloned[filled] = try Self.cloneValueOwned(alloc, items[filled]);            }            return cloned;        }        fn cloneDictionaryOwned(alloc: Allocator, entries: []const V.DictionaryEntry) Allocator.Error![]V.DictionaryEntry {            const cloned = try alloc.alloc(V.DictionaryEntry, entries.len);            var filled: usize = 0;            errdefer {                for (cloned[0..filled]) |entry| {                    ownership.freeValueDeep(D, alloc, entry.key);                    ownership.freeValueDeep(D, alloc, entry.value);                }                alloc.free(cloned);            }            while (filled < entries.len) : (filled += 1) {                const key = try Self.cloneValueOwned(alloc, entries[filled].key);                errdefer ownership.freeValueDeep(D, alloc, key);                cloned[filled] = .{                    .key = key,                    .value = try Self.cloneValueOwned(alloc, entries[filled].value),                };            }            return cloned;        }        fn preservesToGroup(alloc: Allocator, group_type: V, entries_val: V) Allocator.Error!?V {            const gt = switch (group_type) {                .record => |gtr| gtr,                else => return null,            };            const gs = switch (gt.label.*) {                .symbol => |s| s,                else => return null,            };            if (std.mem.eql(u8, gs, symbols_mod.SYM_REC.name)) {                const rec_label = if (gt.fields.len > 0) gt.fields[0] else V{ .symbol = "" };                const fields = try Self.entriesToIndexed(alloc, entries_val);                defer alloc.free(fields);                const converted = try alloc.alloc(V, fields.len);                defer alloc.free(converted);                for (fields, 0..) |f, i| {                    converted[i] = try Self.preservesToPattern(alloc, f);                }                return try H.record(alloc, rec_label, converted);            }            if (std.mem.eql(u8, gs, symbols_mod.SYM_ARR.name)) {                const fields = try Self.entriesToIndexed(alloc, entries_val);                defer alloc.free(fields);                const converted = try alloc.alloc(V, fields.len);                for (fields, 0..) |f, i| {                    converted[i] = try Self.preservesToPattern(alloc, f);                }                return V{ .sequence = converted };            }            if (std.mem.eql(u8, gs, symbols_mod.SYM_DICT.name)) {                switch (entries_val) {                    .dictionary => |d| {                        const converted = try alloc.alloc(V.DictionaryEntry, d.len);                        for (d, 0..) |entry, i| {                            converted[i] = .{                                .key = entry.key,                                .value = try Self.preservesToPattern(alloc, entry.value),                            };                        }                        return V{ .dictionary = converted };                    },                    else => {},                }            }            return null;        }        fn entriesToIndexed(alloc: Allocator, entries_val: V) Allocator.Error![]V {            switch (entries_val) {                .dictionary => |d| {                    const sorted = try alloc.dupe(V.DictionaryEntry, d);                    defer alloc.free(sorted);                    const Cmp = struct {                        fn lt(_: void, a: V.DictionaryEntry, b: V.DictionaryEntry) bool {                            return a.key.compare(b.key) == .lt;                        }                    };                    std.mem.sort(V.DictionaryEntry, sorted, {}, Cmp.lt);                    const result = try alloc.alloc(V, sorted.len);                    for (sorted, 0..) |entry, i| {                        result[i] = entry.value;                    }                    return result;                },                else => return try alloc.alloc(V, 0),            }        }        /// Returns the observer, the second field of an `<Observe pattern observer>` record. The        /// call returns null unless `value` is a record labeled `Observe` with two fields. The        /// result shares storage with `value`, and the call allocates nothing.        pub fn observeObserver(value: V) ?V {            if (!predicates_mod.isRecord(D, value, symbols_mod.SYM_OBSERVE.name, 2)) return null;            return value.record.fields[1];        }        /// Returns the pattern, the first field of an `<Observe pattern observer>` record. The call        /// returns null unless `value` is a record labeled `Observe` with two fields. The result        /// shares storage with `value`, and the call allocates nothing.        pub fn observePattern(value: V) ?V {            if (!predicates_mod.isRecord(D, value, symbols_mod.SYM_OBSERVE.name, 2)) return null;            return value.record.fields[0];        }        /// Calls `callback(context, e)` for each embedded value `e` in `value`, depth first. Code        /// holding references through embedded values calls it, for one visit to each, such as a        /// count or a retain. The call visits record labels and fields, sequence and set items,        /// dictionary keys and values, and the parts of in-memory patterns. The call allocates        /// nothing and returns nothing.        pub fn foreachEmbedded(value: V, context: anytype, callback: anytype) void {            switch (value) {                .embedded => |e| callback(context, e),                .record => |r| {                    Self.foreachEmbedded(r.label.*, context, callback);                    for (r.fields) |field| Self.foreachEmbedded(field, context, callback);                },                .sequence => |s| for (s) |item| Self.foreachEmbedded(item, context, callback),                .set => |s| for (s) |item| Self.foreachEmbedded(item, context, callback),                .dictionary => |d| for (d) |entry| {                    Self.foreachEmbedded(entry.key, context, callback);                    Self.foreachEmbedded(entry.value, context, callback);                },                .capture => |p| Self.foreachEmbedded(p.*, context, callback),                .bind => |b| Self.foreachEmbedded(b.pattern.*, context, callback),                .rest_pattern => |rp| {                    for (rp.prefix) |item| Self.foreachEmbedded(item, context, callback);                    Self.foreachEmbedded(rp.rest.*, context, callback);                },                else => {},            }        }        /// Returns a copy of `value` in which each embedded value `e` is replaced by        /// `map_fn(context, alloc, e)`. Code that moves a value to another type of embedded value,        /// or swaps each embedded value for another value, calls it for the rewritten copy. The        /// call copies records, sequences, sets, dictionaries and in-memory patterns with `alloc`,        /// and returns atoms as they are. The call returns any error `map_fn` returns, and        /// `error.OutOfMemory`. On failure the call does not free what it built before.        pub fn mapEmbedded(            alloc: Allocator,            value: V,            context: anytype,            map_fn: anytype,        ) !V {            switch (value) {                .embedded => |e| return map_fn(context, alloc, e),                .record => |r| {                    const new_label = try alloc.create(V);                    new_label.* = try Self.mapEmbedded(alloc, r.label.*, context, map_fn);                    const new_fields = try alloc.alloc(V, r.fields.len);                    for (r.fields, 0..) |field, i| {                        new_fields[i] = try Self.mapEmbedded(alloc, field, context, map_fn);                    }                    return V{ .record = .{ .label = new_label, .fields = new_fields } };                },                .sequence => |s| {                    const new_items = try alloc.alloc(V, s.len);                    for (s, 0..) |item, i| {                        new_items[i] = try Self.mapEmbedded(alloc, item, context, map_fn);                    }                    return V{ .sequence = new_items };                },                .set => |s| {                    const new_items = try alloc.alloc(V, s.len);                    for (s, 0..) |item, i| {                        new_items[i] = try Self.mapEmbedded(alloc, item, context, map_fn);                    }                    return V{ .set = new_items };                },                .dictionary => |d| {                    const new_entries = try alloc.alloc(V.DictionaryEntry, d.len);                    for (d, 0..) |entry, i| {                        new_entries[i] = .{                            .key = try Self.mapEmbedded(alloc, entry.key, context, map_fn),                            .value = try Self.mapEmbedded(alloc, entry.value, context, map_fn),                        };                    }                    return V{ .dictionary = new_entries };                },                .capture => |p| {                    const new_ptr = try alloc.create(V);                    new_ptr.* = try Self.mapEmbedded(alloc, p.*, context, map_fn);                    return V{ .capture = new_ptr };                },                .bind => |b| {                    const new_pat = try alloc.create(V);                    new_pat.* = try Self.mapEmbedded(alloc, b.pattern.*, context, map_fn);                    return V{ .bind = .{ .name = b.name, .pattern = new_pat } };                },                .rest_pattern => |rp| {                    const new_prefix = try alloc.alloc(V, rp.prefix.len);                    for (rp.prefix, 0..) |item, i| {                        new_prefix[i] = try Self.mapEmbedded(alloc, item, context, map_fn);                    }                    const new_rest = try alloc.create(V);                    new_rest.* = try Self.mapEmbedded(alloc, rp.rest.*, context, map_fn);                    return V{ .rest_pattern = .{ .prefix = new_prefix, .rest = new_rest } };                },                else => return value,            }        }    };}/// The pattern conversions for `Value(NoEmbedded)`. Code whose values are `Value(NoEmbedded)` calls/// these conversions, for a namespace fixed to that type. The top-level functions of this file call/// it.pub const conversions = Conversions(NoEmbedded);/// The pattern conversions for values whose embedded values hold any pointer (`AnyEmbedded`). The/// package root's pattern functions are these, so a caller of `preserves.patternToPreserves` calls/// this instance. The package root and the text formatter use it.pub const any_conversions = Conversions(AnyEmbedded);/// Returns `value` unchanged, the same as `conversions.preserve`.pub fn preserve(alloc: Allocator, value: Value(NoEmbedded)) Value(NoEmbedded) {    return conversions.preserve(alloc, value);}/// Returns the in-memory pattern spelled by `pattern`, the same as `conversions.preservePattern`.pub fn preservePattern(alloc: Allocator, pattern: Value(NoEmbedded)) Value(NoEmbedded) {    return conversions.preservePattern(alloc, pattern);}/// Returns the wire form of `pattern`, the same as `conversions.patternToPreserves`.pub fn patternToPreserves(alloc: Allocator, pattern: Value(NoEmbedded)) !Value(NoEmbedded) {    return conversions.patternToPreserves(alloc, pattern);}/// Returns the in-memory pattern spelled by `value`, the same as `conversions.preservesToPattern`.pub fn preservesToPattern(alloc: Allocator, value: Value(NoEmbedded)) !Value(NoEmbedded) {    return conversions.preservesToPattern(alloc, value);}/// Returns the observer of an `Observe` record, the same as `conversions.observeObserver`.pub fn observeObserver(value: Value(NoEmbedded)) ?Value(NoEmbedded) {    return conversions.observeObserver(value);}/// Returns the pattern of an `Observe` record, the same as `conversions.observePattern`.pub fn observePattern(value: Value(NoEmbedded)) ?Value(NoEmbedded) {    return conversions.observePattern(value);}/// Calls `callback` for each embedded value in `value`, the same as `conversions.foreachEmbedded`.pub fn foreachEmbedded(value: Value(NoEmbedded), context: anytype, callback: anytype) void {    conversions.foreachEmbedded(value, context, callback);}/// Returns a copy of `value` with each embedded value mapped, the same as/// `conversions.mapEmbedded`.pub fn mapEmbedded(    alloc: Allocator,    value: Value(NoEmbedded),    context: anytype,    map_fn: anytype,) !Value(NoEmbedded) {    return conversions.mapEmbedded(alloc, value, context, map_fn);}test "preserve is identity" {    const V = Value(NoEmbedded);    const allocator = std.testing.allocator;    const v = V.initI128(42);    const r = preserve(allocator, v);    try std.testing.expect(r.eql(v));}test "patternToPreserves discard → <_>" {    const V = Value(NoEmbedded);    const allocator = std.testing.allocator;    var arena = std.heap.ArenaAllocator.init(allocator);    defer arena.deinit();    const a = arena.allocator();    const wire = try patternToPreserves(a, V{ .discard = {} });    try std.testing.expectEqual(value_mod.CompoundClass.record, wire.compoundClass().?);    try std.testing.expect(std.mem.eql(u8, wire.record.label.*.symbol, symbols_mod.SYM_DISCARD.name));    try std.testing.expectEqual(@as(usize, 0), wire.record.fields.len);}test "patternToPreserves literal → <lit v>" {    const V = Value(NoEmbedded);    const allocator = std.testing.allocator;    var arena = std.heap.ArenaAllocator.init(allocator);    defer arena.deinit();    const a = arena.allocator();    const wire = try patternToPreserves(a, V.initI128(42));    try std.testing.expect(std.mem.eql(u8, wire.record.label.*.symbol, symbols_mod.SYM_LIT.name));    try std.testing.expectEqual(@as(usize, 1), wire.record.fields.len);    try std.testing.expectEqual(@as(i128, 42), try wire.record.fields[0].signed_integer.toI128());}test "patternToPreserves capture → <bind inner>" {    const V = Value(NoEmbedded);    const allocator = std.testing.allocator;    var arena = std.heap.ArenaAllocator.init(allocator);    defer arena.deinit();    const a = arena.allocator();    const inner = try a.create(V);    inner.* = V{ .discard = {} };    const cap: V = .{ .capture = inner };    const wire = try patternToPreserves(a, cap);    try std.testing.expect(std.mem.eql(u8, wire.record.label.*.symbol, symbols_mod.SYM_BIND_PAT.name));    try std.testing.expectEqual(@as(usize, 1), wire.record.fields.len);    try std.testing.expect(std.mem.eql(u8, wire.record.fields[0].record.label.*.symbol, symbols_mod.SYM_DISCARD.name));}test "preservesToPattern round-trip through discard, literal, capture" {    const V = Value(NoEmbedded);    const allocator = std.testing.allocator;    var arena = std.heap.ArenaAllocator.init(allocator);    defer arena.deinit();    const a = arena.allocator();    const d: V = .{ .discard = {} };    const d_wire = try patternToPreserves(a, d);    const d_back = try preservesToPattern(a, d_wire);    try std.testing.expectEqual(value_mod.PatternFormClass.discard, d_back.patternClass().?);    const lit = V.initI128(7);    const lit_wire = try patternToPreserves(a, lit);    const lit_back = try preservesToPattern(a, lit_wire);    try std.testing.expectEqual(@as(i128, 7), try lit_back.signed_integer.toI128());    const inner = try a.create(V);    inner.* = V{ .discard = {} };    const cap: V = .{ .capture = inner };    const cap_wire = try patternToPreserves(a, cap);    const cap_back = try preservesToPattern(a, cap_wire);    try std.testing.expectEqual(value_mod.PatternFormClass.capture, cap_back.patternClass().?);    try std.testing.expectEqual(value_mod.PatternFormClass.discard, cap_back.capture.*.patternClass().?);}test "preservesToPattern round-trip through record group" {    const V = Value(NoEmbedded);    const allocator = std.testing.allocator;    var arena = std.heap.ArenaAllocator.init(allocator);    defer arena.deinit();    const a = arena.allocator();    const inner_fields = try a.alloc(V, 2);    inner_fields[0] = V{ .discard = {} };    inner_fields[1] = V.initI128(1);    const label = try V.initSymbol(a, "Foo");    const rec = try V.initRecord(a, label, inner_fields);    const wire = try patternToPreserves(a, rec);    try std.testing.expect(std.mem.eql(u8, wire.record.label.*.symbol, symbols_mod.SYM_GROUP.name));    const back = try preservesToPattern(a, wire);    try std.testing.expectEqual(value_mod.CompoundClass.record, back.compoundClass().?);    try std.testing.expect(std.mem.eql(u8, back.record.label.*.symbol, "Foo"));    try std.testing.expectEqual(@as(usize, 2), back.record.fields.len);    try std.testing.expectEqual(value_mod.PatternFormClass.discard, back.record.fields[0].patternClass().?);    try std.testing.expectEqual(@as(i128, 1), try back.record.fields[1].signed_integer.toI128());}test "preservePattern lowers <_> symbol to .discard" {    const V = Value(NoEmbedded);    const allocator = std.testing.allocator;    var arena = std.heap.ArenaAllocator.init(allocator);    defer arena.deinit();    const a = arena.allocator();    const sym_underscore: V = V{ .symbol = symbols_mod.SYM_DISCARD.name };    const got = preservePattern(a, sym_underscore);    try std.testing.expectEqual(value_mod.PatternFormClass.discard, got.patternClass().?);}test "preservesToPattern lowers recursive plain-record wildcards" {    const V = Value(NoEmbedded);    const allocator = std.testing.allocator;    var arena = std.heap.ArenaAllocator.init(allocator);    defer arena.deinit();    const a = arena.allocator();    const fields = try a.alloc(V, 2);    fields[0] = V{ .symbol = symbols_mod.SYM_DISCARD.name };    fields[1] = V.initI128(1);    const label = try V.initSymbol(a, "Note");    const rec = try V.initRecord(a, label, fields);    const got = try preservesToPattern(allocator, rec);    defer ownership.freeValueDeep(NoEmbedded, allocator, got);    try std.testing.expectEqual(value_mod.CompoundClass.record, got.compoundClass().?);    try std.testing.expect(std.mem.eql(u8, got.record.label.*.symbol, "Note"));    try std.testing.expectEqual(@as(usize, 2), got.record.fields.len);    try std.testing.expectEqual(value_mod.PatternFormClass.discard, got.record.fields[0].patternClass().?);    try std.testing.expectEqual(@as(i128, 1), try got.record.fields[1].signed_integer.toI128());}test "preservePattern lowers <bind P> wire record to .capture" {    const V = Value(NoEmbedded);    const allocator = std.testing.allocator;    var arena = std.heap.ArenaAllocator.init(allocator);    defer arena.deinit();    const a = arena.allocator();    const bind_fields = try a.alloc(V, 1);    bind_fields[0] = V.initI128(9);    const label = try V.initSymbol(a, symbols_mod.SYM_BIND_PAT.name);    const wire = try V.initRecord(a, label, bind_fields);    const got = preservePattern(a, wire);    try std.testing.expectEqual(value_mod.PatternFormClass.capture, got.patternClass().?);    try std.testing.expectEqual(@as(i128, 9), try got.capture.*.signed_integer.toI128());}test "observeObserver and observePattern extract fields from <Observe p o>" {    const V = Value(NoEmbedded);    const allocator = std.testing.allocator;    var arena = std.heap.ArenaAllocator.init(allocator);    defer arena.deinit();    const a = arena.allocator();    const fields = try a.alloc(V, 2);    fields[0] = V{ .discard = {} };    fields[1] = V.initBoolean(true);    const label = try V.initSymbol(a, symbols_mod.SYM_OBSERVE.name);    const rec = try V.initRecord(a, label, fields);    try std.testing.expectEqual(value_mod.PatternFormClass.discard, observePattern(rec).?.patternClass().?);    try std.testing.expect(observeObserver(rec).?.boolean);    try std.testing.expect(observePattern(V.initI128(0)) == null);    try std.testing.expect(observeObserver(V.initI128(0)) == null);}test "foreachEmbedded walks a tree with AnyEmbedded values" {    const V = Value(AnyEmbedded);    const allocator = std.testing.allocator;    var arena = std.heap.ArenaAllocator.init(allocator);    defer arena.deinit();    const a = arena.allocator();    var payload_a: u32 = 1;    var payload_b: u32 = 2;    const fields = try a.alloc(V, 2);    fields[0] = V{ .embedded = AnyEmbedded{ .value = &payload_a } };    fields[1] = V{ .embedded = AnyEmbedded{ .value = &payload_b } };    const label = try V.initSymbol(a, "Pair");    const rec = try V.initRecord(a, label, fields);    const Counter = struct {        count: *usize,        fn visit(self: *const @This(), e: AnyEmbedded) void {            _ = e;            self.count.* += 1;        }    };    var count: usize = 0;    const ctx = Counter{ .count = &count };    any_conversions.foreachEmbedded(rec, &ctx, Counter.visit);    try std.testing.expectEqual(@as(usize, 2), count);}test "mapEmbedded transforms every embedded node" {    const V = Value(AnyEmbedded);    const allocator = std.testing.allocator;    var arena = std.heap.ArenaAllocator.init(allocator);    defer arena.deinit();    const a = arena.allocator();    var payload: u32 = 7;    const fields = try a.alloc(V, 1);    fields[0] = V{ .embedded = AnyEmbedded{ .value = &payload } };    const label = try V.initSymbol(a, "Wrap");    const rec = try V.initRecord(a, label, fields);    const Mapper = struct {        fn transform(_: *const @This(), alloc: Allocator, e: AnyEmbedded) !V {            _ = alloc;            _ = e;            return V.initI128(99);        }    };    const ctx = Mapper{};    const mapped = try any_conversions.mapEmbedded(a, rec, &ctx, Mapper.transform);    try std.testing.expectEqual(value_mod.CompoundClass.record, mapped.compoundClass().?);    try std.testing.expectEqual(@as(i128, 99), try mapped.record.fields[0].signed_integer.toI128());}

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

zig
pub const patterns_mod = @import("patterns.zig");

Audit

Definitions9
Public names9
Members0
Version26.7.0
Revisiondaab053ee433