diff --git a/library/src/layout/page.rs b/library/src/layout/page.rs index e1af6ec55..c308571ca 100644 --- a/library/src/layout/page.rs +++ b/library/src/layout/page.rs @@ -200,7 +200,7 @@ impl Cast> for Marginal { fn cast(value: Spanned) -> StrResult { match value.v { Value::None => Ok(Self::None), - Value::Str(v) => Ok(Self::Content(TextNode(v.into()).pack())), + Value::Str(v) => Ok(Self::Content(TextNode::packed(v))), Value::Content(v) => Ok(Self::Content(v)), Value::Func(v) => Ok(Self::Func(v, value.span)), v => Err(format!( diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs index dae869ede..0fad2939f 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -18,7 +18,7 @@ pub struct MathNode { pub display: bool, } -#[node(Show, LayoutInline, Texify)] +#[node(Show, Finalize, LayoutInline, Texify)] impl MathNode { /// The math font family. #[property(referenced)] @@ -29,6 +29,13 @@ impl MathNode { /// The spacing below display math. #[property(resolve, shorthand(around))] pub const BELOW: Option = Some(Ratio::one().into()); + + fn field(&self, name: &str) -> Option { + match name { + "display" => Some(Value::Bool(self.display)), + _ => None, + } + } } impl Show for MathNode { @@ -36,18 +43,16 @@ impl Show for MathNode { self.clone().pack() } - fn field(&self, _: &str) -> Option { - None - } - - fn realize(&self, _: Tracked, _: StyleChain) -> SourceResult { + fn show(&self, _: Tracked, _: StyleChain) -> SourceResult { Ok(if self.display { self.clone().pack().aligned(Axes::with_x(Some(Align::Center.into()))) } else { self.clone().pack() }) } +} +impl Finalize for MathNode { fn finalize( &self, _: Tracked, diff --git a/library/src/prelude.rs b/library/src/prelude.rs index 11095c67c..f51b826fd 100644 --- a/library/src/prelude.rs +++ b/library/src/prelude.rs @@ -9,8 +9,8 @@ pub use typst::frame::*; pub use typst::geom::*; pub use typst::model::{ array, capability, castable, dict, dynamic, format_str, node, Args, Array, Cast, - Content, Dict, Fold, Func, Key, Node, Resolve, Scope, Selector, Show, Smart, Str, - StyleChain, StyleMap, StyleVec, Value, Vm, + Content, Dict, Finalize, Fold, Func, Key, Node, Resolve, Scope, Selector, Show, + Smart, Str, StyleChain, StyleMap, StyleVec, Value, Vm, }; pub use typst::syntax::{Span, Spanned}; pub use typst::util::{format_eco, EcoString}; diff --git a/library/src/structure/heading.rs b/library/src/structure/heading.rs index 62a670009..46e98c186 100644 --- a/library/src/structure/heading.rs +++ b/library/src/structure/heading.rs @@ -12,7 +12,7 @@ pub struct HeadingNode { pub body: Content, } -#[node(Show)] +#[node(Show, Finalize)] impl HeadingNode { /// The heading's font family. Just the normal text family if `auto`. #[property(referenced)] @@ -67,12 +67,6 @@ impl HeadingNode { } .pack()) } -} - -impl Show for HeadingNode { - fn unguard_parts(&self, sel: Selector) -> Content { - Self { body: self.body.unguard(sel), ..*self }.pack() - } fn field(&self, name: &str) -> Option { match name { @@ -81,11 +75,19 @@ impl Show for HeadingNode { _ => None, } } +} - fn realize(&self, _: Tracked, _: StyleChain) -> SourceResult { - Ok(BlockNode(self.body.clone()).pack()) +impl Show for HeadingNode { + fn unguard_parts(&self, sel: Selector) -> Content { + Self { body: self.body.unguard(sel), ..*self }.pack() } + fn show(&self, _: Tracked, _: StyleChain) -> SourceResult { + Ok(BlockNode(self.body.clone()).pack()) + } +} + +impl Finalize for HeadingNode { fn finalize( &self, world: Tracked, diff --git a/library/src/structure/list.rs b/library/src/structure/list.rs index a5e1380ad..499207a4c 100644 --- a/library/src/structure/list.rs +++ b/library/src/structure/list.rs @@ -22,7 +22,7 @@ pub type EnumNode = ListNode; /// A description list. pub type DescNode = ListNode; -#[node(Show)] +#[node(Show, Finalize)] impl ListNode { /// How the list is labelled. #[property(referenced)] @@ -80,16 +80,6 @@ impl ListNode { } .pack()) } -} - -impl Show for ListNode { - fn unguard_parts(&self, sel: Selector) -> Content { - Self { - items: self.items.map(|item| item.unguard(sel)), - ..*self - } - .pack() - } fn field(&self, name: &str) -> Option { match name { @@ -101,8 +91,18 @@ impl Show for ListNode { _ => None, } } +} - fn realize( +impl Show for ListNode { + fn unguard_parts(&self, sel: Selector) -> Content { + Self { + items: self.items.map(|item| item.unguard(sel)), + ..*self + } + .pack() + } + + fn show( &self, world: Tracked, styles: StyleChain, @@ -140,7 +140,7 @@ impl Show for ListNode { ListItem::Enum(_, body) => body.as_ref().clone(), ListItem::Desc(item) => Content::sequence(vec![ HNode { amount: (-body_indent).into(), weak: false }.pack(), - (item.term.clone() + TextNode(':'.into()).pack()).strong(), + (item.term.clone() + TextNode::packed(':')).strong(), SpaceNode.pack(), item.body.clone(), ]), @@ -162,7 +162,9 @@ impl Show for ListNode { } .pack()) } +} +impl Finalize for ListNode { fn finalize( &self, _: Tracked, @@ -312,14 +314,14 @@ impl Label { ) -> SourceResult { Ok(match self { Self::Default => match kind { - LIST => TextNode('•'.into()).pack(), - ENUM => TextNode(format_eco!("{}.", number)).pack(), + LIST => TextNode::packed('•'), + ENUM => TextNode::packed(format_eco!("{}.", number)), DESC | _ => panic!("description lists don't have a label"), }, Self::Pattern(prefix, numbering, upper, suffix) => { let fmt = numbering.apply(number); let mid = if *upper { fmt.to_uppercase() } else { fmt.to_lowercase() }; - TextNode(format_eco!("{}{}{}", prefix, mid, suffix)).pack() + TextNode::packed(format_eco!("{}{}{}", prefix, mid, suffix)) } Self::Content(content) => content.clone(), Self::Func(func, span) => { diff --git a/library/src/structure/reference.rs b/library/src/structure/reference.rs index 56f8b8e33..18f4eecb4 100644 --- a/library/src/structure/reference.rs +++ b/library/src/structure/reference.rs @@ -8,7 +8,14 @@ pub struct RefNode(pub EcoString); #[node(Show)] impl RefNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { - Ok(Self(args.expect("label")?).pack()) + Ok(Self(args.expect("target")?).pack()) + } + + fn field(&self, name: &str) -> Option { + match name { + "target" => Some(Value::Str(self.0.clone().into())), + _ => None, + } } } @@ -17,14 +24,7 @@ impl Show for RefNode { Self(self.0.clone()).pack() } - fn field(&self, name: &str) -> Option { - match name { - "label" => Some(Value::Str(self.0.clone().into())), - _ => None, - } - } - - fn realize(&self, _: Tracked, _: StyleChain) -> SourceResult { - Ok(TextNode(format_eco!("@{}", self.0)).pack()) + fn show(&self, _: Tracked, _: StyleChain) -> SourceResult { + Ok(TextNode::packed(format_eco!("@{}", self.0))) } } diff --git a/library/src/structure/table.rs b/library/src/structure/table.rs index 722f11e6a..fbf1c7c0f 100644 --- a/library/src/structure/table.rs +++ b/library/src/structure/table.rs @@ -12,7 +12,7 @@ pub struct TableNode { pub cells: Vec, } -#[node(Show)] +#[node(Show, Finalize)] impl TableNode { /// How to fill the cells. #[property(referenced)] @@ -46,6 +46,15 @@ impl TableNode { } .pack()) } + + fn field(&self, name: &str) -> Option { + match name { + "cells" => Some(Value::Array( + self.cells.iter().cloned().map(Value::Content).collect(), + )), + _ => None, + } + } } impl Show for TableNode { @@ -58,16 +67,7 @@ impl Show for TableNode { .pack() } - fn field(&self, name: &str) -> Option { - match name { - "cells" => Some(Value::Array( - self.cells.iter().cloned().map(Value::Content).collect(), - )), - _ => None, - } - } - - fn realize( + fn show( &self, world: Tracked, styles: StyleChain, @@ -106,7 +106,9 @@ impl Show for TableNode { } .pack()) } +} +impl Finalize for TableNode { fn finalize( &self, _: Tracked, diff --git a/library/src/text/deco.rs b/library/src/text/deco.rs index 10f3db381..fa0f05a70 100644 --- a/library/src/text/deco.rs +++ b/library/src/text/deco.rs @@ -37,12 +37,6 @@ impl DecoNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Self(args.expect("body")?).pack()) } -} - -impl Show for DecoNode { - fn unguard_parts(&self, sel: Selector) -> Content { - Self(self.0.unguard(sel)).pack() - } fn field(&self, name: &str) -> Option { match name { @@ -50,12 +44,14 @@ impl Show for DecoNode { _ => None, } } +} - fn realize( - &self, - _: Tracked, - styles: StyleChain, - ) -> SourceResult { +impl Show for DecoNode { + fn unguard_parts(&self, sel: Selector) -> Content { + Self(self.0.unguard(sel)).pack() + } + + fn show(&self, _: Tracked, styles: StyleChain) -> SourceResult { Ok(self.0.clone().styled( TextNode::DECO, Decoration { @@ -81,6 +77,15 @@ pub(super) struct Decoration { pub evade: bool, } +impl Fold for Decoration { + type Output = Vec; + + fn fold(self, mut outer: Self::Output) -> Self::Output { + outer.insert(0, self); + outer + } +} + /// A kind of decorative line. pub type DecoLine = usize; diff --git a/library/src/text/link.rs b/library/src/text/link.rs index 82abe5cd4..4312559e9 100644 --- a/library/src/text/link.rs +++ b/library/src/text/link.rs @@ -17,7 +17,7 @@ impl LinkNode { } } -#[node(Show)] +#[node(Show, Finalize)] impl LinkNode { /// The fill color of text in the link. Just the surrounding text color /// if `auto`. @@ -33,16 +33,6 @@ impl LinkNode { }; Ok(Self { dest, body }.pack()) } -} - -impl Show for LinkNode { - fn unguard_parts(&self, sel: Selector) -> Content { - Self { - dest: self.dest.clone(), - body: self.body.as_ref().map(|body| body.unguard(sel)), - } - .pack() - } fn field(&self, name: &str) -> Option { match name { @@ -57,25 +47,33 @@ impl Show for LinkNode { _ => None, } } +} - fn realize(&self, _: Tracked, _: StyleChain) -> SourceResult { - Ok(self - .body - .clone() - .unwrap_or_else(|| match &self.dest { - Destination::Url(url) => { - let mut text = url.as_str(); - for prefix in ["mailto:", "tel:"] { - text = text.trim_start_matches(prefix); - } - let shorter = text.len() < url.len(); - TextNode(if shorter { text.into() } else { url.clone() }).pack() - } - Destination::Internal(_) => Content::empty(), - }) - .styled(TextNode::LINK, Some(self.dest.clone()))) +impl Show for LinkNode { + fn unguard_parts(&self, sel: Selector) -> Content { + Self { + dest: self.dest.clone(), + body: self.body.as_ref().map(|body| body.unguard(sel)), + } + .pack() } + fn show(&self, _: Tracked, _: StyleChain) -> SourceResult { + Ok(self.body.clone().unwrap_or_else(|| match &self.dest { + Destination::Url(url) => { + let mut text = url.as_str(); + for prefix in ["mailto:", "tel:"] { + text = text.trim_start_matches(prefix); + } + let shorter = text.len() < url.len(); + TextNode::packed(if shorter { text.into() } else { url.clone() }) + } + Destination::Internal(_) => Content::empty(), + })) + } +} + +impl Finalize for LinkNode { fn finalize( &self, _: Tracked, @@ -83,6 +81,8 @@ impl Show for LinkNode { mut realized: Content, ) -> SourceResult { let mut map = StyleMap::new(); + map.set(TextNode::LINK, Some(self.dest.clone())); + if let Smart::Custom(fill) = styles.get(Self::FILL) { map.set(TextNode::FILL, fill); } diff --git a/library/src/text/mod.rs b/library/src/text/mod.rs index 6643f821c..86c6884a2 100644 --- a/library/src/text/mod.rs +++ b/library/src/text/mod.rs @@ -28,6 +28,13 @@ use crate::prelude::*; #[derive(Debug, Clone, Hash)] pub struct TextNode(pub EcoString); +impl TextNode { + /// Create a new packed text node. + pub fn packed(text: impl Into) -> Content { + Self(text.into()).pack() + } +} + #[node] impl TextNode { /// A prioritized sequence of font families. @@ -486,12 +493,6 @@ impl StrongNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Self(args.expect("body")?).pack()) } -} - -impl Show for StrongNode { - fn unguard_parts(&self, sel: Selector) -> Content { - Self(self.0.unguard(sel)).pack() - } fn field(&self, name: &str) -> Option { match name { @@ -499,8 +500,14 @@ impl Show for StrongNode { _ => None, } } +} - fn realize(&self, _: Tracked, _: StyleChain) -> SourceResult { +impl Show for StrongNode { + fn unguard_parts(&self, sel: Selector) -> Content { + Self(self.0.unguard(sel)).pack() + } + + fn show(&self, _: Tracked, _: StyleChain) -> SourceResult { Ok(self.0.clone().styled(TextNode::BOLD, Toggle)) } } @@ -514,12 +521,6 @@ impl EmphNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Self(args.expect("body")?).pack()) } -} - -impl Show for EmphNode { - fn unguard_parts(&self, sel: Selector) -> Content { - Self(self.0.unguard(sel)).pack() - } fn field(&self, name: &str) -> Option { match name { @@ -527,8 +528,14 @@ impl Show for EmphNode { _ => None, } } +} - fn realize(&self, _: Tracked, _: StyleChain) -> SourceResult { +impl Show for EmphNode { + fn unguard_parts(&self, sel: Selector) -> Content { + Self(self.0.unguard(sel)).pack() + } + + fn show(&self, _: Tracked, _: StyleChain) -> SourceResult { Ok(self.0.clone().styled(TextNode::ITALIC, Toggle)) } } @@ -544,12 +551,3 @@ impl Fold for Toggle { !outer } } - -impl Fold for Decoration { - type Output = Vec; - - fn fold(self, mut outer: Self::Output) -> Self::Output { - outer.insert(0, self); - outer - } -} diff --git a/library/src/text/raw.rs b/library/src/text/raw.rs index 31f1517e3..5a98cf3bd 100644 --- a/library/src/text/raw.rs +++ b/library/src/text/raw.rs @@ -19,7 +19,7 @@ pub struct RawNode { pub block: bool, } -#[node(Show)] +#[node(Show, Finalize)] impl RawNode { /// The language to syntax-highlight in. #[property(referenced)] @@ -41,12 +41,6 @@ impl RawNode { } .pack()) } -} - -impl Show for RawNode { - fn unguard_parts(&self, _: Selector) -> Content { - Self { text: self.text.clone(), ..*self }.pack() - } fn field(&self, name: &str) -> Option { match name { @@ -55,12 +49,14 @@ impl Show for RawNode { _ => None, } } +} - fn realize( - &self, - _: Tracked, - styles: StyleChain, - ) -> SourceResult { +impl Show for RawNode { + fn unguard_parts(&self, _: Selector) -> Content { + Self { text: self.text.clone(), ..*self }.pack() + } + + fn show(&self, _: Tracked, styles: StyleChain) -> SourceResult { let lang = styles.get(Self::LANG).as_ref().map(|s| s.to_lowercase()); let foreground = THEME .settings @@ -100,7 +96,7 @@ impl Show for RawNode { Content::sequence(seq) } else { - TextNode(self.text.clone()).pack() + TextNode::packed(self.text.clone()) }; if self.block { @@ -114,7 +110,9 @@ impl Show for RawNode { Ok(realized.styled_with_map(map)) } +} +impl Finalize for RawNode { fn finalize( &self, _: Tracked, @@ -134,7 +132,7 @@ impl Show for RawNode { /// Style a piece of text with a syntect style. fn styled(piece: &str, foreground: Paint, style: Style) -> Content { - let mut body = TextNode(piece.into()).pack(); + let mut body = TextNode::packed(piece); let paint = style.foreground.into(); if paint != foreground { diff --git a/library/src/text/shift.rs b/library/src/text/shift.rs index 1117cc00b..0f654b5a9 100644 --- a/library/src/text/shift.rs +++ b/library/src/text/shift.rs @@ -33,12 +33,6 @@ impl ShiftNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Self(args.expect("body")?).pack()) } -} - -impl Show for ShiftNode { - fn unguard_parts(&self, _: Selector) -> Content { - Self(self.0.clone()).pack() - } fn field(&self, name: &str) -> Option { match name { @@ -46,8 +40,14 @@ impl Show for ShiftNode { _ => None, } } +} - fn realize( +impl Show for ShiftNode { + fn unguard_parts(&self, _: Selector) -> Content { + Self(self.0.clone()).pack() + } + + fn show( &self, world: Tracked, styles: StyleChain, @@ -56,7 +56,7 @@ impl Show for ShiftNode { if styles.get(Self::TYPOGRAPHIC) { if let Some(text) = search_text(&self.0, S) { if is_shapable(world, &text, styles) { - transformed = Some(TextNode(text).pack()); + transformed = Some(TextNode::packed(text)); } } }; diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 8e44a8c35..eaa929dc8 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -47,6 +47,7 @@ fn expand_node( let mut properties = vec![]; let mut construct = None; let mut set = None; + let mut field = None; for item in std::mem::take(&mut impl_block.items) { match item { @@ -61,6 +62,7 @@ fn expand_node( match method.sig.ident.to_string().as_str() { "construct" => construct = Some(method), "set" => set = Some(method), + "field" => field = Some(method), _ => return Err(Error::new(method.span(), "unexpected method")), } } @@ -81,6 +83,14 @@ fn expand_node( let set = generate_set(&properties, set); + let field = field.unwrap_or_else(|| { + parse_quote! { + fn field(&self, name: &str) -> Option { + None + } + } + }); + let items: syn::punctuated::Punctuated = parse_quote! { #stream }; @@ -115,11 +125,13 @@ fn expand_node( impl<#params> model::Node for #self_ty { #construct #set - #vtable + #field fn id(&self) -> model::NodeId { model::NodeId::of::() } + + #vtable } #(#key_modules)* diff --git a/src/model/cast.rs b/src/model/cast.rs index cbb2952d6..7a466b72e 100644 --- a/src/model/cast.rs +++ b/src/model/cast.rs @@ -1,7 +1,7 @@ use std::num::NonZeroUsize; use std::str::FromStr; -use super::{Pattern, Regex, Value}; +use super::{Content, Pattern, Regex, Transform, Value}; use crate::diag::{with_alternative, StrResult}; use crate::font::{FontStretch, FontStyle, FontWeight}; use crate::frame::{Destination, Lang, Location, Region}; @@ -189,6 +189,15 @@ castable! { @regex: Regex => Self::Regex(regex.clone()), } +castable! { + Transform, + Expected: "content or function", + Value::None => Self::Content(Content::empty()), + Value::Str(text) => Self::Content(item!(text)(text.into())), + Value::Content(content) => Self::Content(content), + Value::Func(func) => Self::Func(func), +} + dynamic! { Dir: "direction", } diff --git a/src/model/content.rs b/src/model/content.rs index 7b09c697d..0257f4da5 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -5,13 +5,14 @@ use std::iter::{self, Sum}; use std::ops::{Add, AddAssign}; use std::sync::Arc; +use comemo::Tracked; use siphasher::sip128::{Hasher128, SipHasher}; use typst_macros::node; -use super::{Args, Key, Property, Selector, StyleEntry, StyleMap, Vm}; -use crate as typst; +use super::{Args, Key, Property, Recipe, Selector, StyleEntry, StyleMap, Value, Vm}; use crate::diag::{SourceResult, StrResult}; -use crate::util::{EcoString, ReadableTypeId}; +use crate::util::ReadableTypeId; +use crate::World; /// Composable representation of styled content. /// @@ -27,11 +28,6 @@ impl Content { SequenceNode(vec![]).pack() } - /// Create content from a string of text. - pub fn text(text: impl Into) -> Self { - item!(text)(text.into()) - } - /// Create a new sequence node from multiples nodes. pub fn sequence(seq: Vec) -> Self { match seq.as_slice() { @@ -65,6 +61,11 @@ impl Content { Arc::get_mut(&mut self.0)?.as_any_mut().downcast_mut::() } + /// Access a field on this content. + pub fn field(&self, name: &str) -> Option { + self.0.field(name) + } + /// Whether this content has the given capability. pub fn has(&self) -> bool where @@ -97,6 +98,19 @@ impl Content { self.styled_with_entry(StyleEntry::Property(Property::new(key, value))) } + /// Style this content with a recipe, eagerly applying it if possible. + pub fn styled_with_recipe( + self, + world: Tracked, + recipe: Recipe, + ) -> SourceResult { + if recipe.pattern.is_none() { + recipe.transform.apply(world, recipe.span, || Value::Content(self)) + } else { + Ok(self.styled_with_entry(StyleEntry::Recipe(recipe))) + } + } + /// Style this content with a style entry. pub fn styled_with_entry(mut self, entry: StyleEntry) -> Self { if let Some(styled) = self.try_downcast_mut::() { @@ -242,6 +256,9 @@ pub trait Node: 'static { where Self: Sized; + /// Access a field on this node. + fn field(&self, name: &str) -> Option; + /// A unique identifier of the node type. fn id(&self) -> NodeId; diff --git a/src/model/eval.rs b/src/model/eval.rs index 16e66818d..fd43c4c31 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -7,7 +7,7 @@ use unicode_segmentation::UnicodeSegmentation; use super::{ methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, Dict, Flow, Func, - Pattern, Recipe, Scope, Scopes, Show, StyleEntry, StyleMap, Value, Vm, + Pattern, Recipe, Scope, Scopes, StyleMap, Transform, Value, Vm, }; use crate::diag::{bail, error, At, SourceResult, StrResult, Trace, Tracepoint}; use crate::geom::{Abs, Angle, Em, Fr, Ratio}; @@ -133,12 +133,8 @@ fn eval_markup( break; } - eval_markup(vm, nodes)?.styled_with_entry(StyleEntry::Recipe(recipe)) - } - ast::MarkupNode::Expr(ast::Expr::Wrap(wrap)) => { let tail = eval_markup(vm, nodes)?; - vm.scopes.top.define(wrap.binding().take(), tail); - wrap.body().eval(vm)?.display(vm.world) + tail.styled_with_recipe(vm.world, recipe)? } _ => node.eval(vm)?, }); @@ -408,7 +404,6 @@ impl Eval for ast::Expr { Self::Let(v) => v.eval(vm), Self::Set(_) => bail!(forbidden("set")), Self::Show(_) => bail!(forbidden("show")), - Self::Wrap(_) => bail!(forbidden("wrap")), Self::Conditional(v) => v.eval(vm), Self::While(v) => v.eval(vm), Self::For(v) => v.eval(vm), @@ -484,18 +479,12 @@ fn eval_code( } ast::Expr::Show(show) => { let recipe = show.eval(vm)?; - let entry = StyleEntry::Recipe(recipe); if vm.flow.is_some() { break; } let tail = eval_code(vm, exprs)?.display(vm.world); - Value::Content(tail.styled_with_entry(entry)) - } - ast::Expr::Wrap(wrap) => { - let tail = eval_code(vm, exprs)?; - vm.scopes.top.define(wrap.binding().take(), tail); - wrap.body().eval(vm)? + Value::Content(tail.styled_with_recipe(vm.world, recipe)?) } _ => expr.eval(vm)?, }; @@ -671,9 +660,8 @@ impl Eval for ast::FieldAccess { Ok(match object { Value::Dict(dict) => dict.get(&field).at(span)?.clone(), - Value::Content(node) => node - .to::() - .and_then(|node| node.field(&field)) + Value::Content(content) => content + .field(&field) .ok_or_else(|| format!("unknown field {field:?}")) .at(span)?, v => bail!(self.target().span(), "cannot access field on {}", v.type_name()), @@ -838,6 +826,11 @@ impl Eval for ast::SetRule { type Output = StyleMap; fn eval(&self, vm: &mut Vm) -> SourceResult { + if let Some(condition) = self.condition() { + if !condition.eval(vm)?.cast::().at(condition.span())? { + return Ok(StyleMap::new()); + } + } let target = self.target(); let target = target.eval(vm)?.cast::().at(target.span())?; let args = self.args().eval(vm)?; @@ -849,36 +842,16 @@ impl Eval for ast::ShowRule { type Output = Recipe; fn eval(&self, vm: &mut Vm) -> SourceResult { - // Evaluate the target function. - let pattern = self.pattern(); - let pattern = pattern.eval(vm)?.cast::().at(pattern.span())?; + let pattern = self + .pattern() + .map(|pattern| pattern.eval(vm)?.cast::().at(pattern.span())) + .transpose()?; - // Collect captured variables. - let captured = { - let mut visitor = CapturesVisitor::new(&vm.scopes); - visitor.visit(self.as_untyped()); - visitor.finish() - }; + let transform = self.transform(); + let span = transform.span(); + let transform = transform.eval(vm)?.cast::().at(span)?; - // Define parameters. - let mut params = vec![]; - if let Some(binding) = self.binding() { - params.push((binding.take(), None)); - } - - // Define the recipe function. - let body = self.body(); - let span = body.span(); - let func = Func::from_closure(Closure { - location: vm.location, - name: None, - captured, - params, - sink: None, - body, - }); - - Ok(Recipe { pattern, func: Spanned::new(func, span) }) + Ok(Recipe { span, pattern, transform }) } } @@ -1066,7 +1039,7 @@ fn import(vm: &mut Vm, path: &str, span: Span) -> SourceResult { Ok(module) } -impl Eval for ast::BreakStmt { +impl Eval for ast::LoopBreak { type Output = Value; fn eval(&self, vm: &mut Vm) -> SourceResult { @@ -1077,7 +1050,7 @@ impl Eval for ast::BreakStmt { } } -impl Eval for ast::ContinueStmt { +impl Eval for ast::LoopContinue { type Output = Value; fn eval(&self, vm: &mut Vm) -> SourceResult { @@ -1088,7 +1061,7 @@ impl Eval for ast::ContinueStmt { } } -impl Eval for ast::ReturnStmt { +impl Eval for ast::FuncReturn { type Output = Value; fn eval(&self, vm: &mut Vm) -> SourceResult { diff --git a/src/model/func.rs b/src/model/func.rs index 456b6aa68..8cedb158f 100644 --- a/src/model/func.rs +++ b/src/model/func.rs @@ -310,16 +310,6 @@ impl<'a> CapturesVisitor<'a> { self.bind(expr.binding()); } - // A show rule contains a binding, but that binding is only active - // after the target has been evaluated. - Some(ast::Expr::Show(show)) => { - self.visit(show.pattern().as_untyped()); - if let Some(binding) = show.binding() { - self.bind(binding); - } - self.visit(show.body().as_untyped()); - } - // A for loop contains one or two bindings in its pattern. These are // active after the iterable is evaluated but before the body is // evaluated. @@ -391,9 +381,9 @@ mod tests { 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"]); + test("#show y: x => x", &["y"]); + test("#show y: x => x + z", &["y", "z"]); + test("#show x: x => x", &["x"]); // For loop. test("#for x in y { x + z }", &["y", "z"]); diff --git a/src/model/items.rs b/src/model/items.rs index 164d9602a..e9c23c264 100644 --- a/src/model/items.rs +++ b/src/model/items.rs @@ -48,11 +48,11 @@ pub struct LangItems { pub em: fn(StyleChain) -> Abs, /// Access the text direction. pub dir: fn(StyleChain) -> Dir, - /// A space. + /// Whitespace. pub space: fn() -> Content, - /// A forced line break. + /// A forced line break: `\`. pub linebreak: fn(justify: bool) -> Content, - /// Plain text. + /// Plain text without markup. pub text: fn(text: EcoString) -> Content, /// A smart quote: `'` or `"`. pub smart_quote: fn(double: bool) -> Content, @@ -72,18 +72,18 @@ pub struct LangItems { pub heading: fn(level: NonZeroUsize, body: Content) -> Content, /// An item in an unordered list: `- ...`. pub list_item: fn(body: Content) -> Content, - /// An item in an enumeration (ordered list): `1. ...`. + /// An item in an enumeration (ordered list): `+ ...` or `1. ...`. pub enum_item: fn(number: Option, body: Content) -> Content, /// An item in a description list: `/ Term: Details`. pub desc_item: fn(term: Content, body: Content) -> Content, - /// A math formula: `$x$`, `$ x^2 $`. + /// A mathematical formula: `$x$`, `$ x^2 $`. pub math: fn(children: Vec, display: bool) -> Content, - /// A atom in a formula: `x`, `+`, `12`. + /// An atom in a formula: `x`, `+`, `12`. pub math_atom: fn(atom: EcoString) -> Content, - /// A base with an optional sub- and superscript in a formula: `a_1^2`. + /// A base with optional sub- and superscripts in a formula: `a_1^2`. pub math_script: fn(base: Content, sub: Option, sup: Option) -> Content, - /// A fraction in a formula: `x/2` + /// A fraction in a formula: `x/2`. pub math_frac: fn(num: Content, denom: Content) -> Content, /// An alignment indicator in a formula: `&`, `&&`. pub math_align: fn(count: usize) -> Content, diff --git a/src/model/ops.rs b/src/model/ops.rs index 9d55fa63b..0110fb96c 100644 --- a/src/model/ops.rs +++ b/src/model/ops.rs @@ -19,8 +19,8 @@ pub fn join(lhs: Value, rhs: Value) -> StrResult { (a, None) => a, (None, b) => b, (Str(a), Str(b)) => Str(a + b), - (Str(a), Content(b)) => Content(super::Content::text(a) + b), - (Content(a), Str(b)) => Content(a + super::Content::text(b)), + (Str(a), Content(b)) => Content(item!(text)(a.into()) + b), + (Content(a), Str(b)) => Content(a + item!(text)(b.into())), (Content(a), Content(b)) => Content(a + b), (Array(a), Array(b)) => Array(a + b), (Dict(a), Dict(b)) => Dict(a + b), @@ -85,8 +85,8 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult { (Str(a), Str(b)) => Str(a + b), (Content(a), Content(b)) => Content(a + b), - (Content(a), Str(b)) => Content(a + super::Content::text(b)), - (Str(a), Content(b)) => Content(super::Content::text(a) + b), + (Content(a), Str(b)) => Content(a + item!(text)(b.into())), + (Str(a), Content(b)) => Content(item!(text)(a.into()) + b), (Array(a), Array(b)) => Array(a + b), (Dict(a), Dict(b)) => Dict(a + b), diff --git a/src/model/styles.rs b/src/model/styles.rs index 9463e55e5..3800490b8 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -12,7 +12,7 @@ use crate::diag::SourceResult; use crate::geom::{ Abs, Align, Axes, Corners, Em, GenAlign, Length, Numeric, PartialStroke, Rel, Sides, }; -use crate::syntax::Spanned; +use crate::syntax::Span; use crate::util::ReadableTypeId; use crate::World; @@ -283,17 +283,17 @@ impl<'a> StyleChain<'a> { .unguard_parts(sel) .to::() .unwrap() - .realize(world, self)?; + .show(world, 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.to::().unwrap().finalize(world, self, content)?, - ); + if let Some(node) = node.to::() { + if let Some(content) = realized { + realized = Some(node.finalize(world, self, content)?); + } } } } @@ -974,18 +974,20 @@ impl Fold for PartialStroke { /// A show rule recipe. #[derive(Clone, PartialEq, Hash)] pub struct Recipe { - /// The patterns to customize. - pub pattern: Pattern, - /// The function that defines the recipe. - pub func: Spanned, + /// The span errors are reported with. + pub span: Span, + /// The pattern that the rule applies to. + pub pattern: Option, + /// The transformation to perform on the match. + pub transform: Transform, } impl Recipe { /// Whether the recipe is applicable to the target. pub fn applicable(&self, target: Target) -> bool { match (&self.pattern, target) { - (Pattern::Node(id), Target::Node(node)) => *id == node.id(), - (Pattern::Regex(_), Target::Text(_)) => true, + (Some(Pattern::Node(id)), Target::Node(node)) => *id == node.id(), + (Some(Pattern::Regex(_)), Target::Text(_)) => true, _ => false, } } @@ -998,12 +1000,13 @@ impl Recipe { target: Target, ) -> SourceResult> { let content = match (target, &self.pattern) { - (Target::Node(node), &Pattern::Node(id)) if node.id() == id => { - let node = node.to::().unwrap().unguard_parts(sel); - self.call(world, || Value::Content(node))? + (Target::Node(node), Some(Pattern::Node(id))) if node.id() == *id => { + self.transform.apply(world, self.span, || { + Value::Content(node.to::().unwrap().unguard_parts(sel)) + })? } - (Target::Text(text), Pattern::Regex(regex)) => { + (Target::Text(text), Some(Pattern::Regex(regex))) => { let make = world.config().items.text; let mut result = vec![]; let mut cursor = 0; @@ -1014,7 +1017,10 @@ impl Recipe { result.push(make(text[cursor..start].into())); } - result.push(self.call(world, || Value::Str(mat.as_str().into()))?); + let transformed = self + .transform + .apply(world, self.span, || Value::Str(mat.as_str().into()))?; + result.push(transformed); cursor = mat.end(); } @@ -1035,24 +1041,10 @@ impl Recipe { Ok(Some(content.styled_with_entry(StyleEntry::Guard(sel)))) } - /// Call the recipe function, with the argument if desired. - fn call(&self, world: Tracked, arg: F) -> SourceResult - where - F: FnOnce() -> Value, - { - let args = if self.func.v.argc() == Some(0) { - Args::new(self.func.span, []) - } else { - Args::new(self.func.span, [arg()]) - }; - - Ok(self.func.v.call_detached(world, args)?.display(world)) - } - /// Whether this recipe is for the given node. pub fn is_of(&self, node: NodeId) -> bool { match self.pattern { - Pattern::Node(id) => id == node, + Some(Pattern::Node(id)) => id == node, _ => false, } } @@ -1060,7 +1052,7 @@ impl Recipe { impl Debug for Recipe { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "Recipe matching {:?} from {:?}", self.pattern, self.func.span) + write!(f, "Recipe matching {:?}", self.pattern) } } @@ -1080,6 +1072,36 @@ impl Pattern { } } +/// A show rule transformation that can be applied to a match. +#[derive(Debug, Clone, PartialEq, Hash)] +pub enum Transform { + /// Replacement content. + Content(Content), + /// A function to apply to the match. + Func(Func), +} + +impl Transform { + /// Apply the transform. + pub fn apply( + &self, + world: Tracked, + span: Span, + arg: F, + ) -> SourceResult + where + F: FnOnce() -> Value, + { + match self { + Transform::Content(content) => Ok(content.clone()), + Transform::Func(func) => { + let args = Args::new(span, [arg()]); + Ok(func.call_detached(world, args)?.display(world)) + } + } + } +} + /// A target for a show rule recipe. #[derive(Debug, Copy, Clone, PartialEq)] pub enum Target<'a> { @@ -1104,30 +1126,25 @@ pub trait Show: 'static + Sync + Send { /// Unguard nested content against recursive show rules. fn unguard_parts(&self, sel: Selector) -> Content; - /// Access a field on this node. - fn field(&self, name: &str) -> Option; - /// The base recipe for this node that is executed if there is no /// user-defined show rule. - fn realize( + fn show( &self, world: Tracked, styles: StyleChain, ) -> SourceResult; +} +/// Post-process a node after it was realized. +#[capability] +pub trait Finalize: 'static + Sync + Send { /// Finalize this node given the realization of a base or user recipe. Use /// this for effects that should work even in the face of a user-defined - /// show rule, for example: - /// - Application of general settable properties - /// - /// Defaults to just the realized content. - #[allow(unused_variables)] + /// show rule, for example the linking behaviour of a link node. fn finalize( &self, world: Tracked, styles: StyleChain, realized: Content, - ) -> SourceResult { - Ok(realized) - } + ) -> SourceResult; } diff --git a/src/model/value.rs b/src/model/value.rs index 825e6f552..9e6968fa3 100644 --- a/src/model/value.rs +++ b/src/model/value.rs @@ -381,7 +381,7 @@ primitive! { Str: "string", Str } primitive! { Content: "content", Content, None => Content::empty(), - Str(text) => Content::text(text) + Str(text) => item!(text)(text.into()) } primitive! { Array: "array", Array } primitive! { Dict: "dictionary", Dict } diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index 61b8f0e6b..1b0e8985c 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -63,11 +63,11 @@ impl Markup { /// A single piece of markup. #[derive(Debug, Clone, PartialEq)] pub enum MarkupNode { - /// Whitespace containing less than two newlines. + /// Whitespace. Space(Space), - /// A forced line break. + /// A forced line break: `\`. Linebreak(Linebreak), - /// Plain text. + /// Plain text without markup. Text(Text), /// An escape sequence: `\#`, `\u{1F5FA}`. Escape(Escape), @@ -76,9 +76,9 @@ pub enum MarkupNode { Shorthand(Shorthand), /// A smart quote: `'` or `"`. SmartQuote(SmartQuote), - /// Strong markup: `*Strong*`. + /// Strong content: `*Strong*`. Strong(Strong), - /// Emphasized markup: `_Emphasized_`. + /// Emphasized content: `_Emphasized_`. Emph(Emph), /// A raw block with optional syntax highlighting: `` `...` ``. Raw(Raw), @@ -171,7 +171,7 @@ node! { } node! { - /// Plain text. + /// Plain text without markup. Text } @@ -367,12 +367,12 @@ impl ListItem { } node! { - /// An item in an enumeration (ordered list): `1. ...`. + /// An item in an enumeration (ordered list): `+ ...` or `1. ...`. EnumItem } impl EnumItem { - /// The number, if any. + /// The explicit numbering, if any: `23.`. pub fn number(&self) -> Option { self.0.children().find_map(|node| match node.kind() { NodeKind::EnumNumbering(num) => Some(*num), @@ -434,9 +434,9 @@ pub enum MathNode { Linebreak(Linebreak), /// An escape sequence: `\#`, `\u{1F5FA}`. Escape(Escape), - /// A atom: `x`, `+`, `12`. + /// An atom: `x`, `+`, `12`. Atom(Atom), - /// A base with an optional sub- and superscript: `a_1^2`. + /// A base with optional sub- and superscripts: `a_1^2`. Script(Script), /// A fraction: `x/2`. Frac(Frac), @@ -565,9 +565,9 @@ pub enum Expr { Content(ContentBlock), /// A grouped expression: `(1 + 2)`. Parenthesized(Parenthesized), - /// An array expression: `(1, "hi", 12cm)`. + /// An array: `(1, "hi", 12cm)`. Array(Array), - /// A dictionary expression: `(thickness: 3pt, pattern: dashed)`. + /// A dictionary: `(thickness: 3pt, pattern: dashed)`. Dict(Dict), /// A unary operation: `-x`. Unary(Unary), @@ -579,16 +579,14 @@ pub enum Expr { FuncCall(FuncCall), /// An invocation of a method: `array.push(v)`. MethodCall(MethodCall), - /// A closure expression: `(x, y) => z`. + /// A closure: `(x, y) => z`. Closure(Closure), /// A let binding: `let x = 1`. Let(LetBinding), /// A set rule: `set text(...)`. Set(SetRule), - /// A show rule: `show node: heading as [*{nody.body}*]`. + /// A show rule: `show heading: it => [*{it.body}*]`. Show(ShowRule), - /// A wrap rule: `wrap body in columns(2, body)`. - Wrap(WrapRule), /// An if-else conditional: `if x { y } else { z }`. Conditional(Conditional), /// A while loop: `while x { y }`. @@ -599,12 +597,12 @@ pub enum Expr { Import(ModuleImport), /// A module include: `include "chapter1.typ"`. Include(ModuleInclude), - /// A break statement: `break`. - Break(BreakStmt), - /// A continue statement: `continue`. - Continue(ContinueStmt), - /// A return statement: `return`, `return x + 1`. - Return(ReturnStmt), + /// A break from a loop: `break`. + Break(LoopBreak), + /// A continue in a loop: `continue`. + Continue(LoopContinue), + /// A return from a function: `return`, `return x + 1`. + Return(FuncReturn), } impl TypedNode for Expr { @@ -625,15 +623,14 @@ impl TypedNode for Expr { NodeKind::LetBinding => node.cast().map(Self::Let), NodeKind::SetRule => node.cast().map(Self::Set), NodeKind::ShowRule => node.cast().map(Self::Show), - NodeKind::WrapRule => node.cast().map(Self::Wrap), NodeKind::Conditional => node.cast().map(Self::Conditional), NodeKind::WhileLoop => node.cast().map(Self::While), NodeKind::ForLoop => node.cast().map(Self::For), NodeKind::ModuleImport => node.cast().map(Self::Import), NodeKind::ModuleInclude => node.cast().map(Self::Include), - NodeKind::BreakStmt => node.cast().map(Self::Break), - NodeKind::ContinueStmt => node.cast().map(Self::Continue), - NodeKind::ReturnStmt => node.cast().map(Self::Return), + NodeKind::LoopBreak => node.cast().map(Self::Break), + NodeKind::LoopContinue => node.cast().map(Self::Continue), + NodeKind::FuncReturn => node.cast().map(Self::Return), _ => node.cast().map(Self::Lit), } } @@ -656,7 +653,6 @@ impl TypedNode for Expr { Self::Let(v) => v.as_untyped(), Self::Set(v) => v.as_untyped(), Self::Show(v) => v.as_untyped(), - Self::Wrap(v) => v.as_untyped(), Self::Conditional(v) => v.as_untyped(), Self::While(v) => v.as_untyped(), Self::For(v) => v.as_untyped(), @@ -679,7 +675,6 @@ impl Expr { | Self::Let(_) | Self::Set(_) | Self::Show(_) - | Self::Wrap(_) | Self::Conditional(_) | Self::While(_) | Self::For(_) @@ -723,15 +718,15 @@ pub enum LitKind { None, /// The auto literal: `auto`. Auto, - /// A boolean literal: `true`, `false`. + /// A boolean: `true`, `false`. Bool(bool), - /// An integer literal: `120`. + /// An integer: `120`. Int(i64), - /// A floating-point literal: `1.2`, `10e-4`. + /// A floating-point number: `1.2`, `10e-4`. Float(f64), - /// A numeric literal with a unit: `12pt`, `3cm`, `2em`, `90deg`, `50%`. + /// A numeric value with a unit: `12pt`, `3cm`, `2em`, `90deg`, `50%`. Numeric(f64, Unit), - /// A string literal: `"hello!"`. + /// A quoted string: `"..."`. Str(EcoString), } @@ -760,7 +755,7 @@ impl ContentBlock { } node! { - /// A parenthesized expression: `(1 + 2)`. + /// A grouped expression: `(1 + 2)`. Parenthesized } @@ -853,7 +848,7 @@ impl TypedNode for DictItem { } node! { - /// A pair of a name and an expression: `thickness: 3pt`. + /// A named pair: `thickness: 3pt`. Named } @@ -870,7 +865,7 @@ impl Named { } node! { - /// A pair of a string key and an expression: `"spacy key": true`. + /// A keyed pair: `"spacy key": true`. Keyed } @@ -1204,7 +1199,7 @@ impl MethodCall { } node! { - /// The arguments to a function: `12, draw: false`. + /// A function call's argument list: `(12pt, y)`. Args } @@ -1245,7 +1240,7 @@ impl TypedNode for Arg { } node! { - /// A closure expression: `(x, y) => z`. + /// A closure: `(x, y) => z`. Closure } @@ -1347,52 +1342,34 @@ impl SetRule { pub fn args(&self) -> Args { self.0.cast_last_child().expect("set rule is missing argument list") } + + /// A condition under which the set rule applies. + pub fn condition(&self) -> Option { + self.0 + .children() + .skip_while(|child| child.kind() != &NodeKind::If) + .find_map(SyntaxNode::cast) + } } node! { - /// A show rule: `show node: heading as [*{nody.body}*]`. + /// A show rule: `show heading: it => [*{it.body}*]`. ShowRule } impl ShowRule { - /// The binding to assign to. - pub fn binding(&self) -> Option { - let mut children = self.0.children(); - children - .find_map(SyntaxNode::cast) - .filter(|_| children.any(|child| child.kind() == &NodeKind::Colon)) - } - /// The pattern that this rule matches. - pub fn pattern(&self) -> Expr { + pub fn pattern(&self) -> Option { self.0 .children() .rev() - .skip_while(|child| child.kind() != &NodeKind::As) + .skip_while(|child| child.kind() != &NodeKind::Colon) .find_map(SyntaxNode::cast) - .expect("show rule is missing pattern") } - /// The expression that realizes the node. - pub fn body(&self) -> Expr { - self.0.cast_last_child().expect("show rule is missing body") - } -} - -node! { - /// A wrap rule: `wrap body in columns(2, body)`. - WrapRule -} - -impl WrapRule { - /// The binding to assign the remaining markup to. - pub fn binding(&self) -> Ident { - self.0.cast_first_child().expect("wrap rule is missing binding") - } - - /// The expression to evaluate. - pub fn body(&self) -> Expr { - self.0.cast_last_child().expect("wrap rule is missing body") + /// The transformation recipe. + pub fn transform(&self) -> Expr { + self.0.cast_last_child().expect("show rule is missing transform") } } @@ -1462,7 +1439,7 @@ impl ForLoop { } node! { - /// A for-in loop: `for x in y { z }`. + /// A for loop's destructuring pattern: `x` or `x, y`. ForPattern } @@ -1533,21 +1510,21 @@ impl ModuleInclude { } node! { - /// A break expression: `break`. - BreakStmt + /// A break from a loop: `break`. + LoopBreak } node! { - /// A continue expression: `continue`. - ContinueStmt + /// A continue in a loop: `continue`. + LoopContinue } node! { - /// A return expression: `return`, `return x + 1`. - ReturnStmt + /// A return from a function: `return`, `return x + 1`. + FuncReturn } -impl ReturnStmt { +impl FuncReturn { /// The expression to return. pub fn body(&self) -> Option { self.0.cast_last_child() @@ -1555,7 +1532,7 @@ impl ReturnStmt { } node! { - /// An identifier. + /// An identifier: `it`. Ident } diff --git a/src/syntax/highlight.rs b/src/syntax/highlight.rs index d5345fab9..34cce4c0d 100644 --- a/src/syntax/highlight.rs +++ b/src/syntax/highlight.rs @@ -257,7 +257,6 @@ impl Category { NodeKind::Let => Some(Category::Keyword), NodeKind::Set => Some(Category::Keyword), NodeKind::Show => Some(Category::Keyword), - NodeKind::Wrap => Some(Category::Keyword), NodeKind::If => Some(Category::Keyword), NodeKind::Else => Some(Category::Keyword), NodeKind::For => Some(Category::Keyword), @@ -269,7 +268,6 @@ impl Category { NodeKind::Import => Some(Category::Keyword), NodeKind::Include => Some(Category::Keyword), NodeKind::From => Some(Category::Keyword), - NodeKind::As => Some(Category::Keyword), NodeKind::Markup { .. } => match parent.kind() { NodeKind::DescItem @@ -316,8 +314,7 @@ impl Category { if parent .children() .rev() - .skip_while(|child| child.kind() != &NodeKind::As) - .take_while(|child| child.kind() != &NodeKind::Colon) + .skip_while(|child| child.kind() != &NodeKind::Colon) .find(|c| matches!(c.kind(), NodeKind::Ident(_))) .map_or(false, |ident| std::ptr::eq(ident, child)) => { @@ -349,7 +346,6 @@ impl Category { NodeKind::LetBinding => None, NodeKind::SetRule => None, NodeKind::ShowRule => None, - NodeKind::WrapRule => None, NodeKind::Conditional => None, NodeKind::WhileLoop => None, NodeKind::ForLoop => None, @@ -357,9 +353,9 @@ impl Category { NodeKind::ModuleImport => None, NodeKind::ImportItems => None, NodeKind::ModuleInclude => None, - NodeKind::BreakStmt => None, - NodeKind::ContinueStmt => None, - NodeKind::ReturnStmt => None, + NodeKind::LoopBreak => None, + NodeKind::LoopContinue => None, + NodeKind::FuncReturn => None, NodeKind::Error(_, _) => Some(Category::Error), } diff --git a/src/syntax/kind.rs b/src/syntax/kind.rs index 1568693ba..1282b592f 100644 --- a/src/syntax/kind.rs +++ b/src/syntax/kind.rs @@ -106,8 +106,6 @@ pub enum NodeKind { Set, /// The `show` keyword. Show, - /// The `wrap` keyword. - Wrap, /// The `if` keyword. If, /// The `else` keyword. @@ -130,8 +128,6 @@ pub enum NodeKind { Include, /// The `from` keyword. From, - /// The `as` keyword. - As, /// Markup of which all lines must have a minimal indentation. /// @@ -139,7 +135,7 @@ pub enum NodeKind { /// started, but to the right of which column all markup elements must be, /// so it is zero except inside indent-aware constructs like lists. Markup { min_indent: usize }, - /// Consecutive text without markup. + /// Plain text without markup. Text(EcoString), /// A forced line break: `\`. Linebreak, @@ -150,9 +146,9 @@ pub enum NodeKind { Shorthand(char), /// A smart quote: `'` or `"`. SmartQuote { double: bool }, - /// Strong markup: `*Strong*`. + /// Strong content: `*Strong*`. Strong, - /// Emphasized markup: `_Emphasized_`. + /// Emphasized content: `_Emphasized_`. Emph, /// A raw block with optional syntax highlighting: `` `...` ``. Raw(Arc), @@ -164,26 +160,26 @@ pub enum NodeKind { Ref(EcoString), /// A section heading: `= Introduction`. Heading, - /// An item of an unordered list: `- ...`. + /// An item in an unordered list: `- ...`. ListItem, - /// An item of an enumeration (ordered list): `+ ...` or `1. ...`. + /// An item in an enumeration (ordered list): `+ ...` or `1. ...`. EnumItem, /// An explicit enumeration numbering: `23.`. EnumNumbering(usize), - /// An item of a description list: `/ Term: Details. + /// An item in a description list: `/ Term: Details`. DescItem, - /// A math formula: `$x$`, `$ x^2 $`. + /// A mathematical formula: `$x$`, `$ x^2 $`. Math, - /// An atom in a math formula: `x`, `+`, `12`. + /// An atom in a formula: `x`, `+`, `12`. Atom(EcoString), - /// A base with optional sub- and superscript in a math formula: `a_1^2`. + /// A base with optional sub- and superscripts in a formula: `a_1^2`. Script, - /// A fraction in a math formula: `x/2`. + /// A fraction in a formula: `x/2`. Frac, - /// An alignment indicator in a math formula: `&`, `&&`. + /// An alignment indicator in a formula: `&`, `&&`. Align, - /// An identifier: `center`. + /// An identifier: `it`. Ident(EcoString), /// A boolean: `true`, `false`. Bool(bool), @@ -219,9 +215,9 @@ pub enum NodeKind { FuncCall, /// An invocation of a method: `array.push(v)`. MethodCall, - /// A function call's argument list: `(x, y)`. + /// A function call's argument list: `(12pt, y)`. Args, - /// Spreaded arguments or a argument sink: `..x`. + /// Spreaded arguments or an argument sink: `..x`. Spread, /// A closure: `(x, y) => z`. Closure, @@ -231,15 +227,13 @@ pub enum NodeKind { LetBinding, /// A set rule: `set text(...)`. SetRule, - /// A show rule: `show node: heading as [*{nody.body}*]`. + /// A show rule: `show heading: it => [*{it.body}*]`. ShowRule, - /// A wrap rule: `wrap body in columns(2, body)`. - WrapRule, /// An if-else conditional: `if x { y } else { z }`. Conditional, - /// A while loop: `while x { ... }`. + /// A while loop: `while x { y }`. WhileLoop, - /// A for loop: `for x in y { ... }`. + /// A for loop: `for x in y { z }`. ForLoop, /// A for loop's destructuring pattern: `x` or `x, y`. ForPattern, @@ -249,12 +243,12 @@ pub enum NodeKind { ImportItems, /// A module include: `include "chapter1.typ"`. ModuleInclude, - /// A break statement: `break`. - BreakStmt, - /// A continue statement: `continue`. - ContinueStmt, - /// A return statement: `return x + 1`. - ReturnStmt, + /// A break from a loop: `break`. + LoopBreak, + /// A continue in a loop: `continue`. + LoopContinue, + /// A return from a function: `return`, `return x + 1`. + FuncReturn, /// An invalid sequence of characters. Error(ErrorPos, EcoString), @@ -367,7 +361,6 @@ impl NodeKind { Self::Let => "keyword `let`", Self::Set => "keyword `set`", Self::Show => "keyword `show`", - Self::Wrap => "keyword `wrap`", Self::If => "keyword `if`", Self::Else => "keyword `else`", Self::For => "keyword `for`", @@ -379,7 +372,6 @@ impl NodeKind { Self::Import => "keyword `import`", Self::Include => "keyword `include`", Self::From => "keyword `from`", - Self::As => "keyword `as`", Self::Markup { .. } => "markup", Self::Text(_) => "text", Self::Linebreak => "linebreak", @@ -426,7 +418,6 @@ impl NodeKind { Self::LetBinding => "`let` expression", Self::SetRule => "`set` expression", Self::ShowRule => "`show` expression", - Self::WrapRule => "`wrap` expression", Self::Conditional => "`if` expression", Self::WhileLoop => "while-loop expression", Self::ForLoop => "for-loop expression", @@ -434,9 +425,9 @@ impl NodeKind { Self::ModuleImport => "`import` expression", Self::ImportItems => "import items", Self::ModuleInclude => "`include` expression", - Self::BreakStmt => "`break` expression", - Self::ContinueStmt => "`continue` expression", - Self::ReturnStmt => "`return` expression", + Self::LoopBreak => "`break` expression", + Self::LoopContinue => "`continue` expression", + Self::FuncReturn => "`return` expression", Self::Error(_, _) => "syntax error", } } @@ -488,7 +479,6 @@ impl Hash for NodeKind { Self::Let => {} Self::Set => {} Self::Show => {} - Self::Wrap => {} Self::If => {} Self::Else => {} Self::For => {} @@ -500,7 +490,6 @@ impl Hash for NodeKind { Self::Import => {} Self::Include => {} Self::From => {} - Self::As => {} Self::Markup { min_indent } => min_indent.hash(state), Self::Text(s) => s.hash(state), Self::Linebreak => {} @@ -548,7 +537,6 @@ impl Hash for NodeKind { Self::LetBinding => {} Self::SetRule => {} Self::ShowRule => {} - Self::WrapRule => {} Self::Conditional => {} Self::WhileLoop => {} Self::ForLoop => {} @@ -556,9 +544,9 @@ impl Hash for NodeKind { Self::ModuleImport => {} Self::ImportItems => {} Self::ModuleInclude => {} - Self::BreakStmt => {} - Self::ContinueStmt => {} - Self::ReturnStmt => {} + Self::LoopBreak => {} + Self::LoopContinue => {} + Self::FuncReturn => {} Self::Error(pos, msg) => (pos, msg).hash(state), } } diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index fecc527c7..acf76b7e5 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -208,7 +208,6 @@ where }); } -/// Parse a markup node. fn markup_node(p: &mut Parser, at_start: &mut bool) { let Some(token) = p.peek() else { return }; match token { @@ -245,10 +244,10 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) { NodeKind::Eq => heading(p, *at_start), // Lists. - NodeKind::Minus => list_node(p, *at_start), - NodeKind::Plus | NodeKind::EnumNumbering(_) => enum_node(p, *at_start), + NodeKind::Minus => list_item(p, *at_start), + NodeKind::Plus | NodeKind::EnumNumbering(_) => enum_item(p, *at_start), NodeKind::Slash => { - desc_node(p, *at_start).ok(); + desc_item(p, *at_start).ok(); } NodeKind::Colon => { let marker = p.marker(); @@ -261,7 +260,6 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) { | NodeKind::Let | NodeKind::Set | NodeKind::Show - | NodeKind::Wrap | NodeKind::If | NodeKind::While | NodeKind::For @@ -282,7 +280,6 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) { *at_start = false; } -/// Parse strong content. fn strong(p: &mut Parser) { p.perform(NodeKind::Strong, |p| { p.start_group(Group::Strong); @@ -291,7 +288,6 @@ fn strong(p: &mut Parser) { }) } -/// Parse emphasized content. fn emph(p: &mut Parser) { p.perform(NodeKind::Emph, |p| { p.start_group(Group::Emph); @@ -300,7 +296,6 @@ fn emph(p: &mut Parser) { }) } -/// Parse a heading. fn heading(p: &mut Parser, at_start: bool) { let marker = p.marker(); let current_start = p.current_start(); @@ -317,8 +312,7 @@ fn heading(p: &mut Parser, at_start: bool) { } } -/// Parse a single list item. -fn list_node(p: &mut Parser, at_start: bool) { +fn list_item(p: &mut Parser, at_start: bool) { let marker = p.marker(); let text: EcoString = p.peek_src().into(); p.assert(NodeKind::Minus); @@ -332,8 +326,7 @@ fn list_node(p: &mut Parser, at_start: bool) { } } -/// Parse a single enum item. -fn enum_node(p: &mut Parser, at_start: bool) { +fn enum_item(p: &mut Parser, at_start: bool) { let marker = p.marker(); let text: EcoString = p.peek_src().into(); p.eat(); @@ -347,8 +340,7 @@ fn enum_node(p: &mut Parser, at_start: bool) { } } -/// Parse a single description list item. -fn desc_node(p: &mut Parser, at_start: bool) -> ParseResult { +fn desc_item(p: &mut Parser, at_start: bool) -> ParseResult { let marker = p.marker(); let text: EcoString = p.peek_src().into(); p.eat(); @@ -366,7 +358,6 @@ fn desc_node(p: &mut Parser, at_start: bool) -> ParseResult { Ok(()) } -/// Parse an expression within a markup mode. fn markup_expr(p: &mut Parser) { // Does the expression need termination or can content follow directly? let stmt = matches!( @@ -375,7 +366,6 @@ fn markup_expr(p: &mut Parser) { NodeKind::Let | NodeKind::Set | NodeKind::Show - | NodeKind::Wrap | NodeKind::Import | NodeKind::Include ) @@ -389,7 +379,6 @@ fn markup_expr(p: &mut Parser) { p.end_group(); } -/// Parse math. fn math(p: &mut Parser) { p.perform(NodeKind::Math, |p| { p.start_group(Group::Math); @@ -400,12 +389,10 @@ fn math(p: &mut Parser) { }); } -/// Parse a math node. fn math_node(p: &mut Parser) { math_node_prec(p, 0, None) } -/// Parse a math node with operators having at least the minimum precedence. fn math_node_prec(p: &mut Parser, min_prec: usize, stop: Option) { let marker = p.marker(); math_primary(p); @@ -457,19 +444,18 @@ fn math_primary(p: &mut Parser) { | NodeKind::Ident(_) => p.eat(), // Groups. - NodeKind::LeftParen => group(p, Group::Paren, '(', ')'), - NodeKind::LeftBracket => group(p, Group::Bracket, '[', ']'), - NodeKind::LeftBrace => group(p, Group::Brace, '{', '}'), + NodeKind::LeftParen => math_group(p, Group::Paren, '(', ')'), + NodeKind::LeftBracket => math_group(p, Group::Bracket, '[', ']'), + NodeKind::LeftBrace => math_group(p, Group::Brace, '{', '}'), // Alignment indactor. - NodeKind::Amp => align(p), + NodeKind::Amp => math_align(p), _ => p.unexpected(), } } -/// Parse grouped math. -fn group(p: &mut Parser, group: Group, l: char, r: char) { +fn math_group(p: &mut Parser, group: Group, l: char, r: char) { p.perform(NodeKind::Math, |p| { let marker = p.marker(); p.start_group(group); @@ -483,15 +469,13 @@ fn group(p: &mut Parser, group: Group, l: char, r: char) { }) } -/// Parse an alignment indicator. -fn align(p: &mut Parser) { +fn math_align(p: &mut Parser) { p.perform(NodeKind::Align, |p| { p.assert(NodeKind::Amp); while p.eat_if(NodeKind::Amp) {} }) } -/// Parse an expression. fn expr(p: &mut Parser) -> ParseResult { expr_prec(p, false, 0) } @@ -571,7 +555,6 @@ fn expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) -> ParseResult { Ok(()) } -/// Parse a primary expression. fn primary(p: &mut Parser, atomic: bool) -> ParseResult { if literal(p) { return Ok(()); @@ -599,18 +582,17 @@ fn primary(p: &mut Parser, atomic: bool) -> ParseResult { Some(NodeKind::LeftBracket) => Ok(content_block(p)), // Keywords. - Some(NodeKind::Let) => let_expr(p), - Some(NodeKind::Set) => set_expr(p), - Some(NodeKind::Show) => show_expr(p), - Some(NodeKind::Wrap) => wrap_expr(p), - Some(NodeKind::If) => if_expr(p), - Some(NodeKind::While) => while_expr(p), - Some(NodeKind::For) => for_expr(p), - Some(NodeKind::Import) => import_expr(p), - Some(NodeKind::Include) => include_expr(p), - Some(NodeKind::Break) => break_expr(p), - Some(NodeKind::Continue) => continue_expr(p), - Some(NodeKind::Return) => return_expr(p), + Some(NodeKind::Let) => let_binding(p), + Some(NodeKind::Set) => set_rule(p), + Some(NodeKind::Show) => show_rule(p), + Some(NodeKind::If) => conditional(p), + Some(NodeKind::While) => while_loop(p), + Some(NodeKind::For) => for_loop(p), + Some(NodeKind::Import) => module_import(p), + Some(NodeKind::Include) => module_include(p), + Some(NodeKind::Break) => break_stmt(p), + Some(NodeKind::Continue) => continue_stmt(p), + Some(NodeKind::Return) => return_stmt(p), Some(NodeKind::Error(_, _)) => { p.eat(); @@ -625,10 +607,8 @@ fn primary(p: &mut Parser, atomic: bool) -> ParseResult { } } -/// Parse a literal. fn literal(p: &mut Parser) -> bool { match p.peek() { - // Basic values. Some( NodeKind::None | NodeKind::Auto @@ -645,7 +625,6 @@ fn literal(p: &mut Parser) -> bool { } } -/// Parse an identifier. fn ident(p: &mut Parser) -> ParseResult { match p.peek() { Some(NodeKind::Ident(_)) => { @@ -762,8 +741,6 @@ fn collection(p: &mut Parser, keyed: bool) -> (CollectionKind, usize) { (kind, items) } -/// Parse an expression or a named pair, returning whether it's a spread or a -/// named pair. fn item(p: &mut Parser, keyed: bool) -> ParseResult { let marker = p.marker(); if p.eat_if(NodeKind::Dots) { @@ -806,8 +783,6 @@ fn item(p: &mut Parser, keyed: bool) -> ParseResult { } } -/// Convert a collection into an array, producing errors for anything other than -/// expressions. fn array(p: &mut Parser, marker: Marker) { marker.filter_children(p, |x| match x.kind() { NodeKind::Named | NodeKind::Keyed => Err("expected expression"), @@ -816,8 +791,6 @@ fn array(p: &mut Parser, marker: Marker) { marker.end(p, NodeKind::Array); } -/// Convert a collection into a dictionary, producing errors for anything other -/// than named and keyed pairs. fn dict(p: &mut Parser, marker: Marker) { let mut used = HashSet::new(); marker.filter_children(p, |x| match x.kind() { @@ -838,8 +811,6 @@ fn dict(p: &mut Parser, marker: Marker) { marker.end(p, NodeKind::Dict); } -/// Convert a collection into a list of parameters, producing errors for -/// anything other than identifiers, spread operations and named pairs. fn params(p: &mut Parser, marker: Marker) { marker.filter_children(p, |x| match x.kind() { kind if kind.is_paren() => Ok(()), @@ -866,7 +837,6 @@ fn code_block(p: &mut Parser) { }); } -/// Parse expressions. fn code(p: &mut Parser) { while !p.eof() { p.start_group(Group::Expr); @@ -880,7 +850,6 @@ fn code(p: &mut Parser) { } } -/// Parse a content block: `[...]`. fn content_block(p: &mut Parser) { p.perform(NodeKind::ContentBlock, |p| { p.start_group(Group::Bracket); @@ -889,7 +858,6 @@ fn content_block(p: &mut Parser) { }); } -/// Parse the arguments to a function call. fn args(p: &mut Parser) -> ParseResult { match p.peek_direct() { Some(NodeKind::LeftParen) => {} @@ -931,8 +899,7 @@ fn args(p: &mut Parser) -> ParseResult { Ok(()) } -/// Parse a let expression. -fn let_expr(p: &mut Parser) -> ParseResult { +fn let_binding(p: &mut Parser) -> ParseResult { p.perform(NodeKind::LetBinding, |p| { p.assert(NodeKind::Let); @@ -965,45 +932,30 @@ fn let_expr(p: &mut Parser) -> ParseResult { }) } -/// Parse a set expression. -fn set_expr(p: &mut Parser) -> ParseResult { +fn set_rule(p: &mut Parser) -> ParseResult { p.perform(NodeKind::SetRule, |p| { p.assert(NodeKind::Set); ident(p)?; - args(p) - }) -} - -/// Parse a show expression. -fn show_expr(p: &mut Parser) -> ParseResult { - p.perform(NodeKind::ShowRule, |p| { - p.assert(NodeKind::Show); - let marker = p.marker(); - expr(p)?; - if p.eat_if(NodeKind::Colon) { - marker.filter_children(p, |child| match child.kind() { - NodeKind::Ident(_) | NodeKind::Colon => Ok(()), - _ => Err("expected identifier"), - }); + args(p)?; + if p.eat_if(NodeKind::If) { expr(p)?; } - p.expect(NodeKind::As)?; - expr(p) + Ok(()) }) } -/// Parse a wrap expression. -fn wrap_expr(p: &mut Parser) -> ParseResult { - p.perform(NodeKind::WrapRule, |p| { - p.assert(NodeKind::Wrap); - ident(p)?; - p.expect(NodeKind::In)?; - expr(p) +fn show_rule(p: &mut Parser) -> ParseResult { + p.perform(NodeKind::ShowRule, |p| { + p.assert(NodeKind::Show); + expr(p)?; + if p.eat_if(NodeKind::Colon) { + expr(p)?; + } + Ok(()) }) } -/// Parse an if-else expresion. -fn if_expr(p: &mut Parser) -> ParseResult { +fn conditional(p: &mut Parser) -> ParseResult { p.perform(NodeKind::Conditional, |p| { p.assert(NodeKind::If); @@ -1012,7 +964,7 @@ fn if_expr(p: &mut Parser) -> ParseResult { if p.eat_if(NodeKind::Else) { if p.at(NodeKind::If) { - if_expr(p)?; + conditional(p)?; } else { body(p)?; } @@ -1022,8 +974,7 @@ fn if_expr(p: &mut Parser) -> ParseResult { }) } -/// Parse a while expresion. -fn while_expr(p: &mut Parser) -> ParseResult { +fn while_loop(p: &mut Parser) -> ParseResult { p.perform(NodeKind::WhileLoop, |p| { p.assert(NodeKind::While); expr(p)?; @@ -1031,8 +982,7 @@ fn while_expr(p: &mut Parser) -> ParseResult { }) } -/// Parse a for-in expression. -fn for_expr(p: &mut Parser) -> ParseResult { +fn for_loop(p: &mut Parser) -> ParseResult { p.perform(NodeKind::ForLoop, |p| { p.assert(NodeKind::For); for_pattern(p)?; @@ -1042,7 +992,6 @@ fn for_expr(p: &mut Parser) -> ParseResult { }) } -/// Parse a for loop pattern. fn for_pattern(p: &mut Parser) -> ParseResult { p.perform(NodeKind::ForPattern, |p| { ident(p)?; @@ -1053,8 +1002,7 @@ fn for_pattern(p: &mut Parser) -> ParseResult { }) } -/// Parse an import expression. -fn import_expr(p: &mut Parser) -> ParseResult { +fn module_import(p: &mut Parser) -> ParseResult { p.perform(NodeKind::ModuleImport, |p| { p.assert(NodeKind::Import); @@ -1081,33 +1029,29 @@ fn import_expr(p: &mut Parser) -> ParseResult { }) } -/// Parse an include expression. -fn include_expr(p: &mut Parser) -> ParseResult { +fn module_include(p: &mut Parser) -> ParseResult { p.perform(NodeKind::ModuleInclude, |p| { p.assert(NodeKind::Include); expr(p) }) } -/// Parse a break expression. -fn break_expr(p: &mut Parser) -> ParseResult { - p.perform(NodeKind::BreakStmt, |p| { +fn break_stmt(p: &mut Parser) -> ParseResult { + p.perform(NodeKind::LoopBreak, |p| { p.assert(NodeKind::Break); Ok(()) }) } -/// Parse a continue expression. -fn continue_expr(p: &mut Parser) -> ParseResult { - p.perform(NodeKind::ContinueStmt, |p| { +fn continue_stmt(p: &mut Parser) -> ParseResult { + p.perform(NodeKind::LoopContinue, |p| { p.assert(NodeKind::Continue); Ok(()) }) } -/// Parse a return expression. -fn return_expr(p: &mut Parser) -> ParseResult { - p.perform(NodeKind::ReturnStmt, |p| { +fn return_stmt(p: &mut Parser) -> ParseResult { + p.perform(NodeKind::FuncReturn, |p| { p.assert(NodeKind::Return); if !p.at(NodeKind::Comma) && !p.eof() { expr(p)?; @@ -1116,7 +1060,6 @@ fn return_expr(p: &mut Parser) -> ParseResult { }) } -/// Parse a control flow body. fn body(p: &mut Parser) -> ParseResult { match p.peek() { Some(NodeKind::LeftBracket) => Ok(content_block(p)), diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs index f18bb7805..b27832c53 100644 --- a/src/syntax/tokens.rs +++ b/src/syntax/tokens.rs @@ -613,12 +613,10 @@ fn keyword(ident: &str) -> Option { "let" => NodeKind::Let, "set" => NodeKind::Set, "show" => NodeKind::Show, - "wrap" => NodeKind::Wrap, "if" => NodeKind::If, "else" => NodeKind::Else, "for" => NodeKind::For, "in" => NodeKind::In, - "as" => NodeKind::As, "while" => NodeKind::While, "break" => NodeKind::Break, "continue" => NodeKind::Continue, diff --git a/tests/ref/style/set.png b/tests/ref/style/set.png index 502d74b5d..6e1549800 100644 Binary files a/tests/ref/style/set.png and b/tests/ref/style/set.png differ diff --git a/tests/ref/style/wrap.png b/tests/ref/style/show-bare.png similarity index 80% rename from tests/ref/style/wrap.png rename to tests/ref/style/show-bare.png index 7fb8dfc58..ff746d261 100644 Binary files a/tests/ref/style/wrap.png and b/tests/ref/style/show-bare.png differ diff --git a/tests/ref/text/code.png b/tests/ref/text/code.png index c4e94a73b..e03878705 100644 Binary files a/tests/ref/text/code.png and b/tests/ref/text/code.png differ diff --git a/tests/src/tests.rs b/tests/src/tests.rs index 389938a4d..aa9beaee1 100644 --- a/tests/src/tests.rs +++ b/tests/src/tests.rs @@ -38,7 +38,7 @@ fn main() { // cache, a deterministic order is important for reproducibility. for entry in WalkDir::new("typ").sort_by_file_name() { let entry = entry.unwrap(); - if entry.depth() <= 1 { + if entry.depth() == 0 { continue; } diff --git a/tests/typ/base/eval.typ b/tests/typ/base/eval.typ index 86b1f0c47..668264d3f 100644 --- a/tests/typ/base/eval.typ +++ b/tests/typ/base/eval.typ @@ -9,7 +9,7 @@ --- #set raw(around: none) -#show it: raw as text("IBM Plex Sans", eval(it.text)) +#show raw: it => text("IBM Plex Sans", eval(it.text)) Interacting ``` @@ -31,19 +31,18 @@ Blue #move(dy: -0.15em)[🌊] --- // Error: 23-30 cannot access file system from here -#show it: raw as eval(it.text) +#show raw: it => eval(it.text) ``` -#show strong as image("/res/tiger.jpg") -*No absolute tiger!* +#image("/res/tiger.jpg") ``` --- // Error: 23-30 cannot access file system from here -#show it: raw as eval(it.text) +#show raw: it => eval(it.text) ``` -#show emph as image("../../res/giraffe.jpg") +#show emph: _ => image("../../res/giraffe.jpg") _No relative giraffe!_ ``` diff --git a/tests/typ/code/break-continue.typ b/tests/typ/code/break-continue.typ index 2415cb8f9..fb3222fa2 100644 --- a/tests/typ/code/break-continue.typ +++ b/tests/typ/code/break-continue.typ @@ -116,7 +116,7 @@ // Everything should be in smallcaps. #for color in (red, blue, green, yellow) [ #set text("Roboto") - #wrap body in text(fill: color, body) + #show it => text(fill: color, it) #smallcaps(if color != green [ Some ] else [ diff --git a/tests/typ/code/field.typ b/tests/typ/code/field.typ index b63a8768f..abea87fb3 100644 --- a/tests/typ/code/field.typ +++ b/tests/typ/code/field.typ @@ -14,7 +14,7 @@ --- // Test field on node. -#show node: list as { +#show list: node => { test(node.items.len(), 3) } @@ -32,7 +32,7 @@ --- // Error: 29-32 unknown field "fun" -#show node: heading as node.fun +#show heading: node => node.fun = A --- diff --git a/tests/typ/code/import.typ b/tests/typ/code/import.typ index b554d6e7a..5291af395 100644 --- a/tests/typ/code/import.typ +++ b/tests/typ/code/import.typ @@ -1,4 +1,4 @@ -// Test import statements. +// Test module imports. --- // Test importing semantics. diff --git a/tests/typ/code/include.typ b/tests/typ/code/include.typ index cd3328a23..e862adac7 100644 --- a/tests/typ/code/include.typ +++ b/tests/typ/code/include.typ @@ -1,4 +1,4 @@ -// Test include statements. +// Test module includes. --- #set page(width: 200pt) diff --git a/tests/typ/graphics/shape-rect.typ b/tests/typ/graphics/shape-rect.typ index 7d1101808..94686da27 100644 --- a/tests/typ/graphics/shape-rect.typ +++ b/tests/typ/graphics/shape-rect.typ @@ -47,10 +47,10 @@ --- // Outset padding. #set raw(lang: "rust") -#show node: raw as [ +#show raw: it => [ #set text(8pt) #h(5.6pt, weak: true) - #rect(radius: 3pt, outset: (y: 3pt, x: 2.5pt), fill: rgb(239, 241, 243), node) + #rect(radius: 3pt, outset: (y: 3pt, x: 2.5pt), fill: rgb(239, 241, 243), it) #h(5.6pt, weak: true) ] diff --git a/tests/typ/structure/desc.typ b/tests/typ/structure/desc.typ index e12bbd161..af1b29865 100644 --- a/tests/typ/structure/desc.typ +++ b/tests/typ/structure/desc.typ @@ -37,7 +37,7 @@ No: list \ --- // Test grid like show rule. -#show it: desc as table( +#show desc: it => table( columns: 2, padding: 3pt, ..it.items.map(item => (emph(item.term), item.body)).flatten(), diff --git a/tests/typ/structure/heading.typ b/tests/typ/structure/heading.typ index d37d8796a..d08b3687a 100644 --- a/tests/typ/structure/heading.typ +++ b/tests/typ/structure/heading.typ @@ -1,7 +1,7 @@ // Test headings. --- -#show node: heading as text(blue, node.body) +#show heading: it => text(blue, it.body) = No heading @@ -46,8 +46,8 @@ multiline. = Heading #set heading(family: "Roboto", fill: eastern) -#show it: heading as it.body -#show it: strong as it.body + [!] +#show heading: it => it.body +#show strong: it => it.body + [!] ===== Heading 🌍 #heading(level: 5)[Heading] diff --git a/tests/typ/style/set.typ b/tests/typ/style/set.typ index 25dc1b814..2864b81bb 100644 --- a/tests/typ/style/set.typ +++ b/tests/typ/style/set.typ @@ -48,6 +48,19 @@ Hello *{x}* + Rhino + Tiger +--- +// Test conditional set. +#show ref: it => { + set text(red) if it.target == "unknown" + it +} + +@hello from the @unknown + +--- +// Error: 19-24 expected boolean, found integer +#set text(red) if 1 + 2 + --- // Error: 11-25 set is only allowed directly in code and content blocks { let x = set text(blue) } diff --git a/tests/typ/style/wrap.typ b/tests/typ/style/show-bare.typ similarity index 55% rename from tests/typ/style/wrap.typ rename to tests/typ/style/show-bare.typ index e37c4bc9f..2dba742f0 100644 --- a/tests/typ/style/wrap.typ +++ b/tests/typ/style/show-bare.typ @@ -1,4 +1,4 @@ -// Test wrap. +// Test bare show without pattern. --- #set page(height: 130pt) @@ -9,28 +9,25 @@ T. Ypst ] -#wrap body in columns(2, body) +#show columns.with(2) Great typography is at the essence of great storytelling. It is the medium that transports meaning from parchment to reader, the wave that sparks a flame in booklovers and the great fulfiller of human need. --- -// Test wrap in content block. -A [_B #wrap c in [*#c*]; C_] D +// Test bare show in content block. +A [_B #show c => [*#c*]; C_] D --- -// Test wrap style precedence. +// Test style precedence. #set text(fill: eastern, size: 1.5em) -#wrap body in text(fill: forest, body) +#show text.with(fill: forest) Forest --- -// Ok, whatever. -{ - wrap body in 2 * body - 2 -} +#show [Shown] +Ignored --- -// Error: 4-18 wrap is only allowed directly in code and content blocks -{ (wrap body in 2) * body } +// Error: 4-18 show is only allowed directly in code and content blocks +{ (show body => 2) * body } diff --git a/tests/typ/style/show-node.typ b/tests/typ/style/show-node.typ index b35ab4c49..56b7e34aa 100644 --- a/tests/typ/style/show-node.typ +++ b/tests/typ/style/show-node.typ @@ -3,7 +3,7 @@ --- // Override lists. #set list(around: none) -#show v: list as "(" + v.items.join(", ") + ")" +#show list: it => "(" + it.items.join(", ") + ")" - A - B @@ -14,12 +14,12 @@ --- // Test full reset. #set heading(size: 1em, strong: false, around: none) -#show heading as [B] +#show heading: [B] A [= Heading] C --- // Test full removal. -#show heading as [] +#show heading: none #set heading(around: none) Where is @@ -29,13 +29,13 @@ my heading? --- // Test integrated example. #set heading(size: 1em) -#show node: heading as { +#show heading: it => { move(dy: -1pt)[📖] h(5pt) - if node.level == 1 { - underline(text(1.25em, blue, node.body)) + if it.level == 1 { + underline(text(1.25em, blue, it.body)) } else { - text(red, node.body) + text(red, it.body) } } @@ -50,10 +50,10 @@ Another text. --- // Test set and show in code blocks. -#show node: heading as { +#show heading: it => { set text(red) - show "ding" as [🛎] - node.body + show "ding": [🛎] + it.body } = Heading @@ -62,12 +62,12 @@ Another text. // Test that scoping works as expected. { let world = [ World ] - show c: "W" as strong(c) + show "W": strong world { set text(blue) - wrap it in { - show "o" as "Ø" + show it => { + show "o": "Ø" it } world @@ -76,22 +76,27 @@ Another text. } --- -#show heading as 1234 +#show heading: [1234] = Heading --- // Error: 25-29 unknown field "page" -#show it: heading as it.page +#show heading: it => it.page = Heading --- -// Error: 10-15 this function cannot be customized with show -#show _: upper as {} +// Error: 7-12 this function cannot be customized with show +#show upper: it => {} + +--- +// Error: 16-20 expected content or function, found integer +#show heading: 1234 += Heading --- // Error: 7-10 expected function, string or regular expression, found color -#show red as [] +#show red: [] --- -// Error: 7-27 show is only allowed directly in code and content blocks -{ 1 + show heading as none } +// Error: 7-25 show is only allowed directly in code and content blocks +{ 1 + show heading: none } diff --git a/tests/typ/style/show-recursive.typ b/tests/typ/style/show-recursive.typ index 9e93739c2..566879af7 100644 --- a/tests/typ/style/show-recursive.typ +++ b/tests/typ/style/show-recursive.typ @@ -2,17 +2,18 @@ --- // Test basic identity. -#show it: heading as it +#show heading: it => it = Heading --- // Test more recipes down the chain. -#show it: list as scale(origin: left, x: 80%, it) -#show heading as [] -#show enum as [] +#show list: scale.with(origin: left, x: 80%) +#show heading: [] +#show enum: [] - Actual - Tight - List += Nope --- // Test recursive base recipe. (Burn it with fire!) @@ -23,11 +24,11 @@ --- // Test show rule in function. #let starwars(body) = [ - #show v: list as { + #show list: it => { stack(dir: ltr, - text(red, v), + text(red, it), 1fr, - scale(x: -100%, text(blue, v)), + scale(x: -100%, text(blue, it)), ) } #body @@ -44,8 +45,8 @@ --- // Test multi-recursion with nested lists. #set rect(inset: 2pt) -#show v: list as rect(stroke: blue, v) -#show v: list as rect(stroke: red, v) +#show list: rect.with(stroke: blue) +#show list: rect.with(stroke: red) - List - Nested @@ -55,8 +56,8 @@ --- // Inner heading is not finalized. Bug? #set heading(around: none) -#show it: heading as it.body -#show heading as [ +#show heading: it => it.body +#show heading: [ = A [ = B ] diff --git a/tests/typ/style/show-text.typ b/tests/typ/style/show-text.typ index 283a28879..457ce9b7e 100644 --- a/tests/typ/style/show-text.typ +++ b/tests/typ/style/show-text.typ @@ -3,22 +3,22 @@ --- // Test classic example. #set text("Roboto") -#show phrase: "Der Spiegel" as smallcaps[#phrase] +#show "Der Spiegel": smallcaps Die Zeitung Der Spiegel existiert. --- // Another classic example. -#show "TeX" as [T#h(-0.145em)#move(dy: 0.233em)[E]#h(-0.135em)X] -#show name: regex("(Lua)?(La)?TeX") as box(text("Latin Modern Roman")[#name]) +#show "TeX": [T#h(-0.145em)#move(dy: 0.233em)[E]#h(-0.135em)X] +#show regex("(Lua)?(La)?TeX"): name => box(text("Latin Modern Roman")[#name]) TeX, LaTeX, LuaTeX and LuaLaTeX! --- // Test out-of-order guarding. -#show "Good" as [Typst!] -#show "Typst" as [Fun!] -#show "Fun" as [Good!] -#show enum as [] +#show "Good": [Typst!] +#show "Typst": [Fun!] +#show "Fun": [Good!] +#show enum: [] Good \ Fun \ @@ -26,32 +26,32 @@ Typst \ --- // Test that replacements happen exactly once. -#show "A" as [BB] -#show "B" as [CC] +#show "A": [BB] +#show "B": [CC] AA (8) --- // Test caseless match and word boundaries. -#show regex("(?i)\bworld\b") as [🌍] +#show regex("(?i)\bworld\b"): [🌍] Treeworld, the World of worlds, is a world. --- // This is a fun one. #set par(justify: true) -#show letter: regex("\S") as rect(inset: 2pt)[#upper(letter)] +#show regex("\S"): letter => rect(inset: 2pt)[#upper(letter)] #lorem(5) --- // See also: https://github.com/mTvare6/hello-world.rs -#show it: regex("(?i)rust") as [#it (🚀)] +#show regex("(?i)rust"): it => [#it (🚀)] Rust is memory-safe and blazingly fast. Let's rewrite everything in rust. --- // Replace worlds but only in lists. -#show node: list as [ - #show "World" as [🌎] - #node +#show list: it => [ + #show "World": [🌎] + #it ] World @@ -60,6 +60,6 @@ World --- // Test absolute path in layout phase. -#show "GRAPH" as image("/res/graph.png") +#show "GRAPH": image("/res/graph.png") The GRAPH has nodes. diff --git a/tests/typ/text/code.typ b/tests/typ/text/code.typ index 4230dd873..d89f1c055 100644 --- a/tests/typ/text/code.typ +++ b/tests/typ/text/code.typ @@ -7,6 +7,7 @@ #lorem(100) #let hi = "Hello World" +#show heading: emph ``` --- diff --git a/tools/support/typst.tmLanguage.json b/tools/support/typst.tmLanguage.json index 743513235..8e0907f6e 100644 --- a/tools/support/typst.tmLanguage.json +++ b/tools/support/typst.tmLanguage.json @@ -142,7 +142,7 @@ "captures": { "1": { "name": "punctuation.definition.reference.typst" } } }, { - "begin": "(#)(let|set|show|wrap|apply|select)\\b", + "begin": "(#)(let|set|show)\\b", "end": "\n|(;)|(?=])", "beginCaptures": { "0": { "name": "keyword.other.typst" }, @@ -253,7 +253,7 @@ }, { "name": "keyword.other.typst", - "match": "\\b(let|as|in|from|set|show|wrap|apply|select)\\b" + "match": "\\b(let|as|in|from|set|show)\\b" }, { "name": "keyword.control.conditional.typst", @@ -277,6 +277,11 @@ "name": "entity.name.function.typst", "match": "\\b[[:alpha:]_][[:alnum:]_-]*!?(?=\\[|\\()" }, + { + "comment": "Function name", + "name": "entity.name.function.typst", + "match": "(?<=\\bshow\\s*)\\b[[:alpha:]_][[:alnum:]_-]*(?=\\s*:)" + }, { "comment": "Function arguments", "begin": "(?<=\\b[[:alpha:]_][[:alnum:]_-]*!?)\\(", diff --git a/tools/test-helper/extension.js b/tools/test-helper/extension.js index 985112f05..253c78c71 100644 --- a/tools/test-helper/extension.js +++ b/tools/test-helper/extension.js @@ -38,12 +38,12 @@ function activate(context) { const rerunCmd = vscode.commands.registerCommand("ShortcutMenuBar.testRerun", () => { const uri = vscode.window.activeTextEditor.document.uri - const components = uri.fsPath.split('tests') + const components = uri.fsPath.split(/tests[\/\\]/) const dir = components[0] const subPath = components[1] cp.exec( - `cargo test --manifest-path ${dir}/Cargo.toml --test typeset ${subPath}`, + `cargo test --manifest-path ${dir}/Cargo.toml --all --test tests -- ${subPath}`, (err, stdout, stderr) => { console.log('Ran tests') refreshPanel(stdout, stderr)