typst_library/introspection/
state.rs

1use comemo::{Track, Tracked, TrackedMut};
2use ecow::{EcoString, EcoVec, eco_format, eco_vec};
3use typst_syntax::Span;
4
5use crate::World;
6use crate::diag::{At, SourceResult, bail};
7use crate::engine::{Engine, Route, Sink, Traced};
8use crate::foundations::{
9    Args, Construct, Content, Context, Func, LocatableSelector, NativeElement, Repr,
10    Selector, Str, Value, cast, elem, func, scope, select_where, ty,
11};
12use crate::introspection::{Introspector, Locatable, Location};
13use crate::routines::Routines;
14
15/// Manages stateful parts of your document.
16///
17/// Let's say you have some computations in your document and want to remember
18/// the result of your last computation to use it in the next one. You might try
19/// something similar to the code below and expect it to output 10, 13, 26, and
20/// 21. However this **does not work** in Typst. If you test this code, you will
21/// see that Typst complains with the following error message: _Variables from
22/// outside the function are read-only and cannot be modified._
23///
24/// ```typ
25/// // This doesn't work!
26/// #let star = 0
27/// #let compute(expr) = {
28///   star = eval(
29///     expr.replace("⭐", str(star))
30///   )
31///   [New value is #star.]
32/// }
33///
34/// #compute("10") \
35/// #compute("⭐ + 3") \
36/// #compute("⭐ * 2") \
37/// #compute("⭐ - 5")
38/// ```
39///
40/// # State and document markup { #state-and-markup }
41/// Why does it do that? Because, in general, this kind of computation with side
42/// effects is problematic in document markup and Typst is upfront about that.
43/// For the results to make sense, the computation must proceed in the same
44/// order in which the results will be laid out in the document. In our simple
45/// example, that's the case, but in general it might not be.
46///
47/// Let's look at a slightly different, but similar kind of state: The heading
48/// numbering. We want to increase the heading counter at each heading. Easy
49/// enough, right? Just add one. Well, it's not that simple. Consider the
50/// following example:
51///
52/// ```example
53/// #set heading(numbering: "1.")
54/// #let template(body) = [
55///   = Outline
56///   ...
57///   #body
58/// ]
59///
60/// #show: template
61///
62/// = Introduction
63/// ...
64/// ```
65///
66/// Here, Typst first processes the body of the document after the show rule,
67/// sees the `Introduction` heading, then passes the resulting content to the
68/// `template` function and only then sees the `Outline`. Just counting up would
69/// number the `Introduction` with `1` and the `Outline` with `2`.
70///
71/// # Managing state in Typst { #state-in-typst }
72/// So what do we do instead? We use Typst's state management system. Calling
73/// the `state` function with an identifying string key and an optional initial
74/// value gives you a state value which exposes a few functions. The two most
75/// important ones are `get` and `update`:
76///
77/// - The [`get`]($state.get) function retrieves the current value of the state.
78///   Because the value can vary over the course of the document, it is a
79///   _contextual_ function that can only be used when [context]($context) is
80///   available.
81///
82/// - The [`update`]($state.update) function modifies the state. You can give it
83///   any value. If given a non-function value, it sets the state to that value.
84///   If given a function, that function receives the previous state and has to
85///   return the new state.
86///
87/// Our initial example would now look like this:
88///
89/// ```example
90/// #let star = state("star", 0)
91/// #let compute(expr) = {
92///   star.update(old =>
93///     eval(expr.replace("⭐", str(old)))
94///   )
95///   [New value is #context star.get().]
96/// }
97///
98/// #compute("10") \
99/// #compute("⭐ + 3") \
100/// #compute("⭐ * 2") \
101/// #compute("⭐ - 5")
102/// ```
103///
104/// State managed by Typst is always updated in layout order, not in evaluation
105/// order. The `update` method returns content and its effect occurs at the
106/// position where the returned content is inserted into the document.
107///
108/// As a result, we can now also store some of the computations in variables,
109/// but they still show the correct results:
110///
111/// ```example
112/// >>> #let star = state("star", 0)
113/// >>> #let compute(expr) = {
114/// >>>   star.update(old =>
115/// >>>     eval(expr.replace("⭐", str(old)))
116/// >>>   )
117/// >>>   [New value is #context star.get().]
118/// >>> }
119/// <<< ...
120///
121/// #let more = [
122///   #compute("⭐ * 2") \
123///   #compute("⭐ - 5")
124/// ]
125///
126/// #compute("10") \
127/// #compute("⭐ + 3") \
128/// #more
129/// ```
130///
131/// This example is of course a bit silly, but in practice this is often exactly
132/// what you want! A good example are heading counters, which is why Typst's
133/// [counting system]($counter) is very similar to its state system.
134///
135/// # Time Travel
136/// By using Typst's state management system you also get time travel
137/// capabilities! We can find out what the value of the state will be at any
138/// position in the document from anywhere else. In particular, the `at` method
139/// gives us the value of the state at any particular location and the `final`
140/// methods gives us the value of the state at the end of the document.
141///
142/// ```example
143/// >>> #let star = state("star", 0)
144/// >>> #let compute(expr) = {
145/// >>>   star.update(old =>
146/// >>>     eval(expr.replace("⭐", str(old)))
147/// >>>   )
148/// >>>   [New value is #context star.get().]
149/// >>> }
150/// <<< ...
151///
152/// Value at `<here>` is
153/// #context star.at(<here>)
154///
155/// #compute("10") \
156/// #compute("⭐ + 3") \
157/// *Here.* <here> \
158/// #compute("⭐ * 2") \
159/// #compute("⭐ - 5")
160/// ```
161///
162/// # A word of caution { #caution }
163/// To resolve the values of all states, Typst evaluates parts of your code
164/// multiple times. However, there is no guarantee that your state manipulation
165/// can actually be completely resolved.
166///
167/// For instance, if you generate state updates depending on the final value of
168/// a state, the results might never converge. The example below illustrates
169/// this. We initialize our state with `1` and then update it to its own final
170/// value plus 1. So it should be `2`, but then its final value is `2`, so it
171/// should be `3`, and so on. This example displays a finite value because Typst
172/// simply gives up after a few attempts.
173///
174/// ```example
175/// // This is bad!
176/// #let x = state("key", 1)
177/// #context x.update(x.final() + 1)
178/// #context x.get()
179/// ```
180///
181/// In general, you should try not to generate state updates from within context
182/// expressions. If possible, try to express your updates as non-contextual
183/// values or functions that compute the new value from the previous value.
184/// Sometimes, it cannot be helped, but in those cases it is up to you to ensure
185/// that the result converges.
186#[ty(scope)]
187#[derive(Debug, Clone, PartialEq, Hash)]
188pub struct State {
189    /// The key that identifies the state.
190    key: Str,
191    /// The initial value of the state.
192    init: Value,
193}
194
195impl State {
196    /// Create a new state identified by a key.
197    pub fn new(key: Str, init: Value) -> State {
198        Self { key, init }
199    }
200
201    /// Get the value of the state at the given location.
202    pub fn at_loc(&self, engine: &mut Engine, loc: Location) -> SourceResult<Value> {
203        let sequence = self.sequence(engine)?;
204        let offset = engine.introspector.query_count_before(&self.selector(), loc);
205        Ok(sequence[offset].clone())
206    }
207
208    /// Produce the whole sequence of states.
209    ///
210    /// This has to happen just once for all states, cutting down the number
211    /// of state updates from quadratic to linear.
212    fn sequence(&self, engine: &mut Engine) -> SourceResult<EcoVec<Value>> {
213        self.sequence_impl(
214            engine.routines,
215            engine.world,
216            engine.introspector,
217            engine.traced,
218            TrackedMut::reborrow_mut(&mut engine.sink),
219            engine.route.track(),
220        )
221    }
222
223    /// Memoized implementation of `sequence`.
224    #[comemo::memoize]
225    fn sequence_impl(
226        &self,
227        routines: &Routines,
228        world: Tracked<dyn World + '_>,
229        introspector: Tracked<Introspector>,
230        traced: Tracked<Traced>,
231        sink: TrackedMut<Sink>,
232        route: Tracked<Route>,
233    ) -> SourceResult<EcoVec<Value>> {
234        let mut engine = Engine {
235            routines,
236            world,
237            introspector,
238            traced,
239            sink,
240            route: Route::extend(route).unnested(),
241        };
242        let mut state = self.init.clone();
243        let mut stops = eco_vec![state.clone()];
244
245        for elem in introspector.query(&self.selector()) {
246            let elem = elem.to_packed::<StateUpdateElem>().unwrap();
247            match &elem.update {
248                StateUpdate::Set(value) => state = value.clone(),
249                StateUpdate::Func(func) => {
250                    state = func.call(&mut engine, Context::none().track(), [state])?
251                }
252            }
253            stops.push(state.clone());
254        }
255
256        Ok(stops)
257    }
258
259    /// The selector for this state's updates.
260    fn selector(&self) -> Selector {
261        select_where!(StateUpdateElem, key => self.key.clone())
262    }
263
264    /// Selects all state updates.
265    pub fn select_any() -> Selector {
266        StateUpdateElem::ELEM.select()
267    }
268}
269
270#[scope]
271impl State {
272    /// Create a new state identified by a key.
273    #[func(constructor)]
274    pub fn construct(
275        /// The key that identifies this state.
276        ///
277        /// Any [updates]($state.update) to the state will be identified with
278        /// the string key. If you construct multiple states with the same
279        /// `key`, then updating any one will affect all of them.
280        key: Str,
281        /// The initial value of the state.
282        ///
283        /// If you construct multiple states with the same `key` but different
284        /// `init` values, they will each use their own initial value but share
285        /// updates. Specifically, the value of a state at some location in the
286        /// document will be computed from that state's initial value and all
287        /// preceding updates for the state's key.
288        ///
289        /// ```example
290        /// #let banana = state("key", "🍌")
291        /// #let broccoli = state("key", "🥦")
292        ///
293        /// #banana.update(it => it + "😋")
294        ///
295        /// #context [
296        ///   - #state("key", "🍎").get()
297        ///   - #banana.get()
298        ///   - #broccoli.get()
299        /// ]
300        /// ```
301        #[default]
302        init: Value,
303    ) -> State {
304        Self::new(key, init)
305    }
306
307    /// Retrieves the value of the state at the current location.
308    ///
309    /// This is equivalent to `{state.at(here())}`.
310    #[typst_macros::time(name = "state.get", span = span)]
311    #[func(contextual)]
312    pub fn get(
313        &self,
314        engine: &mut Engine,
315        context: Tracked<Context>,
316        span: Span,
317    ) -> SourceResult<Value> {
318        let loc = context.location().at(span)?;
319        self.at_loc(engine, loc)
320    }
321
322    /// Retrieves the value of the state at the given selector's unique match.
323    ///
324    /// The `selector` must match exactly one element in the document. The most
325    /// useful kinds of selectors for this are [labels]($label) and
326    /// [locations]($location).
327    #[typst_macros::time(name = "state.at", span = span)]
328    #[func(contextual)]
329    pub fn at(
330        &self,
331        engine: &mut Engine,
332        context: Tracked<Context>,
333        span: Span,
334        /// The place at which the state's value should be retrieved.
335        selector: LocatableSelector,
336    ) -> SourceResult<Value> {
337        let loc = selector.resolve_unique(engine.introspector, context).at(span)?;
338        self.at_loc(engine, loc)
339    }
340
341    /// Retrieves the value of the state at the end of the document.
342    #[func(contextual)]
343    pub fn final_(
344        &self,
345        engine: &mut Engine,
346        context: Tracked<Context>,
347        span: Span,
348    ) -> SourceResult<Value> {
349        context.introspect().at(span)?;
350        let sequence = self.sequence(engine)?;
351        Ok(sequence.last().unwrap().clone())
352    }
353
354    /// Updates the value of the state.
355    ///
356    /// The update will be in effect at the position where the returned content
357    /// is inserted into the document. If you don't put the output into the
358    /// document, nothing happens! This would be the case, for example, if you
359    /// write `{let _ = state("key").update(7)}`. State updates are always
360    /// applied in layout order and in that case, Typst wouldn't know when to
361    /// update the state.
362    ///
363    /// In contrast to [`get`]($state.get), [`at`]($state.at), and
364    /// [`final`]($state.final), this function does not require [context].
365    #[func]
366    pub fn update(
367        self,
368        span: Span,
369        /// A value to update to or a function to update with.
370        ///
371        /// - If given a non-function value, sets the state to that value.
372        /// - If given a function, that function receives the state's previous
373        ///   value and has to return the state's new value.
374        ///
375        /// When updating the state based on its previous value, you should
376        /// prefer the function form instead of retrieving the previous value
377        /// from the [context]($context). This allows the compiler to resolve
378        /// the final state efficiently, minimizing the number of
379        /// [layout iterations]($context/#compiler-iterations) required.
380        ///
381        /// In the following example, `{fill.update(f => not f)}` will paint odd
382        /// [items in the bullet list]($list.item) as expected. However, if it's
383        /// replaced with `{context fill.update(not fill.get())}`, then layout
384        /// will not converge within 5 attempts, as each update will take one
385        /// additional iteration to propagate.
386        ///
387        /// ```example
388        /// #let fill = state("fill", false)
389        ///
390        /// #show list.item: it => {
391        ///   fill.update(f => not f)
392        ///   context {
393        ///     set text(fill: fuchsia) if fill.get()
394        ///     it
395        ///   }
396        /// }
397        ///
398        /// #lorem(5).split().map(list.item).join()
399        /// ```
400        update: StateUpdate,
401    ) -> Content {
402        StateUpdateElem::new(self.key, update).pack().spanned(span)
403    }
404}
405
406impl Repr for State {
407    fn repr(&self) -> EcoString {
408        eco_format!("state({}, {})", self.key.repr(), self.init.repr())
409    }
410}
411
412/// An update to perform on a state.
413#[derive(Debug, Clone, PartialEq, Hash)]
414pub enum StateUpdate {
415    /// Set the state to the specified value.
416    Set(Value),
417    /// Apply the given function to the state.
418    Func(Func),
419}
420
421cast! {
422    StateUpdate,
423    v: Func => Self::Func(v),
424    v: Value => Self::Set(v),
425}
426
427/// Executes a display of a state.
428#[elem(Construct, Locatable)]
429pub struct StateUpdateElem {
430    /// The key that identifies the state.
431    #[required]
432    key: Str,
433
434    /// The update to perform on the state.
435    #[required]
436    #[internal]
437    update: StateUpdate,
438}
439
440impl Construct for StateUpdateElem {
441    fn construct(_: &mut Engine, args: &mut Args) -> SourceResult<Content> {
442        bail!(args.span, "cannot be constructed manually");
443    }
444}