Locatability and synthesis improvements

This commit is contained in:
Laurenz 2023-03-13 21:40:57 +01:00
parent 880b1847bd
commit 724e9b140c
14 changed files with 156 additions and 88 deletions

View File

@ -27,7 +27,7 @@ pub fn read(
let Spanned { v: path, span } = path; let Spanned { v: path, span } = path;
let path = vm.locate(&path).at(span)?; let path = vm.locate(&path).at(span)?;
let data = vm.world().file(&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") .map_err(|_| "file is not valid utf-8")
.at(span)?; .at(span)?;
Value::Str(text.into()) Value::Str(text.into())

View File

@ -23,7 +23,7 @@ use crate::text::TextNode;
/// ///
/// Display: Figure /// Display: Figure
/// Category: meta /// Category: meta
#[node(Synthesize, Show, LocalName)] #[node(Locatable, Synthesize, Show, LocalName)]
pub struct FigureNode { pub struct FigureNode {
/// The content of the figure. Often, an [image]($func/image). /// The content of the figure. Often, an [image]($func/image).
#[required] #[required]
@ -57,26 +57,24 @@ impl FigureNode {
} }
impl Synthesize for FigureNode { impl Synthesize for FigureNode {
fn synthesize(&self, vt: &mut Vt, styles: StyleChain) -> Content { fn synthesize(&mut self, vt: &Vt, styles: StyleChain) {
let my_id = vt.identify(self); let my_id = self.0.stable_id().unwrap();
let element = self.element(); let element = self.element();
let numbering = self.numbering(styles);
let mut number = None; let mut number = None;
let numbering = self.numbering(styles);
if numbering.is_some() { if numbering.is_some() {
number = NonZeroUsize::new( number = NonZeroUsize::new(
1 + vt 1 + vt
.locate(Selector::node::<Self>()) .locate_node::<Self>()
.into_iter()
.take_while(|&(id, _)| id != my_id) .take_while(|&(id, _)| id != my_id)
.filter(|(_, node)| node.to::<Self>().unwrap().element() == element) .filter(|(_, figure)| figure.element() == element)
.count(), .count(),
); );
} }
let node = self.clone().with_number(number).with_numbering(numbering).pack(); self.push_number(number);
let meta = Meta::Node(my_id, node.clone()); self.push_numbering(numbering);
node.styled(MetaNode::set_data(vec![meta]))
} }
} }

View File

@ -40,7 +40,7 @@ use crate::text::{TextNode, TextSize};
/// ///
/// Display: Heading /// Display: Heading
/// Category: meta /// Category: meta
#[node(Synthesize, Show, Finalize, LocalName)] #[node(Locatable, Synthesize, Show, Finalize, LocalName)]
pub struct HeadingNode { pub struct HeadingNode {
/// The logical nesting depth of the heading, starting from one. /// The logical nesting depth of the heading, starting from one.
#[default(NonZeroUsize::new(1).unwrap())] #[default(NonZeroUsize::new(1).unwrap())]
@ -83,19 +83,16 @@ pub struct HeadingNode {
} }
impl Synthesize for HeadingNode { impl Synthesize for HeadingNode {
fn synthesize(&self, vt: &mut Vt, styles: StyleChain) -> Content { fn synthesize(&mut self, vt: &Vt, styles: StyleChain) {
let my_id = vt.identify(self); let my_id = self.0.stable_id().unwrap();
let numbering = self.numbering(styles); let numbering = self.numbering(styles);
let mut counter = HeadingCounter::new(); let mut counter = HeadingCounter::new();
if numbering.is_some() { if numbering.is_some() {
// Advance past existing headings. // Advance past existing headings.
for (_, node) in vt for (_, heading) in
.locate(Selector::node::<Self>()) vt.locate_node::<Self>().take_while(|&(id, _)| id != my_id)
.into_iter()
.take_while(|&(id, _)| id != my_id)
{ {
let heading = node.to::<Self>().unwrap();
if heading.numbering(StyleChain::default()).is_some() { if heading.numbering(StyleChain::default()).is_some() {
counter.advance(heading); counter.advance(heading);
} }
@ -105,16 +102,10 @@ impl Synthesize for HeadingNode {
counter.advance(self); counter.advance(self);
} }
let node = self self.push_level(self.level(styles));
.clone() self.push_outlined(self.outlined(styles));
.with_level(self.level(styles)) self.push_numbers(numbering.is_some().then(|| counter.take()));
.with_outlined(self.outlined(styles)) self.push_numbering(numbering);
.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]))
} }
} }

View File

@ -86,7 +86,7 @@ impl Show for LinkNode {
impl Finalize for LinkNode { impl Finalize for LinkNode {
fn finalize(&self, realized: Content, _: StyleChain) -> Content { fn finalize(&self, realized: Content, _: StyleChain) -> Content {
realized realized
.styled(MetaNode::set_data(vec![Meta::Link(self.dest())])) .linked(self.dest())
.styled(TextNode::set_hyphenate(Hyphenate(Smart::Custom(false)))) .styled(TextNode::set_hyphenate(Hyphenate(Smart::Custom(false))))
} }
} }

View File

@ -74,15 +74,14 @@ pub struct OutlineNode {
} }
impl Synthesize for OutlineNode { impl Synthesize for OutlineNode {
fn synthesize(&self, vt: &mut Vt, _: StyleChain) -> Content { fn synthesize(&mut self, vt: &Vt, _: StyleChain) {
let headings = vt let headings = vt
.locate(Selector::node::<HeadingNode>()) .locate_node::<HeadingNode>()
.into_iter() .map(|(_, node)| node.clone())
.map(|(_, node)| node.to::<HeadingNode>().unwrap().clone())
.filter(|node| node.outlined(StyleChain::default())) .filter(|node| node.outlined(StyleChain::default()))
.collect(); .collect();
self.clone().with_headings(headings).pack() self.push_headings(headings);
} }
} }

View File

@ -37,9 +37,9 @@ use crate::text::TextNode;
/// Category: meta /// Category: meta
#[node(Synthesize, Show)] #[node(Synthesize, Show)]
pub struct RefNode { pub struct RefNode {
/// The label that should be referenced. /// The target label that should be referenced.
#[required] #[required]
pub label: Label, pub target: Label,
/// The prefix before the referenced number. /// The prefix before the referenced number.
/// ///
@ -59,20 +59,19 @@ pub struct RefNode {
/// ``` /// ```
pub prefix: Smart<Option<Func>>, pub prefix: Smart<Option<Func>>,
/// All elements with the `target` label in the document. /// All elements with the target label in the document.
#[synthesized] #[synthesized]
pub matches: Vec<Content>, pub matches: Vec<Content>,
} }
impl Synthesize for RefNode { impl Synthesize for RefNode {
fn synthesize(&self, vt: &mut Vt, _: StyleChain) -> Content { fn synthesize(&mut self, vt: &Vt, _: StyleChain) {
let matches = vt let matches = vt
.locate(Selector::Label(self.label())) .locate(Selector::Label(self.target()))
.into_iter()
.map(|(_, node)| node.clone()) .map(|(_, node)| node.clone())
.collect(); .collect();
self.clone().with_matches(matches).pack() self.push_matches(matches);
} }
} }

View File

@ -22,9 +22,9 @@ pub use typst::eval::{
pub use typst::geom::*; pub use typst::geom::*;
#[doc(no_inline)] #[doc(no_inline)]
pub use typst::model::{ pub use typst::model::{
node, Construct, Content, Finalize, Fold, Introspector, Label, Node, NodeId, Resolve, node, Construct, Content, Finalize, Fold, Introspector, Label, Locatable, Node,
Selector, Set, Show, StabilityProvider, StyleChain, StyleMap, StyleVec, Synthesize, NodeId, Resolve, Selector, Set, Show, StabilityProvider, StableId, StyleChain,
Unlabellable, Vt, StyleMap, StyleVec, Synthesize, Unlabellable, Vt,
}; };
#[doc(no_inline)] #[doc(no_inline)]
pub use typst::syntax::{Span, Spanned}; pub use typst::syntax::{Span, Spanned};

View File

@ -42,7 +42,7 @@ impl ContentExt for Content {
} }
fn linked(self, dest: Destination) -> Self { 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<Option<GenAlign>>) -> Self { fn aligned(self, aligns: Axes<Option<GenAlign>>) -> Self {

View File

@ -121,8 +121,8 @@ impl RawNode {
} }
impl Synthesize for RawNode { impl Synthesize for RawNode {
fn synthesize(&self, _: &mut Vt, styles: StyleChain) -> Content { fn synthesize(&mut self, _: &Vt, styles: StyleChain) {
self.clone().with_lang(self.lang(styles)).pack() self.push_lang(self.lang(styles));
} }
} }

View File

@ -34,6 +34,7 @@ struct Field {
ident: Ident, ident: Ident,
ident_in: Ident, ident_in: Ident,
with_ident: Ident, with_ident: Ident,
push_ident: Ident,
set_ident: Ident, set_ident: Ident,
ty: syn::Type, ty: syn::Type,
output: syn::Type, output: syn::Type,
@ -81,6 +82,10 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> {
let required = has_attr(&mut attrs, "required") || variadic; let required = has_attr(&mut attrs, "required") || variadic;
let positional = has_attr(&mut attrs, "positional") || required; let positional = has_attr(&mut attrs, "positional") || required;
if ident == "label" {
bail!(ident, "invalid field name");
}
let mut field = Field { let mut field = Field {
name: kebab_case(&ident), name: kebab_case(&ident),
docs: documentation(&attrs), docs: documentation(&attrs),
@ -100,6 +105,7 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> {
ident: ident.clone(), ident: ident.clone(),
ident_in: Ident::new(&format!("{}_in", ident), ident.span()), ident_in: Ident::new(&format!("{}_in", ident), ident.span()),
with_ident: Ident::new(&format!("with_{}", 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()), set_ident: Ident::new(&format!("set_{}", ident), ident.span()),
ty: field.ty.clone(), ty: field.ty.clone(),
output: field.ty.clone(), output: field.ty.clone(),
@ -162,17 +168,23 @@ fn create(node: &Node) -> TokenStream {
let new = create_new_func(node); let new = create_new_func(node);
let field_methods = all.clone().map(create_field_method); let field_methods = all.clone().map(create_field_method);
let field_in_methods = settable.clone().map(create_field_in_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); let field_style_methods = settable.map(create_set_field_method);
// Trait implementations. // Trait implementations.
let construct = node let node_impl = create_node_impl(node);
let construct_impl = node
.capable .capable
.iter() .iter()
.all(|capability| capability != "Construct") .all(|capability| capability != "Construct")
.then(|| create_construct_impl(node)); .then(|| create_construct_impl(node));
let set = create_set_impl(node); let set_impl = create_set_impl(node);
let node = create_node_impl(node); let locatable_impl = node
.capable
.iter()
.any(|capability| capability == "Locatable")
.then(|| quote! { impl ::typst::model::Locatable for #ident {} });
quote! { quote! {
#[doc = #docs] #[doc = #docs]
@ -184,7 +196,8 @@ fn create(node: &Node) -> TokenStream {
#new #new
#(#field_methods)* #(#field_methods)*
#(#field_in_methods)* #(#field_in_methods)*
#(#with_fields_methods)* #(#with_field_methods)*
#(#push_field_methods)*
#(#field_style_methods)* #(#field_style_methods)*
/// The node's span. /// The node's span.
@ -193,9 +206,10 @@ fn create(node: &Node) -> TokenStream {
} }
} }
#node #node_impl
#construct #construct_impl
#set #set_impl
#locatable_impl
impl From<#ident> for ::typst::eval::Value { impl From<#ident> for ::typst::eval::Value {
fn from(value: #ident) -> Self { fn from(value: #ident) -> Self {
@ -232,6 +246,7 @@ fn create_field_method(field: &Field) -> TokenStream {
if field.inherent() || field.synthesized { if field.inherent() || field.synthesized {
quote! { quote! {
#[doc = #docs] #[doc = #docs]
#[track_caller]
#vis fn #ident(&self) -> #output { #vis fn #ident(&self) -> #output {
self.0.expect_field(#name) 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. /// Create a setter method for a field.
fn create_set_field_method(field: &Field) -> TokenStream { fn create_set_field_method(field: &Field) -> TokenStream {
let Field { vis, ident, set_ident, name, ty, .. } = field; let Field { vis, ident, set_ident, name, ty, .. } = field;

View File

@ -8,7 +8,7 @@ use comemo::Tracked;
use ecow::{eco_format, EcoString, EcoVec}; use ecow::{eco_format, EcoString, EcoVec};
use once_cell::sync::Lazy; 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::diag::{SourceResult, StrResult};
use crate::eval::{ use crate::eval::{
cast_from_value, cast_to_value, Args, Cast, Func, FuncInfo, Str, Value, Vm, 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. /// Modifiers that can be attached to content.
#[derive(Debug, Clone, PartialEq, Hash)] #[derive(Debug, Clone, PartialEq, Hash)]
enum Modifier { enum Modifier {
Synthesized, Prepared,
Guard(Guard), Guard(Guard),
Id(StableId),
} }
impl Content { impl Content {
@ -101,6 +102,16 @@ impl Content {
Some(unsafe { &*crate::util::fat::from_raw_parts(data, vtable) }) 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<C>(&mut self) -> Option<&mut C>
where
C: ?Sized + 'static,
{
let vtable = (self.id.0.vtable)(TypeId::of::<C>())?;
let data = self as *mut Self as *mut ();
Some(unsafe { &mut *crate::util::fat::from_raw_parts_mut(data, vtable) })
}
/// The node's span. /// The node's span.
pub fn span(&self) -> Span { pub fn span(&self) -> Span {
self.span self.span
@ -233,17 +244,6 @@ impl Content {
self 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. /// Whether no show rule was executed for this node so far.
pub(super) fn is_pristine(&self) -> bool { pub(super) fn is_pristine(&self) -> bool {
!self !self
@ -257,6 +257,37 @@ impl Content {
self.modifiers.contains(&Modifier::Guard(id)) 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::<dyn Locatable>()
|| self.can::<dyn Synthesize>()
|| 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<StableId> {
self.modifiers.iter().find_map(|modifier| match modifier {
Modifier::Id(id) => Some(*id),
_ => None,
})
}
/// Copy the modifiers from another piece of content. /// Copy the modifiers from another piece of content.
pub(super) fn copy_modifiers(&mut self, from: &Content) { pub(super) fn copy_modifiers(&mut self, from: &Content) {
self.span = from.span; self.span = from.span;

View File

@ -1,9 +1,10 @@
use super::{Content, NodeId, Recipe, Selector, StyleChain, Vt}; use super::{Content, NodeId, Recipe, Selector, StyleChain, Vt};
use crate::diag::SourceResult; use crate::diag::SourceResult;
use crate::doc::{Meta, MetaNode};
/// Whether the target is affected by show rules in the given style chain. /// Whether the target is affected by show rules in the given style chain.
pub fn applicable(target: &Content, styles: StyleChain) -> bool { pub fn applicable(target: &Content, styles: StyleChain) -> bool {
if target.can::<dyn Synthesize>() && !target.is_synthesized() { if target.needs_preparation() {
return true; return true;
} }
@ -31,21 +32,31 @@ pub fn realize(
target: &Content, target: &Content,
styles: StyleChain, styles: StyleChain,
) -> SourceResult<Option<Content>> { ) -> SourceResult<Option<Content>> {
// Pre-process.
if target.needs_preparation() {
let mut node = target.clone();
if target.can::<dyn Locatable>() || target.label().is_some() {
let id = vt.identify(target);
node.set_stable_id(id);
}
if let Some(node) = node.with_mut::<dyn Synthesize>() {
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. // Find out how many recipes there are.
let mut n = styles.recipes().count(); let mut n = styles.recipes().count();
// Synthesize if not already happened for this node.
if target.can::<dyn Synthesize>() && !target.is_synthesized() {
return Ok(Some(
target
.clone()
.synthesized()
.with::<dyn Synthesize>()
.unwrap()
.synthesize(vt, styles),
));
}
// Find an applicable recipe. // Find an applicable recipe.
let mut realized = None; let mut realized = None;
for recipe in styles.recipes() { 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. /// Synthesize fields on a node. This happens before execution of any show rule.
pub trait Synthesize { pub trait Synthesize {
/// Prepare the node for show rule application. /// 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. /// The base recipe for a node.

View File

@ -5,7 +5,7 @@ use std::num::NonZeroUsize;
use comemo::{Track, Tracked, TrackedMut}; use comemo::{Track, Tracked, TrackedMut};
use super::{Content, Selector, StyleChain}; use super::{Content, Node, Selector, StyleChain};
use crate::diag::SourceResult; use crate::diag::SourceResult;
use crate::doc::{Document, Element, Frame, Location, Meta}; use crate::doc::{Document, Element, Frame, Location, Meta};
use crate::geom::Transform; use crate::geom::Transform;
@ -83,8 +83,17 @@ impl<'a> Vt<'a> {
} }
/// Locate all metadata matches for the given selector. /// Locate all metadata matches for the given selector.
pub fn locate(&self, selector: Selector) -> Vec<(StableId, &Content)> { pub fn locate(
self.introspector.locate(selector) &self,
selector: Selector,
) -> impl Iterator<Item = (StableId, &Content)> {
self.introspector.locate(selector).into_iter()
}
/// Locate all metadata matches for the given node.
pub fn locate_node<T: Node>(&self) -> impl Iterator<Item = (StableId, &T)> {
self.locate(Selector::node::<T>())
.map(|(id, content)| (id, content.to::<T>().unwrap()))
} }
} }

View File

@ -51,8 +51,8 @@ Hello *#x*
--- ---
// Test conditional set. // Test conditional set.
#show ref: it => { #show ref: it => {
set text(red) if it.label == <unknown> set text(red) if it.target == <unknown>
"@" + str(it.label) "@" + str(it.target)
} }
@hello from the @unknown @hello from the @unknown