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
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 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 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 iter!(for pattern in array);
158 }
159 (_, Value::Dict(dict)) => {
160 iter!(for pattern in dict.iter());
162 }
163 (Pattern::Normal(_) | Pattern::Placeholder(_), Value::Str(str)) => {
164 iter!(for pattern in str.as_str().graphemes(true));
166 }
167 (Pattern::Normal(_) | Pattern::Placeholder(_), Value::Bytes(bytes)) => {
168 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 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
226fn 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
242fn can_diverge(expr: &SyntaxNode) -> bool {
244 matches!(expr.kind(), SyntaxKind::Break | SyntaxKind::Return)
245 || expr.children().any(can_diverge)
246}