Fork of daniellemaywood.uk/gleam — Wasm codegen work
7.5 kB
255 lines
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2026 The Gleam contributors
3
4#[cfg(test)]
5mod tests;
6
7use num_bigint::{BigInt, Sign};
8use num_traits::ToPrimitive;
9
10#[cfg(test)]
11#[macro_use]
12extern crate pretty_assertions;
13
14#[must_use]
15#[derive(Debug)]
16pub struct ListEnder {
17 /// This is the index in the term builder buffer to where the number of
18 /// elements in the list is written. When we start a list the number is
19 /// zeroed as we may not know the number of elements yet, and it is
20 /// written when finishing the list using this index.
21 size_index: usize,
22
23 /// This field is used by the debug mode `Drop` implementation, to ensure
24 /// that lists are correctly closed after being consumed.
25 #[cfg(debug_assertions)]
26 used: bool,
27}
28
29impl ListEnder {
30 pub fn new(size_index: usize) -> Self {
31 Self {
32 size_index,
33
34 #[cfg(debug_assertions)]
35 used: false,
36 }
37 }
38}
39
40impl ListEnder {
41 #[cfg(debug_assertions)]
42 pub fn consume(mut self) {
43 self.used = true;
44 }
45
46 #[cfg(not(debug_assertions))]
47 pub fn consume(self) {}
48}
49
50/// When testing we want to make sure that all lists are always correctly closed
51/// after being consumed. In production builds this is not implemented, so there
52/// is no runtime cost.
53#[cfg(debug_assertions)]
54impl Drop for ListEnder {
55 fn drop(&mut self) {
56 assert!(self.used, "list not closed");
57 }
58}
59
60/// The first byte of ETF data is this version number.
61///
62const ERLANG_TERM_FORMAT_VERSION_NUMBER: u8 = 131;
63
64/// A data structure used to encode values into the Erlang Term Format:
65/// https://www.erlang.org/doc/apps/erts/erl_ext_dist.html.
66///
67#[derive(Debug)]
68pub struct TermBuilder {
69 bytes: Vec<u8>,
70}
71
72impl Default for TermBuilder {
73 fn default() -> Self {
74 Self::new()
75 }
76}
77
78impl TermBuilder {
79 pub fn new() -> Self {
80 Self {
81 bytes: vec![ERLANG_TERM_FORMAT_VERSION_NUMBER],
82 }
83 }
84
85 /// Get the binary representation of the data structure built so far.
86 pub fn into_vec(self) -> Vec<u8> {
87 self.bytes
88 }
89
90 fn push(&mut self, byte: u8) {
91 self.bytes.push(byte);
92 }
93
94 fn extend(&mut self, bytes: impl IntoIterator<Item = u8>) {
95 self.bytes.extend(bytes);
96 }
97
98 /// Pushes a single raw byte.
99 ///
100 pub fn raw_byte(&mut self, byte: u8) {
101 self.push(byte);
102 }
103
104 /// Start building a list with a number of item that is not known in
105 /// advance.
106 ///
107 /// Once you've then pushed all the items, you _must_ complete the list by
108 /// calling `end_list` with the number of items that were pushed.
109 ///
110 /// ```ignore
111 /// // [1, 2, 3]
112 /// let list = etf.start_list()
113 /// etf.small_integer(1);
114 /// etf.small_integer(2);
115 /// etf.small_integer(3);
116 /// etf.end_list(list, 3)
117 /// ```
118 ///
119 /// https://www.erlang.org/doc/apps/erts/erl_ext_dist.html#list_ext
120 pub fn start_list(&mut self) -> ListEnder {
121 self.push(108);
122 let size_index = self.bytes.len();
123
124 // These 4 bytes store the number of elements (not including the tail).
125 // They are set to zero here, and the `end_list` function overwrites them
126 // with the actual length, as that number may not be known in advance.
127 self.push(0);
128 self.push(0);
129 self.push(0);
130 self.push(0);
131
132 ListEnder::new(size_index)
133 }
134
135 pub fn end_list(&mut self, list: ListEnder, number_of_element: u32) {
136 self.empty_list();
137 self.bytes[list.size_index..list.size_index + 4]
138 .copy_from_slice(&number_of_element.to_be_bytes());
139 list.consume();
140 }
141
142 /// Pushes the most compact representation of the given atom.
143 pub fn atom(&mut self, atom: &str) {
144 // TODO: we could explore using ATOM_CACHE_REF in future.
145 // Might be able to get slightly faster ETF parsing out of erlc if we
146 // use it:
147 // https://www.erlang.org/doc/apps/erts/erl_ext_dist.html#atom_cache_ref
148 if atom.len() <= 255 {
149 self.small_atom_utf8(atom);
150 } else {
151 self.atom_utf8(atom);
152 }
153 }
154
155 /// Pushes the most compact representation of the given big int.
156 pub fn bigint(&mut self, value: BigInt) {
157 if let Some(value) = value.to_u8() {
158 self.small_integer(value);
159 } else if let Some(value) = value.to_i32() {
160 self.integer(value);
161 } else {
162 self.small_big(value);
163 }
164 }
165
166 /// Pushes the most compact representation of the given usize int.
167 ///
168 pub fn usize(&mut self, value: usize) {
169 if let Some(value) = value.to_u8() {
170 self.small_integer(value);
171 } else {
172 self.integer(value as i32);
173 }
174 }
175
176 /// https://www.erlang.org/doc/apps/erts/erl_ext_dist.html#small_integer_ext
177 fn small_integer(&mut self, value: u8) {
178 self.push(97);
179 self.push(value);
180 }
181
182 /// https://www.erlang.org/doc/apps/erts/erl_ext_dist.html#integer_ext
183 fn integer(&mut self, value: i32) {
184 self.push(98);
185 self.extend(value.to_be_bytes());
186 }
187
188 /// https://www.erlang.org/doc/apps/erts/erl_ext_dist.html#new_float_ext
189 pub fn new_float(&mut self, value: f64) {
190 self.push(70);
191 self.extend(value.to_be_bytes());
192 }
193
194 /// https://www.erlang.org/doc/apps/erts/erl_ext_dist.html#small_tuple_ext
195 pub fn small_tuple(&mut self, arity: u8) {
196 self.push(104);
197 self.push(arity);
198 }
199
200 /// https://www.erlang.org/doc/apps/erts/erl_ext_dist.html#large_tuple_ext
201 pub fn large_tuple(&mut self, arity: u32) {
202 self.push(105);
203 self.extend(arity.to_be_bytes());
204 }
205
206 /// https://www.erlang.org/doc/apps/erts/erl_ext_dist.html#nil_ext
207 pub fn empty_list(&mut self) {
208 self.push(106);
209 }
210
211 /// https://www.erlang.org/doc/apps/erts/erl_ext_dist.html#binary_ext
212 pub fn binary(&mut self, bytes_count: u32, bytes: impl IntoIterator<Item = u8>) {
213 self.push(109);
214 self.extend(bytes_count.to_be_bytes());
215 self.extend(bytes);
216 }
217
218 /// https://www.erlang.org/doc/apps/erts/erl_ext_dist.html#small_big_ext
219 fn small_big(&mut self, number: BigInt) {
220 let (sign, bytes) = number.to_bytes_le();
221 self.push(110);
222 self.push(bytes.len() as u8);
223 match sign {
224 Sign::NoSign | Sign::Plus => self.push(0),
225 Sign::Minus => self.push(1),
226 }
227 self.extend(bytes);
228 }
229
230 /// https://www.erlang.org/doc/apps/erts/erl_ext_dist.html#large_big_ext
231 pub fn large_big(&mut self, number: BigInt) {
232 let (sign, bytes) = number.to_bytes_le();
233 self.push(111);
234 self.extend((bytes.len() as u32).to_be_bytes());
235 match sign {
236 Sign::NoSign | Sign::Plus => self.push(0),
237 Sign::Minus => self.push(1),
238 }
239 self.extend(bytes);
240 }
241
242 /// https://www.erlang.org/doc/apps/erts/erl_ext_dist.html#small_atom_utf8_ext
243 fn small_atom_utf8(&mut self, name: &str) {
244 self.push(119);
245 self.push(name.len() as u8);
246 self.extend(name.bytes());
247 }
248
249 /// https://www.erlang.org/doc/apps/erts/erl_ext_dist.html#atom_utf8_ext
250 fn atom_utf8(&mut self, name: &str) {
251 self.push(118);
252 self.extend((name.len() as u16).to_be_bytes());
253 self.extend(name.bytes());
254 }
255}