tiny.preserves.constructors_mod
Defined in tiny.preserves.
Each function here builds one value from Zig data in a single call.
API (13)
Actions
Public operations.
Constructors: Returns a namespace of constructors forValue(D), the Preserves value whose embedded values hold aD.Value: Returns the tagged union of all values whose embedded values have typeD.boolean: Returns the boolean valuevas aValue(NoEmbedded), the same asconstructors.boolean.discard: Returns the discard pattern as aValue(NoEmbedded), the same asconstructors.discard.float: Returns the double valuevas aValue(NoEmbedded), the same asconstructors.float.integer: Returns an integer value holdingvas aValue(NoEmbedded), the same asconstructors.integer.null_val: Returns the symbolnullas aValue(NoEmbedded), the same asconstructors.null_val.string: Returns aValue(NoEmbedded)string that borrowss, the same asconstructors.string.symbol: Returns aValue(NoEmbedded)symbol that borrowsname, the same asconstructors.symbol.
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_constructors: The constructors for values whose embedded values hold any pointer (AnyEmbedded).constructors: The constructors forValue(NoEmbedded).
Source
Source: lib/preserves/src/constructors.zig
zig
//! Each function here builds one value from Zig data in a single call. A caller needs values that//! compare, hash and encode the same way whatever order the caller listed a set's elements or a//! dictionary's entries in. A caller also needs to know which memory the new value points into, so//! it can free exactly what the call allocated.//!//! A set with a repeated element, or a dictionary with a repeated key, has no single meaning, so//! building one has to fail. Copying every string would cost an allocation per atom, and an atom//! built from a string literal can point into bytes that outlive the value.//!//! The string and symbol constructors borrow the caller's bytes, and the compound constructors copy//! only the slice of items they are given. `set` and `dictionary` reject a repeated element or key,//! then store a sorted copy, so the stored order is the package's value order (`compare`).//! `freeValue` frees the one slice or cell that a compound or pattern constructor allocated, and//! leaves the items and atoms it points to alone. `Constructors` takes the type of the embedded//! values as a parameter, and `constructors` and `any_constructors` are its two common instances.const std = @import("std");const Allocator = std.mem.Allocator;const value_mod = @import("value.zig");const domain_mod = @import("domain.zig");const embedded_mod = @import("embedded.zig");const symbols_mod = @import("symbols.zig");pub const Value = value_mod.Value;pub const NoEmbedded = domain_mod.NoEmbedded;pub const AnyEmbedded = embedded_mod.AnyEmbedded;/// Returns a namespace of constructors for `Value(D)`, the Preserves value whose embedded values/// hold a `D`. A caller whose values hold embedded values of one type calls it once at compile/// time, for every constructor over those values. The JSON decoder, the protocol record builders/// and the pattern conversions each call it to build their records. `D` has to provide `eql`,/// `order`, `deinit` and `clone`, or the call is a compile error.pub fn Constructors(comptime D: type) type { domain_mod.assertIsDomain(D); const V = Value(D); return struct { /// Returns the boolean value `v`. The call allocates nothing. pub fn boolean(v: bool) V { return V.initBoolean(v); } /// Returns an integer value holding `v`. The call widens `v` to 128 bits, so the value /// compares and encodes like any other integer. The call allocates nothing. pub fn integer(v: i64) V { return V.initI128(@as(i128, v)); } /// Returns the double value `v`. The call allocates nothing. pub fn float(v: f64) V { return V.initDouble(v); } /// Returns a string value that borrows the bytes `s`. The value points into `s`, so `s` has /// to stay alive while the value is in use. The call checks nothing about `s`, including /// whether it is valid UTF-8. pub fn string(s: []const u8) V { return .{ .string = s }; } /// Returns a symbol value that borrows the bytes `name`. The value points into `name`, so /// `name` has to stay alive while the value is in use. pub fn symbol(name: []const u8) V { return .{ .symbol = name }; } /// Returns the discard pattern, which matches any value. The call allocates nothing. pub fn discard() V { return .{ .discard = {} }; } /// Returns the symbol `null`, the value the package uses for JSON's null. The symbol's /// bytes are a constant of the package, so the value is safe to keep. `isNull` recognizes /// the value. pub fn null_val() V { return .{ .symbol = symbols_mod.SYM_NULL.name }; } /// Returns a capture pattern, which matches what `inner` matches and records the matched /// value. The call allocates one cell for `inner` with `alloc` and moves `inner` into it. /// The only error is running out of memory. pub fn capture(alloc: Allocator, inner: V) !V { const p = try alloc.create(V); p.* = inner; return .{ .capture = p }; } /// Returns a pattern that matches what `inner` matches and records the matched value under /// `name`. The call allocates one cell for `inner` with `alloc` and borrows `name`. On /// failure the call frees its cell and leaves `inner` with the caller. pub fn bindVal(alloc: Allocator, name: []const u8, inner: V) !V { const p = try alloc.create(V); errdefer alloc.destroy(p); p.* = inner; return .{ .bind = .{ .name = name, .pattern = p } }; } /// Returns a pattern that matches a sequence whose first items match `prefix` and whose /// remaining items match `rest`. The call copies the `prefix` slice and allocates one cell /// for `rest`, both with `alloc`. The copy is shallow: the items it holds still point to /// whatever the caller's items pointed to. On failure the call frees what it allocated and /// leaves `prefix` and `rest` with the caller. pub fn restPattern(alloc: Allocator, prefix: []const V, rest: V) !V { const prefix_copy = try alloc.dupe(V, prefix); errdefer alloc.free(prefix_copy); const rest_ptr = try alloc.create(V); errdefer alloc.destroy(rest_ptr); rest_ptr.* = rest; return .{ .rest_pattern = .{ .prefix = prefix_copy, .rest = rest_ptr } }; } /// Returns an embedded value that holds the pointer `ptr`. The call is a compile error /// unless `D` is `AnyEmbedded`. The value carries no equality, cleanup or copy functions, /// so two such values are equal only when they hold the same pointer. Freeing the value /// leaves the pointed-to data alone. pub fn embedded(ptr: *anyopaque) V { if (D != AnyEmbedded) { @compileError("Constructors(" ++ @typeName(D) ++ ").embedded(ptr) requires D == AnyEmbedded"); } return V{ .embedded = AnyEmbedded{ .value = ptr } }; } /// Returns a record with label `label` and fields `fields`. The call allocates one cell for /// the label and a copy of the `fields` slice, both with `alloc`. The copy is shallow: the /// new record holds the same field values the caller passed. `freeValue` frees exactly the /// cell and the slice this call allocated. On failure the call frees its cell and leaves /// `label` and `fields` with the caller. pub fn record(alloc: Allocator, label: V, fields: []const V) !V { const lp = try alloc.create(V); errdefer alloc.destroy(lp); lp.* = label; const fs = try alloc.dupe(V, fields); return .{ .record = .{ .label = lp, .fields = fs } }; } /// Returns a sequence holding a copy of the `items` slice, allocated with `alloc`. The copy /// is shallow: the new sequence holds the same item values the caller passed. `freeValue` /// frees exactly the slice this call allocated. pub fn sequence(alloc: Allocator, items: []const V) !V { const s = try alloc.dupe(V, items); return .{ .sequence = s }; } /// Returns a set holding a sorted copy of `items`, allocated with `alloc`. The copy is /// sorted by `compare`, the package's total order on values. The copy is shallow, and /// `freeValue` frees exactly the slice this call allocated. The duplicate check compares /// every pair of items, so its cost grows with the square of the count. The call returns /// `error.DuplicateSetElement` when two items are equal. That error allocates nothing. The /// duplicate check leaves `items` with the caller. pub fn set(alloc: Allocator, items: []const V) !V { if (!V.setElementsDistinct(items)) return error.DuplicateSetElement; const sorted = try alloc.dupe(V, items); const Cmp = struct { fn lt(_: void, a: V, b: V) bool { return a.compare(b) == .lt; } }; std.mem.sort(V, sorted, {}, Cmp.lt); return .{ .set = sorted }; } /// Returns a dictionary holding a copy of `entries` sorted by key, allocated with `alloc`. /// The keys are sorted by `compare`, the package's total order on values. The copy is /// shallow, and `freeValue` frees exactly the slice this call allocated. The duplicate /// check compares every pair of keys, so its cost grows with the square of the count. The /// call returns `error.DuplicateDictionaryKey` when two keys are equal. That error /// allocates nothing. The duplicate check leaves `entries` with the caller. pub fn dictionary(alloc: Allocator, entries: []const V.DictionaryEntry) !V { if (!V.dictionaryKeysDistinct(entries)) return error.DuplicateDictionaryKey; const sorted = try alloc.dupe(V.DictionaryEntry, entries); 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); return .{ .dictionary = sorted }; } };}/// The constructors for `Value(NoEmbedded)`. A caller whose values are `Value(NoEmbedded)` calls/// these constructors, for a namespace fixed to that type. The protocol record builders and the/// pattern conversions for such values are built on it.pub const constructors = Constructors(NoEmbedded);/// The constructors for values whose embedded values hold any pointer (`AnyEmbedded`). The package/// root re-exports each of its functions under the same name, so a caller that writes/// `preserves.record` calls this instance. The JSON decoder builds its records and patterns with/// it.pub const any_constructors = Constructors(AnyEmbedded);/// Returns the boolean value `v` as a `Value(NoEmbedded)`, the same as `constructors.boolean`.pub fn boolean(v: bool) Value(NoEmbedded) { return constructors.boolean(v);}/// Returns an integer value holding `v` as a `Value(NoEmbedded)`, the same as/// `constructors.integer`.pub fn integer(v: i64) Value(NoEmbedded) { return constructors.integer(v);}/// Returns the double value `v` as a `Value(NoEmbedded)`, the same as `constructors.float`.pub fn float(v: f64) Value(NoEmbedded) { return constructors.float(v);}/// Returns a `Value(NoEmbedded)` string that borrows `s`, the same as `constructors.string`. `s`/// has to stay alive while the value is in use.pub fn string(s: []const u8) Value(NoEmbedded) { return constructors.string(s);}/// Returns a `Value(NoEmbedded)` symbol that borrows `name`, the same as `constructors.symbol`./// `name` has to stay alive while the value is in use.pub fn symbol(name: []const u8) Value(NoEmbedded) { return constructors.symbol(name);}/// Returns the discard pattern as a `Value(NoEmbedded)`, the same as `constructors.discard`.pub fn discard() Value(NoEmbedded) { return constructors.discard();}/// Returns the symbol `null` as a `Value(NoEmbedded)`, the same as `constructors.null_val`.pub fn null_val() Value(NoEmbedded) { return constructors.null_val();}test "top-level constructors round-trip to classes" { const integer_mod = @import("integer.zig"); const V = Value(NoEmbedded); const b = boolean(true); try std.testing.expectEqual(value_mod.AtomClass.boolean, b.atomClass().?); const i = integer(42); try std.testing.expectEqual(value_mod.AtomClass.signed_integer, i.atomClass().?); try std.testing.expectEqual(@as(i64, 42), integer_mod.SignedInteger.toI64Lossy(i.signed_integer)); const f = float(3.14); try std.testing.expectEqual(value_mod.AtomClass.double, f.atomClass().?); const s = string("hi"); try std.testing.expectEqual(value_mod.AtomClass.string, s.atomClass().?); const sy = symbol("tag"); try std.testing.expectEqual(value_mod.AtomClass.symbol, sy.atomClass().?); const d = discard(); try std.testing.expectEqual(value_mod.PatternFormClass.discard, d.patternClass().?); const n = null_val(); try std.testing.expectEqual(value_mod.AtomClass.symbol, n.atomClass().?); try std.testing.expect(std.mem.eql(u8, n.symbol, "null")); _ = V;}test "capture/bindVal/restPattern allocate and build pattern forms" { const V = Value(NoEmbedded); const allocator = std.testing.allocator; var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const a = arena.allocator(); const cap = try constructors.capture(a, V.initI128(7)); try std.testing.expectEqual(value_mod.PatternFormClass.capture, cap.patternClass().?); try std.testing.expectEqual(@as(i128, 7), try cap.capture.*.signed_integer.toI128()); const bound = try constructors.bindVal(a, "x", V.initBoolean(true)); try std.testing.expectEqual(value_mod.PatternFormClass.bind, bound.patternClass().?); try std.testing.expect(std.mem.eql(u8, bound.bind.name, "x")); const prefix = [_]V{ V.initI128(1), V.initI128(2) }; const rp = try constructors.restPattern(a, &prefix, .{ .discard = {} }); try std.testing.expectEqual(value_mod.PatternFormClass.rest_pattern, rp.patternClass().?); try std.testing.expectEqual(@as(usize, 2), rp.rest_pattern.prefix.len); try std.testing.expectEqual(value_mod.PatternFormClass.discard, rp.rest_pattern.rest.*.patternClass().?);}test "record/sequence/set/dictionary constructors copy their inputs" { const V = Value(NoEmbedded); const allocator = std.testing.allocator; var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const a = arena.allocator(); const label = symbol("pair"); const fields = [_]V{ V.initI128(1), V.initI128(2) }; const rec = try constructors.record(a, label, &fields); try std.testing.expectEqual(value_mod.CompoundClass.record, rec.compoundClass().?); try std.testing.expectEqual(@as(usize, 2), rec.record.fields.len); try std.testing.expect(std.mem.eql(u8, rec.record.label.*.symbol, "pair")); const items = [_]V{ V.initBoolean(false), V.initBoolean(true) }; const seq = try constructors.sequence(a, &items); try std.testing.expectEqual(value_mod.CompoundClass.sequence, seq.compoundClass().?); try std.testing.expectEqual(@as(usize, 2), seq.sequence.len); const set_items = [_]V{ V.initI128(3), V.initI128(4) }; const s = try constructors.set(a, &set_items); try std.testing.expectEqual(value_mod.CompoundClass.set, s.compoundClass().?); try std.testing.expectEqual(@as(usize, 2), s.set.len);}test "set rejects duplicate elements without consuming inputs" { const V = Value(NoEmbedded); const allocator = std.testing.allocator; var first = try V.initString(allocator, "duplicate"); defer first.deinit(allocator); var second = try V.initString(allocator, "duplicate"); defer second.deinit(allocator); const items = [_]V{ first, V.initI128(3), second }; try std.testing.expectError(error.DuplicateSetElement, constructors.set(allocator, &items));}test "set sorts distinct elements" { const V = Value(NoEmbedded); const allocator = std.testing.allocator; var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const a = arena.allocator(); const items = [_]V{ V.initI128(4), V.initI128(3), V.initI128(1) }; const s = try constructors.set(a, &items); try std.testing.expectEqual(@as(usize, 3), s.set.len); try std.testing.expectEqual(@as(i128, 1), try s.set[0].signed_integer.toI128()); try std.testing.expectEqual(@as(i128, 3), try s.set[1].signed_integer.toI128()); try std.testing.expectEqual(@as(i128, 4), try s.set[2].signed_integer.toI128());}test "dictionary rejects duplicate keys without consuming inputs" { const V = Value(NoEmbedded); const allocator = std.testing.allocator; var first = try V.initString(allocator, "duplicate"); defer first.deinit(allocator); var second = try V.initString(allocator, "duplicate"); defer second.deinit(allocator); const entries = [_]V.DictionaryEntry{ .{ .key = first, .value = V.initI128(1) }, .{ .key = second, .value = V.initI128(2) }, }; try std.testing.expectError( error.DuplicateDictionaryKey, constructors.dictionary(allocator, &entries), );}test "dictionary sorts distinct keys" { const V = Value(NoEmbedded); const allocator = std.testing.allocator; var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const entries = [_]V.DictionaryEntry{ .{ .key = V.initI128(3), .value = V.initI128(30) }, .{ .key = V.initI128(1), .value = V.initI128(10) }, .{ .key = V.initI128(2), .value = V.initI128(20) }, }; const d = try constructors.dictionary(arena.allocator(), &entries); try std.testing.expectEqual(value_mod.CompoundClass.dictionary, d.compoundClass().?); try std.testing.expectEqual(@as(usize, 3), d.dictionary.len); try std.testing.expectEqual(@as(i128, 1), try d.dictionary[0].key.signed_integer.toI128()); try std.testing.expectEqual(@as(i128, 10), try d.dictionary[0].value.signed_integer.toI128()); try std.testing.expectEqual(@as(i128, 2), try d.dictionary[1].key.signed_integer.toI128()); try std.testing.expectEqual(@as(i128, 3), try d.dictionary[2].key.signed_integer.toI128());}test "any_constructors.embedded wraps a raw pointer" { var payload: u32 = 0xdeadbeef; const e = any_constructors.embedded(&payload); try std.testing.expectEqual(@as(?value_mod.AtomClass, null), e.atomClass()); try std.testing.expect(e == .embedded); try std.testing.expectEqual(@intFromPtr(&payload), @intFromPtr(e.embedded.value));}test "integer round-trips via toI64Lossy" { const integer_mod = @import("integer.zig"); const round = integer(-12345); try std.testing.expectEqual(@as(i64, -12345), integer_mod.SignedInteger.toI64Lossy(round.signed_integer));}Source: lib/preserves/src/root.zig:113
zig
pub const constructors_mod = @import("constructors.zig");Audit
| Definitions | 8 |
|---|---|
| Public names | 8 |
| Members | 0 |
| Version | 26.7.0 |
| Revision | daab053ee433 |