From ddada4509729b0b8dcc394737d7eec588df51040 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sun, 20 Nov 2022 23:32:30 +0100 Subject: [PATCH] Tracepoints for show rules --- macros/src/lib.rs | 6 ++++++ src/diag.rs | 5 +++++ src/model/cast.rs | 7 ++++++- src/model/content.rs | 35 +++++++++++++++++++++++++++++--- src/model/eval.rs | 41 +++++++++++++++++++------------------- src/model/func.rs | 13 +++++++++--- src/model/styles.rs | 34 ++++++++++++++++--------------- tests/typ/code/methods.typ | 4 ++-- 8 files changed, 100 insertions(+), 45 deletions(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index eaa929dc8..7e2caae5e 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -109,6 +109,8 @@ fn expand_node( } }; + let name = self_name.trim_end_matches("Node").to_lowercase(); + // Put everything into a module with a hopefully unique type to isolate // it from the outside. Ok(quote! { @@ -131,6 +133,10 @@ fn expand_node( model::NodeId::of::() } + fn name(&self) -> &'static str { + #name + } + #vtable } diff --git a/src/diag.rs b/src/diag.rs index 7631b6d3d..613789f8b 100644 --- a/src/diag.rs +++ b/src/diag.rs @@ -96,6 +96,8 @@ impl SourceError { pub enum Tracepoint { /// A function call. Call(Option), + /// A show rule application. + Apply(EcoString), /// A module import. Import, } @@ -109,6 +111,9 @@ impl Display for Tracepoint { Tracepoint::Call(None) => { write!(f, "error occured in this function call") } + Tracepoint::Apply(name) => { + write!(f, "error occured while applying show rule to this {name}") + } Tracepoint::Import => { write!(f, "error occured while importing this module") } diff --git a/src/model/cast.rs b/src/model/cast.rs index d2e10a1f1..d5b4893dc 100644 --- a/src/model/cast.rs +++ b/src/model/cast.rs @@ -194,7 +194,12 @@ castable! { Value::None => Self::Content(Content::empty()), Value::Str(text) => Self::Content(item!(text)(text.into())), Value::Content(content) => Self::Content(content), - Value::Func(func) => Self::Func(func), + Value::Func(func) => { + if func.argc().map_or(false, |count| count != 1) { + Err("function must have exactly one parameter")? + } + Self::Func(func) + }, } dynamic! { diff --git a/src/model/content.rs b/src/model/content.rs index 15ea33cbf..210b8bdef 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -11,6 +11,7 @@ use typst_macros::node; use super::{Args, Key, Property, Recipe, RecipeId, Style, StyleMap, Value, Vm}; use crate::diag::{SourceResult, StrResult}; +use crate::syntax::Span; use crate::util::ReadableTypeId; use crate::World; @@ -20,7 +21,7 @@ use crate::World; /// - anything written between square brackets in Typst /// - any constructor function #[derive(Clone, Hash)] -pub struct Content(Arc, Vec); +pub struct Content(Arc, Vec, Option); impl Content { /// Create empty content. @@ -41,6 +42,16 @@ impl Content { self.downcast::().map_or(false, |seq| seq.0.is_empty()) } + /// The node's span. + pub fn span(&self) -> Option { + self.2 + } + + /// The node's human-readable name. + pub fn name(&self) -> &'static str { + (*self.0).name() + } + /// The id of the contained node. pub fn id(&self) -> NodeId { (*self.0).id() @@ -105,7 +116,7 @@ impl Content { recipe: Recipe, ) -> SourceResult { if recipe.selector.is_none() { - recipe.transform.apply(world, recipe.span, || Value::Content(self)) + recipe.transform.apply(world, recipe.span, self) } else { Ok(self.styled_with_entry(Style::Recipe(recipe))) } @@ -139,6 +150,21 @@ impl Content { StyledNode { sub: self, map: styles }.pack() } + /// Attach a span to the content. + pub fn spanned(mut self, span: Span) -> Self { + if let Some(styled) = self.try_downcast_mut::() { + styled.sub.2 = Some(span); + } else if let Some(styled) = self.downcast::() { + self = StyledNode { + sub: styled.sub.clone().spanned(span), + map: styled.map.clone(), + } + .pack(); + } + self.2 = Some(span); + self + } + /// Disable a show rule recipe. pub fn guard(mut self, id: RecipeId) -> Self { self.1.push(id); @@ -252,7 +278,7 @@ pub trait Node: 'static { where Self: Debug + Hash + Sync + Send + Sized + 'static, { - Content(Arc::new(self), vec![]) + Content(Arc::new(self), vec![], None) } /// Construct a node from the arguments. @@ -277,6 +303,9 @@ pub trait Node: 'static { /// A unique identifier of the node type. fn id(&self) -> NodeId; + /// The node's name. + fn name(&self) -> &'static str; + /// Extract the pointer of the vtable of the trait object with the /// given type `id` if this node implements that trait. fn vtable(&self, id: TypeId) -> Option<*const ()>; diff --git a/src/model/eval.rs b/src/model/eval.rs index 2ed8c13b3..8a596afbf 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -155,29 +155,30 @@ impl Eval for ast::MarkupNode { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult { - match self { - Self::Space(v) => Ok(match v.newlines() { + Ok(match self { + Self::Space(v) => match v.newlines() { 0..=1 => (vm.items.space)(), _ => (vm.items.parbreak)(), - }), - Self::Linebreak(v) => v.eval(vm), - Self::Text(v) => v.eval(vm), - Self::Escape(v) => Ok((vm.items.text)(v.get().into())), - Self::Shorthand(v) => v.eval(vm), - Self::SmartQuote(v) => v.eval(vm), - Self::Strong(v) => v.eval(vm), - Self::Emph(v) => v.eval(vm), - Self::Link(v) => v.eval(vm), - Self::Raw(v) => v.eval(vm), - Self::Math(v) => v.eval(vm), - Self::Heading(v) => v.eval(vm), - Self::List(v) => v.eval(vm), - Self::Enum(v) => v.eval(vm), - Self::Desc(v) => v.eval(vm), - Self::Label(v) => v.eval(vm), - Self::Ref(v) => v.eval(vm), - Self::Expr(v) => v.eval(vm).map(|value| value.display(vm.world)), + }, + Self::Linebreak(v) => v.eval(vm)?, + Self::Text(v) => v.eval(vm)?, + Self::Escape(v) => (vm.items.text)(v.get().into()), + Self::Shorthand(v) => v.eval(vm)?, + Self::SmartQuote(v) => v.eval(vm)?, + Self::Strong(v) => v.eval(vm)?, + Self::Emph(v) => v.eval(vm)?, + Self::Link(v) => v.eval(vm)?, + Self::Raw(v) => v.eval(vm)?, + Self::Math(v) => v.eval(vm)?, + Self::Heading(v) => v.eval(vm)?, + Self::List(v) => v.eval(vm)?, + Self::Enum(v) => v.eval(vm)?, + Self::Desc(v) => v.eval(vm)?, + Self::Label(v) => v.eval(vm)?, + Self::Ref(v) => v.eval(vm)?, + Self::Expr(v) => v.eval(vm)?.display(vm.world), } + .spanned(self.span())) } } diff --git a/src/model/func.rs b/src/model/func.rs index 15434bbfe..21e36784d 100644 --- a/src/model/func.rs +++ b/src/model/func.rs @@ -68,9 +68,7 @@ impl Func { /// The number of positional arguments this function takes, if known. pub fn argc(&self) -> Option { match self.0.as_ref() { - Repr::Closure(closure) => Some( - closure.params.iter().filter(|(_, default)| default.is_none()).count(), - ), + Repr::Closure(closure) => closure.argc(), Repr::With(wrapped, applied) => Some(wrapped.argc()?.saturating_sub( applied.items.iter().filter(|arg| arg.name.is_none()).count(), )), @@ -239,6 +237,15 @@ impl Closure { result } + + /// The number of positional arguments this function takes, if known. + pub fn argc(&self) -> Option { + if self.sink.is_some() { + return None; + } + + Some(self.params.iter().filter(|(_, default)| default.is_none()).count()) + } } /// A visitor that determines which variables to capture for a closure. diff --git a/src/model/styles.rs b/src/model/styles.rs index ced11e6ac..a5b9aab3a 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -8,7 +8,7 @@ use std::sync::Arc; use comemo::{Prehashed, Tracked}; use super::{capability, Args, Content, Dict, Func, NodeId, Regex, Smart, Value}; -use crate::diag::SourceResult; +use crate::diag::{SourceResult, Trace, Tracepoint}; use crate::geom::{ Abs, Align, Axes, Corners, Em, GenAlign, Length, Numeric, PartialStroke, Rel, Sides, }; @@ -382,9 +382,7 @@ impl Recipe { return Ok(None); } - self.transform.apply(world, self.span, || { - Value::Content(target.clone().guard(sel)) - })? + self.transform.apply(world, self.span, target.clone().guard(sel))? } Some(Selector::Regex(regex)) => { @@ -402,9 +400,11 @@ impl Recipe { result.push(make(text[cursor..start].into())); } - let transformed = self.transform.apply(world, self.span, || { - Value::Content(make(mat.as_str().into()).guard(sel)) - })?; + let transformed = self.transform.apply( + world, + self.span, + make(mat.as_str().into()).guard(sel), + )?; result.push(transformed); cursor = mat.end(); @@ -486,20 +486,22 @@ pub enum Transform { impl Transform { /// Apply the transform. - pub fn apply( + pub fn apply( &self, world: Tracked, - span: Span, - arg: F, - ) -> SourceResult - where - F: FnOnce() -> Value, - { + rule_span: Span, + content: Content, + ) -> SourceResult { match self { Transform::Content(content) => Ok(content.clone()), Transform::Func(func) => { - let args = Args::new(span, [arg()]); - Ok(func.call_detached(world, args)?.display(world)) + 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(world)) } } } diff --git a/tests/typ/code/methods.typ b/tests/typ/code/methods.typ index b5eff78d7..f5db8ca03 100644 --- a/tests/typ/code/methods.typ +++ b/tests/typ/code/methods.typ @@ -21,9 +21,9 @@ .map(s => s.trim()) .filter(s => s != "") .map(s => s + "!") - .join([\ ]) + .join("\n ") - test(rewritten, [Hello!\ This is a sentence!\ And one more!]) + test(rewritten, "Hello!\n This is a sentence!\n And one more!") } ---