From 26bdc1f0f6fe8113d7fcfb4d5aca46aa5238ccd8 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sun, 5 Dec 2021 12:54:03 +0100 Subject: [PATCH] Set Rules Episode I: The Phantom Style --- benches/oneshot.rs | 2 +- src/eval/mod.rs | 147 +++++++++-- src/eval/node.rs | 213 +++++++++++++++ src/eval/ops.rs | 18 +- src/eval/template.rs | 547 --------------------------------------- src/eval/value.rs | 51 +++- src/eval/walk.rs | 141 ---------- src/lib.rs | 2 +- src/library/align.rs | 11 +- src/library/deco.rs | 11 +- src/library/document.rs | 7 +- src/library/flow.rs | 39 ++- src/library/grid.rs | 11 +- src/library/image.rs | 6 +- src/library/mod.rs | 2 +- src/library/pad.rs | 6 +- src/library/page.rs | 99 ++++--- src/library/par.rs | 66 +++-- src/library/placed.rs | 10 +- src/library/shape.rs | 12 +- src/library/sized.rs | 14 +- src/library/spacing.rs | 14 +- src/library/stack.rs | 40 ++- src/library/text.rs | 68 +++-- src/library/transform.rs | 9 +- 25 files changed, 570 insertions(+), 976 deletions(-) create mode 100644 src/eval/node.rs delete mode 100644 src/eval/template.rs delete mode 100644 src/eval/walk.rs diff --git a/benches/oneshot.rs b/benches/oneshot.rs index 6bfb923b4..8f245b779 100644 --- a/benches/oneshot.rs +++ b/benches/oneshot.rs @@ -60,7 +60,7 @@ fn bench_eval(iai: &mut Iai) { fn bench_to_tree(iai: &mut Iai) { let (mut ctx, id) = context(); let module = ctx.evaluate(id).unwrap(); - iai.run(|| module.template.to_document(ctx.style())); + iai.run(|| module.node.clone().into_document()); } fn bench_layout(iai: &mut Iai) { diff --git a/src/eval/mod.rs b/src/eval/mod.rs index a0c31e983..e0143f6ce 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -8,19 +8,17 @@ mod dict; mod value; mod capture; mod function; +mod node; mod ops; mod scope; -mod template; -mod walk; pub use array::*; pub use capture::*; pub use dict::*; pub use function::*; +pub use node::*; pub use scope::*; -pub use template::*; pub use value::*; -pub use walk::*; use std::cell::RefMut; use std::collections::HashMap; @@ -31,29 +29,31 @@ use std::path::PathBuf; use unicode_segmentation::UnicodeSegmentation; use crate::diag::{At, Error, StrResult, Trace, Tracepoint, TypResult}; -use crate::geom::{Angle, Fractional, Length, Relative}; +use crate::geom::{Angle, Fractional, Length, Relative, Spec}; use crate::image::ImageStore; +use crate::library::{GridNode, TrackSizing}; use crate::loading::Loader; use crate::source::{SourceId, SourceStore}; +use crate::style::Style; use crate::syntax::ast::*; use crate::syntax::{Span, Spanned}; -use crate::util::{EcoString, RefMutExt}; +use crate::util::{BoolExt, EcoString, RefMutExt}; use crate::Context; /// Evaluate a parsed source file into a module. pub fn eval(ctx: &mut Context, source: SourceId, markup: &Markup) -> TypResult { let mut ctx = EvalContext::new(ctx, source); - let template = markup.eval(&mut ctx)?; - Ok(Module { scope: ctx.scopes.top, template }) + let node = markup.eval(&mut ctx)?; + Ok(Module { scope: ctx.scopes.top, node }) } /// An evaluated module, ready for importing or instantiation. -#[derive(Debug, Default, Clone)] +#[derive(Debug, Clone)] pub struct Module { /// The top-level definitions that were bound in this module. pub scope: Scope, - /// The template defined by this module. - pub template: Template, + /// The node defined by this module. + pub node: Node, } /// The context for evaluation. @@ -70,8 +70,8 @@ pub struct EvalContext<'a> { pub modules: HashMap, /// The active scopes. pub scopes: Scopes<'a>, - /// The currently built template. - pub template: Template, + /// The active style. + pub style: Style, } impl<'a> EvalContext<'a> { @@ -84,7 +84,7 @@ impl<'a> EvalContext<'a> { route: vec![source], modules: HashMap::new(), scopes: Scopes::new(Some(&ctx.std)), - template: Template::new(), + style: ctx.style.clone(), } } @@ -126,7 +126,7 @@ impl<'a> EvalContext<'a> { self.route.pop().unwrap(); // Save the evaluated module. - let module = Module { scope: new_scopes.top, template }; + let module = Module { scope: new_scopes.top, node: template }; self.modules.insert(id, module); Ok(id) @@ -155,19 +155,116 @@ pub trait Eval { } impl Eval for Markup { - type Output = Template; + type Output = Node; fn eval(&self, ctx: &mut EvalContext) -> TypResult { - Ok({ - let prev = mem::take(&mut ctx.template); - ctx.template.save(); - self.walk(ctx)?; - ctx.template.restore(); - mem::replace(&mut ctx.template, prev) + let snapshot = ctx.style.clone(); + + let mut result = Node::new(); + for piece in self.nodes() { + result += piece.eval(ctx)?; + } + + ctx.style = snapshot; + Ok(result) + } +} + +impl Eval for MarkupNode { + type Output = Node; + + fn eval(&self, ctx: &mut EvalContext) -> TypResult { + Ok(match self { + Self::Space => Node::Space, + Self::Linebreak => Node::Linebreak, + Self::Parbreak => Node::Parbreak, + Self::Strong => { + ctx.style.text_mut().strong.flip(); + Node::new() + } + Self::Emph => { + ctx.style.text_mut().emph.flip(); + Node::new() + } + Self::Text(text) => Node::Text(text.clone()), + Self::Raw(raw) => raw.eval(ctx)?, + Self::Math(math) => math.eval(ctx)?, + Self::Heading(heading) => heading.eval(ctx)?, + Self::List(list) => list.eval(ctx)?, + Self::Enum(enum_) => enum_.eval(ctx)?, + Self::Expr(expr) => expr.eval(ctx)?.display(), }) } } +impl Eval for RawNode { + type Output = Node; + + fn eval(&self, _: &mut EvalContext) -> TypResult { + // TODO(set): Styled in monospace. + let text = Node::Text(self.text.clone()); + Ok(if self.block { + Node::Block(text.into_block()) + } else { + text + }) + } +} + +impl Eval for MathNode { + type Output = Node; + + fn eval(&self, _: &mut EvalContext) -> TypResult { + // TODO(set): Styled in monospace. + let text = Node::Text(self.formula.clone()); + Ok(if self.display { + Node::Block(text.into_block()) + } else { + text + }) + } +} + +impl Eval for HeadingNode { + type Output = Node; + + fn eval(&self, ctx: &mut EvalContext) -> TypResult { + // TODO(set): Styled appropriately. + Ok(Node::Block(self.body().eval(ctx)?.into_block())) + } +} + +impl Eval for ListNode { + type Output = Node; + + fn eval(&self, ctx: &mut EvalContext) -> TypResult { + let body = self.body().eval(ctx)?; + labelled(ctx, '•'.into(), body) + } +} + +impl Eval for EnumNode { + type Output = Node; + + fn eval(&self, ctx: &mut EvalContext) -> TypResult { + 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 { + // 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(6.0).into())], vec![]), + children: vec![Node::Text(label).into_block(), body.into_block()], + })) +} + impl Eval for Expr { type Output = Value; @@ -177,7 +274,7 @@ impl Eval for Expr { Self::Ident(v) => v.eval(ctx), Self::Array(v) => v.eval(ctx).map(Value::Array), Self::Dict(v) => v.eval(ctx).map(Value::Dict), - Self::Template(v) => v.eval(ctx).map(Value::Template), + Self::Template(v) => v.eval(ctx).map(Value::Node), Self::Group(v) => v.eval(ctx), Self::Block(v) => v.eval(ctx), Self::Call(v) => v.eval(ctx), @@ -244,7 +341,7 @@ impl Eval for DictExpr { } impl Eval for TemplateExpr { - type Output = Template; + type Output = Node; fn eval(&self, ctx: &mut EvalContext) -> TypResult { self.body().eval(ctx) @@ -665,7 +762,7 @@ impl Eval for IncludeExpr { let resolved = path.eval(ctx)?.cast::().at(path.span())?; let file = ctx.import(&resolved, path.span())?; let module = &ctx.modules[&file]; - Ok(Value::Template(module.template.clone())) + Ok(Value::Node(module.node.clone())) } } diff --git a/src/eval/node.rs b/src/eval/node.rs new file mode 100644 index 000000000..58b294833 --- /dev/null +++ b/src/eval/node.rs @@ -0,0 +1,213 @@ +use std::convert::TryFrom; +use std::fmt::{self, Debug, Formatter}; +use std::hash::Hash; +use std::mem; +use std::ops::{Add, AddAssign}; + +use crate::diag::StrResult; +use crate::geom::SpecAxis; +use crate::layout::{Layout, PackedNode}; +use crate::library::{ + Decoration, DocumentNode, FlowChild, FlowNode, PageNode, ParChild, ParNode, Spacing, +}; +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 the block or page level. +#[derive(Clone)] +pub enum Node { + /// A word space. + Space, + /// A line break. + Linebreak, + /// A paragraph break. + Parbreak, + /// A page break. + Pagebreak, + /// Plain text. + Text(EcoString), + /// Spacing. + Spacing(SpecAxis, Spacing), + /// An inline node. + Inline(PackedNode), + /// A block node. + Block(PackedNode), + /// A sequence of nodes (which may themselves contain sequences). + Seq(Vec), +} + +impl Node { + /// Create an empty node. + pub fn new() -> Self { + Self::Seq(vec![]) + } + + /// Create an inline-level node. + pub fn inline(node: T) -> Self + where + T: Layout + Debug + Hash + 'static, + { + Self::Inline(node.pack()) + } + + /// Create a block-level node. + pub fn block(node: T) -> Self + where + T: Layout + Debug + Hash + 'static, + { + Self::Block(node.pack()) + } + + /// Decoration this node. + pub fn decorate(self, _: Decoration) -> Self { + // TODO(set): Actually decorate. + self + } + + /// Lift to a type-erased block-level node. + pub fn into_block(self) -> PackedNode { + if let Node::Block(packed) = self { + packed + } else { + let mut packer = NodePacker::new(); + packer.walk(self); + packer.into_block() + } + } + + /// Lift to a document node, the root of the layout tree. + pub fn into_document(self) -> DocumentNode { + let mut packer = NodePacker::new(); + packer.walk(self); + packer.into_document() + } + + /// Repeat this template `n` times. + pub fn repeat(&self, n: i64) -> StrResult { + let count = usize::try_from(n) + .map_err(|_| format!("cannot repeat this template {} times", n))?; + + // TODO(set): Make more efficient. + Ok(Self::Seq(vec![self.clone(); count])) + } +} + +impl Debug for Node { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad("") + } +} + +impl Default for Node { + fn default() -> Self { + Self::new() + } +} + +impl PartialEq for Node { + fn eq(&self, _: &Self) -> bool { + // TODO(set): Figure out what to do here. + false + } +} + +impl Add for Node { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + // TODO(set): Make more efficient. + Self::Seq(vec![self, rhs]) + } +} + +impl AddAssign for Node { + fn add_assign(&mut self, rhs: Self) { + *self = mem::take(self) + rhs; + } +} + +/// Packs a `Node` into a flow or whole document. +struct NodePacker { + document: Vec, + flow: Vec, + par: Vec, +} + +impl NodePacker { + fn new() -> Self { + Self { + document: vec![], + flow: vec![], + par: vec![], + } + } + + fn into_block(mut self) -> PackedNode { + self.parbreak(); + FlowNode(self.flow).pack() + } + + fn into_document(mut self) -> DocumentNode { + self.parbreak(); + self.pagebreak(); + DocumentNode(self.document) + } + + fn walk(&mut self, node: Node) { + match node { + Node::Space => { + self.push_inline(ParChild::Text(' '.into())); + } + Node::Linebreak => { + self.push_inline(ParChild::Text('\n'.into())); + } + Node::Parbreak => { + self.parbreak(); + } + Node::Pagebreak => { + self.pagebreak(); + } + Node::Text(text) => { + self.push_inline(ParChild::Text(text)); + } + Node::Spacing(axis, amount) => match axis { + SpecAxis::Horizontal => self.push_inline(ParChild::Spacing(amount)), + SpecAxis::Vertical => self.push_block(FlowChild::Spacing(amount)), + }, + Node::Inline(inline) => { + self.push_inline(ParChild::Node(inline)); + } + Node::Block(block) => { + self.push_block(FlowChild::Node(block)); + } + Node::Seq(list) => { + for node in list { + self.walk(node); + } + } + } + } + + fn parbreak(&mut self) { + let children = mem::take(&mut self.par); + if !children.is_empty() { + self.flow.push(FlowChild::Node(ParNode(children).pack())); + } + } + + fn pagebreak(&mut self) { + let children = mem::take(&mut self.flow); + self.document.push(PageNode(FlowNode(children).pack())); + } + + fn push_inline(&mut self, child: ParChild) { + self.par.push(child); + } + + fn push_block(&mut self, child: FlowChild) { + self.parbreak(); + self.flow.push(child); + } +} diff --git a/src/eval/ops.rs b/src/eval/ops.rs index ede1230fa..23530c101 100644 --- a/src/eval/ops.rs +++ b/src/eval/ops.rs @@ -22,9 +22,9 @@ pub fn join(lhs: Value, rhs: Value) -> StrResult { (Str(a), Str(b)) => Str(a + b), (Array(a), Array(b)) => Array(a + b), (Dict(a), Dict(b)) => Dict(a + b), - (Template(a), Template(b)) => Template(a + b), - (Template(a), Str(b)) => Template(a + b), - (Str(a), Template(b)) => Template(a + b), + (Node(a), Node(b)) => Node(a + b), + (Node(a), Str(b)) => Node(a + super::Node::Text(b)), + (Str(a), Node(b)) => Node(super::Node::Text(a) + b), (a, b) => mismatch!("cannot join {} with {}", a, b), }) } @@ -84,9 +84,9 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult { (Str(a), Str(b)) => Str(a + b), (Array(a), Array(b)) => Array(a + b), (Dict(a), Dict(b)) => Dict(a + b), - (Template(a), Template(b)) => Template(a + b), - (Template(a), Str(b)) => Template(a + b), - (Str(a), Template(b)) => Template(a + b), + (Node(a), Node(b)) => Node(a + b), + (Node(a), Str(b)) => Node(a + super::Node::Text(b)), + (Str(a), Node(b)) => Node(super::Node::Text(a) + b), (a, b) => { if let (Dyn(a), Dyn(b)) = (&a, &b) { @@ -179,8 +179,8 @@ pub fn mul(lhs: Value, rhs: Value) -> StrResult { (Int(a), Str(b)) => Str(repeat_str(b, a)?), (Array(a), Int(b)) => Array(a.repeat(b)?), (Int(a), Array(b)) => Array(b.repeat(a)?), - (Template(a), Int(b)) => Template(a.repeat(b)?), - (Int(a), Template(b)) => Template(b.repeat(a)?), + (Node(a), Int(b)) => Node(a.repeat(b)?), + (Int(a), Node(b)) => Node(b.repeat(a)?), (a, b) => mismatch!("cannot multiply {} with {}", a, b), }) @@ -297,7 +297,7 @@ pub fn equal(lhs: &Value, rhs: &Value) -> bool { (Str(a), Str(b)) => a == b, (Array(a), Array(b)) => a == b, (Dict(a), Dict(b)) => a == b, - (Template(a), Template(b)) => a == b, + (Node(a), Node(b)) => a == b, (Func(a), Func(b)) => a == b, (Dyn(a), Dyn(b)) => a == b, diff --git a/src/eval/template.rs b/src/eval/template.rs deleted file mode 100644 index 9c57bbf34..000000000 --- a/src/eval/template.rs +++ /dev/null @@ -1,547 +0,0 @@ -use std::convert::TryFrom; -use std::fmt::{self, Debug, Formatter}; -use std::hash::Hash; -use std::mem; -use std::ops::{Add, AddAssign}; -use std::rc::Rc; - -use crate::diag::StrResult; -use crate::geom::{Align, Dir, Length, Linear, Paint, Sides, Size, SpecAxis}; -use crate::layout::{Layout, PackedNode}; -use crate::library::{ - Decoration, DocumentNode, FlowChild, FlowNode, PageNode, ParChild, ParNode, - PlacedNode, Spacing, -}; -use crate::style::Style; -use crate::util::EcoString; - -/// A template value: `[*Hi* there]`. -#[derive(Default, Clone)] -pub struct Template(Rc>); - -/// One node in a template. -#[derive(Clone)] -enum TemplateNode { - /// A word space. - Space, - /// A line break. - Linebreak, - /// A paragraph break. - Parbreak, - /// A page break. - Pagebreak(bool), - /// Plain text. - Text(EcoString), - /// Spacing. - Spacing(SpecAxis, Spacing), - /// A decorated template. - Decorated(Decoration, Template), - /// An inline node builder. - Inline(Rc PackedNode>), - /// A block node builder. - Block(Rc PackedNode>), - /// Save the current style. - Save, - /// Restore the last saved style. - Restore, - /// A function that can modify the current style. - Modify(Rc), -} - -impl Template { - /// Create a new, empty template. - pub fn new() -> Self { - Self(Rc::new(vec![])) - } - - /// Create a template from a builder for an inline-level node. - pub fn from_inline(f: F) -> Self - where - F: Fn(&Style) -> T + 'static, - T: Layout + Debug + Hash + 'static, - { - let node = TemplateNode::Inline(Rc::new(move |s| f(s).pack())); - Self(Rc::new(vec![node])) - } - - /// Create a template from a builder for a block-level node. - pub fn from_block(f: F) -> Self - where - F: Fn(&Style) -> T + 'static, - T: Layout + Debug + Hash + 'static, - { - let node = TemplateNode::Block(Rc::new(move |s| f(s).pack())); - Self(Rc::new(vec![node])) - } - - /// Add a word space to the template. - pub fn space(&mut self) { - self.make_mut().push(TemplateNode::Space); - } - - /// Add a line break to the template. - pub fn linebreak(&mut self) { - self.make_mut().push(TemplateNode::Linebreak); - } - - /// Add a paragraph break to the template. - pub fn parbreak(&mut self) { - self.make_mut().push(TemplateNode::Parbreak); - } - - /// Add a page break to the template. - pub fn pagebreak(&mut self, keep: bool) { - self.make_mut().push(TemplateNode::Pagebreak(keep)); - } - - /// Add text to the template. - pub fn text(&mut self, text: impl Into) { - self.make_mut().push(TemplateNode::Text(text.into())); - } - - /// Add text, but in monospace. - pub fn monospace(&mut self, text: impl Into) { - self.save(); - self.modify(|style| style.text_mut().monospace = true); - self.text(text); - self.restore(); - } - - /// Add spacing along an axis. - pub fn spacing(&mut self, axis: SpecAxis, spacing: Spacing) { - self.make_mut().push(TemplateNode::Spacing(axis, spacing)); - } - - /// Register a restorable snapshot. - pub fn save(&mut self) { - self.make_mut().push(TemplateNode::Save); - } - - /// Ensure that later nodes are untouched by style modifications made since - /// the last snapshot. - pub fn restore(&mut self) { - self.make_mut().push(TemplateNode::Restore); - } - - /// Modify the style. - pub fn modify(&mut self, f: F) - where - F: Fn(&mut Style) + 'static, - { - self.make_mut().push(TemplateNode::Modify(Rc::new(f))); - } - - /// Return a new template which is modified from start to end. - pub fn modified(self, f: F) -> Self - where - F: Fn(&mut Style) + 'static, - { - let mut wrapper = Self::new(); - wrapper.save(); - wrapper.modify(f); - wrapper += self; - wrapper.restore(); - wrapper - } - - /// Add a decoration to all contained nodes. - pub fn decorate(self, deco: Decoration) -> Self { - Self(Rc::new(vec![TemplateNode::Decorated(deco, self)])) - } - - /// Pack the template into a layout node. - pub fn pack(&self, style: &Style) -> PackedNode { - if let [TemplateNode::Block(f)] = self.0.as_slice() { - f(style) - } else { - let mut builder = Builder::new(style, false); - builder.template(self); - builder.build_flow().pack() - } - } - - /// Build the layout tree resulting from instantiating the template with the - /// given style. - pub fn to_document(&self, style: &Style) -> DocumentNode { - let mut builder = Builder::new(style, true); - builder.template(self); - builder.build_document() - } - - /// Repeat this template `n` times. - pub fn repeat(&self, n: i64) -> StrResult { - let count = usize::try_from(n) - .ok() - .and_then(|n| self.0.len().checked_mul(n)) - .ok_or_else(|| format!("cannot repeat this template {} times", n))?; - - Ok(Self(Rc::new( - self.0.iter().cloned().cycle().take(count).collect(), - ))) - } - - /// Return a mutable reference to the inner vector. - fn make_mut(&mut self) -> &mut Vec { - Rc::make_mut(&mut self.0) - } -} - -impl Debug for Template { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad("