lib/preserves/src/domain.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

 1 //! The rules for a type whose values a host program embeds inside data values, and one such type
 2 //! that carries nothing. A value tree compares, orders, frees and copies the things embedded in it
 3 //! along with everything else, so the type of the embedded values has to supply those four
 4 //! operations. A generic in Zig checks a type's functions only at the line that calls them, so a
 5 //! type missing one fails deep inside the value code with an error that names no rule. Many
 6 //! programs embed nothing, and they still need a type for the embedded values. The
 7 //! [Preserves](https://preserves.dev/) data language lets a host program place its own values
 8 //! inside Preserves data as embedded values, and the package keeps them.
 9 //!
10 //! The package has one generic value type (`Value`). That value type takes the type of its embedded
11 //! values (*domain*) as a compile-time parameter, so each program chooses what it embeds and the
12 //! compiler checks it. A compile-time check (`assertIsDomain`) runs first in every generic of the
13 //! package and stops compilation when the type lacks `eql`, `order`, `deinit` or `clone`, with a
14 //! message that names the missing function and the signature the package calls. The check looks for
15 //! the four names only, and the compiler checks their signatures later, where the value code calls
16 //! them. A `hash` function is optional: `Value.hash` mixes in the embedded value's hash only when
17 //! the type declares one, so without it every embedded value adds the same bytes. `NoEmbedded`
18 //! fills the parameter for programs that embed nothing: its one value carries no data, every two
19 //! are equal, and freeing and copying do nothing. `AnyEmbedded`, which the package root re-exports
20 //! from its `embedded_mod` namespace, is the other type the package ships, for payloads of the host
21 //! program.
22 const std = @import("std");
23 const Allocator = std.mem.Allocator;
24 
25 /// Stops compilation unless `D` declares public functions named `eql`, `order`, `deinit` and
26 /// `clone`. Code that writes a generic over `Value` calls it first, as `Value` and every generic of
27 /// the package do, so a type of embedded values that breaks the rules fails. The check tests that
28 /// the names exist and reads no signature. The error names the missing function and the signature
29 /// the package calls: `eql(a: Self, b: Self) bool`, `order(a: Self, b: Self) std.math.Order`,
30 /// `deinit(self: *Self, allocator: Allocator) void` and
31 /// `clone(self: Self, allocator: Allocator) !Self`.
32 pub fn assertIsDomain(comptime D: type) void {
33     if (!@hasDecl(D, "eql")) {
34         @compileError("Domain " ++ @typeName(D) ++ " is missing `pub fn eql(a: Self, b: Self) bool`");
35     }
36     if (!@hasDecl(D, "order")) {
37         @compileError("Domain " ++ @typeName(D) ++ " is missing `pub fn order(a: Self, b: Self) std.math.Order`");
38     }
39     if (!@hasDecl(D, "deinit")) {
40         @compileError("Domain " ++ @typeName(D) ++ " is missing `pub fn deinit(self: *Self, allocator: Allocator) void`");
41     }
42     if (!@hasDecl(D, "clone")) {
43         @compileError("Domain " ++ @typeName(D) ++ " is missing `pub fn clone(self: Self, allocator: Allocator) !Self`");
44     }
45 }
46 
47 /// A type of embedded values whose one value carries no data, for programs that embed nothing. Code
48 /// with nothing to embed passes it to `Value`, as the binary reader's tests do. Every two of its
49 /// values are equal.
50 pub const NoEmbedded = struct {
51     /// Returns `true` for any two values. `Value.eql` calls it for two embedded values of this
52     /// type.
53     pub fn eql(a: NoEmbedded, b: NoEmbedded) bool {
54         _ = a;
55         _ = b;
56         return true;
57     }
58 
59     /// Returns `.eq` for any two values. `Value.compare` calls it for two embedded values of this
60     /// type.
61     pub fn order(a: NoEmbedded, b: NoEmbedded) std.math.Order {
62         _ = a;
63         _ = b;
64         return .eq;
65     }
66 
67     /// Does nothing, since the value owns nothing. `Value.deinit` calls it for an embedded value of
68     /// this type.
69     pub fn deinit(self: *NoEmbedded, allocator: Allocator) void {
70         _ = self;
71         _ = allocator;
72     }
73 
74     /// Returns the value itself and allocates nothing. `cloneValueDeep` calls it for an embedded
75     /// value of this type. The call never fails, though its type carries an error union for the
76     /// rule's signature.
77     pub fn clone(self: NoEmbedded, allocator: Allocator) !NoEmbedded {
78         _ = allocator;
79         return self;
80     }
81 };
82 
83 test "NoEmbedded satisfies the Domain contract" {
84     comptime assertIsDomain(NoEmbedded);
85 }