From d763f0f5a6a700352ee8926c15c8e58624f705c9 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Fri, 15 Jan 2021 16:53:02 +0100 Subject: [PATCH] =?UTF-8?q?Split=20state=20and=20scopes,=20less=20ref-coun?= =?UTF-8?q?ting=20=F0=9F=94=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bench/src/bench.rs | 31 ++++++++++++++----------------- src/env.rs | 5 ----- src/eval/call.rs | 15 +++++++++++---- src/eval/context.rs | 14 ++++++++------ src/eval/mod.rs | 22 ++++++++++++++-------- src/eval/scope.rs | 41 ++++++++++++++++++++++++++++++++++++----- src/eval/state.rs | 37 +++++++++++++++++++------------------ src/font.rs | 18 ++++++++++++++++++ src/layout/mod.rs | 10 +++++----- src/layout/text.rs | 3 +-- src/lib.rs | 15 +++++++++------ src/library/insert.rs | 6 +----- src/library/mod.rs | 7 +++++-- src/library/style.rs | 5 ++--- src/main.rs | 20 ++++++++++---------- tests/typeset.rs | 32 +++++++++++++++----------------- 16 files changed, 168 insertions(+), 113 deletions(-) diff --git a/bench/src/bench.rs b/bench/src/bench.rs index 58ecf9fd0..e1ae838ad 100644 --- a/bench/src/bench.rs +++ b/bench/src/bench.rs @@ -1,14 +1,12 @@ -use std::cell::RefCell; -use std::rc::Rc; - use criterion::{criterion_group, criterion_main, Criterion}; -use fontdock::fs::{FsIndex, FsSource}; +use fontdock::fs::FsIndex; use typst::env::{Env, ResourceLoader}; use typst::eval::{eval, State}; use typst::export::pdf; -use typst::font::FontLoader; +use typst::font::FsIndexExt; use typst::layout::layout; +use typst::library; use typst::parse::parse; use typst::typeset; @@ -25,25 +23,24 @@ fn benchmarks(c: &mut Criterion) { let mut index = FsIndex::new(); index.search_dir(FONT_DIR); - let (files, descriptors) = index.into_vecs(); - let env = Rc::new(RefCell::new(Env { - fonts: FontLoader::new(Box::new(FsSource::new(files)), descriptors), + let mut env = Env { + fonts: index.into_dynamic_loader(), resources: ResourceLoader::new(), - })); + }; + + let scope = library::new(); + let state = State::default(); // Prepare intermediate results and run warm. - let state = State::default(); let syntax_tree = parse(COMA).output; - let layout_tree = eval(&syntax_tree, Rc::clone(&env), state.clone()).output; - let frames = layout(&layout_tree, Rc::clone(&env)); + let layout_tree = eval(&syntax_tree, &mut env, &scope, state.clone()).output; + let frames = layout(&layout_tree, &mut env); // Bench! bench!("parse-coma": parse(COMA)); - bench!("eval-coma": eval(&syntax_tree, Rc::clone(&env), state.clone())); - bench!("layout-coma": layout(&layout_tree, Rc::clone(&env))); - bench!("typeset-coma": typeset(COMA, Rc::clone(&env), state.clone())); - - let env = env.borrow(); + bench!("eval-coma": eval(&syntax_tree, &mut env, &scope, state.clone())); + bench!("layout-coma": layout(&layout_tree, &mut env)); + bench!("typeset-coma": typeset(COMA, &mut env, &scope, state.clone())); bench!("export-pdf-coma": pdf::export(&frames, &env)); } diff --git a/src/env.rs b/src/env.rs index 58c44a5fc..5746dd2f8 100644 --- a/src/env.rs +++ b/src/env.rs @@ -1,22 +1,17 @@ //! Environment interactions. use std::any::Any; -use std::cell::RefCell; use std::collections::{hash_map::Entry, HashMap}; use std::fmt::{self, Debug, Formatter}; use std::fs; use std::io::Cursor; use std::path::{Path, PathBuf}; -use std::rc::Rc; use image::io::Reader as ImageReader; use image::{DynamicImage, GenericImageView, ImageFormat}; use crate::font::FontLoader; -/// A reference-counted shared environment. -pub type SharedEnv = Rc>; - /// Encapsulates all environment dependencies (fonts, resources). #[derive(Debug)] pub struct Env { diff --git a/src/eval/call.rs b/src/eval/call.rs index f47ee8479..8e75f17cc 100644 --- a/src/eval/call.rs +++ b/src/eval/call.rs @@ -8,7 +8,7 @@ impl Eval for Spanned<&ExprCall> { let name = &self.v.name.v; let span = self.v.name.span; - if let Some(value) = ctx.state.scope.get(name) { + if let Some(value) = ctx.scopes.get(name) { if let Value::Func(func) = value { let func = func.clone(); ctx.deco(Deco::Resolved.with_span(span)); @@ -90,10 +90,10 @@ impl Args { } /// Filter out and remove all convertible positional arguments. - pub fn filter<'a, T>( + pub fn filter<'a, 'b: 'a, T>( &'a mut self, - ctx: &'a mut EvalContext, - ) -> impl Iterator + 'a + ctx: &'a mut EvalContext<'b>, + ) -> impl Iterator + Captures<'a> + Captures<'b> where T: Cast>, { @@ -130,6 +130,13 @@ impl Args { } } +// This is a workaround because `-> impl Trait + 'a + 'b` does not work. +// +// See also: https://github.com/rust-lang/rust/issues/49431 +#[doc(hidden)] +pub trait Captures<'a> {} +impl<'a, T: ?Sized> Captures<'a> for T {} + /// Cast the value into `T`, generating an error if the conversion fails. fn cast(ctx: &mut EvalContext, value: Spanned) -> Option where diff --git a/src/eval/context.rs b/src/eval/context.rs index 1e09aaaf7..a998bbdcb 100644 --- a/src/eval/context.rs +++ b/src/eval/context.rs @@ -6,7 +6,6 @@ use fontdock::FontStyle; use super::*; use crate::diag::Diag; use crate::diag::{Deco, Feedback, Pass}; -use crate::env::SharedEnv; use crate::geom::{ChildAlign, Dir, Gen, LayoutDirs, Length, Linear, Sides, Size}; use crate::layout::{ Expansion, Node, NodePad, NodePages, NodePar, NodeSpacing, NodeStack, NodeText, Tree, @@ -14,9 +13,11 @@ use crate::layout::{ /// The context for evaluation. #[derive(Debug)] -pub struct EvalContext { +pub struct EvalContext<'a> { /// The environment from which resources are gathered. - pub env: SharedEnv, + pub env: &'a mut Env, + /// The active scopes. + pub scopes: Scopes<'a>, /// The active evaluation state. pub state: State, /// The accumulated feedback. @@ -34,11 +35,12 @@ pub struct EvalContext { inner: Vec, } -impl EvalContext { - /// Create a new evaluation context with a base state. - pub fn new(env: SharedEnv, state: State) -> Self { +impl<'a> EvalContext<'a> { + /// Create a new evaluation context with a base state and scope. + pub fn new(env: &'a mut Env, scope: &'a Scope, state: State) -> Self { Self { env, + scopes: Scopes::new(scope), state, groups: vec![], inner: vec![], diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 9abc1074f..efc77f69d 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -18,17 +18,23 @@ use std::rc::Rc; use crate::color::Color; use crate::diag::Pass; -use crate::env::SharedEnv; +use crate::env::Env; use crate::geom::{Angle, Length, Relative, Spec}; use crate::layout::{self, Expansion, NodeSpacing, NodeStack}; use crate::syntax::*; /// Evaluate a syntax tree into a layout tree. /// -/// The given `state` is the base state that may be updated over the course of -/// evaluation. -pub fn eval(tree: &Tree, env: SharedEnv, state: State) -> Pass { - let mut ctx = EvalContext::new(env, state); +/// The `state` is the base state that may be updated over the course of +/// evaluation. The `scope` similarly consists of the base definitions that are +/// present from the beginning (typically, the standard library). +pub fn eval( + tree: &Tree, + env: &mut Env, + scope: &Scope, + state: State, +) -> Pass { + let mut ctx = EvalContext::new(env, scope, state); ctx.start_page_group(Softness::Hard); tree.eval(&mut ctx); ctx.end_page_group(|s| s == Softness::Hard); @@ -118,7 +124,7 @@ impl Eval for Spanned<&NodeRaw> { fn eval(self, ctx: &mut EvalContext) -> Self::Output { let prev = Rc::clone(&ctx.state.font.families); - let families = Rc::make_mut(&mut ctx.state.font.families); + let families = ctx.state.font.families_mut(); families.list.insert(0, "monospace".to_string()); families.flatten(); @@ -151,7 +157,7 @@ impl Eval for Spanned<&Expr> { fn eval(self, ctx: &mut EvalContext) -> Self::Output { match self.v { Expr::None => Value::None, - Expr::Ident(v) => match ctx.state.scope.get(v) { + Expr::Ident(v) => match ctx.scopes.get(v) { Some(value) => value.clone(), None => { ctx.diag(error!(self.span, "unknown variable")); @@ -179,7 +185,7 @@ impl Eval for Spanned<&Expr> { Some(expr) => expr.as_ref().eval(ctx), None => Value::None, }; - Rc::make_mut(&mut ctx.state.scope).set(v.pat.v.as_str(), value); + ctx.scopes.define(v.pat.v.as_str(), value); Value::None } } diff --git a/src/eval/scope.rs b/src/eval/scope.rs index dd7cc1da1..62ee7e402 100644 --- a/src/eval/scope.rs +++ b/src/eval/scope.rs @@ -1,27 +1,58 @@ use std::collections::HashMap; use std::fmt::{self, Debug, Formatter}; +use std::iter; use super::Value; -/// A map from identifiers to values. +/// A hierarchy of scopes. +#[derive(Debug, Clone, PartialEq)] +pub struct Scopes<'a> { + /// The active scope. + top: Scope, + /// The stack of lower scopes. + scopes: Vec, + /// The base scope. + base: &'a Scope, +} + +impl<'a> Scopes<'a> { + /// Create a new hierarchy of scopes. + pub fn new(base: &'a Scope) -> Self { + Self { top: Scope::new(), scopes: vec![], base } + } + + /// Look up the value of a variable in the scopes. + pub fn get(&self, var: &str) -> Option<&Value> { + iter::once(&self.top) + .chain(&self.scopes) + .chain(iter::once(self.base)) + .find_map(|scope| scope.get(var)) + } + + /// Define a variable in the active scope. + pub fn define(&mut self, var: impl Into, value: impl Into) { + self.top.set(var, value); + } +} + +/// A map from variable names to values. #[derive(Default, Clone, PartialEq)] pub struct Scope { values: HashMap, } impl Scope { - // Create a new empty scope with a fallback function that is invoked when no - // match is found. + // Create a new empty scope. pub fn new() -> Self { Self::default() } - /// Return the value of the given variable. + /// Look up the value of a variable. pub fn get(&self, var: &str) -> Option<&Value> { self.values.get(var) } - /// Store the value for the given variable. + /// Store the value for a variable. pub fn set(&mut self, var: impl Into, value: impl Into) { self.values.insert(var.into(), value.into()); } diff --git a/src/eval/state.rs b/src/eval/state.rs index ce6bd0099..a88dfd070 100644 --- a/src/eval/state.rs +++ b/src/eval/state.rs @@ -2,7 +2,6 @@ use std::rc::Rc; use fontdock::{fallback, FallbackTree, FontStretch, FontStyle, FontVariant, FontWeight}; -use super::Scope; use crate::geom::{ Align, ChildAlign, Dir, LayoutDirs, Length, Linear, Relative, Sides, Size, Spec, }; @@ -12,14 +11,12 @@ use crate::paper::{Paper, PaperClass, PAPER_A4}; /// The evaluation state. #[derive(Debug, Clone, PartialEq)] pub struct State { - /// The scope that contains variable definitions. - pub scope: Rc, /// The current page state. - pub page: StatePage, + pub page: PageSettings, /// The current paragraph state. - pub par: StatePar, + pub par: ParSettings, /// The current font state. - pub font: StateFont, + pub font: FontSettings, /// The current directions. pub dirs: LayoutDirs, /// The current alignments. @@ -29,10 +26,9 @@ pub struct State { impl Default for State { fn default() -> Self { Self { - scope: Rc::new(crate::library::_std()), - page: StatePage::default(), - par: StatePar::default(), - font: StateFont::default(), + page: PageSettings::default(), + par: ParSettings::default(), + font: FontSettings::default(), dirs: LayoutDirs::new(Dir::TTB, Dir::LTR), align: ChildAlign::new(Align::Start, Align::Start), } @@ -41,7 +37,7 @@ impl Default for State { /// Defines page properties. #[derive(Debug, Copy, Clone, PartialEq)] -pub struct StatePage { +pub struct PageSettings { /// The class of this page. pub class: PaperClass, /// The width and height of the page. @@ -53,7 +49,7 @@ pub struct StatePage { pub margins: Sides>, } -impl StatePage { +impl PageSettings { /// The default page style for the given paper. pub fn new(paper: Paper) -> Self { Self { @@ -76,7 +72,7 @@ impl StatePage { } } -impl Default for StatePage { +impl Default for PageSettings { fn default() -> Self { Self::new(PAPER_A4) } @@ -84,7 +80,7 @@ impl Default for StatePage { /// Defines paragraph properties. #[derive(Debug, Copy, Clone, PartialEq)] -pub struct StatePar { +pub struct ParSettings { /// The spacing between words (dependent on scaled font size). pub word_spacing: Linear, /// The spacing between lines (dependent on scaled font size). @@ -93,7 +89,7 @@ pub struct StatePar { pub par_spacing: Linear, } -impl Default for StatePar { +impl Default for ParSettings { fn default() -> Self { Self { word_spacing: Relative::new(0.25).into(), @@ -105,7 +101,7 @@ impl Default for StatePar { /// Defines font properties. #[derive(Debug, Clone, PartialEq)] -pub struct StateFont { +pub struct FontSettings { /// A tree of font family names and generic class names. pub families: Rc, /// The selected font variant. @@ -122,14 +118,19 @@ pub struct StateFont { pub emph: bool, } -impl StateFont { +impl FontSettings { + /// Access the `families` mutably. + pub fn families_mut(&mut self) -> &mut FallbackTree { + Rc::make_mut(&mut self.families) + } + /// The absolute font size. pub fn font_size(&self) -> Length { self.scale.resolve(self.size) } } -impl Default for StateFont { +impl Default for FontSettings { fn default() -> Self { Self { /// The default tree of font fallbacks. diff --git a/src/font.rs b/src/font.rs index 40ec59186..01ea48c00 100644 --- a/src/font.rs +++ b/src/font.rs @@ -3,6 +3,9 @@ use fontdock::{ContainsChar, FaceFromVec, FontSource}; use ttf_parser::Face; +#[cfg(feature = "fs")] +use fontdock::fs::{FsIndex, FsSource}; + /// A font loader that is backed by a dynamic source. pub type FontLoader = fontdock::FontLoader>>; @@ -47,3 +50,18 @@ impl ContainsChar for FaceBuf { self.get().glyph_index(c).is_some() } } + +#[cfg(feature = "fs")] +pub trait FsIndexExt { + /// Create a font loader backed by a boxed [`FsSource`] which serves all + /// indexed font faces. + fn into_dynamic_loader(self) -> FontLoader; +} + +#[cfg(feature = "fs")] +impl FsIndexExt for FsIndex { + fn into_dynamic_loader(self) -> FontLoader { + let (files, descriptors) = self.into_vecs(); + FontLoader::new(Box::new(FsSource::new(files)), descriptors) + } +} diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 714bac4b5..44960de7e 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -8,7 +8,7 @@ mod spacing; mod stack; mod text; -use crate::env::{ResourceId, SharedEnv}; +use crate::env::{Env, ResourceId}; use crate::geom::*; use crate::shaping::Shaped; @@ -21,7 +21,7 @@ pub use stack::*; pub use text::*; /// Layout a tree into a collection of frames. -pub fn layout(tree: &Tree, env: SharedEnv) -> Vec { +pub fn layout(tree: &Tree, env: &mut Env) -> Vec { tree.layout(&mut LayoutContext { env }) } @@ -65,10 +65,10 @@ pub trait Layout { } /// The context for layouting. -#[derive(Debug, Clone)] -pub struct LayoutContext { +#[derive(Debug)] +pub struct LayoutContext<'a> { /// The environment from which fonts are gathered. - pub env: SharedEnv, + pub env: &'a mut Env, } /// A collection of areas to layout into. diff --git a/src/layout/text.rs b/src/layout/text.rs index cfd833727..ee85ee171 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -25,13 +25,12 @@ pub struct NodeText { impl Layout for NodeText { fn layout(&self, ctx: &mut LayoutContext, _: &Areas) -> Layouted { - let mut env = ctx.env.borrow_mut(); Layouted::Frame( shaping::shape( &self.text, self.dir, self.font_size, - &mut env.fonts, + &mut ctx.env.fonts, &self.families, self.variant, ), diff --git a/src/lib.rs b/src/lib.rs index 05cc0569b..39ca47a3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,18 +42,21 @@ pub mod pretty; pub mod shaping; pub mod syntax; -use std::rc::Rc; - use crate::diag::{Feedback, Pass}; -use crate::env::SharedEnv; -use crate::eval::State; +use crate::env::Env; +use crate::eval::{Scope, State}; use crate::layout::Frame; /// Process _Typst_ source code directly into a collection of frames. -pub fn typeset(src: &str, env: SharedEnv, state: State) -> Pass> { +pub fn typeset( + src: &str, + env: &mut Env, + scope: &Scope, + state: State, +) -> Pass> { let Pass { output: syntax_tree, feedback: f1 } = parse::parse(src); let Pass { output: layout_tree, feedback: f2 } = - eval::eval(&syntax_tree, Rc::clone(&env), state); + eval::eval(&syntax_tree, env, scope, state); let frames = layout::layout(&layout_tree, env); Pass::new(frames, Feedback::join(f1, f2)) } diff --git a/src/library/insert.rs b/src/library/insert.rs index 51cbbf523..58e8a11c2 100644 --- a/src/library/insert.rs +++ b/src/library/insert.rs @@ -16,12 +16,9 @@ pub fn image(ctx: &mut EvalContext, args: &mut Args) -> Value { let height = args.get(ctx, "height"); if let Some(path) = path { - let mut env = ctx.env.borrow_mut(); - let loaded = env.resources.load(path.v, ImageResource::parse); - + let loaded = ctx.env.resources.load(path.v, ImageResource::parse); if let Some((res, img)) = loaded { let dimensions = img.buf.dimensions(); - drop(env); ctx.push(NodeImage { res, dimensions, @@ -30,7 +27,6 @@ pub fn image(ctx: &mut EvalContext, args: &mut Args) -> Value { align: ctx.state.align, }); } else { - drop(env); ctx.diag(error!(path.span, "failed to load image")); } } diff --git a/src/library/mod.rs b/src/library/mod.rs index f9047fc6d..5c4e774cf 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -1,4 +1,7 @@ //! The standard library. +//! +//! Call [`new`] to obtain a [`Scope`] containing all standard library +//! definitions. mod extend; mod insert; @@ -15,8 +18,8 @@ use fontdock::{FontStretch, FontStyle, FontWeight}; use crate::eval::{Scope, ValueAny, ValueFunc}; use crate::geom::Dir; -/// The scope containing the standard library. -pub fn _std() -> Scope { +/// Construct a scope containing all standard library definitions. +pub fn new() -> Scope { let mut std = Scope::new(); macro_rules! set { (func: $name:expr, $func:expr) => { diff --git a/src/library/style.rs b/src/library/style.rs index 7b2772ccf..2e3484409 100644 --- a/src/library/style.rs +++ b/src/library/style.rs @@ -1,5 +1,4 @@ use std::fmt::{self, Display, Formatter}; -use std::rc::Rc; use fontdock::{FontStretch, FontStyle, FontWeight}; @@ -69,7 +68,7 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> Value { let list: Vec<_> = args.filter::(ctx).map(|f| f.to_string()).collect(); if !list.is_empty() { - let families = Rc::make_mut(&mut ctx.state.font.families); + let families = ctx.state.font.families_mut(); families.list = list; families.flatten(); } @@ -89,7 +88,7 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> Value { for variant in FontFamily::VARIANTS { if let Some(FontFamilies(list)) = args.get(ctx, variant.as_str()) { let strings = list.into_iter().map(|f| f.to_string()).collect(); - let families = Rc::make_mut(&mut ctx.state.font.families); + let families = ctx.state.font.families_mut(); families.update_class_list(variant.to_string(), strings); families.flatten(); } diff --git a/src/main.rs b/src/main.rs index 4746bc0c5..07eb673af 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,15 @@ -use std::cell::RefCell; use std::fs; use std::path::{Path, PathBuf}; -use std::rc::Rc; use anyhow::{anyhow, bail, Context}; -use fontdock::fs::{FsIndex, FsSource}; +use fontdock::fs::FsIndex; use typst::diag::{Feedback, Pass}; use typst::env::{Env, ResourceLoader}; use typst::eval::State; use typst::export::pdf; -use typst::font::FontLoader; +use typst::font::FsIndexExt; +use typst::library; use typst::parse::LineMap; use typst::typeset; @@ -41,17 +40,18 @@ fn main() -> anyhow::Result<()> { index.search_dir("fonts"); index.search_system(); - let (files, descriptors) = index.into_vecs(); - let env = Rc::new(RefCell::new(Env { - fonts: FontLoader::new(Box::new(FsSource::new(files)), descriptors), + let mut env = Env { + fonts: index.into_dynamic_loader(), resources: ResourceLoader::new(), - })); + }; + let scope = library::new(); let state = State::default(); + let Pass { output: frames, feedback: Feedback { mut diags, .. }, - } = typeset(&src, Rc::clone(&env), state); + } = typeset(&src, &mut env, &scope, state); if !diags.is_empty() { diags.sort(); @@ -72,7 +72,7 @@ fn main() -> anyhow::Result<()> { } } - let pdf_data = pdf::export(&frames, &env.borrow()); + let pdf_data = pdf::export(&frames, &env); fs::write(&dest_path, pdf_data).context("Failed to write PDF file.")?; Ok(()) diff --git a/tests/typeset.rs b/tests/typeset.rs index 980e3a6c3..5777950f8 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -1,11 +1,9 @@ -use std::cell::RefCell; use std::env; use std::ffi::OsStr; use std::fs; use std::path::Path; -use std::rc::Rc; -use fontdock::fs::{FsIndex, FsSource}; +use fontdock::fs::FsIndex; use image::{GenericImageView, Rgba}; use tiny_skia::{ Canvas, Color, ColorU8, FillRule, FilterQuality, Paint, PathBuilder, Pattern, Pixmap, @@ -15,12 +13,13 @@ use ttf_parser::OutlineBuilder; use walkdir::WalkDir; use typst::diag::{Diag, Feedback, Level, Pass}; -use typst::env::{Env, ImageResource, ResourceLoader, SharedEnv}; +use typst::env::{Env, ImageResource, ResourceLoader}; use typst::eval::{Args, EvalContext, Scope, State, Value, ValueFunc}; use typst::export::pdf; -use typst::font::FontLoader; +use typst::font::FsIndexExt; use typst::geom::{Length, Point, Sides, Size, Spec}; use typst::layout::{Element, Expansion, Frame, Image}; +use typst::library; use typst::parse::{LineMap, Scanner}; use typst::pretty::{Pretty, Printer}; use typst::shaping::Shaped; @@ -61,11 +60,10 @@ fn main() { let mut index = FsIndex::new(); index.search_dir(FONT_DIR); - let (files, descriptors) = index.into_vecs(); - let env = Rc::new(RefCell::new(Env { - fonts: FontLoader::new(Box::new(FsSource::new(files)), descriptors), + let mut env = Env { + fonts: index.into_dynamic_loader(), resources: ResourceLoader::new(), - })); + }; let playground = Path::new("playground.typ"); if playground.exists() && filtered.is_empty() { @@ -74,7 +72,7 @@ fn main() { Path::new("playground.png"), Path::new("playground.pdf"), None, - &env, + &mut env, ); } @@ -84,7 +82,7 @@ fn main() { let png_path = Path::new(PNG_DIR).join(&relative).with_extension("png"); let pdf_path = Path::new(PDF_DIR).join(&relative).with_extension("pdf"); let ref_path = Path::new(REF_DIR).join(&relative).with_extension("png"); - ok &= test(&src_path, &png_path, &pdf_path, Some(&ref_path), &env); + ok &= test(&src_path, &png_path, &pdf_path, Some(&ref_path), &mut env); } if !ok { @@ -127,7 +125,7 @@ fn test( png_path: &Path, pdf_path: &Path, ref_path: Option<&Path>, - env: &SharedEnv, + env: &mut Env, ) -> bool { let name = src_path.strip_prefix(TYP_DIR).unwrap_or(src_path); println!("Testing {}", name.display()); @@ -143,7 +141,6 @@ fn test( frames.extend(part_frames); } - let env = env.borrow(); if !frames.is_empty() { let pdf_data = pdf::export(&frames, &env); fs::create_dir_all(&pdf_path.parent().unwrap()).unwrap(); @@ -173,23 +170,24 @@ fn test( ok } -fn test_part(i: usize, src: &str, env: &SharedEnv) -> (bool, Vec) { +fn test_part(i: usize, src: &str, env: &mut Env) -> (bool, Vec) { let map = LineMap::new(src); let (compare_ref, ref_diags) = parse_metadata(src, &map); - let mut state = State::default(); + let mut scope = library::new(); + register_helpers(&mut scope); // We want to have "unbounded" pages, so we allow them to be infinitely // large and fit them to match their content. + let mut state = State::default(); state.page.size = Size::new(Length::pt(120.0), Length::raw(f64::INFINITY)); state.page.expand = Spec::new(Expansion::Fill, Expansion::Fit); state.page.margins = Sides::uniform(Some(Length::pt(10.0).into())); - register_helpers(Rc::make_mut(&mut state.scope)); let Pass { output: mut frames, feedback: Feedback { mut diags, .. }, - } = typeset(&src, Rc::clone(env), state); + } = typeset(&src, env, &scope, state); if !compare_ref { frames.clear();