Show rules with type ascribed object

This commit is contained in:
Laurenz 2022-04-23 21:55:58 +02:00
parent 7a2cc3e7d2
commit 04fb8b288a
20 changed files with 294 additions and 165 deletions

View File

@ -88,6 +88,14 @@ impl<'a> CapturesVisitor<'a> {
self.bind(expr.binding()); self.bind(expr.binding());
} }
// 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.body().as_red());
}
// A for loop contains one or two bindings in its pattern. These are // A for loop contains one or two bindings in its pattern. These are
// active after the iterable is evaluated but before the body is // active after the iterable is evaluated but before the body is
// evaluated. // evaluated.
@ -162,6 +170,11 @@ mod tests {
test("{(..x) => x + y}", &["y"]); test("{(..x) => x + y}", &["y"]);
test("{(x, y: x + z) => x + y}", &["x", "z"]); test("{(x, y: x + z) => x + y}", &["x", "z"]);
// Show rule.
test("#show x: y as x", &["y"]);
test("#show x: y as x + z", &["y", "z"]);
test("#show x: x as x", &["x"]);
// For loop. // For loop.
test("#for x in y { x + z }", &["y", "z"]); test("#for x in y { x + z }", &["y", "z"]);
test("#for x, y in y { x + y }", &["y"]); test("#for x, y in y { x + y }", &["y"]);

View File

@ -320,7 +320,7 @@ struct ListBuilder<'a> {
styles: StyleChain<'a>, styles: StyleChain<'a>,
kind: ListKind, kind: ListKind,
items: Vec<ListItem>, items: Vec<ListItem>,
wide: bool, tight: bool,
staged: Vec<(&'a Content, StyleChain<'a>)>, staged: Vec<(&'a Content, StyleChain<'a>)>,
} }
@ -356,15 +356,15 @@ impl<'a> Builder<'a> {
return Ok(()); return Ok(());
} }
Content::List(item) if builder.kind == UNORDERED => { Content::List(item) if builder.kind == UNORDERED => {
builder.wide |= builder.tight &=
builder.staged.iter().any(|&(t, _)| *t == Content::Parbreak); builder.staged.iter().all(|&(t, _)| *t != Content::Parbreak);
builder.staged.clear(); builder.staged.clear();
builder.items.push(item.clone()); builder.items.push(item.clone());
return Ok(()); return Ok(());
} }
Content::Enum(item) if builder.kind == ORDERED => { Content::Enum(item) if builder.kind == ORDERED => {
builder.wide |= builder.tight &=
builder.staged.iter().any(|&(t, _)| *t == Content::Parbreak); builder.staged.iter().all(|&(t, _)| *t != Content::Parbreak);
builder.staged.clear(); builder.staged.clear();
builder.items.push(item.clone()); builder.items.push(item.clone());
return Ok(()); return Ok(());
@ -430,7 +430,7 @@ impl<'a> Builder<'a> {
styles, styles,
kind: UNORDERED, kind: UNORDERED,
items: vec![item.clone()], items: vec![item.clone()],
wide: false, tight: true,
staged: vec![], staged: vec![],
}); });
} }
@ -439,7 +439,7 @@ impl<'a> Builder<'a> {
styles, styles,
kind: ORDERED, kind: ORDERED,
items: vec![item.clone()], items: vec![item.clone()],
wide: false, tight: true,
staged: vec![], staged: vec![],
}); });
} }
@ -454,7 +454,8 @@ impl<'a> Builder<'a> {
} }
Content::Show(node) => { Content::Show(node) => {
let id = node.id(); let id = node.id();
let content = node.show(ctx, styles)?; let realized = styles.realize(ctx, node)?;
let content = node.show(ctx, styles, realized)?;
let stored = self.tpa.alloc(content); let stored = self.tpa.alloc(content);
self.process(ctx, stored, styles.unscoped(id))?; self.process(ctx, stored, styles.unscoped(id))?;
} }
@ -532,14 +533,14 @@ impl<'a> Builder<'a> {
/// Finish the currently built list. /// Finish the currently built list.
fn finish_list(&mut self, ctx: &mut Context) -> TypResult<()> { fn finish_list(&mut self, ctx: &mut Context) -> TypResult<()> {
let ListBuilder { styles, kind, items, wide, staged } = match self.list.take() { let ListBuilder { styles, kind, items, tight, staged } = match self.list.take() {
Some(list) => list, Some(list) => list,
None => return Ok(()), None => return Ok(()),
}; };
let content = match kind { let content = match kind {
UNORDERED => Content::show(ListNode::<UNORDERED> { start: 1, wide, items }), UNORDERED => Content::show(ListNode::<UNORDERED> { start: 1, tight, items }),
ORDERED | _ => Content::show(ListNode::<ORDERED> { start: 1, wide, items }), ORDERED | _ => Content::show(ListNode::<ORDERED> { start: 1, tight, items }),
}; };
let stored = self.tpa.alloc(content); let stored = self.tpa.alloc(content);

View File

@ -624,13 +624,30 @@ impl Eval for ShowExpr {
type Output = StyleMap; type Output = StyleMap;
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.
let target = self.target(); let target = self.target();
let target_span = target.span(); let target_span = target.span();
let target = target.eval(ctx, scp)?.cast::<Func>().at(target_span)?; let target = target.eval(ctx, scp)?.cast::<Func>().at(target_span)?;
let recipe = self.recipe();
let recipe_span = recipe.span(); // Collect captured variables.
let recipe = recipe.eval(ctx, scp)?.cast::<Func>().at(recipe_span)?; let captured = {
Ok(target.show(recipe, recipe_span).at(target_span)?) let mut visitor = CapturesVisitor::new(scp);
visitor.visit(self.as_red());
visitor.finish()
};
// Define the recipe function.
let body = self.body();
let body_span = body.span();
let recipe = Func::from_closure(Closure {
name: None,
captured,
params: vec![(self.binding().take(), None)],
sink: None,
body,
});
Ok(target.show(recipe, body_span).at(target_span)?)
} }
} }

