Skip to documentation
SLOP

tiny.preserves

Reference tiny.preserves

Overview · API · Code relationships · Verification · Audit

Overview

Two programs that exchange structured data have to agree on what a value is and when two values are the same, whichever encoding carried them. The package gives Zig programs one model of such data, with patterns written over its values and serialization that needs no schema. A value is an atom, a compound, or an embedded value that the host program supplies. The atoms are booleans, doubles, integers of any size, strings, byte strings and symbols. The compounds are records, sequences, sets and dictionaries. A record is a label with a list of fields. Values travel in a text syntax, in a compact binary syntax, and as JSON.

Two copies of one value have to be equal, sort to the same place and hash alike, so a value can key a hash map and sort the same way in every program. A set or a dictionary has no order of its own, so the order of its storage must leave equality, order and hash unchanged. A set holds each element once and a dictionary holds each key once, so reading or writing data that repeats one has to fail. A program has to know which memory each value owns and which call frees it.

Sets arrive in any order: a program builds them by hand, and a text document lists elements in the order its author wrote them. Comparing or hashing two sets element by element needs both in sorted order, and sorting a copy would allocate memory on every call. Embedded values belong to the host program, so the package has no way to compare, hash, free or copy them by itself. Integers in these values can exceed the range of every fixed-width Zig integer. A tree of values can mix bytes it owns with bytes it borrows, and freeing the tree then depends on knowing which is which. A Lean model of parser results proves that when a borrowed symbol and an owned symbol look the same, no single cleanup frees exactly the owned bytes of both.

The package implements the Preserves data language, which Tony Garnock-Jones and the Preserves contributors designed for the Syndicate ecosystem. A second source is Garnock-Jones's dissertation Conversational Concurrency. From these sources the package keeps the Preserves values, the patterns that match them, and the text, binary and JSON representations. The Preserves specification treats sets and dictionaries as unordered, requires distinct elements and keys, and compares two of them through their elements in ascending order. The specification's binary syntax writes set elements and dictionary keys sorted by their encoded bytes. The package follows both rules: its comparison reads sets and dictionaries in ascending order, and its binary writer sorts by encoded bytes.

The package changes two things so that it fits explicit memory management: the caller owns and frees all storage, and the binary and text decoders run within explicit limits. Every call that allocates takes an allocator from the caller, and the caller frees what the call returns. The binary decoder takes caller-set limits on nesting depth, value count, collection size and retained bytes. Both text decoders stop at 256 levels of nesting. One generic type, Value, holds every value. That type takes the type of its embedded values (domain) as a parameter, which also supplies their equality, order, cleanup and copy. assertIsDomain stops compilation when a type lacks one of those four functions. Two domains ship with the package. NoEmbedded has one value, and that value carries no data. AnyEmbedded points to a payload of the host program and carries optional functions to compare, hash, free and copy it. Custom equality, hashing and ordering for AnyEmbedded payloads come as one table of functions (SemanticOps). Two payloads are equal only when they share that table. The table's order must be total, its equality must hold exactly when the order returns .eq, and equal payloads must hash equally. An embedded value with no table compares and hashes by the address of its payload.

Value also holds four kinds of pattern (pattern form) beside the data: a discard, a capture of an inner pattern, a bind that gives an inner pattern a name, and a rest pattern that holds a sequence prefix and one pattern for the items after it. So one type carries both data and the patterns written over it. The JSON codec is the one codec that writes the four kinds of pattern and reads them back as patterns. The binary writer and text.encode refuse a pattern with error.PatternFormNotEncodable. toText prints a pattern in its record form, and the text readers read that text back as plain data. The package stores patterns and converts them to and from records, and it runs no match itself. Pattern forms order after every data kind.

Equality, order and hash read every set and dictionary in ascending order under compare, whatever the order of its storage. That walk allocates nothing, and it finds each next element by scanning the whole set, so its cost grows with the square of the set's size. A Lean model proves that sorted copies of two sets are equal exactly when their elements are permutations of each other. The same model proves that every observation of a sorted copy gives one answer for all such permutations. The set and dictionary constructors and every codec reject two equal set elements or two equal dictionary keys, on reading and on writing. Value.initSet and Value.initDictionary store the caller's slice unchecked. The binary writer emits set elements and dictionary keys sorted by their encoded bytes, and the binary reader rejects any other order.

Every decoder copies the bytes it reads, so a decoded value owns all its storage and stays valid after the input is freed. Value.deinit frees a decoded value. A value built by hand can borrow: string and symbol keep the caller's slice, and record, sequence, set and dictionary copy only the outer slice. Three calls free a value, one for each kind of ownership. Value.deinit frees a tree that owns every byte. freeValueDeep frees the compound storage of a tree and leaves its string, byte-string and symbol bytes. freeValue frees only the outer storage of one compound. cloneValueDeep copies a tree into new storage that owns every byte, and Value.deinit frees the copy. A SignedInteger holds an integer in 128 signed bits when it fits, in 128 unsigned bits when it is larger but still fits, and otherwise as its shortest big-endian two's-complement bytes.

The functions at the package root work on values whose domain is AnyEmbedded, the domain that parse and the JSON codec produce. The namespaces group the package: value, atom, integer_mod, domain, embedded_mod and ownership hold the value model. Beside them, symbols, constructors_mod, records_mod, predicates, patterns_mod and containers build and inspect values. parse_error names the text parser's errors, and text, packed and json are the codecs.

zig
const preserves = @import("preserves");const fields = [_]preserves.Value(preserves.Embedded){    preserves.string("world"),};const greeting = try preserves.record(    allocator,    preserves.symbol("greet"),    &fields,);defer preserves.freeValue(allocator, greeting);const text = try preserves.toText(allocator, greeting);defer allocator.free(text);

Definitions

Actions

Public operations.

Types and contracts

Public types and contracts.

Namespaces

Public namespaces.

Values and defaults

Public values and defaults.

Code relationships

Direct static dependencies extracted from parsed source by semantic graph analysis.

Uses: tiny.hypothesis, tiny.peer, tiny.profiling, tiny.python, tiny.sql
Used by: tiny.choir, tiny.smg

Verification

No verification records are cataloged for this module in this build.

Audit

EvidenceValue
Sourcelib/preserves/src/root.zig
Definitions134 of 134 documented
Members0 of 0 documented
Public names135 API, 554 indexed
Version26.7.0
Revisiondaab053ee433
Unresolved targets33