summaryrefslogtreecommitdiff
path: root/src/model/content.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-03-10 20:47:23 +0100
committerLaurenz <laurmaedje@gmail.com>2023-03-10 21:19:50 +0100
commita9fdff244aef859449a76e5f762ee7c343a8ddcc (patch)
tree172b543183296b4bc30b3008650f594688467914 /src/model/content.rs
parent62f35602a87574dcc607f1637aeae1be574981ff (diff)
Expose content representation more
Diffstat (limited to 'src/model/content.rs')
-rw-r--r--src/model/content.rs392
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))
}