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
9const MAX_ITERATIONS: usize = 10_000;
11
12#[derive(Debug, Clone, PartialEq)]
14pub enum FlowEvent {
15 Break(Span),
17 Continue(Span),
19 Return(Span, Option<Value>, bool),
22}
23
24impl FlowEvent {
25 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 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 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 iter!(for pattern in array);
161 }
162 (_, Value::Dict(dict)) => {
163 iter!(for pattern in dict.iter());
165 }
166 (Pattern::Normal(_) | Pattern::Placeholder(_), Value::Str(str)) => {
167 iter!(for pattern in str.as_str().graphemes(true));
169 }
170 (Pattern::Normal(_) | Pattern::Placeholder(_), Value::Bytes(bytes)) => {
171 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 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
229fn 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
245fn can_diverge(expr: &SyntaxNode) -> bool {
247 matches!(expr.kind(), SyntaxKind::Break | SyntaxKind::Return)
248 || expr.children().any(can_diverge)
249}