Tracepoints for show rules

This commit is contained in:
Laurenz 2022-11-20 23:32:30 +01:00
parent 46d469f4be
commit ddada45097
8 changed files with 100 additions and 45 deletions

View File

@ -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::<Self>()
}
fn name(&self) -> &'static str {
#name
}
#vtable
}

View File

@ -96,6 +96,8 @@ impl SourceError {
pub enum Tracepoint {
/// A function call.
Call(Option<EcoString>),
/// 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")
}

View File

@ -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! {

View File

@ -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<dyn Bounds>, Vec<RecipeId>);
pub struct Content(Arc<dyn Bounds>, Vec<RecipeId>, Option<Span>);
impl Content {
/// Create empty content.
@ -41,6 +42,16 @@ impl Content {
self.downcast::<SequenceNode>().map_or(false, |seq| seq.0.is_empty())
}
/// The node's span.
pub fn span(&self) -> Option<Span> {
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<Self> {
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::<StyledNode>() {
styled.sub.2 = Some(span);
} else if let Some(styled) = self.downcast::<StyledNode>() {
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 ()>;

View File

@ -155,29 +155,30 @@ impl Eval for ast::MarkupNode {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
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()))
}
}

View File

@ -68,9 +68,7 @@ impl Func {
/// The number of positional arguments this function takes, if known.
pub fn argc(&self) -> Option<usize> {
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<usize> {
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.

View File

@ -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<F>(
pub fn apply(
&self,
world: Tracked<dyn World>,
span: Span,
arg: F,
) -> SourceResult<Content>
where
F: FnOnce() -> Value,
{
rule_span: Span,
content: Content,
) -> SourceResult<Content> {
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))
}
}
}

View File

@ -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!")
}
---