diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-11-22 20:12:37 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-11-22 20:12:37 +0100 |
| commit | 5992f11b4c33d82fa245205980094accb57a8c76 (patch) | |
| tree | b0043ba4b4cabab2fa8f63ec80f37c9d247e134a /src/model | |
| parent | b476de87b7cea1405bf3c051ff8e0ac7c473dbae (diff) | |
Reorganize content type
Diffstat (limited to 'src/model')
| -rw-r--r-- | src/model/content.rs | 366 | ||||
| -rw-r--r-- | src/model/eval.rs | 11 | ||||
| -rw-r--r-- | src/model/styles.rs | 50 |
3 files changed, 215 insertions, 212 deletions
diff --git a/src/model/content.rs b/src/model/content.rs index d72e4e19..9d78020c 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -9,19 +9,22 @@ use comemo::Tracked; use siphasher::sip128::{Hasher128, SipHasher}; use typst_macros::node; -use super::{Args, Key, Property, Recipe, RecipeId, Style, StyleMap, Value, Vm}; +use super::{ + Args, Key, Property, Recipe, RecipeId, Style, StyleMap, Unlabellable, Value, Vm, +}; use crate::diag::{SourceResult, StrResult}; use crate::syntax::Span; use crate::util::{EcoString, ReadableTypeId}; use crate::World; /// Composable representation of styled content. -/// -/// This results from: -/// - anything written between square brackets in Typst -/// - any constructor function #[derive(Clone, Hash)] -pub struct Content(Arc<dyn Bounds>, Vec<RecipeId>, Option<Span>, Option<EcoString>); +pub struct Content { + obj: Arc<dyn Bounds>, + guards: Vec<RecipeId>, + span: Option<Span>, + label: Option<EcoString>, +} impl Content { /// Create empty content. @@ -37,99 +40,38 @@ impl Content { } } - /// Whether the content is empty. - pub fn is_empty(&self) -> bool { - self.downcast::<SequenceNode>().map_or(false, |seq| seq.0.is_empty()) - } - - /// The node's human-readable name. - pub fn name(&self) -> &'static str { - (*self.0).name() - } - - /// The id of the contained node. - pub fn id(&self) -> NodeId { - (*self.0).id() - } - - /// Whether the contained node is of type `T`. - pub fn is<T: 'static>(&self) -> bool { - (*self.0).as_any().is::<T>() - } - - /// Cast to `T` if the contained node is of type `T`. - pub fn downcast<T: 'static>(&self) -> Option<&T> { - (*self.0).as_any().downcast_ref::<T>() - } - - /// Try to cast to a mutable instance of `T`. - fn try_downcast_mut<T: 'static>(&mut self) -> Option<&mut T> { - Arc::get_mut(&mut self.0)?.as_any_mut().downcast_mut::<T>() - } - - /// Access a field on this content. - pub fn field(&self, name: &str) -> Option<Value> { - if name == "label" { - return Some(match &self.3 { - Some(label) => Value::Str(label.clone().into()), - None => Value::None, - }); + /// Attach a span to the content. + pub fn spanned(mut self, span: Span) -> Self { + if let Some(styled) = self.to_mut::<StyledNode>() { + styled.sub.span = Some(span); + } else if let Some(styled) = self.to::<StyledNode>() { + self = StyledNode { + sub: styled.sub.clone().spanned(span), + map: styled.map.clone(), + } + .pack(); } - - self.0.field(name) - } - - /// Whether this content has the given capability. - pub fn has<C>(&self) -> bool - where - C: Capability + ?Sized, - { - self.0.vtable(TypeId::of::<C>()).is_some() - } - - /// Cast to a trait object if this content has the given capability. - pub fn to<C>(&self) -> Option<&C> - where - C: Capability + ?Sized, - { - let node: &dyn Bounds = &*self.0; - let vtable = node.vtable(TypeId::of::<C>())?; - let data = node as *const dyn Bounds as *const (); - Some(unsafe { &*crate::util::fat::from_raw_parts(data, vtable) }) + self.span = Some(span); + self } - /// Repeat this content `n` times. - pub fn repeat(&self, n: i64) -> StrResult<Self> { - let count = usize::try_from(n) - .map_err(|_| format!("cannot repeat this content {} times", n))?; - - Ok(Self::sequence(vec![self.clone(); count])) + /// Attach a label to the content. + pub fn labelled(mut self, label: EcoString) -> Self { + self.label = Some(label); + self } /// Style this content with a single style property. - pub fn styled<'k, K: Key<'k>>(self, key: K, value: K::Value) -> Self { + pub fn styled<K: Key>(self, key: K, value: K::Value) -> Self { self.styled_with_entry(Style::Property(Property::new(key, value))) } - /// Style this content with a recipe, eagerly applying it if possible. - pub fn styled_with_recipe( - self, - world: Tracked<dyn World>, - recipe: Recipe, - ) -> SourceResult<Self> { - if recipe.selector.is_none() { - recipe.transform.apply(world, recipe.span, self) - } else { - Ok(self.styled_with_entry(Style::Recipe(recipe))) - } - } - /// Style this content with a style entry. pub fn styled_with_entry(mut self, style: Style) -> Self { - if let Some(styled) = self.try_downcast_mut::<StyledNode>() { + if let Some(styled) = self.to_mut::<StyledNode>() { styled.map.apply(style); self - } else if let Some(styled) = self.downcast::<StyledNode>() { + } else if let Some(styled) = self.to::<StyledNode>() { let mut map = styled.map.clone(); map.apply(style); StyledNode { sub: styled.sub.clone(), map }.pack() @@ -144,7 +86,7 @@ impl Content { return self; } - if let Some(styled) = self.try_downcast_mut::<StyledNode>() { + if let Some(styled) = self.to_mut::<StyledNode>() { styled.map.apply_map(&styles); return self; } @@ -152,86 +94,140 @@ impl Content { StyledNode { sub: self, map: styles }.pack() } - /// Disable a show rule recipe. - pub fn guard(mut self, id: RecipeId) -> Self { - self.1.push(id); - self + /// Style this content with a recipe, eagerly applying it if possible. + pub fn styled_with_recipe( + self, + world: Tracked<dyn World>, + recipe: Recipe, + ) -> SourceResult<Self> { + if recipe.selector.is_none() { + recipe.transform.apply(world, recipe.span, self) + } else { + Ok(self.styled_with_entry(Style::Recipe(recipe))) + } } - /// Whether no show rule was executed for this node so far. - pub fn pristine(&self) -> bool { - self.1.is_empty() + /// Repeat this content `n` times. + pub fn repeat(&self, n: i64) -> StrResult<Self> { + let count = usize::try_from(n) + .map_err(|_| format!("cannot repeat this content {} times", n))?; + + Ok(Self::sequence(vec![self.clone(); count])) } +} - /// Check whether a show rule recipe is disabled. - pub fn guarded(&self, id: RecipeId) -> bool { - self.1.contains(&id) +impl Content { + /// The id of the contained node. + pub fn id(&self) -> NodeId { + (*self.obj).id() + } + + /// The node's human-readable name. + pub fn name(&self) -> &'static str { + (*self.obj).name() } /// The node's span. pub fn span(&self) -> Option<Span> { - self.2 + self.span } - /// Set the content's span. - pub fn set_span(&mut self, span: Span) { - if let Some(styled) = self.try_downcast_mut::<StyledNode>() { - styled.sub.2 = Some(span); - } else if let Some(styled) = self.downcast::<StyledNode>() { - *self = StyledNode { - sub: styled.sub.clone().spanned(span), - map: styled.map.clone(), - } - .pack(); + /// The content's label. + pub fn label(&self) -> Option<&EcoString> { + self.label.as_ref() + } + + /// Access a field on this content. + pub fn field(&self, name: &str) -> Option<Value> { + if name == "label" { + return Some(match &self.label { + Some(label) => Value::Str(label.clone().into()), + None => Value::None, + }); } - self.2 = Some(span); + + self.obj.field(name) } - /// Attach a span to the content. - pub fn spanned(mut self, span: Span) -> Self { - self.set_span(span); - self + /// Whether the contained node is of type `T`. + pub fn is<T: 'static>(&self) -> bool { + (*self.obj).as_any().is::<T>() } - /// The content's label. - pub fn label(&self) -> Option<&EcoString> { - self.3.as_ref() + /// Cast to `T` if the contained node is of type `T`. + pub fn to<T: 'static>(&self) -> Option<&T> { + (*self.obj).as_any().downcast_ref::<T>() } - /// Set the content's label. - pub fn set_label(&mut self, label: EcoString) { - self.3 = Some(label); + /// Whether this content has the given capability. + pub fn has<C>(&self) -> bool + where + C: Capability + ?Sized, + { + self.obj.vtable(TypeId::of::<C>()).is_some() } - /// Attacha label to the content. - pub fn labelled(mut self, label: EcoString) -> Self { - self.set_label(label); + /// Cast to a trait object if this content has the given capability. + pub fn with<C>(&self) -> Option<&C> + where + C: Capability + ?Sized, + { + let node: &dyn Bounds = &*self.obj; + let vtable = node.vtable(TypeId::of::<C>())?; + let data = node as *const dyn Bounds as *const (); + Some(unsafe { &*crate::util::fat::from_raw_parts(data, vtable) }) + } + + /// Try to cast to a mutable instance of `T`. + fn to_mut<T: 'static>(&mut self) -> Option<&mut T> { + Arc::get_mut(&mut self.obj)?.as_any_mut().downcast_mut::<T>() + } + + /// Disable a show rule recipe. + #[doc(hidden)] + pub fn guarded(mut self, id: RecipeId) -> Self { + self.guards.push(id); self } - /// Copy the metadata from other content. - pub fn copy_meta(&mut self, from: &Content) { - self.1 = from.1.clone(); - self.2 = from.2; - self.3 = from.3.clone(); + /// Whether a label can be attached to the content. + pub(super) fn labellable(&self) -> bool { + !self.has::<dyn Unlabellable>() } -} -impl Default for Content { - fn default() -> Self { - Self::empty() + /// Whether no show rule was executed for this node so far. + pub(super) fn is_pristine(&self) -> bool { + self.guards.is_empty() + } + + /// Check whether a show rule recipe is disabled. + pub(super) fn is_guarded(&self, id: RecipeId) -> bool { + self.guards.contains(&id) + } + + /// Copy the metadata from other content. + pub(super) fn copy_meta(&mut self, from: &Content) { + self.guards = from.guards.clone(); + self.span = from.span; + self.label = from.label.clone(); } } impl Debug for Content { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.0.fmt(f) + self.obj.fmt(f) + } +} + +impl Default for Content { + fn default() -> Self { + Self::empty() } } impl PartialEq for Content { fn eq(&self, other: &Self) -> bool { - (*self.0).hash128() == (*other.0).hash128() + (*self.obj).hash128() == (*other.obj).hash128() } } @@ -240,10 +236,10 @@ impl Add for Content { fn add(self, mut rhs: Self) -> Self::Output { let mut lhs = self; - if let Some(lhs_mut) = lhs.try_downcast_mut::<SequenceNode>() { - if let Some(rhs_mut) = rhs.try_downcast_mut::<SequenceNode>() { + if let Some(lhs_mut) = lhs.to_mut::<SequenceNode>() { + if let Some(rhs_mut) = rhs.to_mut::<SequenceNode>() { lhs_mut.0.append(&mut rhs_mut.0); - } else if let Some(rhs) = rhs.downcast::<SequenceNode>() { + } else if let Some(rhs) = rhs.to::<SequenceNode>() { lhs_mut.0.extend(rhs.0.iter().cloned()); } else { lhs_mut.0.push(rhs); @@ -251,7 +247,7 @@ impl Add for Content { return lhs; } - let seq = match (lhs.downcast::<SequenceNode>(), rhs.downcast::<SequenceNode>()) { + let seq = match (lhs.to::<SequenceNode>(), rhs.to::<SequenceNode>()) { (Some(lhs), Some(rhs)) => lhs.0.iter().chain(&rhs.0).cloned().collect(), (Some(lhs), None) => lhs.0.iter().cloned().chain(iter::once(rhs)).collect(), (None, Some(rhs)) => iter::once(lhs).chain(rhs.0.iter().cloned()).collect(), @@ -306,14 +302,54 @@ impl Hash for dyn Bounds { } } +/// A node with applied styles. +#[derive(Clone, Hash)] +pub struct StyledNode { + /// The styled content. + pub sub: Content, + /// The styles. + pub map: StyleMap, +} + +#[node] +impl StyledNode {} + +impl Debug for StyledNode { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.map.fmt(f)?; + self.sub.fmt(f) + } +} + +/// A sequence of nodes. +/// +/// Combines other arbitrary content. So, when you write `[Hi] + [you]` in +/// Typst, the two text nodes are combined into a single sequence node. +#[derive(Clone, Hash)] +pub struct SequenceNode(pub Vec<Content>); + +#[node] +impl SequenceNode {} + +impl Debug for SequenceNode { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.debug_list().entries(self.0.iter()).finish() + } +} + /// A constructable, stylable content node. -pub trait Node: 'static { - /// Pack into type-erased content. +pub trait Node: 'static + Capable { + /// Pack a node into type-erased content. fn pack(self) -> Content where - Self: Debug + Hash + Sync + Send + Sized + 'static, + Self: Node + Debug + Hash + Sync + Send + Sized + 'static, { - Content(Arc::new(self), vec![], None, None) + Content { + obj: Arc::new(self), + guards: vec![], + span: None, + label: None, + } } /// A unique identifier of the node type. @@ -340,17 +376,8 @@ pub trait Node: 'static { /// Access a field on this node. fn field(&self, name: &str) -> Option<Value>; - - /// Extract the pointer of the vtable of the trait object with the - /// given type `id` if this node implements that trait. - fn vtable(&self, id: TypeId) -> Option<*const ()>; } -/// A capability a node can have. -/// -/// This is implemented by trait objects. -pub trait Capability: 'static + Send + Sync {} - /// A unique identifier for a node. #[derive(Copy, Clone, Eq, PartialEq, Hash)] pub struct NodeId(ReadableTypeId); @@ -368,37 +395,14 @@ impl Debug for NodeId { } } -/// A node with applied styles. -#[derive(Clone, Hash)] -pub struct StyledNode { - /// The styled content. - pub sub: Content, - /// The styles. - pub map: StyleMap, -} - -#[node] -impl StyledNode {} - -impl Debug for StyledNode { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.map.fmt(f)?; - self.sub.fmt(f) - } -} - -/// A sequence of nodes. +/// A capability a node can have. /// -/// Combines other arbitrary content. So, when you write `[Hi] + [you]` in -/// Typst, the two text nodes are combined into a single sequence node. -#[derive(Clone, Hash)] -pub struct SequenceNode(pub Vec<Content>); - -#[node] -impl SequenceNode {} +/// This is implemented by trait objects. +pub trait Capability: 'static {} -impl Debug for SequenceNode { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.debug_list().entries(self.0.iter()).finish() - } +/// Dynamically access a trait implementation at runtime. +pub unsafe trait Capable { + /// Return the vtable pointer of the trait object with given type `id` + /// if `self` implements the trait. + fn vtable(&self, of: TypeId) -> Option<*const ()>; } diff --git a/src/model/eval.rs b/src/model/eval.rs index 8078c87f..7c0cffb5 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -1,13 +1,14 @@ //! Evaluation of markup into modules. use std::collections::BTreeMap; +use std::mem; use comemo::{Track, Tracked}; use unicode_segmentation::UnicodeSegmentation; use super::{ methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, Dict, Flow, Func, - Recipe, Scope, Scopes, Selector, StyleMap, Transform, Unlabellable, Value, Vm, + Recipe, Scope, Scopes, Selector, StyleMap, Transform, Value, Vm, }; use crate::diag::{bail, error, At, SourceResult, StrResult, Trace, Tracepoint}; use crate::geom::{Abs, Angle, Em, Fr, Ratio}; @@ -137,10 +138,8 @@ fn eval_markup( seq.push(tail.styled_with_recipe(vm.world, recipe)?) } ast::MarkupNode::Label(label) => { - if let Some(node) = - seq.iter_mut().rev().find(|node| !node.has::<dyn Unlabellable>()) - { - node.set_label(label.get().clone()); + if let Some(node) = seq.iter_mut().rev().find(|node| node.labellable()) { + *node = mem::take(node).labelled(label.get().clone()); } } _ => seq.push(node.eval(vm)?), @@ -1080,7 +1079,7 @@ impl Eval for ast::FuncReturn { } /// Access an expression mutably. -pub trait Access { +trait Access { /// Access the value. fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value>; } diff --git a/src/model/styles.rs b/src/model/styles.rs index 30db00bd..96eb0ab4 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -36,19 +36,19 @@ impl StyleMap { /// If the property needs folding and the value is already contained in the /// style map, `self` contributes the outer values and `value` is the inner /// one. - pub fn set<'a, K: Key<'a>>(&mut self, key: K, value: K::Value) { + pub fn set<K: Key>(&mut self, key: K, value: K::Value) { self.0.push(Style::Property(Property::new(key, value))); } /// Set an inner value for a style property if it is `Some(_)`. - pub fn set_opt<'a, K: Key<'a>>(&mut self, key: K, value: Option<K::Value>) { + pub fn set_opt<K: Key>(&mut self, key: K, value: Option<K::Value>) { if let Some(value) = value { self.set(key, value); } } /// Whether the map contains a style property for the given key. - pub fn contains<'a, K: Key<'a>>(&self, _: K) -> bool { + pub fn contains<K: Key>(&self, _: K) -> bool { self.0 .iter() .filter_map(|entry| entry.property()) @@ -188,13 +188,13 @@ impl Debug for Style { /// /// This trait is not intended to be implemented manually, but rather through /// the `#[node]` proc-macro. -pub trait Key<'a>: Copy + 'static { +pub trait Key: Copy + 'static { /// The unfolded type which this property is stored as in a style map. type Value: Debug + Clone + Hash + Sync + Send + 'static; /// The folded type of value that is returned when reading this property /// from a style chain. - type Output; + type Output<'a>; /// The name of the property, used for debug printing. const NAME: &'static str; @@ -204,10 +204,10 @@ pub trait Key<'a>: Copy + 'static { /// Compute an output value from a sequence of values belonging to this key, /// folding if necessary. - fn get( + fn get<'a>( chain: StyleChain<'a>, values: impl Iterator<Item = &'a Self::Value>, - ) -> Self::Output; + ) -> Self::Output<'a>; } /// A style property originating from a set rule or constructor. @@ -229,7 +229,7 @@ pub struct Property { impl Property { /// Create a new property from a key-value pair. - pub fn new<'a, K: Key<'a>>(_: K, value: K::Value) -> Self { + pub fn new<K: Key>(_: K, value: K::Value) -> Self { Self { key: KeyId::of::<K>(), node: K::node(), @@ -241,7 +241,7 @@ impl Property { } /// Whether this property has the given key. - pub fn is<'a, K: Key<'a>>(&self) -> bool { + pub fn is<K: Key>(&self) -> bool { self.key == KeyId::of::<K>() } @@ -251,7 +251,7 @@ impl Property { } /// Access the property's value if it is of the given key. - pub fn downcast<'a, K: Key<'a>>(&'a self) -> Option<&'a K::Value> { + pub fn downcast<K: Key>(&self) -> Option<&K::Value> { if self.key == KeyId::of::<K>() { (**self.value).as_any().downcast_ref() } else { @@ -314,7 +314,7 @@ struct KeyId(ReadableTypeId); impl KeyId { /// The id of the given key. - pub fn of<'a, T: Key<'a>>() -> Self { + pub fn of<T: Key>() -> Self { Self(ReadableTypeId::of::<T>()) } } @@ -327,7 +327,7 @@ impl Debug for KeyId { /// A built-in show rule for a node. #[capability] -pub trait Show: 'static + Sync + Send { +pub trait Show { /// Execute the base recipe for this node. fn show( &self, @@ -338,7 +338,7 @@ pub trait Show: 'static + Sync + Send { /// Post-process a node after it was realized. #[capability] -pub trait Finalize: 'static + Sync + Send { +pub trait Finalize { /// Finalize the fully realized form of the node. Use this for effects that /// should work even in the face of a user-defined show rule, for example /// the linking behaviour of a link node. @@ -352,7 +352,7 @@ pub trait Finalize: 'static + Sync + Send { /// Indicates that a node cannot be labelled. #[capability] -pub trait Unlabellable: 'static + Sync + Send {} +pub trait Unlabellable {} /// A show rule recipe. #[derive(Clone, PartialEq, Hash)] @@ -386,7 +386,7 @@ impl Recipe { return Ok(None); } - self.transform.apply(world, self.span, target.clone().guard(sel))? + self.transform.apply(world, self.span, target.clone().guarded(sel))? } Some(Selector::Regex(regex)) => { @@ -412,7 +412,7 @@ impl Recipe { let transformed = self.transform.apply( world, self.span, - make(m.as_str().into()).guard(sel), + make(m.as_str().into()).guarded(sel), )?; result.push(transformed); @@ -559,7 +559,7 @@ impl<'a> StyleChain<'a> { /// Returns the property's default value if no map in the chain contains an /// entry for it. Also takes care of resolving and folding and returns /// references where applicable. - pub fn get<K: Key<'a>>(self, key: K) -> K::Output { + pub fn get<K: Key>(self, key: K) -> K::Output<'a> { K::get(self, self.values(key)) } @@ -576,7 +576,7 @@ impl<'a> StyleChain<'a> { let mut realized = None; for recipe in self.entries().filter_map(Style::recipe) { let sel = RecipeId::Nth(n); - if recipe.applicable(target) && !target.guarded(sel) { + if recipe.applicable(target) && !target.is_guarded(sel) { if let Some(content) = recipe.apply(world, sel, target)? { realized = Some(content); break; @@ -587,15 +587,15 @@ impl<'a> StyleChain<'a> { // Realize if there was no matching recipe. let base = RecipeId::Base(target.id()); - if realized.is_none() && !target.guarded(base) { - if let Some(showable) = target.to::<dyn Show>() { + if realized.is_none() && !target.is_guarded(base) { + if let Some(showable) = target.with::<dyn Show>() { realized = Some(showable.show(world, self)?); } } // Finalize only if this is the first application for this node. - if let Some(node) = target.to::<dyn Finalize>() { - if target.pristine() { + if let Some(node) = target.with::<dyn Finalize>() { + if target.is_pristine() { if let Some(content) = realized { realized = Some(node.finalize(world, self, content)?); } @@ -612,7 +612,7 @@ impl<'a> StyleChain<'a> { // Find out whether any recipe matches and is unguarded. for recipe in self.entries().filter_map(Style::recipe) { - if recipe.applicable(target) && !target.guarded(RecipeId::Nth(n)) { + if recipe.applicable(target) && !target.is_guarded(RecipeId::Nth(n)) { return true; } n -= 1; @@ -638,7 +638,7 @@ impl<'a> StyleChain<'a> { } /// Iterate over all values for the given property in the chain. - fn values<K: Key<'a>>(self, _: K) -> Values<'a, K> { + fn values<K: Key>(self, _: K) -> Values<'a, K> { Values { entries: self.entries(), key: PhantomData, @@ -682,7 +682,7 @@ struct Values<'a, K> { barriers: usize, } -impl<'a, K: Key<'a>> Iterator for Values<'a, K> { +impl<'a, K: Key> Iterator for Values<'a, K> { type Item = &'a K::Value; fn next(&mut self) -> Option<Self::Item> { |
