summaryrefslogtreecommitdiff
path: root/src/model
diff options
context:
space:
mode:
Diffstat (limited to 'src/model')
-rw-r--r--src/model/content.rs29
-rw-r--r--src/model/dict.rs2
-rw-r--r--src/model/eval.rs114
-rw-r--r--src/model/func.rs39
-rw-r--r--src/model/library.rs6
-rw-r--r--src/model/mod.rs4
-rw-r--r--src/model/realize.rs145
-rw-r--r--src/model/str.rs64
-rw-r--r--src/model/styles.rs435
-rw-r--r--src/model/typeset.rs5
-rw-r--r--src/model/vm.rs97
11 files changed, 460 insertions, 480 deletions
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<dyn Bounds>,
- guards: Vec<RecipeId>,
+ guards: Vec<Guard>,
span: Option<Span>,
label: Option<EcoString>,
}
@@ -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::<StyledNode>() {
- styled.map.apply(style);
+ styled.map.apply_one(style);
self
} else if let Some(styled) = self.to::<StyledNode>() {
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::<StyledNode>() {
- styled.map.apply_map(&styles);
+ styled.map.apply(styles);
return self;
}
@@ -101,7 +99,7 @@ impl Content {
recipe: Recipe,
) -> SourceResult<Self> {
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<Value>;
}
-/// 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<T: 'static>() -> Self {
Self(ReadableTypeId::of::<T>())
}
@@ -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<Func>) -> SourceResult<Array> {
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<Flow>,
+ /// 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<dyn World> {
+ self.world
+ }
+
+ /// Resolve a user-entered path to be relative to the compilation
+ /// environment's root.
+ pub fn locate(&self, path: &str) -> StrResult<PathBuf> {
+ 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<Value>),
+}
+
+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<Self::Output> {
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<Selector> {
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<Value> {
+ fn call(&self, vm: &Vm, args: &mut Args) -> SourceResult<Value> {
// 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<usize> {
+ fn argc(&self) -> Option<usize> {
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<dyn World>,
+ content: &Content,
styles: StyleChain,
) -> SourceResult<Vec<Frame>>,
/// 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<dyn World>,
+ target: &Content,
+ styles: StyleChain,
+) -> SourceResult<Option<Content>> {
+ // 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::<dyn Show>() {
+ 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::<dyn Finalize>() {
+ 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<dyn World>,
+ target: &Content,
+ recipe: &Recipe,
+ guard: Guard,
+) -> SourceResult<Option<Content>> {
+ 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<dyn World>, 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<Self> {
+ pub fn find(&self, pattern: StrPattern) -> Option<Self> {
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<i64> {
+ pub fn position(&self, pattern: StrPattern) -> Option<i64> {
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<Dict> {
+ pub fn match_(&self, pattern: StrPattern) -> Option<Dict> {
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<Pattern>) -> Array {
+ pub fn split(&self, pattern: Option<StrPattern>) -> 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>,
+ pattern: Option<StrPattern>,
at: Option<StrSide>,
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<usize>) -> Self {
+ pub fn replace(&self, pattern: StrPattern, with: Self, count: Option<usize>) -> 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::<K>())
}
- /// 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<Item = &'a Self::Value>,
- ) -> 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<Item = &'a Self::Value>,
+ ) -> 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<dyn World>,
- styles: StyleChain,
- ) -> SourceResult<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,
- world: Tracked<dyn World>,
- styles: StyleChain,
- realized: Content,
- ) -> SourceResult<Content>;
-}
-
-/// 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<dyn World>,
- sel: RecipeId,
- target: &Content,
- ) -> SourceResult<Option<Content>> {
- 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<Content> {
+ 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<dyn World>,
- rule_span: Span,
- content: Content,
- ) -> SourceResult<Content> {
- 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<K: Key>(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<dyn World>,
- target: &Content,
- ) -> SourceResult<Option<Content>> {
- // 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::<dyn Show>() {
- realized = Some(showable.show(world, self)?);
- }
- }
-
- // Finalize only if this is the first application for this node.
- if let Some(node) = target.with::<dyn Finalize>() {
- 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<K: Key>(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<Item = &'a Recipe> {
+ 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<K>,
- barriers: usize,
-}
-
-impl<'a, K: Key> Iterator for Values<'a, K> {
- type Item = &'a K::Value;
-
- fn next(&mut self) -> Option<Self::Item> {
- for entry in &mut self.entries {
- match entry {
- Style::Property(property) => {
- if let Some(value) = property.downcast::<K>() {
- 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<K>,
+ barriers: usize,
+}
+
+impl<'a, K: Key> Iterator for Values<'a, K> {
+ type Item = &'a K::Value;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ for entry in &mut self.entries {
+ match entry {
+ Style::Property(property) => {
+ if let Some(value) = property.downcast::<K>() {
+ 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<T> {
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<dyn World>, content: &Content) -> SourceResult<Vec<Frame>> {
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<Flow>,
- /// 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<dyn World> {
- self.world
- }
-
- /// Resolve a user-entered path to be relative to the compilation
- /// environment's root.
- pub fn locate(&self, path: &str) -> StrResult<PathBuf> {
- 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<Value>),
-}
-
-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")
- }
- }
- }
-}