From 8000783f95ee007d9dda6f1dcc1c42c8e607b122 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 20 Jul 2021 18:35:05 +0200 Subject: [PATCH] FileId instead of Path + FileHash --- bench/src/clock.rs | 18 ++++++---- src/eval/mod.rs | 68 ++++++++++++----------------------- src/font.rs | 42 ++++++++++++++++------ src/image.rs | 10 +++--- src/lib.rs | 16 ++++----- src/library/elements.rs | 4 +-- src/loading/fs.rs | 79 +++++++++++++++++------------------------ src/loading/mod.rs | 41 +++++++++------------ src/main.rs | 31 +++++++--------- tests/typeset.rs | 15 +++----- 10 files changed, 143 insertions(+), 181 deletions(-) diff --git a/bench/src/clock.rs b/bench/src/clock.rs index 129215fe8..c3dcaeb73 100644 --- a/bench/src/clock.rs +++ b/bench/src/clock.rs @@ -9,7 +9,7 @@ use typst::eval::{eval, Module, Scope}; use typst::exec::{exec, State}; use typst::export::pdf; use typst::layout::{layout, Frame, LayoutTree}; -use typst::loading::FsLoader; +use typst::loading::{FileId, FsLoader}; use typst::parse::parse; use typst::syntax::SyntaxTree; use typst::typeset; @@ -20,11 +20,13 @@ const CASES: &[&str] = &["coma.typ", "text/basic.typ"]; fn benchmarks(c: &mut Criterion) { let ctx = Context::new(); + for case in CASES { let path = Path::new(TYP_DIR).join(case); let name = path.file_stem().unwrap().to_string_lossy(); + let src_id = ctx.borrow_mut().loader.resolve_path(&path).unwrap(); let src = std::fs::read_to_string(&path).unwrap(); - let case = Case::new(src, ctx.clone()); + let case = Case::new(src_id, src, ctx.clone()); macro_rules! bench { ($step:literal, setup = |$cache:ident| $setup:expr, code = $code:expr $(,)?) => { @@ -93,6 +95,7 @@ impl Context { /// A test case with prepared intermediate results. struct Case { ctx: Rc>, + src_id: FileId, src: String, scope: Scope, state: State, @@ -103,19 +106,19 @@ struct Case { } impl Case { - fn new(src: impl Into, ctx: Rc>) -> Self { + fn new(src_id: FileId, src: String, ctx: Rc>) -> Self { let mut borrowed = ctx.borrow_mut(); let Context { loader, cache } = &mut *borrowed; let scope = typst::library::new(); let state = typst::exec::State::default(); - let src = src.into(); let ast = Rc::new(parse(&src).output); - let module = eval(loader, cache, None, Rc::clone(&ast), &scope).output; + let module = eval(loader, cache, src_id, Rc::clone(&ast), &scope).output; let tree = exec(&module.template, state.clone()).output; let frames = layout(loader, cache, &tree); drop(borrowed); Self { ctx, + src_id, src, scope, state, @@ -133,7 +136,8 @@ impl Case { fn eval(&self) -> Module { let mut borrowed = self.ctx.borrow_mut(); let Context { loader, cache } = &mut *borrowed; - eval(loader, cache, None, Rc::clone(&self.ast), &self.scope).output + let ast = Rc::clone(&self.ast); + eval(loader, cache, self.src_id, ast, &self.scope).output } fn exec(&self) -> LayoutTree { @@ -150,7 +154,7 @@ impl Case { let mut borrowed = self.ctx.borrow_mut(); let Context { loader, cache } = &mut *borrowed; let state = self.state.clone(); - typeset(loader, cache, None, &self.src, &self.scope, state).output + typeset(loader, cache, self.src_id, &self.src, &self.scope, state).output } fn pdf(&self) -> Vec { diff --git a/src/eval/mod.rs b/src/eval/mod.rs index ed8a81c84..cbb61153c 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -22,28 +22,27 @@ pub use value::*; use std::collections::HashMap; use std::mem; -use std::path::{Path, PathBuf}; +use std::path::Path; use std::rc::Rc; use crate::cache::Cache; use crate::diag::{Diag, DiagSet, Pass}; use crate::eco::EcoString; use crate::geom::{Angle, Fractional, Length, Relative}; -use crate::loading::{FileHash, Loader}; +use crate::loading::{FileId, Loader}; use crate::parse::parse; use crate::syntax::visit::Visit; use crate::syntax::*; -use crate::util::PathExt; /// Evaluate a parsed source file into a module. pub fn eval( loader: &mut dyn Loader, cache: &mut Cache, - path: Option<&Path>, + location: FileId, ast: Rc, scope: &Scope, ) -> Pass { - let mut ctx = EvalContext::new(loader, cache, path, scope); + let mut ctx = EvalContext::new(loader, cache, location, scope); let template = ast.eval(&mut ctx); let module = Module { scope: ctx.scopes.top, template }; Pass::new(module, ctx.diags) @@ -68,12 +67,10 @@ pub struct EvalContext<'a> { pub scopes: Scopes<'a>, /// Evaluation diagnostics. pub diags: DiagSet, - /// The location of the currently evaluated file. - pub path: Option, /// The stack of imported files that led to evaluation of the current file. - pub route: Vec, + pub route: Vec, /// A map of loaded module. - pub modules: HashMap, + pub modules: HashMap, } impl<'a> EvalContext<'a> { @@ -81,25 +78,15 @@ impl<'a> EvalContext<'a> { pub fn new( loader: &'a mut dyn Loader, cache: &'a mut Cache, - path: Option<&Path>, + location: FileId, scope: &'a Scope, ) -> Self { - let path = path.map(PathExt::normalize); - - let mut route = vec![]; - if let Some(path) = &path { - if let Some(hash) = loader.resolve(path) { - route.push(hash); - } - } - Self { loader, cache, scopes: Scopes::new(Some(scope)), diags: DiagSet::new(), - path, - route, + route: vec![location], modules: HashMap::new(), } } @@ -107,37 +94,30 @@ impl<'a> EvalContext<'a> { /// Resolve a path relative to the current file. /// /// Generates an error if the file is not found. - pub fn resolve(&mut self, path: &str, span: Span) -> Option<(PathBuf, FileHash)> { - let path = match &self.path { - Some(current) => current.parent()?.join(path), - None => PathBuf::from(path), - }; - - match self.loader.resolve(&path) { - Some(hash) => Some((path.normalize(), hash)), - None => { - self.diag(error!(span, "file not found")); - None - } - } + pub fn resolve(&mut self, path: &str, span: Span) -> Option { + let base = *self.route.last()?; + self.loader.resolve_from(base, Path::new(path)).or_else(|| { + self.diag(error!(span, "file not found")); + None + }) } /// Process an import of a module relative to the current location. - pub fn import(&mut self, path: &str, span: Span) -> Option { - let (resolved, hash) = self.resolve(path, span)?; + pub fn import(&mut self, path: &str, span: Span) -> Option { + let id = self.resolve(path, span)?; // Prevent cyclic importing. - if self.route.contains(&hash) { + if self.route.contains(&id) { self.diag(error!(span, "cyclic import")); return None; } // Check whether the module was already loaded. - if self.modules.get(&hash).is_some() { - return Some(hash); + if self.modules.get(&id).is_some() { + return Some(id); } - let buffer = self.loader.load_file(&resolved).or_else(|| { + let buffer = self.loader.load_file(id).or_else(|| { self.diag(error!(span, "failed to load file")); None })?; @@ -154,8 +134,7 @@ impl<'a> EvalContext<'a> { let new_scopes = Scopes::new(self.scopes.base); let old_scopes = mem::replace(&mut self.scopes, new_scopes); let old_diags = mem::replace(&mut self.diags, parsed.diags); - let old_path = mem::replace(&mut self.path, Some(resolved)); - self.route.push(hash); + self.route.push(id); // Evaluate the module. let ast = Rc::new(parsed.output); @@ -164,7 +143,6 @@ impl<'a> EvalContext<'a> { // Restore the old context. let new_scopes = mem::replace(&mut self.scopes, old_scopes); let new_diags = mem::replace(&mut self.diags, old_diags); - self.path = old_path; self.route.pop(); // Put all diagnostics from the module on the import. @@ -175,9 +153,9 @@ impl<'a> EvalContext<'a> { // Save the evaluated module. let module = Module { scope: new_scopes.top, template }; - self.modules.insert(hash, module); + self.modules.insert(id, module); - Some(hash) + Some(id) } /// Add a diagnostic. diff --git a/src/font.rs b/src/font.rs index d63c4c1b1..273c29144 100644 --- a/src/font.rs +++ b/src/font.rs @@ -1,18 +1,19 @@ //! Font handling. -use std::collections::HashMap; +use std::collections::{hash_map::Entry, HashMap}; use std::fmt::{self, Debug, Display, Formatter}; use std::ops::Add; +use std::rc::Rc; use decorum::N64; use serde::{Deserialize, Serialize}; use crate::geom::Length; -use crate::loading::{Buffer, Loader}; +use crate::loading::{FileId, Loader}; /// A font face. pub struct Face { - buffer: Buffer, + buffer: Rc>, index: u32, ttf: rustybuzz::Face<'static>, units_per_em: f64, @@ -33,7 +34,7 @@ pub struct LineMetrics { impl Face { /// Parse a font face from a buffer and collection index. - pub fn new(buffer: Buffer, index: u32) -> Option { + pub fn new(buffer: Rc>, index: u32) -> Option { // SAFETY: // - The slices's location is stable in memory: // - We don't move the underlying vector @@ -88,7 +89,7 @@ impl Face { } /// The underlying buffer. - pub fn buffer(&self) -> &Buffer { + pub fn buffer(&self) -> &Rc> { &self.buffer } @@ -204,6 +205,7 @@ impl Add for Em { pub struct FontCache { faces: Vec>, families: HashMap>, + buffers: HashMap>>, on_load: Option>, } @@ -222,7 +224,12 @@ impl FontCache { .or_insert_with(|| vec![id]); } - Self { faces, families, on_load: None } + Self { + faces, + families, + buffers: HashMap::new(), + on_load: None, + } } /// Query for and load the font face from the given `family` that most @@ -270,12 +277,23 @@ impl FontCache { let idx = id.0 as usize; let slot = &mut self.faces[idx]; if slot.is_none() { - let index = infos[idx].index; - let buffer = loader.load_face(idx)?; - let face = Face::new(buffer, index)?; + let FaceInfo { file, index, .. } = infos[idx]; + + // Check the buffer cache since multiple faces may + // refer to the same data (font collection). + let buffer = match self.buffers.entry(file) { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => { + let buffer = loader.load_file(file)?; + entry.insert(Rc::new(buffer)) + } + }; + + let face = Face::new(Rc::clone(buffer), index)?; if let Some(callback) = &self.on_load { callback(id, &face); } + *slot = Some(face); } @@ -322,14 +340,16 @@ impl FaceId { /// Properties of a single font face. #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct FaceInfo { + /// The font file. + pub file: FileId, + /// The collection index in the font file. + pub index: u32, /// The typographic font family this face is part of. pub family: String, /// Properties that distinguish this face from other faces in the same /// family. #[serde(flatten)] pub variant: FontVariant, - /// The collection index in the font file. - pub index: u32, } /// Properties that distinguish a face from other faces in the same family. diff --git a/src/image.rs b/src/image.rs index f4617c5a1..282832593 100644 --- a/src/image.rs +++ b/src/image.rs @@ -3,13 +3,12 @@ use std::collections::{hash_map::Entry, HashMap}; use std::fmt::{self, Debug, Formatter}; use std::io::Cursor; -use std::path::Path; use image::io::Reader as ImageReader; use image::{DynamicImage, GenericImageView, ImageFormat}; use serde::{Deserialize, Serialize}; -use crate::loading::Loader; +use crate::loading::{FileId, Loader}; /// A loaded image. pub struct Image { @@ -69,11 +68,10 @@ impl ImageCache { } /// Load and decode an image file from a path. - pub fn load(&mut self, loader: &mut dyn Loader, path: &Path) -> Option { - let hash = loader.resolve(path)?; - let id = ImageId(hash.into_raw()); + pub fn load(&mut self, loader: &mut dyn Loader, file: FileId) -> Option { + let id = ImageId(file.into_raw()); if let Entry::Vacant(entry) = self.images.entry(id) { - let buffer = loader.load_file(path)?; + let buffer = loader.load_file(file)?; let image = Image::parse(&buffer)?; if let Some(callback) = &self.on_load { callback(id, &image); diff --git a/src/lib.rs b/src/lib.rs index 000d61d3b..d1f433808 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,7 +49,6 @@ pub mod pretty; pub mod syntax; pub mod util; -use std::path::Path; use std::rc::Rc; use crate::cache::Cache; @@ -57,7 +56,7 @@ use crate::diag::Pass; use crate::eval::Scope; use crate::exec::State; use crate::layout::Frame; -use crate::loading::Loader; +use crate::loading::{FileId, Loader}; /// Process source code directly into a collection of layouted frames. /// @@ -65,12 +64,11 @@ use crate::loading::Loader; /// - The `loader` is used to load fonts, images and other source files. /// - The `cache` stores things that are reusable across several compilations /// like loaded fonts, decoded images and layouting artifacts. -/// - The `path` should point to the source file if `src` comes from the file -/// system and is used to resolve relative paths (for importing and image -/// loading). +/// - The `location` is the file id of the source file and is used to resolve +/// relative paths (for importing and image loading). /// - The `src` is the _Typst_ source code to typeset. -/// - The `scope` contains definitions that are available everywhere, -/// typically the standard library. +/// - The `scope` contains definitions that are available everywhere, typically +/// the standard library. /// - The `state` defines initial properties for page size, font selection and /// so on. /// @@ -80,13 +78,13 @@ use crate::loading::Loader; pub fn typeset( loader: &mut dyn Loader, cache: &mut Cache, - path: Option<&Path>, + location: FileId, src: &str, scope: &Scope, state: State, ) -> Pass>> { let ast = parse::parse(src); - let module = eval::eval(loader, cache, path, Rc::new(ast.output), scope); + let module = eval::eval(loader, cache, location, Rc::new(ast.output), scope); let tree = exec::exec(&module.output.template, state); let frames = layout::layout(loader, cache, &tree.output); diff --git a/src/library/elements.rs b/src/library/elements.rs index dd2da877a..03fccfe7b 100644 --- a/src/library/elements.rs +++ b/src/library/elements.rs @@ -15,8 +15,8 @@ pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { let mut node = None; if let Some(path) = &path { - if let Some((resolved, _)) = ctx.resolve(&path.v, path.span) { - if let Some(id) = ctx.cache.image.load(ctx.loader, &resolved) { + if let Some(file) = ctx.resolve(&path.v, path.span) { + if let Some(id) = ctx.cache.image.load(ctx.loader, file) { node = Some(ImageNode { id, width, height }); } else { ctx.diag(error!(path.span, "failed to load image")); diff --git a/src/loading/fs.rs b/src/loading/fs.rs index 7fa1c1202..ea33016cb 100644 --- a/src/loading/fs.rs +++ b/src/loading/fs.rs @@ -1,39 +1,43 @@ -use std::collections::{hash_map::Entry, HashMap}; -use std::fs::File; +use std::collections::HashMap; +use std::fs::{self, File}; use std::io; use std::path::{Path, PathBuf}; -use std::rc::Rc; use memmap2::Mmap; use same_file::Handle; -use serde::{Deserialize, Serialize}; use ttf_parser::{name_id, Face}; use walkdir::WalkDir; -use super::{Buffer, FileHash, Loader}; +use super::{FileId, Loader}; use crate::font::{FaceInfo, FontStretch, FontStyle, FontVariant, FontWeight}; +use crate::util::PathExt; /// Loads fonts and images from the local file system. /// /// _This is only available when the `fs` feature is enabled._ -#[derive(Default, Debug, Clone, Serialize, Deserialize)] +#[derive(Default, Debug, Clone)] pub struct FsLoader { faces: Vec, - files: Vec, - #[serde(skip)] - cache: FileCache, + paths: HashMap, } -/// Maps from resolved file hashes to loaded file buffers. -type FileCache = HashMap; - impl FsLoader { /// Create a new loader without any fonts. pub fn new() -> Self { - Self { - faces: vec![], - files: vec![], - cache: HashMap::new(), + Self { faces: vec![], paths: HashMap::new() } + } + + /// Resolve a file id for a path. + pub fn resolve_path(&mut self, path: &Path) -> io::Result { + let file = File::open(path)?; + let meta = file.metadata()?; + if meta.is_file() { + let handle = Handle::from_file(file)?; + let id = FileId(fxhash::hash64(&handle)); + self.paths.insert(id, path.normalize()); + Ok(id) + } else { + Err(io::Error::new(io::ErrorKind::Other, "not a file")) } } @@ -149,17 +153,11 @@ impl FsLoader { stretch: FontStretch::from_number(face.width().to_number()), }; - // Merge with an existing entry for the same family name. - self.faces.push(FaceInfo { family, variant, index }); - self.files.push(path.to_owned()); + let file = self.resolve_path(path)?; + self.faces.push(FaceInfo { file, index, family, variant }); Ok(()) } - - /// Paths to font files, parallel to [`faces()`](Self::faces). - pub fn files(&self) -> &[PathBuf] { - &self.files - } } impl Loader for FsLoader { @@ -167,30 +165,14 @@ impl Loader for FsLoader { &self.faces } - fn load_face(&mut self, idx: usize) -> Option { - self.load_file(&self.files[idx].clone()) + fn resolve_from(&mut self, base: FileId, path: &Path) -> Option { + let dir = self.paths[&base].parent()?; + let full = dir.join(path); + self.resolve_path(&full).ok() } - fn load_file(&mut self, path: &Path) -> Option { - let hash = self.resolve(path)?; - Some(Rc::clone(match self.cache.entry(hash) { - Entry::Occupied(entry) => entry.into_mut(), - Entry::Vacant(entry) => { - let buffer = std::fs::read(path).ok()?; - entry.insert(Rc::new(buffer)) - } - })) - } - - fn resolve(&self, path: &Path) -> Option { - let file = File::open(path).ok()?; - let meta = file.metadata().ok()?; - if meta.is_file() { - let handle = Handle::from_file(file).ok()?; - Some(FileHash::from_raw(fxhash::hash64(&handle))) - } else { - None - } + fn load_file(&mut self, id: FileId) -> Option> { + fs::read(&self.paths[&id]).ok() } } @@ -203,7 +185,10 @@ mod tests { let mut loader = FsLoader::new(); loader.search_path("fonts"); - assert_eq!(loader.files, &[ + let mut paths: Vec<_> = loader.paths.values().collect(); + paths.sort(); + + assert_eq!(paths, [ Path::new("fonts/EBGaramond-Bold.ttf"), Path::new("fonts/EBGaramond-BoldItalic.ttf"), Path::new("fonts/EBGaramond-Italic.ttf"), diff --git a/src/loading/mod.rs b/src/loading/mod.rs index 0e171796b..c2f7ca395 100644 --- a/src/loading/mod.rs +++ b/src/loading/mod.rs @@ -7,44 +7,39 @@ mod fs; pub use fs::*; use std::path::Path; -use std::rc::Rc; + +use serde::{Deserialize, Serialize}; use crate::font::FaceInfo; -/// A shared byte buffer. -pub type Buffer = Rc>; - /// Loads resources from a local or remote source. pub trait Loader { /// Descriptions of all font faces this loader serves. fn faces(&self) -> &[FaceInfo]; - /// Load the font face with the given index in [`faces()`](Self::faces). - fn load_face(&mut self, idx: usize) -> Option; - - /// Load a file from a path. - fn load_file(&mut self, path: &Path) -> Option; - - /// Resolve a hash for the file the path points to. + /// Resolve a `path` relative to a `base` file. /// - /// This should return the same hash for all paths pointing to the same file + /// This should return the same id for all paths pointing to the same file /// and `None` if the file does not exist. - fn resolve(&self, path: &Path) -> Option; + fn resolve_from(&mut self, base: FileId, path: &Path) -> Option; + + /// Load a file by id. + fn load_file(&mut self, id: FileId) -> Option>; } -/// A file hash that can be [resolved](Loader::resolve) from a path. +/// A file id that can be [resolved](Loader::resolve_from) from a path. /// /// Should be the same for all paths pointing to the same file. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct FileHash(u64); +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +pub struct FileId(u64); -impl FileHash { - /// Create an file hash from a raw hash value. +impl FileId { + /// Create a file id from a raw value. pub fn from_raw(v: u64) -> Self { Self(v) } - /// Convert into the raw underlying hash value. + /// Convert into the raw underlying value. pub fn into_raw(self) -> u64 { self.0 } @@ -58,15 +53,11 @@ impl Loader for BlankLoader { &[] } - fn load_face(&mut self, _: usize) -> Option { + fn resolve_from(&mut self, _: FileId, _: &Path) -> Option { None } - fn load_file(&mut self, _: &Path) -> Option { - None - } - - fn resolve(&self, _: &Path) -> Option { + fn load_file(&mut self, _: FileId) -> Option> { None } } diff --git a/src/main.rs b/src/main.rs index d0762b3e7..036802046 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,19 +16,16 @@ fn main() -> anyhow::Result<()> { loader.search_path("fonts"); loader.search_system(); - // Resolve the canonical path because the compiler needs it for module - // loading. + // Determine source and destination path. let src_path = Path::new(&args[1]); - - // Find out the file name to create the output file. - let name = src_path - .file_name() - .ok_or_else(|| anyhow!("source path is not a file"))?; - - let dest_path = if args.len() <= 2 { - Path::new(name).with_extension("pdf") + let dest_path = if let Some(arg) = args.get(2) { + PathBuf::from(arg) } else { - PathBuf::from(&args[2]) + let name = src_path + .file_name() + .ok_or_else(|| anyhow!("source path is not a file"))?; + + Path::new(name).with_extension("pdf") }; // Ensure that the source file is not overwritten. @@ -36,6 +33,9 @@ fn main() -> anyhow::Result<()> { bail!("source and destination files are the same"); } + // Resolve the file id of the source file. + let src_id = loader.resolve_path(src_path).context("source file not found")?; + // Read the source. let src = fs::read_to_string(&src_path) .map_err(|_| anyhow!("failed to read source file"))?; @@ -44,14 +44,7 @@ fn main() -> anyhow::Result<()> { let mut cache = typst::cache::Cache::new(&loader); let scope = typst::library::new(); let state = typst::exec::State::default(); - let pass = typst::typeset( - &mut loader, - &mut cache, - Some(&src_path), - &src, - &scope, - state, - ); + let pass = typst::typeset(&mut loader, &mut cache, src_id, &src, &scope, state); // Print diagnostics. let map = typst::parse::LineMap::new(&src); diff --git a/tests/typeset.rs b/tests/typeset.rs index 254d1b9e2..512e5879c 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -18,7 +18,7 @@ use typst::exec::{exec, State}; use typst::geom::{self, Length, PathElement, Point, Sides, Size}; use typst::image::ImageId; use typst::layout::{layout, Element, Frame, Geometry, Paint, Text}; -use typst::loading::FsLoader; +use typst::loading::{FileId, FsLoader}; use typst::parse::{parse, LineMap, Scanner}; use typst::syntax::{Location, Pos}; @@ -136,6 +136,7 @@ fn test( println!("Testing {}", name.display()); let src = fs::read_to_string(src_path).unwrap(); + let src_id = loader.resolve_path(src_path).unwrap(); let mut ok = true; let mut frames = vec![]; @@ -159,7 +160,7 @@ fn test( } } else { let (part_ok, compare_here, part_frames) = - test_part(loader, cache, src_path, part, i, compare_ref, lines); + test_part(loader, cache, src_id, part, i, compare_ref, lines); ok &= part_ok; compare_ever |= compare_here; frames.extend(part_frames); @@ -200,7 +201,7 @@ fn test( fn test_part( loader: &mut FsLoader, cache: &mut Cache, - src_path: &Path, + src_id: FileId, src: &str, i: usize, compare_ref: bool, @@ -222,13 +223,7 @@ fn test_part( state.page.margins = Sides::splat(Some(Length::pt(10.0).into())); let parsed = parse(src); - let evaluated = eval( - loader, - cache, - Some(src_path), - Rc::new(parsed.output), - &scope, - ); + let evaluated = eval(loader, cache, src_id, Rc::new(parsed.output), &scope); let executed = exec(&evaluated.output.template, state.clone()); let mut layouted = layout(loader, cache, &executed.output);