1use std::sync::atomic::{AtomicUsize, Ordering};
4
5use comemo::{Track, Tracked, TrackedMut};
6use ecow::EcoVec;
7use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
8use rustc_hash::FxHashSet;
9use typst_syntax::{FileId, Span};
10use typst_utils::{LazyHash, Protected};
11
12use crate::diag::{HintedStrResult, SourceDiagnostic, SourceResult, StrResult, bail};
13use crate::foundations::{Styles, Value};
14use crate::introspection::{Introspect, Introspection, Introspector};
15use crate::{Library, World};
16
17pub struct Engine<'a> {
19 pub world: Tracked<'a, dyn World + 'a>,
21 pub library: &'a LazyHash<Library>,
27 pub introspector: Protected<Tracked<'a, dyn Introspector + 'a>>,
29 pub traced: Tracked<'a, Traced>,
31 pub sink: TrackedMut<'a, Sink>,
33 pub route: Route<'a>,
36}
37
38impl Engine<'_> {
39 pub fn delay<T: Default>(&mut self, result: SourceResult<T>) -> T {
43 match result {
44 Ok(value) => value,
45 Err(errors) => {
46 self.sink.delayed_errors(errors);
47 T::default()
48 }
49 }
50 }
51
52 pub fn parallelize<P, I, T, U, F>(
54 &mut self,
55 iter: P,
56 f: F,
57 ) -> impl Iterator<Item = U> + use<P, I, T, U, F>
58 where
59 P: IntoIterator<IntoIter = I>,
60 I: Iterator<Item = T>,
61 T: Send,
62 U: Send,
63 F: Fn(&mut Engine, T) -> U + Send + Sync,
64 {
65 let Engine {
66 world, introspector, traced, ref route, library, ..
67 } = *self;
68
69 let work: Vec<T> = iter.into_iter().collect();
72
73 let mut pairs: Vec<(U, Sink)> = Vec::with_capacity(work.len());
75 work.into_par_iter()
76 .map(|value| {
77 let mut sink = Sink::new();
78 let mut engine = Engine {
79 world,
80 introspector,
81 traced,
82 sink: sink.track_mut(),
83 route: route.clone(),
84 library,
85 };
86 (f(&mut engine, value), sink)
87 })
88 .collect_into_vec(&mut pairs);
89
90 for (_, sink) in &mut pairs {
92 let sink = std::mem::take(sink);
93 self.sink.extend(
94 sink.introspections,
95 sink.delayed,
96 sink.warnings,
97 sink.values,
98 );
99 }
100
101 pairs.into_iter().map(|(output, _)| output)
102 }
103
104 pub fn introspect<I>(&mut self, introspection: I) -> I::Output
110 where
111 I: Introspect,
112 {
113 let introspector = *self.introspector.access("is okay since we're recording it");
114 let output = introspection.introspect(self, introspector);
115 self.sink.introspection(Introspection::new(introspection));
116 output
117 }
118}
119
120#[derive(Default)]
122pub struct Traced(Option<Span>);
123
124impl Traced {
125 pub fn new(traced: Span) -> Self {
129 Self(Some(traced))
130 }
131}
132
133#[comemo::track]
134impl Traced {
135 pub fn get(&self, id: FileId) -> Option<Span> {
141 if self.0.and_then(Span::id) == Some(id) { self.0 } else { None }
142 }
143}
144
145#[derive(Default, Clone)]
152pub struct Sink {
153 introspections: EcoVec<Introspection>,
155 delayed: EcoVec<SourceDiagnostic>,
161 warnings: EcoVec<SourceDiagnostic>,
163 warnings_set: FxHashSet<u128>,
165 values: EcoVec<(Value, Option<Styles>)>,
167}
168
169impl Sink {
170 pub const MAX_VALUES: usize = 10;
172
173 pub fn new() -> Self {
175 Self::default()
176 }
177
178 pub fn introspections(&self) -> &[Introspection] {
180 &self.introspections
181 }
182
183 pub fn delayed(&mut self) -> EcoVec<SourceDiagnostic> {
185 std::mem::take(&mut self.delayed)
186 }
187
188 pub fn warnings(self) -> EcoVec<SourceDiagnostic> {
190 self.warnings
191 }
192
193 pub fn values(self) -> EcoVec<(Value, Option<Styles>)> {
195 self.values
196 }
197
198 pub fn extend_from_sink(&mut self, other: Sink) {
200 self.extend(other.introspections, other.delayed, other.warnings, other.values);
201 }
202}
203
204#[comemo::track]
205impl Sink {
206 pub fn introspection(&mut self, introspection: Introspection) {
208 self.introspections.push(introspection);
209 }
210
211 pub fn delayed_error(&mut self, error: SourceDiagnostic) {
213 self.delayed.push(error);
214 }
215
216 pub fn delayed_errors(&mut self, errors: EcoVec<SourceDiagnostic>) {
218 self.delayed.extend(errors);
219 }
220
221 pub fn warn(&mut self, warning: SourceDiagnostic) {
223 let hash = typst_utils::hash128(&(&warning.span, &warning.message));
225 if self.warnings_set.insert(hash) {
226 self.warnings.push(warning);
227 }
228 }
229
230 pub fn value(&mut self, value: Value, styles: Option<Styles>) {
232 if self.values.len() < Self::MAX_VALUES {
233 self.values.push((value, styles));
234 }
235 }
236
237 fn extend(
239 &mut self,
240 introspections: EcoVec<Introspection>,
241 delayed: EcoVec<SourceDiagnostic>,
242 warnings: EcoVec<SourceDiagnostic>,
243 values: EcoVec<(Value, Option<Styles>)>,
244 ) {
245 self.introspections.extend(introspections);
246 self.delayed.extend(delayed);
247 for warning in warnings {
248 self.warn(warning);
249 }
250 if let Some(remaining) = Self::MAX_VALUES.checked_sub(self.values.len()) {
251 self.values.extend(values.into_iter().take(remaining));
252 }
253 }
254}
255
256pub struct Route<'a> {
259 outer: Option<Tracked<'a, Self, <Route<'static> as Track>::Call>>,
266 id: Option<FileId>,
269 len: usize,
275 upper: AtomicUsize,
281}
282
283impl<'a> Route<'a> {
284 pub fn root() -> Self {
286 Self {
287 id: None,
288 outer: None,
289 len: 0,
290 upper: AtomicUsize::new(0),
291 }
292 }
293
294 pub fn extend(outer: Tracked<'a, Self>) -> Self {
296 Route {
297 outer: Some(outer),
298 id: None,
299 len: 1,
300 upper: AtomicUsize::new(usize::MAX),
301 }
302 }
303
304 pub fn with_id(self, id: FileId) -> Self {
306 Self { id: Some(id), ..self }
307 }
308
309 pub fn unnested(self) -> Self {
311 Self { len: 0, ..self }
312 }
313
314 pub fn track(&self) -> Tracked<'_, Self> {
319 match self.outer {
320 Some(outer) if self.id.is_none() && self.len == 0 => outer,
321 _ => Track::track(self),
322 }
323 }
324
325 pub fn increase(&mut self) {
327 self.len += 1;
328 }
329
330 pub fn decrease(&mut self) {
332 self.len -= 1;
333 }
334}
335
336impl Route<'_> {
341 const MAX_SHOW_RULE_DEPTH: usize = 64;
343
344 const MAX_LAYOUT_DEPTH: usize = 72;
346
347 const MAX_HTML_DEPTH: usize = 72;
349
350 const MAX_CALL_DEPTH: usize = 80;
352
353 pub fn check_show_depth(&self) -> HintedStrResult<()> {
355 if !self.within(Route::MAX_SHOW_RULE_DEPTH) {
356 bail!(
357 "maximum show rule depth exceeded";
358 hint: "maybe a show rule matches its own output";
359 hint: "maybe there are too deeply nested elements";
360 );
361 }
362 Ok(())
363 }
364
365 pub fn check_layout_depth(&self) -> HintedStrResult<()> {
367 if !self.within(Route::MAX_LAYOUT_DEPTH) {
368 bail!(
369 "maximum layout depth exceeded";
370 hint: "try to reduce the amount of nesting in your layout";
371 );
372 }
373 Ok(())
374 }
375
376 pub fn check_html_depth(&self) -> HintedStrResult<()> {
378 if !self.within(Route::MAX_HTML_DEPTH) {
379 bail!(
380 "maximum HTML depth exceeded";
381 hint: "try to reduce the amount of nesting of your HTML";
382 );
383 }
384 Ok(())
385 }
386
387 pub fn check_call_depth(&self) -> StrResult<()> {
389 if !self.within(Route::MAX_CALL_DEPTH) {
390 bail!("maximum function call depth exceeded");
391 }
392 Ok(())
393 }
394}
395
396#[comemo::track]
397#[allow(clippy::needless_lifetimes)]
398impl<'a> Route<'a> {
399 pub fn contains(&self, id: FileId) -> bool {
401 self.id == Some(id) || self.outer.is_some_and(|outer| outer.contains(id))
402 }
403
404 pub fn within(&self, depth: usize) -> bool {
406 use Ordering::Relaxed;
409
410 let upper = self.upper.load(Relaxed);
411 if upper.saturating_add(self.len) <= depth {
412 return true;
413 }
414
415 match self.outer {
416 Some(_) if depth < self.len => false,
417 Some(outer) => {
418 let within = outer.within(depth - self.len);
419 if within && depth < upper {
420 self.upper.compare_exchange(upper, depth, Relaxed, Relaxed).ok();
423 }
424 within
425 }
426 None => true,
427 }
428 }
429}
430
431impl Default for Route<'_> {
432 fn default() -> Self {
433 Self::root()
434 }
435}
436
437impl Clone for Route<'_> {
438 fn clone(&self) -> Self {
439 Self {
440 outer: self.outer,
441 id: self.id,
442 len: self.len,
443 upper: AtomicUsize::new(self.upper.load(Ordering::Relaxed)),
444 }
445 }
446}