summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-08-17 22:04:18 +0200
committerLaurenz <laurmaedje@gmail.com>2021-08-17 22:20:37 +0200
commit594809e35b9e768f1a50926cf5e7a9df41ba7d16 (patch)
tree488f201599a67329d7916b9b3ecb73dd27ad24d7 /src
parentc53d98a22f367a9eecfb45d1b22f1be5c6cf908d (diff)
Library functions behave more imperatively
- Templates scope state changes - State-modifying function operate in place instead of returning a template - Internal template representation contains actual owned nodes instead of a pointer to a syntax tree + an expression map - No more wide calls
Diffstat (limited to 'src')
-rw-r--r--src/eval/mod.rs201
-rw-r--r--src/eval/state.rs (renamed from src/exec/state.rs)33
-rw-r--r--src/eval/template.rs490
-rw-r--r--src/eval/value.rs11
-rw-r--r--src/exec/context.rs302
-rw-r--r--src/exec/mod.rs173
-rw-r--r--src/layout/par.rs2
-rw-r--r--src/layout/shaping.rs2
-rw-r--r--src/lib.rs33
-rw-r--r--src/library/elements.rs49
-rw-r--r--src/library/layout.rs169
-rw-r--r--src/library/mod.rs1
-rw-r--r--src/library/text.rs114
-rw-r--r--src/parse/mod.rs27
-rw-r--r--src/syntax/expr.rs4
-rw-r--r--src/syntax/node.rs4
-rw-r--r--src/syntax/pretty.rs2
-rw-r--r--src/syntax/visit.rs4
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;
diff --git a/src/lib.rs b/src/lib.rs
index 41ad1d16..b6484012 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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();
}