use comemo::{Track, Tracked, TrackedMut};
use ecow::{eco_format, eco_vec, EcoString, EcoVec};
use crate::diag::{bail, warning, At, SourceResult};
use crate::engine::{Engine, Route, Sink, Traced};
use crate::foundations::{
cast, elem, func, scope, select_where, ty, Args, Construct, Content, Context, Func,
LocatableSelector, NativeElement, Packed, Repr, Selector, Show, Str, StyleChain,
Value,
};
use crate::introspection::{Introspector, Locatable, Location};
use crate::syntax::Span;
use crate::World;
#[ty(scope)]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct State {
key: Str,
init: Value,
}
impl State {
pub fn new(key: Str, init: Value) -> State {
Self { key, init }
}
pub fn at_loc(&self, engine: &mut Engine, loc: Location) -> SourceResult<Value> {
let sequence = self.sequence(engine)?;
let offset = engine.introspector.query_count_before(&self.selector(), loc);
Ok(sequence[offset].clone())
}
fn sequence(&self, engine: &mut Engine) -> SourceResult<EcoVec<Value>> {
self.sequence_impl(
engine.world,
engine.introspector,
engine.traced,
TrackedMut::reborrow_mut(&mut engine.sink),
engine.route.track(),
)
}
#[comemo::memoize]
fn sequence_impl(
&self,
world: Tracked<dyn World + '_>,
introspector: Tracked<Introspector>,
traced: Tracked<Traced>,
sink: TrackedMut<Sink>,
route: Tracked<Route>,
) -> SourceResult<EcoVec<Value>> {
let mut engine = Engine {
world,
introspector,
traced,
sink,
route: Route::extend(route).unnested(),
};
let mut state = self.init.clone();
let mut stops = eco_vec![state.clone()];
for elem in introspector.query(&self.selector()) {
let elem = elem.to_packed::<StateUpdateElem>().unwrap();
match elem.update() {
StateUpdate::Set(value) => state = value.clone(),
StateUpdate::Func(func) => {
state = func.call(&mut engine, Context::none().track(), [state])?
}
}
stops.push(state.clone());
}
Ok(stops)
}
fn selector(&self) -> Selector {
select_where!(StateUpdateElem, Key => self.key.clone())
}
}
#[scope]
impl State {
#[func(constructor)]
pub fn construct(
key: Str,
#[default]
init: Value,
) -> State {
Self::new(key, init)
}
#[func(contextual)]
pub fn get(
&self,
engine: &mut Engine,
context: Tracked<Context>,
span: Span,
) -> SourceResult<Value> {
let loc = context.location().at(span)?;
self.at_loc(engine, loc)
}
#[func(contextual)]
pub fn at(
&self,
engine: &mut Engine,
context: Tracked<Context>,
span: Span,
selector: LocatableSelector,
) -> SourceResult<Value> {
let loc = selector.resolve_unique(engine.introspector, context).at(span)?;
self.at_loc(engine, loc)
}
#[func(contextual)]
pub fn final_(
&self,
engine: &mut Engine,
context: Tracked<Context>,
span: Span,
#[default]
location: Option<Location>,
) -> SourceResult<Value> {
if location.is_none() {
context.location().at(span)?;
} else {
engine.sink.warn(warning!(
span, "calling `state.final` with a location is deprecated";
hint: "try removing the location argument"
));
}
let sequence = self.sequence(engine)?;
Ok(sequence.last().unwrap().clone())
}
#[func]
pub fn update(
self,
span: Span,
update: StateUpdate,
) -> Content {
StateUpdateElem::new(self.key, update).pack().spanned(span)
}
#[func]
pub fn display(
self,
engine: &mut Engine,
span: Span,
#[default]
func: Option<Func>,
) -> Content {
engine.sink.warn(warning!(
span, "`state.display` is deprecated";
hint: "use `state.get` in a `context` expression instead"
));
StateDisplayElem::new(self, func).pack().spanned(span)
}
}
impl Repr for State {
fn repr(&self) -> EcoString {
eco_format!("state({}, {})", self.key.repr(), self.init.repr())
}
}
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum StateUpdate {
Set(Value),
Func(Func),
}
cast! {
StateUpdate,
v: Func => Self::Func(v),
v: Value => Self::Set(v),
}
#[elem(Construct, Locatable, Show)]
struct StateUpdateElem {
#[required]
key: Str,
#[required]
#[internal]
update: StateUpdate,
}
impl Construct for StateUpdateElem {
fn construct(_: &mut Engine, args: &mut Args) -> SourceResult<Content> {
bail!(args.span, "cannot be constructed manually");
}
}
impl Show for Packed<StateUpdateElem> {
fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
Ok(Content::empty())
}
}
#[elem(Construct, Locatable, Show)]
struct StateDisplayElem {
#[required]
#[internal]
state: State,
#[required]
#[internal]
func: Option<Func>,
}
impl Show for Packed<StateDisplayElem> {
#[typst_macros::time(name = "state.display", span = self.span())]
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
let location = self.location().unwrap();
let context = Context::new(Some(location), Some(styles));
let value = self.state().at_loc(engine, location)?;
Ok(match self.func() {
Some(func) => func.call(engine, context.track(), [value])?.display(),
None => value.display(),
})
}
}
impl Construct for StateDisplayElem {
fn construct(_: &mut Engine, args: &mut Args) -> SourceResult<Content> {
bail!(args.span, "cannot be constructed manually");
}
}