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 / javascript / expression.rs
76 kB 2111 lines
1use num_bigint::BigInt; 2use vec1::Vec1; 3 4use super::{ 5 pattern::{Assignment, CompiledPattern}, 6 *, 7}; 8use crate::{ 9 ast::*, 10 line_numbers::LineNumbers, 11 pretty::*, 12 type_::{ 13 ModuleValueConstructor, Type, TypedCallArg, ValueConstructor, ValueConstructorVariant, 14 }, 15}; 16use std::sync::Arc; 17 18#[derive(Debug, Clone)] 19pub enum Position { 20 Tail, 21 NotTail(Ordering), 22 /// We are compiling an expression inside a block, meaning we must assign 23 /// to the `_block` variable at the end of the scope, because blocks are not 24 /// expressions in JS. 25 /// Since JS doesn't have variable shadowing, we must store the name of the 26 /// variable being used, which will include the incrementing counter. 27 /// For example, `block$2` 28 Assign(EcoString), 29} 30 31impl Position { 32 /// Returns `true` if the position is [`Tail`]. 33 /// 34 /// [`Tail`]: Position::Tail 35 #[must_use] 36 pub fn is_tail(&self) -> bool { 37 matches!(self, Self::Tail) 38 } 39 40 #[must_use] 41 pub fn ordering(&self) -> Ordering { 42 match self { 43 Self::NotTail(ordering) => *ordering, 44 Self::Tail | Self::Assign(_) => Ordering::Loose, 45 } 46 } 47} 48 49#[derive(Debug, Clone, Copy)] 50/// Determines whether we can lift blocks into statement level instead of using 51/// immediately invoked function expressions. Consider the following piece of code: 52/// 53/// ```gleam 54/// some_function(function_with_side_effect(), { 55/// let a = 10 56/// other_function_with_side_effects(a) 57/// }) 58/// ``` 59/// Here, if we lift the block that is the second argument of the function, we 60/// would end up running `other_function_with_side_effects` before 61/// `function_with_side_effects`. This would be invalid, as code in Gleam should be 62/// evaluated left-to-right, top-to-bottom. In this case, the ordering would be 63/// `Strict`, indicating that we cannot lift the block. 64/// 65/// However, in this example: 66/// 67/// ```gleam 68/// let value = !{ 69/// let value = False 70/// some_function_with_side_effect() 71/// value 72/// } 73/// ``` 74/// The only expression is the block, meaning it can be safely lifted without 75/// changing the evaluation order of the program. So the ordering is `Loose`. 76/// 77pub enum Ordering { 78 Strict, 79 Loose, 80} 81 82#[derive(Debug)] 83pub(crate) struct Generator<'module, 'ast> { 84 module_name: EcoString, 85 src_path: &'module Utf8Path, 86 project_root: &'module Utf8Path, 87 line_numbers: &'module LineNumbers, 88 function_name: Option<EcoString>, 89 function_arguments: Vec<Option<&'module EcoString>>, 90 current_scope_vars: im::HashMap<EcoString, usize>, 91 pub function_position: Position, 92 pub scope_position: Position, 93 // We register whether these features are used within an expression so that 94 // the module generator can output a suitable function if it is needed. 95 pub tracker: &'module mut UsageTracker, 96 // We track whether tail call recursion is used so that we can render a loop 97 // at the top level of the function to use in place of pushing new stack 98 // frames. 99 pub tail_recursion_used: bool, 100 /// Statements to be compiled when lifting blocks into statement scope. 101 /// For example, when compiling the following code: 102 /// ```gleam 103 /// let a = { 104 /// let b = 1 105 /// b + 1 106 /// } 107 /// ``` 108 /// There will be 2 items in `statement_level`: The first will be `let _block;` 109 /// The second will be the generated code for the block being assigned to `a`. 110 /// This lets use return `_block` as the value that the block evaluated to, 111 /// while still including the necessary code in the output at the right place. 112 /// 113 /// Once the `let` statement has compiled its value, it will add anything accumulated 114 /// in `statement_level` to the generated code, so it will result in: 115 /// 116 /// ```javascript 117 /// let _block; 118 /// {...} 119 /// let a = _block; 120 /// ``` 121 /// 122 statement_level: Vec<Document<'ast>>, 123} 124 125impl<'module, 'a> Generator<'module, 'a> { 126 #[allow(clippy::too_many_arguments)] // TODO: FIXME 127 pub fn new( 128 module_name: EcoString, 129 src_path: &'module Utf8Path, 130 project_root: &'module Utf8Path, 131 line_numbers: &'module LineNumbers, 132 function_name: EcoString, 133 function_arguments: Vec<Option<&'module EcoString>>, 134 tracker: &'module mut UsageTracker, 135 mut current_scope_vars: im::HashMap<EcoString, usize>, 136 ) -> Self { 137 let mut function_name = Some(function_name); 138 for &name in function_arguments.iter().flatten() { 139 // Initialise the function arguments 140 let _ = current_scope_vars.insert(name.clone(), 0); 141 142 // If any of the function arguments shadow the current function then 143 // recursion is no longer possible. 144 if function_name.as_ref() == Some(name) { 145 function_name = None; 146 } 147 } 148 Self { 149 tracker, 150 module_name, 151 src_path, 152 project_root, 153 line_numbers, 154 function_name, 155 function_arguments, 156 tail_recursion_used: false, 157 current_scope_vars, 158 function_position: Position::Tail, 159 scope_position: Position::Tail, 160 statement_level: Vec::new(), 161 } 162 } 163 164 pub fn local_var(&mut self, name: &EcoString) -> EcoString { 165 match self.current_scope_vars.get(name) { 166 None => { 167 let _ = self.current_scope_vars.insert(name.clone(), 0); 168 maybe_escape_identifier(name) 169 } 170 Some(0) => maybe_escape_identifier(name), 171 Some(n) if name == "$" => eco_format!("${n}"), 172 Some(n) => eco_format!("{name}${n}"), 173 } 174 } 175 176 pub fn next_local_var(&mut self, name: &EcoString) -> EcoString { 177 let next = self.current_scope_vars.get(name).map_or(0, |i| i + 1); 178 let _ = self.current_scope_vars.insert(name.clone(), next); 179 self.local_var(name) 180 } 181 182 pub fn function_body( 183 &mut self, 184 body: &'a [TypedStatement], 185 args: &'a [TypedArg], 186 ) -> Output<'a> { 187 let body = self.statements(body)?; 188 if self.tail_recursion_used { 189 self.tail_call_loop(body, args) 190 } else { 191 Ok(body) 192 } 193 } 194 195 fn tail_call_loop(&mut self, body: Document<'a>, args: &'a [TypedArg]) -> Output<'a> { 196 let loop_assignments = concat(args.iter().flat_map(Arg::get_variable_name).map(|name| { 197 let var = maybe_escape_identifier(name); 198 docvec!["let ", var, " = loop$", name, ";", line()] 199 })); 200 Ok(docvec![ 201 "while (true) {", 202 docvec![line(), loop_assignments, body].nest(INDENT), 203 line(), 204 "}" 205 ]) 206 } 207 208 fn statement(&mut self, statement: &'a TypedStatement) -> Output<'a> { 209 let expression_doc = match statement { 210 Statement::Expression(expression) => self.expression(expression), 211 Statement::Assignment(assignment) => self.assignment(assignment), 212 Statement::Use(use_) => self.expression(&use_.call), 213 Statement::Assert(_) => todo!(), 214 }?; 215 Ok(self.add_statement_level(expression_doc)) 216 } 217 218 fn add_statement_level(&mut self, expression: Document<'a>) -> Document<'a> { 219 if self.statement_level.is_empty() { 220 expression 221 } else { 222 let mut statements = std::mem::take(&mut self.statement_level); 223 statements.push(expression); 224 Itertools::intersperse(statements.into_iter(), line()) 225 .collect_vec() 226 .to_doc() 227 } 228 } 229 230 pub fn expression(&mut self, expression: &'a TypedExpr) -> Output<'a> { 231 let document = match expression { 232 TypedExpr::String { value, .. } => Ok(string(value)), 233 234 TypedExpr::Int { value, .. } => Ok(int(value)), 235 TypedExpr::Float { value, .. } => Ok(float(value)), 236 237 TypedExpr::List { elements, tail, .. } => { 238 self.not_in_tail_position(Some(Ordering::Strict), |this| match tail { 239 Some(tail) => { 240 this.tracker.prepend_used = true; 241 let tail = this.wrap_expression(tail)?; 242 prepend( 243 elements.iter().map(|element| this.wrap_expression(element)), 244 tail, 245 ) 246 } 247 None => { 248 this.tracker.list_used = true; 249 list(elements.iter().map(|element| this.wrap_expression(element))) 250 } 251 }) 252 } 253 254 TypedExpr::Tuple { elements, .. } => self.tuple(elements), 255 TypedExpr::TupleIndex { tuple, index, .. } => self.tuple_index(tuple, *index), 256 257 TypedExpr::Case { 258 subjects, clauses, .. 259 } => self.case(subjects, clauses), 260 261 TypedExpr::Call { fun, args, .. } => self.call(fun, args), 262 TypedExpr::Fn { args, body, .. } => self.fn_(args, body), 263 264 TypedExpr::RecordAccess { record, label, .. } => self.record_access(record, label), 265 TypedExpr::RecordUpdate { 266 record, 267 constructor, 268 args, 269 .. 270 } => self.record_update(record, constructor, args), 271 272 TypedExpr::Var { 273 name, constructor, .. 274 } => self.variable(name, constructor), 275 276 TypedExpr::Pipeline { 277 first_value, 278 assignments, 279 finally, 280 .. 281 } => self.pipeline(first_value, assignments.as_slice(), finally), 282 283 TypedExpr::Block { statements, .. } => self.block(statements), 284 285 TypedExpr::BinOp { 286 name, left, right, .. 287 } => self.bin_op(name, left, right), 288 289 TypedExpr::Todo { 290 message, location, .. 291 } => self.todo(message.as_ref().map(|m| &**m), location), 292 293 TypedExpr::Panic { 294 location, message, .. 295 } => self.panic(location, message.as_ref().map(|m| &**m)), 296 297 TypedExpr::BitArray { segments, .. } => self.bit_array(segments), 298 299 TypedExpr::ModuleSelect { 300 module_alias, 301 label, 302 constructor, 303 .. 304 } => Ok(self.module_select(module_alias, label, constructor)), 305 306 TypedExpr::NegateBool { value, .. } => self.negate_with("!", value), 307 308 TypedExpr::NegateInt { value, .. } => self.negate_with("- ", value), 309 310 TypedExpr::Echo { 311 expression, 312 location, 313 .. 314 } => { 315 let expression = expression 316 .as_ref() 317 .expect("echo with no expression outside of pipe"); 318 let expresion_doc = 319 self.not_in_tail_position(None, |this| this.wrap_expression(expression))?; 320 self.echo(expresion_doc, location) 321 } 322 323 TypedExpr::Invalid { .. } => { 324 panic!("invalid expressions should not reach code generation") 325 } 326 }?; 327 Ok(if expression.handles_own_return() { 328 document 329 } else { 330 self.wrap_return(document) 331 }) 332 } 333 334 fn negate_with(&mut self, with: &'static str, value: &'a TypedExpr) -> Output<'a> { 335 self.not_in_tail_position(None, |this| Ok(docvec![with, this.wrap_expression(value)?])) 336 } 337 338 fn bit_array(&mut self, segments: &'a [TypedExprBitArraySegment]) -> Output<'a> { 339 use BitArrayOption as Opt; 340 341 self.tracker.bit_array_literal_used = true; 342 343 // Collect all the values used in segments. 344 let segments_array = array(segments.iter().map(|segment| { 345 let value = self.not_in_tail_position(Some(Ordering::Strict), |this| { 346 this.wrap_expression(&segment.value) 347 })?; 348 349 if segment.has_native_option() { 350 return Err(Error::Unsupported { 351 feature: "This bit array segment option".into(), 352 location: segment.location, 353 }); 354 } 355 356 match segment.options.as_slice() { 357 // Int segment 358 _ if segment.type_.is_int() => { 359 let details = self.sized_bit_array_segment_details(segment)?; 360 match (details.size_value, segment.value.as_ref()) { 361 (Some(size_value), TypedExpr::Int { int_value, .. }) 362 if size_value <= SAFE_INT_SEGMENT_MAX_SIZE.into() 363 && (&size_value % BigInt::from(8) == BigInt::ZERO) => 364 { 365 let bytes = bit_array_segment_int_value_to_bytes( 366 int_value.clone(), 367 size_value, 368 segment.endianness(), 369 )?; 370 371 Ok(u8_slice(&bytes)) 372 } 373 374 (Some(size_value), _) if size_value == 8.into() => Ok(value), 375 376 (Some(size_value), _) if size_value <= 0.into() => Ok(nil()), 377 378 _ => { 379 self.tracker.sized_integer_segment_used = true; 380 let size = details.size; 381 let is_big = bool(segment.endianness().is_big()); 382 Ok(docvec!["sizedInt(", value, ", ", size, ", ", is_big, ")"]) 383 } 384 } 385 } 386 387 // Float segment 388 _ if segment.type_.is_float() => { 389 self.tracker.float_bit_array_segment_used = true; 390 let details = self.sized_bit_array_segment_details(segment)?; 391 let size = details.size; 392 let is_big = bool(segment.endianness().is_big()); 393 Ok(docvec!["sizedFloat(", value, ", ", size, ", ", is_big, ")"]) 394 } 395 396 // UTF8 strings 397 [Opt::Utf8 { .. }] => { 398 self.tracker.string_bit_array_segment_used = true; 399 Ok(docvec!["stringBits(", value, ")"]) 400 } 401 402 // UTF8 codepoints 403 [Opt::Utf8Codepoint { .. }] => { 404 self.tracker.codepoint_bit_array_segment_used = true; 405 Ok(docvec!["codepointBits(", value, ")"]) 406 } 407 408 // Bit arrays 409 [Opt::Bits { .. }] => Ok(value), 410 411 // Bit arrays with explicit size. The explicit size slices the bit array to the 412 // specified size. A runtime exception is thrown if the size exceeds the number 413 // of bits in the bit array. 414 [Opt::Bits { .. }, Opt::Size { value: size, .. }] 415 | [Opt::Size { value: size, .. }, Opt::Bits { .. }] => match &**size { 416 TypedExpr::Int { value: size, .. } => { 417 self.tracker.bit_array_slice_used = true; 418 Ok(docvec!["bitArraySlice(", value, ", 0, ", size, ")"]) 419 } 420 421 TypedExpr::Var { name, .. } => { 422 self.tracker.bit_array_slice_used = true; 423 Ok(docvec!["bitArraySlice(", value, ", 0, ", name, ")"]) 424 } 425 426 _ => Err(Error::Unsupported { 427 feature: "This bit array segment option".into(), 428 location: segment.location, 429 }), 430 }, 431 432 // Anything else 433 _ => Err(Error::Unsupported { 434 feature: "This bit array segment option".into(), 435 location: segment.location, 436 }), 437 } 438 }))?; 439 440 Ok(docvec!["toBitArray(", segments_array, ")"]) 441 } 442 443 fn sized_bit_array_segment_details( 444 &mut self, 445 segment: &'a TypedExprBitArraySegment, 446 ) -> Result<SizedBitArraySegmentDetails<'a>, Error> { 447 let size = segment.size(); 448 let unit = segment.unit(); 449 let (size_value, size) = match size { 450 Some(TypedExpr::Int { int_value, .. }) => { 451 let size_value = int_value * unit; 452 let size = eco_format!("{}", size_value).to_doc(); 453 (Some(size_value), size) 454 } 455 Some(size) => { 456 let mut size = self.not_in_tail_position(Some(Ordering::Strict), |this| { 457 this.wrap_expression(size) 458 })?; 459 460 if unit != 1 { 461 size = size.group().append(" * ".to_doc().append(unit.to_doc())); 462 } 463 464 (None, size) 465 } 466 467 None => { 468 let size_value: usize = if segment.type_.is_int() { 8 } else { 64 }; 469 (Some(BigInt::from(size_value)), docvec![size_value]) 470 } 471 }; 472 473 Ok(SizedBitArraySegmentDetails { size, size_value }) 474 } 475 476 pub fn wrap_return(&mut self, document: Document<'a>) -> Document<'a> { 477 match &self.scope_position { 478 Position::Tail => docvec!["return ", document, ";"], 479 Position::NotTail(_) => document, 480 Position::Assign(name) => docvec![name.clone(), " = ", document, ";"], 481 } 482 } 483 484 pub fn not_in_tail_position<CompileFn>( 485 &mut self, 486 // If ordering is None, it is inherited from the parent scope. 487 // It will be None in cases like `!x`, where `x` can be lifted 488 // only if the ordering is already loose. 489 ordering: Option<Ordering>, 490 compile: CompileFn, 491 ) -> Output<'a> 492 where 493 CompileFn: Fn(&mut Self) -> Output<'a>, 494 { 495 let new_ordering = ordering.unwrap_or(self.scope_position.ordering()); 496 497 let function_position = 498 std::mem::replace(&mut self.function_position, Position::NotTail(new_ordering)); 499 let scope_position = 500 std::mem::replace(&mut self.scope_position, Position::NotTail(new_ordering)); 501 502 let result = compile(self); 503 504 self.function_position = function_position; 505 self.scope_position = scope_position; 506 result 507 } 508 509 /// Use the `_block` variable if the expression is JS statement. 510 pub fn wrap_expression(&mut self, expression: &'a TypedExpr) -> Output<'a> { 511 match (expression, &self.scope_position) { 512 (_, Position::Tail | Position::Assign(_)) => self.expression(expression), 513 ( 514 TypedExpr::Panic { .. } 515 | TypedExpr::Todo { .. } 516 | TypedExpr::Case { .. } 517 | TypedExpr::Pipeline { .. } 518 | TypedExpr::RecordUpdate { .. }, 519 Position::NotTail(Ordering::Loose), 520 ) => self.wrap_block(|this| this.expression(expression)), 521 ( 522 TypedExpr::Panic { .. } 523 | TypedExpr::Todo { .. } 524 | TypedExpr::Case { .. } 525 | TypedExpr::Pipeline { .. } 526 | TypedExpr::RecordUpdate { .. }, 527 Position::NotTail(Ordering::Strict), 528 ) => self.immediately_invoked_function_expression(expression, |this, expr| { 529 this.expression(expr) 530 }), 531 _ => self.expression(expression), 532 } 533 } 534 535 /// Wrap an expression using the `_block` variable if required due to being 536 /// a JS statement, or in parens if required due to being an operator or 537 /// a function literal. 538 pub fn child_expression(&mut self, expression: &'a TypedExpr) -> Output<'a> { 539 match expression { 540 TypedExpr::BinOp { name, .. } if name.is_operator_to_wrap() => {} 541 TypedExpr::Fn { .. } => {} 542 543 _ => return self.wrap_expression(expression), 544 } 545 546 let document = self.expression(expression)?; 547 Ok(match &self.scope_position { 548 // Here the document is a return statement: `return <expr>;` 549 // or an assignment: `_block = <expr>;` 550 Position::Tail | Position::Assign(_) => document, 551 Position::NotTail(_) => docvec!["(", document, ")"], 552 }) 553 } 554 555 /// Wrap an expression in an immediately invoked function expression 556 fn immediately_invoked_function_expression<T, ToDoc>( 557 &mut self, 558 statements: &'a T, 559 to_doc: ToDoc, 560 ) -> Output<'a> 561 where 562 ToDoc: FnOnce(&mut Self, &'a T) -> Output<'a>, 563 { 564 // Save initial state 565 let scope_position = std::mem::replace(&mut self.scope_position, Position::Tail); 566 567 // Set state for in this iife 568 let current_scope_vars = self.current_scope_vars.clone(); 569 570 // Generate the expression 571 let result = to_doc(self, statements); 572 573 // Reset 574 self.current_scope_vars = current_scope_vars; 575 self.scope_position = scope_position; 576 577 // Wrap in iife document 578 let doc = immediately_invoked_function_expression_document(result?); 579 Ok(self.wrap_return(doc)) 580 } 581 582 fn wrap_block<CompileFn>(&mut self, compile: CompileFn) -> Output<'a> 583 where 584 CompileFn: Fn(&mut Self) -> Output<'a>, 585 { 586 let block_variable = self.next_local_var(&BLOCK_VARIABLE.into()); 587 588 // Save initial state 589 let scope_position = std::mem::replace( 590 &mut self.scope_position, 591 Position::Assign(block_variable.clone()), 592 ); 593 let function_position = std::mem::replace( 594 &mut self.function_position, 595 Position::NotTail(Ordering::Strict), 596 ); 597 598 // Generate the expression 599 let statement_doc = compile(self); 600 601 // Reset 602 self.scope_position = scope_position; 603 self.function_position = function_position; 604 605 self.statement_level 606 .push(docvec!["let ", block_variable.clone(), ";"]); 607 self.statement_level.push(statement_doc?); 608 609 Ok(self.wrap_return(block_variable.to_doc())) 610 } 611 612 fn variable(&mut self, name: &'a EcoString, constructor: &'a ValueConstructor) -> Output<'a> { 613 match &constructor.variant { 614 ValueConstructorVariant::LocalConstant { literal } => { 615 constant_expression(Context::Function, self.tracker, literal) 616 } 617 ValueConstructorVariant::Record { arity, .. } => { 618 let type_ = constructor.type_.clone(); 619 let tracker = &mut self.tracker; 620 Ok(record_constructor(type_, None, name, *arity, tracker)) 621 } 622 ValueConstructorVariant::ModuleFn { .. } 623 | ValueConstructorVariant::ModuleConstant { .. } 624 | ValueConstructorVariant::LocalVariable { .. } => Ok(self.local_var(name).to_doc()), 625 } 626 } 627 628 fn pipeline( 629 &mut self, 630 first_value: &'a TypedPipelineAssignment, 631 assignments: &'a [(TypedPipelineAssignment, PipelineAssignmentKind)], 632 finally: &'a TypedExpr, 633 ) -> Output<'a> { 634 let count = assignments.len(); 635 let mut documents = Vec::with_capacity((count + 2) * 2); 636 637 let all_assignments = std::iter::once(first_value) 638 .chain(assignments.iter().map(|(assignment, _kind)| assignment)); 639 640 let mut latest_local_var: Option<EcoString> = None; 641 for assignment in all_assignments { 642 match assignment.value.as_ref() { 643 // An echo in a pipeline won't result in an assignment, instead it 644 // just prints the previous variable assigned in the pipeline. 645 TypedExpr::Echo { 646 expression: None, 647 location, 648 .. 649 } => documents.push(self.not_in_tail_position(Some(Ordering::Strict), |this| { 650 let var = latest_local_var 651 .as_ref() 652 .expect("echo with no previous step in a pipe"); 653 this.echo(var.to_doc(), location) 654 })?), 655 656 // Otherwise we assign the intermediate pipe value to a variable. 657 _ => { 658 let assignment_document = self 659 .not_in_tail_position(Some(Ordering::Strict), |this| { 660 this.simple_variable_assignment(&assignment.name, &assignment.value) 661 })?; 662 documents.push(self.add_statement_level(assignment_document)); 663 latest_local_var = Some(self.local_var(&assignment.name)); 664 } 665 } 666 667 documents.push(line()); 668 } 669 670 match finally { 671 TypedExpr::Echo { 672 expression: None, 673 location, 674 .. 675 } => { 676 let var = latest_local_var.expect("echo with no previous step in a pipe"); 677 documents.push(self.echo(var.to_doc(), location)?); 678 } 679 _ => { 680 let finally = self.expression(finally)?; 681 documents.push(self.add_statement_level(finally)) 682 } 683 } 684 685 Ok(documents.to_doc().force_break()) 686 } 687 688 fn expression_flattening_blocks(&mut self, expression: &'a TypedExpr) -> Output<'a> { 689 match expression { 690 TypedExpr::Block { statements, .. } => self.statements(statements), 691 _ => { 692 let expression_document = self.expression(expression)?; 693 Ok(self.add_statement_level(expression_document)) 694 } 695 } 696 } 697 698 fn block(&mut self, statements: &'a Vec1<TypedStatement>) -> Output<'a> { 699 if statements.len() == 1 { 700 match statements.first() { 701 Statement::Expression(expression) => return self.child_expression(expression), 702 703 Statement::Assignment(assignment) => match &assignment.kind { 704 AssignmentKind::Let | AssignmentKind::Generated => { 705 return self.child_expression(assignment.value.as_ref()); 706 } 707 // We can't just return the right-hand side of a `let assert` 708 // assignment; we still need to check that the pattern matches. 709 AssignmentKind::Assert { .. } => {} 710 }, 711 712 Statement::Use(use_) => return self.child_expression(&use_.call), 713 714 Statement::Assert(_) => todo!(), 715 } 716 } 717 718 match &self.scope_position { 719 Position::Tail | Position::Assign(_) => self.block_document(statements), 720 Position::NotTail(Ordering::Strict) => self 721 .immediately_invoked_function_expression(statements, |this, statements| { 722 this.statements(statements) 723 }), 724 Position::NotTail(Ordering::Loose) => self.wrap_block(|this| { 725 // Save previous scope 726 let current_scope_vars = this.current_scope_vars.clone(); 727 728 let document = this.block_document(statements)?; 729 730 // Restore previous state 731 this.current_scope_vars = current_scope_vars; 732 733 Ok(document) 734 }), 735 } 736 } 737 738 fn block_document(&mut self, statements: &'a Vec1<TypedStatement>) -> Output<'a> { 739 let statements = self.statements(statements)?; 740 Ok(docvec![ 741 "{", 742 docvec![line(), statements].nest(INDENT), 743 line(), 744 "}" 745 ]) 746 } 747 748 fn statements(&mut self, statements: &'a [TypedStatement]) -> Output<'a> { 749 // If there are any statements that need to be printed at statement level, that's 750 // for an outer scope so we don't want to print them inside this one. 751 let statement_level = std::mem::take(&mut self.statement_level); 752 let count = statements.len(); 753 let mut documents = Vec::with_capacity(count * 3); 754 for (i, statement) in statements.iter().enumerate() { 755 if i + 1 < count { 756 documents.push(self.not_in_tail_position(Some(Ordering::Loose), |this| { 757 this.statement(statement) 758 })?); 759 if requires_semicolon(statement) { 760 documents.push(";".to_doc()); 761 } 762 documents.push(line()); 763 } else { 764 documents.push(self.statement(statement)?); 765 } 766 } 767 self.statement_level = statement_level; 768 if count == 1 { 769 Ok(documents.to_doc()) 770 } else { 771 Ok(documents.to_doc().force_break()) 772 } 773 } 774 775 fn simple_variable_assignment( 776 &mut self, 777 name: &'a EcoString, 778 value: &'a TypedExpr, 779 ) -> Output<'a> { 780 // Subject must be rendered before the variable for variable numbering 781 let subject = 782 self.not_in_tail_position(Some(Ordering::Loose), |this| this.wrap_expression(value))?; 783 let js_name = self.next_local_var(name); 784 let assignment = docvec!["let ", js_name.clone(), " = ", subject, ";"]; 785 let assignment = match &self.scope_position { 786 Position::NotTail(_) => assignment, 787 Position::Tail => docvec![assignment, line(), "return ", js_name, ";"], 788 Position::Assign(block_variable) => docvec![ 789 assignment, 790 line(), 791 block_variable.clone(), 792 " = ", 793 js_name, 794 ";" 795 ], 796 }; 797 798 Ok(assignment.force_break()) 799 } 800 801 fn assignment(&mut self, assignment: &'a TypedAssignment) -> Output<'a> { 802 let TypedAssignment { 803 pattern, 804 kind, 805 value, 806 annotation: _, 807 location: _, 808 } = assignment; 809 810 // If it is a simple assignment to a variable we can generate a normal 811 // JS assignment 812 if let TypedPattern::Variable { name, .. } = pattern { 813 return self.simple_variable_assignment(name, value); 814 } 815 816 // Otherwise we need to compile the patterns 817 let (subject, subject_assignment) = pattern::assign_subject(self, value); 818 // Value needs to be rendered before traversing pattern to have correctly incremented variables. 819 let value = 820 self.not_in_tail_position(Some(Ordering::Loose), |this| this.wrap_expression(value))?; 821 let mut pattern_generator = pattern::Generator::new(self); 822 pattern_generator.traverse_pattern(&subject, pattern)?; 823 let compiled = pattern_generator.take_compiled(); 824 825 // If we are in tail position we can return value being assigned 826 let afterwards = match &self.scope_position { 827 Position::NotTail(_) => nil(), 828 Position::Tail => docvec![ 829 line(), 830 "return ", 831 subject_assignment.clone().unwrap_or_else(|| value.clone()), 832 ";" 833 ], 834 Position::Assign(block_variable) => docvec![ 835 line(), 836 block_variable.clone(), 837 " = ", 838 subject_assignment.clone().unwrap_or_else(|| value.clone()), 839 ";" 840 ], 841 }; 842 843 let compiled = 844 self.pattern_into_assignment_doc(compiled, subject, pattern.location(), kind)?; 845 // If there is a subject name given create a variable to hold it for 846 // use in patterns 847 let doc = match subject_assignment { 848 Some(name) => docvec!["let ", name, " = ", value, ";", line(), compiled], 849 None => compiled, 850 }; 851 852 Ok(doc.append(afterwards).force_break()) 853 } 854 855 fn case(&mut self, subject_values: &'a [TypedExpr], clauses: &'a [TypedClause]) -> Output<'a> { 856 let (subjects, subject_assignments): (Vec<_>, Vec<_>) = 857 pattern::assign_subjects(self, subject_values) 858 .into_iter() 859 .unzip(); 860 let mut generator = pattern::Generator::new(self); 861 862 let mut doc = nil(); 863 864 // We wish to be able to know whether this is the first or clause being 865 // processed, so record the index number. We use this instead of 866 // `Iterator.enumerate` because we are using a nested for loop. 867 let mut clause_number = 0; 868 let total_patterns: usize = clauses 869 .iter() 870 .map(|c| c.alternative_patterns.len()) 871 .sum::<usize>() 872 + clauses.len(); 873 874 // A case has many clauses `pattern -> consequence` 875 for clause in clauses { 876 let multipattern = std::iter::once(&clause.pattern); 877 let multipatterns = multipattern.chain(&clause.alternative_patterns); 878 879 // A clause can have many patterns `pattern, pattern ->...` 880 for multipatterns in multipatterns { 881 let scope = generator.expression_generator.current_scope_vars.clone(); 882 let mut compiled = 883 generator.generate(&subjects, multipatterns, clause.guard.as_ref())?; 884 let consequence = generator 885 .expression_generator 886 .expression_flattening_blocks(&clause.then)?; 887 888 // We've seen one more clause 889 clause_number += 1; 890 891 // Reset the scope now that this clause has finished, causing the 892 // variables to go out of scope. 893 generator.expression_generator.current_scope_vars = scope; 894 895 // If the pattern assigns any variables we need to render assignments 896 let body = if compiled.has_assignments() { 897 let assignments = generator 898 .expression_generator 899 .pattern_take_assignments_doc(&mut compiled); 900 docvec![assignments, line(), consequence] 901 } else { 902 consequence 903 }; 904 905 let is_final_clause = clause_number == total_patterns; 906 let is_first_clause = clause_number == 1; 907 let is_only_clause = is_final_clause && is_first_clause; 908 909 doc = if is_only_clause { 910 // If this is the only clause and there are no checks then we can 911 // render just the body as the case does nothing 912 // A block is used as it could declare variables still. 913 doc.append("{") 914 .append(docvec![line(), body].nest(INDENT)) 915 .append(line()) 916 .append("}") 917 } else if is_final_clause { 918 // If this is the final clause and there are no checks then we can 919 // render `else` instead of `else if (...)` 920 doc.append(" else {") 921 .append(docvec![line(), body].nest(INDENT)) 922 .append(line()) 923 .append("}") 924 } else { 925 doc.append(if is_first_clause { 926 "if (" 927 } else { 928 " else if (" 929 }) 930 .append( 931 generator 932 .expression_generator 933 .pattern_take_checks_doc(&mut compiled, true), 934 ) 935 .append(") {") 936 .append(docvec![line(), body].nest(INDENT)) 937 .append(line()) 938 .append("}") 939 }; 940 } 941 } 942 943 // If there is a subject name given create a variable to hold it for 944 // use in patterns 945 let subject_assignments: Vec<_> = subject_assignments 946 .into_iter() 947 .zip(subject_values) 948 .flat_map(|(assignment_name, value)| assignment_name.map(|name| (name, value))) 949 .map(|(name, value)| { 950 let value = self.not_in_tail_position(Some(Ordering::Strict), |this| { 951 this.wrap_expression(value) 952 })?; 953 Ok(docvec!["let ", name, " = ", value, ";", line()]) 954 }) 955 .try_collect()?; 956 957 Ok(docvec![subject_assignments, doc].force_break()) 958 } 959 960 fn assignment_no_match( 961 &mut self, 962 location: SrcSpan, 963 subject: Document<'a>, 964 message: Option<&'a TypedExpr>, 965 ) -> Output<'a> { 966 let message = match message { 967 Some(m) => { 968 self.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(m))? 969 } 970 None => string("Pattern match failed, no pattern matched the value."), 971 }; 972 973 Ok(self.throw_error("let_assert", &message, location, [("value", subject)])) 974 } 975 976 fn tuple(&mut self, elements: &'a [TypedExpr]) -> Output<'a> { 977 self.not_in_tail_position(Some(Ordering::Strict), |this| { 978 array(elements.iter().map(|element| this.wrap_expression(element))) 979 }) 980 } 981 982 fn call(&mut self, fun: &'a TypedExpr, arguments: &'a [TypedCallArg]) -> Output<'a> { 983 let arguments = arguments 984 .iter() 985 .map(|element| { 986 self.not_in_tail_position(Some(Ordering::Strict), |this| { 987 this.wrap_expression(&element.value) 988 }) 989 }) 990 .try_collect()?; 991 992 self.call_with_doc_args(fun, arguments) 993 } 994 995 fn call_with_doc_args( 996 &mut self, 997 fun: &'a TypedExpr, 998 arguments: Vec<Document<'a>>, 999 ) -> Output<'a> { 1000 match fun { 1001 // Qualified record construction 1002 TypedExpr::ModuleSelect { 1003 constructor: ModuleValueConstructor::Record { name, .. }, 1004 module_alias, 1005 .. 1006 } => Ok(self.wrap_return(construct_record(Some(module_alias), name, arguments))), 1007 1008 // Record construction 1009 TypedExpr::Var { 1010 constructor: 1011 ValueConstructor { 1012 variant: ValueConstructorVariant::Record { .. }, 1013 type_, 1014 .. 1015 }, 1016 name, 1017 .. 1018 } => { 1019 if type_.is_result_constructor() { 1020 if name == "Ok" { 1021 self.tracker.ok_used = true; 1022 } else if name == "Error" { 1023 self.tracker.error_used = true; 1024 } 1025 } 1026 Ok(self.wrap_return(construct_record(None, name, arguments))) 1027 } 1028 1029 // Tail call optimisation. If we are calling the current function 1030 // and we are in tail position we can avoid creating a new stack 1031 // frame, enabling recursion with constant memory usage. 1032 TypedExpr::Var { name, .. } 1033 if self.function_name.as_ref() == Some(name) 1034 && self.function_position.is_tail() 1035 && self.current_scope_vars.get(name) == Some(&0) => 1036 { 1037 let mut docs = Vec::with_capacity(arguments.len() * 4); 1038 // Record that tail recursion is happening so that we know to 1039 // render the loop at the top level of the function. 1040 self.tail_recursion_used = true; 1041 1042 for (i, (element, argument)) in arguments 1043 .into_iter() 1044 .zip(&self.function_arguments) 1045 .enumerate() 1046 { 1047 if i != 0 { 1048 docs.push(line()); 1049 } 1050 // Create an assignment for each variable created by the function arguments 1051 if let Some(name) = argument { 1052 docs.push("loop$".to_doc()); 1053 docs.push(name.to_doc()); 1054 docs.push(" = ".to_doc()); 1055 } 1056 // Render the value given to the function. Even if it is not 1057 // assigned we still render it because the expression may 1058 // have some side effects. 1059 docs.push(element); 1060 docs.push(";".to_doc()); 1061 } 1062 Ok(docs.to_doc()) 1063 } 1064 1065 _ => { 1066 let fun = self.not_in_tail_position(None, |this| { 1067 let is_fn_literal = matches!(fun, TypedExpr::Fn { .. }); 1068 let fun = this.wrap_expression(fun)?; 1069 if is_fn_literal { 1070 Ok(docvec!["(", fun, ")"]) 1071 } else { 1072 Ok(fun) 1073 } 1074 })?; 1075 let arguments = call_arguments(arguments.into_iter().map(Ok))?; 1076 Ok(self.wrap_return(docvec![fun, arguments])) 1077 } 1078 } 1079 } 1080 1081 fn fn_(&mut self, arguments: &'a [TypedArg], body: &'a [TypedStatement]) -> Output<'a> { 1082 // New function, this is now the tail position 1083 let function_position = std::mem::replace(&mut self.function_position, Position::Tail); 1084 let scope_position = std::mem::replace(&mut self.scope_position, Position::Tail); 1085 1086 // And there's a new scope 1087 let scope = self.current_scope_vars.clone(); 1088 for name in arguments.iter().flat_map(Arg::get_variable_name) { 1089 let _ = self.current_scope_vars.insert(name.clone(), 0); 1090 } 1091 1092 // This is a new function so unset the recorded name so that we don't 1093 // mistakenly trigger tail call optimisation 1094 let mut name = None; 1095 std::mem::swap(&mut self.function_name, &mut name); 1096 1097 // Generate the function body 1098 let result = self.statements(body); 1099 1100 // Reset function name, scope, and tail position tracking 1101 self.function_position = function_position; 1102 self.scope_position = scope_position; 1103 self.current_scope_vars = scope; 1104 std::mem::swap(&mut self.function_name, &mut name); 1105 1106 Ok(docvec![ 1107 docvec![ 1108 fun_args(arguments, false), 1109 " => {", 1110 break_("", " "), 1111 result? 1112 ] 1113 .nest(INDENT) 1114 .append(break_("", " ")) 1115 .group(), 1116 "}", 1117 ]) 1118 } 1119 1120 fn record_access(&mut self, record: &'a TypedExpr, label: &'a str) -> Output<'a> { 1121 self.not_in_tail_position(None, |this| { 1122 let record = this.wrap_expression(record)?; 1123 Ok(docvec![record, ".", maybe_escape_property_doc(label)]) 1124 }) 1125 } 1126 1127 fn record_update( 1128 &mut self, 1129 record: &'a TypedAssignment, 1130 constructor: &'a TypedExpr, 1131 args: &'a [TypedCallArg], 1132 ) -> Output<'a> { 1133 Ok(docvec![ 1134 self.not_in_tail_position(None, |this| this.assignment(record))?, 1135 line(), 1136 self.call(constructor, args)?, 1137 ]) 1138 } 1139 1140 fn tuple_index(&mut self, tuple: &'a TypedExpr, index: u64) -> Output<'a> { 1141 self.not_in_tail_position(None, |this| { 1142 let tuple = this.wrap_expression(tuple)?; 1143 Ok(docvec![tuple, eco_format!("[{index}]")]) 1144 }) 1145 } 1146 1147 fn bin_op(&mut self, name: &'a BinOp, left: &'a TypedExpr, right: &'a TypedExpr) -> Output<'a> { 1148 match name { 1149 BinOp::And => self.print_bin_op(left, right, "&&"), 1150 BinOp::Or => self.print_bin_op(left, right, "||"), 1151 BinOp::LtInt | BinOp::LtFloat => self.print_bin_op(left, right, "<"), 1152 BinOp::LtEqInt | BinOp::LtEqFloat => self.print_bin_op(left, right, "<="), 1153 BinOp::Eq => self.equal(left, right, true), 1154 BinOp::NotEq => self.equal(left, right, false), 1155 BinOp::GtInt | BinOp::GtFloat => self.print_bin_op(left, right, ">"), 1156 BinOp::GtEqInt | BinOp::GtEqFloat => self.print_bin_op(left, right, ">="), 1157 BinOp::Concatenate | BinOp::AddInt | BinOp::AddFloat => { 1158 self.print_bin_op(left, right, "+") 1159 } 1160 BinOp::SubInt | BinOp::SubFloat => self.print_bin_op(left, right, "-"), 1161 BinOp::MultInt | BinOp::MultFloat => self.print_bin_op(left, right, "*"), 1162 BinOp::RemainderInt => self.remainder_int(left, right), 1163 BinOp::DivInt => self.div_int(left, right), 1164 BinOp::DivFloat => self.div_float(left, right), 1165 } 1166 } 1167 1168 fn div_int(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Output<'a> { 1169 let left = 1170 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left))?; 1171 let right = 1172 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right))?; 1173 self.tracker.int_division_used = true; 1174 Ok(docvec!["divideInt", wrap_args([left, right])]) 1175 } 1176 1177 fn remainder_int(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Output<'a> { 1178 let left = 1179 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left))?; 1180 let right = 1181 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right))?; 1182 self.tracker.int_remainder_used = true; 1183 Ok(docvec!["remainderInt", wrap_args([left, right])]) 1184 } 1185 1186 fn div_float(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Output<'a> { 1187 let left = 1188 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left))?; 1189 let right = 1190 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right))?; 1191 self.tracker.float_division_used = true; 1192 Ok(docvec!["divideFloat", wrap_args([left, right])]) 1193 } 1194 1195 fn equal( 1196 &mut self, 1197 left: &'a TypedExpr, 1198 right: &'a TypedExpr, 1199 should_be_equal: bool, 1200 ) -> Output<'a> { 1201 // If it is a simple scalar type then we can use JS' reference identity 1202 if is_js_scalar(left.type_()) { 1203 let left_doc = self 1204 .not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left))?; 1205 let right_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| { 1206 this.child_expression(right) 1207 })?; 1208 let operator = if should_be_equal { " === " } else { " !== " }; 1209 return Ok(docvec![left_doc, operator, right_doc]); 1210 } 1211 1212 // Other types must be compared using structural equality 1213 let left = 1214 self.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(left))?; 1215 let right = 1216 self.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(right))?; 1217 Ok(self.prelude_equal_call(should_be_equal, left, right)) 1218 } 1219 1220 pub(super) fn prelude_equal_call( 1221 &mut self, 1222 should_be_equal: bool, 1223 left: Document<'a>, 1224 right: Document<'a>, 1225 ) -> Document<'a> { 1226 // Record that we need to import the prelude's isEqual function into the module 1227 self.tracker.object_equality_used = true; 1228 // Construct the call 1229 let args = wrap_args([left, right]); 1230 let operator = if should_be_equal { 1231 "isEqual" 1232 } else { 1233 "!isEqual" 1234 }; 1235 docvec![operator, args] 1236 } 1237 1238 fn print_bin_op( 1239 &mut self, 1240 left: &'a TypedExpr, 1241 right: &'a TypedExpr, 1242 op: &'a str, 1243 ) -> Output<'a> { 1244 let left = 1245 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left))?; 1246 let right = 1247 self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right))?; 1248 Ok(docvec![left, " ", op, " ", right]) 1249 } 1250 1251 fn todo(&mut self, message: Option<&'a TypedExpr>, location: &'a SrcSpan) -> Output<'a> { 1252 let message = match message { 1253 Some(m) => self.not_in_tail_position(None, |this| this.wrap_expression(m))?, 1254 None => string("`todo` expression evaluated. This code has not yet been implemented."), 1255 }; 1256 let doc = self.throw_error("todo", &message, *location, vec![]); 1257 1258 Ok(doc) 1259 } 1260 1261 fn panic(&mut self, location: &'a SrcSpan, message: Option<&'a TypedExpr>) -> Output<'a> { 1262 let message = match message { 1263 Some(m) => self.not_in_tail_position(None, |this| this.wrap_expression(m))?, 1264 None => string("`panic` expression evaluated."), 1265 }; 1266 let doc = self.throw_error("panic", &message, *location, vec![]); 1267 1268 Ok(doc) 1269 } 1270 1271 fn throw_error<Fields>( 1272 &mut self, 1273 error_name: &'a str, 1274 message: &Document<'a>, 1275 location: SrcSpan, 1276 fields: Fields, 1277 ) -> Document<'a> 1278 where 1279 Fields: IntoIterator<Item = (&'a str, Document<'a>)>, 1280 { 1281 self.tracker.make_error_used = true; 1282 let module = self.module_name.clone().to_doc().surround('"', '"'); 1283 let function = self 1284 .function_name 1285 .clone() 1286 .unwrap_or_default() 1287 .to_doc() 1288 .surround("\"", "\""); 1289 let line = self.line_numbers.line_number(location.start).to_doc(); 1290 let fields = wrap_object(fields.into_iter().map(|(k, v)| (k.to_doc(), Some(v)))); 1291 1292 docvec![ 1293 "throw makeError", 1294 wrap_args([ 1295 string(error_name), 1296 module, 1297 line, 1298 function, 1299 message.clone(), 1300 fields 1301 ]), 1302 ] 1303 } 1304 1305 fn module_select( 1306 &mut self, 1307 module: &'a str, 1308 label: &'a EcoString, 1309 constructor: &'a ModuleValueConstructor, 1310 ) -> Document<'a> { 1311 match constructor { 1312 ModuleValueConstructor::Fn { .. } | ModuleValueConstructor::Constant { .. } => { 1313 docvec!["$", module, ".", maybe_escape_identifier(label)] 1314 } 1315 1316 ModuleValueConstructor::Record { 1317 name, arity, type_, .. 1318 } => record_constructor(type_.clone(), Some(module), name, *arity, self.tracker), 1319 } 1320 } 1321 1322 fn pattern_into_assignment_doc( 1323 &mut self, 1324 compiled_pattern: CompiledPattern<'a>, 1325 subject: Document<'a>, 1326 location: SrcSpan, 1327 kind: &'a AssignmentKind<TypedExpr>, 1328 ) -> Output<'a> { 1329 let any_assignments = !compiled_pattern.assignments.is_empty(); 1330 let assignments = Self::pattern_assignments_doc(compiled_pattern.assignments); 1331 1332 // If it's an assert then it is likely that the pattern is inexhaustive. When a value is 1333 // provided that does not get matched the code needs to throw an exception, which is done 1334 // by the pattern_checks_or_throw_doc method. 1335 match kind { 1336 AssignmentKind::Assert { message, .. } if !compiled_pattern.checks.is_empty() => { 1337 let checks = self.pattern_checks_or_throw_doc( 1338 compiled_pattern.checks, 1339 subject, 1340 location, 1341 message.as_deref(), 1342 )?; 1343 1344 if !any_assignments { 1345 Ok(checks) 1346 } else { 1347 Ok(docvec![checks, line(), assignments]) 1348 } 1349 } 1350 _ => Ok(assignments), 1351 } 1352 } 1353 1354 fn pattern_checks_or_throw_doc( 1355 &mut self, 1356 checks: Vec<pattern::Check<'a>>, 1357 subject: Document<'a>, 1358 location: SrcSpan, 1359 message: Option<&'a TypedExpr>, 1360 ) -> Output<'a> { 1361 let checks = self.pattern_checks_doc(checks, false); 1362 Ok(docvec![ 1363 "if (", 1364 docvec![break_("", ""), checks].nest(INDENT), 1365 break_("", ""), 1366 ") {", 1367 docvec![ 1368 line(), 1369 self.assignment_no_match(location, subject, message)? 1370 ] 1371 .nest(INDENT), 1372 line(), 1373 "}", 1374 ] 1375 .group()) 1376 } 1377 1378 fn pattern_assignments_doc(assignments: Vec<Assignment<'_>>) -> Document<'_> { 1379 let assignments = assignments.into_iter().map(Assignment::into_doc); 1380 join(assignments, line()) 1381 } 1382 1383 fn pattern_take_assignments_doc( 1384 &self, 1385 compiled_pattern: &mut CompiledPattern<'a>, 1386 ) -> Document<'a> { 1387 let assignments = std::mem::take(&mut compiled_pattern.assignments); 1388 Self::pattern_assignments_doc(assignments) 1389 } 1390 1391 fn pattern_take_checks_doc( 1392 &self, 1393 compiled_pattern: &mut CompiledPattern<'a>, 1394 match_desired: bool, 1395 ) -> Document<'a> { 1396 let checks = std::mem::take(&mut compiled_pattern.checks); 1397 self.pattern_checks_doc(checks, match_desired) 1398 } 1399 1400 fn pattern_checks_doc( 1401 &self, 1402 checks: Vec<pattern::Check<'a>>, 1403 match_desired: bool, 1404 ) -> Document<'a> { 1405 if checks.is_empty() { 1406 return "true".to_doc(); 1407 }; 1408 let operator = if match_desired { 1409 break_(" &&", " && ") 1410 } else { 1411 break_(" ||", " || ") 1412 }; 1413 1414 let checks_len = checks.len(); 1415 join( 1416 checks.into_iter().map(|check| { 1417 if checks_len > 1 && check.may_require_wrapping() { 1418 docvec!["(", check.into_doc(match_desired), ")"] 1419 } else { 1420 check.into_doc(match_desired) 1421 } 1422 }), 1423 operator, 1424 ) 1425 .group() 1426 } 1427 1428 fn echo(&mut self, expression: Document<'a>, location: &'a SrcSpan) -> Output<'a> { 1429 self.tracker.echo_used = true; 1430 1431 let relative_path = self 1432 .src_path 1433 .strip_prefix(self.project_root) 1434 .unwrap_or(self.src_path) 1435 .as_str() 1436 .replace("\\", "\\\\"); 1437 1438 let relative_path_doc = EcoString::from(relative_path).to_doc(); 1439 1440 let echo_argument = call_arguments(vec![ 1441 Ok(expression), 1442 Ok(relative_path_doc.surround("\"", "\"")), 1443 Ok(self.line_numbers.line_number(location.start).to_doc()), 1444 ])?; 1445 Ok(self.wrap_return(docvec!["echo", echo_argument])) 1446 } 1447} 1448 1449pub fn int(value: &str) -> Document<'_> { 1450 let mut out = EcoString::with_capacity(value.len()); 1451 1452 if value.starts_with('-') { 1453 out.push('-'); 1454 } else if value.starts_with('+') { 1455 out.push('+'); 1456 }; 1457 let value = value.trim_start_matches(['+', '-'].as_ref()); 1458 1459 let value = if value.starts_with("0x") { 1460 out.push_str("0x"); 1461 value.trim_start_matches("0x") 1462 } else if value.starts_with("0o") { 1463 out.push_str("0o"); 1464 value.trim_start_matches("0o") 1465 } else if value.starts_with("0b") { 1466 out.push_str("0b"); 1467 value.trim_start_matches("0b") 1468 } else { 1469 value 1470 }; 1471 1472 let value = value.trim_start_matches('0'); 1473 if value.is_empty() { 1474 out.push('0'); 1475 } 1476 1477 // If the number starts with a `0` then an underscore, the `0` will be stripped, 1478 // leaving the number to look something like `_1_2_3`, which is not valid syntax. 1479 // Therefore, we strip the `_` to avoid this case. 1480 let value = value.trim_start_matches('_'); 1481 1482 out.push_str(value); 1483 1484 out.to_doc() 1485} 1486 1487pub fn float(value: &str) -> Document<'_> { 1488 let mut out = EcoString::with_capacity(value.len()); 1489 1490 if value.starts_with('-') { 1491 out.push('-'); 1492 } else if value.starts_with('+') { 1493 out.push('+'); 1494 }; 1495 let value = value.trim_start_matches(['+', '-'].as_ref()); 1496 1497 let value = value.trim_start_matches('0'); 1498 if value.starts_with(['.', 'e', 'E']) { 1499 out.push('0'); 1500 } 1501 out.push_str(value); 1502 1503 out.to_doc() 1504} 1505 1506pub(crate) fn guard_constant_expression<'a>( 1507 assignments: &mut Vec<Assignment<'a>>, 1508 tracker: &mut UsageTracker, 1509 expression: &'a TypedConstant, 1510) -> Output<'a> { 1511 match expression { 1512 Constant::Tuple { elements, .. } => array( 1513 elements 1514 .iter() 1515 .map(|element| guard_constant_expression(assignments, tracker, element)), 1516 ), 1517 1518 Constant::List { elements, .. } => { 1519 tracker.list_used = true; 1520 list( 1521 elements 1522 .iter() 1523 .map(|element| guard_constant_expression(assignments, tracker, element)), 1524 ) 1525 } 1526 Constant::Record { type_, name, .. } if type_.is_bool() && name == "True" => { 1527 Ok("true".to_doc()) 1528 } 1529 Constant::Record { type_, name, .. } if type_.is_bool() && name == "False" => { 1530 Ok("false".to_doc()) 1531 } 1532 Constant::Record { type_, .. } if type_.is_nil() => Ok("undefined".to_doc()), 1533 1534 Constant::Record { 1535 args, 1536 module, 1537 name, 1538 tag, 1539 type_, 1540 .. 1541 } => { 1542 if type_.is_result() { 1543 if tag == "Ok" { 1544 tracker.ok_used = true; 1545 } else { 1546 tracker.error_used = true; 1547 } 1548 } 1549 1550 // If there's no arguments and the type is a function that takes 1551 // arguments then this is the constructor being referenced, not the 1552 // function being called. 1553 if let Some(arity) = type_.fn_arity() { 1554 if args.is_empty() && arity != 0 { 1555 let arity = arity as u16; 1556 return Ok(record_constructor( 1557 type_.clone(), 1558 None, 1559 name, 1560 arity, 1561 tracker, 1562 )); 1563 } 1564 } 1565 1566 let field_values: Vec<_> = args 1567 .iter() 1568 .map(|arg| guard_constant_expression(assignments, tracker, &arg.value)) 1569 .try_collect()?; 1570 Ok(construct_record( 1571 module.as_ref().map(|(module, _)| module.as_str()), 1572 name, 1573 field_values, 1574 )) 1575 } 1576 1577 Constant::BitArray { segments, .. } => bit_array(tracker, segments, |tracker, constant| { 1578 guard_constant_expression(assignments, tracker, constant) 1579 }), 1580 1581 Constant::Var { name, .. } => Ok(assignments 1582 .iter() 1583 .find(|assignment| assignment.name == name) 1584 .map(|assignment| assignment.subject.clone()) 1585 .unwrap_or_else(|| maybe_escape_identifier(name).to_doc())), 1586 1587 expression => constant_expression(Context::Function, tracker, expression), 1588 } 1589} 1590 1591#[derive(Debug, Clone, Copy)] 1592/// The context where the constant expression is used, it might be inside a 1593/// function call, or in the definition of another constant. 1594/// 1595/// Based on the context we might want to annotate pure function calls as 1596/// "@__PURE__". 1597/// 1598pub enum Context { 1599 Constant, 1600 Function, 1601} 1602 1603pub(crate) fn constant_expression<'a>( 1604 context: Context, 1605 tracker: &mut UsageTracker, 1606 expression: &'a TypedConstant, 1607) -> Output<'a> { 1608 match expression { 1609 Constant::Int { value, .. } => Ok(int(value)), 1610 Constant::Float { value, .. } => Ok(float(value)), 1611 Constant::String { value, .. } => Ok(string(value)), 1612 Constant::Tuple { elements, .. } => array( 1613 elements 1614 .iter() 1615 .map(|element| constant_expression(context, tracker, element)), 1616 ), 1617 1618 Constant::List { elements, .. } => { 1619 tracker.list_used = true; 1620 let list = list( 1621 elements 1622 .iter() 1623 .map(|element| constant_expression(context, tracker, element)), 1624 )?; 1625 1626 match context { 1627 Context::Constant => Ok(docvec!["/* @__PURE__ */ ", list]), 1628 Context::Function => Ok(list), 1629 } 1630 } 1631 1632 Constant::Record { type_, name, .. } if type_.is_bool() && name == "True" => { 1633 Ok("true".to_doc()) 1634 } 1635 Constant::Record { type_, name, .. } if type_.is_bool() && name == "False" => { 1636 Ok("false".to_doc()) 1637 } 1638 Constant::Record { type_, .. } if type_.is_nil() => Ok("undefined".to_doc()), 1639 1640 Constant::Record { 1641 args, 1642 module, 1643 name, 1644 tag, 1645 type_, 1646 .. 1647 } => { 1648 if type_.is_result() { 1649 if tag == "Ok" { 1650 tracker.ok_used = true; 1651 } else { 1652 tracker.error_used = true; 1653 } 1654 } 1655 1656 // If there's no arguments and the type is a function that takes 1657 // arguments then this is the constructor being referenced, not the 1658 // function being called. 1659 if let Some(arity) = type_.fn_arity() { 1660 if args.is_empty() && arity != 0 { 1661 let arity = arity as u16; 1662 return Ok(record_constructor( 1663 type_.clone(), 1664 None, 1665 name, 1666 arity, 1667 tracker, 1668 )); 1669 } 1670 } 1671 1672 let field_values: Vec<_> = args 1673 .iter() 1674 .map(|arg| constant_expression(context, tracker, &arg.value)) 1675 .try_collect()?; 1676 1677 let constructor = construct_record( 1678 module.as_ref().map(|(module, _)| module.as_str()), 1679 name, 1680 field_values, 1681 ); 1682 match context { 1683 Context::Constant => Ok(docvec!["/* @__PURE__ */ ", constructor]), 1684 Context::Function => Ok(constructor), 1685 } 1686 } 1687 1688 Constant::BitArray { segments, .. } => { 1689 let bit_array = bit_array(tracker, segments, |tracker, expr| { 1690 constant_expression(context, tracker, expr) 1691 })?; 1692 match context { 1693 Context::Constant => Ok(docvec!["/* @__PURE__ */ ", bit_array]), 1694 Context::Function => Ok(bit_array), 1695 } 1696 } 1697 1698 Constant::Var { name, module, .. } => Ok({ 1699 match module { 1700 None => maybe_escape_identifier(name).to_doc(), 1701 Some((module, _)) => { 1702 // JS keywords can be accessed here, but we must escape anyway 1703 // as we escape when exporting such names in the first place, 1704 // and the imported name has to match the exported name. 1705 docvec!["$", module, ".", maybe_escape_identifier(name)] 1706 } 1707 } 1708 }), 1709 1710 Constant::StringConcatenation { left, right, .. } => { 1711 let left = constant_expression(context, tracker, left)?; 1712 let right = constant_expression(context, tracker, right)?; 1713 Ok(docvec![left, " + ", right]) 1714 } 1715 1716 Constant::Invalid { .. } => panic!("invalid constants should not reach code generation"), 1717 } 1718} 1719 1720fn bit_array<'a>( 1721 tracker: &mut UsageTracker, 1722 segments: &'a [TypedConstantBitArraySegment], 1723 mut constant_expr_fun: impl FnMut(&mut UsageTracker, &'a TypedConstant) -> Output<'a>, 1724) -> Output<'a> { 1725 use BitArrayOption as Opt; 1726 1727 tracker.bit_array_literal_used = true; 1728 let segments_array = array(segments.iter().map(|segment| { 1729 let value = constant_expr_fun(tracker, &segment.value)?; 1730 1731 if segment.has_native_option() { 1732 return Err(Error::Unsupported { 1733 feature: "This bit array segment option".into(), 1734 location: segment.location, 1735 }); 1736 } 1737 1738 match segment.options.as_slice() { 1739 // Int segment 1740 _ if segment.type_.is_int() => { 1741 let details = 1742 sized_bit_array_segment_details(segment, tracker, &mut constant_expr_fun)?; 1743 match (details.size_value, segment.value.as_ref()) { 1744 (Some(size_value), Constant::Int { int_value, .. }) 1745 if size_value <= SAFE_INT_SEGMENT_MAX_SIZE.into() 1746 && (&size_value % BigInt::from(8) == BigInt::ZERO) => 1747 { 1748 let bytes = bit_array_segment_int_value_to_bytes( 1749 int_value.clone(), 1750 size_value, 1751 segment.endianness(), 1752 )?; 1753 1754 Ok(u8_slice(&bytes)) 1755 } 1756 1757 (Some(size_value), _) if size_value == 8.into() => Ok(value), 1758 1759 (Some(size_value), _) if size_value <= 0.into() => Ok(nil()), 1760 1761 _ => { 1762 tracker.sized_integer_segment_used = true; 1763 let size = details.size; 1764 let is_big = bool(segment.endianness().is_big()); 1765 Ok(docvec!["sizedInt(", value, ", ", size, ", ", is_big, ")"]) 1766 } 1767 } 1768 } 1769 1770 // Float segments 1771 _ if segment.type_.is_float() => { 1772 tracker.float_bit_array_segment_used = true; 1773 let details = 1774 sized_bit_array_segment_details(segment, tracker, &mut constant_expr_fun)?; 1775 let size = details.size; 1776 let is_big = bool(segment.endianness().is_big()); 1777 Ok(docvec!["sizedFloat(", value, ", ", size, ", ", is_big, ")"]) 1778 } 1779 1780 // UTF8 strings 1781 [Opt::Utf8 { .. }] => { 1782 tracker.string_bit_array_segment_used = true; 1783 Ok(docvec!["stringBits(", value, ")"]) 1784 } 1785 1786 // UTF8 codepoints 1787 [Opt::Utf8Codepoint { .. }] => { 1788 tracker.codepoint_bit_array_segment_used = true; 1789 Ok(docvec!["codepointBits(", value, ")"]) 1790 } 1791 1792 // Bit arrays 1793 [Opt::Bits { .. }] => Ok(value), 1794 1795 // Bit arrays with explicit size. The explicit size slices the bit array to the 1796 // specified size. A runtime exception is thrown if the size exceeds the number 1797 // of bits in the bit array. 1798 [Opt::Bits { .. }, Opt::Size { value: size, .. }] 1799 | [Opt::Size { value: size, .. }, Opt::Bits { .. }] => match &**size { 1800 Constant::Int { value: size, .. } => { 1801 tracker.bit_array_slice_used = true; 1802 Ok(docvec!["bitArraySlice(", value, ", 0, ", size, ")"]) 1803 } 1804 1805 _ => Err(Error::Unsupported { 1806 feature: "This bit array segment option".into(), 1807 location: segment.location, 1808 }), 1809 }, 1810 1811 // Anything else 1812 _ => Err(Error::Unsupported { 1813 feature: "This bit array segment option".into(), 1814 location: segment.location, 1815 }), 1816 } 1817 }))?; 1818 1819 Ok(docvec!["toBitArray(", segments_array, ")"]) 1820} 1821 1822#[derive(Debug)] 1823struct SizedBitArraySegmentDetails<'a> { 1824 size: Document<'a>, 1825 /// The size of the bit array segment stored as a BigInt. 1826 /// This has a value when the segment's size is known at compile time. 1827 size_value: Option<BigInt>, 1828} 1829 1830fn sized_bit_array_segment_details<'a>( 1831 segment: &'a TypedConstantBitArraySegment, 1832 tracker: &mut UsageTracker, 1833 constant_expr_fun: &mut impl FnMut(&mut UsageTracker, &'a TypedConstant) -> Output<'a>, 1834) -> Result<SizedBitArraySegmentDetails<'a>, Error> { 1835 let size = segment.size(); 1836 let unit = segment.unit(); 1837 let (size_value, size) = match size { 1838 Some(Constant::Int { int_value, .. }) => { 1839 let size_value = int_value * unit; 1840 let size = eco_format!("{}", size_value).to_doc(); 1841 (Some(size_value), size) 1842 } 1843 1844 Some(size) => { 1845 let mut size = constant_expr_fun(tracker, size)?; 1846 1847 if unit != 1 { 1848 size = size.group().append(" * ".to_doc().append(unit.to_doc())); 1849 } 1850 1851 (None, size) 1852 } 1853 1854 None => { 1855 let size_value: usize = if segment.type_.is_int() { 8 } else { 64 }; 1856 (Some(BigInt::from(size_value)), docvec![size_value]) 1857 } 1858 }; 1859 1860 Ok(SizedBitArraySegmentDetails { size, size_value }) 1861} 1862 1863pub fn string(value: &str) -> Document<'_> { 1864 if value.contains('\n') { 1865 EcoString::from(value.replace('\n', r"\n")) 1866 .to_doc() 1867 .surround("\"", "\"") 1868 } else { 1869 value.to_doc().surround("\"", "\"") 1870 } 1871} 1872 1873pub fn array<'a, Elements: IntoIterator<Item = Output<'a>>>(elements: Elements) -> Output<'a> { 1874 let elements = Itertools::intersperse(elements.into_iter(), Ok(break_(",", ", "))) 1875 .collect::<Result<Vec<_>, _>>()?; 1876 if elements.is_empty() { 1877 // Do not add a trailing comma since that adds an 'undefined' element 1878 Ok("[]".to_doc()) 1879 } else { 1880 Ok(docvec![ 1881 "[", 1882 docvec![break_("", ""), elements].nest(INDENT), 1883 break_(",", ""), 1884 "]" 1885 ] 1886 .group()) 1887 } 1888} 1889 1890fn list<'a, I: IntoIterator<Item = Output<'a>>>(elements: I) -> Output<'a> 1891where 1892 I::IntoIter: DoubleEndedIterator + ExactSizeIterator, 1893{ 1894 let array = array(elements); 1895 Ok(docvec!["toList(", array?, ")"]) 1896} 1897 1898fn prepend<'a, I: IntoIterator<Item = Output<'a>>>(elements: I, tail: Document<'a>) -> Output<'a> 1899where 1900 I::IntoIter: DoubleEndedIterator + ExactSizeIterator, 1901{ 1902 elements.into_iter().rev().try_fold(tail, |tail, element| { 1903 let args = call_arguments([element, Ok(tail)])?; 1904 Ok(docvec!["listPrepend", args]) 1905 }) 1906} 1907 1908fn call_arguments<'a, Elements: IntoIterator<Item = Output<'a>>>(elements: Elements) -> Output<'a> { 1909 let elements = Itertools::intersperse(elements.into_iter(), Ok(break_(",", ", "))) 1910 .collect::<Result<Vec<_>, _>>()? 1911 .to_doc(); 1912 if elements.is_empty() { 1913 return Ok("()".to_doc()); 1914 } 1915 Ok(docvec![ 1916 "(", 1917 docvec![break_("", ""), elements].nest(INDENT), 1918 break_(",", ""), 1919 ")" 1920 ] 1921 .group()) 1922} 1923 1924fn construct_record<'a>( 1925 module: Option<&'a str>, 1926 name: &'a str, 1927 arguments: impl IntoIterator<Item = Document<'a>>, 1928) -> Document<'a> { 1929 let mut any_arguments = false; 1930 let arguments = join( 1931 arguments.into_iter().inspect(|_| { 1932 any_arguments = true; 1933 }), 1934 break_(",", ", "), 1935 ); 1936 let arguments = docvec![break_("", ""), arguments].nest(INDENT); 1937 let name = if let Some(module) = module { 1938 docvec!["$", module, ".", name] 1939 } else { 1940 name.to_doc() 1941 }; 1942 if any_arguments { 1943 docvec!["new ", name, "(", arguments, break_(",", ""), ")"].group() 1944 } else { 1945 docvec!["new ", name, "()"] 1946 } 1947} 1948 1949impl TypedExpr { 1950 fn handles_own_return(&self) -> bool { 1951 match self { 1952 TypedExpr::Todo { .. } 1953 | TypedExpr::Call { .. } 1954 | TypedExpr::Case { .. } 1955 | TypedExpr::Panic { .. } 1956 | TypedExpr::Block { .. } 1957 | TypedExpr::Echo { .. } 1958 | TypedExpr::Pipeline { .. } 1959 | TypedExpr::RecordUpdate { .. } => true, 1960 1961 TypedExpr::Int { .. } 1962 | TypedExpr::Float { .. } 1963 | TypedExpr::String { .. } 1964 | TypedExpr::Var { .. } 1965 | TypedExpr::Fn { .. } 1966 | TypedExpr::List { .. } 1967 | TypedExpr::BinOp { .. } 1968 | TypedExpr::RecordAccess { .. } 1969 | TypedExpr::ModuleSelect { .. } 1970 | TypedExpr::Tuple { .. } 1971 | TypedExpr::TupleIndex { .. } 1972 | TypedExpr::BitArray { .. } 1973 | TypedExpr::NegateBool { .. } 1974 | TypedExpr::NegateInt { .. } 1975 | TypedExpr::Invalid { .. } => false, 1976 } 1977 } 1978} 1979 1980impl BinOp { 1981 fn is_operator_to_wrap(&self) -> bool { 1982 match self { 1983 BinOp::And 1984 | BinOp::Or 1985 | BinOp::Eq 1986 | BinOp::NotEq 1987 | BinOp::LtInt 1988 | BinOp::LtEqInt 1989 | BinOp::LtFloat 1990 | BinOp::LtEqFloat 1991 | BinOp::GtEqInt 1992 | BinOp::GtInt 1993 | BinOp::GtEqFloat 1994 | BinOp::GtFloat 1995 | BinOp::AddInt 1996 | BinOp::AddFloat 1997 | BinOp::SubInt 1998 | BinOp::SubFloat 1999 | BinOp::MultFloat 2000 | BinOp::DivInt 2001 | BinOp::DivFloat 2002 | BinOp::RemainderInt 2003 | BinOp::Concatenate => true, 2004 BinOp::MultInt => false, 2005 } 2006 } 2007} 2008 2009pub fn is_js_scalar(t: Arc<Type>) -> bool { 2010 t.is_int() || t.is_float() || t.is_bool() || t.is_nil() || t.is_string() 2011} 2012 2013fn requires_semicolon(statement: &TypedStatement) -> bool { 2014 match statement { 2015 Statement::Expression( 2016 TypedExpr::Int { .. } 2017 | TypedExpr::Fn { .. } 2018 | TypedExpr::Var { .. } 2019 | TypedExpr::List { .. } 2020 | TypedExpr::Call { .. } 2021 | TypedExpr::Echo { .. } 2022 | TypedExpr::Float { .. } 2023 | TypedExpr::String { .. } 2024 | TypedExpr::BinOp { .. } 2025 | TypedExpr::Tuple { .. } 2026 | TypedExpr::NegateInt { .. } 2027 | TypedExpr::BitArray { .. } 2028 | TypedExpr::TupleIndex { .. } 2029 | TypedExpr::NegateBool { .. } 2030 | TypedExpr::RecordAccess { .. } 2031 | TypedExpr::ModuleSelect { .. } 2032 | TypedExpr::Block { .. }, 2033 ) => true, 2034 2035 Statement::Expression( 2036 TypedExpr::Todo { .. } 2037 | TypedExpr::Case { .. } 2038 | TypedExpr::Panic { .. } 2039 | TypedExpr::Pipeline { .. } 2040 | TypedExpr::RecordUpdate { .. } 2041 | TypedExpr::Invalid { .. }, 2042 ) => false, 2043 2044 Statement::Assignment(_) => false, 2045 Statement::Use(_) => false, 2046 Statement::Assert(_) => false, 2047 } 2048} 2049 2050/// Wrap a document in an immediately invoked function expression 2051fn immediately_invoked_function_expression_document(document: Document<'_>) -> Document<'_> { 2052 docvec![ 2053 docvec!["(() => {", break_("", " "), document].nest(INDENT), 2054 break_("", " "), 2055 "})()", 2056 ] 2057 .group() 2058} 2059 2060fn record_constructor<'a>( 2061 type_: Arc<Type>, 2062 qualifier: Option<&'a str>, 2063 name: &'a str, 2064 arity: u16, 2065 tracker: &mut UsageTracker, 2066) -> Document<'a> { 2067 if qualifier.is_none() && type_.is_result_constructor() { 2068 if name == "Ok" { 2069 tracker.ok_used = true; 2070 } else if name == "Error" { 2071 tracker.error_used = true; 2072 } 2073 } 2074 if type_.is_bool() && name == "True" { 2075 "true".to_doc() 2076 } else if type_.is_bool() { 2077 "false".to_doc() 2078 } else if type_.is_nil() { 2079 "undefined".to_doc() 2080 } else if arity == 0 { 2081 match qualifier { 2082 Some(module) => docvec!["new $", module, ".", name, "()"], 2083 None => docvec!["new ", name, "()"], 2084 } 2085 } else { 2086 let vars = (0..arity).map(|i| eco_format!("var{i}").to_doc()); 2087 let body = docvec![ 2088 "return ", 2089 construct_record(qualifier, name, vars.clone()), 2090 ";" 2091 ]; 2092 docvec![ 2093 docvec![wrap_args(vars), " => {", break_("", " "), body] 2094 .nest(INDENT) 2095 .append(break_("", " ")) 2096 .group(), 2097 "}", 2098 ] 2099 } 2100} 2101 2102fn u8_slice<'a>(bytes: &[u8]) -> Document<'a> { 2103 let s: EcoString = bytes 2104 .iter() 2105 .map(u8::to_string) 2106 .collect::<Vec<_>>() 2107 .join(", ") 2108 .into(); 2109 2110 docvec![s] 2111}