diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-07-02 19:59:52 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-07-02 20:07:43 +0200 |
| commit | ebfdb1dafa430786db10dad2ef7d5467c1bdbed1 (patch) | |
| tree | 2bbc24ddb4124c4bb14dec0e536129d4de37b056 /library/src/meta/state.rs | |
| parent | 3ab19185093d7709f824b95b979060ce125389d8 (diff) | |
Move everything into `crates/` directory
Diffstat (limited to 'library/src/meta/state.rs')
| -rw-r--r-- | library/src/meta/state.rs | 440 |
1 files changed, 0 insertions, 440 deletions
diff --git a/library/src/meta/state.rs b/library/src/meta/state.rs deleted file mode 100644 index aee53a29..00000000 --- a/library/src/meta/state.rs +++ /dev/null @@ -1,440 +0,0 @@ -use std::fmt::{self, Debug, Formatter, Write}; - -use ecow::{eco_vec, EcoVec}; -use typst::eval::Tracer; -use typst::model::DelayedErrors; - -use crate::prelude::*; - -/// Manages stateful parts of your document. -/// -/// Let's say you have some computations in your document and want to remember -/// the result of your last computation to use it in the next one. You might try -/// something similar to the code below and expect it to output 10, 13, 26, and -/// 21. However this **does not work** in Typst. If you test this code, you will -/// see that Typst complains with the following error message: _Variables from -/// outside the function are read-only and cannot be modified._ -/// -/// ```typ -/// #let x = 0 -/// #let compute(expr) = { -/// x = eval( -/// expr.replace("x", str(x)) -/// ) -/// [New value is #x. ] -/// } -/// -/// #compute("10") \ -/// #compute("x + 3") \ -/// #compute("x * 2") \ -/// #compute("x - 5") -/// ``` -/// -/// ## State and document markup { #state-and-markup } -/// Why does it do that? Because, in general, this kind of computation with side -/// effects is problematic in document markup and Typst is upfront about that. -/// For the results to make sense, the computation must proceed in the same -/// order in which the results will be laid out in the document. In our simple -/// example, that's the case, but in general it might not be. -/// -/// Let's look at a slightly different, but similar kind of state: The heading -/// numbering. We want to increase the heading counter at each heading. Easy -/// enough, right? Just add one. Well, it's not that simple. Consider the -/// following example: -/// -/// ```example -/// #set heading(numbering: "1.") -/// #let template(body) = [ -/// = Outline -/// ... -/// #body -/// ] -/// -/// #show: template -/// -/// = Introduction -/// ... -/// ``` -/// -/// Here, Typst first processes the body of the document after the show rule, -/// sees the `Introduction` heading, then passes the resulting content to the -/// `template` function and only then sees the `Outline`. Just counting up would -/// number the `Introduction` with `1` and the `Outline` with `2`. -/// -/// ## Managing state in Typst { #state-in-typst } -/// So what do we do instead? We use Typst's state management system. Calling -/// the `state` function with an identifying string key and an optional initial -/// value gives you a state value which exposes a few methods. The two most -/// important ones are `display` and `update`: -/// -/// - The `display` method shows the current value of the state. You can -/// optionally give it a function that receives the value and formats it in -/// some way. -/// -/// - The `update` method modifies the state. You can give it any value. If -/// given a non-function value, it sets the state to that value. If given a -/// function, that function receives the previous state and has to return the -/// new state. -/// -/// Our initial example would now look like this: -/// -/// ```example -/// #let s = state("x", 0) -/// #let compute(expr) = [ -/// #s.update(x => -/// eval(expr.replace("x", str(x))) -/// ) -/// New value is #s.display(). -/// ] -/// -/// #compute("10") \ -/// #compute("x + 3") \ -/// #compute("x * 2") \ -/// #compute("x - 5") -/// ``` -/// -/// State managed by Typst is always updated in layout order, not in evaluation -/// order. The `update` method returns content and its effect occurs at the -/// position where the returned content is inserted into the document. -/// -/// As a result, we can now also store some of the computations in -/// variables, but they still show the correct results: -/// -/// ```example -/// >>> #let s = state("x", 0) -/// >>> #let compute(expr) = [ -/// >>> #s.update(x => -/// >>> eval(expr.replace("x", str(x))) -/// >>> ) -/// >>> New value is #s.display(). -/// >>> ] -/// <<< ... -/// -/// #let more = [ -/// #compute("x * 2") \ -/// #compute("x - 5") -/// ] -/// -/// #compute("10") \ -/// #compute("x + 3") \ -/// #more -/// ``` -/// -/// This example is of course a bit silly, but in practice this is often exactly -/// what you want! A good example are heading counters, which is why Typst's -/// [counting system]($func/counter) is very similar to its state system. -/// -/// ## Time Travel { #time-travel } -/// By using Typst's state management system you also get time travel -/// capabilities! By combining the state system with [`locate`]($func/locate) -/// and [`query`]($func/query), we can find out what the value of the state will -/// be at any position in the document from anywhere else. In particular, the -/// `at` method gives us the value of the state at any location and the `final` -/// methods gives us the value of the state at the end of the document. -/// -/// ```example -/// >>> #let s = state("x", 0) -/// >>> #let compute(expr) = [ -/// >>> #s.update(x => { -/// >>> eval(expr.replace("x", str(x))) -/// >>> }) -/// >>> New value is #s.display(). -/// >>> ] -/// <<< ... -/// -/// Value at `<here>` is -/// #locate(loc => s.at( -/// query(<here>, loc) -/// .first() -/// .location() -/// )) -/// -/// #compute("10") \ -/// #compute("x + 3") \ -/// *Here.* <here> \ -/// #compute("x * 2") \ -/// #compute("x - 5") -/// ``` -/// -/// ## A word of caution { #caution } -/// To resolve the values of all states, Typst evaluates parts of your code -/// multiple times. However, there is no guarantee that your state manipulation -/// can actually be completely resolved. -/// -/// For instance, if you generate state updates depending on the final value of -/// a state, the results might never converge. The example below illustrates -/// this. We initialize our state with `1` and then update it to its own final -/// value plus 1. So it should be `2`, but then its final value is `2`, so it -/// should be `3`, and so on. This example display `4` because Typst simply -/// gives up after a few attempts. -/// -/// ```example -/// #let s = state("x", 1) -/// #locate(loc => { -/// s.update(s.final(loc) + 1) -/// }) -/// #s.display() -/// ``` -/// -/// In general, you should _typically_ not generate state updates from within -/// `locate` calls or `display` calls of state or counters. Instead, pass a -/// function to `update` that determines the value of the state based on its -/// previous value. -/// -/// ## Methods -/// ### display() -/// Displays the value of the state. -/// -/// - format: function (positional) -/// A function which receives the value of the state and can return arbitrary -/// content which is then displayed. If this is omitted, the value is directly -/// displayed. -/// -/// - returns: content -/// -/// ### update() -/// Updates the value of the state. -/// -/// The update will be in effect at the position where the returned content is -/// inserted into the document. If you don't put the output into the document, -/// nothing happens! This would be the case, for example, if you write -/// `{let _ = state("key").update(7)}`. State updates are always applied in -/// layout order and in that case, Typst wouldn't know when to update the state. -/// -/// - value: any or function (positional, required) -/// If given a non function-value, sets the state to that value. If given a -/// function, that function receives the previous state and has to return the -/// new state. -/// -/// - returns: content -/// -/// ### at() -/// Gets the value of the state at the given location. -/// -/// - location: location (positional, required) -/// The location at which the state's value should be retrieved. A suitable -/// location can be retrieved from [`locate`]($func/locate) or -/// [`query`]($func/query). -/// -/// - returns: any -/// -/// ### final() -/// Gets the value of the state at the end of the document. -/// -/// - location: location (positional, required) -/// Can be any location. Why is it required then? As noted before, Typst has -/// to evaluate parts of your code multiple times to determine the values of -/// all state. By only allowing this method within [`locate`]($func/locate) -/// calls, the amount of code that can depend on the method's result is -/// reduced. If you could call `final` directly at the top level of a module, -/// the evaluation of the whole module and its exports could depend on the -/// state's value. -/// -/// - returns: any -/// -/// Display: State -/// Category: meta -#[func] -pub fn state( - /// The key that identifies this state. - key: Str, - /// The initial value of the state. - #[default] - init: Value, -) -> State { - State { key, init } -} - -/// A state. -#[derive(Clone, PartialEq, Hash)] -pub struct State { - /// The key that identifies the state. - key: Str, - /// The initial value of the state. - init: Value, -} - -impl State { - /// Call a method on a state. - #[tracing::instrument(skip(vm))] - pub fn call_method( - self, - vm: &mut Vm, - method: &str, - mut args: Args, - span: Span, - ) -> SourceResult<Value> { - let value = match method { - "display" => self.display(args.eat()?).into_value(), - "at" => self.at(&mut vm.vt, args.expect("location")?)?, - "final" => self.final_(&mut vm.vt, args.expect("location")?)?, - "update" => self.update(args.expect("value or function")?).into_value(), - _ => bail!(span, "type state has no method `{}`", method), - }; - args.finish()?; - Ok(value) - } - - /// Display the current value of the state. - pub fn display(self, func: Option<Func>) -> Content { - DisplayElem::new(self, func).pack() - } - - /// Get the value of the state at the given location. - #[tracing::instrument(skip(self, vt))] - pub fn at(self, vt: &mut Vt, location: Location) -> SourceResult<Value> { - let sequence = self.sequence(vt)?; - let offset = vt.introspector.query(&self.selector().before(location, true)).len(); - Ok(sequence[offset].clone()) - } - - /// Get the value of the state at the final location. - #[tracing::instrument(skip(self, vt))] - pub fn final_(self, vt: &mut Vt, _: Location) -> SourceResult<Value> { - let sequence = self.sequence(vt)?; - Ok(sequence.last().unwrap().clone()) - } - - /// Produce content that performs a state update. - pub fn update(self, update: StateUpdate) -> Content { - UpdateElem::new(self, update).pack() - } - - /// Produce the whole sequence of states. - /// - /// This has to happen just once for all states, cutting down the number - /// of state updates from quadratic to linear. - fn sequence(&self, vt: &mut Vt) -> SourceResult<EcoVec<Value>> { - self.sequence_impl( - vt.world, - vt.introspector, - vt.locator.track(), - TrackedMut::reborrow_mut(&mut vt.delayed), - TrackedMut::reborrow_mut(&mut vt.tracer), - ) - } - - /// Memoized implementation of `sequence`. - #[comemo::memoize] - fn sequence_impl( - &self, - world: Tracked<dyn World + '_>, - introspector: Tracked<Introspector>, - locator: Tracked<Locator>, - delayed: TrackedMut<DelayedErrors>, - tracer: TrackedMut<Tracer>, - ) -> SourceResult<EcoVec<Value>> { - let mut locator = Locator::chained(locator); - let mut vt = Vt { - world, - introspector, - locator: &mut locator, - delayed, - tracer, - }; - let mut state = self.init.clone(); - let mut stops = eco_vec![state.clone()]; - - for elem in introspector.query(&self.selector()) { - let elem = elem.to::<UpdateElem>().unwrap(); - match elem.update() { - StateUpdate::Set(value) => state = value, - StateUpdate::Func(func) => state = func.call_vt(&mut vt, [state])?, - } - stops.push(state.clone()); - } - - Ok(stops) - } - - /// The selector for this state's updates. - fn selector(&self) -> Selector { - Selector::Elem(UpdateElem::func(), Some(dict! { "state" => self.clone() })) - } -} - -impl Debug for State { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_str("state(")?; - self.key.fmt(f)?; - f.write_str(", ")?; - self.init.fmt(f)?; - f.write_char(')') - } -} - -cast! { - type State: "state", -} - -/// An update to perform on a state. -#[derive(Clone, PartialEq, Hash)] -pub enum StateUpdate { - /// Set the state to the specified value. - Set(Value), - /// Apply the given function to the state. - Func(Func), -} - -impl Debug for StateUpdate { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad("..") - } -} - -cast! { - type StateUpdate: "state update", - v: Func => Self::Func(v), - v: Value => Self::Set(v), -} - -/// Executes a display of a state. -/// -/// Display: State -/// Category: special -#[element(Locatable, Show)] -struct DisplayElem { - /// The state. - #[required] - state: State, - - /// The function to display the state with. - #[required] - func: Option<Func>, -} - -impl Show for DisplayElem { - #[tracing::instrument(name = "DisplayElem::show", skip(self, vt))] - fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> { - Ok(vt.delayed(|vt| { - let location = self.0.location().unwrap(); - let value = self.state().at(vt, location)?; - Ok(match self.func() { - Some(func) => func.call_vt(vt, [value])?.display(), - None => value.display(), - }) - })) - } -} - -/// Executes a display of a state. -/// -/// Display: State -/// Category: special -#[element(Locatable, Show)] -struct UpdateElem { - /// The state. - #[required] - state: State, - - /// The update to perform on the state. - #[required] - update: StateUpdate, -} - -impl Show for UpdateElem { - #[tracing::instrument(name = "UpdateElem::show")] - fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> { - Ok(Content::empty()) - } -} |
