Tracepoints for show rules
This commit is contained in:
parent
46d469f4be
commit
ddada45097
@ -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
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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! {
|
||||
|
@ -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 ()>;
|
||||
|
@ -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()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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!")
|
||||
}
|
||||
|
||||
---
|
||||
|
Loading…
x
Reference in New Issue
Block a user