typst_eval/
flow.rs

1use typst_library::diag::{bail, error, At, SourceDiagnostic, SourceResult};
2use typst_library::foundations::{ops, IntoValue, Value};
3use typst_syntax::ast::{self, AstNode};
4use typst_syntax::{Span, SyntaxKind, SyntaxNode};
5use unicode_segmentation::UnicodeSegmentation;
6
7use crate::{destructure, Eval, Vm};
8
9/// The maximum number of loop iterations.
10const MAX_ITERATIONS: usize = 10_000;
11
12/// A control flow event that occurred during evaluation.
13#[derive(Debug, Clone, PartialEq)]
14pub enum FlowEvent {
15    /// Stop iteration in a loop.
16    Break(Span),
17    /// Skip the remainder of the current iteration in a loop.
18    Continue(Span),
19    /// Stop execution of a function early, optionally returning an explicit
20    /// value. The final boolean indicates whether the return was conditional.
21    Return(Span, Option<Value>, bool),
22}
23
24impl FlowEvent {
25    /// Return an error stating that this control flow is forbidden.
26    pub fn forbidden(&self) -> SourceDiagnostic {
27        match *self {
28            Self::Break(span) => {
29                error!(span, "cannot break outside of loop")
30            }
31            Self::Continue(span) => {
32                error!(span, "cannot continue outside of loop")
33            }
34            Self::Return(span, _, _) => {
35                error!(span, "cannot return outside of function")
36            }
37        }
38    }
39}
40
41impl Eval for ast::Conditional<'_> {
42    type Output = Value;
43
44    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
45        let condition = self.condition();
46        let output = if condition.eval(vm)?.cast::<bool>().at(condition.span())? {
47            self.if_body().eval(vm)?
48        } else if let Some(else_body) = self.else_body() {
49            else_body.eval(vm)?
50        } else {
51            Value::None
52        };
53
54        // Mark the return as conditional.
55        if let Some(FlowEvent::Return(_, _, conditional)) = &mut vm.flow {
56            *conditional = true;
57        }
58
59        Ok(output)
60    }
61}
62
63impl Eval for ast::WhileLoop<'_> {
64    type Output = Value;
65
66    #[typst_macros::time(name = "while loop", span = self.span())]
67    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
68        let flow = vm.flow.take();
69        let mut output = Value::None;
70        let mut i = 0;
71
72        let condition = self.condition();
73        let body = self.body();
74
75        while condition.eval(vm)?.cast::<bool>().at(condition.span())? {
76            if i == 0
77                && is_invariant(condition.to_untyped())
78                && !can_diverge(body.to_untyped())
79            {
80                bail!(condition.span(), "condition is always true");
81            } else if i >= MAX_ITERATIONS {
82                bail!(self.span(), "loop seems to be infinite");
83            }
84
85            let value = body.eval(vm)?;
86            let span = body.span();
87            output = ops::join(output, value, &mut (&mut vm.engine, span)).at(span)?;
88
89            match vm.flow {
90                Some(FlowEvent::Break(_)) => {
91                    vm.flow = None;
92                    break;
93                }
94                Some(FlowEvent::Continue(_)) => vm.flow = None,
95                Some(FlowEvent::Return(..)) => break,
96                None => {}
97            }
98
99            i += 1;
100        }
101
102        if flow.is_some() {
103            vm.flow = flow;
104        }
105
106        // Mark the return as conditional.
107        if let Some(FlowEvent::Return(_, _, conditional)) = &mut vm.flow {
108            *conditional = true;
109        }
110
111        Ok(output)
112    }
113}
114
115impl Eval for ast::ForLoop<'_> {
116    type Output = Value;
117
118    #[typst_macros::time(name = "for loop", span = self.span())]
119    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
120        let flow = vm.flow.take();
121        let mut output = Value::None;
122
123        macro_rules! iter {
124            (for $pat:ident in $iterable:expr) => {{
125                vm.scopes.enter();
126
127                #[allow(unused_parens)]
128                for value in $iterable {
129                    destructure(vm, $pat, value.into_value())?;
130
131                    let body = self.body();
132                    let value = body.eval(vm)?;
133                    let span = body.span();
134                    output =
135                        ops::join(output, value, &mut (&mut vm.engine, span)).at(span)?;
136
137                    match vm.flow {
138                        Some(FlowEvent::Break(_)) => {
139                            vm.flow = None;
140                            break;
141                        }
142                        Some(FlowEvent::Continue(_)) => vm.flow = None,
143                        Some(FlowEvent::Return(..)) => break,
144                        None => {}
145                    }
146                }
147
148                vm.scopes.exit();
149            }};
150        }
151
152        let pattern = self.pattern();
153        let iterable = self.iterable().eval(vm)?;
154        let iterable_type = iterable.ty();
155
156        use ast::Pattern;
157        match (pattern, iterable) {
158            (_, Value::Array(array)) => {
159                // Iterate over values of array.
160                iter!(for pattern in array);
161            }
162            (_, Value::Dict(dict)) => {
163                // Iterate over key-value pairs of dict.
164                iter!(for pattern in dict.iter());
165            }
166            (Pattern::Normal(_) | Pattern::Placeholder(_), Value::Str(str)) => {
167                // Iterate over graphemes of string.
168                iter!(for pattern in str.as_str().graphemes(true));
169            }
170            (Pattern::Normal(_) | Pattern::Placeholder(_), Value::Bytes(bytes)) => {
171                // Iterate over the integers of bytes.
172                iter!(for pattern in bytes.as_slice());
173            }
174            (Pattern::Destructuring(_), Value::Str(_) | Value::Bytes(_)) => {
175                bail!(pattern.span(), "cannot destructure values of {}", iterable_type);
176            }
177            _ => {
178                bail!(self.iterable().span(), "cannot loop over {}", iterable_type);
179            }
180        }
181
182        if flow.is_some() {
183            vm.flow = flow;
184        }
185
186        // Mark the return as conditional.
187        if let Some(FlowEvent::Return(_, _, conditional)) = &mut vm.flow {
188            *conditional = true;
189        }
190
191        Ok(output)
192    }
193}
194
195impl Eval for ast::LoopBreak<'_> {
196    type Output = Value;
197
198    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
199        if vm.flow.is_none() {
200            vm.flow = Some(FlowEvent::Break(self.span()));
201        }
202        Ok(Value::None)
203    }
204}
205
206impl Eval for ast::LoopContinue<'_> {
207    type Output = Value;
208
209    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
210        if vm.flow.is_none() {
211            vm.flow = Some(FlowEvent::Continue(self.span()));
212        }
213        Ok(Value::None)
214    }
215}
216
217impl Eval for ast::FuncReturn<'_> {
218    type Output = Value;
219
220    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
221        let value = self.body().map(|body| body.eval(vm)).transpose()?;
222        if vm.flow.is_none() {
223            vm.flow = Some(FlowEvent::Return(self.span(), value, false));
224        }
225        Ok(Value::None)
226    }
227}
228
229/// Whether the expression always evaluates to the same value.
230fn is_invariant(expr: &SyntaxNode) -> bool {
231    match expr.cast() {
232        Some(ast::Expr::Ident(_)) => false,
233        Some(ast::Expr::MathIdent(_)) => false,
234        Some(ast::Expr::FieldAccess(access)) => {
235            is_invariant(access.target().to_untyped())
236        }
237        Some(ast::Expr::FuncCall(call)) => {
238            is_invariant(call.callee().to_untyped())
239                && is_invariant(call.args().to_untyped())
240        }
241        _ => expr.children().all(is_invariant),
242    }
243}
244
245/// Whether the expression contains a break or return.
246fn can_diverge(expr: &SyntaxNode) -> bool {
247    matches!(expr.kind(), SyntaxKind::Break | SyntaxKind::Return)
248        || expr.children().any(can_diverge)
249}