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.
Conversions: Returns a namespace of pattern conversions forValue(D).Value: Returns the tagged union of all values whose embedded values have typeD.foreachEmbedded: Callscallbackfor each embedded value invalue, the same asconversions.foreachEmbedded.mapEmbedded: Returns a copy ofvaluewith each embedded value mapped, the same asconversions.mapEmbedded.observeObserver: Returns the observer of anObserverecord, the same asconversions.observeObserver.observePattern: Returns the pattern of anObserverecord, the same asconversions.observePattern.patternToPreserves: Returns the wire form ofpattern, the same asconversions.patternToPreserves.preserve: Returnsvalueunchanged, the same asconversions.preserve.preservePattern: Returns the in-memory pattern spelled bypattern, the same asconversions.preservePattern.preservesToPattern: Returns the in-memory pattern spelled byvalue, the same asconversions.preservesToPattern.
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.NoEmbedded: A type of embedded values whose one value carries no data, for programs that embed nothing.
Values and defaults
Public values and defaults.
any_conversions: The pattern conversions for values whose embedded values hold any pointer (AnyEmbedded).conversions: The pattern conversions forValue(NoEmbedded).
Source
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
| Definitions | 9 |
|---|---|
| Public names | 9 |
| Members | 0 |
| Version | 26.7.0 |
| Revision | daab053ee433 |