mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
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
|
// Put everything into a module with a hopefully unique type to isolate
|
||||||
// it from the outside.
|
// it from the outside.
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
@ -131,6 +133,10 @@ fn expand_node(
|
|||||||
model::NodeId::of::<Self>()
|
model::NodeId::of::<Self>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
#name
|
||||||
|
}
|
||||||
|
|
||||||
#vtable
|
#vtable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,6 +96,8 @@ impl SourceError {
|
|||||||
pub enum Tracepoint {
|
pub enum Tracepoint {
|
||||||
/// A function call.
|
/// A function call.
|
||||||
Call(Option<EcoString>),
|
Call(Option<EcoString>),
|
||||||
|
/// A show rule application.
|
||||||
|
Apply(EcoString),
|
||||||
/// A module import.
|
/// A module import.
|
||||||
Import,
|
Import,
|
||||||
}
|
}
|
||||||
@ -109,6 +111,9 @@ impl Display for Tracepoint {
|
|||||||
Tracepoint::Call(None) => {
|
Tracepoint::Call(None) => {
|
||||||
write!(f, "error occured in this function call")
|
write!(f, "error occured in this function call")
|
||||||
}
|
}
|
||||||
|
Tracepoint::Apply(name) => {
|
||||||
|
write!(f, "error occured while applying show rule to this {name}")
|
||||||
|
}
|
||||||
Tracepoint::Import => {
|
Tracepoint::Import => {
|
||||||
write!(f, "error occured while importing this module")
|
write!(f, "error occured while importing this module")
|
||||||
}
|
}
|
||||||
|
@ -194,7 +194,12 @@ castable! {
|
|||||||
Value::None => Self::Content(Content::empty()),
|
Value::None => Self::Content(Content::empty()),
|
||||||
Value::Str(text) => Self::Content(item!(text)(text.into())),
|
Value::Str(text) => Self::Content(item!(text)(text.into())),
|
||||||
Value::Content(content) => Self::Content(content),
|
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! {
|
dynamic! {
|
||||||
|
@ -11,6 +11,7 @@ use typst_macros::node;
|
|||||||
|
|
||||||
use super::{Args, Key, Property, Recipe, RecipeId, Style, StyleMap, Value, Vm};
|
use super::{Args, Key, Property, Recipe, RecipeId, Style, StyleMap, Value, Vm};
|
||||||
use crate::diag::{SourceResult, StrResult};
|
use crate::diag::{SourceResult, StrResult};
|
||||||
|
use crate::syntax::Span;
|
||||||
use crate::util::ReadableTypeId;
|
use crate::util::ReadableTypeId;
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ use crate::World;
|
|||||||
/// - anything written between square brackets in Typst
|
/// - anything written between square brackets in Typst
|
||||||
/// - any constructor function
|
/// - any constructor function
|
||||||
#[derive(Clone, Hash)]
|
#[derive(Clone, Hash)]
|
||||||
pub struct Content(Arc<dyn Bounds>, Vec<RecipeId>);
|
pub struct Content(Arc<dyn Bounds>, Vec<RecipeId>, Option<Span>);
|
||||||
|
|
||||||
impl Content {
|
impl Content {
|
||||||
/// Create empty content.
|
/// Create empty content.
|
||||||
@ -41,6 +42,16 @@ impl Content {
|
|||||||
self.downcast::<SequenceNode>().map_or(false, |seq| seq.0.is_empty())
|
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.
|
/// The id of the contained node.
|
||||||
pub fn id(&self) -> NodeId {
|
pub fn id(&self) -> NodeId {
|
||||||
(*self.0).id()
|
(*self.0).id()
|
||||||
@ -105,7 +116,7 @@ impl Content {
|
|||||||
recipe: Recipe,
|
recipe: Recipe,
|
||||||
) -> SourceResult<Self> {
|
) -> SourceResult<Self> {
|
||||||
if recipe.selector.is_none() {
|
if recipe.selector.is_none() {
|
||||||
recipe.transform.apply(world, recipe.span, || Value::Content(self))
|
recipe.transform.apply(world, recipe.span, self)
|
||||||
} else {
|
} else {
|
||||||
Ok(self.styled_with_entry(Style::Recipe(recipe)))
|
Ok(self.styled_with_entry(Style::Recipe(recipe)))
|
||||||
}
|
}
|
||||||
@ -139,6 +150,21 @@ impl Content {
|
|||||||
StyledNode { sub: self, map: styles }.pack()
|
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.
|
/// Disable a show rule recipe.
|
||||||
pub fn guard(mut self, id: RecipeId) -> Self {
|
pub fn guard(mut self, id: RecipeId) -> Self {
|
||||||
self.1.push(id);
|
self.1.push(id);
|
||||||
@ -252,7 +278,7 @@ pub trait Node: 'static {
|
|||||||
where
|
where
|
||||||
Self: Debug + Hash + Sync + Send + Sized + 'static,
|
Self: Debug + Hash + Sync + Send + Sized + 'static,
|
||||||
{
|
{
|
||||||
Content(Arc::new(self), vec![])
|
Content(Arc::new(self), vec![], None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct a node from the arguments.
|
/// Construct a node from the arguments.
|
||||||
@ -277,6 +303,9 @@ pub trait Node: 'static {
|
|||||||
/// A unique identifier of the node type.
|
/// A unique identifier of the node type.
|
||||||
fn id(&self) -> NodeId;
|
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
|
/// Extract the pointer of the vtable of the trait object with the
|
||||||
/// given type `id` if this node implements that trait.
|
/// given type `id` if this node implements that trait.
|
||||||
fn vtable(&self, id: TypeId) -> Option<*const ()>;
|
fn vtable(&self, id: TypeId) -> Option<*const ()>;
|
||||||
|
@ -155,29 +155,30 @@ impl Eval for ast::MarkupNode {
|
|||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
match self {
|
Ok(match self {
|
||||||
Self::Space(v) => Ok(match v.newlines() {
|
Self::Space(v) => match v.newlines() {
|
||||||
0..=1 => (vm.items.space)(),
|
0..=1 => (vm.items.space)(),
|
||||||
_ => (vm.items.parbreak)(),
|
_ => (vm.items.parbreak)(),
|
||||||
}),
|
},
|
||||||
Self::Linebreak(v) => v.eval(vm),
|
Self::Linebreak(v) => v.eval(vm)?,
|
||||||
Self::Text(v) => v.eval(vm),
|
Self::Text(v) => v.eval(vm)?,
|
||||||
Self::Escape(v) => Ok((vm.items.text)(v.get().into())),
|
Self::Escape(v) => (vm.items.text)(v.get().into()),
|
||||||
Self::Shorthand(v) => v.eval(vm),
|
Self::Shorthand(v) => v.eval(vm)?,
|
||||||
Self::SmartQuote(v) => v.eval(vm),
|
Self::SmartQuote(v) => v.eval(vm)?,
|
||||||
Self::Strong(v) => v.eval(vm),
|
Self::Strong(v) => v.eval(vm)?,
|
||||||
Self::Emph(v) => v.eval(vm),
|
Self::Emph(v) => v.eval(vm)?,
|
||||||
Self::Link(v) => v.eval(vm),
|
Self::Link(v) => v.eval(vm)?,
|
||||||
Self::Raw(v) => v.eval(vm),
|
Self::Raw(v) => v.eval(vm)?,
|
||||||
Self::Math(v) => v.eval(vm),
|
Self::Math(v) => v.eval(vm)?,
|
||||||
Self::Heading(v) => v.eval(vm),
|
Self::Heading(v) => v.eval(vm)?,
|
||||||
Self::List(v) => v.eval(vm),
|
Self::List(v) => v.eval(vm)?,
|
||||||
Self::Enum(v) => v.eval(vm),
|
Self::Enum(v) => v.eval(vm)?,
|
||||||
Self::Desc(v) => v.eval(vm),
|
Self::Desc(v) => v.eval(vm)?,
|
||||||
Self::Label(v) => v.eval(vm),
|
Self::Label(v) => v.eval(vm)?,
|
||||||
Self::Ref(v) => v.eval(vm),
|
Self::Ref(v) => v.eval(vm)?,
|
||||||
Self::Expr(v) => v.eval(vm).map(|value| value.display(vm.world)),
|
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.
|
/// The number of positional arguments this function takes, if known.
|
||||||
pub fn argc(&self) -> Option<usize> {
|
pub fn argc(&self) -> Option<usize> {
|
||||||
match self.0.as_ref() {
|
match self.0.as_ref() {
|
||||||
Repr::Closure(closure) => Some(
|
Repr::Closure(closure) => closure.argc(),
|
||||||
closure.params.iter().filter(|(_, default)| default.is_none()).count(),
|
|
||||||
),
|
|
||||||
Repr::With(wrapped, applied) => Some(wrapped.argc()?.saturating_sub(
|
Repr::With(wrapped, applied) => Some(wrapped.argc()?.saturating_sub(
|
||||||
applied.items.iter().filter(|arg| arg.name.is_none()).count(),
|
applied.items.iter().filter(|arg| arg.name.is_none()).count(),
|
||||||
)),
|
)),
|
||||||
@ -239,6 +237,15 @@ impl Closure {
|
|||||||
|
|
||||||
result
|
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.
|
/// A visitor that determines which variables to capture for a closure.
|
||||||
|
@ -8,7 +8,7 @@ use std::sync::Arc;
|
|||||||
use comemo::{Prehashed, Tracked};
|
use comemo::{Prehashed, Tracked};
|
||||||
|
|
||||||
use super::{capability, Args, Content, Dict, Func, NodeId, Regex, Smart, Value};
|
use super::{capability, Args, Content, Dict, Func, NodeId, Regex, Smart, Value};
|
||||||
use crate::diag::SourceResult;
|
use crate::diag::{SourceResult, Trace, Tracepoint};
|
||||||
use crate::geom::{
|
use crate::geom::{
|
||||||
Abs, Align, Axes, Corners, Em, GenAlign, Length, Numeric, PartialStroke, Rel, Sides,
|
Abs, Align, Axes, Corners, Em, GenAlign, Length, Numeric, PartialStroke, Rel, Sides,
|
||||||
};
|
};
|
||||||
@ -382,9 +382,7 @@ impl Recipe {
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.transform.apply(world, self.span, || {
|
self.transform.apply(world, self.span, target.clone().guard(sel))?
|
||||||
Value::Content(target.clone().guard(sel))
|
|
||||||
})?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(Selector::Regex(regex)) => {
|
Some(Selector::Regex(regex)) => {
|
||||||
@ -402,9 +400,11 @@ impl Recipe {
|
|||||||
result.push(make(text[cursor..start].into()));
|
result.push(make(text[cursor..start].into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let transformed = self.transform.apply(world, self.span, || {
|
let transformed = self.transform.apply(
|
||||||
Value::Content(make(mat.as_str().into()).guard(sel))
|
world,
|
||||||
})?;
|
self.span,
|
||||||
|
make(mat.as_str().into()).guard(sel),
|
||||||
|
)?;
|
||||||
|
|
||||||
result.push(transformed);
|
result.push(transformed);
|
||||||
cursor = mat.end();
|
cursor = mat.end();
|
||||||
@ -486,20 +486,22 @@ pub enum Transform {
|
|||||||
|
|
||||||
impl Transform {
|
impl Transform {
|
||||||
/// Apply the transform.
|
/// Apply the transform.
|
||||||
pub fn apply<F>(
|
pub fn apply(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
span: Span,
|
rule_span: Span,
|
||||||
arg: F,
|
content: Content,
|
||||||
) -> SourceResult<Content>
|
) -> SourceResult<Content> {
|
||||||
where
|
|
||||||
F: FnOnce() -> Value,
|
|
||||||
{
|
|
||||||
match self {
|
match self {
|
||||||
Transform::Content(content) => Ok(content.clone()),
|
Transform::Content(content) => Ok(content.clone()),
|
||||||
Transform::Func(func) => {
|
Transform::Func(func) => {
|
||||||
let args = Args::new(span, [arg()]);
|
let args = Args::new(rule_span, [Value::Content(content.clone())]);
|
||||||
Ok(func.call_detached(world, args)?.display(world))
|
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())
|
.map(s => s.trim())
|
||||||
.filter(s => s != "")
|
.filter(s => s != "")
|
||||||
.map(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