From 25b5bd117529cd04bb789e1988eb3a3db8025a0e Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 7 Mar 2023 15:17:13 +0100 Subject: Fully untyped model --- src/model/content.rs | 412 ++++++++++++++++++++++++--------------------------- src/model/mod.rs | 2 +- src/model/realize.rs | 7 +- src/model/styles.rs | 337 +++++++++++++---------------------------- src/model/typeset.rs | 3 +- 5 files changed, 300 insertions(+), 461 deletions(-) (limited to 'src/model') 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, + id: NodeId, span: Option, + fields: EcoVec<(EcoString, Value)>, modifiers: EcoVec, } @@ -30,55 +27,43 @@ pub struct Content { enum Modifier { Prepared, Guard(Guard), - Label(Label), - Field(EcoString, Value), } impl Content { + pub fn new() -> 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 { 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::() { - styled.sub.span = Some(span); - } else if let Some(styled) = self.to::() { - self = StyledNode { - sub: styled.sub.clone().spanned(span), - map: styled.map.clone(), - } - .pack(); + if let Some(styled) = self.to::() { + 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, 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::() { - styled.map.apply_one(style); - self - } else if let Some(styled) = self.to::() { - 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::() { - styled.map.apply(styles); - return self; + self + } else if let Some(styled) = self.to::() { + 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 { - 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, + value: impl Into, + ) -> 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, value: impl Into) { + 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(&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(&self) -> bool where - T: Capable + 'static, + T: Node + 'static, { - (*self.obj).as_any().is::() + self.id == NodeId::of::() } /// Cast to `T` if the contained node is of type `T`. pub fn to(&self) -> Option<&T> where - T: Capable + 'static, + T: Node + 'static, { - (*self.obj).as_any().downcast_ref::() + self.is::().then(|| unsafe { std::mem::transmute(self) }) } /// Whether this content has the given capability. pub fn has(&self) -> bool where - C: Capability + ?Sized, + C: ?Sized + 'static, { - self.obj.vtable(TypeId::of::()).is_some() + (self.id.0.vtable)(TypeId::of::()).is_some() } /// Cast to a trait object if this content has the given capability. pub fn with(&self) -> Option<&C> where - C: Capability + ?Sized, + C: ?Sized + 'static, { - let node: &dyn Bounds = &*self.obj; - let vtable = node.vtable(TypeId::of::())?; - let data = node as *const dyn Bounds as *const (); + let vtable = (self.id.0.vtable)(TypeId::of::())?; + 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(&mut self) -> Option<&mut T> { - Arc::get_mut(&mut self.obj)?.as_any_mut().downcast_mut::() - } - /// 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::() { + styled.map().fmt(f)?; + styled.sub().fmt(f) + } else if let Some(seq) = self.to::() { + 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::() { - if let Some(rhs_mut) = rhs.to_mut::() { - lhs_mut.0.append(&mut rhs_mut.0); - } else if let Some(rhs) = rhs.to::() { - 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::(), rhs.to::()) { - (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 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(&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); - #[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, } /// 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() -> 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 - 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 - 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 - 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; +impl Hash for NodeId { + fn hash(&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() -> Self { - Self(ReadableTypeId::of::()) +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; +} + +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; + + /// List the settable properties. + fn properties() -> Vec; } /// 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") +} diff --git a/src/model/mod.rs b/src/model/mod.rs index 692d18d5..07329e3f 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -13,4 +13,4 @@ pub use self::typeset::*; #[doc(hidden)] pub use once_cell; -pub use typst_macros::{capability, capable, node}; +pub use typst_macros::node; diff --git a/src/model/realize.rs b/src/model/realize.rs index b33cc0bb..2f38df51 100644 --- a/src/model/realize.rs +++ b/src/model/realize.rs @@ -1,4 +1,4 @@ -use super::{capability, Content, NodeId, Recipe, Selector, StyleChain, Vt}; +use super::{Content, NodeId, Recipe, Selector, StyleChain, Vt}; use crate::diag::SourceResult; /// Whether the target is affected by show rules in the given style chain. @@ -105,7 +105,7 @@ fn try_apply( let mut result = vec![]; let mut cursor = 0; - for m in regex.find_iter(text) { + for m in regex.find_iter(&text) { let start = m.start(); if cursor < start { result.push(make(text[cursor..start].into())); @@ -133,7 +133,6 @@ fn try_apply( } /// Preparations before execution of any show rule. -#[capability] pub trait Prepare { /// Prepare the node for show rule application. fn prepare( @@ -145,7 +144,6 @@ pub trait Prepare { } /// The base recipe for a node. -#[capability] pub trait Show { /// Execute the base recipe for this node. fn show( @@ -157,7 +155,6 @@ pub trait Show { } /// Post-process a node after it was realized. -#[capability] 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 diff --git a/src/model/styles.rs b/src/model/styles.rs index 18507491..cbf4cfb2 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -1,21 +1,16 @@ use std::any::Any; use std::fmt::{self, Debug, Formatter}; -use std::hash::Hash; +use std::hash::{Hash, Hasher}; use std::iter; use std::marker::PhantomData; -use std::sync::Arc; -use comemo::{Prehashed, Tracked}; +use comemo::Tracked; +use ecow::EcoString; -use super::{Content, Label, NodeId}; +use super::{Content, Label, Node, NodeId}; use crate::diag::{SourceResult, Trace, Tracepoint}; -use crate::eval::{Args, Dict, Func, Regex, Value}; -use crate::geom::{ - Abs, Align, Axes, Corners, Em, GenAlign, Length, Numeric, PartialStroke, Rel, Sides, - Smart, -}; +use crate::eval::{cast_from_value, Args, Cast, Dict, Func, Regex, Value}; use crate::syntax::Span; -use crate::util::ReadableTypeId; use crate::World; /// A map of style properties. @@ -76,7 +71,7 @@ impl StyleMap { /// Mark all contained properties as _scoped_. This means that they only /// apply to the first descendant node (of their type) in the hierarchy and /// not its children, too. This is used by - /// [constructors](super::Node::construct). + /// [constructors](super::Construct::construct). pub fn scoped(mut self) -> Self { for entry in &mut self.0 { if let Style::Property(property) = entry { @@ -98,7 +93,7 @@ impl StyleMap { /// Returns `Some(_)` with an optional span if this map contains styles for /// the given `node`. - pub fn interruption(&self) -> Option> { + pub fn interruption(&self) -> Option> { let node = NodeId::of::(); self.0.iter().find_map(|entry| match entry { Style::Property(property) => property.is_of(node).then(|| property.origin), @@ -114,6 +109,12 @@ impl From