diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-12-05 12:54:03 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2021-12-05 12:54:03 +0100 |
| commit | 26bdc1f0f6fe8113d7fcfb4d5aca46aa5238ccd8 (patch) | |
| tree | 4c12a187032501735d858648a64fe66603f106a6 /src/eval | |
| parent | 738ff7e1f573bef678932b313be9969a17af8d22 (diff) | |
Set Rules Episode I: The Phantom Style
Diffstat (limited to 'src/eval')
| -rw-r--r-- | src/eval/mod.rs | 147 | ||||
| -rw-r--r-- | src/eval/node.rs | 213 | ||||
| -rw-r--r-- | src/eval/ops.rs | 18 | ||||
| -rw-r--r-- | src/eval/template.rs | 547 | ||||
| -rw-r--r-- | src/eval/value.rs | 51 | ||||
| -rw-r--r-- | src/eval/walk.rs | 141 |
6 files changed, 386 insertions, 731 deletions
diff --git a/src/eval/mod.rs b/src/eval/mod.rs index a0c31e98..e0143f6c 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<Module> { 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<SourceId, Module>, /// 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<Self::Output> { - 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<Self::Output> { + 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<Self::Output> { + // 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<Self::Output> { + // 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<Self::Output> { + // 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<Self::Output> { + let body = self.body().eval(ctx)?; + labelled(ctx, '•'.into(), body) + } +} + +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(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::Output> { self.body().eval(ctx) @@ -665,7 +762,7 @@ impl Eval for IncludeExpr { let resolved = path.eval(ctx)?.cast::<EcoString>().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 00000000..58b29483 --- /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<Self>), +} + +impl Node { + /// Create an empty node. + pub fn new() -> Self { + Self::Seq(vec![]) + } + + /// Create an inline-level node. + pub fn inline<T>(node: T) -> Self + where + T: Layout + Debug + Hash + 'static, + { + Self::Inline(node.pack()) + } + + /// Create a block-level node. + pub fn block<T>(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<Self> { + 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("<node>") + } +} + +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<PageNode>, + flow: Vec<FlowChild>, + par: Vec<ParChild>, +} + +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 ede1230f..23530c10 100644 --- a/src/eval/ops.rs +++ b/src/eval/ops.rs @@ -22,9 +22,9 @@ pub fn join(lhs: Value, rhs: Value) -> StrResult<Value> { (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<Value> { (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<Value> { (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 9c57bbf3..00000000 --- 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<Vec<TemplateNode>>); - -/// 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<dyn Fn(&Style) -> PackedNode>), - /// A block node builder. - Block(Rc<dyn Fn(&Style) -> PackedNode>), - /// Save the current style. - Save, - /// Restore the last saved style. - Restore, - /// A function that can modify the current style. - Modify(Rc<dyn Fn(&mut Style)>), -} - -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, T>(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, T>(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<EcoString>) { - self.make_mut().push(TemplateNode::Text(text.into())); - } - - /// Add text, but in monospace. - pub fn monospace(&mut self, text: impl Into<EcoString>) { - 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<F>(&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<F>(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<Self> { - 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<TemplateNode> { - Rc::make_mut(&mut self.0) - } -} - -impl Debug for Template { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad("<template>") - } -} - -impl PartialEq for Template { - fn eq(&self, other: &Self) -> bool { - Rc::ptr_eq(&self.0, &other.0) - } -} - -impl Add for Template { - type Output = Self; - - fn add(mut self, rhs: Self) -> Self::Output { - self += rhs; - self - } -} - -impl AddAssign for Template { - fn add_assign(&mut self, rhs: Template) { - let sink = Rc::make_mut(&mut self.0); - match Rc::try_unwrap(rhs.0) { - Ok(source) => sink.extend(source), - Err(rc) => sink.extend(rc.iter().cloned()), - } - } -} - -impl Add<EcoString> for Template { - type Output = Self; - - fn add(mut self, rhs: EcoString) -> Self::Output { - Rc::make_mut(&mut self.0).push(TemplateNode::Text(rhs)); - self - } -} - -impl Add<Template> for EcoString { - type Output = Template; - - fn add(self, mut rhs: Template) -> Self::Output { - Rc::make_mut(&mut rhs.0).insert(0, TemplateNode::Text(self)); - rhs - } -} - -/// Transforms from template to layout representation. -struct Builder { - /// The current style. - style: Style, - /// Snapshots of the style. - snapshots: Vec<Style>, - /// The finished page nodes. - finished: Vec<PageNode>, - /// When we are building the top-level layout trees, this contains metrics - /// of the page. While building a flow, this is `None`. - page: Option<PageBuilder>, - /// The currently built flow of paragraphs. - flow: FlowBuilder, -} - -impl Builder { - /// Create a new builder with a base style. - fn new(style: &Style, pages: bool) -> Self { - Self { - style: style.clone(), - snapshots: vec![], - finished: vec![], - page: pages.then(|| PageBuilder::new(style, true)), - flow: FlowBuilder::new(style), - } - } - - /// Build a template. - fn template(&mut self, template: &Template) { - for node in template.0.iter() { - self.node(node); - } - } - - /// Build a template node. - fn node(&mut self, node: &TemplateNode) { - match node { - TemplateNode::Save => self.snapshots.push(self.style.clone()), - TemplateNode::Restore => { - let style = self.snapshots.pop().unwrap(); - let newpage = style.page != self.style.page; - self.style = style; - if newpage { - self.pagebreak(true, false); - } - } - TemplateNode::Space => self.space(), - TemplateNode::Linebreak => self.linebreak(), - TemplateNode::Parbreak => self.parbreak(), - TemplateNode::Pagebreak(keep) => self.pagebreak(*keep, true), - TemplateNode::Text(text) => self.text(text), - TemplateNode::Spacing(axis, amount) => self.spacing(*axis, *amount), - TemplateNode::Decorated(deco, template) => { - self.flow.par.push(ParChild::Decorate(deco.clone())); - self.template(template); - self.flow.par.push(ParChild::Undecorate); - } - TemplateNode::Inline(f) => self.inline(f(&self.style)), - TemplateNode::Block(f) => self.block(f(&self.style)), - TemplateNode::Modify(f) => f(&mut self.style), - } - } - - /// Push a word space into the active paragraph. - fn space(&mut self) { - self.flow.par.push_soft(self.make_text_node(' ')); - } - - /// Apply a forced line break. - fn linebreak(&mut self) { - self.flow.par.push_hard(self.make_text_node('\n')); - } - - /// Apply a forced paragraph break. - fn parbreak(&mut self) { - let amount = self.style.par_spacing(); - self.flow.finish_par(&self.style); - self.flow - .push_soft(FlowChild::Spacing(Spacing::Linear(amount.into()))); - } - - /// Apply a forced page break. - fn pagebreak(&mut self, keep: bool, hard: bool) { - if let Some(builder) = &mut self.page { - let page = mem::replace(builder, PageBuilder::new(&self.style, hard)); - let flow = mem::replace(&mut self.flow, FlowBuilder::new(&self.style)); - self.finished.extend(page.build(flow.build(), keep)); - } - } - - /// Push text into the active paragraph. - fn text(&mut self, text: impl Into<EcoString>) { - self.flow.par.push(self.make_text_node(text)); - } - - /// Push an inline node into the active paragraph. - fn inline(&mut self, node: PackedNode) { - self.flow.par.push(ParChild::Node(node.into())); - } - - /// Push a block node into the active flow, finishing the active paragraph. - fn block(&mut self, node: PackedNode) { - let mut is_placed = false; - if let Some(placed) = node.downcast::<PlacedNode>() { - is_placed = true; - - // This prevents paragraph spacing after the placed node if it - // is completely out-of-flow. - if placed.out_of_flow() { - self.flow.last = Last::None; - } - } - - self.parbreak(); - self.flow.push(FlowChild::Node(node)); - self.parbreak(); - - // This prevents paragraph spacing between the placed node and - // the paragraph below it. - if is_placed { - self.flow.last = Last::None; - } - } - - /// Push spacing into the active paragraph or flow depending on the `axis`. - fn spacing(&mut self, axis: SpecAxis, spacing: Spacing) { - match axis { - SpecAxis::Vertical => { - self.flow.finish_par(&self.style); - self.flow.push_hard(FlowChild::Spacing(spacing)); - } - SpecAxis::Horizontal => { - self.flow.par.push_hard(ParChild::Spacing(spacing)); - } - } - } - - /// Finish building and return the created flow. - fn build_flow(self) -> FlowNode { - assert!(self.page.is_none()); - self.flow.build() - } - - /// Finish building and return the created layout tree. - fn build_document(mut self) -> DocumentNode { - assert!(self.page.is_some()); - self.pagebreak(true, false); - DocumentNode { pages: self.finished } - } - - /// Construct a text node with the given text and settings from the current - /// style. - fn make_text_node(&self, text: impl Into<EcoString>) -> ParChild { - ParChild::Text(text.into(), Rc::clone(&self.style.text)) - } -} - -struct PageBuilder { - size: Size, - padding: Sides<Linear>, - fill: Option<Paint>, - hard: bool, -} - -impl PageBuilder { - fn new(style: &Style, hard: bool) -> Self { - Self { - size: style.page.size, - padding: style.page.margins(), - fill: style.page.fill, - hard, - } - } - - fn build(self, child: FlowNode, keep: bool) -> Option<PageNode> { - let Self { size, padding, fill, hard } = self; - (!child.children.is_empty() || (keep && hard)).then(|| PageNode { - child: child.pack().padded(padding), - size, - fill, - }) - } -} - -struct FlowBuilder { - children: Vec<FlowChild>, - last: Last<FlowChild>, - par: ParBuilder, -} - -impl FlowBuilder { - fn new(style: &Style) -> Self { - Self { - children: vec![], - last: Last::None, - par: ParBuilder::new(style), - } - } - - fn push(&mut self, child: FlowChild) { - self.children.extend(self.last.any()); - self.children.push(child); - } - - fn push_soft(&mut self, child: FlowChild) { - self.last.soft(child); - } - - fn push_hard(&mut self, child: FlowChild) { - self.last.hard(); - self.children.push(child); - } - - fn finish_par(&mut self, style: &Style) { - let par = mem::replace(&mut self.par, ParBuilder::new(style)); - if let Some(par) = par.build() { - self.push(par); - } - } - - fn build(self) -> FlowNode { - let Self { mut children, par, mut last } = self; - if let Some(par) = par.build() { - children.extend(last.any()); - children.push(par); - } - FlowNode { children } - } -} - -struct ParBuilder { - dir: Dir, - align: Align, - leading: Length, - children: Vec<ParChild>, - last: Last<ParChild>, -} - -impl ParBuilder { - fn new(style: &Style) -> Self { - Self { - dir: style.par.dir, - align: style.par.align, - leading: style.leading(), - children: vec![], - last: Last::None, - } - } - - fn push(&mut self, child: ParChild) { - if let Some(soft) = self.last.any() { - self.push_inner(soft); - } - self.push_inner(child); - } - - fn push_soft(&mut self, child: ParChild) { - self.last.soft(child); - } - - fn push_hard(&mut self, child: ParChild) { - self.last.hard(); - self.push_inner(child); - } - - fn push_inner(&mut self, child: ParChild) { - if let ParChild::Text(text2, style2) = &child { - if let Some(ParChild::Text(text1, style1)) = self.children.last_mut() { - if Rc::ptr_eq(style1, style2) { - text1.push_str(text2); - return; - } - } - } - - self.children.push(child); - } - - fn build(self) -> Option<FlowChild> { - let Self { dir, align, leading, children, .. } = self; - (!children.is_empty()) - .then(|| FlowChild::Node(ParNode { dir, align, leading, children }.pack())) - } -} - -/// Finite state machine for spacing coalescing. -enum Last<N> { - None, - Any, - Soft(N), -} - -impl<N> Last<N> { - fn any(&mut self) -> Option<N> { - match mem::replace(self, Self::Any) { - Self::Soft(soft) => Some(soft), - _ => None, - } - } - - fn soft(&mut self, soft: N) { - if let Self::Any = self { - *self = Self::Soft(soft); - } - } - - fn hard(&mut self) { - *self = Self::None; - } -} diff --git a/src/eval/value.rs b/src/eval/value.rs index 16e8b810..a6230956 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -1,11 +1,13 @@ use std::any::Any; use std::cmp::Ordering; use std::fmt::{self, Debug, Formatter}; +use std::hash::Hash; use std::rc::Rc; -use super::{ops, Array, Dict, Function, Template}; +use super::{ops, Array, Dict, Function, Node}; use crate::diag::StrResult; use crate::geom::{Angle, Color, Fractional, Length, Linear, Relative, RgbaColor}; +use crate::layout::Layout; use crate::syntax::Spanned; use crate::util::EcoString; @@ -40,8 +42,8 @@ pub enum Value { Array(Array), /// A dictionary value: `(color: #f79143, pattern: dashed)`. Dict(Dict), - /// A template value: `[*Hi* there]`. - Template(Template), + /// A node value: `[*Hi* there]`. + Node(Node), /// An executable function. Func(Function), /// A dynamic value. @@ -49,6 +51,22 @@ pub enum Value { } impl Value { + /// Create an inline-level node value. + pub fn inline<T>(node: T) -> Self + where + T: Layout + Debug + Hash + 'static, + { + Self::Node(Node::inline(node)) + } + + /// Create a block-level node value. + pub fn block<T>(node: T) -> Self + where + T: Layout + Debug + Hash + 'static, + { + Self::Node(Node::block(node)) + } + /// The name of the stored value's type. pub fn type_name(&self) -> &'static str { match self { @@ -66,7 +84,7 @@ impl Value { Self::Str(_) => EcoString::TYPE_NAME, Self::Array(_) => Array::TYPE_NAME, Self::Dict(_) => Dict::TYPE_NAME, - Self::Template(_) => Template::TYPE_NAME, + Self::Node(_) => Node::TYPE_NAME, Self::Func(_) => Function::TYPE_NAME, Self::Dyn(v) => v.type_name(), } @@ -80,14 +98,29 @@ impl Value { T::cast(self) } + /// Join the value with another value. + pub fn join(self, rhs: Self) -> StrResult<Self> { + ops::join(self, rhs) + } + /// Return the debug representation of the value. pub fn repr(&self) -> EcoString { format_eco!("{:?}", self) } - /// Join the value with another value. - pub fn join(self, rhs: Self) -> StrResult<Self> { - ops::join(self, rhs) + /// Return the display representation of a value in form of a node. + pub fn display(self) -> Node { + match self { + Value::None => Node::new(), + Value::Int(v) => Node::Text(format_eco!("{}", v)), + Value::Float(v) => Node::Text(format_eco!("{}", v)), + Value::Str(v) => Node::Text(v), + Value::Node(v) => v, + // For values which can't be shown "naturally", we print the + // representation in monospace. + // TODO(set): Styled in monospace. + v => Node::Text(v.repr()), + } } } @@ -114,7 +147,7 @@ impl Debug for Value { Self::Str(v) => Debug::fmt(v, f), Self::Array(v) => Debug::fmt(v, f), Self::Dict(v) => Debug::fmt(v, f), - Self::Template(v) => Debug::fmt(v, f), + Self::Node(v) => Debug::fmt(v, f), Self::Func(v) => Debug::fmt(v, f), Self::Dyn(v) => Debug::fmt(v, f), } @@ -360,7 +393,7 @@ primitive! { Color: "color", Color } primitive! { EcoString: "string", Str } primitive! { Array: "array", Array } primitive! { Dict: "dictionary", Dict } -primitive! { Template: "template", Template } +primitive! { Node: "node", Node } primitive! { Function: "function", Func } impl Cast<Value> for Value { diff --git a/src/eval/walk.rs b/src/eval/walk.rs deleted file mode 100644 index 0898f20b..00000000 --- a/src/eval/walk.rs +++ /dev/null @@ -1,141 +0,0 @@ -use std::rc::Rc; - -use super::{Eval, EvalContext, Template, Value}; -use crate::diag::TypResult; -use crate::geom::Spec; -use crate::layout::Layout; -use crate::library::{GridNode, ParChild, ParNode, TrackSizing}; -use crate::syntax::ast::*; -use crate::util::{BoolExt, EcoString}; - -/// Walk markup, filling the currently built template. -pub trait Walk { - /// Walk the node. - fn walk(&self, ctx: &mut EvalContext) -> TypResult<()>; -} - -impl Walk for Markup { - fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> { - for node in self.nodes() { - node.walk(ctx)?; - } - Ok(()) - } -} - -impl Walk for MarkupNode { - fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> { - match self { - Self::Space => ctx.template.space(), - Self::Linebreak => ctx.template.linebreak(), - Self::Parbreak => ctx.template.parbreak(), - Self::Strong => ctx.template.modify(|s| s.text_mut().strong.flip()), - Self::Emph => ctx.template.modify(|s| s.text_mut().emph.flip()), - Self::Text(text) => ctx.template.text(text), - Self::Raw(raw) => raw.walk(ctx)?, - Self::Math(math) => math.walk(ctx)?, - Self::Heading(heading) => heading.walk(ctx)?, - Self::List(list) => list.walk(ctx)?, - Self::Enum(enum_) => enum_.walk(ctx)?, - Self::Expr(expr) => match expr.eval(ctx)? { - Value::None => {} - Value::Int(v) => ctx.template.text(format_eco!("{}", v)), - Value::Float(v) => ctx.template.text(format_eco!("{}", v)), - Value::Str(v) => ctx.template.text(v), - Value::Template(v) => ctx.template += v, - // For values which can't be shown "naturally", we print the - // representation in monospace. - other => ctx.template.monospace(other.repr()), - }, - } - Ok(()) - } -} - -impl Walk for RawNode { - fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> { - if self.block { - ctx.template.parbreak(); - } - - ctx.template.monospace(&self.text); - - if self.block { - ctx.template.parbreak(); - } - - Ok(()) - } -} - -impl Walk for MathNode { - fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> { - if self.display { - ctx.template.parbreak(); - } - - ctx.template.monospace(self.formula.trim()); - - if self.display { - ctx.template.parbreak(); - } - - Ok(()) - } -} - -impl Walk for HeadingNode { - fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> { - let level = self.level(); - let body = self.body().eval(ctx)?; - - ctx.template.parbreak(); - ctx.template.save(); - ctx.template.modify(move |style| { - let text = style.text_mut(); - let upscale = (1.6 - 0.1 * level as f64).max(0.75); - text.size *= upscale; - text.strong = true; - }); - ctx.template += body; - ctx.template.restore(); - ctx.template.parbreak(); - - Ok(()) - } -} - -impl Walk for ListNode { - fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> { - let body = self.body().eval(ctx)?; - walk_item(ctx, EcoString::from('•'), body); - Ok(()) - } -} - -impl Walk for EnumNode { - fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> { - let body = self.body().eval(ctx)?; - let label = format_eco!("{}.", self.number().unwrap_or(1)); - walk_item(ctx, label, body); - Ok(()) - } -} - -fn walk_item(ctx: &mut EvalContext, label: EcoString, body: Template) { - ctx.template += Template::from_block(move |style| { - let label = Layout::pack(ParNode { - dir: style.par.dir, - align: style.par.align, - leading: style.leading(), - children: vec![ParChild::Text(label.clone(), Rc::clone(&style.text))], - }); - - let spacing = style.text.size / 2.0; - GridNode { - tracks: Spec::new(vec![TrackSizing::Auto; 2], vec![]), - gutter: Spec::new(vec![TrackSizing::Linear(spacing.into())], vec![]), - children: vec![label, body.pack(style)], - } - }); -} |
