Skip to main content

typst_eval/
code.rs

1use ecow::{EcoVec, eco_vec};
2use typst_library::diag::{At, SourceResult, bail, error, warning};
3use typst_library::engine::Engine;
4use typst_library::foundations::{
5    Array, Capturer, Closure, ClosureNode, Content, ContextElem, Dict, Func,
6    NativeElement, Selector, Str, Value, ops,
7};
8use typst_library::introspection::{Counter, State};
9use typst_syntax::Span;
10use typst_syntax::ast::{self, AstNode};
11use typst_utils::singleton;
12
13use crate::{CapturesVisitor, Eval, FlowEvent, Vm};
14
15impl Eval for ast::Code<'_> {
16    type Output = Value;
17
18    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
19        eval_code(vm, &mut self.exprs())
20    }
21}
22
23/// Evaluate a stream of expressions.
24fn eval_code<'a>(
25    vm: &mut Vm,
26    exprs: &mut impl Iterator<Item = ast::Expr<'a>>,
27) -> SourceResult<Value> {
28    let flow = vm.flow.take();
29    let mut output = Value::None;
30
31    while let Some(expr) = exprs.next() {
32        let span = expr.span();
33        let value = match expr {
34            ast::Expr::SetRule(set) => {
35                let styles = set.eval(vm)?;
36                if vm.flow.is_some() {
37                    break;
38                }
39
40                let tail = eval_code(vm, exprs)?.display();
41                Value::Content(tail.styled_with_map(styles))
42            }
43            ast::Expr::ShowRule(show) => {
44                let recipe = show.eval(vm)?;
45                if vm.flow.is_some() {
46                    break;
47                }
48
49                let tail = eval_code(vm, exprs)?.display();
50                Value::Content(tail.styled_with_recipe(
51                    &mut vm.engine,
52                    vm.context,
53                    recipe,
54                )?)
55            }
56            _ => expr.eval(vm)?,
57        };
58
59        output = ops::join(output, value).at(span)?;
60
61        if let Some(event) = &vm.flow {
62            warn_for_discarded_content(&mut vm.engine, event, &output);
63            break;
64        }
65    }
66
67    if flow.is_some() {
68        vm.flow = flow;
69    }
70
71    Ok(output)
72}
73
74impl Eval for ast::Expr<'_> {
75    type Output = Value;
76
77    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
78        let span = self.span();
79        let forbidden = |name| {
80            error!(span, "{name} is only allowed directly in code and content blocks")
81        };
82
83        let value = match self {
84            Self::Text(v) => v.eval(vm).map(Value::Content),
85            Self::Space(v) => v.eval(vm).map(Value::Content),
86            Self::Linebreak(v) => v.eval(vm).map(Value::Content),
87            Self::Parbreak(v) => v.eval(vm).map(Value::Content),
88            Self::Escape(v) => v.eval(vm),
89            Self::Shorthand(v) => v.eval(vm),
90            Self::SmartQuote(v) => v.eval(vm).map(Value::Content),
91            Self::Strong(v) => v.eval(vm).map(Value::Content),
92            Self::Emph(v) => v.eval(vm).map(Value::Content),
93            Self::Raw(v) => v.eval(vm).map(Value::Content),
94            Self::Link(v) => v.eval(vm).map(Value::Content),
95            Self::Label(v) => v.eval(vm),
96            Self::Ref(v) => v.eval(vm).map(Value::Content),
97            Self::Heading(v) => v.eval(vm).map(Value::Content),
98            Self::ListItem(v) => v.eval(vm).map(Value::Content),
99            Self::EnumItem(v) => v.eval(vm).map(Value::Content),
100            Self::TermItem(v) => v.eval(vm).map(Value::Content),
101            Self::Equation(v) => v.eval(vm).map(Value::Content),
102            Self::Math(v) => v.eval(vm).map(Value::Content),
103            Self::MathText(v) => v.eval(vm).map(Value::Content),
104            Self::MathIdent(v) => v.eval(vm),
105            Self::MathFieldAccess(v) => v.eval(vm),
106            Self::MathShorthand(v) => v.eval(vm),
107            Self::MathAlignPoint(v) => v.eval(vm).map(Value::Content),
108            Self::MathCall(v) => v.eval(vm),
109            Self::MathDelimited(v) => v.eval(vm).map(Value::Content),
110            Self::MathAttach(v) => v.eval(vm).map(Value::Content),
111            Self::MathPrimes(v) => v.eval(vm).map(Value::Content),
112            Self::MathFrac(v) => v.eval(vm).map(Value::Content),
113            Self::MathRoot(v) => v.eval(vm).map(Value::Content),
114            Self::Ident(v) => v.eval(vm),
115            Self::None(v) => v.eval(vm),
116            Self::Auto(v) => v.eval(vm),
117            Self::Bool(v) => v.eval(vm),
118            Self::Int(v) => v.eval(vm),
119            Self::Float(v) => v.eval(vm),
120            Self::Numeric(v) => v.eval(vm),
121            Self::Str(v) => v.eval(vm),
122            Self::CodeBlock(v) => v.eval(vm),
123            Self::ContentBlock(v) => v.eval(vm).map(Value::Content),
124            Self::Array(v) => v.eval(vm).map(Value::Array),
125            Self::Dict(v) => v.eval(vm).map(Value::Dict),
126            Self::Parenthesized(v) => v.eval(vm),
127            Self::FieldAccess(v) => v.eval(vm),
128            Self::FuncCall(v) => v.eval(vm),
129            Self::Closure(v) => v.eval(vm),
130            Self::Unary(v) => v.eval(vm),
131            Self::Binary(v) => v.eval(vm),
132            Self::LetBinding(v) => v.eval(vm),
133            Self::DestructAssignment(v) => v.eval(vm),
134            Self::SetRule(_) => bail!(forbidden("set")),
135            Self::ShowRule(_) => bail!(forbidden("show")),
136            Self::Contextual(v) => v.eval(vm).map(Value::Content),
137            Self::Conditional(v) => v.eval(vm),
138            Self::WhileLoop(v) => v.eval(vm),
139            Self::ForLoop(v) => v.eval(vm),
140            Self::ModuleImport(v) => v.eval(vm),
141            Self::ModuleInclude(v) => v.eval(vm).map(Value::Content),
142            Self::LoopBreak(v) => v.eval(vm),
143            Self::LoopContinue(v) => v.eval(vm),
144            Self::FuncReturn(v) => v.eval(vm),
145        }?
146        .spanned(span);
147
148        // This satisfies the obligation to call `Vm::trace` for almost all
149        // value-producing expressions!
150        vm.trace_at(span, &value);
151
152        Ok(value)
153    }
154}
155
156impl Eval for ast::Ident<'_> {
157    type Output = Value;
158
159    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
160        let span = self.span();
161        Ok(vm
162            .scopes
163            .get(&self)
164            .at(span)?
165            .read_checked((&mut vm.engine, span))
166            .clone())
167    }
168}
169
170impl Eval for ast::None<'_> {
171    type Output = Value;
172
173    fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
174        Ok(Value::None)
175    }
176}
177
178impl Eval for ast::Auto<'_> {
179    type Output = Value;
180
181    fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
182        Ok(Value::Auto)
183    }
184}
185
186impl Eval for ast::Bool<'_> {
187    type Output = Value;
188
189    fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
190        Ok(Value::Bool(self.get()))
191    }
192}
193
194impl Eval for ast::Int<'_> {
195    type Output = Value;
196
197    fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
198        Ok(Value::Int(self.get()))
199    }
200}
201
202impl Eval for ast::Float<'_> {
203    type Output = Value;
204
205    fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
206        Ok(Value::Float(self.get()))
207    }
208}
209
210impl Eval for ast::Numeric<'_> {
211    type Output = Value;
212
213    fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
214        Ok(Value::numeric(self.get()))
215    }
216}
217
218impl Eval for ast::Str<'_> {
219    type Output = Value;
220
221    fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
222        Ok(Value::Str(self.get().into()))
223    }
224}
225
226impl Eval for ast::Array<'_> {
227    type Output = Array;
228
229    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
230        let mut items = self.items();
231
232        let mut vec = EcoVec::with_capacity(items.size_hint().0);
233
234        // We raise an error when one of the array items is the spread of a
235        // dictionary. If _all_ of the array items are spreads of dictionaries,
236        // the user probably wanted to write `(: ..dict_a, ..dict_b)` instead
237        // to create a dictionary, not an array.
238        let mut all_dict_spreads = true;
239
240        while let Some(item) = items.next() {
241            match item {
242                ast::ArrayItem::Pos(expr) => {
243                    all_dict_spreads = false;
244                    vec.push(expr.eval(vm)?)
245                }
246                ast::ArrayItem::Spread(spread) => match spread.expr().eval(vm)? {
247                    Value::None => {}
248                    Value::Array(array) => {
249                        all_dict_spreads = false;
250                        vec.extend(array);
251                    }
252                    v @ Value::Dict(_)
253                        if all_dict_spreads
254                        // Lookahead to see whether remaining items are spreads
255                        // of dicts
256                        && items.all(|item| matches!(
257                            item,
258                            ast::ArrayItem::Spread(spread) if matches!(
259                                spread.expr().eval(vm),
260                                Ok(Value::Dict(_)),
261                            ),
262                        )) =>
263                    {
264                        let fixed = self.to_untyped().full_text().replacen("(", "(: ", 1);
265                        bail!(
266                            spread.span(), "cannot spread {} into array", v.ty();
267                            hint: "add a colon to create a dictionary instead: `{fixed}`";
268                        )
269                    }
270                    v => bail!(spread.span(), "cannot spread {} into array", v.ty()),
271                },
272            }
273        }
274
275        Ok(vec.into())
276    }
277}
278
279impl Eval for ast::Dict<'_> {
280    type Output = Dict;
281
282    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
283        let mut map = indexmap::IndexMap::default();
284        let mut invalid_keys = eco_vec![];
285
286        for item in self.items() {
287            match item {
288                ast::DictItem::Named(named) => {
289                    map.insert(named.name().get().clone().into(), named.expr().eval(vm)?);
290                }
291                ast::DictItem::Keyed(keyed) => {
292                    let raw_key = keyed.key();
293                    let key = raw_key.eval(vm)?;
294                    let key =
295                        key.cast::<Str>().at(raw_key.span()).unwrap_or_else(|errors| {
296                            invalid_keys.extend(errors);
297                            Str::default()
298                        });
299                    map.insert(key, keyed.expr().eval(vm)?);
300                }
301                ast::DictItem::Spread(spread) => match spread.expr().eval(vm)? {
302                    Value::None => {}
303                    Value::Dict(dict) => map.extend(dict),
304                    v => bail!(spread.span(), "cannot spread {} into dictionary", v.ty()),
305                },
306            }
307        }
308
309        if !invalid_keys.is_empty() {
310            return Err(invalid_keys);
311        }
312
313        Ok(map.into())
314    }
315}
316
317impl Eval for ast::CodeBlock<'_> {
318    type Output = Value;
319
320    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
321        vm.scopes.enter();
322        let output = self.body().eval(vm)?;
323        vm.scopes.exit();
324        Ok(output)
325    }
326}
327
328impl Eval for ast::ContentBlock<'_> {
329    type Output = Content;
330
331    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
332        vm.scopes.enter();
333        let content = self.body().eval(vm)?;
334        vm.scopes.exit();
335        Ok(content)
336    }
337}
338
339impl Eval for ast::Parenthesized<'_> {
340    type Output = Value;
341
342    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
343        self.expr().eval(vm)
344    }
345}
346
347impl Eval for ast::FieldAccess<'_> {
348    type Output = Value;
349
350    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
351        let target = self.target().eval(vm)?;
352        let field = self.field();
353        access_field(vm, target, field.as_str(), field.span())
354    }
355}
356
357/// Access a field on a target value.
358pub(crate) fn access_field(
359    vm: &mut Vm,
360    target: Value,
361    field: &str,
362    field_span: Span,
363) -> SourceResult<Value> {
364    let err = match target.field(field, (&mut vm.engine, field_span)).at(field_span) {
365        Ok(value) => return Ok(value),
366        Err(err) => err,
367    };
368
369    // Check whether this is a get rule field access.
370    if let Value::Func(func) = &target
371        && let Some(element) = func.to_element()
372        && let Some(id) = element.field_id(field)
373        && let styles = vm.context.styles().at(field_span)
374        && let Ok(value) =
375            element.field_from_styles(id, styles.as_ref().map(|&s| s).unwrap_or_default())
376    {
377        // Only validate the contextual styles once we know that this is indeed
378        // a field from the style chain.
379        let _ = styles?;
380        return Ok(value);
381    }
382
383    Err(err)
384}
385
386impl Eval for ast::Contextual<'_> {
387    type Output = Content;
388
389    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
390        let body = self.body();
391
392        // Collect captured variables.
393        let captured = {
394            let mut visitor = CapturesVisitor::new(Some(&vm.scopes), Capturer::Context);
395            visitor.visit(body.to_untyped());
396            visitor.finish()
397        };
398
399        // Define the closure.
400        let closure = Closure {
401            node: ClosureNode::Context(self.body().to_untyped().clone()),
402            defaults: vec![],
403            captured,
404            num_pos_params: 0,
405        };
406
407        let func = Func::from(closure).spanned(body.span());
408        Ok(ContextElem::new(func).pack().spanned(body.span()))
409    }
410}
411
412/// Emits a warning when we discard content while returning unconditionally.
413fn warn_for_discarded_content(engine: &mut Engine, event: &FlowEvent, joined: &Value) {
414    let FlowEvent::Return(span, Some(_), false) = event else { return };
415    let Value::Content(tree) = &joined else { return };
416
417    let selector = singleton!(
418        Selector,
419        Selector::Or(eco_vec![State::select_any(), Counter::select_any()])
420    );
421
422    let mut warning = warning!(
423        *span,
424        "this return unconditionally discards the content before it";
425        hint: "try omitting the `return` to automatically join all values";
426    );
427
428    if tree.query_first_naive(selector).is_some() {
429        warning.hint("state/counter updates are content that must end up in the document to have an effect");
430    }
431
432    engine.sink.warn(warning);
433}