Skip to documentation
SLOP

tiny.preserves.packed_writer

Reference tiny.preserves packed_writer

Defined in tiny.preserves.

Writes values as bytes in a binary encoding.

API (5)

Actions

Public operations.

Types and contracts

Public types and contracts.

No direct callersNo direct callstiny.preservespacked writer
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Called byCallspacked_writerencodeprivate sourcelib.preserves.src.packed.writerwriteAtomprivate sourcelib.preserves.src.packed.writerwriteDictionaryprivate sourcelib.preserves.src.packed.writerwriteDoubleprivate sourcelib.preserves.src.packed.writerwriteSetprivate sourcelib.preserves.src.packed.writerwriteSignedIntegerpacked_writerwriteValue
Static calls · unresolved targets: 8 · external targets: 1.
Called byCallsNo direct callstest sourcelib.preserves.src.packed.writertest: writeVarint encodes 0 as a sing...test sourcelib.preserves.src.packed.writertest: writeVarint encodes 128 as two ...private sourcelib.preserves.src.packed.writerwriteAtomprivate sourcelib.preserves.src.packed.writerwriteDoublepacked_writerwriteVarint
Static calls · unresolved targets: 1 · external targets: 0.

Source: lib/preserves/src/packed/writer.zig

zig
//! Writes values as bytes in a binary encoding. A caller that hashes or compares encoded bytes//! needs equal values to produce equal bytes. A set or dictionary can store its items in any order,//! and still be equal to one stored in another order. One integer can be written with any number of//! leading sign bytes. The encoding is the binary syntax of the [Preserves](https://preserves.dev/)//! data language, which the package keeps. The writer sorts set elements and dictionary entries by//! their encoded bytes, and writes each integer in its shortest two's-complement form. It refuses//! what the packed reader could not read back as the same value: repeated set elements or//! dictionary keys, and discards, captures, binds and rest patterns. It writes an embedded value//! only when the type of the embedded values supplies `encodePacked`, and `NoEmbedded` and//! `AnyEmbedded` both lack it.const std = @import("std");const Allocator = std.mem.Allocator;const ArrayList = std.ArrayListUnmanaged(u8);const preserves = @import("../root.zig");const value_mod = preserves.value;const integer_mod = preserves.integer_mod;const constants = @import("constants.zig");pub const Tag = constants.Tag;/// The errors `encode`, `writeValue` and `writeVarint` return, so the caller can switch on them or/// fold them into its own error set. `EmbeddedNotSupported` means the value holds an embedded value/// whose type lacks `encodePacked`. `DuplicateSetElement` and `DuplicateDictionaryKey` mean a set/// holds two equal elements, or a dictionary two equal keys, by value equality or by equal/// encodings. `PatternFormNotEncodable` means the value holds a discard, capture, bind or rest/// pattern. `OutOfMemory` means an allocation failed.pub const EncodeError = Allocator.Error || error{    EmbeddedNotSupported,    DuplicateSetElement,    DuplicateDictionaryKey,    PatternFormNotEncodable,};/// Returns the binary encoding of `value` as new bytes allocated with `allocator`, for code that/// stores, sends or fingerprints a value. The caller owns the bytes and frees them with/// `allocator`. On any error the call frees everything it allocated. Encoding a value that `decode`/// returned gives back the bytes `decode` read.pub fn encode(comptime D: type, allocator: Allocator, value: value_mod.Value(D)) EncodeError![]u8 {    var buf: ArrayList = .empty;    errdefer buf.deinit(allocator);    try writeValue(D, allocator, &buf, value);    return buf.toOwnedSlice(allocator);}/// Appends the binary encoding of `value` to `out`, growing `out` with `allocator`. `encode` calls/// it with a fresh buffer, and code that packs more than one value into one buffer can call it/// directly. On error, the bytes appended before the failure stay in `out`, so the caller discards/// the buffer. Each set element and each dictionary entry is encoded into its own buffer, then/// sorted by those bytes before it is appended. A double is written as the tag 0x87, the length 8/// and its eight bytes, big-endian. Strings, byte strings, symbols and integers are written as a/// tag, a varint length and the payload. Records and sequences are written as a tag, their parts in/// order and the end marker.pub fn writeValue(    comptime D: type,    allocator: Allocator,    out: *ArrayList,    value: value_mod.Value(D),) EncodeError!void {    switch (value) {        .boolean => |b| try out.append(allocator, if (b) Tag.true_.byte() else Tag.false_.byte()),        .double => |v| try writeDouble(allocator, out, v),        .signed_integer => |si| try writeSignedInteger(allocator, out, si),        .string => |s| try writeAtom(allocator, out, .string, s),        .byte_string => |s| try writeAtom(allocator, out, .byte_string, s),        .symbol => |s| try writeAtom(allocator, out, .symbol, s),        .record => |r| {            try out.append(allocator, Tag.record.byte());            try writeValue(D, allocator, out, r.label.*);            for (r.fields) |f| try writeValue(D, allocator, out, f);            try out.append(allocator, Tag.end.byte());        },        .sequence => |s| {            try out.append(allocator, Tag.sequence.byte());            for (s) |item| try writeValue(D, allocator, out, item);            try out.append(allocator, Tag.end.byte());        },        .set => |items| try writeSet(D, allocator, out, items),        .dictionary => |entries| try writeDictionary(D, allocator, out, entries),        .embedded => |d| {            if (!@hasDecl(D, "encodePacked")) return error.EmbeddedNotSupported;            try out.append(allocator, Tag.embedded.byte());            try D.encodePacked(d, allocator, out);        },        .discard, .capture, .bind, .rest_pattern => return error.PatternFormNotEncodable,    }}/// Appends `value_in` to `out` as a varint: seven bits per byte, low bits first, with the high bit/// set on every byte but the last. The writer calls it for every length prefix. Zero is written as/// the single byte 0x00, and 128 as 0x80 0x01. The packed reader's `readVarint` reads this form/// back. The only error is running out of memory.pub fn writeVarint(allocator: Allocator, out: *ArrayList, value_in: u64) EncodeError!void {    var v = value_in;    while (true) {        var byte: u8 = @intCast(v & 0x7f);        v >>= 7;        if (v != 0) byte |= 0x80;        try out.append(allocator, byte);        if (v == 0) return;    }}fn writeAtom(allocator: Allocator, out: *ArrayList, tag: Tag, payload: []const u8) EncodeError!void {    try out.append(allocator, tag.byte());    try writeVarint(allocator, out, @intCast(payload.len));    try out.appendSlice(allocator, payload);}fn writeDouble(allocator: Allocator, out: *ArrayList, v: f64) EncodeError!void {    try out.append(allocator, Tag.ieee754.byte());    try writeVarint(allocator, out, 8);    const bits: u64 = @bitCast(v);    var bytes: [8]u8 = undefined;    std.mem.writeInt(u64, &bytes, bits, .big);    try out.appendSlice(allocator, &bytes);}fn writeSignedInteger(allocator: Allocator, out: *ArrayList, si: integer_mod.SignedInteger) EncodeError!void {    const bytes = try si.toCanonicalBytes(allocator);    defer allocator.free(bytes);    try writeAtom(allocator, out, .signed_integer, bytes);}fn writeSet(    comptime D: type,    allocator: Allocator,    out: *ArrayList,    items: []const value_mod.Value(D),) EncodeError!void {    if (!value_mod.Value(D).setElementsDistinct(items)) {        return error.DuplicateSetElement;    }    try out.append(allocator, Tag.set.byte());    const bufs = try allocator.alloc([]u8, items.len);    var filled: usize = 0;    defer {        for (bufs[0..filled]) |b| allocator.free(b);        allocator.free(bufs);    }    for (items) |it| {        bufs[filled] = try encode(D, allocator, it);        filled += 1;    }    std.mem.sortUnstable([]u8, bufs, {}, lessThanBytes);    if (bufs.len > 1) {        for (bufs[1..], bufs[0 .. bufs.len - 1]) |current, previous| {            if (std.mem.eql(u8, previous, current)) return error.DuplicateSetElement;        }    }    for (bufs) |b| try out.appendSlice(allocator, b);    try out.append(allocator, Tag.end.byte());}fn writeDictionary(    comptime D: type,    allocator: Allocator,    out: *ArrayList,    entries: []const value_mod.Value(D).DictionaryEntry,) EncodeError!void {    const Pair = struct { key: []u8, value: []u8 };    if (!value_mod.Value(D).dictionaryKeysDistinct(entries)) {        return error.DuplicateDictionaryKey;    }    try out.append(allocator, Tag.dictionary.byte());    const pairs = try allocator.alloc(Pair, entries.len);    var filled: usize = 0;    defer {        for (pairs[0..filled]) |p| {            allocator.free(p.key);            allocator.free(p.value);        }        allocator.free(pairs);    }    for (entries) |e| {        const k = try encode(D, allocator, e.key);        errdefer allocator.free(k);        const v = try encode(D, allocator, e.value);        pairs[filled] = .{ .key = k, .value = v };        filled += 1;    }    const PairLess = struct {        fn lt(_: void, a: Pair, b: Pair) bool {            return std.mem.order(u8, a.key, b.key) == .lt;        }    };    std.mem.sortUnstable(Pair, pairs, {}, PairLess.lt);    if (pairs.len > 1) {        for (pairs[1..], pairs[0 .. pairs.len - 1]) |current, previous| {            if (std.mem.eql(u8, previous.key, current.key)) return error.DuplicateDictionaryKey;        }    }    for (pairs) |p| {        try out.appendSlice(allocator, p.key);        try out.appendSlice(allocator, p.value);    }    try out.append(allocator, Tag.end.byte());}fn lessThanBytes(_: void, a: []u8, b: []u8) bool {    return std.mem.order(u8, a, b) == .lt;}const SemanticEncodingDomain = struct {    representation: u8,    pub fn eql(a: SemanticEncodingDomain, b: SemanticEncodingDomain) bool {        return a.representation % 2 == b.representation % 2;    }    pub fn hash(self: SemanticEncodingDomain) u64 {        return self.representation % 2;    }    pub fn order(a: SemanticEncodingDomain, b: SemanticEncodingDomain) std.math.Order {        return std.math.order(a.representation % 2, b.representation % 2);    }    pub fn deinit(self: *SemanticEncodingDomain, allocator: Allocator) void {        _ = self;        _ = allocator;    }    pub fn clone(        self: SemanticEncodingDomain,        allocator: Allocator,    ) Allocator.Error!SemanticEncodingDomain {        _ = allocator;        return self;    }    pub fn encodePacked(        self: SemanticEncodingDomain,        allocator: Allocator,        out: *ArrayList,    ) Allocator.Error!void {        try out.append(allocator, self.representation);    }};test "writeVarint encodes 0 as a single zero byte" {    const allocator = std.testing.allocator;    var buf: ArrayList = .empty;    defer buf.deinit(allocator);    try writeVarint(allocator, &buf, 0);    try std.testing.expectEqualSlices(u8, &[_]u8{0x00}, buf.items);}test "writeVarint encodes 128 as two bytes" {    const allocator = std.testing.allocator;    var buf: ArrayList = .empty;    defer buf.deinit(allocator);    try writeVarint(allocator, &buf, 128);    try std.testing.expectEqualSlices(u8, &[_]u8{ 0x80, 0x01 }, buf.items);}test "encode bool and end-of-record are single tag bytes" {    const allocator = std.testing.allocator;    const V = value_mod.Value(preserves.domain.NoEmbedded);    const true_bytes = try encode(preserves.domain.NoEmbedded, allocator, V.initBoolean(true));    defer allocator.free(true_bytes);    try std.testing.expectEqualSlices(u8, &[_]u8{0x81}, true_bytes);    const false_bytes = try encode(preserves.domain.NoEmbedded, allocator, V.initBoolean(false));    defer allocator.free(false_bytes);    try std.testing.expectEqualSlices(u8, &[_]u8{0x80}, false_bytes);}test "packed encode rejects semantic duplicates with distinct representations" {    const allocator = std.testing.allocator;    const V = value_mod.Value(SemanticEncodingDomain);    var set_items = [_]V{        V.initEmbedded(.{ .representation = Tag.true_.byte() }),        V.initEmbedded(.{ .representation = Tag.string.byte() }),    };    var entries = [_]V.DictionaryEntry{        .{            .key = V.initEmbedded(.{ .representation = Tag.true_.byte() }),            .value = V.initBoolean(true),        },        .{            .key = V.initEmbedded(.{ .representation = Tag.string.byte() }),            .value = V.initBoolean(false),        },    };    try std.testing.expect(set_items[0].eql(set_items[1]));    try std.testing.expectError(        error.DuplicateSetElement,        encode(SemanticEncodingDomain, allocator, V.initSet(&set_items)),    );    try std.testing.expectError(        error.DuplicateDictionaryKey,        encode(SemanticEncodingDomain, allocator, V.initDictionary(&entries)),    );}

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

zig
pub const packed_writer = @"packed".writer;

Also reachable as

@"packed".writer.

Audit

Definitions4
Public names8
Members0
Version26.7.0
Revisiondaab053ee433