tiny.preserves.packed_writer
Defined in tiny.preserves.
Writes values as bytes in a binary encoding.
API (5)
Actions
Public operations.
encode: Returns the binary encoding ofvalueas new bytes allocated withallocator, for code that stores, sends or fingerprints a value.writeValue: Appends the binary encoding ofvaluetoout, growingoutwithallocator.writeVarint: Appendsvalue_intooutas a varint: seven bits per byte, low bits first, with the high bit set on every byte but the last.
Types and contracts
Public types and contracts.
EncodeError: The errorsencode,writeValueandwriteVarintreturn, so the caller can switch on them or fold them into its own error set.Tag: The tag byte that starts each encoded value, as an enum overu8, so code that writes encoded bytes by hand names each tag through it.
Source
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
Audit
| Definitions | 4 |
|---|---|
| Public names | 8 |
| Members | 0 |
| Version | 26.7.0 |
| Revision | daab053ee433 |