diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-03-07 15:17:13 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-03-07 15:17:13 +0100 |
| commit | 25b5bd117529cd04bb789e1988eb3a3db8025a0e (patch) | |
| tree | 2fbb4650903123da047a1f1f11a0abda95286e12 /src/model/content.rs | |
| parent | 6ab7760822ccd24b4ef126d4737d41f1be15fe19 (diff) | |
Fully untyped model
Diffstat (limited to 'src/model/content.rs')
| -rw-r--r-- | src/model/content.rs | 412 |
1 files changed, 192 insertions, 220 deletions
diff --git a/src/model/content.rs b/src/model/content.rs index b10a3409..2af4ae72 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -1,27 +1,24 @@ -use std::any::{Any, TypeId}; -use std::fmt::{self, Debug, Formatter}; +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::sync::Arc; use comemo::Tracked; use ecow::{EcoString, EcoVec}; -use siphasher::sip128::{Hasher128, SipHasher}; -use typst_macros::node; -use super::{capability, capable, Guard, Key, Property, Recipe, Style, StyleMap}; +use super::{node, Guard, Key, Property, Recipe, Style, StyleMap}; use crate::diag::{SourceResult, StrResult}; -use crate::eval::{Args, ParamInfo, Value, Vm}; +use crate::eval::{cast_from_value, Args, Cast, ParamInfo, Value, Vm}; use crate::syntax::Span; -use crate::util::ReadableTypeId; use crate::World; /// Composable representation of styled content. #[derive(Clone, Hash)] pub struct Content { - obj: Arc<dyn Bounds>, + id: NodeId, span: Option<Span>, + fields: EcoVec<(EcoString, Value)>, modifiers: EcoVec<Modifier>, } @@ -30,55 +27,43 @@ pub struct Content { enum Modifier { Prepared, Guard(Guard), - Label(Label), - Field(EcoString, Value), } impl Content { + pub fn new<T: Node>() -> Self { + Self { + id: T::id(), + span: None, + fields: EcoVec::new(), + modifiers: EcoVec::new(), + } + } + /// Create empty content. pub fn empty() -> Self { - SequenceNode(vec![]).pack() + SequenceNode::new(vec![]).pack() } /// Create a new sequence node from multiples nodes. pub fn sequence(seq: Vec<Self>) -> Self { match seq.as_slice() { [_] => seq.into_iter().next().unwrap(), - _ => SequenceNode(seq).pack(), + _ => SequenceNode::new(seq).pack(), } } /// 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(); + if let Some(styled) = self.to::<StyledNode>() { + self = StyledNode::new(styled.sub().spanned(span), styled.map()).pack(); } self.span = Some(span); self } /// Attach a label to the content. - pub fn labelled(mut self, label: Label) -> Self { - for (i, modifier) in self.modifiers.iter().enumerate() { - if matches!(modifier, Modifier::Label(_)) { - self.modifiers.make_mut()[i] = Modifier::Label(label); - return self; - } - } - - self.modifiers.push(Modifier::Label(label)); - self - } - - /// Attach a field to the content. - pub fn push_field(&mut self, name: impl Into<EcoString>, value: Value) { - self.modifiers.push(Modifier::Field(name.into(), value)); + pub fn labelled(self, label: Label) -> Self { + self.with_field("label", label) } /// Style this content with a single style property. @@ -87,31 +72,21 @@ impl Content { } /// Style this content with a style entry. - pub fn styled_with_entry(mut self, style: Style) -> Self { - if let Some(styled) = self.to_mut::<StyledNode>() { - styled.map.apply_one(style); - self - } else if let Some(styled) = self.to::<StyledNode>() { - let mut map = styled.map.clone(); - map.apply_one(style); - StyledNode { sub: styled.sub.clone(), map }.pack() - } else { - StyledNode { sub: self, map: style.into() }.pack() - } + pub fn styled_with_entry(self, style: Style) -> Self { + self.styled_with_map(style.into()) } /// Style this content with a full style map. - pub fn styled_with_map(mut self, styles: StyleMap) -> Self { + pub fn styled_with_map(self, styles: StyleMap) -> Self { if styles.is_empty() { - return self; - } - - if let Some(styled) = self.to_mut::<StyledNode>() { - styled.map.apply(styles); - return self; + self + } else if let Some(styled) = self.to::<StyledNode>() { + let mut map = styled.map(); + map.apply(styles); + StyledNode::new(styled.sub(), map).pack() + } else { + StyledNode::new(self, styles).pack() } - - StyledNode { sub: self, map: styles }.pack() } /// Style this content with a recipe, eagerly applying it if possible. @@ -139,12 +114,12 @@ impl Content { impl Content { /// The id of the contained node. pub fn id(&self) -> NodeId { - (*self.obj).id() + self.id } /// The node's human-readable name. pub fn name(&self) -> &'static str { - (*self.obj).name() + self.id.name() } /// The node's span. @@ -154,72 +129,86 @@ impl Content { /// The content's label. pub fn label(&self) -> Option<&Label> { - self.modifiers.iter().find_map(|modifier| match modifier { - Modifier::Label(label) => Some(label), + match self.field("label")? { + Value::Label(label) => Some(label), _ => None, - }) + } } - /// 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::Label(label.clone()), - None => Value::None, - }); - } + pub fn with_field( + mut self, + name: impl Into<EcoString>, + value: impl Into<Value>, + ) -> Self { + self.push_field(name, value); + self + } - for modifier in &self.modifiers { - if let Modifier::Field(other, value) = modifier { - if name == other { - return Some(value.clone()); - } - } + /// Attach a field to the content. + pub fn push_field(&mut self, name: impl Into<EcoString>, value: impl Into<Value>) { + let name = name.into(); + if let Some(i) = self.fields.iter().position(|(field, _)| *field == name) { + self.fields.make_mut()[i] = (name, value.into()); + } else { + self.fields.push((name, value.into())); } + } - self.obj.field(name) + 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)) + } + + pub fn fields(&self) -> &[(EcoString, Value)] { + &self.fields + } + + #[track_caller] + pub fn cast_field<T: Cast>(&self, name: &str) -> T { + match self.field(name) { + Some(value) => value.clone().cast().unwrap(), + None => field_is_missing(name), + } } /// Whether the contained node is of type `T`. pub fn is<T>(&self) -> bool where - T: Capable + 'static, + T: Node + 'static, { - (*self.obj).as_any().is::<T>() + self.id == NodeId::of::<T>() } /// Cast to `T` if the contained node is of type `T`. pub fn to<T>(&self) -> Option<&T> where - T: Capable + 'static, + T: Node + 'static, { - (*self.obj).as_any().downcast_ref::<T>() + self.is::<T>().then(|| unsafe { std::mem::transmute(self) }) } /// Whether this content has the given capability. pub fn has<C>(&self) -> bool where - C: Capability + ?Sized, + C: ?Sized + 'static, { - self.obj.vtable(TypeId::of::<C>()).is_some() + (self.id.0.vtable)(TypeId::of::<C>()).is_some() } /// Cast to a trait object if this content has the given capability. pub fn with<C>(&self) -> Option<&C> where - C: Capability + ?Sized, + C: ?Sized + 'static, { - let node: &dyn Bounds = &*self.obj; - let vtable = node.vtable(TypeId::of::<C>())?; - let data = node as *const dyn Bounds as *const (); + 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) }) } - /// 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: Guard) -> Self { @@ -262,12 +251,40 @@ impl Content { pub(super) fn copy_modifiers(&mut self, from: &Content) { self.span = from.span; self.modifiers = from.modifiers.clone(); + if let Some(label) = from.label() { + self.push_field("label", label.clone()) + } } } impl Debug for Content { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.obj.fmt(f) + struct Pad<'a>(&'a str); + impl Debug for Pad<'_> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(self.0) + } + } + + if let Some(styled) = self.to::<StyledNode>() { + styled.map().fmt(f)?; + styled.sub().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() + } } } @@ -280,27 +297,19 @@ impl Default for Content { impl Add for Content { type Output = Self; - fn add(self, mut rhs: Self) -> Self::Output { - let mut lhs = self; - 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.to::<SequenceNode>() { - lhs_mut.0.extend(rhs.0.iter().cloned()); - } else { - lhs_mut.0.push(rhs); - } - return lhs; - } - + fn add(self, rhs: Self) -> Self::Output { + let lhs = self; 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(), + (Some(lhs), Some(rhs)) => { + lhs.children().into_iter().chain(rhs.children()).collect() + } + (Some(lhs), None) => { + lhs.children().into_iter().chain(iter::once(rhs)).collect() + } + (None, Some(rhs)) => iter::once(lhs).chain(rhs.children()).collect(), (None, None) => vec![lhs, rhs], }; - - SequenceNode(seq).pack() + SequenceNode::new(seq).pack() } } @@ -316,73 +325,33 @@ impl Sum for Content { } } -trait Bounds: Node + Debug + Sync + Send + 'static { - fn as_any(&self) -> &dyn Any; - fn as_any_mut(&mut self) -> &mut dyn Any; - fn hash128(&self) -> u128; -} - -impl<T> Bounds for T -where - T: Node + Debug + Hash + Sync + Send + 'static, -{ - fn as_any(&self) -> &dyn Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } - - fn hash128(&self) -> u128 { - let mut state = SipHasher::new(); - self.type_id().hash(&mut state); - self.hash(&mut state); - state.finish128().as_u128() - } -} - -impl Hash for dyn Bounds { - fn hash<H: Hasher>(&self, state: &mut H) { - state.write_u128(self.hash128()); - } -} - /// A node with applied styles. -#[capable] -#[derive(Clone, Hash)] +#[node] pub struct StyledNode { /// The styled content. + #[positional] + #[required] pub sub: Content, + /// The styles. + #[positional] + #[required] 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) - } +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. -#[capable] -#[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() - } +pub struct SequenceNode { + #[variadic] + #[required] + pub children: Vec<Content>, } /// A label for a node. @@ -396,80 +365,83 @@ impl Debug for Label { } /// A constructable, stylable content node. -pub trait Node: 'static + Capable { +pub trait Node: Construct + Set + Sized + 'static { + /// The node's ID. + fn id() -> NodeId; + /// Pack a node into type-erased content. - fn pack(self) -> Content - where - Self: Node + Debug + Hash + Sync + Send + Sized + 'static, - { - Content { - obj: Arc::new(self), - span: None, - modifiers: EcoVec::new(), - } - } + fn pack(self) -> Content; +} - /// A unique identifier of the node type. - fn id(&self) -> NodeId; +/// A unique identifier for a node. +#[derive(Copy, Clone)] +pub struct NodeId(&'static NodeMeta); - /// The node's name. - fn name(&self) -> &'static str; +impl NodeId { + pub fn of<T: Node>() -> Self { + T::id() + } - /// Construct a node from the arguments. - /// - /// This is passed only the arguments that remain after execution of the - /// node's set rule. - fn construct(vm: &Vm, args: &mut Args) -> SourceResult<Content> - where - Self: Sized; + pub fn from_meta(meta: &'static NodeMeta) -> Self { + Self(meta) + } - /// Parse relevant arguments into style properties for this node. - /// - /// When `constructor` is true, [`construct`](Self::construct) will run - /// after this invocation of `set` with the remaining arguments. - fn set(args: &mut Args, constructor: bool) -> SourceResult<StyleMap> - where - Self: Sized; + /// The name of the identified node. + pub fn name(self) -> &'static str { + self.0.name + } +} - /// List the settable properties. - fn properties() -> Vec<ParamInfo> - where - Self: Sized; +impl Debug for NodeId { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(self.name()) + } +} - /// Access a field on this node. - fn field(&self, name: &str) -> Option<Value>; +impl Hash for NodeId { + fn hash<H: Hasher>(&self, state: &mut H) { + state.write_usize(self.0 as *const _ as usize); + } } -/// A unique identifier for a node type. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct NodeId(ReadableTypeId); +impl Eq for NodeId {} -impl NodeId { - /// The id of the given node type. - pub fn of<T: 'static>() -> Self { - Self(ReadableTypeId::of::<T>()) +impl PartialEq for NodeId { + fn eq(&self, other: &Self) -> bool { + std::ptr::eq(self.0, other.0) } } -impl Debug for NodeId { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.0.fmt(f) - } +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct NodeMeta { + pub name: &'static str, + pub vtable: fn(of: TypeId) -> Option<*const ()>, } -/// A capability a node can have. -/// -/// Should be implemented by trait objects that are accessible through -/// [`Capable`]. -pub trait Capability: 'static {} - -/// 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 ()>; +pub trait Construct { + /// Construct a node from the arguments. + /// + /// This is passed only the arguments that remain after execution of the + /// node's set rule. + fn construct(vm: &Vm, args: &mut Args) -> SourceResult<Content>; +} + +pub trait Set { + /// Parse relevant arguments into style properties for this node. + /// + /// When `constructor` is true, [`construct`](Construct::construct) will run + /// after this invocation of `set` with the remaining arguments. + fn set(args: &mut Args, constructor: bool) -> SourceResult<StyleMap>; + + /// List the settable properties. + fn properties() -> Vec<ParamInfo>; } /// Indicates that a node cannot be labelled. -#[capability] pub trait Unlabellable {} + +#[cold] +#[track_caller] +fn field_is_missing(name: &str) -> ! { + panic!("required field `{name}` is missing") +} |
