//! The compiler for the _Typst_ typesetting language. //! //! # Steps //! - **Parsing:** The parsing step first transforms a plain string into an //! [iterator of tokens][tokens]. This token stream is [parsed] into a [green //! tree]. The green tree itself is untyped, but a typed layer over it is //! provided in the [AST] module. //! - **Evaluation:** The next step is to [evaluate] the markup. This produces a //! [module], consisting of a scope of values that were exported by the code //! and [content], a hierarchical, styled representation with the contents //! of the module. The nodes of this tree are well structured and //! order-independent and thus much better suited for layouting than the raw //! markup. //! - **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. //! - **Exporting:** The finished layout can be exported into a supported //! format. Currently, the only supported output format is [PDF]. //! //! [tokens]: parse::Tokens //! [parsed]: parse::parse //! [green tree]: syntax::GreenNode //! [AST]: syntax::ast //! [evaluate]: eval::Eval //! [module]: eval::Module //! [content]: eval::Content //! [layouted]: eval::Content::layout //! [PDF]: export::pdf #![allow(clippy::len_without_is_empty)] #![allow(clippy::or_fun_call)] #![allow(clippy::try_err)] #[macro_use] pub mod util; #[macro_use] pub mod geom; #[macro_use] pub mod diag; #[macro_use] pub mod eval; pub mod export; pub mod font; pub mod frame; pub mod image; pub mod library; pub mod loading; pub mod parse; pub mod source; pub mod syntax; use std::any::Any; use std::collections::HashMap; use std::fmt::{self, Display, Formatter}; use std::hash::Hash; use std::path::PathBuf; use std::sync::Arc; use crate::diag::TypResult; use crate::eval::{Eval, Module, Scope, Scopes, StyleMap}; use crate::font::FontStore; use crate::frame::Frame; use crate::image::ImageStore; use crate::loading::Loader; use crate::source::{SourceId, SourceStore}; /// The core context which holds the loader, configuration and cached artifacts. pub struct Context { /// The loader the context was created with. pub loader: Arc, /// Stores loaded source files. pub sources: SourceStore, /// Stores parsed font faces. pub fonts: FontStore, /// Stores decoded images. pub images: ImageStore, /// The standard library scope. std: Arc, /// The default styles. styles: Arc, /// Cached modules. modules: HashMap, /// Cached queries. cache: HashMap, /// The stack of imported files that led to evaluation of the current file. route: Vec, /// The dependencies of the current evaluation process. deps: Vec<(SourceId, usize)>, } impl Context { /// Create a new context with the default settings. pub fn new(loader: Arc) -> Self { Self::builder().build(loader) } /// Create a new context with advanced settings. pub fn builder() -> ContextBuilder { ContextBuilder::default() } /// A read-only reference to the standard library scope. pub fn std(&self) -> &Scope { &self.std } /// A read-only reference to the styles. pub fn styles(&self) -> &StyleMap { &self.styles } /// Evaluate a source file and return the resulting module. /// /// Returns either a module containing a scope with top-level bindings and /// layoutable contents or diagnostics in the form of a vector of error /// messages with file and span information. pub fn evaluate(&mut self, id: SourceId) -> TypResult { // Prevent cyclic evaluation. if self.route.contains(&id) { let path = self.sources.get(id).path().display(); panic!("Tried to cyclicly evaluate {}", path); } // Check whether the module was already evaluated. if let Some(module) = self.modules.get(&id) { if module.valid(&self.sources) { return Ok(module.clone()); } else { self.modules.remove(&id); } } // Parse the file. let source = self.sources.get(id); let ast = source.ast()?; let std = self.std.clone(); let mut scp = Scopes::new(Some(&std)); // Evaluate the module. let prev = std::mem::replace(&mut self.deps, vec![(id, source.rev())]); self.route.push(id); let content = ast.eval(self, &mut scp); self.route.pop().unwrap(); let deps = std::mem::replace(&mut self.deps, prev); // Assemble the module. let module = Module { scope: scp.top, content: content?, deps }; // Save the evaluated module. self.modules.insert(id, module.clone()); Ok(module) } /// 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>> { self.evaluate(id)?.content.layout(self) } /// Resolve a user-entered path (relative to the current evaluation /// location) to be relative to the compilation environment's root. pub fn complete_path(&self, path: &str) -> PathBuf { if let Some(&id) = self.route.last() { if let Some(dir) = self.sources.get(id).path().parent() { return dir.join(path); } } path.into() } } /// A builder for a [`Context`]. /// /// This struct is created by [`Context::builder`]. pub struct ContextBuilder { std: Option>, styles: Option>, } impl ContextBuilder { /// The scope containing definitions that are available everywhere /// (the standard library). pub fn std(mut self, std: impl Into>) -> Self { self.std = Some(std.into()); self } /// The default properties for page size, font selection and so on. pub fn styles(mut self, styles: impl Into>) -> Self { self.styles = Some(styles.into()); self } /// Finish building the context by providing the `loader` used to load /// fonts, images, source files and other resources. pub fn build(self, loader: Arc) -> Context { Context { sources: SourceStore::new(Arc::clone(&loader)), fonts: FontStore::new(Arc::clone(&loader)), images: ImageStore::new(Arc::clone(&loader)), loader, std: self.std.unwrap_or_else(|| Arc::new(library::new())), styles: self.styles.unwrap_or_default(), modules: HashMap::new(), cache: HashMap::new(), route: vec![], deps: vec![], } } } impl Default for ContextBuilder { fn default() -> Self { Self { std: None, styles: None } } } /// An entry in the query cache. struct CacheEntry { /// The query's results. data: Box, /// How many evictions have passed since the entry has been last used. age: usize, } impl Context { /// Execute a query. /// /// This hashes all inputs to the query and then either returns a cached /// version or executes the query, saves the results in the cache and /// returns a reference to them. pub fn query( &mut self, input: I, query: fn(ctx: &mut Self, input: I) -> O, ) -> &O where I: Hash, O: 'static, { let hash = fxhash::hash64(&input); if !self.cache.contains_key(&hash) { let output = query(self, input); self.cache.insert(hash, CacheEntry { data: Box::new(output), age: 0 }); } let entry = self.cache.get_mut(&hash).unwrap(); entry.age = 0; entry.data.downcast_ref().expect("oh no, a hash collision") } /// Garbage-collect the query cache. This deletes elements which haven't /// been used in a while. /// /// Returns details about the eviction. pub fn evict(&mut self) -> Eviction { const MAX_AGE: usize = 5; let before = self.cache.len(); self.cache.retain(|_, entry| { entry.age += 1; entry.age <= MAX_AGE }); Eviction { before, after: self.cache.len() } } } /// Details about a cache eviction. pub struct Eviction { /// The number of items in the cache before the eviction. pub before: usize, /// The number of items in the cache after the eviction. pub after: usize, } impl Display for Eviction { fn fmt(&self, f: &mut Formatter) -> fmt::Result { writeln!(f, "Before: {}", self.before)?; writeln!(f, "Evicted: {}", self.before - self.after)?; writeln!(f, "After: {}", self.after) } }