//! Evaluation of markup into modules. #[macro_use] mod array; #[macro_use] mod dict; #[macro_use] mod value; #[macro_use] mod styles; mod capture; mod class; mod func; mod ops; mod scope; mod template; pub use array::*; pub use capture::*; pub use class::*; pub use dict::*; pub use func::*; pub use scope::*; pub use styles::*; pub use template::*; pub use value::*; use std::cell::RefMut; use std::collections::HashMap; use std::io; use std::mem; use std::path::PathBuf; use once_cell::sync::Lazy; use syntect::easy::HighlightLines; use syntect::highlighting::{FontStyle, Highlighter, Style as SynStyle, Theme, ThemeSet}; use syntect::parsing::SyntaxSet; use unicode_segmentation::UnicodeSegmentation; use crate::diag::{At, Error, StrResult, Trace, Tracepoint, TypResult}; use crate::geom::{Angle, Color, Fractional, Length, Paint, Relative}; use crate::image::ImageStore; use crate::layout::RootNode; use crate::library::{self, DecoLine, TextNode}; use crate::loading::Loader; use crate::parse; use crate::source::{SourceId, SourceStore}; use crate::syntax; use crate::syntax::ast::*; use crate::syntax::{RedNode, Span, Spanned}; use crate::util::{EcoString, RefMutExt}; use crate::Context; static THEME: Lazy = Lazy::new(|| ThemeSet::load_defaults().themes.remove("InspiredGitHub").unwrap()); static SYNTAXES: Lazy = Lazy::new(|| SyntaxSet::load_defaults_newlines()); /// An evaluated module, ready for importing or conversion to a root layout /// tree. #[derive(Debug, Default, Clone)] pub struct Module { /// The top-level definitions that were bound in this module. pub scope: Scope, /// The module's layoutable contents. pub template: Template, } impl Module { /// Convert this module's template into a layout tree. pub fn into_root(self) -> RootNode { self.template.into_root() } } /// Evaluate an expression. pub trait Eval { /// The output of evaluating the expression. type Output; /// Evaluate the expression to the output value. fn eval(&self, ctx: &mut EvalContext) -> TypResult; } /// The context for evaluation. pub struct EvalContext<'a> { /// The loader from which resources (files and images) are loaded. pub loader: &'a dyn Loader, /// Stores loaded source files. pub sources: &'a mut SourceStore, /// Stores decoded images. pub images: &'a mut ImageStore, /// The stack of imported files that led to evaluation of the current file. pub route: Vec, /// Caches imported modules. pub modules: HashMap, /// The active scopes. pub scopes: Scopes<'a>, } impl<'a> EvalContext<'a> { /// Create a new evaluation context. pub fn new(ctx: &'a mut Context, source: SourceId) -> Self { Self { loader: ctx.loader.as_ref(), sources: &mut ctx.sources, images: &mut ctx.images, route: vec![source], modules: HashMap::new(), scopes: Scopes::new(Some(&ctx.std)), } } /// Process an import of a module relative to the current location. pub fn import(&mut self, path: &str, span: Span) -> TypResult { // Load the source file. let full = self.make_path(path); let id = self.sources.load(&full).map_err(|err| { Error::boxed(span, match err.kind() { io::ErrorKind::NotFound => "file not found".into(), _ => format!("failed to load source file ({})", err), }) })?; // Prevent cyclic importing. if self.route.contains(&id) { bail!(span, "cyclic import"); } // Check whether the module was already loaded. if self.modules.get(&id).is_some() { return Ok(id); } // Parse the file. let source = self.sources.get(id); let ast = source.ast()?; // Prepare the new context. let new_scopes = Scopes::new(self.scopes.base); let prev_scopes = mem::replace(&mut self.scopes, new_scopes); self.route.push(id); // Evaluate the module. let template = ast.eval(self).trace(|| Tracepoint::Import, span)?; // Restore the old context. let new_scopes = mem::replace(&mut self.scopes, prev_scopes); self.route.pop().unwrap(); // Save the evaluated module. let module = Module { scope: new_scopes.top, template }; self.modules.insert(id, module); Ok(id) } /// Complete a user-entered path (relative to the source file) to be /// relative to the compilation environment's root. pub fn make_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() } } impl Eval for Markup { type Output = Template; fn eval(&self, ctx: &mut EvalContext) -> TypResult { eval_markup(ctx, &mut self.nodes()) } } /// Evaluate a stream of markup nodes. fn eval_markup( ctx: &mut EvalContext, nodes: &mut impl Iterator, ) -> TypResult