diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-12-20 14:18:29 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2021-12-21 00:20:24 +0100 |
| commit | 11565a40b315212474f52eb576a9fd92b11f1132 (patch) | |
| tree | c6b7afb35103065bc92b407094ca905bb75cfc73 /src/eval | |
| parent | 958f74f77707340f34ee36d09492bdb74523aa2a (diff) | |
Set Rules Episode IX: The Rise of Testing
Diffstat (limited to 'src/eval')
| -rw-r--r-- | src/eval/mod.rs | 84 | ||||
| -rw-r--r-- | src/eval/node.rs | 31 | ||||
| -rw-r--r-- | src/eval/styles.rs | 90 |
3 files changed, 100 insertions, 105 deletions
diff --git a/src/eval/mod.rs b/src/eval/mod.rs index ae330134..d05f2ddf 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -34,9 +34,10 @@ use std::path::PathBuf; use unicode_segmentation::UnicodeSegmentation; use crate::diag::{At, Error, StrResult, Trace, Tracepoint, TypResult}; -use crate::geom::{Angle, Fractional, Length, Relative, Spec}; +use crate::geom::{Angle, Fractional, Length, Relative}; use crate::image::ImageStore; -use crate::library::{GridNode, TextNode, TrackSizing}; +use crate::layout::RootNode; +use crate::library::{self, TextNode}; use crate::loading::Loader; use crate::source::{SourceId, SourceStore}; use crate::syntax::ast::*; @@ -44,15 +45,9 @@ use crate::syntax::{Span, Spanned}; use crate::util::{EcoString, RefMutExt}; use crate::Context; -/// Evaluate a parsed source file into a module. -pub fn eval(ctx: &mut Context, source: SourceId, markup: &Markup) -> TypResult<Module> { - let mut ctx = EvalContext::new(ctx, source); - let node = markup.eval(&mut ctx)?; - Ok(Module { scope: ctx.scopes.top, node }) -} - -/// An evaluated module, ready for importing or instantiation. -#[derive(Debug, Clone)] +/// An evaluated module, ready for importing or conversion to a root layout +/// tree. +#[derive(Debug, Default, Clone)] pub struct Module { /// The top-level definitions that were bound in this module. pub scope: Scope, @@ -60,6 +55,22 @@ pub struct Module { pub node: Node, } +impl Module { + /// Convert this module's node into a layout tree. + pub fn into_root(self) -> RootNode { + self.node.into_root() + } +} + +/// Evaluate an expression. +pub trait Eval { + /// The output of evaluating the expression. + type Output; + + /// Evaluate the expression to the output value. + fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output>; +} + /// The context for evaluation. pub struct EvalContext<'a> { /// The loader from which resources (files and images) are loaded. @@ -124,7 +135,7 @@ impl<'a> EvalContext<'a> { self.route.push(id); // Evaluate the module. - let template = ast.eval(self).trace(|| Tracepoint::Import, span)?; + let node = ast.eval(self).trace(|| Tracepoint::Import, span)?; // Restore the old context. let new_scopes = mem::replace(&mut self.scopes, prev_scopes); @@ -132,7 +143,7 @@ impl<'a> EvalContext<'a> { self.route.pop().unwrap(); // Save the evaluated module. - let module = Module { scope: new_scopes.top, node: template }; + let module = Module { scope: new_scopes.top, node }; self.modules.insert(id, module); Ok(id) @@ -151,15 +162,6 @@ impl<'a> EvalContext<'a> { } } -/// Evaluate an expression. -pub trait Eval { - /// The output of evaluating the expression. - type Output; - - /// Evaluate the expression to the output value. - fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output>; -} - impl Eval for Markup { type Output = Node; @@ -231,13 +233,10 @@ impl Eval for HeadingNode { type Output = Node; fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> { - let upscale = (1.6 - 0.1 * self.level() as f64).max(0.75); - let mut styles = Styles::new(); - styles.set(TextNode::STRONG, true); - styles.set(TextNode::SIZE, Relative::new(upscale).into()); - Ok(Node::Block( - self.body().eval(ctx)?.into_block().styled(styles), - )) + Ok(Node::block(library::HeadingNode { + child: self.body().eval(ctx)?.into_block(), + level: self.level(), + })) } } @@ -245,8 +244,10 @@ impl Eval for ListNode { type Output = Node; fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> { - let body = self.body().eval(ctx)?; - labelled(ctx, '•'.into(), body) + Ok(Node::block(library::ListNode { + child: self.body().eval(ctx)?.into_block(), + labelling: library::Unordered, + })) } } @@ -254,22 +255,11 @@ impl Eval for EnumNode { type Output = Node; fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> { - let body = self.body().eval(ctx)?; - let label = format_eco!("{}.", self.number().unwrap_or(1)); - labelled(ctx, label, body) - } -} - -/// Evaluate a labelled list / enum. -fn labelled(_: &mut EvalContext, label: EcoString, body: Node) -> TypResult<Node> { - // Create a grid containing the label, a bit of gutter space and then - // the item's body. - // TODO: Switch to em units for gutter once available. - Ok(Node::block(GridNode { - tracks: Spec::new(vec![TrackSizing::Auto; 2], vec![]), - gutter: Spec::new(vec![TrackSizing::Linear(Length::pt(5.0).into())], vec![]), - children: vec![Node::Text(label).into_block(), body.into_block()], - })) + Ok(Node::block(library::ListNode { + child: self.body().eval(ctx)?.into_block(), + labelling: library::Ordered(self.number()), + })) + } } impl Eval for Expr { diff --git a/src/eval/node.rs b/src/eval/node.rs index acdf4ed6..e2b02955 100644 --- a/src/eval/node.rs +++ b/src/eval/node.rs @@ -8,17 +8,18 @@ use std::ops::{Add, AddAssign}; use super::Styles; use crate::diag::StrResult; use crate::geom::SpecAxis; -use crate::layout::{Layout, PackedNode}; +use crate::layout::{Layout, PackedNode, RootNode}; use crate::library::{ - DocumentNode, FlowChild, FlowNode, PageNode, ParChild, ParNode, PlacedNode, - SpacingKind, SpacingNode, TextNode, + FlowChild, FlowNode, PageNode, ParChild, ParNode, PlacedNode, SpacingKind, + SpacingNode, TextNode, }; use crate::util::EcoString; /// A partial representation of a layout node. /// /// A node is a composable intermediate representation that can be converted -/// into a proper layout node by lifting it to a block-level or document node. +/// into a proper layout node by lifting it to a [block-level](PackedNode) or +/// [root node](RootNode). #[derive(Debug, PartialEq, Clone, Hash)] pub enum Node { /// A word space. @@ -90,19 +91,19 @@ impl Node { } } - /// Lift to a document node, the root of the layout tree. - pub fn into_document(self) -> DocumentNode { + /// Lift to a root layout tree node. + pub fn into_root(self) -> RootNode { let mut packer = Packer::new(true); packer.walk(self, Styles::new()); - packer.into_document() + packer.into_root() } - /// Repeat this template `n` times. + /// Repeat this node `n` times. pub fn repeat(&self, n: i64) -> StrResult<Self> { let count = usize::try_from(n) .map_err(|_| format!("cannot repeat this template {} times", n))?; - // TODO(set): Make more efficient. + // TODO(style): Make more efficient. Ok(Self::Sequence(vec![(self.clone(), Styles::new()); count])) } } @@ -117,7 +118,7 @@ impl Add for Node { type Output = Self; fn add(self, rhs: Self) -> Self::Output { - // TODO(set): Make more efficient. + // TODO(style): Make more efficient. Self::Sequence(vec![(self, Styles::new()), (rhs, Styles::new())]) } } @@ -134,9 +135,9 @@ impl Sum for Node { } } -/// Packs a [`Node`] into a flow or whole document. +/// Packs a [`Node`] into a flow or root node. struct Packer { - /// Whether this packer produces the top-level document. + /// Whether this packer produces a root node. top: bool, /// The accumulated page nodes. pages: Vec<PageNode>, @@ -163,10 +164,10 @@ impl Packer { FlowNode(self.flow.children).pack() } - /// Finish up and return the resulting document. - fn into_document(mut self) -> DocumentNode { + /// Finish up and return the resulting root node. + fn into_root(mut self) -> RootNode { self.pagebreak(); - DocumentNode(self.pages) + RootNode(self.pages) } /// Consider a node with the given styles. diff --git a/src/eval/styles.rs b/src/eval/styles.rs index 2396646f..5304e0ad 100644 --- a/src/eval/styles.rs +++ b/src/eval/styles.rs @@ -20,6 +20,11 @@ impl Styles { Self { map: vec![] } } + /// Whether this map contains no styles. + pub fn is_empty(&self) -> bool { + self.map.is_empty() + } + /// Create a style map with a single property-value pair. pub fn one<P: Property>(key: P, value: P::Value) -> Self { let mut styles = Self::new(); @@ -27,11 +32,6 @@ impl Styles { styles } - /// Whether this map contains no styles. - pub fn is_empty(&self) -> bool { - self.map.is_empty() - } - /// Set the value for a style property. pub fn set<P: Property>(&mut self, key: P, value: P::Value) { let id = StyleId::of::<P>(); @@ -47,6 +47,13 @@ impl Styles { self.map.push((id, Entry::new(key, value))); } + /// Set a value for a style property if it is `Some(_)`. + pub fn set_opt<P: Property>(&mut self, key: P, value: Option<P::Value>) { + if let Some(value) = value { + self.set(key, value); + } + } + /// Toggle a boolean style property. pub fn toggle<P: Property<Value = bool>>(&mut self, key: P) { let id = StyleId::of::<P>(); @@ -82,13 +89,22 @@ impl Styles { } /// Get a reference to a style directly in this map (no default value). - pub fn get_direct<P: Property>(&self, _: P) -> Option<&P::Value> { + fn get_direct<P: Property>(&self, _: P) -> Option<&P::Value> { self.map .iter() .find(|pair| pair.0 == StyleId::of::<P>()) .and_then(|pair| pair.1.downcast()) } + /// Create new styles combining `self` with `outer`. + /// + /// Properties from `self` take precedence over the ones from `outer`. + pub fn chain(&self, outer: &Self) -> Self { + let mut styles = self.clone(); + styles.apply(outer); + styles + } + /// Apply styles from `outer` in-place. /// /// Properties from `self` take precedence over the ones from `outer`. @@ -105,13 +121,9 @@ impl Styles { } } - /// Create new styles combining `self` with `outer`. - /// - /// Properties from `self` take precedence over the ones from `outer`. - pub fn chain(&self, outer: &Self) -> Self { - let mut styles = self.clone(); - styles.apply(outer); - styles + /// Keep only those styles that are not also in `other`. + pub fn erase(&mut self, other: &Self) { + self.map.retain(|a| other.map.iter().all(|b| a != b)); } /// Keep only those styles that are also in `other`. @@ -119,18 +131,13 @@ impl Styles { self.map.retain(|a| other.map.iter().any(|b| a == b)); } - /// Keep only those styles that are not also in `other`. - pub fn erase(&mut self, other: &Self) { - self.map.retain(|a| other.map.iter().all(|b| a != b)); - } - /// Whether two style maps are equal when filtered down to the given /// properties. pub fn compatible<F>(&self, other: &Self, filter: F) -> bool where F: Fn(StyleId) -> bool, { - // TODO(set): Filtered length + one direction equal should suffice. + // TODO(style): Filtered length + one direction equal should suffice. let f = |e: &&(StyleId, Entry)| filter(e.0); self.map.iter().filter(f).all(|pair| other.map.contains(pair)) && other.map.iter().filter(f).all(|pair| self.map.contains(pair)) @@ -177,7 +184,7 @@ impl Entry { impl Debug for Entry { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.0.fmt(f) + self.0.dyn_fmt(f) } } @@ -195,22 +202,23 @@ impl Hash for Entry { trait Bounds: 'static { fn as_any(&self) -> &dyn Any; - fn fmt(&self, f: &mut Formatter) -> fmt::Result; + fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result; fn dyn_eq(&self, other: &Entry) -> bool; fn hash64(&self) -> u64; fn combine(&self, outer: &Entry) -> Entry; } -impl<P> Bounds for (P, P::Value) -where - P: Property, - P::Value: Debug + Hash + PartialEq + 'static, -{ +// `P` is always zero-sized. We only implement the trait for a pair of key and +// associated value so that `P` is a constrained type parameter that we can use +// in `dyn_fmt` to access the property's name. This way, we can effectively +// store the property's name in its vtable instead of having an actual runtime +// string somewhere in `Entry`. +impl<P: Property> Bounds for (P, P::Value) { fn as_any(&self) -> &dyn Any { &self.1 } - fn fmt(&self, f: &mut Formatter) -> fmt::Result { + fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result { if f.alternate() { write!(f, "#[{} = {:?}]", P::NAME, self.1) } else { @@ -242,11 +250,12 @@ where /// Style property keys. /// /// This trait is not intended to be implemented manually, but rather through -/// the `properties!` macro. +/// the `#[properties]` proc-macro. pub trait Property: Copy + 'static { - /// The type of this property, for example, this could be - /// [`Length`](crate::geom::Length) for a `WIDTH` property. - type Value: Debug + Clone + Hash + PartialEq + 'static; + /// The type of value that is returned when getting this property from a + /// style map. For example, this could be [`Length`](crate::geom::Length) + /// for a `WIDTH` property. + type Value: Debug + Clone + PartialEq + Hash + 'static; /// The name of the property, used for debug printing. const NAME: &'static str; @@ -257,12 +266,16 @@ pub trait Property: Copy + 'static { /// A static reference to the default value of the property. /// /// This is automatically implemented through lazy-initialization in the - /// `properties!` macro. This way, expensive defaults don't need to be + /// `#[properties]` macro. This way, expensive defaults don't need to be /// recreated all the time. fn default_ref() -> &'static Self::Value; - /// Combine the property with an outer value. - fn combine(inner: Self::Value, _: Self::Value) -> Self::Value { + /// Fold the property with an outer value. + /// + /// For example, this would combine a relative font size with an outer + /// absolute font size. + #[allow(unused_variables)] + fn combine(inner: Self::Value, outer: Self::Value) -> Self::Value { inner } } @@ -277,12 +290,3 @@ impl StyleId { Self(TypeId::of::<P>()) } } - -/// Set a style property to a value if the value is `Some`. -macro_rules! set { - ($styles:expr, $target:expr => $value:expr) => { - if let Some(v) = $value { - $styles.set($target, v); - } - }; -} |
