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 { 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. /// Create positional arguments from a span and values.
pub fn from_values(span: Span, values: impl IntoIterator<Item = Value>) -> Self { pub fn from_values(span: Span, values: impl IntoIterator<Item = Value>) -> Self {
Self { let items = values
span,
items: values
.into_iter() .into_iter()
.map(|value| Arg { .map(|value| Arg {
span, span,
name: None, name: None,
value: Spanned::new(value, span), value: Spanned::new(value, span),
}) })
.collect(), .collect();
} Self { span, items }
} }
/// Consume and cast the first positional argument. /// 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 // A show rule contains a binding, but that binding is only active
// after the target has been evaluated. // after the target has been evaluated.
Some(Expr::Show(show)) => { Some(Expr::Show(show)) => {
self.visit(show.target().as_red()); self.visit(show.pattern().as_red());
self.bind(show.binding()); if let Some(binding) = show.binding() {
self.bind(binding);
}
self.visit(show.body().as_red()); self.visit(show.body().as_red());
} }

View File

@ -46,8 +46,8 @@ impl Dict {
} }
/// Borrow the value the given `key` maps to. /// Borrow the value the given `key` maps to.
pub fn get(&self, key: EcoString) -> StrResult<&Value> { pub fn get(&self, key: &EcoString) -> StrResult<&Value> {
self.0.get(&key).ok_or_else(|| missing_key(&key)) self.0.get(key).ok_or_else(|| missing_key(key))
} }
/// Mutably borrow the value the given `key` maps to. /// Mutably borrow the value the given `key` maps to.
@ -59,7 +59,7 @@ impl Dict {
} }
/// Whether the dictionary contains a specific key. /// 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) self.0.contains_key(key)
} }
@ -69,10 +69,10 @@ impl Dict {
} }
/// Remove a mapping by `key`. /// Remove a mapping by `key`.
pub fn remove(&mut self, key: EcoString) -> StrResult<()> { pub fn remove(&mut self, key: &EcoString) -> StrResult<()> {
match Arc::make_mut(&mut self.0).remove(&key) { match Arc::make_mut(&mut self.0).remove(key) {
Some(_) => Ok(()), 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 super::{Args, Control, Eval, Scope, Scopes, Value};
use crate::diag::{StrResult, TypResult}; use crate::diag::{StrResult, TypResult};
use crate::model::{Content, StyleMap}; use crate::model::{Content, NodeId, StyleMap};
use crate::syntax::ast::Expr; use crate::syntax::ast::Expr;
use crate::syntax::Span;
use crate::util::EcoString; use crate::util::EcoString;
use crate::Context; use crate::Context;
@ -35,7 +34,7 @@ impl Func {
name, name,
func, func,
set: None, set: None,
show: None, node: None,
}))) })))
} }
@ -49,15 +48,7 @@ impl Func {
Ok(Value::Content(content.styled_with_map(styles.scoped()))) Ok(Value::Content(content.styled_with_map(styles.scoped())))
}, },
set: Some(T::set), set: Some(T::set),
show: if T::SHOWABLE { node: T::SHOWABLE.then(|| NodeId::of::<T>()),
Some(|recipe, span| {
let mut styles = StyleMap::new();
styles.set_recipe::<T>(recipe, span);
styles
})
} else {
None
},
}))) })))
} }
@ -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> { pub fn call(&self, ctx: &mut Context, mut args: Args) -> TypResult<Value> {
let value = match self.0.as_ref() { let value = match self.0.as_ref() {
Repr::Native(native) => (native.func)(ctx, &mut args)?, Repr::Native(native) => (native.func)(ctx, &mut args)?,
@ -104,10 +108,10 @@ impl Func {
Ok(styles) Ok(styles)
} }
/// Execute the function's show rule. /// The id of the node to customize with this function's show rule.
pub fn show(&self, recipe: Func, span: Span) -> StrResult<StyleMap> { pub fn node(&self) -> StrResult<NodeId> {
match self.0.as_ref() { 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")?, _ => Err("this function cannot be customized with show")?,
} }
} }
@ -138,8 +142,8 @@ struct Native {
pub func: fn(&mut Context, &mut Args) -> TypResult<Value>, pub func: fn(&mut Context, &mut Args) -> TypResult<Value>,
/// The set rule. /// The set rule.
pub set: Option<fn(&mut Args) -> TypResult<StyleMap>>, pub set: Option<fn(&mut Args) -> TypResult<StyleMap>>,
/// The show rule. /// The id of the node to customize with this function's show rule.
pub show: Option<fn(Func, Span) -> StyleMap>, pub node: Option<NodeId>,
} }
impl Hash for Native { impl Hash for Native {
@ -147,7 +151,7 @@ impl Hash for Native {
self.name.hash(state); self.name.hash(state);
(self.func as usize).hash(state); (self.func as usize).hash(state);
self.set.map(|set| set 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 { Value::Dict(dict) => match method {
"remove" => dict.remove(args.expect("key")?).at(span)?, "remove" => dict.remove(&args.expect("key")?).at(span)?,
_ => missing()?, _ => missing()?,
}, },

View File

@ -38,7 +38,7 @@ use unicode_segmentation::UnicodeSegmentation;
use crate::diag::{At, StrResult, Trace, Tracepoint, TypResult}; use crate::diag::{At, StrResult, Trace, Tracepoint, TypResult};
use crate::geom::{Angle, Em, Fraction, Length, Ratio}; use crate::geom::{Angle, Em, Fraction, Length, Ratio};
use crate::library; use crate::library;
use crate::model::{Content, StyleMap}; use crate::model::{Content, Pattern, Recipe, StyleEntry, StyleMap};
use crate::syntax::ast::*; use crate::syntax::ast::*;
use crate::syntax::{Span, Spanned}; use crate::syntax::{Span, Spanned};
use crate::util::EcoString; use crate::util::EcoString;
@ -79,8 +79,9 @@ fn eval_markup(
eval_markup(ctx, scp, nodes)?.styled_with_map(styles) eval_markup(ctx, scp, nodes)?.styled_with_map(styles)
} }
MarkupNode::Expr(Expr::Show(show)) => { MarkupNode::Expr(Expr::Show(show)) => {
let styles = show.eval(ctx, scp)?; let recipe = show.eval(ctx, scp)?;
eval_markup(ctx, scp, nodes)?.styled_with_map(styles) eval_markup(ctx, scp, nodes)?
.styled_with_entry(StyleEntry::Recipe(recipe).into())
} }
MarkupNode::Expr(Expr::Wrap(wrap)) => { MarkupNode::Expr(Expr::Wrap(wrap)) => {
let tail = eval_markup(ctx, scp, nodes)?; 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> { fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> {
let object = self.object().eval(ctx, scp)?; let object = self.object().eval(ctx, scp)?;
let span = self.field().span();
let field = self.field().take();
Ok(match object { 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!( v => bail!(
self.object().span(), self.object().span(),
@ -455,7 +465,7 @@ impl Eval for FuncCall {
Ok(match callee { Ok(match callee {
Value::Array(array) => array.get(args.into_index()?).at(self.span())?.clone(), 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) => { Value::Func(func) => {
let point = || Tracepoint::Call(func.name().map(ToString::to_string)); let point = || Tracepoint::Call(func.name().map(ToString::to_string));
func.call(ctx, args).trace(point, self.span())? func.call(ctx, args).trace(point, self.span())?
@ -615,13 +625,12 @@ impl Eval for SetExpr {
} }
impl Eval for ShowExpr { impl Eval for ShowExpr {
type Output = StyleMap; type Output = Recipe;
fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> { fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> {
// Evaluate the target function. // Evaluate the target function.
let target = self.target(); let pattern = self.pattern();
let target_span = target.span(); let pattern = pattern.eval(ctx, scp)?.cast::<Pattern>().at(pattern.span())?;
let target = target.eval(ctx, scp)?.cast::<Func>().at(target_span)?;
// Collect captured variables. // Collect captured variables.
let captured = { let captured = {
@ -630,18 +639,24 @@ impl Eval for ShowExpr {
visitor.finish() visitor.finish()
}; };
// Define parameters.
let mut params = vec![];
if let Some(binding) = self.binding() {
params.push((binding.take(), None));
}
// Define the recipe function. // Define the recipe function.
let body = self.body(); let body = self.body();
let body_span = body.span(); let span = body.span();
let recipe = Func::from_closure(Closure { let func = Func::from_closure(Closure {
name: None, name: None,
captured, captured,
params: vec![(self.binding().take(), None)], params,
sink: None, sink: None,
body, 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, Angle, Color, Dir, Em, Fraction, Length, Paint, Ratio, Relative, RgbaColor, Sides,
}; };
use crate::library::text::RawNode; use crate::library::text::RawNode;
use crate::model::{Content, Layout, LayoutNode}; use crate::model::{Content, Layout, LayoutNode, Pattern};
use crate::syntax::Spanned; use crate::syntax::Spanned;
use crate::util::EcoString; use crate::util::EcoString;
@ -617,13 +617,13 @@ where
} }
let sides = Sides { let sides = Sides {
left: dict.get("left".into()).or_else(|_| dict.get("x".into())), left: dict.get(&"left".into()).or_else(|_| dict.get(&"x".into())),
top: dict.get("top".into()).or_else(|_| dict.get("y".into())), top: dict.get(&"top".into()).or_else(|_| dict.get(&"y".into())),
right: dict.get("right".into()).or_else(|_| dict.get("x".into())), right: dict.get(&"right".into()).or_else(|_| dict.get(&"x".into())),
bottom: dict.get("bottom".into()).or_else(|_| dict.get("y".into())), bottom: dict.get(&"bottom".into()).or_else(|_| dict.get(&"y".into())),
} }
.map(|side| { .map(|side| {
side.or_else(|_| dict.get("rest".into())) side.or_else(|_| dict.get(&"rest".into()))
.and_then(|v| T::cast(v.clone())) .and_then(|v| T::cast(v.clone()))
.unwrap_or_default() .unwrap_or_default()
}); });
@ -684,6 +684,12 @@ castable! {
Value::Content(content) => content.pack(), Value::Content(content) => content.pack(),
} }
castable! {
Pattern,
Expected: "function",
Value::Func(func) => Pattern::Node(func.node()?),
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -52,7 +52,11 @@ impl<T: Numeric> Relative<T> {
impl<T: Numeric> Debug for Relative<T> { impl<T: Numeric> Debug for Relative<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { 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 { impl Show for MathNode {
fn unguard(&self, _: Selector) -> ShowNode {
Self { formula: self.formula.clone(), ..*self }.pack()
}
fn encode(&self) -> Dict { fn encode(&self) -> Dict {
dict! { dict! {
"formula" => Value::Str(self.formula.clone()), "formula" => Value::Str(self.formula.clone()),

View File

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

View File

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

View File

@ -51,6 +51,15 @@ impl TableNode {
} }
impl Show for 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 { fn encode(&self) -> Dict {
dict! { dict! {
"cells" => Value::Array( "cells" => Value::Array(

View File

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

View File

@ -28,6 +28,14 @@ impl LinkNode {
} }
impl Show for 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 { fn encode(&self) -> Dict {
dict! { dict! {
"url" => Value::Str(self.url.clone()), "url" => Value::Str(self.url.clone()),

View File

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

View File

@ -618,7 +618,10 @@ fn shared_get<'a, K: Key<'a>>(
children: &StyleVec<ParChild>, children: &StyleVec<ParChild>,
key: K, key: K,
) -> Option<K::Output> { ) -> 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. /// Find suitable linebreaks.

View File

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

View File

@ -7,8 +7,8 @@ use std::ops::{Add, AddAssign};
use typed_arena::Arena; use typed_arena::Arena;
use super::{ use super::{
CollapsingBuilder, Interruption, Key, Layout, LayoutNode, Show, ShowNode, StyleMap, CollapsingBuilder, Interruption, Key, Layout, LayoutNode, Property, Show, ShowNode,
StyleVecBuilder, StyleEntry, StyleMap, StyleVecBuilder, Target,
}; };
use crate::diag::StrResult; use crate::diag::StrResult;
use crate::library::layout::{FlowChild, FlowNode, PageNode, PlaceNode, Spacing}; use crate::library::layout::{FlowChild, FlowNode, PageNode, PlaceNode, Spacing};
@ -38,6 +38,8 @@ use crate::util::EcoString;
/// sequences. /// sequences.
#[derive(PartialEq, Clone, Hash)] #[derive(PartialEq, Clone, Hash)]
pub enum Content { pub enum Content {
/// Empty content.
Empty,
/// A word space. /// A word space.
Space, Space,
/// A forced line break. /// A forced line break.
@ -68,8 +70,9 @@ pub enum Content {
Pagebreak { weak: bool }, Pagebreak { weak: bool },
/// A page node. /// A page node.
Page(PageNode), Page(PageNode),
/// A node that can be realized with styles. /// A node that can be realized with styles, optionally with attached
Show(ShowNode), /// properties.
Show(ShowNode, Option<Dict>),
/// Content with attached styles. /// Content with attached styles.
Styled(Arc<(Self, StyleMap)>), Styled(Arc<(Self, StyleMap)>),
/// A sequence of multiple nodes. /// A sequence of multiple nodes.
@ -79,7 +82,7 @@ pub enum Content {
impl Content { impl Content {
/// Create empty content. /// Create empty content.
pub fn new() -> Self { pub fn new() -> Self {
Self::sequence(vec![]) Self::Empty
} }
/// Create content from an inline-level node. /// Create content from an inline-level node.
@ -103,15 +106,15 @@ impl Content {
where where
T: Show + Debug + Hash + Sync + Send + 'static, T: Show + Debug + Hash + Sync + Send + 'static,
{ {
Self::Show(node.pack()) Self::Show(node.pack(), None)
} }
/// Create a new sequence nodes from multiples nodes. /// Create a new sequence nodes from multiples nodes.
pub fn sequence(seq: Vec<Self>) -> Self { pub fn sequence(seq: Vec<Self>) -> Self {
if seq.len() == 1 { match seq.as_slice() {
seq.into_iter().next().unwrap() [] => Self::Empty,
} else { [_] => seq.into_iter().next().unwrap(),
Self::Sequence(Arc::new(seq)) _ => Self::Sequence(Arc::new(seq)),
} }
} }
@ -124,15 +127,20 @@ impl Content {
} }
/// Style this content with a single style property. /// 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 Self::Styled(styled) = &mut self {
if let Some((_, map)) = Arc::get_mut(styled) { if let Some((_, map)) = Arc::get_mut(styled) {
map.apply(key, value); map.apply(entry);
return self; 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. /// Style this content with a full style map.
@ -151,6 +159,11 @@ impl Content {
Self::Styled(Arc::new((self, styles))) 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. /// Underline this content.
pub fn underlined(self) -> Self { pub fn underlined(self) -> Self {
Self::show(DecoNode::<UNDERLINE>(self)) Self::show(DecoNode::<UNDERLINE>(self))
@ -228,6 +241,7 @@ impl Default for Content {
impl Debug for Content { impl Debug for Content {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self { match self {
Self::Empty => f.pad("Empty"),
Self::Space => f.pad("Space"), Self::Space => f.pad("Space"),
Self::Linebreak { justified } => write!(f, "Linebreak({justified})"), Self::Linebreak { justified } => write!(f, "Linebreak({justified})"),
Self::Horizontal { amount, weak } => { Self::Horizontal { amount, weak } => {
@ -245,7 +259,7 @@ impl Debug for Content {
Self::Item(item) => item.fmt(f), Self::Item(item) => item.fmt(f),
Self::Pagebreak { weak } => write!(f, "Pagebreak({weak})"), Self::Pagebreak { weak } => write!(f, "Pagebreak({weak})"),
Self::Page(page) => page.fmt(f), Self::Page(page) => page.fmt(f),
Self::Show(node) => node.fmt(f), Self::Show(node, _) => node.fmt(f),
Self::Styled(styled) => { Self::Styled(styled) => {
let (sub, map) = styled.as_ref(); let (sub, map) = styled.as_ref();
map.fmt(f)?; map.fmt(f)?;
@ -261,6 +275,8 @@ impl Add for Content {
fn add(self, rhs: Self) -> Self::Output { fn add(self, rhs: Self) -> Self::Output {
Self::Sequence(match (self, rhs) { Self::Sequence(match (self, rhs) {
(Self::Empty, rhs) => return rhs,
(lhs, Self::Empty) => return lhs,
(Self::Sequence(mut lhs), Self::Sequence(rhs)) => { (Self::Sequence(mut lhs), Self::Sequence(rhs)) => {
let mutable = Arc::make_mut(&mut lhs); let mutable = Arc::make_mut(&mut lhs);
match Arc::try_unwrap(rhs) { 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<()> { fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> TypResult<()> {
// Handle special content kinds. // Handle special content kinds.
match content { 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::Styled(styled) => return self.styled(styled, styles),
Content::Sequence(seq) => return self.sequence(seq, 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<()> { fn show(&mut self, node: &ShowNode, styles: StyleChain<'a>) -> TypResult<()> {
let id = node.id(); if let Some(realized) = styles.apply(self.ctx, Target::Node(node))? {
let realized = match styles.realize(self.ctx, node)? { let stored = self.scratch.templates.alloc(realized);
Some(content) => content, self.accept(stored, styles.unscoped(node.id()))?;
None => node.realize(self.ctx, styles)?, }
}; Ok(())
let content = node.finalize(self.ctx, styles, realized)?;
let stored = self.scratch.templates.alloc(content);
self.accept(stored, styles.unscoped(id))
} }
fn styled( fn styled(

View File

@ -1,14 +1,17 @@
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use super::NodeId; use super::{Content, Interruption, NodeId, Show, ShowNode, StyleEntry};
use crate::eval::{Func, Node}; use crate::diag::{At, TypResult};
use crate::eval::{Args, Func, Value};
use crate::library::structure::{EnumNode, ListNode};
use crate::syntax::Span; use crate::syntax::Span;
use crate::Context;
/// A show rule recipe. /// A show rule recipe.
#[derive(Clone, PartialEq, Hash)] #[derive(Clone, PartialEq, Hash)]
pub struct Recipe { pub struct Recipe {
/// The affected node. /// The patterns to customize.
pub node: NodeId, pub pattern: Pattern,
/// The function that defines the recipe. /// The function that defines the recipe.
pub func: Func, pub func: Func,
/// The span to report all erros with. /// The span to report all erros with.
@ -16,14 +19,87 @@ pub struct Recipe {
} }
impl Recipe { impl Recipe {
/// Create a new recipe for the node `T`. /// Whether the recipe is applicable to the target.
pub fn new<T: Node>(func: Func, span: Span) -> Self { pub fn applicable(&self, target: Target) -> bool {
Self { node: NodeId::of::<T>(), func, span } 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 { impl Debug for Recipe {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { 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::hash::Hash;
use std::sync::Arc; use std::sync::Arc;
use super::{Content, NodeId, StyleChain}; use super::{Content, NodeId, Selector, StyleChain};
use crate::diag::TypResult; use crate::diag::TypResult;
use crate::eval::Dict; use crate::eval::Dict;
use crate::util::Prehashed; use crate::util::Prehashed;
@ -10,6 +10,9 @@ use crate::Context;
/// A node that can be realized given some styles. /// A node that can be realized given some styles.
pub trait Show: 'static { pub trait Show: 'static {
/// Unguard nested content against recursive show rules.
fn unguard(&self, sel: Selector) -> ShowNode;
/// Encode this node into a dictionary. /// Encode this node into a dictionary.
fn encode(&self) -> Dict; fn encode(&self) -> Dict;
@ -63,6 +66,10 @@ impl ShowNode {
} }
impl Show for ShowNode { impl Show for ShowNode {
fn unguard(&self, sel: Selector) -> ShowNode {
self.0.unguard(sel)
}
fn encode(&self) -> Dict { fn encode(&self) -> Dict {
self.0.encode() self.0.encode()
} }

View File

@ -3,11 +3,9 @@ use std::hash::Hash;
use std::iter; use std::iter;
use std::marker::PhantomData; use std::marker::PhantomData;
use super::{Barrier, Content, Key, Property, Recipe, Show, ShowNode}; use super::{Barrier, Content, Key, Property, Recipe, Selector, Show, Target};
use crate::diag::{At, TypResult}; use crate::diag::TypResult;
use crate::eval::{Args, Func, Node, Value};
use crate::library::text::{FontFamily, TextNode}; use crate::library::text::{FontFamily, TextNode};
use crate::syntax::Span;
use crate::util::ReadableTypeId; use crate::util::ReadableTypeId;
use crate::Context; 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. /// Whether the map contains a style property for the given key.
pub fn contains<'a, K: Key<'a>>(&self, _: K) -> bool { pub fn contains<'a, K: Key<'a>>(&self, _: K) -> bool {
self.0 self.0
@ -91,16 +84,12 @@ impl StyleMap {
} }
} }
/// Set an outer value for a style property. /// Set an outer 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.
/// ///
/// Like [`chain`](Self::chain) or [`apply_map`](Self::apply_map), but with /// Like [`chain`](Self::chain) or [`apply_map`](Self::apply_map), but with
/// only a single property. /// only a entry.
pub fn apply<'a, K: Key<'a>>(&mut self, key: K, value: K::Value) { pub fn apply(&mut self, entry: StyleEntry) {
self.0.insert(0, StyleEntry::Property(Property::new(key, value))); self.0.insert(0, entry);
} }
/// Apply styles from `tail` in-place. The resulting style map is equivalent /// 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. /// The highest-level kind of of structure the map interrupts.
pub fn interruption(&self) -> Option<Interruption> { pub fn interruption(&self) -> Option<Interruption> {
self.0 self.0.iter().filter_map(|entry| entry.interruption()).max()
.iter()
.filter_map(|entry| entry.property())
.filter_map(|property| property.interruption())
.max()
} }
} }
@ -182,29 +167,17 @@ pub enum Interruption {
pub enum StyleEntry { pub enum StyleEntry {
/// A style property originating from a set rule or constructor. /// A style property originating from a set rule or constructor.
Property(Property), Property(Property),
/// A barrier for scoped styles.
Barrier(Barrier),
/// A show rule recipe. /// A show rule recipe.
Recipe(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 { 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. /// Make this style the first link of the `tail` chain.
pub fn chain<'a>(&'a self, tail: &'a StyleChain) -> StyleChain<'a> { pub fn chain<'a>(&'a self, tail: &'a StyleChain) -> StyleChain<'a> {
if let StyleEntry::Barrier(barrier) = self { if let StyleEntry::Barrier(barrier) = self {
@ -222,6 +195,31 @@ impl StyleEntry {
tail: Some(tail), 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 { impl Debug for StyleEntry {
@ -231,6 +229,8 @@ impl Debug for StyleEntry {
Self::Property(property) => property.fmt(f)?, Self::Property(property) => property.fmt(f)?,
Self::Recipe(recipe) => recipe.fmt(f)?, Self::Recipe(recipe) => recipe.fmt(f)?,
Self::Barrier(barrier) => barrier.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("]") f.write_str("]")
} }
@ -262,35 +262,6 @@ impl<'a> StyleChain<'a> {
Self { head: &root.0, tail: None } 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 /// Return the chain, but without trailing scoped properties for the given
/// `node`. /// `node`.
pub fn unscoped(mut self, node: NodeId) -> Self { pub fn unscoped(mut self, node: NodeId) -> Self {
@ -306,6 +277,80 @@ impl<'a> StyleChain<'a> {
self 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. /// Remove the last link from the chain.
fn pop(&mut self) { fn pop(&mut self) {
*self = self.tail.copied().unwrap_or_default(); *self = self.tail.copied().unwrap_or_default();
@ -386,7 +431,7 @@ impl<'a, K: Key<'a>> Iterator for Values<'a, K> {
StyleEntry::Barrier(barrier) => { StyleEntry::Barrier(barrier) => {
self.depth += barrier.is_for(K::node()) as usize; 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()` /// Map the contained items.
/// does not yield the same result as calling `iter()` because this method pub fn map<F, U>(&self, f: F) -> StyleVec<U>
/// only returns maps once that are shared by consecutive items. This method where
/// is designed for use cases where you want to check, for example, whether F: FnMut(&T) -> U,
/// any of the maps fulfills a specific property. {
pub fn maps(&self) -> impl Iterator<Item = &StyleMap> { StyleVec {
self.maps.iter().map(|(map, _)| map) items: self.items.iter().map(f).collect(),
maps: self.maps.clone(),
}
} }
/// Iterate over the contained items. /// Iterate over the contained items.
@ -473,6 +520,15 @@ impl<T> StyleVec<T> {
self.items.iter() 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. /// Iterate over references to the contained items and associated style maps.
pub fn iter(&self) -> impl Iterator<Item = (&T, &StyleMap)> + '_ { pub fn iter(&self) -> impl Iterator<Item = (&T, &StyleMap)> + '_ {
self.items().zip( self.items().zip(

View File

@ -807,9 +807,15 @@ fn set_expr(p: &mut Parser) -> ParseResult {
fn show_expr(p: &mut Parser) -> ParseResult { fn show_expr(p: &mut Parser) -> ParseResult {
p.perform(NodeKind::ShowExpr, |p| { p.perform(NodeKind::ShowExpr, |p| {
p.assert(NodeKind::Show); p.assert(NodeKind::Show);
ident(p)?; let marker = p.marker();
p.expect(NodeKind::Colon)?; expr(p)?;
ident(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)?; p.expect(NodeKind::As)?;
expr(p) expr(p)
}) })

View File

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