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