typst_library/introspection/
state.rs

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