diff options
Diffstat (limited to 'src/model/content.rs')
| -rw-r--r-- | src/model/content.rs | 392 |
1 files changed, 194 insertions, 198 deletions
diff --git a/src/model/content.rs b/src/model/content.rs index d845ce1e..b895553c 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -2,22 +2,24 @@ use std::any::TypeId; use std::fmt::{self, Debug, Formatter, Write}; use std::hash::{Hash, Hasher}; use std::iter::{self, Sum}; -use std::ops::{Add, AddAssign}; +use std::ops::{Add, AddAssign, Deref}; use comemo::Tracked; -use ecow::{EcoString, EcoVec}; +use ecow::{eco_format, EcoString, EcoVec}; +use once_cell::sync::Lazy; use super::{node, Guard, Recipe, Style, StyleMap}; use crate::diag::{SourceResult, StrResult}; -use crate::eval::{cast_from_value, Args, Cast, NodeFunc, Value, Vm}; +use crate::eval::{cast_from_value, Args, FuncInfo, Str, Value, Vm}; use crate::syntax::Span; +use crate::util::pretty_array; use crate::World; /// Composable representation of styled content. #[derive(Clone, Hash)] pub struct Content { id: NodeId, - span: Option<Span>, + span: Span, fields: EcoVec<(EcoString, Value)>, modifiers: EcoVec<Modifier>, } @@ -33,7 +35,7 @@ impl Content { pub fn new<T: Node>() -> Self { Self { id: T::id(), - span: None, + span: Span::detached(), fields: EcoVec::new(), modifiers: EcoVec::new(), } @@ -52,82 +54,67 @@ impl Content { } } - /// Attach a span to the content. - pub fn spanned(mut self, span: Span) -> Self { - if let Some(styled) = self.to::<StyledNode>() { - self = StyledNode::new(styled.map(), styled.body().spanned(span)).pack(); - } - self.span = Some(span); - self - } - - /// Attach a label to the content. - pub fn labelled(self, label: Label) -> Self { - self.with_field("label", label) + /// The id of the contained node. + pub fn id(&self) -> NodeId { + self.id } - /// Style this content with a style entry. - pub fn styled(self, style: impl Into<Style>) -> Self { - self.styled_with_map(style.into().into()) + /// Whether the contained node is of type `T`. + pub fn is<T>(&self) -> bool + where + T: Node + 'static, + { + self.id == NodeId::of::<T>() } - /// Style this content with a full style map. - pub fn styled_with_map(self, styles: StyleMap) -> Self { - if styles.is_empty() { - self - } else if let Some(styled) = self.to::<StyledNode>() { - let mut map = styled.map(); - map.apply(styles); - StyledNode::new(map, styled.body()).pack() - } else { - StyledNode::new(styles, self).pack() - } + /// Cast to `T` if the contained node is of type `T`. + pub fn to<T>(&self) -> Option<&T> + where + T: Node + 'static, + { + self.is::<T>().then(|| unsafe { std::mem::transmute(self) }) } - /// Style this content with a recipe, eagerly applying it if possible. - pub fn apply_recipe( - self, - world: Tracked<dyn World>, - recipe: Recipe, - ) -> SourceResult<Self> { - if recipe.selector.is_none() { - recipe.apply(world, self) - } else { - Ok(self.styled(Style::Recipe(recipe))) - } + /// Whether this content has the given capability. + pub fn can<C>(&self) -> bool + where + C: ?Sized + 'static, + { + (self.id.0.vtable)(TypeId::of::<C>()).is_some() } - /// 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])) + /// Cast to a trait object if this content has the given capability. + pub fn with<C>(&self) -> Option<&C> + where + C: ?Sized + 'static, + { + let vtable = (self.id.0.vtable)(TypeId::of::<C>())?; + let data = self as *const Self as *const (); + Some(unsafe { &*crate::util::fat::from_raw_parts(data, vtable) }) } -} -impl Content { - /// The id of the contained node. - pub fn id(&self) -> NodeId { - self.id + /// The node's span. + pub fn span(&self) -> Span { + self.span } - /// The node's human-readable name. - pub fn name(&self) -> &'static str { - self.id.name() + /// Attach a span to the content. + pub fn spanned(mut self, span: Span) -> Self { + self.span = span; + self } - /// The node's span. - pub fn span(&self) -> Option<Span> { - self.span + /// Access a field on the content. + pub fn field(&self, name: &str) -> Option<&Value> { + self.fields + .iter() + .find(|(field, _)| field == name) + .map(|(_, value)| value) } - /// The content's label. - pub fn label(&self) -> Option<&Label> { - match self.field("label")? { - Value::Label(label) => Some(label), - _ => None, - } + /// List all fields on the content. + pub fn fields(&self) -> &[(EcoString, Value)] { + &self.fields } /// Attach a field to the content. @@ -150,88 +137,88 @@ impl Content { } } - /// Access a field on the content. - pub fn field(&self, name: &str) -> Option<&Value> { - static NONE: Value = Value::None; - self.fields - .iter() - .find(|(field, _)| field == name) - .map(|(_, value)| value) - .or_else(|| (name == "label").then(|| &NONE)) + /// Whether the content has the specified field. + pub fn has(&self, field: &str) -> bool { + self.field(field).is_some() + } + + /// Borrow the value of the given field. + pub fn at(&self, field: &str) -> StrResult<&Value> { + self.field(field).ok_or_else(|| missing_field(field)) } - #[track_caller] - pub fn cast_required_field<T: Cast>(&self, name: &str) -> T { - match self.field(name) { - Some(value) => value.clone().cast().unwrap(), - None => field_is_missing(name), + /// The content's label. + pub fn label(&self) -> Option<&Label> { + match self.field("label")? { + Value::Label(label) => Some(label), + _ => None, } } - /// List all fields on the content. - pub fn fields(&self) -> &[(EcoString, Value)] { - &self.fields + /// Attach a label to the content. + pub fn labelled(self, label: Label) -> Self { + self.with_field("label", label) } - /// Whether the contained node is of type `T`. - pub fn is<T>(&self) -> bool - where - T: Node + 'static, - { - self.id == NodeId::of::<T>() + /// Style this content with a style entry. + pub fn styled(self, style: impl Into<Style>) -> Self { + self.styled_with_map(style.into().into()) } - /// Cast to `T` if the contained node is of type `T`. - pub fn to<T>(&self) -> Option<&T> - where - T: Node + 'static, - { - self.is::<T>().then(|| unsafe { std::mem::transmute(self) }) + /// Style this content with a full style map. + pub fn styled_with_map(self, styles: StyleMap) -> Self { + if styles.is_empty() { + self + } else if let Some(styled) = self.to::<StyledNode>() { + let mut map = styled.styles(); + map.apply(styles); + StyledNode::new(map, styled.body()).pack() + } else { + StyledNode::new(styles, self).pack() + } } - /// Whether this content has the given capability. - pub fn has<C>(&self) -> bool - where - C: ?Sized + 'static, - { - (self.id.0.vtable)(TypeId::of::<C>()).is_some() + /// 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.apply(world, self) + } else { + Ok(self.styled(Style::Recipe(recipe))) + } } - /// Cast to a trait object if this content has the given capability. - pub fn with<C>(&self) -> Option<&C> - where - C: ?Sized + 'static, - { - let vtable = (self.id.0.vtable)(TypeId::of::<C>())?; - let data = self as *const Self as *const (); - Some(unsafe { &*crate::util::fat::from_raw_parts(data, vtable) }) + /// 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])) } +} +#[doc(hidden)] +impl Content { /// Disable a show rule recipe. - #[doc(hidden)] pub fn guarded(mut self, id: Guard) -> Self { self.modifiers.push(Modifier::Guard(id)); self } /// Mark this content as prepared. - #[doc(hidden)] pub fn prepared(mut self) -> Self { self.modifiers.push(Modifier::Prepared); self } /// Whether this node was prepared. - #[doc(hidden)] pub fn is_prepared(&self) -> bool { self.modifiers.contains(&Modifier::Prepared) } - /// Whether a label can be attached to the content. - pub(crate) fn labellable(&self) -> bool { - !self.has::<dyn Unlabellable>() - } - /// Whether no show rule was executed for this node so far. pub(super) fn is_pristine(&self) -> bool { !self @@ -257,32 +244,24 @@ impl Content { impl Debug for Content { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - struct Pad<'a>(&'a str); - impl Debug for Pad<'_> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(self.0) - } + let name = self.id.name; + if let Some(text) = item!(text_str)(self) { + f.write_char('[')?; + f.write_str(&text)?; + f.write_char(']')?; + return Ok(()); + } else if name == "space" { + return f.write_str("[ ]"); } - if let Some(styled) = self.to::<StyledNode>() { - styled.map().fmt(f)?; - styled.body().fmt(f) - } else if let Some(seq) = self.to::<SequenceNode>() { - f.debug_list().entries(&seq.children()).finish() - } else if self.id.name() == "space" { - ' '.fmt(f) - } else if self.id.name() == "text" { - self.field("text").unwrap().fmt(f) - } else { - f.write_str(self.name())?; - if self.fields.is_empty() { - return Ok(()); - } - f.write_char(' ')?; - f.debug_map() - .entries(self.fields.iter().map(|(name, value)| (Pad(name), value))) - .finish() - } + let pieces: Vec<_> = self + .fields + .iter() + .map(|(name, value)| eco_format!("{name}: {value:?}")) + .collect(); + + f.write_str(name)?; + f.write_str(&pretty_array(&pieces, false)) } } @@ -292,6 +271,17 @@ impl Default for Content { } } +impl PartialEq for Content { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + && self.fields.len() == other.fields.len() + && self + .fields + .iter() + .all(|(name, value)| other.field(name) == Some(value)) + } +} + impl Add for Content { type Output = Self; @@ -323,48 +313,6 @@ impl Sum for Content { } } -/// A node with applied styles. -/// -/// Display: Styled -/// Category: special -#[node] -pub struct StyledNode { - /// The styles. - #[required] - pub map: StyleMap, - - /// The styled content. - #[required] - pub body: Content, -} - -cast_from_value! { - StyleMap: "style map", -} - -/// 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. -/// -/// Display: Sequence -/// Category: special -#[node] -pub struct SequenceNode { - #[variadic] - pub children: Vec<Content>, -} - -/// A label for a node. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Label(pub EcoString); - -impl Debug for Label { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "<{}>", self.0) - } -} - /// A constructable, stylable content node. pub trait Node: Construct + Set + Sized + 'static { /// The node's ID. @@ -372,33 +320,22 @@ pub trait Node: Construct + Set + Sized + 'static { /// Pack a node into type-erased content. fn pack(self) -> Content; - - /// The node's function. - fn func() -> NodeFunc; } /// A unique identifier for a node. #[derive(Copy, Clone)] -pub struct NodeId(&'static NodeMeta); +pub struct NodeId(pub &'static NodeMeta); impl NodeId { + /// Get the id of a node. pub fn of<T: Node>() -> Self { T::id() } - - pub fn from_meta(meta: &'static NodeMeta) -> Self { - Self(meta) - } - - /// The name of the identified node. - pub fn name(self) -> &'static str { - self.0.name - } } impl Debug for NodeId { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(self.name()) + f.pad(self.name) } } @@ -416,10 +353,26 @@ impl PartialEq for NodeId { } } -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +impl Deref for NodeId { + type Target = NodeMeta; + + fn deref(&self) -> &Self::Target { + self.0 + } +} + +/// Static node for a node. pub struct NodeMeta { + /// The node's name. pub name: &'static str, + /// The node's vtable for caspability dispatch. pub vtable: fn(of: TypeId) -> Option<*const ()>, + /// The node's constructor. + pub construct: fn(&Vm, &mut Args) -> SourceResult<Content>, + /// The node's set rule. + pub set: fn(&mut Args) -> SourceResult<StyleMap>, + /// Details about the function. + pub info: Lazy<FuncInfo>, } /// A node's constructor function. @@ -440,8 +393,51 @@ pub trait Set { /// Indicates that a node cannot be labelled. pub trait Unlabellable {} +/// A label for a node. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct Label(pub EcoString); + +impl Debug for Label { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "<{}>", self.0) + } +} + +/// 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. +/// +/// Display: Sequence +/// Category: special +#[node] +pub struct SequenceNode { + #[variadic] + pub children: Vec<Content>, +} + +/// A node with applied styles. +/// +/// Display: Styled +/// Category: special +#[node] +pub struct StyledNode { + /// The styles. + #[required] + pub styles: StyleMap, + + /// The styled content. + #[required] + pub body: Content, +} + +cast_from_value! { + StyleMap: "style map", +} + +/// The missing key access error message. #[cold] #[track_caller] -fn field_is_missing(name: &str) -> ! { - panic!("required field `{name}` is missing") +fn missing_field(key: &str) -> EcoString { + eco_format!("content does not contain field {:?}", Str::from(key)) } |
