Support recursive show rules

This commit is contained in:
Laurenz 2022-05-03 15:58:15 +02:00
parent f77f1f61bf
commit d59109e8ff
25 changed files with 436 additions and 185 deletions

View File

@ -26,19 +26,22 @@ pub struct Arg {
}
impl Args {
/// Create empty arguments from a span.
pub fn new(span: Span) -> Self {
Self { span, items: vec![] }
}
/// Create positional arguments from a span and values.
pub fn from_values(span: Span, values: impl IntoIterator<Item = Value>) -> Self {
Self {
span,
items: values
.into_iter()
.map(|value| Arg {
span,
name: None,
value: Spanned::new(value, span),
})
.collect(),
}
let items = values
.into_iter()
.map(|value| Arg {
span,
name: None,
value: Spanned::new(value, span),
})
.collect();
Self { span, items }
}
/// Consume and cast the first positional argument.

View File

@ -91,8 +91,10 @@ impl<'a> CapturesVisitor<'a> {
// A show rule contains a binding, but that binding is only active
// after the target has been evaluated.
Some(Expr::Show(show)) => {
self.visit(show.target().as_red());
self.bind(show.binding());
self.visit(show.pattern().as_red());
if let Some(binding) = show.binding() {
self.bind(binding);
}
self.visit(show.body().as_red());
}

View File

@ -46,8 +46,8 @@ impl Dict {
}
/// Borrow the value the given `key` maps to.
pub fn get(&self, key: EcoString) -> StrResult<&Value> {
self.0.get(&key).ok_or_else(|| missing_key(&key))
pub fn get(&self, key: &EcoString) -> StrResult<&Value> {
self.0.get(key).ok_or_else(|| missing_key(key))
}
/// Mutably borrow the value the given `key` maps to.
@ -59,7 +59,7 @@ impl Dict {
}
/// Whether the dictionary contains a specific key.
pub fn contains(&self, key: &str) -> bool {
pub fn contains(&self, key: &EcoString) -> bool {
self.0.contains_key(key)
}
@ -69,10 +69,10 @@ impl Dict {
}
/// Remove a mapping by `key`.
pub fn remove(&mut self, key: EcoString) -> StrResult<()> {
match Arc::make_mut(&mut self.0).remove(&key) {
pub fn remove(&mut self, key: &EcoString) -> StrResult<()> {
match Arc::make_mut(&mut self.0).remove(key) {
Some(_) => Ok(()),
None => Err(missing_key(&key)),
None => Err(missing_key(key)),
}
}

View File

@ -4,9 +4,8 @@ use std::sync::Arc;
use super::{Args, Control, Eval, Scope, Scopes, Value};
use crate::diag::{StrResult, TypResult};
use crate::model::{Content, StyleMap};
use crate::model::{Content, NodeId, StyleMap};
use crate::syntax::ast::Expr;
use crate::syntax::Span;
use crate::util::EcoString;
use crate::Context;
@ -35,7 +34,7 @@ impl Func {
name,
func,
set: None,
show: None,
node: None,
})))
}
@ -49,15 +48,7 @@ impl Func {
Ok(Value::Content(content.styled_with_map(styles.scoped())))
},
set: Some(T::set),
show: if T::SHOWABLE {
Some(|recipe, span| {
let mut styles = StyleMap::new();
styles.set_recipe::<T>(recipe, span);
styles
})
} else {
None
},
node: T::SHOWABLE.then(|| NodeId::of::<T>()),
})))
}
@ -80,7 +71,20 @@ impl Func {
}
}
/// Call the function with a virtual machine and arguments.
/// 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::With(wrapped, applied) => Some(wrapped.argc()?.saturating_sub(
applied.items.iter().filter(|arg| arg.name.is_none()).count(),
)),
_ => None,
}
}
/// Call the function with the given arguments.
pub fn call(&self, ctx: &mut Context, mut args: Args) -> TypResult<Value> {
let value = match self.0.as_ref() {
Repr::Native(native) => (native.func)(ctx, &mut args)?,
@ -104,10 +108,10 @@ impl Func {
Ok(styles)
}
/// Execute the function's show rule.
pub fn show(&self, recipe: Func, span: Span) -> StrResult<StyleMap> {
/// The id of the node to customize with this function's show rule.
pub fn node(&self) -> StrResult<NodeId> {
match self.0.as_ref() {
Repr::Native(Native { show: Some(show), .. }) => Ok(show(recipe, span)),
Repr::Native(Native { node: Some(id), .. }) => Ok(*id),
_ => Err("this function cannot be customized with show")?,
}
}
@ -138,8 +142,8 @@ struct Native {
pub func: fn(&mut Context, &mut Args) -> TypResult<Value>,
/// The set rule.
pub set: Option<fn(&mut Args) -> TypResult<StyleMap>>,
/// The show rule.
pub show: Option<fn(Func, Span) -> StyleMap>,
/// The id of the node to customize with this function's show rule.
pub node: Option<NodeId>,
}
impl Hash for Native {
@ -147,7 +151,7 @@ impl Hash for Native {
self.name.hash(state);
(self.func as usize).hash(state);
self.set.map(|set| set as usize).hash(state);
self.show.map(|show| show as usize).hash(state);
self.node.hash(state);
}
}

View File

@ -96,7 +96,7 @@ pub fn call_mut(
},
Value::Dict(dict) => match method {
"remove" => dict.remove(args.expect("key")?).at(span)?,
"remove" => dict.remove(&args.expect("key")?).at(span)?,
_ => missing()?,
},

View File

@ -38,7 +38,7 @@ use unicode_segmentation::UnicodeSegmentation;
use crate::diag::{At, StrResult, Trace, Tracepoint, TypResult};
use crate::geom::{Angle, Em, Fraction, Length, Ratio};
use crate::library;
use crate::model::{Content, StyleMap};
use crate::model::{Content, Pattern, Recipe, StyleEntry, StyleMap};
use crate::syntax::ast::*;
use crate::syntax::{Span, Spanned};
use crate::util::EcoString;
@ -79,8 +79,9 @@ fn eval_markup(
eval_markup(ctx, scp, nodes)?.styled_with_map(styles)
}
MarkupNode::Expr(Expr::Show(show)) => {
let styles = show.eval(ctx, scp)?;
eval_markup(ctx, scp, nodes)?.styled_with_map(styles)
let recipe = show.eval(ctx, scp)?;
eval_markup(ctx, scp, nodes)?
.styled_with_entry(StyleEntry::Recipe(recipe).into())
}
MarkupNode::Expr(Expr::Wrap(wrap)) => {
let tail = eval_markup(ctx, scp, nodes)?;
@ -434,8 +435,17 @@ impl Eval for FieldAccess {
fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> {
let object = self.object().eval(ctx, scp)?;
let span = self.field().span();
let field = self.field().take();
Ok(match object {
Value::Dict(dict) => dict.get(self.field().take()).at(self.span())?.clone(),
Value::Dict(dict) => dict.get(&field).at(span)?.clone(),
Value::Content(Content::Show(_, Some(dict))) => dict
.get(&field)
.map_err(|_| format!("unknown field {field:?}"))
.at(span)?
.clone(),
v => bail!(
self.object().span(),
@ -455,7 +465,7 @@ impl Eval for FuncCall {
Ok(match callee {
Value::Array(array) => array.get(args.into_index()?).at(self.span())?.clone(),
Value::Dict(dict) => dict.get(args.into_key()?).at(self.span())?.clone(),
Value::Dict(dict) => dict.get(&args.into_key()?).at(self.span())?.clone(),
Value::Func(func) => {
let point = || Tracepoint::Call(func.name().map(ToString::to_string));
func.call(ctx, args).trace(point, self.span())?
@ -615,13 +625,12 @@ impl Eval for SetExpr {
}
impl Eval for ShowExpr {
type Output = StyleMap;
type Output = Recipe;
fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> {
// Evaluate the target function.
let target = self.target();
let target_span = target.span();
let target = target.eval(ctx, scp)?.cast::<Func>().at(target_span)?;
let pattern = self.pattern();
let pattern = pattern.eval(ctx, scp)?.cast::<Pattern>().at(pattern.span())?;
// Collect captured variables.
let captured = {
@ -630,18 +639,24 @@ impl Eval for ShowExpr {
visitor.finish()
};
// Define parameters.
let mut params = vec![];
if let Some(binding) = self.binding() {
params.push((binding.take(), None));
}
// Define the recipe function.
let body = self.body();
let body_span = body.span();
let recipe = Func::from_closure(Closure {
let span = body.span();
let func = Func::from_closure(Closure {
name: None,
captured,
params: vec![(self.binding().take(), None)],
params,
sink: None,
body,
});
Ok(target.show(recipe, body_span).at(target_span)?)
Ok(Recipe { pattern, func, span })
}
}

View File

@ -11,7 +11,7 @@ use crate::geom::{
Angle, Color, Dir, Em, Fraction, Length, Paint, Ratio, Relative, RgbaColor, Sides,
};
use crate::library::text::RawNode;
use crate::model::{Content, Layout, LayoutNode};
use crate::model::{Content, Layout, LayoutNode, Pattern};
use crate::syntax::Spanned;
use crate::util::EcoString;
@ -617,13 +617,13 @@ where
}
let sides = Sides {
left: dict.get("left".into()).or_else(|_| dict.get("x".into())),
top: dict.get("top".into()).or_else(|_| dict.get("y".into())),
right: dict.get("right".into()).or_else(|_| dict.get("x".into())),
bottom: dict.get("bottom".into()).or_else(|_| dict.get("y".into())),
left: dict.get(&"left".into()).or_else(|_| dict.get(&"x".into())),
top: dict.get(&"top".into()).or_else(|_| dict.get(&"y".into())),
right: dict.get(&"right".into()).or_else(|_| dict.get(&"x".into())),
bottom: dict.get(&"bottom".into()).or_else(|_| dict.get(&"y".into())),
}
.map(|side| {
side.or_else(|_| dict.get("rest".into()))
side.or_else(|_| dict.get(&"rest".into()))
.and_then(|v| T::cast(v.clone()))
.unwrap_or_default()
});
@ -684,6 +684,12 @@ castable! {
Value::Content(content) => content.pack(),
}
castable! {
Pattern,
Expected: "function",
Value::Func(func) => Pattern::Node(func.node()?),
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -52,7 +52,11 @@ impl<T: Numeric> Relative<T> {
impl<T: Numeric> Debug for Relative<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{:?} + {:?}", self.rel, self.abs)
match (self.rel.is_zero(), self.abs.is_zero()) {
(false, false) => write!(f, "{:?} + {:?}", self.rel, self.abs),
(false, true) => self.rel.fmt(f),
(true, _) => self.abs.fmt(f),
}
}
}

View File

@ -36,6 +36,10 @@ impl MathNode {
}
impl Show for MathNode {
fn unguard(&self, _: Selector) -> ShowNode {
Self { formula: self.formula.clone(), ..*self }.pack()
}
fn encode(&self) -> Dict {
dict! {
"formula" => Value::Str(self.formula.clone()),

View File

@ -15,8 +15,8 @@ pub use crate::eval::{
pub use crate::frame::*;
pub use crate::geom::*;
pub use crate::model::{
Content, Fold, Key, Layout, LayoutNode, Regions, Resolve, Show, ShowNode, StyleChain,
StyleMap, StyleVec,
Content, Fold, Key, Layout, LayoutNode, Regions, Resolve, Selector, Show, ShowNode,
StyleChain, StyleMap, StyleVec,
};
pub use crate::syntax::{Span, Spanned};
pub use crate::util::{EcoString, OptionExt};

View File

@ -64,6 +64,10 @@ impl HeadingNode {
}
impl Show for HeadingNode {
fn unguard(&self, sel: Selector) -> ShowNode {
Self { body: self.body.unguard(sel), ..*self }.pack()
}
fn encode(&self) -> Dict {
dict! {
"level" => Value::Int(self.level.get() as i64),

View File

@ -75,6 +75,17 @@ impl<const L: ListKind> ListNode<L> {
}
impl<const L: ListKind> Show for ListNode<L> {
fn unguard(&self, sel: Selector) -> ShowNode {
Self {
items: self.items.map(|item| ListItem {
body: Box::new(item.body.unguard(sel)),
..*item
}),
..*self
}
.pack()
}
fn encode(&self) -> Dict {
dict! {
"start" => Value::Int(self.start as i64),
@ -83,7 +94,7 @@ impl<const L: ListKind> Show for ListNode<L> {
"items" => Value::Array(
self.items
.items()
.map(|item| Value::Content((*item.body).clone()))
.map(|item| Value::Content(item.body.as_ref().clone()))
.collect()
),
}

View File

@ -51,6 +51,15 @@ impl TableNode {
}
impl Show for TableNode {
fn unguard(&self, sel: Selector) -> ShowNode {
Self {
tracks: self.tracks.clone(),
gutter: self.gutter.clone(),
cells: self.cells.iter().map(|cell| cell.unguard(sel)).collect(),
}
.pack()
}
fn encode(&self) -> Dict {
dict! {
"cells" => Value::Array(

View File

@ -36,11 +36,15 @@ impl<const L: DecoLine> DecoNode<L> {
pub const EVADE: bool = true;
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
Ok(Content::show(Self(args.expect::<Content>("body")?)))
Ok(Content::show(Self(args.expect("body")?)))
}
}
impl<const L: DecoLine> Show for DecoNode<L> {
fn unguard(&self, sel: Selector) -> ShowNode {
Self(self.0.unguard(sel)).pack()
}
fn encode(&self) -> Dict {
dict! { "body" => Value::Content(self.0.clone()) }
}

View File

@ -28,6 +28,14 @@ impl LinkNode {
}
impl Show for LinkNode {
fn unguard(&self, sel: Selector) -> ShowNode {
Self {
url: self.url.clone(),
body: self.body.as_ref().map(|body| body.unguard(sel)),
}
.pack()
}
fn encode(&self) -> Dict {
dict! {
"url" => Value::Str(self.url.clone()),

View File

@ -463,6 +463,10 @@ impl StrongNode {
}
impl Show for StrongNode {
fn unguard(&self, sel: Selector) -> ShowNode {
Self(self.0.unguard(sel)).pack()
}
fn encode(&self) -> Dict {
dict! { "body" => Value::Content(self.0.clone()) }
}
@ -484,6 +488,10 @@ impl EmphNode {
}
impl Show for EmphNode {
fn unguard(&self, sel: Selector) -> ShowNode {
Self(self.0.unguard(sel)).pack()
}
fn encode(&self) -> Dict {
dict! { "body" => Value::Content(self.0.clone()) }
}

View File

@ -618,7 +618,10 @@ fn shared_get<'a, K: Key<'a>>(
children: &StyleVec<ParChild>,
key: K,
) -> Option<K::Output> {
children.maps().all(|map| !map.contains(key)).then(|| styles.get(key))
children
.styles()
.all(|map| !map.contains(key))
.then(|| styles.get(key))
}
/// Find suitable linebreaks.

View File

@ -51,6 +51,10 @@ impl RawNode {
}
impl Show for RawNode {
fn unguard(&self, _: Selector) -> ShowNode {
Self { text: self.text.clone(), ..*self }.pack()
}
fn encode(&self) -> Dict {
dict! {
"text" => Value::Str(self.text.clone()),

View File

@ -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(

View File

@ -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),
}

View File

@ -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()
}

View File

@ -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,29 +167,17 @@ 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 {
/// If this is a property, return it.
pub fn property(&self) -> Option<&Property> {
match self {
Self::Property(property) => Some(property),
_ => None,
}
}
/// If this is a recipe, return it.
pub fn recipe(&self) -> Option<&Recipe> {
match self {
Self::Recipe(recipe) => Some(recipe),
_ => None,
}
}
/// 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 {
@ -222,6 +195,31 @@ impl StyleEntry {
tail: Some(tail),
}
}
/// If this is a property, return it.
pub fn property(&self) -> Option<&Property> {
match self {
Self::Property(property) => Some(property),
_ => None,
}
}
/// If this is a recipe, return it.
pub fn recipe(&self) -> Option<&Recipe> {
match self {
Self::Recipe(recipe) => Some(recipe),
_ => None,
}
}
/// 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,
}
}
}
impl Debug for StyleEntry {
@ -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,13 +504,15 @@ impl<T> StyleVec<T> {
}
}
/// 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> {
self.maps.iter().map(|(map, _)| map)
/// 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.
@ -473,6 +520,15 @@ impl<T> StyleVec<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 styles(&self) -> impl Iterator<Item = &StyleMap> {
self.maps.iter().map(|(map, _)| map)
}
/// Iterate over references to the contained items and associated style maps.
pub fn iter(&self) -> impl Iterator<Item = (&T, &StyleMap)> + '_ {
self.items().zip(

View File

@ -807,9 +807,15 @@ fn set_expr(p: &mut Parser) -> ParseResult {
fn show_expr(p: &mut Parser) -> ParseResult {
p.perform(NodeKind::ShowExpr, |p| {
p.assert(NodeKind::Show);
ident(p)?;
p.expect(NodeKind::Colon)?;
ident(p)?;
let marker = p.marker();
expr(p)?;
if p.eat_if(NodeKind::Colon) {
marker.filter_children(p, |child| match child.kind() {
NodeKind::Ident(_) | NodeKind::Colon => Ok(()),
_ => Err("expected identifier"),
});
expr(p)?;
}
p.expect(NodeKind::As)?;
expr(p)
})

View File

@ -1012,17 +1012,21 @@ node! {
impl ShowExpr {
/// The binding to assign to.
pub fn binding(&self) -> Ident {
self.0.cast_first_child().expect("show rule is missing binding")
pub fn binding(&self) -> Option<Ident> {
let mut children = self.0.children();
children
.find_map(RedRef::cast)
.filter(|_| children.any(|child| child.kind() == &NodeKind::Colon))
}
/// The function to customize with this show rule.
pub fn target(&self) -> Ident {
/// The pattern that this rule matches.
pub fn pattern(&self) -> Expr {
self.0
.children()
.filter_map(RedRef::cast)
.nth(1)
.expect("show rule is missing target")
.rev()
.skip_while(|child| child.kind() != &NodeKind::As)
.find_map(RedRef::cast)
.expect("show rule is missing pattern")
}
/// The expression that realizes the node.

View File

@ -12,7 +12,7 @@
}
---
// Error: 2-13 dictionary does not contain key: "invalid"
// Error: 6-13 dictionary does not contain key: "invalid"
{(:).invalid}
---