Skip to main content

typst_library/introspection/
counter.rs

1use std::fmt::Write;
2use std::num::NonZeroUsize;
3use std::str::FromStr;
4use std::sync::Arc;
5
6use comemo::{Track, Tracked, TrackedMut};
7use ecow::{EcoString, EcoVec, eco_format, eco_vec};
8use smallvec::{SmallVec, smallvec};
9use typst_syntax::Span;
10use typst_utils::{LazyHash, NonZeroExt, Protected};
11
12use crate::diag::{At, HintedStrResult, SourceDiagnostic, SourceResult, bail, warning};
13use crate::engine::{Engine, Route, Sink, Traced};
14use crate::foundations::{
15    Args, Array, Construct, Content, Context, Element, Func, IntoValue, Label,
16    LocatableSelector, NativeElement, Packed, Repr, Selector, ShowFn, Smart, Str,
17    StyleChain, Value, cast, elem, func, scope, select_where, ty,
18};
19use crate::introspection::{
20    History, Introspect, Introspector, Locatable, Location, QueryFirstIntrospection, Tag,
21    Unqueriable,
22};
23use crate::layout::{Frame, FrameItem, PageElem};
24use crate::math::EquationElem;
25use crate::model::{FigureElem, FootnoteElem, HeadingElem, Numbering, NumberingPattern};
26use crate::{Library, World};
27
28/// Counts through pages, elements, and more.
29///
30/// With the counter function, you can access and modify counters for pages,
31/// headings, figures, and more. Moreover, you can define custom counters for
32/// other things you want to count.
33///
34/// Since counters change throughout the course of the document, their current
35/// value is _contextual._ It is recommended to read the chapter on
36/// @reference:context[context] before continuing here.
37///
38/// = #short-or-long[Accessing][Accessing a counter] <accessing>
39/// To access the raw value of a counter, we can use the @counter.get[`get`]
40/// function. This function returns an @array[array]: Counters can have multiple
41/// levels (in the case of headings for sections, subsections, and so on), and
42/// each item in the array corresponds to one level.
43///
44/// ```example
45/// #set heading(numbering: "1.")
46///
47/// = Introduction
48/// Raw value of heading counter is
49/// #context counter(heading).get()
50/// ```
51///
52/// = #short-or-long[Displaying][Displaying a counter] <displaying>
53/// Often, we want to display the value of a counter in a more human-readable
54/// way. To do that, we can call the @counter.display[`display`] function on the
55/// counter. This function retrieves the current counter value and formats it
56/// either with a provided or with an automatically inferred
57/// @numbering[numbering].
58///
59/// ```example
60/// #set heading(numbering: "1.")
61///
62/// = Introduction
63/// Some text here.
64///
65/// = Background
66/// The current value is: #context {
67///   counter(heading).display()
68/// }
69///
70/// Or in roman numerals: #context {
71///   counter(heading).display("I")
72/// }
73/// ```
74///
75/// = #short-or-long[Modifying][Modifying a counter] <modifying>
76/// To modify a counter, you can use the `step` and `update` methods:
77///
78/// - The `step` method increases the value of the counter by one. Because
79///   counters can have multiple levels , it optionally takes a `level`
80///   argument. If given, the counter steps at the given depth.
81///
82/// - The `update` method allows you to arbitrarily modify the counter. In its
83///   basic form, you give it an integer (or an array for multiple levels). For
84///   more flexibility, you can instead also give it a function that receives
85///   the current value and returns a new value.
86///
87/// The heading counter is stepped before the heading is displayed, so
88/// `Analysis` gets the number seven even though the counter is at six after the
89/// second update.
90///
91/// ```example
92/// #set heading(numbering: "1.")
93///
94/// = Introduction
95/// #counter(heading).step()
96///
97/// = Background
98/// #counter(heading).update(3)
99/// #counter(heading).update(n => n * 2)
100///
101/// = Analysis
102/// Let's skip 7.1.
103/// #counter(heading).step(level: 2)
104///
105/// == Analysis
106/// Still at #context {
107///   counter(heading).display()
108/// }
109/// ```
110///
111/// = Page counter <page-counter>
112/// The page counter is special. It is automatically stepped at each pagebreak.
113/// But like other counters, you can also step it manually. For example, you
114/// could have Roman page numbers for your preface, then switch to Arabic page
115/// numbers for your main content and reset the page counter to one.
116///
117/// ```example
118/// >>> #set page(
119/// >>>   height: 100pt,
120/// >>>   margin: (bottom: 24pt, rest: 16pt),
121/// >>> )
122/// #set page(numbering: "(i)")
123///
124/// = Preface
125/// The preface is numbered with
126/// roman numerals.
127///
128/// #set page(numbering: "1 / 1")
129/// #counter(page).update(1)
130///
131/// = Main text
132/// Here, the counter is reset to one.
133/// We also display both the current
134/// page and total number of pages in
135/// Arabic numbers.
136/// ```
137///
138/// = Custom counters <custom-counters>
139/// To define your own counter, call the `counter` function with a string as a
140/// key. This key identifies the counter globally.
141///
142/// ```example
143/// #let mine = counter("mycounter")
144/// #context mine.display() \
145/// #mine.step()
146/// #context mine.display() \
147/// #mine.update(c => c * 3)
148/// #context mine.display()
149/// ```
150///
151/// = How to step <how-to-step>
152/// When you define and use a custom counter, in general, you should first step
153/// the counter and then display it. This way, the stepping behaviour of a
154/// counter can depend on the element it is stepped for. If you were writing a
155/// counter for, let's say, theorems, your theorem's definition would thus first
156/// include the counter step and only then display the counter and the theorem's
157/// contents.
158///
159/// ```example
160/// #let c = counter("theorem")
161/// #let theorem(it) = block[
162///   #c.step()
163///   *Theorem #context c.display():*
164///   #it
165/// ]
166///
167/// #theorem[$1 = 1$]
168/// #theorem[$2 < 3$]
169/// ```
170///
171/// The rationale behind this is best explained on the example of the heading
172/// counter: An update to the heading counter depends on the heading's level. By
173/// stepping directly before the heading, we can correctly step from `1` to
174/// `1.1` when encountering a level 2 heading. If we were to step after the
175/// heading, we wouldn't know what to step to.
176///
177/// Because counters should always be stepped before the elements they count,
178/// they always start at zero. This way, they are at one for the first display
179/// (which happens after the first step).
180///
181/// = Time travel <time-travel>
182/// Counters can travel through time! You can find out the final value of the
183/// counter before it is reached and even determine what the value was at any
184/// particular location in the document.
185///
186/// ```example
187/// #let mine = counter("mycounter")
188///
189/// = Values
190/// #context [
191///   Value here: #mine.get() \
192///   At intro: #mine.at(<intro>) \
193///   Final value: #mine.final()
194/// ]
195///
196/// #mine.update(n => n + 3)
197///
198/// = Introduction <intro>
199/// #lorem(10)
200///
201/// #mine.step()
202/// #mine.step()
203/// ```
204///
205/// = #short-or-long[Other State][Other kinds of state] <other-state>
206/// The `counter` type is closely related to @state[state] type. Read its
207/// documentation for more details on state management in Typst and why it
208/// doesn't just use normal variables for counters.
209#[ty(scope)]
210#[derive(Debug, Clone, PartialEq, Hash)]
211pub struct Counter(CounterKey);
212
213impl Counter {
214    /// Create a new counter identified by a key.
215    pub fn new(key: CounterKey) -> Counter {
216        Self(key)
217    }
218
219    /// The counter for the given element.
220    pub fn of(func: Element) -> Self {
221        Self::new(CounterKey::Selector(Selector::Elem(func, None)))
222    }
223
224    /// Selects all state updates.
225    pub fn select_any() -> Selector {
226        CounterUpdateElem::ELEM.select()
227    }
228
229    /// The selector relevant for this counter's updates.
230    pub fn select(
231        &self,
232        introspector: Tracked<dyn Introspector + '_>,
233        loc: Location,
234    ) -> Selector {
235        let mut selector = select_where!(CounterUpdateElem, key => self.0.clone());
236
237        match &self.0 {
238            CounterKey::Page => {
239                // In bundle export, we only want the page counter updates in
240                // the current document.
241                if let Some(doc_location) = introspector.document(loc) {
242                    selector = Selector::Within {
243                        selector: Arc::new(selector),
244                        ancestor: Arc::new(doc_location.into()),
245                    };
246                }
247            }
248            CounterKey::Selector(key) => {
249                selector = Selector::Or(eco_vec![selector, key.clone()]);
250            }
251            CounterKey::Str(_) => {}
252        }
253
254        selector
255    }
256
257    /// Whether this is the page counter.
258    pub fn is_page(&self) -> bool {
259        self.0 == CounterKey::Page
260    }
261
262    /// Displays the value of the counter at the given location.
263    pub fn display_at(
264        &self,
265        engine: &mut Engine,
266        loc: Location,
267        styles: StyleChain,
268        numbering: &Numbering,
269        span: Span,
270    ) -> SourceResult<Content> {
271        let context = Context::new(Some(loc), Some(styles));
272        Ok(engine
273            .introspect(CounterAtIntrospection(self.clone(), loc, span))?
274            .display(engine, context.track(), span, numbering)?
275            .display())
276    }
277
278    /// Resolves the numbering for this counter track.
279    ///
280    /// This coupling between the counter type and the remaining standard
281    /// library is not great ...
282    fn matching_numbering(
283        &self,
284        engine: &mut Engine,
285        styles: StyleChain,
286        loc: Location,
287        span: Span,
288    ) -> Option<Numbering> {
289        match self.0 {
290            CounterKey::Page => loc.page_numbering(engine, span),
291            CounterKey::Selector(Selector::Elem(func, _)) => engine
292                .introspect(QueryFirstIntrospection(Selector::Location(loc), span))
293                .and_then(|content| {
294                    if func == HeadingElem::ELEM {
295                        content
296                            .to_packed::<HeadingElem>()
297                            .and_then(|elem| elem.numbering.as_option().clone())
298                            .flatten()
299                    } else if func == FigureElem::ELEM {
300                        content
301                            .to_packed::<FigureElem>()
302                            .and_then(|elem| elem.numbering.as_option().clone())
303                            .flatten()
304                    } else if func == EquationElem::ELEM {
305                        content
306                            .to_packed::<EquationElem>()
307                            .and_then(|elem| elem.numbering.as_option().clone())
308                            .flatten()
309                    } else if func == FootnoteElem::ELEM {
310                        content
311                            .to_packed::<FootnoteElem>()
312                            .and_then(|elem| elem.numbering.as_option().clone())
313                    } else {
314                        None
315                    }
316                })
317                .or_else(|| {
318                    if func == HeadingElem::ELEM {
319                        styles.get_cloned(HeadingElem::numbering)
320                    } else if func == FigureElem::ELEM {
321                        styles.get_cloned(FigureElem::numbering)
322                    } else if func == EquationElem::ELEM {
323                        styles.get_cloned(EquationElem::numbering)
324                    } else if func == FootnoteElem::ELEM {
325                        Some(styles.get_cloned(FootnoteElem::numbering))
326                    } else {
327                        None
328                    }
329                }),
330            _ => None,
331        }
332    }
333}
334
335#[scope]
336impl Counter {
337    /// Create a new counter identified by a key.
338    #[func(constructor)]
339    pub fn construct(
340        /// The key that identifies this counter globally.
341        ///
342        /// - If it is a string, creates a custom counter that is only affected
343        ///   by manual updates,
344        /// - If it is the @page function, counts through pages,
345        /// - If it is a @selector[selector], counts through elements that match
346        ///   the selector. For example,
347        ///   - provide an element function: counts elements of that type,
348        ///   - provide a @function.where[`where`] selector: counts a type of
349        ///     element with specific fields,
350        ///   - provide a @label[`{<label>}`]: counts elements with that label.
351        key: CounterKey,
352    ) -> Counter {
353        Self::new(key)
354    }
355
356    /// Retrieves the value of the counter at the current location. Always
357    /// returns an array of integers, even if the counter has just one number.
358    ///
359    /// This is equivalent to `{counter.at(here())}`.
360    #[func(contextual)]
361    pub fn get(
362        &self,
363        engine: &mut Engine,
364        context: Tracked<Context>,
365        span: Span,
366    ) -> SourceResult<CounterState> {
367        let loc = context.location().at(span)?;
368        engine.introspect(CounterAtIntrospection(self.clone(), loc, span))
369    }
370
371    /// Displays the value of the counter.
372    ///
373    /// You can provide both a custom numbering and a custom location. Both
374    /// default to `{auto}`, selecting sensible defaults (the numbering of the
375    /// counted element and the current location, respectively).
376    ///
377    /// Returns the formatted output.
378    #[func(contextual)]
379    pub fn display(
380        self,
381        engine: &mut Engine,
382        context: Tracked<Context>,
383        span: Span,
384        /// A @numbering[numbering pattern or a function], which specifies how
385        /// to display the counter. If given a function, that function receives
386        /// each number of the counter as a separate argument. If the amount of
387        /// numbers varies, e.g. for the heading argument, you can use an
388        /// @arguments[argument sink].
389        ///
390        /// If this is omitted or set to `{auto}`, displays the counter with the
391        /// numbering style for the counted element or with the pattern
392        /// `{"1.1"}` if no such style exists.
393        #[default]
394        numbering: Smart<Numbering>,
395        /// The place at which the counter should be displayed.
396        ///
397        /// If a selector is used, it must match exactly one element in the
398        /// document. The most useful kinds of selectors for this are
399        /// @label[labels] and @location[locations].
400        ///
401        /// If this is omitted or set to `{auto}`, this displays the counter at
402        /// the current location. This is equivalent to using @here[`{here()}`].
403        ///
404        /// The numbering will be executed with a context in which `{here()}`
405        /// resolves to the provided location, so that numberings which involve
406        /// further counters resolve correctly.
407        #[named]
408        #[default]
409        at: Smart<LocatableSelector>,
410        /// If enabled, displays the current and final top-level count together.
411        /// Both can be styled through a single numbering pattern. This is used
412        /// by the page numbering property to display the current and total
413        /// number of pages when a pattern like `{"1 / 1"}` is given.
414        #[named]
415        #[default(false)]
416        both: bool,
417    ) -> SourceResult<Value> {
418        let location = match at {
419            Smart::Auto => context.location().at(span)?,
420            Smart::Custom(ref selector) => {
421                selector.resolve_unique(engine, context, span)?
422            }
423        };
424        let state = if both {
425            engine.introspect(CounterBothIntrospection(self.clone(), location, span))?
426        } else {
427            engine.introspect(CounterAtIntrospection(self.clone(), location, span))?
428        };
429
430        let numbering = numbering
431            .custom()
432            .or_else(|| {
433                self.matching_numbering(engine, context.styles().ok()?, location, span)
434            })
435            .unwrap_or_else(|| NumberingPattern::from_str("1.1").unwrap().into());
436
437        if at.is_custom() {
438            let context = Context::new(Some(location), context.styles().ok());
439            state.display(engine, context.track(), span, &numbering)
440        } else {
441            state.display(engine, context, span, &numbering)
442        }
443    }
444
445    /// Retrieves the value of the counter at the given location. Always returns
446    /// an array of integers, even if the counter has just one number.
447    ///
448    /// The `selector` must match exactly one element in the document. The most
449    /// useful kinds of selectors for this are @label[labels] and
450    /// @location[locations].
451    #[func(contextual)]
452    pub fn at(
453        &self,
454        engine: &mut Engine,
455        context: Tracked<Context>,
456        span: Span,
457        /// The place at which the counter's value should be retrieved.
458        selector: LocatableSelector,
459    ) -> SourceResult<CounterState> {
460        let loc = selector.resolve_unique(engine, context, span)?;
461        engine.introspect(CounterAtIntrospection(self.clone(), loc, span))
462    }
463
464    /// Retrieves the value of the counter at the end of the document. Always
465    /// returns an array of integers, even if the counter has just one number.
466    #[func(contextual)]
467    pub fn final_(
468        &self,
469        engine: &mut Engine,
470        context: Tracked<Context>,
471        span: Span,
472    ) -> SourceResult<CounterState> {
473        let loc = context.location().at(span)?;
474        engine.introspect(CounterFinalIntrospection(self.clone(), loc, span))
475    }
476
477    /// Increases the value of the counter by one.
478    ///
479    /// The update will be in effect at the position where the returned content
480    /// is inserted into the document. If you don't put the output into the
481    /// document, nothing happens! This would be the case, for example, if you
482    /// write `{let _ = counter(page).step()}`. Counter updates are always
483    /// applied in layout order and in that case, Typst wouldn't know when to
484    /// step the counter.
485    #[func]
486    pub fn step(
487        self,
488        span: Span,
489        /// The depth at which to step the counter. Defaults to `{1}`.
490        #[named]
491        #[default(NonZeroUsize::ONE)]
492        level: NonZeroUsize,
493    ) -> Content {
494        self.update(span, CounterUpdate::Step(level))
495    }
496
497    /// Updates the value of the counter.
498    ///
499    /// Just like with `step`, the update only occurs if you put the resulting
500    /// content into the document.
501    #[func]
502    pub fn update(
503        self,
504        span: Span,
505        /// If given an integer or array of integers, sets the counter to that
506        /// value. If given a function, that function receives the previous
507        /// counter value (with each number as a separate argument) and has to
508        /// return the new value (integer or array).
509        update: CounterUpdate,
510    ) -> Content {
511        CounterUpdateElem::new(self.0, update).pack().spanned(span)
512    }
513}
514
515impl Repr for Counter {
516    fn repr(&self) -> EcoString {
517        eco_format!("counter({})", self.0.repr())
518    }
519}
520
521/// Identifies a counter.
522#[derive(Debug, Clone, PartialEq, Hash)]
523pub enum CounterKey {
524    /// The page counter.
525    Page,
526    /// Counts elements matching the given selectors. Only works for
527    /// [locatable]($location/#locatable)
528    /// elements or labels.
529    Selector(Selector),
530    /// Counts through manual counters with the same key.
531    Str(Str),
532}
533
534cast! {
535    CounterKey,
536    self => match self {
537        Self::Page => PageElem::ELEM.into_value(),
538        Self::Selector(v) => v.into_value(),
539        Self::Str(v) => v.into_value(),
540    },
541    v: Str => Self::Str(v),
542    v: Label => Self::Selector(Selector::Label(v)),
543    v: Element => {
544        if v == PageElem::ELEM {
545            Self::Page
546        } else {
547            Self::Selector(LocatableSelector::from_value(v.into_value())?.0)
548        }
549    },
550    v: LocatableSelector => Self::Selector(v.0),
551}
552
553impl Repr for CounterKey {
554    fn repr(&self) -> EcoString {
555        match self {
556            Self::Page => "page".into(),
557            Self::Selector(selector) => selector.repr(),
558            Self::Str(str) => str.repr(),
559        }
560    }
561}
562
563/// An update to perform on a counter.
564#[derive(Debug, Clone, PartialEq, Hash)]
565pub enum CounterUpdate {
566    /// Set the counter to the specified state.
567    Set(CounterState),
568    /// Increase the number for the given level by one.
569    Step(NonZeroUsize),
570    /// Apply the given function to the counter's state.
571    Func(Func),
572}
573
574cast! {
575    CounterUpdate,
576    v: CounterState => Self::Set(v),
577    v: Func => Self::Func(v),
578}
579
580/// Elements that have special counting behaviour.
581pub trait Count {
582    /// Get the counter update for this element.
583    fn update(&self) -> Option<CounterUpdate>;
584}
585
586/// Counts through elements with different levels.
587#[derive(Debug, Clone, PartialEq, Hash)]
588pub struct CounterState(pub SmallVec<[u64; 3]>);
589
590impl CounterState {
591    /// Get the initial counter state for the key.
592    pub fn init(page: bool) -> Self {
593        // Special case, because pages always start at one.
594        Self(smallvec![u64::from(page)])
595    }
596
597    /// Advance the counter and return the numbers for the given heading.
598    pub fn update(
599        &mut self,
600        engine: &mut Engine,
601        update: CounterUpdate,
602    ) -> SourceResult<()> {
603        match update {
604            CounterUpdate::Set(state) => *self = state,
605            CounterUpdate::Step(level) => self.step(level, 1),
606            CounterUpdate::Func(func) => {
607                *self = func
608                    .call(engine, Context::none().track(), self.0.iter().copied())?
609                    .cast()
610                    .at(func.span())?
611            }
612        }
613        Ok(())
614    }
615
616    /// Advance the number of the given level by the specified amount.
617    pub fn step(&mut self, level: NonZeroUsize, by: u64) {
618        let level = level.get();
619
620        while self.0.len() < level {
621            self.0.push(0);
622        }
623
624        self.0[level - 1] = self.0[level - 1].saturating_add(by);
625        self.0.truncate(level);
626    }
627
628    /// Get the first number of the state.
629    pub fn first(&self) -> u64 {
630        self.0.first().copied().unwrap_or(1)
631    }
632
633    /// Display the counter state with a numbering.
634    pub fn display(
635        &self,
636        engine: &mut Engine,
637        context: Tracked<Context>,
638        span: Span,
639        numbering: &Numbering,
640    ) -> SourceResult<Value> {
641        numbering.apply(engine, context, span, &self.0)
642    }
643}
644
645cast! {
646    CounterState,
647    self => Value::Array(self.0.into_iter().map(IntoValue::into_value).collect()),
648    num: u64 => Self(smallvec![num]),
649    array: Array => Self(array
650        .into_iter()
651        .map(Value::cast)
652        .collect::<HintedStrResult<_>>()?),
653}
654
655/// Executes an update of a counter.
656#[elem(Construct, Locatable, Count)]
657pub struct CounterUpdateElem {
658    /// The key that identifies the counter.
659    #[required]
660    key: CounterKey,
661
662    /// The update to perform on the counter.
663    #[required]
664    #[internal]
665    update: CounterUpdate,
666}
667
668impl Construct for CounterUpdateElem {
669    fn construct(_: &mut Engine, args: &mut Args) -> SourceResult<Content> {
670        bail!(args.span, "cannot be constructed manually");
671    }
672}
673
674impl Count for Packed<CounterUpdateElem> {
675    fn update(&self) -> Option<CounterUpdate> {
676        Some(self.update.clone())
677    }
678}
679
680/// Executes a display of a counter.
681#[elem(Construct, Unqueriable, Locatable)]
682pub struct CounterDisplayElem {
683    /// The counter.
684    #[required]
685    #[internal]
686    counter: Counter,
687
688    /// The numbering to display the counter with.
689    #[required]
690    #[internal]
691    numbering: Smart<Numbering>,
692
693    /// Whether to display both the current and final value.
694    #[required]
695    #[internal]
696    both: bool,
697}
698
699impl Construct for CounterDisplayElem {
700    fn construct(_: &mut Engine, args: &mut Args) -> SourceResult<Content> {
701        bail!(args.span, "cannot be constructed manually");
702    }
703}
704
705pub const COUNTER_DISPLAY_RULE: ShowFn<CounterDisplayElem> = |elem, engine, styles| {
706    Ok(elem
707        .counter
708        .clone()
709        .display(
710            engine,
711            Context::new(elem.location(), Some(styles)).track(),
712            elem.span(),
713            elem.numbering.clone(),
714            Smart::Auto,
715            elem.both,
716        )?
717        .display())
718};
719
720/// A specialized handler of the page counter that tracks both the physical
721/// and the logical page counter.
722#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
723pub struct ManualPageCounter {
724    physical: NonZeroUsize,
725    logical: u64,
726}
727
728impl ManualPageCounter {
729    /// Create a new fast page counter, starting at 1.
730    pub fn new() -> Self {
731        Self { physical: NonZeroUsize::ONE, logical: 1 }
732    }
733
734    /// Get the current physical page counter state.
735    pub fn physical(&self) -> NonZeroUsize {
736        self.physical
737    }
738
739    /// Get the current logical page counter state.
740    pub fn logical(&self) -> u64 {
741        self.logical
742    }
743
744    /// Advance past a page.
745    pub fn visit(&mut self, engine: &mut Engine, page: &Frame) -> SourceResult<()> {
746        for (_, item) in page.items() {
747            match item {
748                FrameItem::Group(group) => self.visit(engine, &group.frame)?,
749                FrameItem::Tag(Tag::Start(elem, _)) => {
750                    let Some(elem) = elem.to_packed::<CounterUpdateElem>() else {
751                        continue;
752                    };
753                    if elem.key == CounterKey::Page {
754                        let mut state = CounterState(smallvec![self.logical]);
755                        state.update(engine, elem.update.clone())?;
756                        self.logical = state.first();
757                    }
758                }
759                _ => {}
760            }
761        }
762
763        Ok(())
764    }
765
766    /// Step past a page _boundary._
767    pub fn step(&mut self) {
768        self.physical = self.physical.saturating_add(1);
769        self.logical += 1;
770    }
771}
772
773impl Default for ManualPageCounter {
774    fn default() -> Self {
775        Self::new()
776    }
777}
778
779/// Retrieves a counter at a specific location.
780#[derive(Debug, Clone, PartialEq, Hash)]
781struct CounterAtIntrospection(Counter, Location, Span);
782
783impl Introspect for CounterAtIntrospection {
784    type Output = SourceResult<CounterState>;
785
786    fn introspect(
787        &self,
788        engine: &mut Engine,
789        introspector: Tracked<dyn Introspector + '_>,
790    ) -> Self::Output {
791        let Self(counter, loc, _) = self;
792        let selector = counter.select(introspector, *loc);
793        let sequence = sequence(counter, &selector, engine, introspector)?;
794        let offset = introspector.query_count_before(&selector, *loc);
795        let (mut state, page) = sequence[offset].clone();
796        if counter.is_page() {
797            let delta = introspector
798                .page(*loc)
799                .unwrap_or(NonZeroUsize::ONE)
800                .get()
801                .saturating_sub(page.get());
802            state.step(NonZeroUsize::ONE, delta as u64);
803        }
804        Ok(state)
805    }
806
807    fn diagnose(&self, history: &History<Self::Output>) -> SourceDiagnostic {
808        format_convergence_warning(&self.0, self.2, history)
809    }
810}
811
812/// Retrieves the first number of a counter at a specific location and the first
813/// number of the final state, both in one operation.
814#[derive(Debug, Clone, PartialEq, Hash)]
815struct CounterBothIntrospection(Counter, Location, Span);
816
817impl Introspect for CounterBothIntrospection {
818    type Output = SourceResult<CounterState>;
819
820    fn introspect(
821        &self,
822        engine: &mut Engine,
823        introspector: Tracked<dyn Introspector + '_>,
824    ) -> Self::Output {
825        let Self(counter, loc, _) = self;
826        let selector = counter.select(introspector, *loc);
827        let sequence = sequence(counter, &selector, engine, introspector)?;
828        let offset = introspector.query_count_before(&selector, *loc);
829        let (mut at_state, at_page) = sequence[offset].clone();
830        let (mut final_state, final_page) = sequence.last().unwrap().clone();
831        if counter.is_page() {
832            let at_delta = introspector
833                .page(*loc)
834                .unwrap_or(NonZeroUsize::ONE)
835                .get()
836                .saturating_sub(at_page.get());
837            at_state.step(NonZeroUsize::ONE, at_delta as u64);
838            let final_delta = introspector
839                .pages(*loc)
840                .unwrap_or(NonZeroUsize::ONE)
841                .get()
842                .saturating_sub(final_page.get());
843            final_state.step(NonZeroUsize::ONE, final_delta as u64);
844        }
845        Ok(CounterState(smallvec![at_state.first(), final_state.first()]))
846    }
847
848    fn diagnose(&self, history: &History<Self::Output>) -> SourceDiagnostic {
849        format_convergence_warning(&self.0, self.2, history)
850    }
851}
852
853/// Retrieves the final state of a counter.
854#[derive(Debug, Clone, PartialEq, Hash)]
855struct CounterFinalIntrospection(Counter, Location, Span);
856
857impl Introspect for CounterFinalIntrospection {
858    type Output = SourceResult<CounterState>;
859
860    fn introspect(
861        &self,
862        engine: &mut Engine,
863        introspector: Tracked<dyn Introspector + '_>,
864    ) -> Self::Output {
865        let Self(counter, loc, _) = self;
866        let selector = counter.select(introspector, *loc);
867        let sequence = sequence(counter, &selector, engine, introspector)?;
868        let (mut state, page) = sequence.last().unwrap().clone();
869        if counter.is_page() {
870            let delta = introspector
871                .pages(*loc)
872                .unwrap_or(NonZeroUsize::ONE)
873                .get()
874                .saturating_sub(page.get());
875            state.step(NonZeroUsize::ONE, delta as u64);
876        }
877        Ok(state)
878    }
879
880    fn diagnose(&self, history: &History<Self::Output>) -> SourceDiagnostic {
881        format_convergence_warning(&self.0, self.2, history)
882    }
883}
884
885/// Produces the whole sequence of a counter.
886///
887/// Due to memoization, this has to happen just once for all retrievals of the
888/// same counter, cutting down the number of computations from quadratic to
889/// linear.
890fn sequence(
891    counter: &Counter,
892    selector: &Selector,
893    engine: &mut Engine,
894    introspector: Tracked<dyn Introspector + '_>,
895) -> SourceResult<EcoVec<(CounterState, NonZeroUsize)>> {
896    sequence_impl(
897        counter,
898        selector,
899        engine.world,
900        engine.library,
901        introspector,
902        engine.traced,
903        TrackedMut::reborrow_mut(&mut engine.sink),
904        engine.route.track(),
905    )
906}
907
908/// Memoized implementation of `sequence`.
909#[comemo::memoize]
910#[allow(clippy::too_many_arguments)]
911fn sequence_impl(
912    counter: &Counter,
913    selector: &Selector,
914    world: Tracked<dyn World + '_>,
915    library: &LazyHash<Library>,
916    introspector: Tracked<dyn Introspector + '_>,
917    traced: Tracked<Traced>,
918    sink: TrackedMut<Sink>,
919    route: Tracked<Route>,
920) -> SourceResult<EcoVec<(CounterState, NonZeroUsize)>> {
921    let mut engine = Engine {
922        library,
923        world,
924        introspector: Protected::from_raw(introspector),
925        traced,
926        sink,
927        route: Route::extend(route).unnested(),
928    };
929
930    let mut current = CounterState::init(matches!(counter.0, CounterKey::Page));
931    let mut page = NonZeroUsize::ONE;
932    let mut stops = eco_vec![(current.clone(), page)];
933
934    for elem in introspector.query(selector) {
935        if counter.is_page() {
936            let prev = page;
937            page = introspector
938                .page(elem.location().unwrap())
939                .unwrap_or(NonZeroUsize::ONE);
940
941            let delta = page.get() - prev.get();
942            if delta > 0 {
943                current.step(NonZeroUsize::ONE, delta as u64);
944            }
945        }
946
947        if let Some(update) = match elem.with::<dyn Count>() {
948            Some(countable) => countable.update(),
949            None => Some(CounterUpdate::Step(NonZeroUsize::ONE)),
950        } {
951            current.update(&mut engine, update)?;
952        }
953
954        stops.push((current.clone(), page));
955    }
956
957    Ok(stops)
958}
959
960/// The warning when a counter failed to converge.
961fn format_convergence_warning(
962    counter: &Counter,
963    span: Span,
964    history: &History<SourceResult<CounterState>>,
965) -> SourceDiagnostic {
966    warning!(span, "value of {} did not converge", format_key(counter)).with_hint(
967        history.hint("values", |ret| match ret {
968            Ok(v) => format_counter_state(v),
969            Err(_) => "(errored)".into(),
970        }),
971    )
972}
973
974/// Formats a counter's key human-readably.
975fn format_key(counter: &Counter) -> EcoString {
976    match counter.0 {
977        CounterKey::Page => "the page counter".into(),
978        _ => eco_format!("`{}`", counter.repr()),
979    }
980}
981
982/// Formats a counter's state human-readably.
983fn format_counter_state(state: &CounterState) -> EcoString {
984    let mut output = EcoString::new();
985    let mut sep = "";
986    for value in state.0.as_slice() {
987        write!(output, "{sep}{value}").unwrap();
988        sep = ", ";
989    }
990    output
991}