From 594809e35b9e768f1a50926cf5e7a9df41ba7d16 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 17 Aug 2021 22:04:18 +0200 Subject: [PATCH] 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 --- bench/src/clock.rs | 45 +-- src/eval/mod.rs | 201 +++++++++---- src/{exec => eval}/state.rs | 33 +-- src/eval/template.rs | 490 +++++++++++++++++++++++++++----- src/eval/value.rs | 11 +- src/exec/context.rs | 302 -------------------- src/exec/mod.rs | 173 ----------- src/layout/par.rs | 2 +- src/layout/shaping.rs | 2 +- src/lib.rs | 33 ++- src/library/elements.rs | 49 ++-- src/library/layout.rs | 171 +++++------ src/library/mod.rs | 1 - src/library/text.rs | 114 ++++---- src/parse/mod.rs | 27 +- src/syntax/expr.rs | 4 +- src/syntax/node.rs | 4 +- src/syntax/pretty.rs | 2 +- src/syntax/visit.rs | 4 +- tests/ref/code/call.png | Bin 3722 -> 1932 bytes tests/ref/code/ops.png | Bin 1404 -> 1364 bytes tests/ref/layout/page.png | Bin 7432 -> 7368 bytes tests/typ/code/call.typ | 34 +-- tests/typ/code/ops.typ | 2 +- tests/typ/coma.typ | 2 +- tests/typ/insert/circle.typ | 23 +- tests/typ/insert/ellipse.typ | 14 +- tests/typ/insert/image.typ | 8 +- tests/typ/insert/rect.typ | 2 +- tests/typ/insert/square.typ | 11 +- tests/typ/layout/containers.typ | 2 +- tests/typ/layout/grid-1.typ | 8 +- tests/typ/layout/grid-2.typ | 2 +- tests/typ/layout/grid-3.typ | 10 +- tests/typ/layout/pad.typ | 11 +- tests/typ/layout/page.typ | 37 +-- tests/typ/layout/pagebreak.typ | 4 +- tests/typ/text/basic.typ | 2 +- tests/typ/text/bidi.typ | 36 +-- tests/typ/text/chinese.typ | 2 +- tests/typ/text/decorations.typ | 10 +- tests/typ/text/font.typ | 49 ++-- tests/typ/text/par.typ | 2 +- tests/typ/text/shaping.typ | 10 +- tests/typ/text/whitespace.typ | 4 +- tests/typeset.rs | 25 +- 46 files changed, 945 insertions(+), 1033 deletions(-) rename src/{exec => eval}/state.rs (90%) delete mode 100644 src/exec/context.rs delete mode 100644 src/exec/mod.rs diff --git a/bench/src/clock.rs b/bench/src/clock.rs index b86b06dcd..22f1b6dfb 100644 --- a/bench/src/clock.rs +++ b/bench/src/clock.rs @@ -5,8 +5,7 @@ use std::rc::Rc; use criterion::{criterion_group, criterion_main, Criterion}; use typst::diag::TypResult; -use typst::eval::{eval, Module}; -use typst::exec::exec; +use typst::eval::{eval, Module, State}; use typst::export::pdf; use typst::layout::{layout, Frame, LayoutTree}; use typst::loading::FsLoader; @@ -30,7 +29,7 @@ fn benchmarks(c: &mut Criterion) { let case = Case::new(ctx.clone(), id); macro_rules! bench { - ($step:literal, setup = |$ctx:ident| $setup:expr, code = $code:expr $(,)?) => { + ($step:literal, setup: |$ctx:ident| $setup:expr, code: $code:expr $(,)?) => { c.bench_function(&format!("{}-{}", $step, name), |b| { b.iter_batched( || { @@ -49,7 +48,7 @@ fn benchmarks(c: &mut Criterion) { bench!("parse", case.parse()); bench!("eval", case.eval()); - bench!("exec", case.exec()); + bench!("build", case.build()); #[cfg(not(feature = "layout-cache"))] { @@ -59,16 +58,8 @@ fn benchmarks(c: &mut Criterion) { #[cfg(feature = "layout-cache")] { - bench!( - "layout", - setup = |ctx| ctx.layouts.clear(), - code = case.layout(), - ); - bench!( - "typeset", - setup = |ctx| ctx.layouts.clear(), - code = case.typeset(), - ); + bench!("layout", setup: |ctx| ctx.layouts.clear(), code: case.layout()); + bench!("typeset", setup: |ctx| ctx.layouts.clear(), code: case.typeset()); bench!("layout-cached", case.layout()); bench!("typeset-cached", case.typeset()); } @@ -80,8 +71,9 @@ fn benchmarks(c: &mut Criterion) { /// A test case with prepared intermediate results. struct Case { ctx: Rc>, + state: State, id: SourceId, - ast: Rc, + ast: SyntaxTree, module: Module, tree: LayoutTree, frames: Vec>, @@ -90,13 +82,22 @@ struct Case { impl Case { fn new(ctx: Rc>, id: SourceId) -> Self { let mut borrowed = ctx.borrow_mut(); + let state = State::default(); let source = borrowed.sources.get(id); - let ast = Rc::new(parse(source).unwrap()); - let module = eval(&mut borrowed, id, Rc::clone(&ast)).unwrap(); - let tree = exec(&mut borrowed, &module.template); + let ast = parse(source).unwrap(); + let module = eval(&mut borrowed, id, &ast).unwrap(); + let tree = module.template.to_tree(&state); let frames = layout(&mut borrowed, &tree); drop(borrowed); - Self { ctx, id, ast, module, tree, frames } + Self { + ctx, + state, + id, + ast, + module, + tree, + frames, + } } fn parse(&self) -> SyntaxTree { @@ -104,11 +105,11 @@ impl Case { } fn eval(&self) -> TypResult { - eval(&mut self.ctx.borrow_mut(), self.id, Rc::clone(&self.ast)) + eval(&mut self.ctx.borrow_mut(), self.id, &self.ast) } - fn exec(&self) -> LayoutTree { - exec(&mut self.ctx.borrow_mut(), &self.module.template) + fn build(&self) -> LayoutTree { + self.module.template.to_tree(&self.state) } fn layout(&self) -> Vec> { diff --git a/src/eval/mod.rs b/src/eval/mod.rs index d8ce7884d..d49893713 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, -) -> TypResult { +pub fn eval(ctx: &mut Context, source: SourceId, ast: &SyntaxTree) -> TypResult { 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, /// 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; } -impl Eval for Rc { +impl Eval for SyntaxTree { type Output = Template; fn eval(&self, ctx: &mut EvalContext) -> TypResult { - 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 similarity index 90% rename from src/exec/state.rs rename to src/eval/state.rs index 56cf5f2e9..760a830a9 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, - /// The current alignments of layouts in their parents. + /// The alignments of layouts in their parents. pub aligns: Gen, - /// The current page settings. + /// The page settings. pub page: Rc, - /// The current paragraph settings. + /// The paragraph settings. pub par: Rc, - /// The current font settings. + /// The font settings. pub font: Rc, } @@ -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 96aa8a863..595e55547 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>); +/// 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 LayoutNode>), + /// An block node builder. + Block(Rc LayoutNode>), + /// Save the current state. + Save, + /// Restore the last saved state. + Restore, + /// A function that can modify the current state. + Modify(Rc), +} + impl Template { - /// Create a new template from a vector of nodes. - pub fn new(nodes: Vec) -> Self { - Self(Rc::new(nodes)) + /// Create a new, empty template. + pub fn new() -> Self { + Self(Rc::new(vec![])) } - /// Iterate over the contained template nodes. - pub fn iter(&self) -> std::slice::Iter { - self.0.iter() + /// Create a template from a builder for an inline-level node. + pub fn from_inline(f: F) -> Self + where + F: Fn(&State) -> T + 'static, + T: Into, + { + 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: F) -> Self + where + F: Fn(&State) -> T + 'static, + T: Into, + { + 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) { + self.make_mut().push(TemplateNode::Text(text.into())); + } + + /// Add text, but in monospace. + pub fn monospace(&mut self, text: impl Into) { + self.save(); + self.modify(|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); + } + + /// Modify the state. + pub fn modify(&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 { + 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 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