lib/preserves/src/text/format.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const Allocator = std.mem.Allocator;
  3 const ArrayList = std.ArrayListUnmanaged(u8);
  4 
  5 const preserves = @import("../root.zig");
  6 const value_mod = preserves.value;
  7 const embedded_mod = preserves.embedded_mod;
  8 const patterns_mod = preserves.patterns_mod;
  9 const ownership = preserves.ownership;
 10 const text_writer_mod = @import("writer.zig");
 11 
 12 const AnyEmbedded = embedded_mod.AnyEmbedded;
 13 const Value = value_mod.Value(AnyEmbedded);
 14 
 15 /// Returns `value` as text, allocated with `alloc`. The JSON encoder calls it for a text spelling
 16 /// of each dictionary key other than a string or symbol, and code that logs or prints any value
 17 /// calls it for a readable form. The caller owns the bytes and frees them with `alloc`, and on
 18 /// error the call frees what it wrote.
 19 ///
 20 /// Atoms other than byte strings are spelled as the text writer spells them. A byte string is
 21 /// written as `#"…"`, with `\x` escapes for bytes outside printable ASCII and for `"` and `\`. The
 22 /// discard pattern is written `<_>`, a capture or bind as its wire form, and a rest pattern as
 23 /// `[prefix … . rest]`. An embedded value from `parse` is written `#:` and its value, and any other
 24 /// embedded value `#:` and its pointer in decimal. Sets and dictionaries are written in the order
 25 /// they are stored, with dictionary entries separated by a space.
 26 ///
 27 /// The call returns `error.DuplicateSetElement` or `error.DuplicateDictionaryKey` for a set with
 28 /// two equal elements or a dictionary with two equal keys, and `error.OutOfMemory`. Writing a
 29 /// capture or bind frees parts of `value` when its pattern holds a set, a record label, a
 30 /// dictionary key or a big integer. Running out of memory while it writes a capture or bind leaks
 31 /// the wire record built so far.
 32 pub fn toText(alloc: Allocator, value: Value) ![]const u8 {
 33     var buf: ArrayList = .empty;
 34     errdefer buf.deinit(alloc);
 35     try writeValue(alloc, &buf, value);
 36     return buf.toOwnedSlice(alloc);
 37 }
 38 
 39 fn writeValue(alloc: Allocator, buf: *ArrayList, value: Value) !void {
 40     switch (value) {
 41         .discard => try buf.appendSlice(alloc, "<_>"),
 42         .capture, .bind => {
 43             const wire = try patterns_mod.any_conversions.patternToPreserves(alloc, value);
 44             defer ownership.freeValueDeep(AnyEmbedded, alloc, wire);
 45             try writeValue(alloc, buf, wire);
 46         },
 47         .rest_pattern => |rp| {
 48             try buf.append(alloc, '[');
 49             for (rp.prefix, 0..) |item, i| {
 50                 if (i > 0) try buf.append(alloc, ' ');
 51                 try writeValue(alloc, buf, item);
 52             }
 53             if (rp.prefix.len > 0) try buf.append(alloc, ' ');
 54             try buf.appendSlice(alloc, ". ");
 55             try writeValue(alloc, buf, rp.rest.*);
 56             try buf.append(alloc, ']');
 57         },
 58         .boolean, .double, .signed_integer, .string, .symbol => {
 59             text_writer_mod.writeValue(AnyEmbedded, alloc, buf, value) catch |err| switch (err) {
 60                 error.EmbeddedNotSupported,
 61                 error.DuplicateSetElement,
 62                 error.DuplicateDictionaryKey,
 63                 error.PatternFormNotEncodable,
 64                 => unreachable,
 65                 error.OutOfMemory => return error.OutOfMemory,
 66             };
 67         },
 68         .byte_string => |b| try writeLegacyByteString(alloc, buf, b),
 69         .record => |r| {
 70             try buf.append(alloc, '<');
 71             try writeValue(alloc, buf, r.label.*);
 72             for (r.fields) |field| {
 73                 try buf.append(alloc, ' ');
 74                 try writeValue(alloc, buf, field);
 75             }
 76             try buf.append(alloc, '>');
 77         },
 78         .sequence => |items| {
 79             try buf.append(alloc, '[');
 80             for (items, 0..) |item, i| {
 81                 if (i > 0) try buf.append(alloc, ' ');
 82                 try writeValue(alloc, buf, item);
 83             }
 84             try buf.append(alloc, ']');
 85         },
 86         .set => |items| {
 87             if (!Value.setElementsDistinct(items)) return error.DuplicateSetElement;
 88             try buf.appendSlice(alloc, "#{");
 89             for (items, 0..) |item, i| {
 90                 if (i > 0) try buf.append(alloc, ' ');
 91                 try writeValue(alloc, buf, item);
 92             }
 93             try buf.append(alloc, '}');
 94         },
 95         .dictionary => |entries| {
 96             if (!Value.dictionaryKeysDistinct(entries)) {
 97                 return error.DuplicateDictionaryKey;
 98             }
 99             try buf.append(alloc, '{');
100             for (entries, 0..) |entry, i| {
101                 if (i > 0) try buf.append(alloc, ' ');
102                 try writeValue(alloc, buf, entry.key);
103                 try buf.appendSlice(alloc, ": ");
104                 try writeValue(alloc, buf, entry.value);
105             }
106             try buf.append(alloc, '}');
107         },
108         .embedded => |e| {
109             try buf.appendSlice(alloc, "#:");
110             if (e.semantic_ops == embedded_mod.parsedEmbeddedOps(Value)) {
111                 const inner_ptr: *const Value = @ptrCast(@alignCast(e.value));
112                 try writeValue(alloc, buf, inner_ptr.*);
113             } else {
114                 var tmp: [24]u8 = undefined;
115                 const s = std.fmt.bufPrint(&tmp, "{d}", .{@intFromPtr(e.value)}) catch unreachable;
116                 try buf.appendSlice(alloc, s);
117             }
118         },
119     }
120 }
121 
122 fn writeLegacyByteString(alloc: Allocator, buf: *ArrayList, bytes: []const u8) !void {
123     try buf.appendSlice(alloc, "#\"");
124     for (bytes) |byte| {
125         if (byte < 32 or byte > 126 or byte == 0x22 or byte == 0x5C) {
126             try buf.appendSlice(alloc, "\\x");
127             var hex: [2]u8 = undefined;
128             _ = std.fmt.bufPrint(&hex, "{x:0>2}", .{byte}) catch unreachable;
129             try buf.appendSlice(alloc, &hex);
130         } else {
131             try buf.append(alloc, byte);
132         }
133     }
134     try buf.append(alloc, '"');
135 }