typst_eval/
flow.rs

1use typst_library::diag::{At, SourceDiagnostic, SourceResult, bail, error};
2use typst_library::foundations::{IntoValue, Value, ops};
3use typst_syntax::ast::{self, AstNode};
4use typst_syntax::{Span, SyntaxKind, SyntaxNode};
5use unicode_segmentation::UnicodeSegmentation;
6
7use crate::{Eval, Vm, destructure};
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            output = ops::join(output, value).at(body.span())?;
87
88            match vm.flow {
89                Some(FlowEvent::Break(_)) => {
90                    vm.flow = None;
91                    break;
92                }
93                Some(FlowEvent::Continue(_)) => vm.flow = None,
94                Some(FlowEvent::Return(..)) => break,
95                None => {}
96            }
97
98            i += 1;
99        }
100
101        if flow.is_some() {
102            vm.flow = flow;
103        }
104
105        // Mark the return as conditional.
106        if let Some(FlowEvent::Return(_, _, conditional)) = &mut vm.flow {
107            *conditional = true;
108        }
109
110        Ok(output)
111    }
112}
113
114impl Eval for ast::ForLoop<'_> {
115    type Output = Value;
116
117    #[typst_macros::time(name = "for loop", span = self.span())]
118    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
119        let flow = vm.flow.take();
120        let mut output = Value::None;
121
122        macro_rules! iter {
123            (for $pat:ident in $iterable:expr) => {{
124                vm.scopes.enter();
125
126                #[allow(unused_parens)]
127                for value in $iterable {
128                    destructure(vm, $pat, value.into_value())?;
129
130                    let body = self.body();
131                    let value = body.eval(vm)?;
132                    output = ops::join(output, value).at(body.span())?;
133
134                    match vm.flow {
135                        Some(FlowEvent::Break(_)) => {
136                            vm.flow = None;
137                            break;
138                        }
139                        Some(FlowEvent::Continue(_)) => vm.flow = None,
140                        Some(FlowEvent::Return(..)) => break,
141                        None => {}
142                    }
143                }
144
145                vm.scopes.exit();
146            }};
147        }
148
149        let pattern = self.pattern();
150        let iterable = self.iterable().eval(vm)?;
151        let iterable_type = iterable.ty();
152
153        use ast::Pattern;
154        match (pattern, iterable) {
155            (_, Value::Array(array)) => {
156                // Iterate over values of array.
157                iter!(for pattern in array);
158            }
159            (_, Value::Dict(dict)) => {
160                // Iterate over key-value pairs of dict.
161                iter!(for pattern in dict.iter());
162            }
163            (Pattern::Normal(_) | Pattern::Placeholder(_), Value::Str(str)) => {
164                // Iterate over graphemes of string.
165                iter!(for pattern in str.as_str().graphemes(true));
166            }
167            (Pattern::Normal(_) | Pattern::Placeholder(_), Value::Bytes(bytes)) => {
168                // Iterate over the integers of bytes.
169                iter!(for pattern in bytes.as_slice());
170            }
171            (Pattern::Destructuring(_), Value::Str(_) | Value::Bytes(_)) => {
172                bail!(pattern.span(), "cannot destructure values of {}", iterable_type);
173            }
174            _ => {
175                bail!(self.iterable().span(), "cannot loop over {}", iterable_type);
176            }
177        }
178
179        if flow.is_some() {
180            vm.flow = flow;
181        }
182
183        // Mark the return as conditional.
184        if let Some(FlowEvent::Return(_, _, conditional)) = &mut vm.flow {
185            *conditional = true;
186        }
187
188        Ok(output)
189    }
190}
191
192impl Eval for ast::LoopBreak<'_> {
193    type Output = Value;
194
195    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
196        if vm.flow.is_none() {
197            vm.flow = Some(FlowEvent::Break(self.span()));
198        }
199        Ok(Value::None)
200    }
201}
202
203impl Eval for ast::LoopContinue<'_> {
204    type Output = Value;
205
206    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
207        if vm.flow.is_none() {
208            vm.flow = Some(FlowEvent::Continue(self.span()));
209        }
210        Ok(Value::None)
211    }
212}
213
214impl Eval for ast::FuncReturn<'_> {
215    type Output = Value;
216
217    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
218        let value = self.body().map(|body| body.eval(vm)).transpose()?;
219        if vm.flow.is_none() {
220            vm.flow = Some(FlowEvent::Return(self.span(), value, false));
221        }
222        Ok(Value::None)
223    }
224}
225
226/// Whether the expression always evaluates to the same value.
227fn is_invariant(expr: &SyntaxNode) -> bool {
228    match expr.cast() {
229        Some(ast::Expr::Ident(_)) => false,
230        Some(ast::Expr::MathIdent(_)) => false,
231        Some(ast::Expr::FieldAccess(access)) => {
232            is_invariant(access.target().to_untyped())
233        }
234        Some(ast::Expr::FuncCall(call)) => {
235            is_invariant(call.callee().to_untyped())
236                && is_invariant(call.args().to_untyped())
237        }
238        _ => expr.children().all(is_invariant),
239    }
240}
241
242/// Whether the expression contains a break or return.
243fn can_diverge(expr: &SyntaxNode) -> bool {
244    matches!(expr.kind(), SyntaxKind::Break | SyntaxKind::Return)
245        || expr.children().any(can_diverge)
246}