From 724e9b140cc0a87208aa9c4914b1b8aeddf25c30 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Mon, 13 Mar 2023 21:40:57 +0100 Subject: [PATCH] Locatability and synthesis improvements --- library/src/compute/data.rs | 2 +- library/src/meta/figure.rs | 18 +++++------ library/src/meta/heading.rs | 27 ++++++----------- library/src/meta/link.rs | 2 +- library/src/meta/outline.rs | 9 +++--- library/src/meta/reference.rs | 13 ++++---- library/src/prelude.rs | 6 ++-- library/src/shared/ext.rs | 2 +- library/src/text/raw.rs | 4 +-- macros/src/node.rs | 43 +++++++++++++++++++++----- src/model/content.rs | 57 +++++++++++++++++++++++++++-------- src/model/realize.rs | 42 +++++++++++++++++--------- src/model/typeset.rs | 15 +++++++-- tests/typ/compiler/set.typ | 4 +-- 14 files changed, 156 insertions(+), 88 deletions(-) diff --git a/library/src/compute/data.rs b/library/src/compute/data.rs index 7addff783..82ff3a4b6 100644 --- a/library/src/compute/data.rs +++ b/library/src/compute/data.rs @@ -27,7 +27,7 @@ pub fn read( let Spanned { v: path, span } = path; let path = vm.locate(&path).at(span)?; let data = vm.world().file(&path).at(span)?; - let text = String::from_utf8(data.to_vec()) + let text = std::str::from_utf8(&data) .map_err(|_| "file is not valid utf-8") .at(span)?; Value::Str(text.into()) diff --git a/library/src/meta/figure.rs b/library/src/meta/figure.rs index 4a0ddc680..4f6ccc00b 100644 --- a/library/src/meta/figure.rs +++ b/library/src/meta/figure.rs @@ -23,7 +23,7 @@ use crate::text::TextNode; /// /// Display: Figure /// Category: meta -#[node(Synthesize, Show, LocalName)] +#[node(Locatable, Synthesize, Show, LocalName)] pub struct FigureNode { /// The content of the figure. Often, an [image]($func/image). #[required] @@ -57,26 +57,24 @@ impl FigureNode { } impl Synthesize for FigureNode { - fn synthesize(&self, vt: &mut Vt, styles: StyleChain) -> Content { - let my_id = vt.identify(self); + fn synthesize(&mut self, vt: &Vt, styles: StyleChain) { + let my_id = self.0.stable_id().unwrap(); let element = self.element(); - let numbering = self.numbering(styles); let mut number = None; + let numbering = self.numbering(styles); if numbering.is_some() { number = NonZeroUsize::new( 1 + vt - .locate(Selector::node::()) - .into_iter() + .locate_node::() .take_while(|&(id, _)| id != my_id) - .filter(|(_, node)| node.to::().unwrap().element() == element) + .filter(|(_, figure)| figure.element() == element) .count(), ); } - let node = self.clone().with_number(number).with_numbering(numbering).pack(); - let meta = Meta::Node(my_id, node.clone()); - node.styled(MetaNode::set_data(vec![meta])) + self.push_number(number); + self.push_numbering(numbering); } } diff --git a/library/src/meta/heading.rs b/library/src/meta/heading.rs index 470479274..4d1b87e65 100644 --- a/library/src/meta/heading.rs +++ b/library/src/meta/heading.rs @@ -40,7 +40,7 @@ use crate::text::{TextNode, TextSize}; /// /// Display: Heading /// Category: meta -#[node(Synthesize, Show, Finalize, LocalName)] +#[node(Locatable, Synthesize, Show, Finalize, LocalName)] pub struct HeadingNode { /// The logical nesting depth of the heading, starting from one. #[default(NonZeroUsize::new(1).unwrap())] @@ -83,19 +83,16 @@ pub struct HeadingNode { } impl Synthesize for HeadingNode { - fn synthesize(&self, vt: &mut Vt, styles: StyleChain) -> Content { - let my_id = vt.identify(self); + fn synthesize(&mut self, vt: &Vt, styles: StyleChain) { + let my_id = self.0.stable_id().unwrap(); let numbering = self.numbering(styles); let mut counter = HeadingCounter::new(); if numbering.is_some() { // Advance past existing headings. - for (_, node) in vt - .locate(Selector::node::()) - .into_iter() - .take_while(|&(id, _)| id != my_id) + for (_, heading) in + vt.locate_node::().take_while(|&(id, _)| id != my_id) { - let heading = node.to::().unwrap(); if heading.numbering(StyleChain::default()).is_some() { counter.advance(heading); } @@ -105,16 +102,10 @@ impl Synthesize for HeadingNode { counter.advance(self); } - let node = self - .clone() - .with_level(self.level(styles)) - .with_outlined(self.outlined(styles)) - .with_numbers(numbering.is_some().then(|| counter.take())) - .with_numbering(numbering) - .pack(); - - let meta = Meta::Node(my_id, node.clone()); - node.styled(MetaNode::set_data(vec![meta])) + self.push_level(self.level(styles)); + self.push_outlined(self.outlined(styles)); + self.push_numbers(numbering.is_some().then(|| counter.take())); + self.push_numbering(numbering); } } diff --git a/library/src/meta/link.rs b/library/src/meta/link.rs index 4aba36974..bca1945a5 100644 --- a/library/src/meta/link.rs +++ b/library/src/meta/link.rs @@ -86,7 +86,7 @@ impl Show for LinkNode { impl Finalize for LinkNode { fn finalize(&self, realized: Content, _: StyleChain) -> Content { realized - .styled(MetaNode::set_data(vec![Meta::Link(self.dest())])) + .linked(self.dest()) .styled(TextNode::set_hyphenate(Hyphenate(Smart::Custom(false)))) } } diff --git a/library/src/meta/outline.rs b/library/src/meta/outline.rs index 81785212c..7ce0ce1d2 100644 --- a/library/src/meta/outline.rs +++ b/library/src/meta/outline.rs @@ -74,15 +74,14 @@ pub struct OutlineNode { } impl Synthesize for OutlineNode { - fn synthesize(&self, vt: &mut Vt, _: StyleChain) -> Content { + fn synthesize(&mut self, vt: &Vt, _: StyleChain) { let headings = vt - .locate(Selector::node::()) - .into_iter() - .map(|(_, node)| node.to::().unwrap().clone()) + .locate_node::() + .map(|(_, node)| node.clone()) .filter(|node| node.outlined(StyleChain::default())) .collect(); - self.clone().with_headings(headings).pack() + self.push_headings(headings); } } diff --git a/library/src/meta/reference.rs b/library/src/meta/reference.rs index 55f2fc6e4..18f0aa3fb 100644 --- a/library/src/meta/reference.rs +++ b/library/src/meta/reference.rs @@ -37,9 +37,9 @@ use crate::text::TextNode; /// Category: meta #[node(Synthesize, Show)] pub struct RefNode { - /// The label that should be referenced. + /// The target label that should be referenced. #[required] - pub label: Label, + pub target: Label, /// The prefix before the referenced number. /// @@ -59,20 +59,19 @@ pub struct RefNode { /// ``` pub prefix: Smart>, - /// All elements with the `target` label in the document. + /// All elements with the target label in the document. #[synthesized] pub matches: Vec, } impl Synthesize for RefNode { - fn synthesize(&self, vt: &mut Vt, _: StyleChain) -> Content { + fn synthesize(&mut self, vt: &Vt, _: StyleChain) { let matches = vt - .locate(Selector::Label(self.label())) - .into_iter() + .locate(Selector::Label(self.target())) .map(|(_, node)| node.clone()) .collect(); - self.clone().with_matches(matches).pack() + self.push_matches(matches); } } diff --git a/library/src/prelude.rs b/library/src/prelude.rs index a9b19f589..36f7cc89b 100644 --- a/library/src/prelude.rs +++ b/library/src/prelude.rs @@ -22,9 +22,9 @@ pub use typst::eval::{ pub use typst::geom::*; #[doc(no_inline)] pub use typst::model::{ - node, Construct, Content, Finalize, Fold, Introspector, Label, Node, NodeId, Resolve, - Selector, Set, Show, StabilityProvider, StyleChain, StyleMap, StyleVec, Synthesize, - Unlabellable, Vt, + node, Construct, Content, Finalize, Fold, Introspector, Label, Locatable, Node, + NodeId, Resolve, Selector, Set, Show, StabilityProvider, StableId, StyleChain, + StyleMap, StyleVec, Synthesize, Unlabellable, Vt, }; #[doc(no_inline)] pub use typst::syntax::{Span, Spanned}; diff --git a/library/src/shared/ext.rs b/library/src/shared/ext.rs index 44f933e2e..e335b4c8f 100644 --- a/library/src/shared/ext.rs +++ b/library/src/shared/ext.rs @@ -42,7 +42,7 @@ impl ContentExt for Content { } fn linked(self, dest: Destination) -> Self { - self.styled(MetaNode::set_data(vec![Meta::Link(dest.clone())])) + self.styled(MetaNode::set_data(vec![Meta::Link(dest)])) } fn aligned(self, aligns: Axes>) -> Self { diff --git a/library/src/text/raw.rs b/library/src/text/raw.rs index 3f03ba8e3..b6cc0d3d7 100644 --- a/library/src/text/raw.rs +++ b/library/src/text/raw.rs @@ -121,8 +121,8 @@ impl RawNode { } impl Synthesize for RawNode { - fn synthesize(&self, _: &mut Vt, styles: StyleChain) -> Content { - self.clone().with_lang(self.lang(styles)).pack() + fn synthesize(&mut self, _: &Vt, styles: StyleChain) { + self.push_lang(self.lang(styles)); } } diff --git a/macros/src/node.rs b/macros/src/node.rs index 678a154d7..148560743 100644 --- a/macros/src/node.rs +++ b/macros/src/node.rs @@ -34,6 +34,7 @@ struct Field { ident: Ident, ident_in: Ident, with_ident: Ident, + push_ident: Ident, set_ident: Ident, ty: syn::Type, output: syn::Type, @@ -81,6 +82,10 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result { let required = has_attr(&mut attrs, "required") || variadic; let positional = has_attr(&mut attrs, "positional") || required; + if ident == "label" { + bail!(ident, "invalid field name"); + } + let mut field = Field { name: kebab_case(&ident), docs: documentation(&attrs), @@ -100,6 +105,7 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result { ident: ident.clone(), ident_in: Ident::new(&format!("{}_in", ident), ident.span()), with_ident: Ident::new(&format!("with_{}", ident), ident.span()), + push_ident: Ident::new(&format!("push_{}", ident), ident.span()), set_ident: Ident::new(&format!("set_{}", ident), ident.span()), ty: field.ty.clone(), output: field.ty.clone(), @@ -162,17 +168,23 @@ fn create(node: &Node) -> TokenStream { let new = create_new_func(node); let field_methods = all.clone().map(create_field_method); let field_in_methods = settable.clone().map(create_field_in_method); - let with_fields_methods = all.map(create_with_field_method); + let with_field_methods = all.clone().map(create_with_field_method); + let push_field_methods = all.map(create_push_field_method); let field_style_methods = settable.map(create_set_field_method); // Trait implementations. - let construct = node + let node_impl = create_node_impl(node); + let construct_impl = node .capable .iter() .all(|capability| capability != "Construct") .then(|| create_construct_impl(node)); - let set = create_set_impl(node); - let node = create_node_impl(node); + let set_impl = create_set_impl(node); + let locatable_impl = node + .capable + .iter() + .any(|capability| capability == "Locatable") + .then(|| quote! { impl ::typst::model::Locatable for #ident {} }); quote! { #[doc = #docs] @@ -184,7 +196,8 @@ fn create(node: &Node) -> TokenStream { #new #(#field_methods)* #(#field_in_methods)* - #(#with_fields_methods)* + #(#with_field_methods)* + #(#push_field_methods)* #(#field_style_methods)* /// The node's span. @@ -193,9 +206,10 @@ fn create(node: &Node) -> TokenStream { } } - #node - #construct - #set + #node_impl + #construct_impl + #set_impl + #locatable_impl impl From<#ident> for ::typst::eval::Value { fn from(value: #ident) -> Self { @@ -232,6 +246,7 @@ fn create_field_method(field: &Field) -> TokenStream { if field.inherent() || field.synthesized { quote! { #[doc = #docs] + #[track_caller] #vis fn #ident(&self) -> #output { self.0.expect_field(#name) } @@ -293,6 +308,18 @@ fn create_with_field_method(field: &Field) -> TokenStream { } } +/// Create a set-style method for a field. +fn create_push_field_method(field: &Field) -> TokenStream { + let Field { vis, ident, push_ident, name, ty, .. } = field; + let doc = format!("Push the [`{}`](Self::{}) field.", name, ident); + quote! { + #[doc = #doc] + #vis fn #push_ident(&mut self, #ident: #ty) { + self.0.push_field(#name, #ident); + } + } +} + /// Create a setter method for a field. fn create_set_field_method(field: &Field) -> TokenStream { let Field { vis, ident, set_ident, name, ty, .. } = field; diff --git a/src/model/content.rs b/src/model/content.rs index 071a58622..58b804877 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -8,7 +8,7 @@ use comemo::Tracked; use ecow::{eco_format, EcoString, EcoVec}; use once_cell::sync::Lazy; -use super::{node, Guard, Recipe, Style, StyleMap}; +use super::{node, Guard, Locatable, Recipe, StableId, Style, StyleMap, Synthesize}; use crate::diag::{SourceResult, StrResult}; use crate::eval::{ cast_from_value, cast_to_value, Args, Cast, Func, FuncInfo, Str, Value, Vm, @@ -29,8 +29,9 @@ pub struct Content { /// Modifiers that can be attached to content. #[derive(Debug, Clone, PartialEq, Hash)] enum Modifier { - Synthesized, + Prepared, Guard(Guard), + Id(StableId), } impl Content { @@ -101,6 +102,16 @@ impl Content { Some(unsafe { &*crate::util::fat::from_raw_parts(data, vtable) }) } + /// Cast to a trait object if this content has the given capability. + pub fn with_mut(&mut self) -> Option<&mut C> + where + C: ?Sized + 'static, + { + let vtable = (self.id.0.vtable)(TypeId::of::())?; + let data = self as *mut Self as *mut (); + Some(unsafe { &mut *crate::util::fat::from_raw_parts_mut(data, vtable) }) + } + /// The node's span. pub fn span(&self) -> Span { self.span @@ -233,17 +244,6 @@ impl Content { self } - /// Mark this content as prepared. - pub fn synthesized(mut self) -> Self { - self.modifiers.push(Modifier::Synthesized); - self - } - - /// Whether this node was prepared. - pub fn is_synthesized(&self) -> bool { - self.modifiers.contains(&Modifier::Synthesized) - } - /// Whether no show rule was executed for this node so far. pub(super) fn is_pristine(&self) -> bool { !self @@ -257,6 +257,37 @@ impl Content { self.modifiers.contains(&Modifier::Guard(id)) } + /// Whether this node was prepared. + pub fn is_prepared(&self) -> bool { + self.modifiers.contains(&Modifier::Prepared) + } + + /// Whether the node needs to be realized specially. + pub fn needs_preparation(&self) -> bool { + (self.can::() + || self.can::() + || self.label().is_some()) + && !self.is_prepared() + } + + /// Mark this content as prepared. + pub fn mark_prepared(&mut self) { + self.modifiers.push(Modifier::Prepared); + } + + /// Attach a stable id to this content. + pub fn set_stable_id(&mut self, id: StableId) { + self.modifiers.push(Modifier::Id(id)); + } + + /// This content's stable identifier. + pub fn stable_id(&self) -> Option { + self.modifiers.iter().find_map(|modifier| match modifier { + Modifier::Id(id) => Some(*id), + _ => None, + }) + } + /// Copy the modifiers from another piece of content. pub(super) fn copy_modifiers(&mut self, from: &Content) { self.span = from.span; diff --git a/src/model/realize.rs b/src/model/realize.rs index 4685a6052..3ead8f7de 100644 --- a/src/model/realize.rs +++ b/src/model/realize.rs @@ -1,9 +1,10 @@ use super::{Content, NodeId, Recipe, Selector, StyleChain, Vt}; use crate::diag::SourceResult; +use crate::doc::{Meta, MetaNode}; /// Whether the target is affected by show rules in the given style chain. pub fn applicable(target: &Content, styles: StyleChain) -> bool { - if target.can::() && !target.is_synthesized() { + if target.needs_preparation() { return true; } @@ -31,21 +32,31 @@ pub fn realize( target: &Content, styles: StyleChain, ) -> SourceResult> { + // Pre-process. + if target.needs_preparation() { + let mut node = target.clone(); + if target.can::() || target.label().is_some() { + let id = vt.identify(target); + node.set_stable_id(id); + } + + if let Some(node) = node.with_mut::() { + node.synthesize(vt, styles); + } + + node.mark_prepared(); + + if let Some(id) = node.stable_id() { + let meta = Meta::Node(id, node.clone()); + return Ok(Some(node.styled(MetaNode::set_data(vec![meta])))); + } + + return Ok(Some(node)); + } + // Find out how many recipes there are. let mut n = styles.recipes().count(); - // Synthesize if not already happened for this node. - if target.can::() && !target.is_synthesized() { - return Ok(Some( - target - .clone() - .synthesized() - .with::() - .unwrap() - .synthesize(vt, styles), - )); - } - // Find an applicable recipe. let mut realized = None; for recipe in styles.recipes() { @@ -144,10 +155,13 @@ fn try_apply( } } +/// Makes this node locatable through `vt.locate`. +pub trait Locatable {} + /// Synthesize fields on a node. This happens before execution of any show rule. pub trait Synthesize { /// Prepare the node for show rule application. - fn synthesize(&self, vt: &mut Vt, styles: StyleChain) -> Content; + fn synthesize(&mut self, vt: &Vt, styles: StyleChain); } /// The base recipe for a node. diff --git a/src/model/typeset.rs b/src/model/typeset.rs index 377c7c76f..8719ea0c1 100644 --- a/src/model/typeset.rs +++ b/src/model/typeset.rs @@ -5,7 +5,7 @@ use std::num::NonZeroUsize; use comemo::{Track, Tracked, TrackedMut}; -use super::{Content, Selector, StyleChain}; +use super::{Content, Node, Selector, StyleChain}; use crate::diag::SourceResult; use crate::doc::{Document, Element, Frame, Location, Meta}; use crate::geom::Transform; @@ -83,8 +83,17 @@ impl<'a> Vt<'a> { } /// Locate all metadata matches for the given selector. - pub fn locate(&self, selector: Selector) -> Vec<(StableId, &Content)> { - self.introspector.locate(selector) + pub fn locate( + &self, + selector: Selector, + ) -> impl Iterator { + self.introspector.locate(selector).into_iter() + } + + /// Locate all metadata matches for the given node. + pub fn locate_node(&self) -> impl Iterator { + self.locate(Selector::node::()) + .map(|(id, content)| (id, content.to::().unwrap())) } } diff --git a/tests/typ/compiler/set.typ b/tests/typ/compiler/set.typ index a4482e6d3..99bb90d0d 100644 --- a/tests/typ/compiler/set.typ +++ b/tests/typ/compiler/set.typ @@ -51,8 +51,8 @@ Hello *#x* --- // Test conditional set. #show ref: it => { - set text(red) if it.label == - "@" + str(it.label) + set text(red) if it.target == + "@" + str(it.target) } @hello from the @unknown