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 }