mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Support recursive show rules
This commit is contained in:
parent
f77f1f61bf
commit
d59109e8ff
@ -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.
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()?,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -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 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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::*;
|
||||||
|
@ -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),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()),
|
||||||
|
@ -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};
|
||||||
|
@ -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),
|
||||||
|
@ -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()
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
|
@ -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()) }
|
||||||
}
|
}
|
||||||
|
@ -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()),
|
||||||
|
@ -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()) }
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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()),
|
||||||
|
@ -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(
|
||||||
|
@ -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),
|
||||||
|
}
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
|
@ -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)
|
||||||
})
|
})
|
||||||
|
@ -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.
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 2-13 dictionary does not contain key: "invalid"
|
// Error: 6-13 dictionary does not contain key: "invalid"
|
||||||
{(:).invalid}
|
{(:).invalid}
|
||||||
|
|
||||||
---
|
---
|
||||||
|
Loading…
x
Reference in New Issue
Block a user