View File

@ -110,7 +110,6 @@ impl Resolve for RawStroke {
} }
} }
// This faciliates RawStroke => Stroke.
impl Fold for RawStroke<Length> { impl Fold for RawStroke<Length> {
type Output = Self; type Output = Self;

View File

@ -3,15 +3,24 @@ use std::fmt::{self, Debug, Formatter};
use std::hash::Hash; use std::hash::Hash;
use std::sync::Arc; use std::sync::Arc;
use super::{Content, StyleChain}; use super::{Content, Dict, StyleChain};
use crate::diag::TypResult; use crate::diag::TypResult;
use crate::util::Prehashed; use crate::util::Prehashed;
use crate::Context; 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 {
/// Realize this node in the given styles. /// Encode this node into a dictionary.
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content>; fn encode(&self) -> Dict;
/// Show this node in the given styles and optionally given the realization
/// of a show rule.
fn show(
&self,
ctx: &mut Context,
styles: StyleChain,
realized: Option<Content>,
) -> TypResult<Content>;
/// Convert to a packed show node. /// Convert to a packed show node.
fn pack(self) -> ShowNode fn pack(self) -> ShowNode
@ -42,8 +51,17 @@ impl ShowNode {
} }
impl Show for ShowNode { impl Show for ShowNode {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> { fn encode(&self) -> Dict {
self.0.show(ctx, styles) self.0.encode()
}
fn show(
&self,
ctx: &mut Context,
styles: StyleChain,
realized: Option<Content>,
) -> TypResult<Content> {
self.0.show(ctx, styles, realized)
} }
fn pack(self) -> ShowNode { fn pack(self) -> ShowNode {

View File

@ -4,7 +4,7 @@ use std::hash::Hash;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::sync::Arc; use std::sync::Arc;
use super::{Args, Content, Func, Layout, Node, Smart, Span, Value}; use super::{Args, Content, Func, Layout, Node, Show, ShowNode, Smart, Span, Value};
use crate::diag::{At, TypResult}; use crate::diag::{At, TypResult};
use crate::geom::{Numeric, Relative, Sides, Spec}; use crate::geom::{Numeric, Relative, Sides, Spec};
use crate::library::layout::PageNode; use crate::library::layout::PageNode;
@ -510,19 +510,20 @@ impl<'a> StyleChain<'a> {
K::get(self, self.values(key)) K::get(self, self.values(key))
} }
/// Execute and return the result of a user recipe for a node if there is /// Realize a node with a user recipe.
/// any. pub fn realize(
pub fn show<T, I>(self, ctx: &mut Context, values: I) -> TypResult<Option<Content>> self,
where ctx: &mut Context,
T: Node, node: &ShowNode,
I: IntoIterator<Item = Value>, ) -> TypResult<Option<Content>> {
{ let id = node.id();
if let Some(recipe) = self if let Some(recipe) = self
.entries() .entries()
.filter_map(Entry::recipe) .filter_map(Entry::recipe)
.find(|recipe| recipe.node == TypeId::of::<T>()) .find(|recipe| recipe.node == id)
{ {
let args = Args::from_values(recipe.span, values); 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)?)) Ok(Some(recipe.func.call(ctx, args)?.cast().at(recipe.span)?))
} else { } else {
Ok(None) Ok(None)

View File

@ -8,7 +8,7 @@ pub struct GridNode {
/// Defines sizing of gutter rows and columns between content. /// Defines sizing of gutter rows and columns between content.
pub gutter: Spec<Vec<TrackSizing>>, pub gutter: Spec<Vec<TrackSizing>>,
/// The nodes to be arranged in a grid. /// The nodes to be arranged in a grid.
pub children: Vec<LayoutNode>, pub cells: Vec<LayoutNode>,
} }
#[node] #[node]
@ -25,7 +25,7 @@ impl GridNode {
column_gutter.unwrap_or_else(|| base_gutter.clone()), column_gutter.unwrap_or_else(|| base_gutter.clone()),
row_gutter.unwrap_or(base_gutter), row_gutter.unwrap_or(base_gutter),
), ),
children: args.all()?, cells: args.all()?,
})) }))
} }
} }
@ -41,7 +41,7 @@ impl Layout for GridNode {
let layouter = GridLayouter::new( let layouter = GridLayouter::new(
self.tracks.as_deref(), self.tracks.as_deref(),
self.gutter.as_deref(), self.gutter.as_deref(),
&self.children, &self.cells,
regions, regions,
styles, styles,
); );

View File

@ -28,11 +28,21 @@ impl MathNode {
} }
impl Show for MathNode { impl Show for MathNode {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> { fn encode(&self) -> Dict {
let args = [Value::Str(self.formula.clone()), Value::Bool(self.display)]; dict! {
let mut content = styles "formula" => Value::Str(self.formula.clone()),
.show::<Self, _>(ctx, args)? "display" => Value::Bool(self.display)
.unwrap_or_else(|| Content::Text(self.formula.trim().into())); }
}
fn show(
&self,
_: &mut Context,
styles: StyleChain,
realized: Option<Content>,
) -> TypResult<Content> {
let mut content =
realized.unwrap_or_else(|| Content::Text(self.formula.trim().into()));
let mut map = StyleMap::new(); let mut map = StyleMap::new();
if let Smart::Custom(family) = styles.get(Self::FAMILY) { if let Smart::Custom(family) = styles.get(Self::FAMILY) {

View File

@ -56,21 +56,26 @@ impl HeadingNode {
} }
impl Show for HeadingNode { impl Show for HeadingNode {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> { fn encode(&self) -> Dict {
dict! {
"level" => Value::Int(self.level as i64),
"body" => Value::Content(self.body.clone()),
}
}
fn show(
&self,
ctx: &mut Context,
styles: StyleChain,
realized: Option<Content>,
) -> TypResult<Content> {
macro_rules! resolve { macro_rules! resolve {
($key:expr) => { ($key:expr) => {
styles.get($key).resolve(ctx, self.level)? styles.get($key).resolve(ctx, self.level)?
}; };
} }
let args = [ let mut body = realized.unwrap_or_else(|| self.body.clone());
Value::Int(self.level as i64),
Value::Content(self.body.clone()),
];
let mut body = styles
.show::<Self, _>(ctx, args)?
.unwrap_or_else(|| self.body.clone());
let mut map = StyleMap::new(); let mut map = StyleMap::new();
map.set(TextNode::SIZE, resolve!(Self::SIZE)); map.set(TextNode::SIZE, resolve!(Self::SIZE));

View File

@ -10,9 +10,9 @@ use crate::library::utility::Numbering;
pub struct ListNode<const L: ListKind = UNORDERED> { pub struct ListNode<const L: ListKind = UNORDERED> {
/// Where the list starts. /// Where the list starts.
pub start: usize, pub start: usize,
/// If true, there is paragraph spacing between the items, if false /// If false, there is paragraph spacing between the items, if true
/// there is list spacing between the items. /// there is list spacing between the items.
pub wide: bool, pub tight: bool,
/// The individual bulleted or numbered items. /// The individual bulleted or numbered items.
pub items: Vec<ListItem>, pub items: Vec<ListItem>,
} }
@ -55,7 +55,7 @@ impl<const L: ListKind> ListNode<L> {
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
Ok(Content::show(Self { Ok(Content::show(Self {
start: args.named("start")?.unwrap_or(1), start: args.named("start")?.unwrap_or(1),
wide: args.named("wide")?.unwrap_or(false), tight: args.named("tight")?.unwrap_or(true),
items: args items: args
.all()? .all()?
.into_iter() .into_iter()
@ -66,30 +66,47 @@ impl<const L: ListKind> ListNode<L> {
} }
impl<const L: ListKind> Show for ListNode<L> { impl<const L: ListKind> Show for ListNode<L> {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> { fn encode(&self) -> Dict {
let args = self.items.iter().map(|item| Value::Content((*item.body).clone())); dict! {
let content = if let Some(content) = styles.show::<Self, _>(ctx, args)? { "start" => Value::Int(self.start as i64),
"tight" => Value::Bool(self.tight),
"items" => Value::Array(
self.items
.iter()
.map(|item| Value::Content((*item.body).clone()))
.collect()
),
}
}
fn show(
&self,
ctx: &mut Context,
styles: StyleChain,
realized: Option<Content>,
) -> TypResult<Content> {
let content = if let Some(content) = realized {
content content
} else { } else {
let mut children = vec![]; let mut cells = vec![];
let mut number = self.start; let mut number = self.start;
let label = styles.get(Self::LABEL); let label = styles.get(Self::LABEL);
for item in &self.items { for item in &self.items {
number = item.number.unwrap_or(number); number = item.number.unwrap_or(number);
children.push(LayoutNode::default()); cells.push(LayoutNode::default());
children.push(label.resolve(ctx, L, number)?.pack()); cells.push(label.resolve(ctx, L, number)?.pack());
children.push(LayoutNode::default()); cells.push(LayoutNode::default());
children.push((*item.body).clone().pack()); cells.push((*item.body).clone().pack());
number += 1; number += 1;
} }
let leading = styles.get(ParNode::LEADING); let leading = styles.get(ParNode::LEADING);
let spacing = if self.wide { let spacing = if self.tight {
styles.get(ParNode::SPACING)
} else {
styles.get(Self::SPACING) styles.get(Self::SPACING)
} else {
styles.get(ParNode::SPACING)
}; };
let gutter = leading + spacing; let gutter = leading + spacing;
@ -104,7 +121,7 @@ impl<const L: ListKind> Show for ListNode<L> {
TrackSizing::Auto, TrackSizing::Auto,
]), ]),
gutter: Spec::with_y(vec![TrackSizing::Relative(gutter.into())]), gutter: Spec::with_y(vec![TrackSizing::Relative(gutter.into())]),
children, cells,
}) })
}; };
@ -127,7 +144,7 @@ impl<const L: ListKind> Show for ListNode<L> {
impl<const L: ListKind> From<ListItem> for ListNode<L> { impl<const L: ListKind> From<ListItem> for ListNode<L> {
fn from(item: ListItem) -> Self { fn from(item: ListItem) -> Self {
Self { items: vec![item], wide: false, start: 1 } Self { items: vec![item], tight: true, start: 1 }
} }
} }

View File

@ -9,7 +9,7 @@ pub struct TableNode {
/// Defines sizing of gutter rows and columns between content. /// Defines sizing of gutter rows and columns between content.
pub gutter: Spec<Vec<TrackSizing>>, pub gutter: Spec<Vec<TrackSizing>>,
/// The nodes to be arranged in the table. /// The nodes to be arranged in the table.
pub children: Vec<Content>, pub cells: Vec<Content>,
} }
#[node(showable)] #[node(showable)]
@ -37,7 +37,7 @@ impl TableNode {
column_gutter.unwrap_or_else(|| base_gutter.clone()), column_gutter.unwrap_or_else(|| base_gutter.clone()),
row_gutter.unwrap_or(base_gutter), row_gutter.unwrap_or(base_gutter),
), ),
children: args.all()?, cells: args.all()?,
})) }))
} }
@ -53,9 +53,24 @@ impl TableNode {
} }
impl Show for TableNode { impl Show for TableNode {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> { fn encode(&self) -> Dict {
let args = self.children.iter().map(|child| Value::Content(child.clone())); dict! {
if let Some(content) = styles.show::<Self, _>(ctx, args)? { "cells" => Value::Array(
self.cells
.iter()
.map(|cell| Value::Content(cell.clone()))
.collect()
),
}
}
fn show(
&self,
_: &mut Context,
styles: StyleChain,
realized: Option<Content>,
) -> TypResult<Content> {
if let Some(content) = realized {
return Ok(content); return Ok(content);
} }
@ -65,8 +80,8 @@ impl Show for TableNode {
let padding = styles.get(Self::PADDING); let padding = styles.get(Self::PADDING);
let cols = self.tracks.x.len().max(1); let cols = self.tracks.x.len().max(1);
let children = self let cells = self
.children .cells
.iter() .iter()
.cloned() .cloned()
.enumerate() .enumerate()
@ -90,7 +105,7 @@ impl Show for TableNode {
Ok(Content::block(GridNode { Ok(Content::block(GridNode {
tracks: self.tracks.clone(), tracks: self.tracks.clone(),
gutter: self.gutter.clone(), gutter: self.gutter.clone(),
children, cells,
})) }))
} }
} }

View File

@ -43,18 +43,25 @@ impl<const L: DecoLine> DecoNode<L> {
} }
impl<const L: DecoLine> Show for DecoNode<L> { impl<const L: DecoLine> Show for DecoNode<L> {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> { fn encode(&self) -> Dict {
Ok(styles dict! { "body" => Value::Content(self.0.clone()) }
.show::<Self, _>(ctx, [Value::Content(self.0.clone())])? }
.unwrap_or_else(|| {
self.0.clone().styled(TextNode::DECO, Decoration { fn show(
line: L, &self,
stroke: styles.get(Self::STROKE).unwrap_or_default(), _: &mut Context,
offset: styles.get(Self::OFFSET), styles: StyleChain,
extent: styles.get(Self::EXTENT), realized: Option<Content>,
evade: styles.get(Self::EVADE), ) -> TypResult<Content> {
}) Ok(realized.unwrap_or_else(|| {
})) self.0.clone().styled(TextNode::DECO, Decoration {
line: L,
stroke: styles.get(Self::STROKE).unwrap_or_default(),
offset: styles.get(Self::OFFSET),
extent: styles.get(Self::EXTENT),
evade: styles.get(Self::EVADE),
})
}))
} }
} }

View File

@ -28,24 +28,31 @@ impl LinkNode {
} }
impl Show for LinkNode { impl Show for LinkNode {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> { fn encode(&self) -> Dict {
let args = [Value::Str(self.url.clone()), match &self.body { dict! {
Some(body) => Value::Content(body.clone()), "url" => Value::Str(self.url.clone()),
None => Value::None, "body" => match &self.body {
}]; Some(body) => Value::Content(body.clone()),
None => Value::None,
},
}
}
let mut body = styles fn show(
.show::<Self, _>(ctx, args)? &self,
.or_else(|| self.body.clone()) _: &mut Context,
.unwrap_or_else(|| { styles: StyleChain,
let url = &self.url; realized: Option<Content>,
let mut text = url.as_str(); ) -> TypResult<Content> {
for prefix in ["mailto:", "tel:"] { let mut body = realized.or_else(|| self.body.clone()).unwrap_or_else(|| {
text = text.trim_start_matches(prefix); let url = &self.url;
} let mut text = url.as_str();
let shorter = text.len() < url.len(); for prefix in ["mailto:", "tel:"] {
Content::Text(if shorter { text.into() } else { url.clone() }) text = text.trim_start_matches(prefix);
}); }
let shorter = text.len() < url.len();
Content::Text(if shorter { text.into() } else { url.clone() })
});
let mut map = StyleMap::new(); let mut map = StyleMap::new();
map.set(TextNode::LINK, Some(self.url.clone())); map.set(TextNode::LINK, Some(self.url.clone()));

View File

@ -471,10 +471,17 @@ impl StrongNode {
} }
impl Show for StrongNode { impl Show for StrongNode {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> { fn encode(&self) -> Dict {
Ok(styles dict! { "body" => Value::Content(self.0.clone()) }
.show::<Self, _>(ctx, [Value::Content(self.0.clone())])? }
.unwrap_or_else(|| self.0.clone().styled(TextNode::STRONG, Toggle)))
fn show(
&self,
_: &mut Context,
_: StyleChain,
realized: Option<Content>,
) -> TypResult<Content> {
Ok(realized.unwrap_or_else(|| self.0.clone().styled(TextNode::STRONG, Toggle)))
} }
} }
@ -490,9 +497,16 @@ impl EmphNode {
} }
impl Show for EmphNode { impl Show for EmphNode {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> { fn encode(&self) -> Dict {
Ok(styles dict! { "body" => Value::Content(self.0.clone()) }
.show::<Self, _>(ctx, [Value::Content(self.0.clone())])? }
.unwrap_or_else(|| self.0.clone().styled(TextNode::EMPH, Toggle)))
fn show(
&self,
_: &mut Context,
_: StyleChain,
realized: Option<Content>,
) -> TypResult<Content> {
Ok(realized.unwrap_or_else(|| self.0.clone().styled(TextNode::EMPH, Toggle)))
} }
} }

View File

@ -43,7 +43,19 @@ impl RawNode {
} }
impl Show for RawNode { impl Show for RawNode {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> { fn encode(&self) -> Dict {
dict! {
"text" => Value::Str(self.text.clone()),
"block" => Value::Bool(self.block)
}
}
fn show(
&self,
_: &mut Context,
styles: StyleChain,
realized: Option<Content>,
) -> TypResult<Content> {
let lang = styles.get(Self::LANG).as_ref(); let lang = styles.get(Self::LANG).as_ref();
let foreground = THEME let foreground = THEME
.settings .settings
@ -52,16 +64,7 @@ impl Show for RawNode {
.unwrap_or(Color::BLACK) .unwrap_or(Color::BLACK)
.into(); .into();
let args = [ let mut content = if let Some(content) = realized {
Value::Str(self.text.clone()),
match lang {
Some(lang) => Value::Str(lang.clone()),
None => Value::None,
},
Value::Bool(self.block),
];
let mut content = if let Some(content) = styles.show::<Self, _>(ctx, args)? {
content content
} else if matches!( } else if matches!(
lang.map(|s| s.to_lowercase()).as_deref(), lang.map(|s| s.to_lowercase()).as_deref(),

View File

@ -809,19 +809,10 @@ fn show_expr(p: &mut Parser) -> ParseResult {
p.perform(NodeKind::ShowExpr, |p| { p.perform(NodeKind::ShowExpr, |p| {
p.eat_assert(&NodeKind::Show); p.eat_assert(&NodeKind::Show);
ident(p)?; ident(p)?;
if !p.at(&NodeKind::LeftParen) { p.eat_expect(&NodeKind::Colon)?;
p.expected_found("parameter list"); ident(p)?;
return Err(ParseError); p.eat_expect(&NodeKind::As)?;
} expr(p)
p.perform(NodeKind::ClosureExpr, |p| {
let marker = p.marker();
p.start_group(Group::Paren);
collection(p);
p.end_group();
params(p, marker);
p.eat_expect(&NodeKind::As)?;
expr(p)
})
}) })
} }

View File

@ -249,7 +249,7 @@ pub enum Expr {
Let(LetExpr), Let(LetExpr),
/// A set expression: `set text(...)`. /// A set expression: `set text(...)`.
Set(SetExpr), Set(SetExpr),
/// A show expression: `show heading(body) as [*{body}*]`. /// A show expression: `show node: heading as [*{nody.body}*]`.
Show(ShowExpr), Show(ShowExpr),
/// A wrap expression: `wrap body in columns(2, body)`. /// A wrap expression: `wrap body in columns(2, body)`.
Wrap(WrapExpr), Wrap(WrapExpr),
@ -999,19 +999,28 @@ impl SetExpr {
} }
node! { node! {
/// A show expression: `show heading(body) as [*{body}*]`. /// A show expression: `show node: heading as [*{nody.body}*]`.
ShowExpr ShowExpr
} }
impl ShowExpr { impl ShowExpr {
/// The function to customize with this show rule. /// The binding to assign to.
pub fn target(&self) -> Ident { pub fn binding(&self) -> Ident {
self.0.cast_first_child().expect("show rule is missing target") self.0.cast_first_child().expect("show rule is missing binding")
} }
/// The closure that defines the rule. /// The function to customize with this show rule.
pub fn recipe(&self) -> ClosureExpr { pub fn target(&self) -> Ident {
self.0.cast_last_child().expect("show rule is missing closure") self.0
.children()
.filter_map(RedRef::cast)
.nth(1)
.expect("show rule is missing target")
}
/// The expression that realizes the node.
pub fn body(&self) -> Expr {
self.0.cast_last_child().expect("show rule is missing body")
} }
} }

View File

@ -178,13 +178,21 @@ impl Category {
NodeKind::None => Some(Category::None), NodeKind::None => Some(Category::None),
NodeKind::Auto => Some(Category::Auto), NodeKind::Auto => Some(Category::Auto),
NodeKind::Ident(_) => match parent.kind() { NodeKind::Ident(_) => match parent.kind() {
NodeKind::Named => None, NodeKind::Markup(_) => Some(Category::Variable),
NodeKind::ClosureExpr if i == 0 => Some(Category::Function),
NodeKind::SetExpr => Some(Category::Function),
NodeKind::ShowExpr => Some(Category::Function),
NodeKind::FuncCall => Some(Category::Function), NodeKind::FuncCall => Some(Category::Function),
NodeKind::MethodCall if i > 0 => Some(Category::Function), NodeKind::MethodCall if i > 0 => Some(Category::Function),
NodeKind::Markup(_) => Some(Category::Variable), NodeKind::ClosureExpr if i == 0 => Some(Category::Function),
NodeKind::SetExpr => Some(Category::Function),
NodeKind::ShowExpr
if parent
.children()
.filter(|c| matches!(c.kind(), NodeKind::Ident(_)))
.map(RedRef::span)
.nth(1)
.map_or(false, |span| span == child.span()) =>
{
Some(Category::Function)
}
_ => None, _ => None,
}, },
NodeKind::Bool(_) => Some(Category::Bool), NodeKind::Bool(_) => Some(Category::Bool),

View File

@ -671,7 +671,7 @@ pub enum NodeKind {
LetExpr, LetExpr,
/// A set expression: `set text(...)`. /// A set expression: `set text(...)`.
SetExpr, SetExpr,
/// A show expression: `show heading(body) as [*{body}*]`. /// A show expression: `show node: heading as [*{nody.body}*]`.
ShowExpr, ShowExpr,
/// A wrap expression: `wrap body in columns(2, body)`. /// A wrap expression: `wrap body in columns(2, body)`.
WrapExpr, WrapExpr,

View File

@ -4,13 +4,13 @@
#let i = 1 #let i = 1
#set heading(size: 1em) #set heading(size: 1em)
#show heading(level, body) as { #show node: heading as {
if level == 1 { if node.level == 1 {
v(10pt) v(10pt)
underline(text(1.5em, blue)[{i}. #body]) underline(text(1.5em, blue)[{i}. {node.body}])
i += 1 i += 1
} else { } else {
text(red, body) text(red, node.body)
} }
} }
@ -30,29 +30,23 @@ Another text.
--- ---
#set heading(size: 1em, strong: false, block: false) #set heading(size: 1em, strong: false, block: false)
#show heading(a, b) as [B] #show _: heading as [B]
A [= Heading] C A [= Heading] C
--- ---
// Error: 14-22 unexpected argument // Error: 21-25 expected content, found string
#show heading() as [] #show _: heading as "hi"
= Heading = Heading
--- ---
// Error: 14-28 expected content, found string // Error: 22-29 dictionary does not contain key: "page"
#show heading(_, _) as "hi" #show it: heading as it.page
= Heading = Heading
--- ---
// Error: 7-12 this function cannot be customized with show // Error: 10-15 this function cannot be customized with show
#show upper() as {} #show _: upper as {}
---
// Ref: false
// // Error: 1-29 show rule is recursive
// #show strong(x) as strong(x)
// *Hi*
--- ---
// Error: 2-19 set, show and wrap are only allowed directly in markup // Error: 2-19 set, show and wrap are only allowed directly in markup
{show list(a) as b} {show a: list as a}