diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/eval/mod.rs | 201 | ||||
| -rw-r--r-- | src/eval/state.rs (renamed from src/exec/state.rs) | 33 | ||||
| -rw-r--r-- | src/eval/template.rs | 490 | ||||
| -rw-r--r-- | src/eval/value.rs | 11 | ||||
| -rw-r--r-- | src/exec/context.rs | 302 | ||||
| -rw-r--r-- | src/exec/mod.rs | 173 | ||||
| -rw-r--r-- | src/layout/par.rs | 2 | ||||
| -rw-r--r-- | src/layout/shaping.rs | 2 | ||||
| -rw-r--r-- | src/lib.rs | 33 | ||||
| -rw-r--r-- | src/library/elements.rs | 49 | ||||
| -rw-r--r-- | src/library/layout.rs | 169 | ||||
| -rw-r--r-- | src/library/mod.rs | 1 | ||||
| -rw-r--r-- | src/library/text.rs | 114 | ||||
| -rw-r--r-- | src/parse/mod.rs | 27 | ||||
| -rw-r--r-- | src/syntax/expr.rs | 4 | ||||
| -rw-r--r-- | src/syntax/node.rs | 4 | ||||
| -rw-r--r-- | src/syntax/pretty.rs | 2 | ||||
| -rw-r--r-- | src/syntax/visit.rs | 4 |
18 files changed, 783 insertions, 838 deletions
diff --git a/src/eval/mod.rs b/src/eval/mod.rs index d8ce7884..d4989371 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -1,4 +1,4 @@ -//! Evaluation of syntax trees. +//! Evaluation of syntax trees into modules. #[macro_use] mod array; @@ -10,6 +10,7 @@ mod capture; mod function; mod ops; mod scope; +mod state; mod str; mod template; @@ -19,39 +20,38 @@ pub use capture::*; pub use dict::*; pub use function::*; pub use scope::*; +pub use state::*; pub use template::*; pub use value::*; use std::cell::RefMut; use std::collections::HashMap; +use std::fmt::Write; use std::io; use std::mem; use std::path::PathBuf; use std::rc::Rc; use crate::diag::{At, Error, StrResult, Trace, Tracepoint, TypResult}; -use crate::geom::{Angle, Fractional, Length, Relative}; +use crate::geom::{Angle, Fractional, Gen, Length, Relative}; use crate::image::ImageStore; +use crate::layout::{ParChild, ParNode, StackChild, StackNode}; use crate::loading::Loader; use crate::parse::parse; use crate::source::{SourceId, SourceStore}; use crate::syntax::visit::Visit; use crate::syntax::*; -use crate::util::RefMutExt; +use crate::util::{EcoString, RefMutExt}; use crate::Context; /// Evaluate a parsed source file into a module. -pub fn eval( - ctx: &mut Context, - source: SourceId, - ast: Rc<SyntaxTree>, -) -> TypResult<Module> { +pub fn eval(ctx: &mut Context, source: SourceId, ast: &SyntaxTree) -> TypResult<Module> { let mut ctx = EvalContext::new(ctx, source); let template = ast.eval(&mut ctx)?; Ok(Module { scope: ctx.scopes.top, template }) } -/// An evaluated module, ready for importing or execution. +/// An evaluated module, ready for importing or instantiation. #[derive(Debug, Clone, PartialEq)] pub struct Module { /// The top-level definitions that were bound in this module. @@ -74,8 +74,8 @@ pub struct EvalContext<'a> { pub modules: HashMap<SourceId, Module>, /// The active scopes. pub scopes: Scopes<'a>, - /// The expression map for the currently built template. - pub map: ExprMap, + /// The currently built template. + pub template: Template, } impl<'a> EvalContext<'a> { @@ -88,7 +88,7 @@ impl<'a> EvalContext<'a> { route: vec![source], modules: HashMap::new(), scopes: Scopes::new(Some(&ctx.std)), - map: ExprMap::new(), + template: Template::new(), } } @@ -158,17 +158,17 @@ pub trait Eval { fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output>; } -impl Eval for Rc<SyntaxTree> { +impl Eval for SyntaxTree { type Output = Template; fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> { - let map = { - let prev = mem::take(&mut ctx.map); + Ok({ + let prev = mem::take(&mut ctx.template); + ctx.template.save(); self.walk(ctx)?; - mem::replace(&mut ctx.map, prev) - }; - - Ok(TemplateTree { tree: Rc::clone(self), map }.into()) + ctx.template.restore(); + mem::replace(&mut ctx.template, prev) + }) } } @@ -671,43 +671,6 @@ impl Eval for IncludeExpr { } } -/// Walk a node in a template, filling the context's expression map. -pub trait Walk { - /// Walk the node. - fn walk(&self, ctx: &mut EvalContext) -> TypResult<()>; -} - -impl Walk for SyntaxTree { - fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> { - for node in self.iter() { - node.walk(ctx)?; - } - Ok(()) - } -} - -impl Walk for SyntaxNode { - fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> { - match self { - Self::Space => {} - Self::Text(_) => {} - Self::Linebreak(_) => {} - Self::Parbreak(_) => {} - Self::Strong(_) => {} - Self::Emph(_) => {} - Self::Raw(_) => {} - Self::Heading(n) => n.body.walk(ctx)?, - Self::List(n) => n.body.walk(ctx)?, - Self::Enum(n) => n.body.walk(ctx)?, - Self::Expr(n) => { - let value = n.eval(ctx)?; - ctx.map.insert(n as *const _, value); - } - } - Ok(()) - } -} - /// Try to mutably access the value an expression points to. /// /// This only works if the expression is a valid lvalue. @@ -754,3 +717,129 @@ impl Access for CallExpr { }) } } + +/// Walk a syntax node and fill the currently built template. +pub trait Walk { + /// Walk the node. + fn walk(&self, ctx: &mut EvalContext) -> TypResult<()>; +} + +impl Walk for SyntaxTree { + fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> { + for node in self.iter() { + node.walk(ctx)?; + } + Ok(()) + } +} + +impl Walk for SyntaxNode { + 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(|state| state.font_mut().strong ^= true); + } + Self::Emph(_) => { + ctx.template.modify(|state| state.font_mut().emph ^= true); + } + Self::Text(text) => ctx.template.text(text), + Self::Raw(raw) => raw.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(v.to_string()), + Value::Float(v) => ctx.template.text(v.to_string()), + 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.to_string()), + }, + } + 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 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 |state| { + let font = state.font_mut(); + let upscale = 1.6 - 0.1 * level as f64; + font.size *= upscale; + font.strong = true; + }); + ctx.template += body; + ctx.template.restore(); + ctx.template.parbreak(); + + Ok(()) + } +} + +impl Walk for ListItem { + fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> { + let body = self.body.eval(ctx)?; + walk_item(ctx, '•'.into(), body); + Ok(()) + } +} + +impl Walk for EnumItem { + fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> { + let body = self.body.eval(ctx)?; + let mut label = EcoString::new(); + write!(&mut label, "{}.", self.number.unwrap_or(1)).unwrap(); + walk_item(ctx, label, body); + Ok(()) + } +} + +/// Walk a list or enum item, converting it into a stack. +fn walk_item(ctx: &mut EvalContext, label: EcoString, body: Template) { + ctx.template += Template::from_block(move |state| { + let label = ParNode { + dir: state.dirs.cross, + line_spacing: state.line_spacing(), + children: vec![ParChild::Text( + label.clone(), + state.aligns.cross, + Rc::clone(&state.font), + )], + }; + StackNode { + dirs: Gen::new(state.dirs.main, state.dirs.cross), + aspect: None, + children: vec![ + StackChild::Any(label.into(), Gen::default()), + StackChild::Spacing((state.font.size / 2.0).into()), + StackChild::Any(body.to_stack(&state).into(), Gen::default()), + ], + } + }); +} diff --git a/src/exec/state.rs b/src/eval/state.rs index 56cf5f2e..760a830a 100644 --- a/src/exec/state.rs +++ b/src/eval/state.rs @@ -8,18 +8,18 @@ use crate::geom::*; use crate::layout::Paint; use crate::paper::{PaperClass, PAPER_A4}; -/// The execution state. +/// Defines an set of properties a template can be instantiated with. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct State { /// The direction for text and other inline objects. pub dirs: Gen<Dir>, - /// The current alignments of layouts in their parents. + /// The alignments of layouts in their parents. pub aligns: Gen<Align>, - /// The current page settings. + /// The page settings. pub page: Rc<PageState>, - /// The current paragraph settings. + /// The paragraph settings. pub par: Rc<ParState>, - /// The current font settings. + /// The font settings. pub font: Rc<FontState>, } @@ -98,7 +98,7 @@ impl Default for PageState { } } -/// Style paragraph properties. +/// Defines paragraph properties. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct ParState { /// The spacing between paragraphs (dependent on scaled font size). @@ -119,13 +119,12 @@ impl Default for ParState { /// Defines font properties. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct FontState { - /// Whether the strong toggle is active or inactive. This determines - /// whether the next `*` adds or removes font weight. + /// Whether 300 extra font weight should be added to what is defined by the + /// `variant`. pub strong: bool, - /// Whether the emphasis toggle is active or inactive. This determines - /// whether the next `_` makes italic or non-italic. + /// Whether the the font style defined by the `variant` should be inverted. pub emph: bool, - /// Whether the monospace toggle is active or inactive. + /// Whether a monospace font should be preferred. pub monospace: bool, /// The font size. pub size: Length, @@ -176,11 +175,13 @@ impl FontState { .then(|| self.families.monospace.as_slice()) .unwrap_or_default(); - let core = self.families.list.iter().flat_map(move |family| match family { - FontFamily::Named(name) => std::slice::from_ref(name), - FontFamily::Serif => &self.families.serif, - FontFamily::SansSerif => &self.families.sans_serif, - FontFamily::Monospace => &self.families.monospace, + let core = self.families.list.iter().flat_map(move |family| { + match family { + FontFamily::Named(name) => std::slice::from_ref(name), + FontFamily::Serif => &self.families.serif, + FontFamily::SansSerif => &self.families.sans_serif, + FontFamily::Monospace => &self.families.monospace, + } }); head.iter() diff --git a/src/eval/template.rs b/src/eval/template.rs index 96aa8a86..595e5554 100644 --- a/src/eval/template.rs +++ b/src/eval/template.rs @@ -1,28 +1,145 @@ -use std::collections::HashMap; use std::convert::TryFrom; use std::fmt::{self, Debug, Display, Formatter}; -use std::ops::{Add, AddAssign, Deref}; +use std::mem; +use std::ops::{Add, AddAssign}; use std::rc::Rc; -use super::{Str, Value}; +use super::{State, Str}; use crate::diag::StrResult; -use crate::exec::ExecContext; -use crate::syntax::{Expr, SyntaxTree}; +use crate::geom::{Align, Dir, Gen, GenAxis, Length, Linear, Sides, Size}; +use crate::layout::{ + LayoutNode, LayoutTree, PadNode, PageRun, ParChild, ParNode, StackChild, StackNode, +}; use crate::util::EcoString; /// A template value: `[*Hi* there]`. -#[derive(Debug, Default, Clone)] +#[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(GenAxis, Linear), + /// An inline node builder. + Inline(Rc<dyn Fn(&State) -> LayoutNode>), + /// An block node builder. + Block(Rc<dyn Fn(&State) -> LayoutNode>), + /// Save the current state. + Save, + /// Restore the last saved state. + Restore, + /// A function that can modify the current state. + Modify(Rc<dyn Fn(&mut State)>), +} + impl Template { - /// Create a new template from a vector of nodes. - pub fn new(nodes: Vec<TemplateNode>) -> Self { - Self(Rc::new(nodes)) + /// 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(&State) -> T + 'static, + T: Into<LayoutNode>, + { + let node = TemplateNode::Inline(Rc::new(move |s| f(s).into())); + 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(&State) -> T + 'static, + T: Into<LayoutNode>, + { + let node = TemplateNode::Block(Rc::new(move |s| f(s).into())); + 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(|state| state.font_mut().monospace = true); + self.text(text); + self.restore(); + } + + /// Add spacing along an axis. + pub fn spacing(&mut self, axis: GenAxis, spacing: Linear) { + 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 state modifications made since + /// the last snapshot. + pub fn restore(&mut self) { + self.make_mut().push(TemplateNode::Restore); } - /// Iterate over the contained template nodes. - pub fn iter(&self) -> std::slice::Iter<TemplateNode> { - self.0.iter() + /// Modify the state. + pub fn modify<F>(&mut self, f: F) + where + F: Fn(&mut State) + 'static, + { + self.make_mut().push(TemplateNode::Modify(Rc::new(f))); + } + + /// Build the stack node resulting from instantiating the template in the + /// given state. + pub fn to_stack(&self, state: &State) -> StackNode { + let mut builder = Builder::new(state, false); + builder.template(self); + builder.build_stack() + } + + /// Build the layout tree resulting from instantiating the template in the + /// given state. + pub fn to_tree(&self, state: &State) -> LayoutTree { + let mut builder = Builder::new(state, true); + builder.template(self); + builder.build_tree() } /// Repeat this template `n` times. @@ -33,9 +150,14 @@ impl Template { .ok_or_else(|| format!("cannot repeat this template {} times", n))?; Ok(Self(Rc::new( - self.iter().cloned().cycle().take(count).collect(), + 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 Display for Template { @@ -44,6 +166,12 @@ impl Display for Template { } } +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) @@ -73,7 +201,7 @@ impl Add<Str> for Template { type Output = Self; fn add(mut self, rhs: Str) -> Self::Output { - Rc::make_mut(&mut self.0).push(TemplateNode::Str(rhs.into())); + Rc::make_mut(&mut self.0).push(TemplateNode::Text(rhs.into())); self } } @@ -82,86 +210,306 @@ impl Add<Template> for Str { type Output = Template; fn add(self, mut rhs: Template) -> Self::Output { - Rc::make_mut(&mut rhs.0).insert(0, TemplateNode::Str(self.into())); + Rc::make_mut(&mut rhs.0).insert(0, TemplateNode::Text(self.into())); rhs } } -impl From<TemplateTree> for Template { - fn from(tree: TemplateTree) -> Self { - Self::new(vec![TemplateNode::Tree(tree)]) - } +/// Transforms from template to layout representation. +struct Builder { + /// The active state. + state: State, + /// Snapshots of the state. + snapshots: Vec<State>, + /// The tree of finished page runs. + tree: LayoutTree, + /// When we are building the top-level layout trees, this contains metrics + /// of the page. While building a stack, this is `None`. + page: Option<PageBuilder>, + /// The currently built stack of paragraphs. + stack: StackBuilder, } -impl From<TemplateFunc> for Template { - fn from(func: TemplateFunc) -> Self { - Self::new(vec![TemplateNode::Func(func)]) +impl Builder { + /// Create a new builder with a base state. + fn new(state: &State, pages: bool) -> Self { + Self { + state: state.clone(), + snapshots: vec![], + tree: LayoutTree { runs: vec![] }, + page: pages.then(|| PageBuilder::new(state, true)), + stack: StackBuilder::new(state), + } + } + + /// 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.state.clone()), + TemplateNode::Restore => { + let state = self.snapshots.pop().unwrap(); + let newpage = state.page != self.state.page; + self.state = state; + 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::Inline(f) => self.inline(f(&self.state)), + TemplateNode::Block(f) => self.block(f(&self.state)), + TemplateNode::Modify(f) => f(&mut self.state), + } + } + + /// Push a word space into the active paragraph. + fn space(&mut self) { + self.stack.par.push_soft(self.make_text_node(' ')); + } + + /// Apply a forced line break. + fn linebreak(&mut self) { + self.stack.par.push_hard(self.make_text_node('\n')); + } + + /// Apply a forced paragraph break. + fn parbreak(&mut self) { + let amount = self.state.par_spacing(); + self.stack.finish_par(&self.state); + self.stack.push_soft(StackChild::Spacing(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.state, hard)); + let stack = mem::replace(&mut self.stack, StackBuilder::new(&self.state)); + self.tree.runs.extend(page.build(stack.build(), keep)); + } + } + + /// Push text into the active paragraph. + /// + /// The text is split into lines at newlines. + fn text(&mut self, text: impl Into<EcoString>) { + self.stack.par.push(self.make_text_node(text)); + } + + /// Push an inline node into the active paragraph. + fn inline(&mut self, node: impl Into<LayoutNode>) { + let align = self.state.aligns.cross; + self.stack.par.push(ParChild::Any(node.into(), align)); + } + + /// Push a block node into the active stack, finishing the active paragraph. + fn block(&mut self, node: impl Into<LayoutNode>) { + self.parbreak(); + let aligns = self.state.aligns; + self.stack.push(StackChild::Any(node.into(), aligns)); + self.parbreak(); } -} -impl From<Str> for Template { - fn from(string: Str) -> Self { - Self::new(vec![TemplateNode::Str(string.into())]) + /// Push spacing into the active paragraph or stack depending on the `axis`. + fn spacing(&mut self, axis: GenAxis, amount: Linear) { + match axis { + GenAxis::Main => { + self.stack.finish_par(&self.state); + self.stack.push_hard(StackChild::Spacing(amount)); + } + GenAxis::Cross => { + self.stack.par.push_hard(ParChild::Spacing(amount)); + } + } + } + + /// Finish building and return the created stack. + fn build_stack(self) -> StackNode { + assert!(self.page.is_none()); + self.stack.build() + } + + /// Finish building and return the created layout tree. + fn build_tree(mut self) -> LayoutTree { + assert!(self.page.is_some()); + self.pagebreak(true, false); + self.tree + } + + /// Construct a text node with the given text and settings from the active + /// state. + fn make_text_node(&self, text: impl Into<EcoString>) -> ParChild { + ParChild::Text( + text.into(), + self.state.aligns.cross, + Rc::clone(&self.state.font), + ) } } -/// One node of a template. -/// -/// Evaluating a template expression creates only a single node. Adding multiple -/// templates can yield multi-node templates. -#[derive(Debug, Clone)] -pub enum TemplateNode { - /// A template that was evaluated from a template expression. - Tree(TemplateTree), - /// A function template that can implement custom behaviour. - Func(TemplateFunc), - /// A template that was converted from a string. - Str(EcoString), +struct PageBuilder { + size: Size, + padding: Sides<Linear>, + hard: bool, } -/// A template that consists of a syntax tree plus already evaluated -/// expressions. -#[derive(Debug, Clone)] -pub struct TemplateTree { - /// The syntax tree of the corresponding template expression. - pub tree: Rc<SyntaxTree>, - /// The evaluated expressions in the syntax tree. - pub map: ExprMap, +impl PageBuilder { + fn new(state: &State, hard: bool) -> Self { + Self { + size: state.page.size, + padding: state.page.margins(), + hard, + } + } + + fn build(self, child: StackNode, keep: bool) -> Option<PageRun> { + let Self { size, padding, hard } = self; + (!child.children.is_empty() || (keep && hard)).then(|| PageRun { + size, + child: PadNode { padding, child: child.into() }.into(), + }) + } } -/// A map from expressions to the values they evaluated to. -/// -/// The raw pointers point into the expressions contained in some -/// [`SyntaxTree`]. Since the lifetime is erased, the tree could go out of scope -/// while the hash map still lives. Although this could lead to lookup panics, -/// it is safe since the pointers are never dereferenced. -pub type ExprMap = HashMap<*const Expr, Value>; +struct StackBuilder { + dirs: Gen<Dir>, + children: Vec<StackChild>, + last: Last<StackChild>, + par: ParBuilder, +} -/// A reference-counted dynamic template node that can implement custom -/// behaviour. -#[derive(Clone)] -pub struct TemplateFunc(Rc<dyn Fn(&mut ExecContext)>); +impl StackBuilder { + fn new(state: &State) -> Self { + Self { + dirs: state.dirs, + children: vec![], + last: Last::None, + par: ParBuilder::new(state), + } + } -impl TemplateFunc { - /// Create a new function template from a rust function or closure. - pub fn new<F>(f: F) -> Self - where - F: Fn(&mut ExecContext) + 'static, - { - Self(Rc::new(f)) + fn push(&mut self, child: StackChild) { + self.children.extend(self.last.any()); + self.children.push(child); + } + + fn push_soft(&mut self, child: StackChild) { + self.last.soft(child); + } + + fn push_hard(&mut self, child: StackChild) { + self.last.hard(); + self.children.push(child); + } + + fn finish_par(&mut self, state: &State) { + let par = mem::replace(&mut self.par, ParBuilder::new(state)); + if let Some(par) = par.build() { + self.push(par); + } + } + + fn build(self) -> StackNode { + let Self { dirs, mut children, par, mut last } = self; + if let Some(par) = par.build() { + children.extend(last.any()); + children.push(par); + } + StackNode { dirs, aspect: None, children } } } -impl Debug for TemplateFunc { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.debug_struct("TemplateFunc").finish() +struct ParBuilder { + aligns: Gen<Align>, + dir: Dir, + line_spacing: Length, + children: Vec<ParChild>, + last: Last<ParChild>, +} + +impl ParBuilder { + fn new(state: &State) -> Self { + Self { + aligns: state.aligns, + dir: state.dirs.cross, + line_spacing: state.line_spacing(), + 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(curr_text, curr_props, curr_align) = &child { + if let Some(ParChild::Text(prev_text, prev_props, prev_align)) = + self.children.last_mut() + { + if prev_align == curr_align && prev_props == curr_props { + prev_text.push_str(&curr_text); + return; + } + } + } + + self.children.push(child); + } + + fn build(self) -> Option<StackChild> { + let Self { aligns, dir, line_spacing, children, .. } = self; + (!children.is_empty()).then(|| { + let node = ParNode { dir, line_spacing, children }; + StackChild::Any(node.into(), aligns) + }) } } -impl Deref for TemplateFunc { - type Target = dyn Fn(&mut ExecContext); +/// 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 deref(&self) -> &Self::Target { - self.0.as_ref() + fn hard(&mut self) { + *self = Self::None; } } diff --git a/src/eval/value.rs b/src/eval/value.rs index 62899cc1..99efc2e5 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -3,10 +3,9 @@ use std::cmp::Ordering; use std::fmt::{self, Debug, Display, Formatter}; use std::rc::Rc; -use super::{ops, Array, Dict, Function, Str, Template, TemplateFunc}; +use super::{ops, Array, Dict, Function, Str, Template}; use crate::color::{Color, RgbaColor}; use crate::diag::StrResult; -use crate::exec::ExecContext; use crate::geom::{Angle, Fractional, Length, Linear, Relative}; use crate::syntax::Spanned; use crate::util::EcoString; @@ -51,14 +50,6 @@ pub enum Value { } impl Value { - /// Create a new template consisting of a single function node. - pub fn template<F>(f: F) -> Self - where - F: Fn(&mut ExecContext) + 'static, - { - Self::Template(TemplateFunc::new(f).into()) - } - /// The name of the stored value's type. pub fn type_name(&self) -> &'static str { match self { diff --git a/src/exec/context.rs b/src/exec/context.rs deleted file mode 100644 index d8ce6528..00000000 --- a/src/exec/context.rs +++ /dev/null @@ -1,302 +0,0 @@ -use std::mem; -use std::rc::Rc; - -use super::{Exec, ExecWithMap, State}; -use crate::eval::{ExprMap, Template}; -use crate::geom::{Align, Dir, Gen, GenAxis, Length, Linear, Sides, Size}; -use crate::layout::{ - LayoutNode, LayoutTree, PadNode, PageRun, ParChild, ParNode, StackChild, StackNode, -}; -use crate::syntax::SyntaxTree; -use crate::util::EcoString; -use crate::Context; - -/// The context for execution. -pub struct ExecContext { - /// The active execution state. - pub state: State, - /// The tree of finished page runs. - tree: LayoutTree, - /// When we are building the top-level stack, this contains metrics of the - /// page. While building a group stack through `exec_group`, this is `None`. - page: Option<PageBuilder>, - /// The currently built stack of paragraphs. - stack: StackBuilder, -} - -impl ExecContext { - /// Create a new execution context with a base state. - pub fn new(ctx: &mut Context) -> Self { - Self { - state: ctx.state.clone(), - tree: LayoutTree { runs: vec![] }, - page: Some(PageBuilder::new(&ctx.state, true)), - stack: StackBuilder::new(&ctx.state), - } - } - - /// Push a word space into the active paragraph. - pub fn space(&mut self) { - self.stack.par.push_soft(self.make_text_node(' ')); - } - - /// Apply a forced line break. - pub fn linebreak(&mut self) { - self.stack.par.push_hard(self.make_text_node('\n')); - } - - /// Apply a forced paragraph break. - pub fn parbreak(&mut self) { - let amount = self.state.par_spacing(); - self.stack.finish_par(&self.state); - self.stack.push_soft(StackChild::Spacing(amount.into())); - } - - /// Apply a forced page break. - pub fn pagebreak(&mut self, keep: bool, hard: bool) { - if let Some(builder) = &mut self.page { - let page = mem::replace(builder, PageBuilder::new(&self.state, hard)); - let stack = mem::replace(&mut self.stack, StackBuilder::new(&self.state)); - self.tree.runs.extend(page.build(stack.build(), keep)); - } - } - - /// Push text into the active paragraph. - /// - /// The text is split into lines at newlines. - pub fn text(&mut self, text: impl Into<EcoString>) { - self.stack.par.push(self.make_text_node(text)); - } - - /// Push text, but in monospace. - pub fn text_mono(&mut self, text: impl Into<EcoString>) { - let prev = Rc::clone(&self.state.font); - self.state.font_mut().monospace = true; - self.text(text); - self.state.font = prev; - } - - /// Push an inline node into the active paragraph. - pub fn inline(&mut self, node: impl Into<LayoutNode>) { - let align = self.state.aligns.cross; - self.stack.par.push(ParChild::Any(node.into(), align)); - } - - /// Push a block node into the active stack, finishing the active paragraph. - pub fn block(&mut self, node: impl Into<LayoutNode>) { - self.parbreak(); - let aligns = self.state.aligns; - self.stack.push(StackChild::Any(node.into(), aligns)); - self.parbreak(); - } - - /// Push spacing into the active paragraph or stack depending on the `axis`. - pub fn spacing(&mut self, axis: GenAxis, amount: Linear) { - match axis { - GenAxis::Main => { - self.stack.finish_par(&self.state); - self.stack.push_hard(StackChild::Spacing(amount)); - } - GenAxis::Cross => { - self.stack.par.push_hard(ParChild::Spacing(amount)); - } - } - } - - /// Execute a template and return the result as a stack node. - pub fn exec_template(&mut self, template: &Template) -> StackNode { - self.exec_to_stack(|ctx| template.exec(ctx)) - } - - /// Execute a syntax tree with a map and return the result as a stack node. - pub fn exec_tree(&mut self, tree: &SyntaxTree, map: &ExprMap) -> StackNode { - self.exec_to_stack(|ctx| tree.exec_with_map(ctx, map)) - } - - /// Execute something and return the result as a stack node. - pub fn exec_to_stack(&mut self, f: impl FnOnce(&mut Self)) -> StackNode { - let snapshot = self.state.clone(); - let page = self.page.take(); - let stack = mem::replace(&mut self.stack, StackBuilder::new(&self.state)); - - f(self); - - self.state = snapshot; - self.page = page; - mem::replace(&mut self.stack, stack).build() - } - - /// Finish execution and return the created layout tree. - pub fn finish(mut self) -> LayoutTree { - assert!(self.page.is_some()); - self.pagebreak(true, false); - self.tree - } - - /// Construct a text node with the given text and settings from the active - /// state. - fn make_text_node(&self, text: impl Into<EcoString>) -> ParChild { - ParChild::Text( - text.into(), - self.state.aligns.cross, - Rc::clone(&self.state.font), - ) - } -} - -struct PageBuilder { - size: Size, - padding: Sides<Linear>, - hard: bool, -} - -impl PageBuilder { - fn new(state: &State, hard: bool) -> Self { - Self { - size: state.page.size, - padding: state.page.margins(), - hard, - } - } - - fn build(self, child: StackNode, keep: bool) -> Option<PageRun> { - let Self { size, padding, hard } = self; - (!child.children.is_empty() || (keep && hard)).then(|| PageRun { - size, - child: PadNode { padding, child: child.into() }.into(), - }) - } -} - -struct StackBuilder { - dirs: Gen<Dir>, - children: Vec<StackChild>, - last: Last<StackChild>, - par: ParBuilder, -} - -impl StackBuilder { - fn new(state: &State) -> Self { - Self { - dirs: state.dirs, - children: vec![], - last: Last::None, - par: ParBuilder::new(state), - } - } - - fn push(&mut self, child: StackChild) { - self.children.extend(self.last.any()); - self.children.push(child); - } - - fn push_soft(&mut self, child: StackChild) { - self.last.soft(child); - } - - fn push_hard(&mut self, child: StackChild) { - self.last.hard(); - self.children.push(child); - } - - fn finish_par(&mut self, state: &State) { - let par = mem::replace(&mut self.par, ParBuilder::new(state)); - if let Some(par) = par.build() { - self.push(par); - } - } - - fn build(self) -> StackNode { - let Self { dirs, mut children, par, mut last } = self; - if let Some(par) = par.build() { - children.extend(last.any()); - children.push(par); - } - StackNode { dirs, aspect: None, children } - } -} - -struct ParBuilder { - aligns: Gen<Align>, - dir: Dir, - line_spacing: Length, - children: Vec<ParChild>, - last: Last<ParChild>, -} - -impl ParBuilder { - fn new(state: &State) -> Self { - Self { - aligns: state.aligns, - dir: state.dirs.cross, - line_spacing: state.line_spacing(), - 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(curr_text, curr_props, curr_align) = &child { - if let Some(ParChild::Text(prev_text, prev_props, prev_align)) = - self.children.last_mut() - { - if prev_align == curr_align && prev_props == curr_props { - prev_text.push_str(&curr_text); - return; - } - } - } - - self.children.push(child); - } - - fn build(self) -> Option<StackChild> { - let Self { aligns, dir, line_spacing, children, .. } = self; - (!children.is_empty()).then(|| { - let node = ParNode { dir, line_spacing, children }; - StackChild::Any(node.into(), aligns) - }) - } -} - -/// 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/exec/mod.rs b/src/exec/mod.rs deleted file mode 100644 index 16fd75f8..00000000 --- a/src/exec/mod.rs +++ /dev/null @@ -1,173 +0,0 @@ -//! Execution of syntax trees. - -mod context; -mod state; - -pub use context::*; -pub use state::*; - -use std::fmt::Write; - -use crate::eval::{ExprMap, Template, TemplateFunc, TemplateNode, TemplateTree, Value}; -use crate::geom::Gen; -use crate::layout::{LayoutTree, StackChild, StackNode}; -use crate::syntax::*; -use crate::util::EcoString; -use crate::Context; - -/// Execute a template to produce a layout tree. -pub fn exec(ctx: &mut Context, template: &Template) -> LayoutTree { - let mut ctx = ExecContext::new(ctx); - template.exec(&mut ctx); - ctx.finish() -} - -/// Execute a node. -/// -/// This manipulates active styling and document state and produces layout -/// nodes. Because syntax nodes and layout nodes do not correspond one-to-one, -/// constructed layout nodes are pushed into the context instead of returned. -/// The context takes care of reshaping the nodes into the correct tree -/// structure. -pub trait Exec { - /// Execute the node. - fn exec(&self, ctx: &mut ExecContext); -} - -/// Execute a node with an expression map that applies to it. -pub trait ExecWithMap { - /// Execute the node. - fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap); -} - -impl ExecWithMap for SyntaxTree { - fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) { - for node in self { - node.exec_with_map(ctx, map); - } - } -} - -impl ExecWithMap for SyntaxNode { - fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) { - match self { - Self::Space => ctx.space(), - Self::Text(text) => ctx.text(text), - Self::Linebreak(_) => ctx.linebreak(), - Self::Parbreak(_) => ctx.parbreak(), - Self::Strong(_) => ctx.state.font_mut().strong ^= true, - Self::Emph(_) => ctx.state.font_mut().emph ^= true, - Self::Raw(n) => n.exec(ctx), - Self::Heading(n) => n.exec_with_map(ctx, map), - Self::List(n) => n.exec_with_map(ctx, map), - Self::Enum(n) => n.exec_with_map(ctx, map), - Self::Expr(n) => map[&(n as *const _)].exec(ctx), - } - } -} - -impl Exec for RawNode { - fn exec(&self, ctx: &mut ExecContext) { - if self.block { - ctx.parbreak(); - } - - ctx.text_mono(&self.text); - - if self.block { - ctx.parbreak(); - } - } -} - -impl ExecWithMap for HeadingNode { - fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) { - ctx.parbreak(); - - let snapshot = ctx.state.clone(); - let font = ctx.state.font_mut(); - let upscale = 1.6 - 0.1 * self.level as f64; - font.size *= upscale; - font.strong = true; - - self.body.exec_with_map(ctx, map); - ctx.state = snapshot; - - ctx.parbreak(); - } -} - -impl ExecWithMap for ListItem { - fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) { - exec_item(ctx, '•'.into(), &self.body, map); - } -} - -impl ExecWithMap for EnumItem { - fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) { - let mut label = EcoString::new(); - write!(&mut label, "{}.", self.number.unwrap_or(1)).unwrap(); - exec_item(ctx, label, &self.body, map); - } -} - -fn exec_item(ctx: &mut ExecContext, label: EcoString, body: &SyntaxTree, map: &ExprMap) { - let label = ctx.exec_to_stack(|ctx| ctx.text(label)); - let body = ctx.exec_tree(body, map); - ctx.block(StackNode { - dirs: Gen::new(ctx.state.dirs.main, ctx.state.dirs.cross), - aspect: None, - children: vec![ - StackChild::Any(label.into(), Gen::default()), - StackChild::Spacing((ctx.state.font.size / 2.0).into()), - StackChild::Any(body.into(), Gen::default()), - ], - }); -} - -impl Exec for Value { - fn exec(&self, ctx: &mut ExecContext) { - match self { - Value::None => {} - Value::Int(v) => ctx.text(v.to_string()), - Value::Float(v) => ctx.text(v.to_string()), - Value::Str(v) => ctx.text(v), - Value::Template(v) => v.exec(ctx), - // For values which can't be shown "naturally", we print the - // representation in monospace. - other => ctx.text_mono(other.to_string()), - } - } -} - -impl Exec for Template { - fn exec(&self, ctx: &mut ExecContext) { - for node in self.iter() { - node.exec(ctx); - } - } -} - -impl Exec for TemplateNode { - fn exec(&self, ctx: &mut ExecContext) { - match self { - Self::Tree(v) => v.exec(ctx), - Self::Func(v) => v.exec(ctx), - Self::Str(v) => ctx.text(v), - } - } -} - -impl Exec for TemplateTree { - fn exec(&self, ctx: &mut ExecContext) { - self.tree.exec_with_map(ctx, &self.map) - } -} - -impl Exec for TemplateFunc { - fn exec(&self, ctx: &mut ExecContext) { - let snapshot = ctx.state.clone(); - self(ctx); - ctx.state = snapshot; - } -} diff --git a/src/layout/par.rs b/src/layout/par.rs index c8512243..317a91ad 100644 --- a/src/layout/par.rs +++ b/src/layout/par.rs @@ -5,7 +5,7 @@ use unicode_bidi::{BidiInfo, Level}; use xi_unicode::LineBreakIterator; use super::*; -use crate::exec::FontState; +use crate::eval::FontState; use crate::util::{EcoString, RangeExt, SliceExt}; type Range = std::ops::Range<usize>; diff --git a/src/layout/shaping.rs b/src/layout/shaping.rs index 3ede5122..ba8704ea 100644 --- a/src/layout/shaping.rs +++ b/src/layout/shaping.rs @@ -5,7 +5,7 @@ use std::ops::Range; use rustybuzz::UnicodeBuffer; use super::{Element, Frame, Glyph, LayoutContext, Text}; -use crate::exec::{FontState, LineState}; +use crate::eval::{FontState, LineState}; use crate::font::{Face, FaceId, FontVariant, LineMetrics}; use crate::geom::{Dir, Length, Point, Size}; use crate::layout::Geometry; @@ -6,12 +6,12 @@ //! tree]. The structures describing the tree can be found in the [syntax] //! module. //! - **Evaluation:** The next step is to [evaluate] the syntax tree. This -//! computes the value of each node in the document and produces a [module]. -//! - **Execution:** Now, we can [execute] the parsed and evaluated module. This -//! results in a [layout tree], a high-level, fully styled representation of -//! the document. The nodes of this tree are self-contained and -//! order-independent and thus much better suited for layouting than the -//! syntax tree. +//! produces a [module], consisting of a scope of values that were exported by +//! the module and a template with the contents of the module. This template +//! can be [instantiated] in a state to produce a layout tree, a high-level, +//! fully styled representation of the document. The nodes of this tree are +//! self-contained and order-independent and thus much better suited for +//! layouting than a syntax tree. //! - **Layouting:** Next, the tree is [layouted] into a portable version of the //! typeset document. The output of this is a collection of [`Frame`]s (one //! per page), ready for exporting. @@ -23,7 +23,7 @@ //! [syntax tree]: syntax::SyntaxTree //! [evaluate]: eval::eval //! [module]: eval::Module -//! [execute]: exec::exec +//! [instantiated]: eval::Template::to_tree //! [layout tree]: layout::LayoutTree //! [layouted]: layout::layout //! [PDF]: export::pdf @@ -33,7 +33,6 @@ pub mod diag; #[macro_use] pub mod eval; pub mod color; -pub mod exec; pub mod export; pub mod font; pub mod geom; @@ -50,13 +49,12 @@ pub mod util; use std::rc::Rc; use crate::diag::TypResult; -use crate::eval::Scope; -use crate::exec::State; +use crate::eval::{Scope, State}; use crate::font::FontStore; use crate::image::ImageStore; -use crate::layout::Frame; #[cfg(feature = "layout-cache")] use crate::layout::LayoutCache; +use crate::layout::{Frame, LayoutTree}; use crate::loading::Loader; use crate::source::{SourceId, SourceStore}; @@ -90,16 +88,21 @@ impl Context { ContextBuilder::default() } + /// Execute a source file and produce the resulting layout tree. + pub fn execute(&mut self, id: SourceId) -> TypResult<LayoutTree> { + let source = self.sources.get(id); + let ast = parse::parse(source)?; + let module = eval::eval(self, id, &ast)?; + Ok(module.template.to_tree(&self.state)) + } + /// Typeset a source file into a collection of layouted frames. /// /// Returns either a vector of frames representing individual pages or /// diagnostics in the form of a vector of error message with file and span /// information. pub fn typeset(&mut self, id: SourceId) -> TypResult<Vec<Rc<Frame>>> { - let source = self.sources.get(id); - let ast = parse::parse(source)?; - let module = eval::eval(self, id, Rc::new(ast))?; - let tree = exec::exec(self, &module.template); + let tree = self.execute(id)?; let frames = layout::layout(self, &tree); Ok(frames) } diff --git a/src/library/elements.rs b/src/library/elements.rs index f4577084..f90363bb 100644 --- a/src/library/elements.rs +++ b/src/library/elements.rs @@ -23,9 +23,11 @@ pub fn image(ctx: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { }) })?; - Ok(Value::template(move |ctx| { - ctx.inline(ImageNode { id, width, height }) - })) + Ok(Value::Template(Template::from_inline(move |_| ImageNode { + id, + width, + height, + }))) } /// `rect`: A rectangle with optional content. @@ -61,22 +63,23 @@ fn rect_impl( fill: Option<Color>, body: Template, ) -> Value { - Value::template(move |ctx| { - let mut stack = ctx.exec_template(&body); + Value::Template(Template::from_inline(move |state| { + let mut stack = body.to_stack(state); stack.aspect = aspect; - let fixed = FixedNode { width, height, child: stack.into() }; + let mut node = FixedNode { width, height, child: stack.into() }.into(); if let Some(fill) = fill { - ctx.inline(BackgroundNode { + node = BackgroundNode { shape: BackgroundShape::Rect, fill: Paint::Color(fill), - child: fixed.into(), - }); - } else { - ctx.inline(fixed); + child: node, + } + .into(); } - }) + + node + })) } /// `ellipse`: An ellipse with optional content. @@ -112,15 +115,15 @@ fn ellipse_impl( fill: Option<Color>, body: Template, ) -> Value { - Value::template(move |ctx| { + Value::Template(Template::from_inline(move |state| { // This padding ratio ensures that the rectangular padded region fits // perfectly into the ellipse. const PAD: f64 = 0.5 - SQRT_2 / 4.0; - let mut stack = ctx.exec_template(&body); + let mut stack = body.to_stack(state); stack.aspect = aspect; - let fixed = FixedNode { + let mut node = FixedNode { width, height, child: PadNode { @@ -128,16 +131,18 @@ fn ellipse_impl( child: stack.into(), } .into(), - }; + } + .into(); if let Some(fill) = fill { - ctx.inline(BackgroundNode { + node = BackgroundNode { shape: BackgroundShape::Ellipse, fill: Paint::Color(fill), - child: fixed.into(), - }); - } else { - ctx.inline(fixed); + child: node, + } + .into(); } - }) + + node + })) } diff --git a/src/library/layout.rs b/src/library/layout.rs index e910139c..b1510cb6 100644 --- a/src/library/layout.rs +++ b/src/library/layout.rs @@ -3,7 +3,7 @@ use crate::layout::{FixedNode, GridNode, PadNode, StackChild, StackNode, TrackSi use crate::paper::{Paper, PaperClass}; /// `page`: Configure pages. -pub fn page(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { +pub fn page(ctx: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { let paper = match args.eat::<Spanned<Str>>() { Some(name) => match Paper::from_name(&name.v) { None => bail!(name.span, "invalid paper name"), @@ -20,87 +20,83 @@ pub fn page(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { let right = args.named("right")?; let bottom = args.named("bottom")?; let flip = args.named("flip")?; - let body = args.expect::<Template>("body")?; - Ok(Value::template(move |ctx| { - let snapshot = ctx.state.clone(); - let state = ctx.state.page_mut(); + ctx.template.modify(move |state| { + let page = state.page_mut(); if let Some(paper) = paper { - state.class = paper.class(); - state.size = paper.size(); + page.class = paper.class(); + page.size = paper.size(); } if let Some(width) = width { - state.class = PaperClass::Custom; - state.size.width = width; + page.class = PaperClass::Custom; + page.size.width = width; } if let Some(height) = height { - state.class = PaperClass::Custom; - state.size.height = height; + page.class = PaperClass::Custom; + page.size.height = height; } if let Some(margins) = margins { - state.margins = Sides::splat(Some(margins)); + page.margins = Sides::splat(Some(margins)); } if let Some(left) = left { - state.margins.left = Some(left); + page.margins.left = Some(left); } if let Some(top) = top { - state.margins.top = Some(top); + page.margins.top = Some(top); } if let Some(right) = right { - state.margins.right = Some(right); + page.margins.right = Some(right); } if let Some(bottom) = bottom { - state.margins.bottom = Some(bottom); + page.margins.bottom = Some(bottom); } if flip.unwrap_or(false) { - std::mem::swap(&mut state.size.width, &mut state.size.height); + std::mem::swap(&mut page.size.width, &mut page.size.height); } + }); - ctx.pagebreak(false, true); - body.exec(ctx); + ctx.template.pagebreak(false); - ctx.state = snapshot; - ctx.pagebreak(true, false); - })) + Ok(Value::None) } /// `pagebreak`: Start a new page. -pub fn pagebreak(_: &mut EvalContext, _: &mut Arguments) -> TypResult<Value> { - Ok(Value::template(move |ctx| ctx.pagebreak(true, true))) +pub fn pagebreak(ctx: &mut EvalContext, _: &mut Arguments) -> TypResult<Value> { + ctx.template.pagebreak(true); + Ok(Value::None) } /// `h`: Horizontal spacing. -pub fn h(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { +pub fn h(ctx: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { let spacing = args.expect("spacing")?; - Ok(Value::template(move |ctx| { - ctx.spacing(GenAxis::Cross, spacing); - })) + ctx.template.spacing(GenAxis::Cross, spacing); + Ok(Value::None) } /// `v`: Vertical spacing. -pub fn v(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { +pub fn v(ctx: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { let spacing = args.expect("spacing")?; - Ok(Value::template(move |ctx| { - ctx.spacing(GenAxis::Main, spacing); - })) + ctx.template.spacing(GenAxis::Main, spacing); + Ok(Value::None) } /// `align`: Configure the alignment along the layouting axes. -pub fn align(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { - let mut horizontal = args.named("horizontal")?; - let mut vertical = args.named("vertical")?; +pub fn align(ctx: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { let first = args.eat::<Align>(); let second = args.eat::<Align>(); - let body = args.expect::<Template>("body")?; + let body = args.eat::<Template>(); + + let mut horizontal = args.named("horizontal")?; + let mut vertical = args.named("vertical")?; for value in first.into_iter().chain(second) { match value.axis() { @@ -114,38 +110,52 @@ pub fn align(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { } } - Ok(Value::template(move |ctx| { - if let Some(horizontal) = horizontal { - ctx.state.aligns.cross = horizontal; - } + let realign = |template: &mut Template| { + template.modify(move |state| { + if let Some(horizontal) = horizontal { + state.aligns.cross = horizontal; + } - if let Some(vertical) = vertical { - ctx.state.aligns.main = vertical; - ctx.parbreak(); + if let Some(vertical) = vertical { + state.aligns.main = vertical; + } + }); + + if vertical.is_some() { + template.parbreak(); } + }; - body.exec(ctx); - })) + if let Some(body) = body { + let mut template = Template::new(); + template.save(); + realign(&mut template); + template += body; + template.restore(); + Ok(Value::Template(template)) + } else { + realign(&mut ctx.template); + Ok(Value::None) + } } /// `box`: Place content in a rectangular box. pub fn boxed(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { let width = args.named("width")?; let height = args.named("height")?; - let body = args.eat().unwrap_or_default(); - Ok(Value::template(move |ctx| { - let child = ctx.exec_template(&body).into(); - ctx.inline(FixedNode { width, height, child }); - })) + let body: Template = args.eat().unwrap_or_default(); + Ok(Value::Template(Template::from_inline(move |state| { + let child = body.to_stack(state).into(); + FixedNode { width, height, child } + }))) } /// `block`: Place content in a block. pub fn block(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { - let body = args.expect("body")?; - Ok(Value::template(move |ctx| { - let block = ctx.exec_template(&body); - ctx.block(block); - })) + let body: Template = args.expect("body")?; + Ok(Value::Template(Template::from_block(move |state| { + body.to_stack(state) + }))) } /// `pad`: Pad content at the sides. @@ -155,7 +165,7 @@ pub fn pad(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { let top = args.named("top")?; let right = args.named("right")?; let bottom = args.named("bottom")?; - let body = args.expect("body")?; + let body: Template = args.expect("body")?; let padding = Sides::new( left.or(all).unwrap_or_default(), @@ -164,36 +174,38 @@ pub fn pad(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { bottom.or(all).unwrap_or_default(), ); - Ok(Value::template(move |ctx| { - let child = ctx.exec_template(&body).into(); - ctx.block(PadNode { padding, child }); - })) + Ok(Value::Template(Template::from_block(move |state| { + PadNode { + padding, + child: body.to_stack(&state).into(), + } + }))) } /// `stack`: Stack children along an axis. pub fn stack(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { let dir = args.named("dir")?; - let children: Vec<_> = args.all().collect(); + let children: Vec<Template> = args.all().collect(); - Ok(Value::template(move |ctx| { + Ok(Value::Template(Template::from_block(move |state| { let children = children .iter() .map(|child| { - let child = ctx.exec_template(child).into(); - StackChild::Any(child, ctx.state.aligns) + let child = child.to_stack(state).into(); + StackChild::Any(child, state.aligns) }) .collect(); - let mut dirs = Gen::new(None, dir).unwrap_or(ctx.state.dirs); + let mut dirs = Gen::new(None, dir).unwrap_or(state.dirs); // If the directions become aligned, fix up the cross direction since // that's the one that is not user-defined. if dirs.main.axis() == dirs.cross.axis() { - dirs.cross = ctx.state.dirs.main; + dirs.cross = state.dirs.main; } - ctx.block(StackNode { dirs, aspect: None, children }); - })) + StackNode { dirs, aspect: None, children } + }))) } /// `grid`: Arrange children into a grid. @@ -211,7 +223,7 @@ pub fn grid(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { let column_dir = args.named("column-dir")?; let row_dir = args.named("row-dir")?; - let children: Vec<_> = args.all().collect(); + let children: Vec<Template> = args.all().collect(); let tracks = Gen::new(columns, rows); let gutter = Gen::new( @@ -219,14 +231,13 @@ pub fn grid(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { gutter_rows.unwrap_or(default), ); - Ok(Value::template(move |ctx| { + Ok(Value::Template(Template::from_block(move |state| { let children = - children.iter().map(|child| ctx.exec_template(child).into()).collect(); - - let mut dirs = Gen::new(column_dir, row_dir).unwrap_or(ctx.state.dirs); + children.iter().map(|child| child.to_stack(&state).into()).collect(); // If the directions become aligned, try to fix up the direction which // is not user-defined. + let mut dirs = Gen::new(column_dir, row_dir).unwrap_or(state.dirs); if dirs.main.axis() == dirs.cross.axis() { let target = if column_dir.is_some() { &mut dirs.main @@ -234,20 +245,20 @@ pub fn grid(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { &mut dirs.cross }; - *target = if target.axis() == ctx.state.dirs.cross.axis() { - ctx.state.dirs.main + *target = if target.axis() == state.dirs.cross.axis() { + state.dirs.main } else { - ctx.state.dirs.cross + state.dirs.cross }; } - ctx.block(GridNode { + GridNode { dirs, tracks: tracks.clone(), gutter: gutter.clone(), children, - }) - })) + } + }))) } /// Defines size of rows and columns in a grid. diff --git a/src/library/mod.rs b/src/library/mod.rs index f1134282..dd1574f3 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -19,7 +19,6 @@ use std::rc::Rc; use crate::color::{Color, RgbaColor}; use crate::diag::TypResult; use crate::eval::{Arguments, EvalContext, Scope, Str, Template, Value}; -use crate::exec::Exec; use crate::font::{FontFamily, FontStretch, FontStyle, FontWeight, VerticalFontMetric}; use crate::geom::*; use crate::syntax::Spanned; diff --git a/src/library/text.rs b/src/library/text.rs index 9ffb182a..6154885c 100644 --- a/src/library/text.rs +++ b/src/library/text.rs @@ -1,10 +1,15 @@ -use crate::exec::{FontState, LineState}; +use crate::eval::{FontState, LineState}; use crate::layout::Paint; use super::*; /// `font`: Configure the font. -pub fn font(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { +pub fn font(ctx: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { + let list = args.named("family")?.or_else(|| { + let families: Vec<_> = args.all().collect(); + (!families.is_empty()).then(|| FontDef(Rc::new(families))) + }); + let size = args.named::<Linear>("size")?.or_else(|| args.eat()); let style = args.named("style")?; let weight = args.named("weight")?; @@ -12,67 +17,59 @@ pub fn font(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { let top_edge = args.named("top-edge")?; let bottom_edge = args.named("bottom-edge")?; let fill = args.named("fill")?; - - let list = args.named("family")?.or_else(|| { - let families: Vec<_> = args.all().collect(); - (!families.is_empty()).then(|| FontDef(Rc::new(families))) - }); - let serif = args.named("serif")?; let sans_serif = args.named("sans-serif")?; let monospace = args.named("monospace")?; - let body = args.expect::<Template>("body")?; - - Ok(Value::template(move |ctx| { - let state = ctx.state.font_mut(); + ctx.template.modify(move |state| { + let font = state.font_mut(); if let Some(size) = size { - state.size = size.resolve(state.size); + font.size = size.resolve(font.size); } if let Some(style) = style { - state.variant.style = style; + font.variant.style = style; } if let Some(weight) = weight { - state.variant.weight = weight; + font.variant.weight = weight; } if let Some(stretch) = stretch { - state.variant.stretch = stretch; + font.variant.stretch = stretch; } if let Some(top_edge) = top_edge { - state.top_edge = top_edge; + font.top_edge = top_edge; } if let Some(bottom_edge) = bottom_edge { - state.bottom_edge = bottom_edge; + font.bottom_edge = bottom_edge; } if let Some(fill) = fill { - state.fill = Paint::Color(fill); + font.fill = Paint::Color(fill); } if let Some(FontDef(list)) = &list { - state.families_mut().list = list.clone(); + font.families_mut().list = list.clone(); } if let Some(FamilyDef(serif)) = &serif { - state.families_mut().serif = serif.clone(); + font.families_mut().serif = serif.clone(); } if let Some(FamilyDef(sans_serif)) = &sans_serif { - state.families_mut().sans_serif = sans_serif.clone(); + font.families_mut().sans_serif = sans_serif.clone(); } if let Some(FamilyDef(monospace)) = &monospace { - state.families_mut().monospace = monospace.clone(); + font.families_mut().monospace = monospace.clone(); } + }); - body.exec(ctx); - })) + Ok(Value::None) } struct FontDef(Rc<Vec<FontFamily>>); @@ -104,29 +101,29 @@ castable! { } /// `par`: Configure paragraphs. -pub fn par(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { +pub fn par(ctx: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { let par_spacing = args.named("spacing")?; let line_spacing = args.named("leading")?; - let body = args.expect::<Template>("body")?; - Ok(Value::template(move |ctx| { - let state = ctx.state.par_mut(); + ctx.template.modify(move |state| { + let par = state.par_mut(); if let Some(par_spacing) = par_spacing { - state.par_spacing = par_spacing; + par.par_spacing = par_spacing; } if let Some(line_spacing) = line_spacing { - state.line_spacing = line_spacing; + par.line_spacing = line_spacing; } + }); - ctx.parbreak(); - body.exec(ctx); - })) + ctx.template.parbreak(); + + Ok(Value::None) } /// `lang`: Configure the language. -pub fn lang(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { +pub fn lang(ctx: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { let iso = args.eat::<Str>(); let dir = if let Some(dir) = args.named::<Spanned<Dir>>("dir")? { if dir.v.axis() == SpecAxis::Horizontal { @@ -138,16 +135,13 @@ pub fn lang(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { iso.as_deref().map(lang_dir) }; - let body = args.expect::<Template>("body")?; + if let Some(dir) = dir { + ctx.template.modify(move |state| state.dirs.cross = dir); + } - Ok(Value::template(move |ctx| { - if let Some(dir) = dir { - ctx.state.dirs.cross = dir; - } + ctx.template.parbreak(); - ctx.parbreak(); - body.exec(ctx); - })) + Ok(Value::None) } /// The default direction for the language identified by the given `iso` code. @@ -159,22 +153,23 @@ fn lang_dir(iso: &str) -> Dir { } } -/// `strike`: Enable striken-through text. -pub fn strike(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { - line_impl(args, |font| &mut font.strikethrough) +/// `strike`: Set striken-through text. +pub fn strike(ctx: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { + line_impl(ctx, args, |font| &mut font.strikethrough) } -/// `underline`: Enable underlined text. -pub fn underline(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { - line_impl(args, |font| &mut font.underline) +/// `underline`: Set underlined text. +pub fn underline(ctx: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { + line_impl(ctx, args, |font| &mut font.underline) } -/// `overline`: Add an overline above text. -pub fn overline(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { - line_impl(args, |font| &mut font.overline) +/// `overline`: Set text with an overline. +pub fn overline(ctx: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { + line_impl(ctx, args, |font| &mut font.overline) } fn line_impl( + _: &mut EvalContext, args: &mut Arguments, substate: fn(&mut FontState) -> &mut Option<Rc<LineState>>, ) -> TypResult<Value> { @@ -182,10 +177,10 @@ fn line_impl( let thickness = args.named::<Linear>("thickness")?.or_else(|| args.eat()); let offset = args.named("offset")?; let extent = args.named("extent")?.unwrap_or_default(); - let body = args.expect::<Template>("body")?; + let body = args.expect("body")?; // Suppress any existing strikethrough if strength is explicitly zero. - let state = thickness.map_or(true, |s| !s.is_zero()).then(|| { + let line = thickness.map_or(true, |s| !s.is_zero()).then(|| { Rc::new(LineState { stroke: stroke.map(Paint::Color), thickness, @@ -194,8 +189,11 @@ fn line_impl( }) }); - Ok(Value::template(move |ctx| { - *substate(ctx.state.font_mut()) = state.clone(); - body.exec(ctx); - })) + let mut template = Template::new(); + template.save(); + template.modify(move |state| *substate(state.font_mut()) = line.clone()); + template += body; + template.restore(); + + Ok(Value::Template(template)) } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index c29b9c1c..37b65c04 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -57,23 +57,8 @@ where // or template to know whether things like headings are allowed. let mut tree = vec![]; while !p.eof() && f(p) { - if let Some(mut node) = node(p, &mut at_start) { + if let Some(node) = node(p, &mut at_start) { at_start &= matches!(node, SyntaxNode::Space | SyntaxNode::Parbreak(_)); - - // Look for wide call. - if let SyntaxNode::Expr(Expr::Call(call)) = &mut node { - if call.wide { - let start = p.next_start(); - let tree = tree_while(p, true, f); - call.args.items.push(CallArg::Pos(Expr::Template(Box::new( - TemplateExpr { - span: p.span_from(start), - tree: Rc::new(tree), - }, - )))); - } - } - tree.push(node); } } @@ -538,7 +523,7 @@ fn idents(p: &mut Parser, items: Vec<CallArg>) -> Vec<Ident> { // Parse a template value: `[...]`. fn template(p: &mut Parser) -> Expr { p.start_group(Group::Bracket, TokenMode::Markup); - let tree = Rc::new(tree(p)); + let tree = tree(p); let span = p.end_group(); Expr::Template(Box::new(TemplateExpr { span, tree })) } @@ -566,13 +551,6 @@ fn block(p: &mut Parser, scoping: bool) -> Expr { /// Parse a function call. fn call(p: &mut Parser, callee: Expr) -> Option<Expr> { - let mut wide = p.eat_if(Token::Excl); - if wide && p.outer_mode() == TokenMode::Code { - let span = p.span_from(callee.span().start); - p.error(span, "wide calls are only allowed directly in templates"); - wide = false; - } - let mut args = match p.peek_direct() { Some(Token::LeftParen) => args(p), Some(Token::LeftBracket) => CallArgs { @@ -593,7 +571,6 @@ fn call(p: &mut Parser, callee: Expr) -> Option<Expr> { Some(Expr::Call(Box::new(CallExpr { span: p.span_from(callee.span().start), callee, - wide, args, }))) } diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index 3a71bedd..aac23a6f 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -170,7 +170,7 @@ pub struct TemplateExpr { /// The source code location. pub span: Span, /// The contents of the template. - pub tree: Rc<SyntaxTree>, + pub tree: SyntaxTree, } /// A grouped expression: `(1 + 2)`. @@ -406,8 +406,6 @@ pub struct CallExpr { pub span: Span, /// The function to call. pub callee: Expr, - /// Whether the call is wide, that is, capturing the template behind it. - pub wide: bool, /// The arguments to the function. pub args: CallArgs, } diff --git a/src/syntax/node.rs b/src/syntax/node.rs index 2ca861dc..4ff69c17 100644 --- a/src/syntax/node.rs +++ b/src/syntax/node.rs @@ -5,8 +5,6 @@ use super::*; pub enum SyntaxNode { /// Whitespace containing less than two newlines. Space, - /// Plain text. - Text(EcoString), /// A forced line break: `\`. Linebreak(Span), /// A paragraph break: Two or more newlines. @@ -15,6 +13,8 @@ pub enum SyntaxNode { Strong(Span), /// Emphasized text was enabled / disabled: `_`. Emph(Span), + /// Plain text. + Text(EcoString), /// A raw block with optional syntax highlighting: `` `...` ``. Raw(Box<RawNode>), /// A section heading: `= Introduction`. diff --git a/src/syntax/pretty.rs b/src/syntax/pretty.rs index 40ebf758..cf9ee69d 100644 --- a/src/syntax/pretty.rs +++ b/src/syntax/pretty.rs @@ -88,11 +88,11 @@ impl Pretty for SyntaxNode { match self { // TODO: Handle escaping. Self::Space => p.push(' '), - Self::Text(text) => p.push_str(text), Self::Linebreak(_) => p.push_str(r"\"), Self::Parbreak(_) => p.push_str("\n\n"), Self::Strong(_) => p.push('*'), Self::Emph(_) => p.push('_'), + Self::Text(text) => p.push_str(text), Self::Raw(raw) => raw.pretty(p), Self::Heading(n) => n.pretty(p), Self::List(n) => n.pretty(p), diff --git a/src/syntax/visit.rs b/src/syntax/visit.rs index 5e00e1e6..2b4649de 100644 --- a/src/syntax/visit.rs +++ b/src/syntax/visit.rs @@ -87,11 +87,11 @@ impl_visitors! { visit_node(v, node: SyntaxNode) { match node { SyntaxNode::Space => {} - SyntaxNode::Text(_) => {} SyntaxNode::Linebreak(_) => {} SyntaxNode::Parbreak(_) => {} SyntaxNode::Strong(_) => {} SyntaxNode::Emph(_) => {} + SyntaxNode::Text(_) => {} SyntaxNode::Raw(_) => {} SyntaxNode::Heading(n) => v.visit_heading(n), SyntaxNode::List(n) => v.visit_list(n), @@ -149,7 +149,7 @@ impl_visitors! { visit_template(v, template: TemplateExpr) { v.visit_enter(); - v.visit_tree(r!(rc: template.tree)); + v.visit_tree(r!(template.tree)); v.visit_exit(); } |
