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
16 kB 430 lines
1// SPDX-License-Identifier: Apache-2.0 2// SPDX-FileCopyrightText: 2021 The Gleam contributors 3 4use erlang_generation::BitArraySegmentSpecifier; 5 6use crate::{analyse::Inferred, parse::LiteralFloatValue}; 7 8use super::*; 9 10/// This is used to generate the code for a pattern. 11/// Most Gleam patterns can be translated to Erlang in a pretty straightforward 12/// way but there's notable exceptions that require some extra bookeping, this 13/// helps with that. 14pub(super) struct PatternGenerator<'a, 'generator, 'module> { 15 pub generator: &'generator mut FunctionGenerator<'a, 'module>, 16 17 /// Not all Gleam patterns can be cleanly (or efficiently!) translated to 18 /// Erlang ones. In particular, we allow aliasing almost all patterns like: 19 /// 20 /// ```gleam 21 /// "a" as letter <> _ -> todo 22 /// // ^^^^^^^^^ This... 23 /// <<1 as number, _:bits>> -> todo 24 /// // ^^^^^^^^^ ...or this! 25 /// ``` 26 /// 27 /// In those cases we generate a pattern matching on the literal value and 28 /// keep track of the fact we'll have to define such variable in the 29 /// following case branch. 30 /// 31 /// This map maps the name of those Gleam variables that have been 32 /// introduced with an alias to their constant value and position. 33 /// You can check the docs of `AliasedValue` for some more examples and a 34 /// more in depth explanation. 35 pub variables_to_add_later: HashMap<EcoString, AliasedLiteral>, 36} 37 38/// This is used to hold data about string prefix pattern with an alias like: 39/// `"a" as letter <> _`. 40/// 41/// This pattern cannot be easily translated to Erlang since it doesn't allow to 42/// write something like this in a bitstring: `<<"a" = Letter, _:bits>>`. 43/// So what the generator will do is it will generate the following simpler 44/// pattern: 45/// 46/// ```erl 47/// <<"a", _:bits>> 48/// % ^^^ Notice how this isn't bound to a `Letter` variable 49/// ``` 50/// 51/// And it will return this data structure so we can then generate the needed 52/// variable assignment later in the case body. So, overall, this: 53/// 54/// ```gleam 55/// "a" as letter <> _ -> ... 56/// ``` 57/// 58/// Will become: 59/// 60/// ```erl 61/// <<"a", _:bits>> -> 62/// Letter = "a", 63/// ... 64/// ``` 65/// 66/// > Note: We could have also generated slightly different code, where we use 67/// > a guard `<<Letter, _:bits>> when Letter =:= "a"`. That would mean we don't 68/// > have to add that additional variable binding; the problem is that the 69/// > Erlang compiler doesn't seem to be able to optimise that as well as the 70/// > one with the literal value in the pattern! 71/// 72#[derive(Debug)] 73pub enum AliasedLiteral { 74 String { 75 /// The location of the name given to the alias: 76 /// 77 /// ```gleam 78 /// "a" as letter <> _ 79 /// // ^^^^^^ This span here 80 /// 81 /// <<"a" as letter>> 82 /// // ^^^^^^ or, if we're dealing with bit arrays this span here 83 /// ``` 84 /// 85 location: SrcSpan, 86 87 /// This is the content of the literal string. 88 /// 89 /// ```gleam 90 /// "książka" as word <> _ 91 /// // ^^^^^^^ This right here 92 /// ``` 93 /// 94 value: EcoString, 95 }, 96 Int { 97 /// The location of the name given to the alias: 98 /// 99 /// ```gleam 100 /// <<1 as digit>> 101 /// // ^^^^^ This span here 102 /// ``` 103 location: SrcSpan, 104 105 /// The value of the literal int being aliased. 106 value: BigInt, 107 }, 108 Float { 109 /// The location of the name given to the alias: 110 /// 111 /// ```gleam 112 /// <<1.1 as number>> 113 /// // ^^^^^^ This span here 114 /// ``` 115 location: SrcSpan, 116 117 /// The value of the literal float being aliased. 118 value: LiteralFloatValue, 119 }, 120} 121 122impl<'a, 'generator, 'module> PatternGenerator<'a, 'generator, 'module> { 123 pub(super) fn new(generator: &'generator mut FunctionGenerator<'a, 'module>) -> Self { 124 Self { 125 generator, 126 variables_to_add_later: HashMap::new(), 127 } 128 } 129 130 pub(super) fn pattern<Output>( 131 &mut self, 132 builder: &mut impl ErlangBuilder<Output>, 133 pattern: &'a TypedPattern, 134 ) { 135 match pattern { 136 Pattern::Discard { .. } => builder.discard_pattern(), 137 Pattern::Float { float_value, .. } => builder.float_pattern(float_value.value()), 138 Pattern::Int { int_value, .. } => builder.int_pattern(int_value.clone()), 139 Pattern::String { value, .. } => builder.string_pattern(value), 140 Pattern::Variable { name, location, .. } => { 141 builder.variable_pattern(&self.generator.new_erlang_variable(name, *location)); 142 } 143 144 Pattern::Assign { 145 name, 146 pattern, 147 location, 148 } => { 149 builder.match_pattern(); 150 self.pattern(builder, pattern); 151 builder.variable_pattern(&self.generator.new_erlang_variable(name, *location)); 152 } 153 154 Pattern::Tuple { elements, .. } => { 155 let tuple = builder.start_tuple_pattern(); 156 for element in elements { 157 self.pattern(builder, element); 158 } 159 builder.end_tuple_pattern(tuple); 160 } 161 162 Pattern::List { elements, tail, .. } => { 163 for element in elements { 164 builder.cons_list_pattern(); 165 self.pattern(builder, element); 166 } 167 if let Some(tail) = tail { 168 self.pattern(builder, &tail.pattern); 169 } else { 170 builder.empty_list_pattern(); 171 } 172 } 173 174 Pattern::Constructor { 175 arguments, 176 constructor, 177 .. 178 } => { 179 let Inferred::Known(PatternConstructor { name, .. }) = constructor else { 180 panic!("uninferred constructor made it to codegen ") 181 }; 182 183 if arguments.is_empty() { 184 builder.atom_pattern(&to_snake_case(name)); 185 } else { 186 let tuple = builder.start_tuple_pattern(); 187 builder.atom_pattern(&to_snake_case(name)); 188 for argument in arguments { 189 self.pattern(builder, &argument.value); 190 } 191 builder.end_tuple_pattern(tuple); 192 } 193 } 194 195 Pattern::StringPrefix { 196 left_side_string, 197 left_side_assignment, 198 right_side_assignment, 199 right_location, 200 .. 201 } => { 202 // If the constant string prefix is being aliased we need to add 203 // that value to the variables that are going to be generated 204 // later: 205 if let Some((prefix_name, prefix_location)) = left_side_assignment { 206 let _ = self.variables_to_add_later.insert( 207 prefix_name.clone(), 208 AliasedLiteral::String { 209 location: *prefix_location, 210 value: left_side_string.clone(), 211 }, 212 ); 213 } 214 215 let bit_array = builder.start_bit_array_pattern(); 216 217 // We first generate a segment matching on the literal prefix. 218 builder.bit_array_segment(); 219 builder.string_pattern(left_side_string); 220 builder.bit_array_segment_default_size(); 221 builder.bit_array_segment_specifiers([BitArraySegmentSpecifier::Utf8]); 222 223 // We then add a segment matching on the rest of the string. 224 builder.bit_array_segment(); 225 match right_side_assignment { 226 AssignName::Variable(name) => builder.variable_pattern( 227 &self.generator.new_erlang_variable(name, *right_location), 228 ), 229 AssignName::Discard(_) => builder.discard_pattern(), 230 } 231 builder.bit_array_segment_default_size(); 232 builder.bit_array_segment_specifiers([BitArraySegmentSpecifier::Binary]); 233 234 builder.end_bit_array_pattern(bit_array); 235 } 236 237 Pattern::BitArray { segments, .. } => { 238 let bit_array = builder.start_bit_array_pattern(); 239 for segment in segments { 240 builder.bit_array_segment(); 241 self.bit_array_pattern_segment_value(builder, segment); 242 self.bit_array_pattern_segment_size(builder, segment); 243 self.generator 244 .bit_array_segment_specifiers(builder, segment); 245 } 246 builder.end_bit_array_pattern(bit_array); 247 } 248 249 Pattern::BitArraySize(size) => self.bit_array_size(builder, size), 250 251 Pattern::Invalid { .. } => { 252 panic!("invalid patterns should not reach code generation") 253 } 254 } 255 } 256 257 fn bit_array_size<Output>( 258 &mut self, 259 builder: &mut impl ErlangBuilder<Output>, 260 size: &'a TypedBitArraySize, 261 ) { 262 match size { 263 BitArraySize::Int { int_value, .. } => builder.int(int_value.clone()), 264 BitArraySize::Block { inner, .. } => self.bit_array_size(builder, inner), 265 266 BitArraySize::Variable { 267 constructor, name, .. 268 } => match self.variables_to_add_later.get(name) { 269 Some(AliasedLiteral::Int { value, .. }) => builder.int(value.clone()), 270 Some(_) => panic!("segment size that is not int made it through type checking"), 271 None => { 272 let constructor = constructor.as_ref().expect("variable with no constructor"); 273 match &constructor.variant { 274 ValueConstructorVariant::ModuleConstant { literal, .. } => { 275 self.generator.inlined_constant(builder, literal); 276 } 277 ValueConstructorVariant::LocalVariable { location, .. } => { 278 builder.variable(&self.generator.local_var_name(location)); 279 } 280 ValueConstructorVariant::ModuleFn { .. } 281 | ValueConstructorVariant::Record { .. } => panic!("invalid segment"), 282 } 283 } 284 }, 285 286 BitArraySize::BinaryOperator { 287 operator, 288 left, 289 right, 290 .. 291 } => { 292 let operator = match operator { 293 IntOperator::Add => "+", 294 IntOperator::Subtract => "-", 295 IntOperator::Multiply => "*", 296 IntOperator::Divide => { 297 return self.bit_array_size_divide(builder, left, right, "div"); 298 } 299 IntOperator::Remainder => { 300 return self.bit_array_size_divide(builder, left, right, "rem"); 301 } 302 }; 303 builder.binary_operator(operator); 304 self.bit_array_size(builder, left); 305 self.bit_array_size(builder, right); 306 } 307 } 308 } 309 310 fn bit_array_pattern_segment_value<Output>( 311 &mut self, 312 builder: &mut impl ErlangBuilder<Output>, 313 segment: &'a TypedPatternBitArraySegment, 314 ) { 315 let Pattern::Assign { 316 name, 317 location, 318 pattern, 319 } = segment.value.as_ref() 320 else { 321 // If the pattern is not an assign, it needs no extra care, we can 322 // just produce the code for such pattern! 323 self.pattern(builder, &segment.value); 324 return; 325 }; 326 327 // But if we're dealing with an assign pattern inside a bit array 328 // segment we have to give it the same treatment we reserve for string 329 // prefixes (after all those are aliased bit array patterns too, since 330 // strings are just bitstrings!). 331 // 332 // After reading the docs for those you might already be familiar with 333 // the problem. But it's still worth going over that too one more time. 334 // In Gleam we can write `<<1 as a, _:bits>>` but in Erlang we can't 335 // produce the following pattern: `<<1 = A, _:bits>>`. 336 // So what we will do is match on the literal value and keep track of 337 // the constant value we'll have to add into scope later. 338 let aliased_value = match pattern.as_ref() { 339 Pattern::Int { int_value, .. } => AliasedLiteral::Int { 340 location: *location, 341 value: int_value.clone(), 342 }, 343 344 Pattern::Float { float_value, .. } => AliasedLiteral::Float { 345 location: *location, 346 value: *float_value, 347 }, 348 Pattern::String { value, .. } => AliasedLiteral::String { 349 location: *location, 350 value: value.clone(), 351 }, 352 353 // Aliasing a discard is the same as just producing a variable 354 // pattern, that makes things even simpler, we can just produce the 355 // code for a variable pattern with the wanted name and call it a day 356 Pattern::Discard { .. } => { 357 builder.variable_pattern(&self.generator.new_erlang_variable(name, *location)); 358 return; 359 } 360 361 Pattern::Variable { .. } 362 | Pattern::BitArraySize(_) 363 | Pattern::Assign { .. } 364 | Pattern::List { .. } 365 | Pattern::Constructor { .. } 366 | Pattern::Tuple { .. } 367 | Pattern::BitArray { .. } 368 | Pattern::StringPrefix { .. } 369 | Pattern::Invalid { .. } => { 370 panic!("invalid pattern inside aliased bit array pattern segment") 371 } 372 }; 373 374 let _ = self 375 .variables_to_add_later 376 .insert(name.clone(), aliased_value); 377 self.pattern(builder, pattern); 378 } 379 380 fn bit_array_pattern_segment_size<Output>( 381 &mut self, 382 builder: &mut impl ErlangBuilder<Output>, 383 segment: &'a TypedPatternBitArraySegment, 384 ) { 385 let Some(size) = segment.size() else { 386 builder.bit_array_segment_default_size(); 387 return; 388 }; 389 let TypedPattern::BitArraySize(size) = size else { 390 panic!("invalid size in pattern size segment") 391 }; 392 self.bit_array_size(builder, size); 393 } 394 395 fn bit_array_size_divide<Output>( 396 &mut self, 397 builder: &mut impl ErlangBuilder<Output>, 398 left: &'a TypedBitArraySize, 399 right: &'a TypedBitArraySize, 400 operator: &'static str, 401 ) { 402 if right.non_zero_compile_time_number() { 403 builder.binary_operator(operator); 404 self.bit_array_size(builder, left); 405 self.bit_array_size(builder, right); 406 } else { 407 let case = builder.start_case(); 408 self.bit_array_size(builder, right); 409 let case = builder.end_case_subject(case); 410 411 let clause = builder.start_case_clause(); 412 builder.int_pattern(BigInt::ZERO); 413 let clause = builder.end_clause_pattern(clause); 414 let clause = builder.end_clause_guards(clause); 415 builder.int(BigInt::ZERO); 416 builder.end_clause_body(clause); 417 418 let clause = builder.start_case_clause(); 419 let denominator = self.generator.new_generated_variable(); 420 builder.variable_pattern(&denominator); 421 let clause = builder.end_clause_pattern(clause); 422 let clause = builder.end_clause_guards(clause); 423 builder.binary_operator(operator); 424 self.bit_array_size(builder, left); 425 builder.variable(&denominator); 426 builder.end_clause_body(clause); 427 builder.end_case(case); 428 } 429 } 430}