summaryrefslogtreecommitdiff
path: root/src/model
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-05-03 15:58:15 +0200
committerLaurenz <laurmaedje@gmail.com>2022-05-03 15:58:15 +0200
commitd59109e8fffa1d0b03234329e12f5d3e578804e8 (patch)
treefe7453da6f2ae327993e5ca6436ddc6a448a2c41 /src/model
parentf77f1f61bf05ae506689be3c40252c5807276280 (diff)
Support recursive show rules
Diffstat (limited to 'src/model')
-rw-r--r--src/model/content.rs61
-rw-r--r--src/model/recipe.rs92
-rw-r--r--src/model/show.rs9
-rw-r--r--src/model/styles.rs206
4 files changed, 260 insertions, 108 deletions
diff --git a/src/model/content.rs b/src/model/content.rs
index 31255a29..70205acc 100644
--- a/src/model/content.rs
+++ b/src/model/content.rs
@@ -7,8 +7,8 @@ use std::ops::{Add, AddAssign};
use typed_arena::Arena;
use super::{
- CollapsingBuilder, Interruption, Key, Layout, LayoutNode, Show, ShowNode, StyleMap,
- StyleVecBuilder,
+ CollapsingBuilder, Interruption, Key, Layout, LayoutNode, Property, Show, ShowNode,
+ StyleEntry, StyleMap, StyleVecBuilder, Target,
};
use crate::diag::StrResult;
use crate::library::layout::{FlowChild, FlowNode, PageNode, PlaceNode, Spacing};
@@ -38,6 +38,8 @@ use crate::util::EcoString;
/// sequences.
#[derive(PartialEq, Clone, Hash)]
pub enum Content {
+ /// Empty content.
+ Empty,
/// A word space.
Space,
/// A forced line break.
@@ -68,8 +70,9 @@ pub enum Content {
Pagebreak { weak: bool },
/// A page node.
Page(PageNode),
- /// A node that can be realized with styles.
- Show(ShowNode),
+ /// A node that can be realized with styles, optionally with attached
+ /// properties.
+ Show(ShowNode, Option<Dict>),
/// Content with attached styles.
Styled(Arc<(Self, StyleMap)>),
/// A sequence of multiple nodes.
@@ -79,7 +82,7 @@ pub enum Content {
impl Content {
/// Create empty content.
pub fn new() -> Self {
- Self::sequence(vec![])
+ Self::Empty
}
/// Create content from an inline-level node.
@@ -103,15 +106,15 @@ impl Content {
where
T: Show + Debug + Hash + Sync + Send + 'static,
{
- Self::Show(node.pack())
+ Self::Show(node.pack(), None)
}
/// Create a new sequence nodes from multiples nodes.
pub fn sequence(seq: Vec<Self>) -> Self {
- if seq.len() == 1 {
- seq.into_iter().next().unwrap()
- } else {
- Self::Sequence(Arc::new(seq))
+ match seq.as_slice() {
+ [] => Self::Empty,
+ [_] => seq.into_iter().next().unwrap(),
+ _ => Self::Sequence(Arc::new(seq)),
}
}
@@ -124,15 +127,20 @@ impl Content {
}
/// Style this content with a single style property.
- pub fn styled<'k, K: Key<'k>>(mut self, key: K, value: K::Value) -> Self {
+ pub fn styled<'k, K: Key<'k>>(self, key: K, value: K::Value) -> Self {
+ self.styled_with_entry(StyleEntry::Property(Property::new(key, value)))
+ }
+
+ /// Style this content with a style entry.
+ pub fn styled_with_entry(mut self, entry: StyleEntry) -> Self {
if let Self::Styled(styled) = &mut self {
if let Some((_, map)) = Arc::get_mut(styled) {
- map.apply(key, value);
+ map.apply(entry);
return self;
}
}
- Self::Styled(Arc::new((self, StyleMap::with(key, value))))
+ Self::Styled(Arc::new((self, entry.into())))
}
/// Style this content with a full style map.
@@ -151,6 +159,11 @@ impl Content {
Self::Styled(Arc::new((self, styles)))
}
+ /// Reenable the show rule identified by the selector.
+ pub fn unguard(&self, sel: Selector) -> Self {
+ self.clone().styled_with_entry(StyleEntry::Unguard(sel))
+ }
+
/// Underline this content.
pub fn underlined(self) -> Self {
Self::show(DecoNode::<UNDERLINE>(self))
@@ -228,6 +241,7 @@ impl Default for Content {
impl Debug for Content {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
+ Self::Empty => f.pad("Empty"),
Self::Space => f.pad("Space"),
Self::Linebreak { justified } => write!(f, "Linebreak({justified})"),
Self::Horizontal { amount, weak } => {
@@ -245,7 +259,7 @@ impl Debug for Content {
Self::Item(item) => item.fmt(f),
Self::Pagebreak { weak } => write!(f, "Pagebreak({weak})"),
Self::Page(page) => page.fmt(f),
- Self::Show(node) => node.fmt(f),
+ Self::Show(node, _) => node.fmt(f),
Self::Styled(styled) => {
let (sub, map) = styled.as_ref();
map.fmt(f)?;
@@ -261,6 +275,8 @@ impl Add for Content {
fn add(self, rhs: Self) -> Self::Output {
Self::Sequence(match (self, rhs) {
+ (Self::Empty, rhs) => return rhs,
+ (lhs, Self::Empty) => return lhs,
(Self::Sequence(mut lhs), Self::Sequence(rhs)) => {
let mutable = Arc::make_mut(&mut lhs);
match Arc::try_unwrap(rhs) {
@@ -352,7 +368,8 @@ impl<'a, 'ctx> Builder<'a, 'ctx> {
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> TypResult<()> {
// Handle special content kinds.
match content {
- Content::Show(node) => return self.show(node, styles),
+ Content::Empty => return Ok(()),
+ Content::Show(node, _) => return self.show(node, styles),
Content::Styled(styled) => return self.styled(styled, styles),
Content::Sequence(seq) => return self.sequence(seq, styles),
_ => {}
@@ -388,15 +405,11 @@ impl<'a, 'ctx> Builder<'a, 'ctx> {
}
fn show(&mut self, node: &ShowNode, styles: StyleChain<'a>) -> TypResult<()> {
- let id = node.id();
- let realized = match styles.realize(self.ctx, node)? {
- Some(content) => content,
- None => node.realize(self.ctx, styles)?,
- };
-
- let content = node.finalize(self.ctx, styles, realized)?;
- let stored = self.scratch.templates.alloc(content);
- self.accept(stored, styles.unscoped(id))
+ if let Some(realized) = styles.apply(self.ctx, Target::Node(node))? {
+ let stored = self.scratch.templates.alloc(realized);
+ self.accept(stored, styles.unscoped(node.id()))?;
+ }
+ Ok(())
}
fn styled(
diff --git a/src/model/recipe.rs b/src/model/recipe.rs
index 2f5f7c09..f6adf4a5 100644
--- a/src/model/recipe.rs
+++ b/src/model/recipe.rs
@@ -1,14 +1,17 @@
use std::fmt::{self, Debug, Formatter};
-use super::NodeId;
-use crate::eval::{Func, Node};
+use super::{Content, Interruption, NodeId, Show, ShowNode, StyleEntry};
+use crate::diag::{At, TypResult};
+use crate::eval::{Args, Func, Value};
+use crate::library::structure::{EnumNode, ListNode};
use crate::syntax::Span;
+use crate::Context;
/// A show rule recipe.
#[derive(Clone, PartialEq, Hash)]
pub struct Recipe {
- /// The affected node.
- pub node: NodeId,
+ /// The patterns to customize.
+ pub pattern: Pattern,
/// The function that defines the recipe.
pub func: Func,
/// The span to report all erros with.
@@ -16,14 +19,87 @@ pub struct Recipe {
}
impl Recipe {
- /// Create a new recipe for the node `T`.
- pub fn new<T: Node>(func: Func, span: Span) -> Self {
- Self { node: NodeId::of::<T>(), func, span }
+ /// Whether the recipe is applicable to the target.
+ pub fn applicable(&self, target: Target) -> bool {
+ match (&self.pattern, target) {
+ (Pattern::Node(id), Target::Node(node)) => *id == node.id(),
+ _ => false,
+ }
+ }
+
+ /// Try to apply the recipe to the target.
+ pub fn apply(
+ &self,
+ ctx: &mut Context,
+ sel: Selector,
+ target: Target,
+ ) -> TypResult<Option<Content>> {
+ let content = match (target, &self.pattern) {
+ (Target::Node(node), &Pattern::Node(id)) if node.id() == id => {
+ let node = node.unguard(sel);
+ self.call(ctx, || {
+ let dict = node.encode();
+ Value::Content(Content::Show(node, Some(dict)))
+ })?
+ }
+
+ _ => return Ok(None),
+ };
+
+ Ok(Some(content.styled_with_entry(StyleEntry::Guard(sel))))
+ }
+
+ /// Call the recipe function, with the argument if desired.
+ fn call<F>(&self, ctx: &mut Context, arg: F) -> TypResult<Content>
+ where
+ F: FnOnce() -> Value,
+ {
+ let args = if self.func.argc() == Some(0) {
+ Args::new(self.span)
+ } else {
+ Args::from_values(self.span, [arg()])
+ };
+
+ self.func.call(ctx, args)?.cast().at(self.span)
+ }
+
+ /// What kind of structure the property interrupts.
+ pub fn interruption(&self) -> Option<Interruption> {
+ if let Pattern::Node(id) = self.pattern {
+ if id == NodeId::of::<ListNode>() || id == NodeId::of::<EnumNode>() {
+ return Some(Interruption::List);
+ }
+ }
+
+ None
}
}
impl Debug for Recipe {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- write!(f, "Recipe for {:?} from {:?}", self.node, self.span)
+ write!(f, "Recipe matching {:?} from {:?}", self.pattern, self.span)
}
}
+
+/// A show rule pattern that may match a target.
+#[derive(Debug, Clone, PartialEq, Hash)]
+pub enum Pattern {
+ /// Defines the appearence of some node.
+ Node(NodeId),
+}
+
+/// A target for a show rule recipe.
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub enum Target<'a> {
+ /// A showable node.
+ Node(&'a ShowNode),
+}
+
+/// Identifies a show rule recipe.
+#[derive(Debug, Copy, Clone, PartialEq, Hash)]
+pub enum Selector {
+ /// The nth recipe from the top of the chain.
+ Nth(usize),
+ /// The base recipe for a kind of node.
+ Base(NodeId),
+}
diff --git a/src/model/show.rs b/src/model/show.rs
index 16374deb..af87d930 100644
--- a/src/model/show.rs
+++ b/src/model/show.rs
@@ -2,7 +2,7 @@ use std::fmt::{self, Debug, Formatter, Write};
use std::hash::Hash;
use std::sync::Arc;
-use super::{Content, NodeId, StyleChain};
+use super::{Content, NodeId, Selector, StyleChain};
use crate::diag::TypResult;
use crate::eval::Dict;
use crate::util::Prehashed;
@@ -10,6 +10,9 @@ use crate::Context;
/// A node that can be realized given some styles.
pub trait Show: 'static {
+ /// Unguard nested content against recursive show rules.
+ fn unguard(&self, sel: Selector) -> ShowNode;
+
/// Encode this node into a dictionary.
fn encode(&self) -> Dict;
@@ -63,6 +66,10 @@ impl ShowNode {
}
impl Show for ShowNode {
+ fn unguard(&self, sel: Selector) -> ShowNode {
+ self.0.unguard(sel)
+ }
+
fn encode(&self) -> Dict {
self.0.encode()
}
diff --git a/src/model/styles.rs b/src/model/styles.rs
index b3020c88..3c25971c 100644
--- a/src/model/styles.rs
+++ b/src/model/styles.rs
@@ -3,11 +3,9 @@ use std::hash::Hash;
use std::iter;
use std::marker::PhantomData;
-use super::{Barrier, Content, Key, Property, Recipe, Show, ShowNode};
-use crate::diag::{At, TypResult};
-use crate::eval::{Args, Func, Node, Value};
+use super::{Barrier, Content, Key, Property, Recipe, Selector, Show, Target};
+use crate::diag::TypResult;
use crate::library::text::{FontFamily, TextNode};
-use crate::syntax::Span;
use crate::util::ReadableTypeId;
use crate::Context;
@@ -65,11 +63,6 @@ impl StyleMap {
);
}
- /// Set a show rule recipe for a node.
- pub fn set_recipe<T: Node>(&mut self, func: Func, span: Span) {
- self.push(StyleEntry::Recipe(Recipe::new::<T>(func, span)));
- }
-
/// Whether the map contains a style property for the given key.
pub fn contains<'a, K: Key<'a>>(&self, _: K) -> bool {
self.0
@@ -91,16 +84,12 @@ impl StyleMap {
}
}
- /// Set an outer value for a style property.
- ///
- /// If the property needs folding and the value is already contained in the
- /// style map, `self` contributes the inner values and `value` is the outer
- /// one.
+ /// Set an outer style property.
///
/// Like [`chain`](Self::chain) or [`apply_map`](Self::apply_map), but with
- /// only a single property.
- pub fn apply<'a, K: Key<'a>>(&mut self, key: K, value: K::Value) {
- self.0.insert(0, StyleEntry::Property(Property::new(key, value)));
+ /// only a entry.
+ pub fn apply(&mut self, entry: StyleEntry) {
+ self.0.insert(0, entry);
}
/// Apply styles from `tail` in-place. The resulting style map is equivalent
@@ -126,11 +115,7 @@ impl StyleMap {
/// The highest-level kind of of structure the map interrupts.
pub fn interruption(&self) -> Option<Interruption> {
- self.0
- .iter()
- .filter_map(|entry| entry.property())
- .filter_map(|property| property.interruption())
- .max()
+ self.0.iter().filter_map(|entry| entry.interruption()).max()
}
}
@@ -182,13 +167,35 @@ pub enum Interruption {
pub enum StyleEntry {
/// A style property originating from a set rule or constructor.
Property(Property),
- /// A barrier for scoped styles.
- Barrier(Barrier),
/// A show rule recipe.
Recipe(Recipe),
+ /// A barrier for scoped styles.
+ Barrier(Barrier),
+ /// Guards against recursive show rules.
+ Guard(Selector),
+ /// Allows recursive show rules again.
+ Unguard(Selector),
}
impl StyleEntry {
+ /// Make this style the first link of the `tail` chain.
+ pub fn chain<'a>(&'a self, tail: &'a StyleChain) -> StyleChain<'a> {
+ if let StyleEntry::Barrier(barrier) = self {
+ if !tail
+ .entries()
+ .filter_map(StyleEntry::property)
+ .any(|p| p.scoped && barrier.is_for(p.node))
+ {
+ return *tail;
+ }
+ }
+
+ StyleChain {
+ head: std::slice::from_ref(self),
+ tail: Some(tail),
+ }
+ }
+
/// If this is a property, return it.
pub fn property(&self) -> Option<&Property> {
match self {
@@ -205,21 +212,12 @@ impl StyleEntry {
}
}
- /// Make this style the first link of the `tail` chain.
- pub fn chain<'a>(&'a self, tail: &'a StyleChain) -> StyleChain<'a> {
- if let StyleEntry::Barrier(barrier) = self {
- if !tail
- .entries()
- .filter_map(StyleEntry::property)
- .any(|p| p.scoped && barrier.is_for(p.node))
- {
- return *tail;
- }
- }
-
- StyleChain {
- head: std::slice::from_ref(self),
- tail: Some(tail),
+ /// The highest-level kind of of structure the entry interrupts.
+ pub fn interruption(&self) -> Option<Interruption> {
+ match self {
+ Self::Property(property) => property.interruption(),
+ Self::Recipe(recipe) => recipe.interruption(),
+ _ => None,
}
}
}
@@ -231,6 +229,8 @@ impl Debug for StyleEntry {
Self::Property(property) => property.fmt(f)?,
Self::Recipe(recipe) => recipe.fmt(f)?,
Self::Barrier(barrier) => barrier.fmt(f)?,
+ Self::Guard(sel) => write!(f, "Guard against {sel:?}")?,
+ Self::Unguard(sel) => write!(f, "Unguard against {sel:?}")?,
}
f.write_str("]")
}
@@ -262,35 +262,6 @@ impl<'a> StyleChain<'a> {
Self { head: &root.0, tail: None }
}
- /// Get the output value of a style property.
- ///
- /// Returns the property's default value if no map in the chain contains an
- /// entry for it. Also takes care of resolving and folding and returns
- /// references where applicable.
- pub fn get<K: Key<'a>>(self, key: K) -> K::Output {
- K::get(self, self.values(key))
- }
-
- /// Realize a node with a user recipe.
- pub fn realize(
- self,
- ctx: &mut Context,
- node: &ShowNode,
- ) -> TypResult<Option<Content>> {
- let id = node.id();
- if let Some(recipe) = self
- .entries()
- .filter_map(StyleEntry::recipe)
- .find(|recipe| recipe.node == id)
- {
- let dict = node.encode();
- let args = Args::from_values(recipe.span, [Value::Dict(dict)]);
- Ok(Some(recipe.func.call(ctx, args)?.cast().at(recipe.span)?))
- } else {
- Ok(None)
- }
- }
-
/// Return the chain, but without trailing scoped properties for the given
/// `node`.
pub fn unscoped(mut self, node: NodeId) -> Self {
@@ -306,6 +277,80 @@ impl<'a> StyleChain<'a> {
self
}
+ /// Get the output value of a style property.
+ ///
+ /// Returns the property's default value if no map in the chain contains an
+ /// entry for it. Also takes care of resolving and folding and returns
+ /// references where applicable.
+ pub fn get<K: Key<'a>>(self, key: K) -> K::Output {
+ K::get(self, self.values(key))
+ }
+
+ /// Apply show recipes in this style chain to a target.
+ pub fn apply(self, ctx: &mut Context, target: Target) -> TypResult<Option<Content>> {
+ // Find out how many recipes there any and whether any of their patterns
+ // match.
+ let mut n = 0;
+ let mut any = true;
+ for recipe in self.entries().filter_map(StyleEntry::recipe) {
+ n += 1;
+ any |= recipe.applicable(target);
+ }
+
+ // Find an applicable recipe.
+ let mut realized = None;
+ let mut guarded = false;
+ if any {
+ for recipe in self.entries().filter_map(StyleEntry::recipe) {
+ if recipe.applicable(target) {
+ let sel = Selector::Nth(n);
+ if self.guarded(sel) {
+ guarded = true;
+ } else if let Some(content) = recipe.apply(ctx, sel, target)? {
+ realized = Some(content);
+ break;
+ }
+ }
+ n -= 1;
+ }
+ }
+
+ if let Target::Node(node) = target {
+ // Realize if there was no matching recipe.
+ if realized.is_none() {
+ let sel = Selector::Base(node.id());
+ if self.guarded(sel) {
+ guarded = true;
+ } else {
+ let content = node.unguard(sel).realize(ctx, self)?;
+ realized = Some(content.styled_with_entry(StyleEntry::Guard(sel)));
+ }
+ }
+
+ // Finalize only if guarding didn't stop any recipe.
+ if !guarded {
+ if let Some(content) = realized {
+ realized = Some(node.finalize(ctx, self, content)?);
+ }
+ }
+ }
+
+ Ok(realized)
+ }
+
+ /// Whether the recipe identified by the selector is guarded.
+ fn guarded(&self, sel: Selector) -> bool {
+ for entry in self.entries() {
+ match *entry {
+ StyleEntry::Guard(s) if s == sel => return true,
+ StyleEntry::Unguard(s) if s == sel => return false,
+ _ => {}
+ }
+ }
+
+ false
+ }
+
/// Remove the last link from the chain.
fn pop(&mut self) {
*self = self.tail.copied().unwrap_or_default();
@@ -386,7 +431,7 @@ impl<'a, K: Key<'a>> Iterator for Values<'a, K> {
StyleEntry::Barrier(barrier) => {
self.depth += barrier.is_for(K::node()) as usize;
}
- StyleEntry::Recipe(_) => {}
+ _ => {}
}
}
@@ -459,20 +504,31 @@ impl<T> StyleVec<T> {
}
}
+ /// Map the contained items.
+ pub fn map<F, U>(&self, f: F) -> StyleVec<U>
+ where
+ F: FnMut(&T) -> U,
+ {
+ StyleVec {
+ items: self.items.iter().map(f).collect(),
+ maps: self.maps.clone(),
+ }
+ }
+
+ /// Iterate over the contained items.
+ pub fn items(&self) -> std::slice::Iter<'_, T> {
+ self.items.iter()
+ }
+
/// Iterate over the contained maps. Note that zipping this with `items()`
/// does not yield the same result as calling `iter()` because this method
/// only returns maps once that are shared by consecutive items. This method
/// is designed for use cases where you want to check, for example, whether
/// any of the maps fulfills a specific property.
- pub fn maps(&self) -> impl Iterator<Item = &StyleMap> {
+ pub fn styles(&self) -> impl Iterator<Item = &StyleMap> {
self.maps.iter().map(|(map, _)| map)
}
- /// Iterate over the contained items.
- pub fn items(&self) -> std::slice::Iter<'_, T> {
- self.items.iter()
- }
-
/// Iterate over references to the contained items and associated style maps.
pub fn iter(&self) -> impl Iterator<Item = (&T, &StyleMap)> + '_ {
self.items().zip(