1use comemo::{Tracked, TrackedMut};
2use ecow::{EcoString, EcoVec, eco_format};
3use typst_library::diag::{
4 At, HintedStrResult, HintedString, SourceResult, Trace, Tracepoint, bail, error,
5};
6use typst_library::engine::{Engine, Sink, Traced};
7use typst_library::foundations::{
8 Arg, Args, Binding, Capturer, Closure, ClosureNode, Content, Context, Func,
9 NativeElement, Scope, Scopes, SequenceElem, SymbolElem, Value,
10};
11use typst_library::introspection::Introspector;
12use typst_library::math::LrElem;
13use typst_library::{Library, World};
14use typst_syntax::ast::{self, AstNode};
15use typst_syntax::{Span, Spanned, SyntaxNode};
16use typst_utils::{LazyHash, Protected};
17
18use crate::{
19 Access, Eval, FlowEvent, Route, Vm, call_method_mut, hint_if_shadowed_std,
20 is_dict_mutating_method, is_mutating_method,
21};
22
23impl Eval for ast::FuncCall<'_> {
24 type Output = Value;
25
26 fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
27 let span = self.span();
28 let callee = self.callee();
29
30 vm.engine.route.check_call_depth().at(span)?;
31
32 if let ast::Expr::FieldAccess(access) = callee {
34 let target_expr = access.target();
35 let field = access.field();
36 let (target, maybe_args) = if is_mutating_method(field.as_str()) {
37 match maybe_resolve_mutating(vm, target_expr, field, self.args(), span)? {
38 Ok(value) => return Ok(value),
39 Err((target, args)) => (target, Some(args)),
40 }
41 } else {
42 (target_expr.eval(vm)?, None)
43 };
44 match eval_field_callee(
45 vm,
46 access.to_untyped(),
47 field.as_str(),
48 field.span(),
49 target,
50 false,
51 )? {
52 FieldCallee::Func(func) => {
53 let args = match maybe_args {
54 Some(args) => args,
55 None => self.args().eval(vm)?.spanned(span),
56 };
57 call_func(vm, func, args, span)
58 }
59 FieldCallee::Method(func, target) => {
60 let mut args = match maybe_args {
61 Some(args) => args,
62 None => self.args().eval(vm)?.spanned(span),
63 };
64 args.insert(0, target_expr.span(), target);
66 call_func(vm, func, args, span)
67 }
68 FieldCallee::NonFunc(_, err) => Err(err).at(callee.span()),
69 }
70 } else {
71 let func = callee
73 .eval(vm)?
74 .cast::<Func>()
75 .map_err(|err| hint_if_shadowed_std(vm, &callee, err))
76 .at(callee.span())?;
77 let args = self.args().eval(vm)?.spanned(span);
78 call_func(vm, func, args, span)
79 }
80 }
81}
82
83impl Eval for ast::MathCall<'_> {
84 type Output = Value;
85
86 fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
87 eval_math_call(vm, self)
88 }
89}
90
91fn eval_math_call(vm: &mut Vm, math_call: ast::MathCall) -> SourceResult<Value> {
93 let span = math_call.span();
94 let callee = math_call.callee();
95 let mut target_span = Span::detached();
96
97 vm.engine.route.check_call_depth().at(span)?;
98
99 let math_call_result = match callee {
100 ast::MathAccess::MathIdent(ident) => {
101 let callee_value = ident.eval(vm)?;
102 vm.trace_at(ident.span(), &callee_value);
105 match callee_value.clone().cast::<Func>() {
106 Ok(func) => FieldCallee::Func(func),
107 Err(err) => FieldCallee::NonFunc(callee_value, err),
108 }
109 }
110 ast::MathAccess::MathFieldAccess(access) => {
111 let target_expr = access.target();
112 target_span = target_expr.span();
113 let field = access.field();
114 let target = target_expr.eval(vm)?;
115 if is_mutating_method(field.as_str())
116 && matches!(target, Value::Array(_) | Value::Dict(_))
117 {
118 bail!(
129 span,
130 "cannot call mutating methods in math";
131 hint: "try using code mode to call the method: `#{}`",
132 math_call.to_untyped().full_text();
133 );
134 }
135 eval_field_callee(
136 vm,
137 access.to_untyped(),
138 field.as_str(),
139 field.span(),
140 target,
141 true,
142 )?
143 }
144 };
145
146 let args = math_call.args();
147 match math_call_result {
148 FieldCallee::Func(func) => {
149 let args = args.eval(vm)?.spanned(span);
150 call_func(vm, func, args, span)
151 }
152 FieldCallee::Method(func, target) => {
153 let mut args = args.eval(vm)?.spanned(span);
154 args.insert(0, target_span, target);
156 call_func(vm, func, args, span)
157 }
158 FieldCallee::NonFunc(callee_value, _) => {
159 let parens = unparse_math_args(vm, args, callee)?;
160 Ok(Value::Content(callee_value.display().spanned(callee.span()) + parens))
161 }
162 }
163}
164
165fn call_func(vm: &mut Vm, func: Func, args: Args, span: Span) -> SourceResult<Value> {
167 let func = func.spanned(span);
168 let point = || Tracepoint::Call(func.name().map(Into::into));
169 let f = || {
170 func.call(&mut vm.engine, vm.context, args)
171 .trace(vm.world(), point, span)
172 };
173
174 #[cfg(target_arch = "wasm32")]
176 return f();
177
178 #[cfg(not(target_arch = "wasm32"))]
179 stacker::maybe_grow(32 * 1024, 2 * 1024 * 1024, f)
180}
181
182fn maybe_resolve_mutating(
190 vm: &mut Vm,
191 target: ast::Expr,
192 field: ast::Ident,
193 args: ast::Args,
194 span: Span,
195) -> SourceResult<Result<Value, (Value, Args)>> {
196 let args = args.eval(vm)?.spanned(span);
199 match target.access(vm)? {
200 target @ Value::Dict(_) if !is_dict_mutating_method(field.as_str()) => {
202 Ok(Err((target.clone(), args)))
203 }
204 target @ (Value::Array(_) | Value::Dict(_)) => {
206 let value = call_method_mut(target, &field, args, span);
207 let point = || Tracepoint::Call(Some(field.get().clone()));
208 Ok(Ok(value.trace(vm.world(), point, span)?))
209 }
210 target => Ok(Err((target.clone(), args))),
211 }
212}
213
214enum FieldCallee {
216 Method(Func, Value),
219 Func(Func),
221 NonFunc(Value, HintedString),
224}
225
226fn eval_field_callee<'a, 'b>(
240 vm: &'a mut Vm<'b>,
241 access: &SyntaxNode,
242 field: &str,
243 field_span: Span,
244 target: Value,
245 in_math: bool,
246) -> SourceResult<FieldCallee> {
247 let sink = (&mut vm.engine, field_span);
248
249 let mut is_method_call = false;
250 let callee_value = if let Some(method) = target.ty().scope().get(field) {
251 is_method_call = true;
252 method.read_checked(sink).clone()
253 } else if let Value::Content(content) = &target
254 && let Some(method) = content.elem().scope().get(field)
255 {
256 is_method_call = true;
257 method.read_checked(sink).clone()
258 } else if matches!(
259 target,
260 Value::Symbol(_) | Value::Func(_) | Value::Type(_) | Value::Module(_)
261 ) {
262 target.field(field, sink).at(field_span)?
264 } else {
265 match target.field(field, sink) {
267 Ok(callee_value) => {
269 let is_dict = matches!(target, Value::Dict(_));
280 let is_named = matches!(target, Value::Args(_));
281 let mut err = if is_dict {
282 error!(
286 access.span(),
287 "cannot directly call dictionary keys as functions";
288 )
289 } else if is_named {
290 error!(
292 access.span(),
293 "cannot directly call named argument fields as functions";
294 )
295 } else {
296 let (kind, name) = element_or_type_with_name(&target);
297 error!(
298 access.span(),
299 "`{field}` is not a valid method for {kind} `{name}`";
300 )
301 };
302 if callee_value.clone().cast::<Func>().is_ok() {
303 err.hint(eco_format!(
304 "to call the stored function, {}wrap the field access \
305 in parentheses: `{}({})(..)`",
306 if in_math { "use code mode and " } else { "" },
307 if in_math { "#" } else { "" },
308 access.full_text(),
309 ));
310 } else if in_math {
311 err.hint("try adding a space before the parentheses");
312 } else {
313 err.hint(eco_format!(
314 "to access the `{field}` {}, remove the function arguments: `{}`",
315 if is_dict {
316 "key"
317 } else if is_named {
318 "argument"
319 } else {
320 "field"
321 },
322 access.full_text(),
323 ));
324 }
325 if is_dict {
326 err.hint(
327 "dictionary keys cannot be used with method syntax as keys \
328 could conflict with built-in method names",
329 );
330 } else if is_named {
331 err.hint(
332 "named arguments cannot be used with method syntax as argument \
333 names could conflict with built-in method names",
334 );
335 }
336
337 bail!(err)
338 }
339 Err(_) => {
342 let (kind, name) = element_or_type_with_name(&target);
343 bail!(access.span(), "{kind} {name} has no method `{field}`")
344 }
345 }
346 };
347
348 vm.trace_at(access.span(), &callee_value);
349
350 match callee_value.clone().cast::<Func>() {
351 Ok(func) if is_method_call => Ok(FieldCallee::Method(func, target)),
352 Ok(func) => Ok(FieldCallee::Func(func)),
353 Err(err) => Ok(FieldCallee::NonFunc(callee_value, err)),
354 }
355}
356
357fn element_or_type_with_name(value: &Value) -> (&'static str, &'static str) {
360 if let Value::Content(content) = value {
361 ("element", content.elem().name())
362 } else {
363 ("type", value.ty().long_name())
364 }
365}
366
367impl Eval for ast::Args<'_> {
368 type Output = Args;
369
370 fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
371 let mut items = EcoVec::with_capacity(self.items().count());
372
373 for arg in self.items() {
374 let span = arg.span();
375 match arg {
376 ast::Arg::Pos(expr) => {
377 items.push(Arg {
378 span,
379 name: None,
380 value: Spanned::new(expr.eval(vm)?, expr.span()),
381 });
382 }
383 ast::Arg::Named(named) => {
384 let expr = named.expr();
385 items.push(Arg {
386 span,
387 name: Some(named.name().get().clone().into()),
388 value: Spanned::new(expr.eval(vm)?, expr.span()),
389 });
390 }
391 ast::Arg::Spread(spread) => match spread.expr().eval(vm)? {
392 Value::None => {}
393 Value::Array(array) => {
394 items.extend(array.into_iter().map(|value| Arg {
395 span,
396 name: None,
397 value: Spanned::new(value, span),
398 }));
399 }
400 Value::Dict(dict) => {
401 items.extend(dict.into_iter().map(|(key, value)| Arg {
402 span,
403 name: Some(key),
404 value: Spanned::new(value, span),
405 }));
406 }
407 Value::Args(args) => items.extend(args.items),
408 v => bail!(spread.span(), "cannot spread {}", v.ty()),
409 },
410 }
411 }
412
413 Ok(Args { span: Span::detached(), items })
416 }
417}
418
419impl Eval for ast::MathArgs<'_> {
420 type Output = Args;
421
422 fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
423 let mut named = EcoVec::new();
426 let mut pos = Vec::new();
427 let mut two_dim_start: Option<usize> = None;
428
429 fn drain_into_array(pos: &mut Vec<Arg>, start: usize, span: Span) {
433 let array = pos.drain(start..).map(|arg| arg.value.v).collect();
434 pos.push(Arg {
435 span,
436 name: None,
437 value: Spanned::new(Value::Array(array), span),
438 });
439 }
440
441 for ast::MathArg { arg, ends_in_semicolon } in self.arg_items() {
442 let span = arg.span();
443 match arg {
444 ast::Arg::Pos(expr) => {
445 pos.push(Arg {
446 span,
447 name: None,
448 value: Spanned::new(expr.eval(vm)?, expr.span()),
449 });
450 }
451 ast::Arg::Named(named_arg) => {
452 let expr = named_arg.expr();
453 named.push(Arg {
454 span,
455 name: Some(named_arg.name().get().clone().into()),
456 value: Spanned::new(expr.eval(vm)?, expr.span()),
457 });
458 }
459 ast::Arg::Spread(spread) => match spread.expr().eval(vm)? {
460 Value::None => {}
461 Value::Array(array) => {
462 pos.extend(array.into_iter().map(|value| Arg {
463 span,
464 name: None,
465 value: Spanned::new(value, span),
466 }));
467 }
468 Value::Dict(dict) => {
469 named.extend(dict.into_iter().map(|(key, value)| Arg {
470 span,
471 name: Some(key),
472 value: Spanned::new(value, span),
473 }));
474 }
475 Value::Args(args) => {
476 for arg in args.items {
477 if arg.name.is_none() {
478 pos.push(arg);
479 } else {
480 named.push(arg);
481 }
482 }
483 }
484 v => bail!(spread.span(), "cannot spread {}", v.ty()),
485 },
486 }
487 if ends_in_semicolon {
488 let start = two_dim_start.unwrap_or(0);
489 drain_into_array(&mut pos, start, self.span());
491 two_dim_start = Some(pos.len());
492 }
493 }
494
495 if let Some(start) = two_dim_start
496 && start != pos.len()
497 {
498 drain_into_array(&mut pos, start, self.span());
499 }
500
501 named.extend(pos);
502 Ok(Args { span: Span::detached(), items: named })
503 }
504}
505
506fn unparse_math_args(
509 vm: &mut Vm,
510 args: ast::MathArgs,
511 callee: ast::MathAccess,
512) -> SourceResult<Content> {
513 let mut body = Vec::new();
514 let mut errors = EcoVec::new();
515 for item in args.content_items() {
516 match item {
517 ast::MathArgItem::Space(space) => {
518 body.push(space.eval(vm)?.spanned(space.span()));
519 }
520 ast::MathArgItem::Comma(c, node)
521 | ast::MathArgItem::Semicolon(c, node)
522 | ast::MathArgItem::LeftParen(c, node)
523 | ast::MathArgItem::RightParen(c, node) => {
524 body.push(SymbolElem::packed(c).spanned(node.span()));
525 }
526 ast::MathArgItem::Arg(ast::Arg::Pos(expr)) => {
527 body.push(expr.eval(vm)?.display().spanned(expr.span()));
531 }
532 ast::MathArgItem::Arg(ast::Arg::Named(named)) => {
533 let name = callee.to_untyped().full_text();
534 let fixed = named.to_untyped().full_text().replacen(":", "\\:", 1);
535 errors.push(error!(
536 named.span(), "named-argument syntax can only be used with functions";
537 hint[callee.span()]: "`{name}` is not a function";
538 hint: "to render the colon as text, escape it: `{fixed}`";
539 ));
540 }
541 ast::MathArgItem::Arg(ast::Arg::Spread(spread)) => {
542 let name = callee.to_untyped().full_text();
543 let fixed = spread.to_untyped().full_text().replacen("..", ".. ", 1);
544 errors.push(error!(
545 spread.span(), "spread-argument syntax can only be used with functions";
546 hint[callee.span()]: "`{name}` is not a function";
547 hint: "to render the dots as text, add a space: `{fixed}`";
548 ));
549 }
550 }
551 }
552
553 if !errors.is_empty() {
554 return Err(errors);
555 }
556
557 Ok(LrElem::new(SequenceElem::new(body).pack())
558 .pack()
559 .spanned(args.span()))
560}
561
562impl Eval for ast::Closure<'_> {
563 type Output = Value;
564
565 fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
566 let mut defaults = Vec::new();
568 for param in self.params().children() {
569 if let ast::Param::Named(named) = param {
570 defaults.push(named.expr().eval(vm)?);
571 }
572 }
573
574 let captured = {
576 let mut visitor = CapturesVisitor::new(Some(&vm.scopes), Capturer::Function);
577 visitor.visit(self.to_untyped());
578 visitor.finish()
579 };
580
581 let closure = Closure {
583 node: ClosureNode::Closure(self.to_untyped().clone()),
584 defaults,
585 captured,
586 num_pos_params: self
587 .params()
588 .children()
589 .filter(|p| matches!(p, ast::Param::Pos(_)))
590 .count(),
591 };
592
593 Ok(Value::Func(Func::from(closure).spanned(self.params().span())))
594 }
595}
596
597#[comemo::memoize]
599#[allow(clippy::too_many_arguments)]
600pub fn eval_closure(
601 func: &Func,
602 closure: &LazyHash<Closure>,
603 world: Tracked<dyn World + '_>,
604 library: &LazyHash<Library>,
605 introspector: Tracked<dyn Introspector + '_>,
606 traced: Tracked<Traced>,
607 sink: TrackedMut<Sink>,
608 route: Tracked<Route>,
609 context: Tracked<Context>,
610 mut args: Args,
611) -> SourceResult<Value> {
612 let (name, params, body) = match closure.node {
613 ClosureNode::Closure(ref node) => {
614 let closure =
615 node.cast::<ast::Closure>().expect("node to be an `ast::Closure`");
616 (closure.name(), closure.params(), closure.body())
617 }
618 ClosureNode::Context(ref node) => {
619 (None, ast::Params::placeholder(), node.cast().unwrap())
620 }
621 };
622
623 let mut scopes = Scopes::new(None);
626 scopes.top = closure.captured.clone();
627
628 let introspector = Protected::from_raw(introspector);
630 let engine = Engine {
631 library,
632 world,
633 introspector,
634 traced,
635 sink,
636 route: Route::extend(route),
637 };
638
639 let mut vm = Vm::new(engine, context, scopes, body.span());
641
642 if let Some(name) = name {
644 vm.define(name, func.clone());
645 }
646
647 let num_pos_args = args.to_pos().len();
648 let sink_size = num_pos_args.checked_sub(closure.num_pos_params);
649
650 let mut sink = None;
651 let mut sink_pos_values = None;
652 let mut defaults = closure.defaults.iter();
653 for p in params.children() {
654 match p {
655 ast::Param::Pos(pattern) => match pattern {
656 ast::Pattern::Normal(ast::Expr::Ident(ident)) => {
657 vm.define(ident, args.expect::<Value>(&ident)?)
658 }
659 pattern => {
660 crate::destructure(
661 &mut vm,
662 pattern,
663 args.expect::<Value>("pattern parameter")?,
664 )?;
665 }
666 },
667 ast::Param::Spread(spread) => {
668 sink = Some(spread.sink_ident());
669 if let Some(sink_size) = sink_size {
670 sink_pos_values = Some(args.consume(sink_size)?);
671 }
672 }
673 ast::Param::Named(named) => {
674 let name = named.name();
675 let default = defaults.next().unwrap();
676 let value =
677 args.named::<Value>(&name)?.unwrap_or_else(|| default.clone());
678 vm.define(name, value);
679 }
680 }
681 }
682
683 if let Some(sink) = sink {
684 let mut remaining_args = args.take();
686 if let Some(sink_name) = sink {
687 if let Some(sink_pos_values) = sink_pos_values {
688 remaining_args.items.extend(sink_pos_values);
689 }
690 vm.define(sink_name, remaining_args);
691 }
692 }
693
694 args.finish()?;
696
697 let output = body.eval(&mut vm)?;
699 match vm.flow {
700 Some(FlowEvent::Return(_, Some(explicit), _)) => return Ok(explicit),
701 Some(FlowEvent::Return(_, None, _)) => {}
702 Some(flow) => bail!(flow.forbidden()),
703 None => {}
704 }
705
706 Ok(output)
707}
708
709pub struct CapturesVisitor<'a> {
711 external: Option<&'a Scopes<'a>>,
712 internal: Scopes<'a>,
713 captures: Scope,
714 capturer: Capturer,
715}
716
717impl<'a> CapturesVisitor<'a> {
718 pub fn new(external: Option<&'a Scopes<'a>>, capturer: Capturer) -> Self {
720 Self {
721 external,
722 internal: Scopes::new(None),
723 captures: Scope::new(),
724 capturer,
725 }
726 }
727
728 pub fn finish(self) -> Scope {
730 self.captures
731 }
732
733 pub fn visit(&mut self, node: &SyntaxNode) {
735 match node.cast() {
736 Some(ast::Expr::Ident(ident)) => self.capture(ident.get(), Scopes::get),
741 Some(ast::Expr::MathIdent(ident)) => {
742 self.capture(ident.get(), Scopes::get_in_math)
743 }
744
745 Some(ast::Expr::CodeBlock(_) | ast::Expr::ContentBlock(_)) => {
747 self.internal.enter();
748 for child in node.children() {
749 self.visit(child);
750 }
751 self.internal.exit();
752 }
753
754 Some(ast::Expr::FieldAccess(access)) => {
756 self.visit(access.target().to_untyped());
757 }
758 Some(ast::Expr::MathFieldAccess(access)) => {
759 self.visit(access.target().to_untyped());
760 }
761
762 Some(ast::Expr::Closure(expr)) => {
766 for param in expr.params().children() {
767 if let ast::Param::Named(named) = param {
768 self.visit(named.expr().to_untyped());
769 }
770 }
771
772 self.internal.enter();
773 if let Some(name) = expr.name() {
774 self.bind(name);
775 }
776
777 for param in expr.params().children() {
778 match param {
779 ast::Param::Pos(pattern) => {
780 for ident in pattern.bindings() {
781 self.bind(ident);
782 }
783 }
784 ast::Param::Named(named) => self.bind(named.name()),
785 ast::Param::Spread(spread) => {
786 if let Some(ident) = spread.sink_ident() {
787 self.bind(ident);
788 }
789 }
790 }
791 }
792
793 self.visit(expr.body().to_untyped());
794 self.internal.exit();
795 }
796
797 Some(ast::Expr::LetBinding(expr)) => {
800 if let Some(init) = expr.init() {
801 self.visit(init.to_untyped());
802 }
803
804 for ident in expr.kind().bindings() {
805 self.bind(ident);
806 }
807 }
808
809 Some(ast::Expr::ForLoop(expr)) => {
813 self.visit(expr.iterable().to_untyped());
814 self.internal.enter();
815
816 let pattern = expr.pattern();
817 for ident in pattern.bindings() {
818 self.bind(ident);
819 }
820
821 self.visit(expr.body().to_untyped());
822 self.internal.exit();
823 }
824
825 Some(ast::Expr::ModuleImport(expr)) => {
828 self.visit(expr.source().to_untyped());
829 if let Some(ast::Imports::Items(items)) = expr.imports() {
830 for item in items.iter() {
831 self.bind(item.bound_name());
832 }
833 }
834 }
835
836 _ => {
837 if let Some(named) = node.cast::<ast::Named>() {
839 self.visit(named.expr().to_untyped());
840 return;
841 }
842
843 for child in node.children() {
845 self.visit(child);
846 }
847 }
848 }
849 }
850
851 fn bind(&mut self, ident: ast::Ident) {
853 self.internal
856 .top
857 .bind(ident.get().clone(), Binding::detached(Value::None));
858 }
859
860 fn capture(
862 &mut self,
863 ident: &EcoString,
864 getter: impl FnOnce(&'a Scopes<'a>, &str) -> HintedStrResult<&'a Binding>,
865 ) {
866 if self.internal.get(ident).is_ok() {
867 return;
868 }
869
870 let binding = match self.external {
871 Some(external) => match getter(external, ident) {
872 Ok(binding) => binding.capture(self.capturer),
873 Err(_) => return,
874 },
875 None => Binding::detached(Value::None),
878 };
879
880 self.captures.bind(ident.clone(), binding);
881 }
882}
883
884#[cfg(test)]
885mod tests {
886 use typst_syntax::parse;
887
888 use super::*;
889
890 #[track_caller]
891 fn test(scopes: &Scopes, text: &str, result: &[&str]) {
892 let mut visitor = CapturesVisitor::new(Some(scopes), Capturer::Function);
893 let root = parse(text);
894 visitor.visit(&root);
895
896 let captures = visitor.finish();
897 let mut names: Vec<_> = captures.iter().map(|(k, ..)| k).collect();
898 names.sort();
899
900 assert_eq!(names, result);
901 }
902
903 #[test]
904 fn test_captures() {
905 let mut scopes = Scopes::new(None);
906 scopes.top.define("f", 0);
907 scopes.top.define("x", 0);
908 scopes.top.define("y", 0);
909 scopes.top.define("z", 0);
910 let s = &scopes;
911
912 test(s, "#let x = x", &["x"]);
914 test(s, "#let x; #(x + y)", &["y"]);
915 test(s, "#let f(x, y) = x + y", &[]);
916 test(s, "#let f(x, y) = f", &[]);
917 test(s, "#let f = (x, y) => f", &["f"]);
918
919 test(s, "#((x, y) => x + z)", &["z"]);
921 test(s, "#((x: y, z) => x + z)", &["y"]);
922 test(s, "#((..x) => x + y)", &["y"]);
923 test(s, "#((x, y: x + z) => x + y)", &["x", "z"]);
924 test(s, "#{x => x; x}", &["x"]);
925
926 test(s, "#show y: x => x", &["y"]);
928 test(s, "#show y: x => x + z", &["y", "z"]);
929 test(s, "#show x: x => x", &["x"]);
930
931 test(s, "#for x in y { x + z }", &["y", "z"]);
933 test(s, "#for (x, y) in y { x + y }", &["y"]);
934 test(s, "#for x in y {} #x", &["x", "y"]);
935
936 test(s, "#import z: x, y", &["z"]);
938 test(s, "#import x + y: x, y, z", &["x", "y"]);
939
940 test(s, "#{ let x = 1; { let y = 2; y }; x + y }", &["y"]);
942 test(s, "#[#let x = 1]#x", &["x"]);
943
944 test(s, "#x.y.f(z)", &["x", "z"]);
946
947 test(s, "#f(x: 1)", &["f"]);
949 test(s, "#(x: 1)", &[]);
950 test(s, "#(x = 1)", &["x"]);
951 test(s, "#(x += y)", &["x", "y"]);
952 test(s, "#{ (x, z) = (y, 1) }", &["x", "y", "z"]);
953 test(s, "#(x.at(y) = 5)", &["x", "y"]);
954 }
955
956 #[test]
957 fn test_captures_in_math() {
958 let mut scopes = Scopes::new(None);
959 scopes.top.define("f", 0);
960 scopes.top.define("x", 0);
961 scopes.top.define("y", 0);
962 scopes.top.define("z", 0);
963 scopes.top.define("foo", 0);
965 scopes.top.define("bar", 0);
966 scopes.top.define("x-bar", 0);
967 scopes.top.define("x_bar", 0);
968 let s = &scopes;
969
970 test(s, "$ x f(z) $", &[]); test(s, "$ #x #f(z) $", &["f", "x", "z"]);
973 test(s, "$ foo f(bar) $", &["bar", "foo"]);
974 test(s, "$ #foo[#$bar$] $", &["bar", "foo"]);
975 test(s, "$ #let foo = x; foo $", &["x"]);
976
977 test(s, "$ x-y x_y foo-x x_bar $", &["bar", "foo"]);
979 test(s, "$ #x-bar #x_bar $", &["x-bar", "x_bar"]);
980
981 test(s, "$ foo(bar: y) $", &["foo"]);
983 test(s, "$ foo(x-y: 1, bar-z: 2) $", &["foo"]);
984
985 test(s, "$ foo.bar $", &["foo"]);
987 test(s, "$ foo.x $", &["foo"]);
988 test(s, "$ x.foo $", &["foo"]);
989 test(s, "$ foo . bar $", &["bar", "foo"]);
990 test(s, "$ foo.x.y.bar(z) $", &["foo"]);
991 test(s, "$ foo.x-bar $", &["bar", "foo"]);
992 test(s, "$ foo.x_bar $", &["bar", "foo"]);
993 test(s, "$ #x_bar.x-bar $", &["x_bar"]);
994 }
995}