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

Configure Feed

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

gleam / compiler-core / src / erlang / pattern.rs
14 kB 376 lines
1// SPDX-License-Identifier: Apache-2.0 2// SPDX-FileCopyrightText: 2021 The Gleam contributors 3 4use ecow::eco_format; 5 6use crate::analyse::Inferred; 7 8use super::*; 9 10pub(super) struct PatternPrinter<'a, 'env> { 11 pub environment: &'env mut Env<'a>, 12 pub variables: Vec<&'a str>, 13 pub guards: Vec<Document<'a>>, 14 /// In case we're dealing with string patterns, we might have something like 15 /// this: `"a" as letter <> rest`. In this case we want to compile it to 16 /// `<<"a"/utf8, rest/binary>>` and then bind a variable to `"a"`. 17 /// This way it's easier for the erlang compiler to optimise the pattern 18 /// matching. 19 /// 20 /// Here we store a list of gleam variable name to its name used in the 21 /// Erlang code and its literal value. 22 pub assignments: Vec<StringPatternAssignment<'a>>, 23} 24 25/// This is used to hold data about string patterns with an alias like: 26/// `"a" as letter <> _` 27pub struct StringPatternAssignment<'a> { 28 /// The name assigned to the pattern in the Gleam code: 29 /// 30 /// ```gleam 31 /// "a" as letter <> _ 32 /// // ^^^^^^ This one 33 /// ``` 34 /// 35 pub gleam_name: EcoString, 36 /// The name we're using for that same variable in the generated Erlang 37 /// code, could have numbers added to it to make sure it's unique, like 38 /// `Letter@1`. 39 /// 40 pub erlang_name: Document<'a>, 41 /// The document representing the literal value of that variable. For 42 /// example, if we had this pattern `"a" <> letter` it's literal value in 43 /// Erlang is going to be a document with the following string 44 /// `<<"a"/utf8>>`. 45 /// 46 pub literal_value: Document<'a>, 47} 48 49impl<'a> StringPatternAssignment<'a> { 50 pub fn to_assignment_doc(&self) -> Document<'a> { 51 docvec![self.erlang_name.clone(), " = ", self.literal_value.clone()] 52 } 53} 54 55impl<'a, 'env> PatternPrinter<'a, 'env> { 56 pub(super) fn new(environment: &'env mut Env<'a>) -> Self { 57 Self { 58 environment, 59 variables: vec![], 60 guards: vec![], 61 assignments: vec![], 62 } 63 } 64 65 pub(super) fn reset_variables(&mut self) { 66 self.variables = vec![]; 67 } 68 69 pub(super) fn print(&mut self, pattern: &'a TypedPattern) -> Document<'a> { 70 match pattern { 71 Pattern::Assign { name, pattern, .. } => { 72 self.variables.push(name); 73 self.print(pattern) 74 .append(" = ") 75 .append(self.environment.next_local_var_name(name)) 76 } 77 78 Pattern::List { elements, tail, .. } => self.pattern_list(elements, tail.as_deref()), 79 80 Pattern::Discard { .. } => "_".to_doc(), 81 82 Pattern::BitArraySize(size) => match size { 83 BitArraySize::Int { .. } 84 | BitArraySize::Variable { .. } 85 | BitArraySize::Block { .. } => self.bit_array_size(size), 86 BitArraySize::BinaryOperator { .. } => self.bit_array_size(size).surround("(", ")"), 87 }, 88 89 Pattern::Variable { name, .. } => { 90 self.variables.push(name); 91 self.environment.next_local_var_name(name) 92 } 93 94 Pattern::Int { value, .. } => int(value), 95 Pattern::Float { value, .. } => float(value), 96 Pattern::String { value, .. } => string(value), 97 98 Pattern::Constructor { 99 arguments, 100 constructor: Inferred::Known(PatternConstructor { name, .. }), 101 .. 102 } => self.tag_tuple_pattern(name, arguments), 103 104 Pattern::Constructor { 105 constructor: Inferred::Unknown, 106 .. 107 } => { 108 panic!("Erlang generation performed with uninferred pattern constructor") 109 } 110 111 Pattern::Tuple { elements, .. } => { 112 tuple(elements.iter().map(|pattern| self.print(pattern))) 113 } 114 115 Pattern::BitArray { segments, .. } => bit_array( 116 segments 117 .iter() 118 .map(|s| self.pattern_segment(&s.value, &s.options)), 119 ), 120 121 Pattern::StringPrefix { 122 left_side_string, 123 right_side_assignment, 124 left_side_assignment, 125 .. 126 } => { 127 let right = match right_side_assignment { 128 AssignName::Variable(right) => { 129 self.variables.push(right); 130 self.environment.next_local_var_name(right) 131 } 132 AssignName::Discard(_) => "_".to_doc(), 133 }; 134 135 if let Some((left_name, _)) = left_side_assignment { 136 // "wibble" as prefix <> rest 137 // ^^^^^^^^^ In case the left prefix of the pattern matching is given an alias 138 // we bind it to a local variable so that it can be correctly 139 // referenced inside the case branch. 140 // 141 // So we will end up with something that looks like this: 142 // 143 // <<"wibble"/binary, Rest/binary>> -> 144 // Prefix = "wibble", 145 // ... 146 // 147 self.variables.push(left_name); 148 149 self.assignments.push(StringPatternAssignment { 150 gleam_name: left_name.clone(), 151 erlang_name: self.environment.next_local_var_name(left_name), 152 literal_value: string(left_side_string), 153 }); 154 } 155 156 docvec![ 157 "<<\"", 158 string_inner(left_side_string), 159 "\"/utf8", 160 ", ", 161 right, 162 "/binary>>" 163 ] 164 } 165 166 Pattern::Invalid { .. } => panic!("invalid patterns should not reach code generation"), 167 } 168 } 169 170 fn bit_array_size(&mut self, size: &'a TypedBitArraySize) -> Document<'a> { 171 match size { 172 BitArraySize::Int { value, .. } => int(value), 173 BitArraySize::Block { inner, .. } => self.bit_array_size(inner).surround("(", ")"), 174 BitArraySize::Variable { 175 name, constructor, .. 176 } => { 177 let variant = &constructor 178 .as_ref() 179 .expect("Constructor not found for variable usage") 180 .variant; 181 match variant { 182 ValueConstructorVariant::ModuleConstant { literal, .. } => { 183 const_inline(literal, self.environment) 184 } 185 ValueConstructorVariant::LocalVariable { .. } 186 | ValueConstructorVariant::ModuleFn { .. } 187 | ValueConstructorVariant::Record { .. } => { 188 self.environment.local_var_name(name) 189 } 190 } 191 } 192 BitArraySize::BinaryOperator { 193 operator, 194 left, 195 right, 196 .. 197 } => { 198 let operator = match operator { 199 IntOperator::Add => " + ", 200 IntOperator::Subtract => " - ", 201 IntOperator::Multiply => " * ", 202 IntOperator::Divide => { 203 return self.bit_array_size_divide(left, right, "div"); 204 } 205 IntOperator::Remainder => { 206 return self.bit_array_size_divide(left, right, "rem"); 207 } 208 }; 209 210 docvec![ 211 self.bit_array_size(left), 212 operator, 213 self.bit_array_size(right) 214 ] 215 } 216 } 217 } 218 219 fn bit_array_size_divide( 220 &mut self, 221 left: &'a TypedBitArraySize, 222 right: &'a TypedBitArraySize, 223 operator: &'static str, 224 ) -> Document<'a> { 225 if right.non_zero_compile_time_number() { 226 return self.bit_array_size_operator(left, operator, right); 227 } 228 229 let left = self.bit_array_size(left); 230 let right = self.bit_array_size(right); 231 let denominator = self.environment.next_local_var_name("gleam@denominator"); 232 let clauses = docvec![ 233 line(), 234 "0 -> 0;", 235 line(), 236 denominator.clone(), 237 " -> ", 238 binop_documents(left, operator, denominator) 239 ]; 240 docvec!["case ", right, " of", clauses.nest(INDENT), line(), "end"] 241 } 242 243 fn bit_array_size_operator( 244 &mut self, 245 left: &'a TypedBitArraySize, 246 operator: &'static str, 247 right: &'a TypedBitArraySize, 248 ) -> Document<'a> { 249 let left = if let BitArraySize::BinaryOperator { .. } = left { 250 self.bit_array_size(left).surround("(", ")") 251 } else { 252 self.bit_array_size(left) 253 }; 254 let right = if let BitArraySize::BinaryOperator { .. } = right { 255 self.bit_array_size(right).surround("(", ")") 256 } else { 257 self.bit_array_size(right) 258 }; 259 binop_documents(left, operator, right) 260 } 261 262 fn tag_tuple_pattern( 263 &mut self, 264 name: &'a str, 265 arguments: &'a [CallArg<TypedPattern>], 266 ) -> Document<'a> { 267 if arguments.is_empty() { 268 atom_string(to_snake_case(name)) 269 } else { 270 tuple( 271 [atom_string(to_snake_case(name))] 272 .into_iter() 273 .chain(arguments.iter().map(|argument| self.print(&argument.value))), 274 ) 275 } 276 } 277 278 fn pattern_list( 279 &mut self, 280 elements: &'a [TypedPattern], 281 tail: Option<&'a TypedTailPattern>, 282 ) -> Document<'a> { 283 let elements = join( 284 elements.iter().map(|element| self.print(element)), 285 break_(",", ", "), 286 ); 287 let tail = tail.map(|tail| self.print(&tail.pattern)); 288 list(elements, tail) 289 } 290 291 fn pattern_segment( 292 &mut self, 293 value: &'a TypedPattern, 294 options: &'a [BitArrayOption<TypedPattern>], 295 ) -> Document<'a> { 296 let pattern_is_a_string_literal = matches!(value, Pattern::String { .. }); 297 let pattern_is_a_discard = matches!(value, Pattern::Discard { .. }); 298 299 let create_document = |this: &mut PatternPrinter<'a, 'env>| match value { 300 Pattern::String { value, .. } => string_inner(value).surround("\"", "\""), 301 Pattern::Discard { .. } 302 | Pattern::Variable { .. } 303 | Pattern::Int { .. } 304 | Pattern::Float { .. } => this.print(value), 305 306 Pattern::Assign { name, pattern, .. } => { 307 this.variables.push(name); 308 let variable_name = this.environment.next_local_var_name(name); 309 310 match pattern.as_ref() { 311 // In Erlang, assignment patterns inside bit arrays are not allowed. So instead of 312 // generating `<<1 = A>>`, we use guards, and generate `<<A>> when A =:= 1`. 313 Pattern::Int { value, .. } => { 314 this.guards 315 .push(docvec![variable_name.clone(), " =:= ", int(value)]); 316 variable_name 317 } 318 Pattern::Float { value, .. } => { 319 this.guards 320 .push(docvec![variable_name.clone(), " =:= ", float(value)]); 321 variable_name 322 } 323 324 // Here we do the same as for floats and ints, but we must calculate the size of 325 // the string first, so we can correctly match the bit array segment then compare 326 // it afterwards. 327 Pattern::String { value, .. } => { 328 this.guards 329 .push(docvec![variable_name.clone(), " =:= ", string(value)]); 330 docvec![variable_name, ":", string_length_utf8_bytes(value)] 331 } 332 333 // Doing a pattern such as `<<_ as a>>` is the same as just `<<a>>`, so we treat it 334 // as such. 335 Pattern::Discard { .. } => variable_name, 336 337 // Any other pattern is invalid as a bit array segment. We already handle the case 338 // of `<<a as b>>` in the type-checker, and assignment patterns cannot be nested. 339 Pattern::Variable { .. } 340 | Pattern::BitArraySize(_) 341 | Pattern::Assign { .. } 342 | Pattern::List { .. } 343 | Pattern::Constructor { .. } 344 | Pattern::Tuple { .. } 345 | Pattern::BitArray { .. } 346 | Pattern::StringPrefix { .. } 347 | Pattern::Invalid { .. } => panic!("Pattern segment match not recognised"), 348 } 349 } 350 351 Pattern::BitArraySize(_) 352 | Pattern::List { .. } 353 | Pattern::Constructor { .. } 354 | Pattern::Tuple { .. } 355 | Pattern::BitArray { .. } 356 | Pattern::StringPrefix { .. } 357 | Pattern::Invalid { .. } => panic!("Pattern segment match not recognised"), 358 }; 359 360 let size = |value: &'a TypedPattern, this: &mut PatternPrinter<'a, 'env>| { 361 Some(":".to_doc().append(this.print(value))) 362 }; 363 364 let unit = |value: &'a u8| Some(eco_format!("unit:{value}").to_doc()); 365 366 bit_array_segment( 367 create_document, 368 options, 369 size, 370 unit, 371 pattern_is_a_string_literal, 372 pattern_is_a_discard, 373 self, 374 ) 375 } 376}