summaryrefslogtreecommitdiff
path: root/src/eval
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-12-20 14:18:29 +0100
committerLaurenz <laurmaedje@gmail.com>2021-12-21 00:20:24 +0100
commit11565a40b315212474f52eb576a9fd92b11f1132 (patch)
treec6b7afb35103065bc92b407094ca905bb75cfc73 /src/eval
parent958f74f77707340f34ee36d09492bdb74523aa2a (diff)
Set Rules Episode IX: The Rise of Testing
Diffstat (limited to 'src/eval')
-rw-r--r--src/eval/mod.rs84
-rw-r--r--src/eval/node.rs31
-rw-r--r--src/eval/styles.rs90
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);
- }
- };
-}