Skip to documentation
SLOP

tiny.preserves.text_writer

Reference tiny.preserves text_writer

Defined in tiny.preserves.

Writes values as text, whatever the type of their embedded values.

API (3)

Actions

Public operations.

Types and contracts

Public types and contracts.

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

Source

Called byCallstext.writerencodeprivate sourcelib.preserves.src.text.writerwriteByteStringprivate sourcelib.preserves.src.text.writerwriteDoubleprivate sourcelib.preserves.src.text.writerwriteQuotedStringprivate sourcelib.preserves.src.text.writerwriteSignedIntegerprivate sourcelib.preserves.src.text.writerwriteSymboltext_writerwriteValue
Static calls · unresolved targets: 2 · external targets: 2.

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

zig
pub const text_writer = text.writer;

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

zig
//! Writes values as text, whatever the type of their embedded values. A caller writing a value for//! people to read, or for a program to read back, needs a spelling each text reader accepts. A//! double that is infinite or NaN has no decimal spelling. A symbol can look like a number or hold//! characters that end a bare token.//!//! Special doubles are written as their bits, `#xd"…"`, and every other double carries a `.` or//! exponent so it reads back as a double. A symbol is written bare only when every character is a//! letter, digit or one of `-~!$%^&*?_=+/.|` and it does not look like a number, and the symbol is//! quoted with `'` otherwise. Sets and dictionaries are written in the order they are stored, so//! equal values stored in different orders give different text. The writer refuses discards,//! captures, binds and rest patterns, repeated set elements or dictionary keys, and embedded values//! whose type lacks `encodeText`, as `NoEmbedded` and `AnyEmbedded` both do.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;/// The errors `encode` and `writeValue` return. Code that calls `encode` or `writeValue` switches/// on these errors. `EmbeddedNotSupported`: the value holds an embedded value whose type lacks/// `encodeText`. `DuplicateSetElement` and `DuplicateDictionaryKey`: a set with two equal elements,/// or a dictionary with two equal keys. `PatternFormNotEncodable`: the value holds a discard,/// capture, bind or rest pattern. `OutOfMemory`: an allocation failed.pub const EncodeError = Allocator.Error || error{    EmbeddedNotSupported,    DuplicateSetElement,    DuplicateDictionaryKey,    PatternFormNotEncodable,};/// Returns the text of `value` as new bytes allocated with `allocator`. Code that saves or prints a/// value calls it for new text the caller owns. The caller owns the bytes and frees them with/// `allocator`. On any error the call frees what it wrote.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 text of `value` to `out`, growing `out` with `allocator`. `toText` calls it for each/// atom, and code that builds one text from more than one value calls it for each. On error, the/// text appended before the failure stays in `out`. Booleans are `#t` and `#f`, and integers are/// decimal at any width. Strings are quoted with `"`, with backslash escapes for quotes,/// backslashes and control characters. Byte strings are written as unpadded base64 in `#[…]`./// Record fields and sequence and set items are separated by spaces, and dictionary entries by/// `, `. A quoted symbol escapes a `'` as `\'`, which `parse` accepts. A bare symbol can contain/// `_`, so a symbol such as `1_000` is written bare and `parse` reads it as a symbol.pub fn writeValue(    comptime D: type,    allocator: Allocator,    out: *ArrayList,    value: value_mod.Value(D),) EncodeError!void {    switch (value) {        .boolean => |b| try out.appendSlice(allocator, if (b) "#t" else "#f"),        .double => |v| try writeDouble(allocator, out, v),        .signed_integer => |si| try writeSignedInteger(allocator, out, si),        .string => |s| try writeQuotedString(allocator, out, s),        .byte_string => |s| try writeByteString(allocator, out, s),        .symbol => |s| try writeSymbol(allocator, out, s),        .record => |r| {            try out.append(allocator, '<');            try writeValue(D, allocator, out, r.label.*);            for (r.fields) |f| {                try out.append(allocator, ' ');                try writeValue(D, allocator, out, f);            }            try out.append(allocator, '>');        },        .sequence => |items| {            try out.append(allocator, '[');            for (items, 0..) |item, i| {                if (i > 0) try out.append(allocator, ' ');                try writeValue(D, allocator, out, item);            }            try out.append(allocator, ']');        },        .set => |items| {            if (!value_mod.Value(D).setElementsDistinct(items)) {                return error.DuplicateSetElement;            }            try out.appendSlice(allocator, "#{");            for (items, 0..) |item, i| {                if (i > 0) try out.append(allocator, ' ');                try writeValue(D, allocator, out, item);            }            try out.append(allocator, '}');        },        .dictionary => |entries| {            if (!value_mod.Value(D).dictionaryKeysDistinct(entries)) {                return error.DuplicateDictionaryKey;            }            try out.append(allocator, '{');            for (entries, 0..) |e, i| {                if (i > 0) try out.appendSlice(allocator, ", ");                try writeValue(D, allocator, out, e.key);                try out.appendSlice(allocator, ": ");                try writeValue(D, allocator, out, e.value);            }            try out.append(allocator, '}');        },        .embedded => |d| {            if (!@hasDecl(D, "encodeText")) return error.EmbeddedNotSupported;            try out.appendSlice(allocator, "#:");            try D.encodeText(d, allocator, out);        },        .discard, .capture, .bind, .rest_pattern => return error.PatternFormNotEncodable,    }}fn writeDouble(allocator: Allocator, out: *ArrayList, v: f64) EncodeError!void {    if (std.math.isNan(v) or std.math.isInf(v)) {        const bits: u64 = @bitCast(v);        try out.appendSlice(allocator, "#xd\"");        var hex: [16]u8 = undefined;        _ = std.fmt.bufPrint(&hex, "{x:0>16}", .{bits}) catch unreachable;        try out.appendSlice(allocator, &hex);        try out.append(allocator, '"');        return;    }    var tmp: [384]u8 = undefined;    const s = std.fmt.bufPrint(&tmp, "{d}", .{v}) catch unreachable;    try out.appendSlice(allocator, s);    if (std.mem.indexOfScalar(u8, s, '.') == null and        std.mem.indexOfAny(u8, s, "eE") == null)    {        try out.appendSlice(allocator, ".0");    }}fn writeSignedInteger(    allocator: Allocator,    out: *ArrayList,    si: integer_mod.SignedInteger,) EncodeError!void {    switch (si.repr) {        .i128 => |v| {            var tmp: [48]u8 = undefined;            const s = std.fmt.bufPrint(&tmp, "{d}", .{v}) catch unreachable;            try out.appendSlice(allocator, s);        },        .u128 => |v| {            var tmp: [48]u8 = undefined;            const s = std.fmt.bufPrint(&tmp, "{d}", .{v}) catch unreachable;            try out.appendSlice(allocator, s);        },        .big => |bytes| try writeBigDecimal(allocator, out, bytes),    }}fn writeBigDecimal(allocator: Allocator, out: *ArrayList, bytes: []const u8) EncodeError!void {    if (bytes.len == 0) {        try out.append(allocator, '0');        return;    }    const is_negative = (bytes[0] & 0x80) != 0;    var mag = try allocator.alloc(u8, bytes.len);    defer allocator.free(mag);    if (is_negative) {        var carry: u16 = 1;        var i: usize = bytes.len;        while (i > 0) {            i -= 1;            const inv: u16 = @as(u16, ~bytes[i]) & 0xff;            const sum = inv + carry;            mag[i] = @intCast(sum & 0xff);            carry = sum >> 8;        }    } else {        @memcpy(mag, bytes);    }    var digits: std.ArrayListUnmanaged(u8) = .empty;    defer digits.deinit(allocator);    const remaining = try allocator.dupe(u8, mag);    defer allocator.free(remaining);    while (true) {        var all_zero = true;        var rem: u16 = 0;        for (remaining) |*b| {            const cur = (rem << 8) | @as(u16, b.*);            const q: u8 = @intCast(cur / 10);            rem = cur % 10;            b.* = q;            if (q != 0) all_zero = false;        }        try digits.append(allocator, @intCast(rem + '0'));        if (all_zero) break;    }    if (is_negative) try out.append(allocator, '-');    var j: usize = digits.items.len;    while (j > 0) {        j -= 1;        try out.append(allocator, digits.items[j]);    }}fn writeQuotedString(allocator: Allocator, out: *ArrayList, s: []const u8) EncodeError!void {    try out.append(allocator, '"');    for (s) |ch| {        switch (ch) {            '"' => try out.appendSlice(allocator, "\\\""),            '\\' => try out.appendSlice(allocator, "\\\\"),            '\n' => try out.appendSlice(allocator, "\\n"),            '\r' => try out.appendSlice(allocator, "\\r"),            '\t' => try out.appendSlice(allocator, "\\t"),            0x08 => try out.appendSlice(allocator, "\\b"),            0x0c => try out.appendSlice(allocator, "\\f"),            else => {                if (ch < 0x20) {                    try out.appendSlice(allocator, "\\u");                    var hex: [4]u8 = undefined;                    _ = std.fmt.bufPrint(&hex, "{x:0>4}", .{ch}) catch unreachable;                    try out.appendSlice(allocator, &hex);                } else {                    try out.append(allocator, ch);                }            },        }    }    try out.append(allocator, '"');}fn writeByteString(allocator: Allocator, out: *ArrayList, bytes: []const u8) EncodeError!void {    try out.append(allocator, '#');    try out.append(allocator, '[');    const enc = std.base64.standard_no_pad.Encoder;    const needed = enc.calcSize(bytes.len);    const dst = try allocator.alloc(u8, needed);    defer allocator.free(dst);    _ = enc.encode(dst, bytes);    try out.appendSlice(allocator, dst);    try out.append(allocator, ']');}fn writeSymbol(allocator: Allocator, out: *ArrayList, s: []const u8) EncodeError!void {    if (s.len > 0 and isBareSymbol(s) and !looksLikeNumber(s)) {        try out.appendSlice(allocator, s);    } else {        try out.append(allocator, '\'');        for (s) |ch| {            switch (ch) {                '\'' => try out.appendSlice(allocator, "\\'"),                '\\' => try out.appendSlice(allocator, "\\\\"),                '\n' => try out.appendSlice(allocator, "\\n"),                '\r' => try out.appendSlice(allocator, "\\r"),                '\t' => try out.appendSlice(allocator, "\\t"),                else => {                    if (ch < 0x20) {                        try out.appendSlice(allocator, "\\u");                        var hex: [4]u8 = undefined;                        _ = std.fmt.bufPrint(&hex, "{x:0>4}", .{ch}) catch unreachable;                        try out.appendSlice(allocator, &hex);                    } else {                        try out.append(allocator, ch);                    }                },            }        }        try out.append(allocator, '\'');    }}fn isBareSymbol(s: []const u8) bool {    for (s) |c| {        if (!isBareSymbolChar(c)) return false;    }    return true;}fn isBareSymbolChar(c: u8) bool {    return switch (c) {        'a'...'z', 'A'...'Z', '0'...'9' => true,        '-', '~', '!', '$', '%', '^', '&', '*', '?', '_', '=', '+', '/', '.', '|' => true,        else => false,    };}fn looksLikeNumber(s: []const u8) bool {    if (s.len == 0) return false;    var i: usize = 0;    if (s[i] == '-' or s[i] == '+') i += 1;    if (i >= s.len or !isDigit(s[i])) return false;    const int_start = i;    while (i < s.len and isDigit(s[i])) i += 1;    if (i == int_start) return false;    if (i == s.len) return true;    if (s[i] == '.') {        i += 1;        const frac_start = i;        while (i < s.len and isDigit(s[i])) i += 1;        if (i == frac_start) return false;    }    if (i < s.len and (s[i] == 'e' or s[i] == 'E')) {        i += 1;        if (i < s.len and (s[i] == '-' or s[i] == '+')) i += 1;        const exp_start = i;        while (i < s.len and isDigit(s[i])) i += 1;        if (i == exp_start) return false;    }    return i == s.len;}fn isDigit(c: u8) bool {    return c >= '0' and c <= '9';}test "text encode primitives" {    const allocator = std.testing.allocator;    const NE = preserves.domain.NoEmbedded;    const V = value_mod.Value(NE);    const t = try encode(NE, allocator, V.initBoolean(true));    defer allocator.free(t);    try std.testing.expectEqualStrings("#t", t);    const n = try encode(NE, allocator, V.initI128(-42));    defer allocator.free(n);    try std.testing.expectEqualStrings("-42", n);    const s = try encode(NE, allocator, V{ .string = "hi" });    defer allocator.free(s);    try std.testing.expectEqualStrings("\"hi\"", s);    const sym = try encode(NE, allocator, V{ .symbol = "hello" });    defer allocator.free(sym);    try std.testing.expectEqualStrings("hello", sym);}test "symbol that looks like a number is quoted" {    const allocator = std.testing.allocator;    const NE = preserves.domain.NoEmbedded;    const V = value_mod.Value(NE);    const out = try encode(NE, allocator, V{ .symbol = "123" });    defer allocator.free(out);    try std.testing.expectEqualStrings("'123'", out);}test "u128 and big integer decimal" {    const allocator = std.testing.allocator;    const NE = preserves.domain.NoEmbedded;    const V = value_mod.Value(NE);    const above: u128 = @as(u128, @intCast(std.math.maxInt(i128))) + 1;    const big = V.initU128(above);    const out = try encode(NE, allocator, big);    defer allocator.free(out);    try std.testing.expectEqualStrings("170141183460469231731687303715884105728", out);}test "text encode rejects duplicate set elements and dictionary keys" {    const allocator = std.testing.allocator;    const NE = preserves.domain.NoEmbedded;    const V = value_mod.Value(NE);    var set_items = [_]V{ V.initI128(1), V.initI128(1) };    var entries = [_]V.DictionaryEntry{        .{ .key = V.initI128(1), .value = V.initBoolean(true) },        .{ .key = V.initI128(1), .value = V.initBoolean(false) },    };    try std.testing.expectError(        error.DuplicateSetElement,        encode(NE, allocator, V.initSet(&set_items)),    );    try std.testing.expectError(        error.DuplicateDictionaryKey,        encode(NE, allocator, V.initDictionary(&entries)),    );}

Also reachable as

text.writer.

Audit

Definitions3
Public names6
Members0
Version26.7.0
Revisiondaab053ee433