From bf5edbbbbb75120d065d1c9587ccfa4eed4fdca1 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Fri, 25 Nov 2022 10:36:31 +0100 Subject: Tidy up --- src/diag.rs | 4 +- src/export/render.rs | 2 +- src/lib.rs | 26 +-- src/model/content.rs | 29 ++-- src/model/dict.rs | 2 +- src/model/eval.rs | 114 ++++++++++++-- src/model/func.rs | 39 ++--- src/model/library.rs | 6 +- src/model/mod.rs | 4 +- src/model/realize.rs | 145 +++++++++++++++++ src/model/str.rs | 64 ++++---- src/model/styles.rs | 435 ++++++++++++++++----------------------------------- src/model/typeset.rs | 5 +- src/model/vm.rs | 97 ------------ 14 files changed, 477 insertions(+), 495 deletions(-) create mode 100644 src/model/realize.rs delete mode 100644 src/model/vm.rs (limited to 'src') diff --git a/src/diag.rs b/src/diag.rs index 99ef1291..58eebf8b 100644 --- a/src/diag.rs +++ b/src/diag.rs @@ -100,7 +100,7 @@ pub enum Tracepoint { /// A function call. Call(Option), /// A show rule application. - Apply(EcoString), + Show(EcoString), /// A module import. Import, } @@ -114,7 +114,7 @@ impl Display for Tracepoint { Tracepoint::Call(None) => { write!(f, "error occured in this function call") } - Tracepoint::Apply(name) => { + Tracepoint::Show(name) => { write!(f, "error occured while applying show rule to this {name}") } Tracepoint::Import => { diff --git a/src/export/render.rs b/src/export/render.rs index bc82e9c8..7cff7ad8 100644 --- a/src/export/render.rs +++ b/src/export/render.rs @@ -14,7 +14,7 @@ use crate::geom::{ }; use crate::image::{DecodedImage, Image}; -/// Export a frame into a rendered image. +/// Export a frame into a raster image. /// /// This renders the frame at the given number of pixels per printer's point and /// returns the resulting `tiny-skia` pixel buffer. diff --git a/src/lib.rs b/src/lib.rs index 4607cd0c..6e0a68a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,21 +1,21 @@ -//! The compiler for the _Typst_ typesetting language. +//! The compiler for the _Typst_ markup language. //! //! # Steps -//! - **Parsing:** The parsing step first transforms a plain string into an +//! - **Parsing:** The compiler first transforms a plain string into an //! [iterator of tokens][tokens]. This token stream is [parsed] into a [syntax //! tree]. The tree itself is untyped, but the [AST] module provides a typed //! layer over it. //! - **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 of the text, -//! structure, layouts, etc. of the module. The nodes of the content tree are -//! well structured and order-independent and thus much better suited for -//! layouting than the raw markup. -//! - **Layouting:** Next, the content 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]. +//! and [content], a hierarchical, styled representation of what was written +//! in the source file. The nodes of the content tree are well structured and +//! order-independent and thus much better suited for further processing than +//! the raw markup. +//! - **Typesetting:** Next, the content is [typeset] into a collection of +//! [`Frame`]s (one per page) with elements and fixed positions, ready for +//! exporting. +//! - **Exporting:** These frames can finally be exported into an output format +//! (currently supported are [PDF] and [raster images]). //! //! [tokens]: syntax::Tokens //! [parsed]: syntax::parse @@ -24,7 +24,9 @@ //! [evaluate]: model::eval //! [module]: model::Module //! [content]: model::Content +//! [typeset]: model::typeset //! [PDF]: export::pdf +//! [raster images]: export::render extern crate self as typst; @@ -73,7 +75,7 @@ pub fn compile( /// The environment in which typesetting occurs. #[comemo::track] pub trait World { - /// The compilation root. + /// The path relative to which absolute paths are. fn root(&self) -> &Path; /// The standard library. diff --git a/src/model/content.rs b/src/model/content.rs index 90df5a4e..0faf76cb 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -9,9 +9,7 @@ use comemo::Tracked; use siphasher::sip128::{Hasher128, SipHasher}; use typst_macros::node; -use super::{ - Args, Key, Property, Recipe, RecipeId, Style, StyleMap, Unlabellable, Value, Vm, -}; +use super::{capability, Args, Guard, Key, Property, Recipe, Style, StyleMap, Value, Vm}; use crate::diag::{SourceResult, StrResult}; use crate::syntax::Span; use crate::util::{EcoString, ReadableTypeId}; @@ -21,7 +19,7 @@ use crate::World; #[derive(Clone, Hash)] pub struct Content { obj: Arc, - guards: Vec, + guards: Vec, span: Option, label: Option, } @@ -69,11 +67,11 @@ impl Content { /// Style this content with a style entry. pub fn styled_with_entry(mut self, style: Style) -> Self { if let Some(styled) = self.to_mut::() { - styled.map.apply(style); + styled.map.apply_one(style); self } else if let Some(styled) = self.to::() { let mut map = styled.map.clone(); - map.apply(style); + map.apply_one(style); StyledNode { sub: styled.sub.clone(), map }.pack() } else { StyledNode { sub: self, map: style.into() }.pack() @@ -87,7 +85,7 @@ impl Content { } if let Some(styled) = self.to_mut::() { - styled.map.apply_map(&styles); + styled.map.apply(styles); return self; } @@ -101,7 +99,7 @@ impl Content { recipe: Recipe, ) -> SourceResult { if recipe.selector.is_none() { - recipe.transform.apply(world, recipe.span, self) + recipe.apply(world, self) } else { Ok(self.styled_with_entry(Style::Recipe(recipe))) } @@ -185,7 +183,7 @@ impl Content { /// Disable a show rule recipe. #[doc(hidden)] - pub fn guarded(mut self, id: RecipeId) -> Self { + pub fn guarded(mut self, id: Guard) -> Self { self.guards.push(id); self } @@ -201,7 +199,7 @@ impl Content { } /// Check whether a show rule recipe is disabled. - pub(super) fn is_guarded(&self, id: RecipeId) -> bool { + pub(super) fn is_guarded(&self, id: Guard) -> bool { self.guards.contains(&id) } @@ -378,12 +376,12 @@ pub trait Node: 'static + Capable { fn field(&self, name: &str) -> Option; } -/// A unique identifier for a node. +/// A unique identifier for a node type. #[derive(Copy, Clone, Eq, PartialEq, Hash)] pub struct NodeId(ReadableTypeId); impl NodeId { - /// The id of the given node. + /// The id of the given node type. pub fn of() -> Self { Self(ReadableTypeId::of::()) } @@ -397,7 +395,8 @@ impl Debug for NodeId { /// A capability a node can have. /// -/// This is implemented by trait objects. +/// Should be implemented by trait objects that are accessible through +/// [`Capable`]. pub trait Capability: 'static {} /// Dynamically access a trait implementation at runtime. @@ -406,3 +405,7 @@ pub unsafe trait Capable { /// if `self` implements the trait. fn vtable(&self, of: TypeId) -> Option<*const ()>; } + +/// Indicates that a node cannot be labelled. +#[capability] +pub trait Unlabellable {} diff --git a/src/model/dict.rs b/src/model/dict.rs index 270c2c9c..1c299795 100644 --- a/src/model/dict.rs +++ b/src/model/dict.rs @@ -104,7 +104,7 @@ impl Dict { self.0.values().cloned().collect() } - /// Transform each pair in the array with a function. + /// Transform each pair in the dictionary with a function. pub fn map(&self, vm: &Vm, f: Spanned) -> SourceResult { self.iter() .map(|(key, value)| { diff --git a/src/model/eval.rs b/src/model/eval.rs index e68f6e7a..6a93ca13 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -2,19 +2,22 @@ use std::collections::BTreeMap; use std::mem; +use std::path::PathBuf; use comemo::{Track, Tracked}; use unicode_segmentation::UnicodeSegmentation; use super::{ - methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, Dict, Flow, Func, - Recipe, Scope, Scopes, Selector, StyleMap, Transform, Value, Vm, + methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, Dict, Func, + LangItems, Recipe, Scope, Scopes, Selector, StyleMap, Transform, Value, +}; +use crate::diag::{ + bail, error, At, SourceError, SourceResult, StrResult, Trace, Tracepoint, }; -use crate::diag::{bail, error, At, SourceResult, StrResult, Trace, Tracepoint}; use crate::geom::{Abs, Angle, Em, Fr, Ratio}; use crate::syntax::ast::AstNode; use crate::syntax::{ast, Source, SourceId, Span, Spanned, Unit}; -use crate::util::{format_eco, EcoString}; +use crate::util::{format_eco, EcoString, PathExt}; use crate::World; /// Evaluate a source file and return the resulting module. @@ -54,6 +57,94 @@ pub fn eval( Ok(Module { scope: vm.scopes.top, content: result? }) } +/// A virtual machine. +/// +/// Holds the state needed to [evaluate](super::eval()) Typst sources. A new +/// virtual machine is created for each module evaluation and function call. +pub struct Vm<'a> { + /// The compilation environment. + pub(super) world: Tracked<'a, dyn World>, + /// The language items. + pub(super) items: LangItems, + /// The route of source ids the VM took to reach its current location. + pub(super) route: Tracked<'a, Route>, + /// The current location. + pub(super) location: SourceId, + /// A control flow event that is currently happening. + pub(super) flow: Option, + /// The stack of scopes. + pub(super) scopes: Scopes<'a>, +} + +impl<'a> Vm<'a> { + /// Create a new virtual machine. + pub fn new( + world: Tracked<'a, dyn World>, + route: Tracked<'a, Route>, + location: SourceId, + scopes: Scopes<'a>, + ) -> Self { + Self { + world, + items: world.library().items.clone(), + route, + location, + flow: None, + scopes, + } + } + + /// Access the underlying world. + pub fn world(&self) -> Tracked { + self.world + } + + /// Resolve a user-entered path to be relative to the compilation + /// environment's root. + pub fn locate(&self, path: &str) -> StrResult { + if !self.location.is_detached() { + if let Some(path) = path.strip_prefix('/') { + return Ok(self.world.root().join(path).normalize()); + } + + if let Some(dir) = self.world.source(self.location).path().parent() { + return Ok(dir.join(path).normalize()); + } + } + + Err("cannot access file system from here".into()) + } +} + +/// A control flow event that occurred during evaluation. +#[derive(Debug, Clone, PartialEq)] +pub enum Flow { + /// Stop iteration in a loop. + Break(Span), + /// Skip the remainder of the current iteration in a loop. + Continue(Span), + /// Stop execution of a function early, optionally returning an explicit + /// value. + Return(Span, Option), +} + +impl Flow { + /// Return an error stating that this control flow is forbidden. + pub fn forbidden(&self) -> SourceError { + match *self { + Self::Break(span) => { + error!(span, "cannot break outside of loop") + } + Self::Continue(span) => { + error!(span, "cannot continue outside of loop") + } + Self::Return(span, _) => { + error!(span, "cannot return outside of function") + } + } + } +} + /// A route of source ids. #[derive(Default)] pub struct Route { @@ -87,7 +178,7 @@ impl Route { } } -/// An evaluated module, ready for importing or layouting. +/// An evaluated module, ready for importing or typesetting. #[derive(Debug, Clone)] pub struct Module { /// The top-level definitions that were bound in this module. @@ -700,18 +791,19 @@ impl Eval for ast::MethodCall { fn eval(&self, vm: &mut Vm) -> SourceResult { let span = self.span(); let method = self.method().take(); - let point = || Tracepoint::Call(Some(method.clone())); - Ok(if methods::is_mutating(&method) { + let result = if methods::is_mutating(&method) { let args = self.args().eval(vm)?; let value = self.target().access(vm)?; - methods::call_mut(value, &method, args, span).trace(vm.world, point, span)?; - Value::None + methods::call_mut(value, &method, args, span).map(|()| Value::None) } else { let value = self.target().eval(vm)?; let args = self.args().eval(vm)?; - methods::call(vm, value, &method, args, span).trace(vm.world, point, span)? - }) + methods::call(vm, value, &method, args, span) + }; + + let point = || Tracepoint::Call(Some(method.clone())); + result.trace(vm.world, point, span) } } diff --git a/src/model/func.rs b/src/model/func.rs index c0431ac6..4a2a162b 100644 --- a/src/model/func.rs +++ b/src/model/func.rs @@ -52,7 +52,7 @@ impl Func { } /// Create a new function from a closure. - pub fn from_closure(closure: Closure) -> Self { + pub(super) fn from_closure(closure: Closure) -> Self { Self(Arc::new(Repr::Closure(closure))) } @@ -127,7 +127,8 @@ impl Func { } } - /// Create a selector from this node and the given arguments. + /// Create a selector for this function's node type, filtering by node's + /// whose [fields](super::Content::field) match the given arguments. pub fn where_(self, args: &mut Args) -> StrResult { match self.0.as_ref() { Repr::Native(Native { node: Some(id), .. }) => { @@ -178,7 +179,7 @@ impl Hash for Native { /// A user-defined closure. #[derive(Hash)] -pub struct Closure { +pub(super) struct Closure { /// The source file where the closure was defined. pub location: SourceId, /// The name of the closure. @@ -196,7 +197,7 @@ pub struct Closure { impl Closure { /// Call the function in the context with the arguments. - pub fn call(&self, vm: &Vm, args: &mut Args) -> SourceResult { + fn call(&self, vm: &Vm, args: &mut Args) -> SourceResult { // Don't leak the scopes from the call site. Instead, we use the scope // of captured variables we collected earlier. let mut scopes = Scopes::new(None); @@ -241,7 +242,7 @@ impl Closure { } /// The number of positional arguments this function takes, if known. - pub fn argc(&self) -> Option { + fn argc(&self) -> Option { if self.sink.is_some() { return None; } @@ -272,20 +273,6 @@ impl<'a> CapturesVisitor<'a> { self.captures } - /// Bind a new internal variable. - pub fn bind(&mut self, ident: ast::Ident) { - self.internal.top.define(ident.take(), Value::None); - } - - /// Capture a variable if it isn't internal. - pub fn capture(&mut self, ident: ast::Ident) { - if self.internal.get(&ident).is_err() { - if let Ok(value) = self.external.get(&ident) { - self.captures.define_captured(ident.take(), value.clone()); - } - } - } - /// Visit any node and collect all captured variables. pub fn visit(&mut self, node: &SyntaxNode) { match node.cast() { @@ -366,6 +353,20 @@ impl<'a> CapturesVisitor<'a> { } } } + + /// Bind a new internal variable. + fn bind(&mut self, ident: ast::Ident) { + self.internal.top.define(ident.take(), Value::None); + } + + /// Capture a variable if it isn't internal. + fn capture(&mut self, ident: ast::Ident) { + if self.internal.get(&ident).is_err() { + if let Ok(value) = self.external.get(&ident) { + self.captures.define_captured(ident.take(), value.clone()); + } + } + } } #[cfg(test)] diff --git a/src/model/library.rs b/src/model/library.rs index 8433e514..2ee09b27 100644 --- a/src/model/library.rs +++ b/src/model/library.rs @@ -12,7 +12,7 @@ use crate::geom::{Abs, Dir}; use crate::util::{hash128, EcoString}; use crate::World; -/// A Typst standard library. +/// Definition of Typst's standard library. #[derive(Debug, Clone, Hash)] pub struct Library { /// The scope containing definitions that are available everywhere. @@ -23,13 +23,13 @@ pub struct Library { pub items: LangItems, } -/// Definition of certain standard library items the language is aware of. +/// Definition of library items the language is aware of. #[derive(Clone)] pub struct LangItems { /// The root layout function. pub layout: fn( - content: &Content, world: Tracked, + content: &Content, styles: StyleChain, ) -> SourceResult>, /// Access the em size. diff --git a/src/model/mod.rs b/src/model/mod.rs index f5c1e6dd..6943217b 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -20,9 +20,9 @@ mod eval; mod func; mod methods; mod ops; +mod realize; mod scope; mod typeset; -mod vm; #[doc(hidden)] pub use once_cell; @@ -36,9 +36,9 @@ pub use self::dict::*; pub use self::eval::*; pub use self::func::*; pub use self::library::*; +pub use self::realize::*; pub use self::scope::*; pub use self::str::*; pub use self::styles::*; pub use self::typeset::*; pub use self::value::*; -pub use self::vm::*; diff --git a/src/model/realize.rs b/src/model/realize.rs new file mode 100644 index 00000000..d63c1aac --- /dev/null +++ b/src/model/realize.rs @@ -0,0 +1,145 @@ +use comemo::Tracked; + +use super::{capability, Content, NodeId, Recipe, Selector, StyleChain}; +use crate::diag::SourceResult; +use crate::World; + +/// Whether the target is affected by show rules in the given style chain. +pub fn applicable(target: &Content, styles: StyleChain) -> bool { + // Find out how many recipes there are. + let mut n = styles.recipes().count(); + + // Find out whether any recipe matches and is unguarded. + for recipe in styles.recipes() { + if recipe.applicable(target) && !target.is_guarded(Guard::Nth(n)) { + return true; + } + n -= 1; + } + + false +} + +/// Apply the show rules in the given style chain to a target. +pub fn realize( + world: Tracked, + target: &Content, + styles: StyleChain, +) -> SourceResult> { + // Find out how many recipes there are. + let mut n = styles.recipes().count(); + + // Find an applicable recipe. + let mut realized = None; + for recipe in styles.recipes() { + let guard = Guard::Nth(n); + if recipe.applicable(target) && !target.is_guarded(guard) { + if let Some(content) = try_apply(world, &target, recipe, guard)? { + realized = Some(content); + break; + } + } + n -= 1; + } + + // Realize if there was no matching recipe. + if let Some(showable) = target.with::() { + let guard = Guard::Base(target.id()); + if realized.is_none() && !target.is_guarded(guard) { + realized = Some(showable.show(world, styles)); + } + } + + // Finalize only if this is the first application for this node. + if let Some(node) = target.with::() { + if target.is_pristine() { + if let Some(already) = realized { + realized = Some(node.finalize(already)); + } + } + } + + Ok(realized) +} + +/// Try to apply a recipe to the target. +fn try_apply( + world: Tracked, + target: &Content, + recipe: &Recipe, + guard: Guard, +) -> SourceResult> { + match &recipe.selector { + Some(Selector::Node(id, _)) => { + if target.id() != *id { + return Ok(None); + } + + recipe.apply(world, target.clone().guarded(guard)).map(Some) + } + + Some(Selector::Regex(regex)) => { + let Some(text) = item!(text_str)(&target) else { + return Ok(None); + }; + + let make = |s| { + let mut content = item!(text)(s); + content.copy_meta(&target); + content + }; + + let mut result = vec![]; + let mut cursor = 0; + + for m in regex.find_iter(text) { + let start = m.start(); + if cursor < start { + result.push(make(text[cursor..start].into())); + } + + let piece = make(m.as_str().into()).guarded(guard); + let transformed = recipe.apply(world, piece)?; + result.push(transformed); + cursor = m.end(); + } + + if result.is_empty() { + return Ok(None); + } + + if cursor < text.len() { + result.push(make(text[cursor..].into())); + } + + Ok(Some(Content::sequence(result))) + } + + None => Ok(None), + } +} + +/// The base recipe for a node. +#[capability] +pub trait Show { + /// Execute the base recipe for this node. + fn show(&self, world: Tracked, styles: StyleChain) -> Content; +} + +/// Post-process a node after it was realized. +#[capability] +pub trait Finalize { + /// Finalize the fully realized form of the node. Use this for effects that + /// should work even in the face of a user-defined show rule, for example + /// the linking behaviour of a link node. + fn finalize(&self, realized: Content) -> Content; +} + +/// Guards content against being affected by the same show rule multiple times. +#[derive(Debug, Copy, Clone, PartialEq, Hash)] +pub enum Guard { + /// The nth recipe from the top of the chain. + Nth(usize), + /// The [base recipe](Show) for a kind of node. + Base(NodeId), +} diff --git a/src/model/str.rs b/src/model/str.rs index 454c561f..1fcf7075 100644 --- a/src/model/str.rs +++ b/src/model/str.rs @@ -77,69 +77,69 @@ impl Str { } /// Whether the given pattern exists in this string. - pub fn contains(&self, pattern: Pattern) -> bool { + pub fn contains(&self, pattern: StrPattern) -> bool { match pattern { - Pattern::Str(pat) => self.0.contains(pat.as_str()), - Pattern::Regex(re) => re.is_match(self), + StrPattern::Str(pat) => self.0.contains(pat.as_str()), + StrPattern::Regex(re) => re.is_match(self), } } /// Whether this string begins with the given pattern. - pub fn starts_with(&self, pattern: Pattern) -> bool { + pub fn starts_with(&self, pattern: StrPattern) -> bool { match pattern { - Pattern::Str(pat) => self.0.starts_with(pat.as_str()), - Pattern::Regex(re) => re.find(self).map_or(false, |m| m.start() == 0), + StrPattern::Str(pat) => self.0.starts_with(pat.as_str()), + StrPattern::Regex(re) => re.find(self).map_or(false, |m| m.start() == 0), } } /// Whether this string ends with the given pattern. - pub fn ends_with(&self, pattern: Pattern) -> bool { + pub fn ends_with(&self, pattern: StrPattern) -> bool { match pattern { - Pattern::Str(pat) => self.0.ends_with(pat.as_str()), - Pattern::Regex(re) => { + StrPattern::Str(pat) => self.0.ends_with(pat.as_str()), + StrPattern::Regex(re) => { re.find_iter(self).last().map_or(false, |m| m.end() == self.0.len()) } } } /// The text of the pattern's first match in this string. - pub fn find(&self, pattern: Pattern) -> Option { + pub fn find(&self, pattern: StrPattern) -> Option { match pattern { - Pattern::Str(pat) => self.0.contains(pat.as_str()).then(|| pat), - Pattern::Regex(re) => re.find(self).map(|m| m.as_str().into()), + StrPattern::Str(pat) => self.0.contains(pat.as_str()).then(|| pat), + StrPattern::Regex(re) => re.find(self).map(|m| m.as_str().into()), } } /// The position of the pattern's first match in this string. - pub fn position(&self, pattern: Pattern) -> Option { + pub fn position(&self, pattern: StrPattern) -> Option { match pattern { - Pattern::Str(pat) => self.0.find(pat.as_str()).map(|i| i as i64), - Pattern::Regex(re) => re.find(self).map(|m| m.start() as i64), + StrPattern::Str(pat) => self.0.find(pat.as_str()).map(|i| i as i64), + StrPattern::Regex(re) => re.find(self).map(|m| m.start() as i64), } } /// The start and, text and capture groups (if any) of the first match of /// the pattern in this string. - pub fn match_(&self, pattern: Pattern) -> Option { + pub fn match_(&self, pattern: StrPattern) -> Option { match pattern { - Pattern::Str(pat) => { + StrPattern::Str(pat) => { self.0.match_indices(pat.as_str()).next().map(match_to_dict) } - Pattern::Regex(re) => re.captures(self).map(captures_to_dict), + StrPattern::Regex(re) => re.captures(self).map(captures_to_dict), } } /// The start, end, text and capture groups (if any) of all matches of the /// pattern in this string. - pub fn matches(&self, pattern: Pattern) -> Array { + pub fn matches(&self, pattern: StrPattern) -> Array { match pattern { - Pattern::Str(pat) => self + StrPattern::Str(pat) => self .0 .match_indices(pat.as_str()) .map(match_to_dict) .map(Value::Dict) .collect(), - Pattern::Regex(re) => re + StrPattern::Regex(re) => re .captures_iter(self) .map(captures_to_dict) .map(Value::Dict) @@ -148,14 +148,14 @@ impl Str { } /// Split this string at whitespace or a specific pattern. - pub fn split(&self, pattern: Option) -> Array { + pub fn split(&self, pattern: Option) -> Array { let s = self.as_str(); match pattern { None => s.split_whitespace().map(|v| Value::Str(v.into())).collect(), - Some(Pattern::Str(pat)) => { + Some(StrPattern::Str(pat)) => { s.split(pat.as_str()).map(|v| Value::Str(v.into())).collect() } - Some(Pattern::Regex(re)) => { + Some(StrPattern::Regex(re)) => { re.split(s).map(|v| Value::Str(v.into())).collect() } } @@ -167,7 +167,7 @@ impl Str { /// pattern. pub fn trim( &self, - pattern: Option, + pattern: Option, at: Option, repeat: bool, ) -> Self { @@ -180,7 +180,7 @@ impl Str { Some(StrSide::Start) => self.0.trim_start(), Some(StrSide::End) => self.0.trim_end(), }, - Some(Pattern::Str(pat)) => { + Some(StrPattern::Str(pat)) => { let pat = pat.as_str(); let mut s = self.as_str(); if repeat { @@ -200,7 +200,7 @@ impl Str { } s } - Some(Pattern::Regex(re)) => { + Some(StrPattern::Regex(re)) => { let s = self.as_str(); let mut last = 0; let mut range = 0..s.len(); @@ -240,13 +240,13 @@ impl Str { /// Replace at most `count` occurances of the given pattern with a /// replacement string (beginning from the start). - pub fn replace(&self, pattern: Pattern, with: Self, count: Option) -> Self { + pub fn replace(&self, pattern: StrPattern, with: Self, count: Option) -> Self { match pattern { - Pattern::Str(pat) => match count { + StrPattern::Str(pat) => match count { Some(n) => self.0.replacen(pat.as_str(), &with, n).into(), None => self.0.replace(pat.as_str(), &with).into(), }, - Pattern::Regex(re) => match count { + StrPattern::Regex(re) => match count { Some(n) => re.replacen(self, n, with.as_str()).into(), None => re.replace(self, with.as_str()).into(), }, @@ -433,7 +433,7 @@ impl Hash for Regex { /// A pattern which can be searched for in a string. #[derive(Debug, Clone)] -pub enum Pattern { +pub enum StrPattern { /// Just a string. Str(Str), /// A regular expression. @@ -441,7 +441,7 @@ pub enum Pattern { } castable! { - Pattern, + StrPattern, Expected: "string or regular expression", Value::Str(text) => Self::Str(text), @regex: Regex => Self::Regex(regex.clone()), diff --git a/src/model/styles.rs b/src/model/styles.rs index 96eb0ab4..966a57ec 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use comemo::{Prehashed, Tracked}; -use super::{capability, Args, Content, Dict, Func, NodeId, Regex, Smart, Value}; +use super::{Args, Content, Dict, Func, NodeId, Regex, Smart, Value}; use crate::diag::{SourceResult, Trace, Tracepoint}; use crate::geom::{ Abs, Align, Axes, Corners, Em, GenAlign, Length, Numeric, PartialStroke, Rel, Sides, @@ -55,34 +55,15 @@ impl StyleMap { .any(|property| property.is::()) } - /// Make `self` the first link of the `tail` chain. - /// - /// The resulting style chain contains styles from `self` as well as - /// `tail`. The ones from `self` take precedence over the ones from - /// `tail`. For folded properties `self` contributes the inner value. - pub fn chain<'a>(&'a self, tail: &'a StyleChain<'a>) -> StyleChain<'a> { - if self.is_empty() { - *tail - } else { - StyleChain { head: &self.0, tail: Some(tail) } - } + /// Apply outer styles. Like [`chain`](StyleChain::chain), but in-place. + pub fn apply(&mut self, outer: Self) { + self.0.splice(0..0, outer.0.iter().cloned()); } - /// Set an outer style. - /// - /// Like [`chain`](Self::chain) or [`apply_map`](Self::apply_map), but with - /// only a entry. - pub fn apply(&mut self, style: Style) { - self.0.insert(0, style); - } - - /// Apply styles from `tail` in-place. The resulting style map is equivalent - /// to the style chain created by `self.chain(StyleChain::new(tail))`. - /// - /// This is useful over `chain` when you want to combine two maps, but you - /// still need an owned map without a lifetime. - pub fn apply_map(&mut self, tail: &Self) { - self.0.splice(0..0, tail.0.iter().cloned()); + /// Set an outer style. Like [`chain_one`](StyleChain::chain_one), but + /// in-place. + pub fn apply_one(&mut self, outer: Style) { + self.0.insert(0, outer); } /// Mark all contained properties as _scoped_. This means that they only @@ -131,21 +112,6 @@ pub enum Style { } impl Style { - /// Make this style the first link of the `tail` chain. - pub fn chain<'a>(&'a self, tail: &'a StyleChain) -> StyleChain<'a> { - if let Style::Barrier(id) = self { - if !tail - .entries() - .filter_map(Style::property) - .any(|p| p.scoped() && *id == p.node()) - { - return *tail; - } - } - - StyleChain { head: std::slice::from_ref(self), tail: Some(tail) } - } - /// If this is a property, return it. pub fn property(&self) -> Option<&Property> { match self { @@ -184,32 +150,6 @@ impl Debug for Style { } } -/// A style property key. -/// -/// This trait is not intended to be implemented manually, but rather through -/// the `#[node]` proc-macro. -pub trait Key: Copy + 'static { - /// The unfolded type which this property is stored as in a style map. - type Value: Debug + Clone + Hash + Sync + Send + 'static; - - /// The folded type of value that is returned when reading this property - /// from a style chain. - type Output<'a>; - - /// The name of the property, used for debug printing. - const NAME: &'static str; - - /// The id of the node the key belongs to. - fn node() -> NodeId; - - /// Compute an output value from a sequence of values belonging to this key, - /// folding if necessary. - fn get<'a>( - chain: StyleChain<'a>, - values: impl Iterator, - ) -> Self::Output<'a>; -} - /// A style property originating from a set rule or constructor. #[derive(Clone, Hash)] pub struct Property { @@ -308,6 +248,32 @@ where } } +/// A style property key. +/// +/// This trait is not intended to be implemented manually, but rather through +/// the `#[node]` proc-macro. +pub trait Key: Copy + 'static { + /// The unfolded type which this property is stored as in a style map. + type Value: Debug + Clone + Hash + Sync + Send + 'static; + + /// The folded type of value that is returned when reading this property + /// from a style chain. + type Output<'a>; + + /// The name of the property, used for debug printing. + const NAME: &'static str; + + /// The id of the node the key belongs to. + fn node() -> NodeId; + + /// Compute an output value from a sequence of values belonging to this key, + /// folding if necessary. + fn get<'a>( + chain: StyleChain<'a>, + values: impl Iterator, + ) -> Self::Output<'a>; +} + /// A unique identifier for a property key. #[derive(Copy, Clone, Eq, PartialEq, Hash)] struct KeyId(ReadableTypeId); @@ -325,35 +291,6 @@ impl Debug for KeyId { } } -/// A built-in show rule for a node. -#[capability] -pub trait Show { - /// Execute the base recipe for this node. - fn show( - &self, - world: Tracked, - styles: StyleChain, - ) -> SourceResult; -} - -/// Post-process a node after it was realized. -#[capability] -pub trait Finalize { - /// Finalize the fully realized form of the node. Use this for effects that - /// should work even in the face of a user-defined show rule, for example - /// the linking behaviour of a link node. - fn finalize( - &self, - world: Tracked, - styles: StyleChain, - realized: Content, - ) -> SourceResult; -} - -/// Indicates that a node cannot be labelled. -#[capability] -pub trait Unlabellable {} - /// A show rule recipe. #[derive(Clone, PartialEq, Hash)] pub struct Recipe { @@ -366,6 +303,14 @@ pub struct Recipe { } impl Recipe { + /// Whether this recipe is for the given node. + pub fn is_of(&self, node: NodeId) -> bool { + match self.selector { + Some(Selector::Node(id, _)) => id == node, + _ => false, + } + } + /// Whether the recipe is applicable to the target. pub fn applicable(&self, target: &Content) -> bool { self.selector @@ -373,74 +318,24 @@ impl Recipe { .map_or(false, |selector| selector.matches(target)) } - /// Try to apply the recipe to the target. + /// Apply the recipe to the given content. pub fn apply( &self, world: Tracked, - sel: RecipeId, - target: &Content, - ) -> SourceResult> { - let content = match &self.selector { - Some(Selector::Node(id, _)) => { - if target.id() != *id { - return Ok(None); - } - - self.transform.apply(world, self.span, target.clone().guarded(sel))? - } - - Some(Selector::Regex(regex)) => { - let Some(text) = item!(text_str)(target) else { - return Ok(None); - }; - - let make = |s| { - let mut content = item!(text)(s); - content.copy_meta(target); - content - }; - - let mut result = vec![]; - let mut cursor = 0; - - for m in regex.find_iter(text) { - let start = m.start(); - if cursor < start { - result.push(make(text[cursor..start].into())); - } - - let transformed = self.transform.apply( - world, - self.span, - make(m.as_str().into()).guarded(sel), - )?; - - result.push(transformed); - cursor = m.end(); - } - - if result.is_empty() { - return Ok(None); - } - - if cursor < text.len() { - result.push(make(text[cursor..].into())); + content: Content, + ) -> SourceResult { + match &self.transform { + Transform::Content(content) => Ok(content.clone()), + Transform::Func(func) => { + let args = Args::new(self.span, [Value::Content(content.clone())]); + let mut result = func.call_detached(world, args); + if let Some(span) = content.span() { + let point = || Tracepoint::Show(content.name().into()); + result = result.trace(world, point, span); } - - Content::sequence(result) + Ok(result?.display()) } - - None => return Ok(None), - }; - - Ok(Some(content)) - } - - /// Whether this recipe is for the given node. - pub fn is_of(&self, node: NodeId) -> bool { - match self.selector { - Some(Selector::Node(id, _)) => id == node, - _ => false, + Transform::Style(styles) => Ok(content.styled_with_map(styles.clone())), } } } @@ -495,39 +390,6 @@ pub enum Transform { Style(StyleMap), } -impl Transform { - /// Apply the transform. - pub fn apply( - &self, - world: Tracked, - rule_span: Span, - content: Content, - ) -> SourceResult { - match self { - Transform::Content(content) => Ok(content.clone()), - Transform::Func(func) => { - let args = Args::new(rule_span, [Value::Content(content.clone())]); - let mut result = func.call_detached(world, args); - if let Some(span) = content.span() { - let point = || Tracepoint::Apply(content.name().into()); - result = result.trace(world, point, span); - } - Ok(result?.display()) - } - Transform::Style(styles) => Ok(content.styled_with_map(styles.clone())), - } - } -} - -/// Identifies a show rule recipe. -#[derive(Debug, Copy, Clone, PartialEq, Hash)] -pub enum RecipeId { - /// The nth recipe from the top of the chain. - Nth(usize), - /// The base recipe for a kind of node. - Base(NodeId), -} - /// A chain of style maps, similar to a linked list. /// /// A style chain allows to combine properties from multiple style maps in a @@ -544,97 +406,54 @@ pub struct StyleChain<'a> { } impl<'a> StyleChain<'a> { - /// Create a new, empty style chain. - pub fn new() -> Self { - Self::default() - } - /// Start a new style chain with a root map. - pub fn with_root(root: &'a StyleMap) -> Self { + pub fn new(root: &'a StyleMap) -> Self { Self { head: &root.0, tail: None } } - /// Get the output value of a style property. + /// Make the given map the first link of this chain. /// - /// Returns the property's default value if no map in the chain contains an - /// entry for it. Also takes care of resolving and folding and returns - /// references where applicable. - pub fn get(self, key: K) -> K::Output<'a> { - K::get(self, self.values(key)) - } - - /// Apply show recipes in this style chain to a target. - pub fn show( - self, - world: Tracked, - target: &Content, - ) -> SourceResult> { - // Find out how many recipes there are. - let mut n = self.entries().filter_map(Style::recipe).count(); - - // Find an applicable recipe. - let mut realized = None; - for recipe in self.entries().filter_map(Style::recipe) { - let sel = RecipeId::Nth(n); - if recipe.applicable(target) && !target.is_guarded(sel) { - if let Some(content) = recipe.apply(world, sel, target)? { - realized = Some(content); - break; - } - } - n -= 1; - } - - // Realize if there was no matching recipe. - let base = RecipeId::Base(target.id()); - if realized.is_none() && !target.is_guarded(base) { - if let Some(showable) = target.with::() { - realized = Some(showable.show(world, self)?); - } - } - - // Finalize only if this is the first application for this node. - if let Some(node) = target.with::() { - if target.is_pristine() { - if let Some(content) = realized { - realized = Some(node.finalize(world, self, content)?); - } - } + /// The resulting style chain contains styles from `map` as well as + /// `self`. The ones from `map` take precedence over the ones from + /// `self`. For folded properties `map` contributes the inner value. + pub fn chain<'b>(&'b self, map: &'b StyleMap) -> StyleChain<'b> { + if map.is_empty() { + *self + } else { + StyleChain { head: &map.0, tail: Some(self) } } - - Ok(realized) } - /// Whether the style chain has a matching recipe for the content. - pub fn applicable(self, target: &Content) -> bool { - // Find out how many recipes there are. - let mut n = self.entries().filter_map(Style::recipe).count(); - - // Find out whether any recipe matches and is unguarded. - for recipe in self.entries().filter_map(Style::recipe) { - if recipe.applicable(target) && !target.is_guarded(RecipeId::Nth(n)) { - return true; + /// Make the given style the first link of the this chain. + pub fn chain_one<'b>(&'b self, style: &'b Style) -> StyleChain<'b> { + if let Style::Barrier(id) = style { + if !self + .entries() + .filter_map(Style::property) + .any(|p| p.scoped() && *id == p.node()) + { + return *self; } - n -= 1; } - false + StyleChain { + head: std::slice::from_ref(style), + tail: Some(self), + } } - /// Remove the last link from the chain. - fn pop(&mut self) { - *self = self.tail.copied().unwrap_or_default(); + /// Get the output value of a style property. + /// + /// Returns the property's default value if no map in the chain contains an + /// entry for it. Also takes care of resolving and folding and returns + /// references where applicable. + pub fn get(self, key: K) -> K::Output<'a> { + K::get(self, self.values(key)) } - /// Build a style map from the suffix (all links beyond the `len`) of the - /// chain. - fn suffix(self, len: usize) -> StyleMap { - let mut suffix = StyleMap::new(); - let take = self.links().count().saturating_sub(len); - for link in self.links().take(take) { - suffix.0.splice(0..0, link.iter().cloned()); - } - suffix + /// Iterate over all style recipes in the chain. + pub fn recipes(self) -> impl Iterator { + self.entries().filter_map(Style::recipe) } /// Iterate over all values for the given property in the chain. @@ -655,6 +474,22 @@ impl<'a> StyleChain<'a> { fn links(self) -> Links<'a> { Links(Some(self)) } + + /// Build a style map from the suffix (all links beyond the `len`) of the + /// chain. + fn suffix(self, len: usize) -> StyleMap { + let mut suffix = StyleMap::new(); + let take = self.links().count().saturating_sub(len); + for link in self.links().take(take) { + suffix.0.splice(0..0, link.iter().cloned()); + } + suffix + } + + /// Remove the last link from the chain. + fn pop(&mut self) { + *self = self.tail.copied().unwrap_or_default(); + } } impl Debug for StyleChain<'_> { @@ -675,37 +510,6 @@ impl PartialEq for StyleChain<'_> { } } -/// An iterator over the values in a style chain. -struct Values<'a, K> { - entries: Entries<'a>, - key: PhantomData, - barriers: usize, -} - -impl<'a, K: Key> Iterator for Values<'a, K> { - type Item = &'a K::Value; - - fn next(&mut self) -> Option { - for entry in &mut self.entries { - match entry { - Style::Property(property) => { - if let Some(value) = property.downcast::() { - if !property.scoped() || self.barriers <= 1 { - return Some(value); - } - } - } - Style::Barrier(id) => { - self.barriers += (*id == K::node()) as usize; - } - _ => {} - } - } - - None - } -} - /// An iterator over the entries in a style chain. struct Entries<'a> { inner: std::slice::Iter<'a, Style>, @@ -742,6 +546,37 @@ impl<'a> Iterator for Links<'a> { } } +/// An iterator over the values in a style chain. +struct Values<'a, K> { + entries: Entries<'a>, + key: PhantomData, + barriers: usize, +} + +impl<'a, K: Key> Iterator for Values<'a, K> { + type Item = &'a K::Value; + + fn next(&mut self) -> Option { + for entry in &mut self.entries { + match entry { + Style::Property(property) => { + if let Some(value) = property.downcast::() { + if !property.scoped() || self.barriers <= 1 { + return Some(value); + } + } + } + Style::Barrier(id) => { + self.barriers += (*id == K::node()) as usize; + } + _ => {} + } + } + + None + } +} + /// A sequence of items with associated styles. #[derive(Clone, Hash)] pub struct StyleVec { diff --git a/src/model/typeset.rs b/src/model/typeset.rs index 2026bf99..ad2af3b2 100644 --- a/src/model/typeset.rs +++ b/src/model/typeset.rs @@ -10,8 +10,9 @@ use crate::World; /// 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. +#[comemo::memoize] pub fn typeset(world: Tracked, content: &Content) -> SourceResult> { let library = world.library(); - let styles = StyleChain::with_root(&library.styles); - (library.items.layout)(content, world, styles) + let styles = StyleChain::new(&library.styles); + (library.items.layout)(world, content, styles) } diff --git a/src/model/vm.rs b/src/model/vm.rs deleted file mode 100644 index d3509eae..00000000 --- a/src/model/vm.rs +++ /dev/null @@ -1,97 +0,0 @@ -use std::path::PathBuf; - -use comemo::Tracked; - -use super::{LangItems, Route, Scopes, Value}; -use crate::diag::{error, SourceError, StrResult}; -use crate::syntax::{SourceId, Span}; -use crate::util::PathExt; -use crate::World; - -/// A virtual machine. -/// -/// Holds the state needed to evaluate Typst sources. A new virtual machine is -/// created for each module evaluation and function call. -pub struct Vm<'a> { - /// The core context. - pub(crate) world: Tracked<'a, dyn World>, - /// The route of source ids the VM took to reach its current location. - pub(crate) route: Tracked<'a, Route>, - /// The current location. - pub(crate) location: SourceId, - /// The stack of scopes. - pub(crate) scopes: Scopes<'a>, - /// A control flow event that is currently happening. - pub(crate) flow: Option, - /// The language items. - pub(crate) items: LangItems, -} - -impl<'a> Vm<'a> { - /// Create a new virtual machine. - pub fn new( - world: Tracked<'a, dyn World>, - route: Tracked<'a, Route>, - location: SourceId, - scopes: Scopes<'a>, - ) -> Self { - Self { - world, - route, - location, - scopes, - flow: None, - items: world.library().items.clone(), - } - } - - /// Access the underlying world. - pub fn world(&self) -> Tracked { - self.world - } - - /// Resolve a user-entered path to be relative to the compilation - /// environment's root. - pub fn locate(&self, path: &str) -> StrResult { - if !self.location.is_detached() { - if let Some(path) = path.strip_prefix('/') { - return Ok(self.world.root().join(path).normalize()); - } - - if let Some(dir) = self.world.source(self.location).path().parent() { - return Ok(dir.join(path).normalize()); - } - } - - Err("cannot access file system from here".into()) - } -} - -/// A control flow event that occurred during evaluation. -#[derive(Debug, Clone, PartialEq)] -pub enum Flow { - /// Stop iteration in a loop. - Break(Span), - /// Skip the remainder of the current iteration in a loop. - Continue(Span), - /// Stop execution of a function early, optionally returning an explicit - /// value. - Return(Span, Option), -} - -impl Flow { - /// Return an error stating that this control flow is forbidden. - pub fn forbidden(&self) -> SourceError { - match *self { - Self::Break(span) => { - error!(span, "cannot break outside of loop") - } - Self::Continue(span) => { - error!(span, "cannot continue outside of loop") - } - Self::Return(span, _) => { - error!(span, "cannot return outside of function") - } - } - } -} -- cgit v1.2.3