Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

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}