diff --git a/library/src/basics/enum.rs b/library/src/basics/enum.rs index f1784a7ef..c5d4d8aef 100644 --- a/library/src/basics/enum.rs +++ b/library/src/basics/enum.rs @@ -1,9 +1,8 @@ use std::str::FromStr; -use crate::compute::NumberingPattern; +use crate::compute::{Numbering, NumberingPattern}; use crate::layout::{BlockNode, GridNode, ParNode, Spacing, TrackSizing}; use crate::prelude::*; -use crate::text::TextNode; /// # Numbered List /// A numbered list. @@ -98,7 +97,7 @@ pub struct EnumNode { #[node] impl EnumNode { /// How to number the enumeration. Accepts a - /// [numbering pattern](@numbering). + /// [numbering pattern or function](@numbering). /// /// # Example /// ``` @@ -109,8 +108,8 @@ impl EnumNode { /// + Style /// ``` #[property(referenced)] - pub const NUMBERING: EnumNumbering = - EnumNumbering::Pattern(NumberingPattern::from_str("1.").unwrap()); + pub const NUMBERING: Numbering = + Numbering::Pattern(NumberingPattern::from_str("1.").unwrap()); /// The indentation of each item's label. #[property(resolve)] @@ -188,7 +187,7 @@ impl Layout for EnumNode { let mut number = NonZeroUsize::new(1).unwrap(); for ((n, item), map) in self.items.iter() { number = n.unwrap_or(number); - let resolved = numbering.resolve(vt, number)?; + let resolved = numbering.apply(vt.world(), &[number])?.display(); cells.push(Content::empty()); cells.push(resolved.styled_with_map(map.clone())); cells.push(Content::empty()); @@ -209,43 +208,3 @@ impl Layout for EnumNode { .layout(vt, styles, regions) } } - -/// How to number an enumeration. -#[derive(Debug, Clone, Hash)] -pub enum EnumNumbering { - /// A pattern with prefix, numbering, lower / upper case and suffix. - Pattern(NumberingPattern), - /// A closure mapping from an item's number to content. - Func(Func, Span), -} - -impl EnumNumbering { - /// Resolve the marker based on the number. - pub fn resolve(&self, vt: &Vt, number: NonZeroUsize) -> SourceResult { - Ok(match self { - Self::Pattern(pattern) => TextNode::packed(pattern.apply(&[number])), - Self::Func(func, span) => { - let args = Args::new(*span, [Value::Int(number.get() as i64)]); - func.call_detached(vt.world(), args)?.display() - } - }) - } -} - -impl Cast> for EnumNumbering { - fn is(value: &Spanned) -> bool { - matches!(&value.v, Value::Content(_) | Value::Func(_)) - } - - fn cast(value: Spanned) -> StrResult { - match value.v { - Value::Str(v) => Ok(Self::Pattern(v.parse()?)), - Value::Func(v) => Ok(Self::Func(v, value.span)), - v => Self::error(v), - } - } - - fn describe() -> CastInfo { - CastInfo::Union(vec![CastInfo::Type("string"), CastInfo::Type("function")]) - } -} diff --git a/library/src/basics/heading.rs b/library/src/basics/heading.rs index 925d23a20..c2696dd6e 100644 --- a/library/src/basics/heading.rs +++ b/library/src/basics/heading.rs @@ -1,6 +1,6 @@ use typst::font::FontWeight; -use crate::compute::NumberingPattern; +use crate::compute::Numbering; use crate::layout::{BlockNode, VNode}; use crate::prelude::*; use crate::text::{SpaceNode, TextNode, TextSize}; @@ -15,8 +15,8 @@ use crate::text::{SpaceNode, TextNode, TextSize}; /// (not the document's title). /// /// Typst can automatically number your headings for you. To enable numbering, -/// specify how you want your headings to be numbered with a [numbering -/// pattern](@numbering). +/// specify how you want your headings to be numbered with a +/// [numbering pattern or function](@numbering). /// /// Independently from the numbering, Typst can also automatically generate an /// [outline](@outline) of all headings for you. To exclude one or more headings @@ -60,7 +60,8 @@ pub struct HeadingNode { #[node] impl HeadingNode { - /// How to number the heading. Accepts a [numbering pattern](@numbering). + /// How to number the heading. Accepts a + /// [numbering pattern or function](@numbering). /// /// # Example /// ``` @@ -71,7 +72,7 @@ impl HeadingNode { /// === A sub-subsection /// ``` #[property(referenced)] - pub const NUMBERING: Option = None; + pub const NUMBERING: Option = None; /// Whether the heading should appear in the outline. /// @@ -106,7 +107,12 @@ impl HeadingNode { } impl Prepare for HeadingNode { - fn prepare(&self, vt: &mut Vt, mut this: Content, styles: StyleChain) -> Content { + fn prepare( + &self, + vt: &mut Vt, + mut this: Content, + styles: StyleChain, + ) -> SourceResult { let my_id = vt.identify(&this); let mut counter = HeadingCounter::new(); @@ -115,30 +121,32 @@ impl Prepare for HeadingNode { break; } - if matches!(node.field("numbers"), Some(Value::Str(_))) { + let numbers = node.field("numbers").unwrap(); + if numbers != Value::None { let heading = node.to::().unwrap(); counter.advance(heading); } } let mut numbers = Value::None; - if let Some(pattern) = styles.get(Self::NUMBERING) { - numbers = Value::Str(pattern.apply(counter.advance(self)).into()); + if let Some(numbering) = styles.get(Self::NUMBERING) { + numbers = numbering.apply(vt.world(), counter.advance(self))?; } this.push_field("outlined", Value::Bool(styles.get(Self::OUTLINED))); this.push_field("numbers", numbers); let meta = Meta::Node(my_id, this.clone()); - this.styled(Meta::DATA, vec![meta]) + Ok(this.styled(Meta::DATA, vec![meta])) } } impl Show for HeadingNode { fn show(&self, _: &mut Vt, this: &Content, _: StyleChain) -> SourceResult { let mut realized = self.title.clone(); - if let Some(Value::Str(numbering)) = this.field("numbers") { - realized = TextNode::packed(numbering) + SpaceNode.pack() + realized; + let numbers = this.field("numbers").unwrap(); + if numbers != Value::None { + realized = numbers.display() + SpaceNode.pack() + realized; } Ok(BlockNode(realized).pack()) } diff --git a/library/src/basics/table.rs b/library/src/basics/table.rs index 01b16898e..208818301 100644 --- a/library/src/basics/table.rs +++ b/library/src/basics/table.rs @@ -199,7 +199,7 @@ pub enum Celled { /// A bare value, the same for all cells. Value(T), /// A closure mapping from cell coordinates to a value. - Func(Func, Span), + Func(Func), } impl Celled { @@ -207,24 +207,25 @@ impl Celled { pub fn resolve(&self, vt: &Vt, x: usize, y: usize) -> SourceResult { Ok(match self { Self::Value(value) => value.clone(), - Self::Func(func, span) => { - let args = Args::new(*span, [Value::Int(x as i64), Value::Int(y as i64)]); - func.call_detached(vt.world(), args)?.cast().at(*span)? + Self::Func(func) => { + let args = + Args::new(func.span(), [Value::Int(x as i64), Value::Int(y as i64)]); + func.call_detached(vt.world(), args)?.cast().at(func.span())? } }) } } -impl Cast> for Celled { - fn is(value: &Spanned) -> bool { - matches!(&value.v, Value::Func(_)) || T::is(&value.v) +impl Cast for Celled { + fn is(value: &Value) -> bool { + matches!(value, Value::Func(_)) || T::is(value) } - fn cast(value: Spanned) -> StrResult { - match value.v { - Value::Func(v) => Ok(Self::Func(v, value.span)), + fn cast(value: Value) -> StrResult { + match value { + Value::Func(v) => Ok(Self::Func(v)), v if T::is(&v) => Ok(Self::Value(T::cast(v)?)), - v => Self::error(v), + v => ::error(v), } } diff --git a/library/src/compute/data.rs b/library/src/compute/data.rs index bcf64360d..e3013389b 100644 --- a/library/src/compute/data.rs +++ b/library/src/compute/data.rs @@ -218,10 +218,7 @@ fn convert_json(value: serde_json::Value) -> Value { /// Format the user-facing JSON error message. fn format_json_error(error: serde_json::Error) -> String { assert!(error.is_syntax() || error.is_eof()); - format!( - "failed to parse json file: syntax error in line {}", - error.line() - ) + format!("failed to parse json file: syntax error in line {}", error.line()) } /// # XML @@ -252,22 +249,22 @@ fn format_json_error(error: serde_json::Error) -> String { /// let author = findChild(elem, "author") /// let pars = findChild(elem, "content") /// -/// heading((title.children)(0)) +/// heading(title.children.first()) /// text(10pt, weight: "medium")[ /// Published by -/// {(author.children)(0)} +/// {author.children.first()} /// ] /// /// for p in pars.children { /// if (type(p) == "dictionary") { /// parbreak() -/// (p.children)(0) +/// p.children.first() /// } /// } /// } /// -/// #let file = xml("example.xml") -/// #for child in file(0).children { +/// #let data = xml("example.xml") +/// #for child in data.first().children { /// if (type(child) == "dictionary") { /// article(child) /// } diff --git a/library/src/compute/utility.rs b/library/src/compute/utility.rs index 78cf39533..414a62f50 100644 --- a/library/src/compute/utility.rs +++ b/library/src/compute/utility.rs @@ -35,53 +35,105 @@ pub fn lorem(args: &mut Args) -> SourceResult { } /// # Numbering -/// Apply a numbering pattern to a sequence of numbers. +/// Apply a numbering to a sequence of numbers. /// -/// Numbering patterns are strings that define how a sequence of numbers should -/// be rendered as text. The patterns consist of [counting -/// symbols](#parameters--pattern) for which the actual number is substituted, -/// their prefixes, and one suffix. The prefixes and the suffix are repeated as-is. +/// A numbering defines how a sequence of numbers should be displayed as +/// content. It is defined either through a pattern string or an arbitrary +/// function. +/// +/// A numbering pattern consists of [counting symbols](#parameters--numbering) +/// for which the actual number is substituted, their prefixes, and one suffix. +/// The prefixes and the suffix are repeated as-is. /// /// ## Example /// ``` /// #numbering("1.1)", 1, 2, 3) \ /// #numbering("1.a.i", 1, 2) \ -/// #numbering("I – 1", 12, 2) +/// #numbering("I – 1", 12, 2) \ +/// #numbering( +/// (..nums) => nums +/// .pos() +/// .map(str) +/// .join(".") + ")", +/// 1, 2, 3, +/// ) /// ``` /// /// ## Parameters -/// - pattern: NumberingPattern (positional, required) -/// A string that defines how the numbering works. +/// - numbering: Numbering (positional, required) +/// Defines how the numbering works. /// -/// **Counting symbols** are `1`, `a`, `A`, `i`, `I` and `*`. They are replaced -/// by the number in the sequence, in the given case. +/// **Counting symbols** are `1`, `a`, `A`, `i`, `I` and `*`. They are +/// replaced by the number in the sequence, in the given case. /// -/// The `*` character means that symbols should be used to count, in the order -/// of `*`, `†`, `‡`, `§`, `¶`, and `‖`. If there are more than six items, the -/// number is represented using multiple symbols. +/// The `*` character means that symbols should be used to count, in the +/// order of `*`, `†`, `‡`, `§`, `¶`, and `‖`. If there are more than six +/// items, the number is represented using multiple symbols. /// /// **Suffixes** are all characters after the last counting symbol. They are /// repeated as-is at the end of any rendered number. /// /// **Prefixes** are all characters that are neither counting symbols nor -/// suffixes. They are repeated as-is at in front of their rendered equivalent -/// of their counting symbol. +/// suffixes. They are repeated as-is at in front of their rendered +/// equivalent of their counting symbol. +/// +/// This parameter can also be an arbitrary function that gets each number as +/// an individual argument. When given a function, the `numbering` function +/// just forwards the arguments to that function. While this is not +/// particularly useful in itself, it means that you can just give arbitrary +/// numberings to the `numbering` function without caring whether they are +/// defined as a pattern or function. /// /// - numbers: NonZeroUsize (positional, variadic) -/// The numbers to apply the pattern to. Must be positive. +/// The numbers to apply the numbering to. Must be positive. /// -/// If more numbers than counting symbols are given, the last counting symbol -/// with its prefix is repeated. +/// If `numbering` is a pattern and more numbers than counting symbols are +/// given, the last counting symbol with its prefix is repeated. /// -/// - returns: string +/// - returns: any /// /// ## Category /// utility #[func] -pub fn numbering(args: &mut Args) -> SourceResult { - let pattern = args.expect::("pattern")?; +pub fn numbering(vm: &Vm, args: &mut Args) -> SourceResult { + let numbering = args.expect::("pattern or function")?; let numbers = args.all::()?; - Ok(Value::Str(pattern.apply(&numbers).into())) + numbering.apply(vm.world(), &numbers) +} + +/// How to number an enumeration. +#[derive(Debug, Clone, Hash)] +pub enum Numbering { + /// A pattern with prefix, numbering, lower / upper case and suffix. + Pattern(NumberingPattern), + /// A closure mapping from an item's number to content. + Func(Func), +} + +impl Numbering { + /// Apply the pattern to the given numbers. + pub fn apply( + &self, + world: Tracked, + numbers: &[NonZeroUsize], + ) -> SourceResult { + Ok(match self { + Self::Pattern(pattern) => Value::Str(pattern.apply(numbers).into()), + Self::Func(func) => { + let args = Args::new( + func.span(), + numbers.iter().map(|n| Value::Int(n.get() as i64)), + ); + func.call_detached(world, args)? + } + }) + } +} + +castable! { + Numbering, + v: Str => Self::Pattern(v.parse()?), + v: Func => Self::Func(v), } /// How to turn a number into text. @@ -157,11 +209,6 @@ impl FromStr for NumberingPattern { } } -castable! { - NumberingPattern, - string: EcoString => string.parse()?, -} - /// Different kinds of numberings. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] enum NumberingKind { diff --git a/library/src/layout/mod.rs b/library/src/layout/mod.rs index 91822be83..330db7166 100644 --- a/library/src/layout/mod.rs +++ b/library/src/layout/mod.rs @@ -317,7 +317,8 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> { // Prepare only if this is the first application for this node. if let Some(node) = content.with::() { if !content.is_prepared() { - let prepared = node.prepare(self.vt, content.clone().prepared(), styles); + let prepared = + node.prepare(self.vt, content.clone().prepared(), styles)?; let stored = self.scratch.content.alloc(prepared); return self.accept(stored, styles); } diff --git a/library/src/layout/page.rs b/library/src/layout/page.rs index f4f9620c0..4f5231def 100644 --- a/library/src/layout/page.rs +++ b/library/src/layout/page.rs @@ -2,7 +2,6 @@ use std::str::FromStr; use super::ColumnsNode; use crate::prelude::*; -use crate::text::TextNode; /// # Page /// Layouts its child onto one or multiple pages. @@ -174,7 +173,7 @@ impl PageNode { /// #lorem(18) /// ``` #[property(referenced)] - pub const HEADER: Marginal = Marginal::None; + pub const HEADER: Option = None; /// The page's footer. /// @@ -199,7 +198,7 @@ impl PageNode { /// #lorem(18) /// ``` #[property(referenced)] - pub const FOOTER: Marginal = Marginal::None; + pub const FOOTER: Option = None; /// Content in the page's background. /// @@ -221,7 +220,7 @@ impl PageNode { /// (of typesetting). /// ``` #[property(referenced)] - pub const BACKGROUND: Marginal = Marginal::None; + pub const BACKGROUND: Option = None; /// Content in the page's foreground. /// @@ -239,7 +238,7 @@ impl PageNode { /// not understand our approach... /// ``` #[property(referenced)] - pub const FOREGROUND: Marginal = Marginal::None; + pub const FOREGROUND: Option = None; fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self(args.expect("body")?).pack()) @@ -323,14 +322,15 @@ impl PageNode { (foreground, Point::zero(), size), (background, Point::zero(), size), ] { - if let Some(content) = marginal.resolve(vt, page)? { - let pod = Regions::one(area, area, Axes::splat(true)); - let sub = content.layout(vt, styles, pod)?.into_frame(); - if std::ptr::eq(marginal, background) { - frame.prepend_frame(pos, sub); - } else { - frame.push_frame(pos, sub); - } + let in_background = std::ptr::eq(marginal, background); + let Some(marginal) = marginal else { continue }; + let content = marginal.resolve(vt, page)?; + let pod = Regions::one(area, area, Axes::splat(true)); + let sub = content.layout(vt, styles, pod)?.into_frame(); + if in_background { + frame.prepend_frame(pos, sub); + } else { + frame.push_frame(pos, sub); } } @@ -396,53 +396,29 @@ impl PagebreakNode { /// A header, footer, foreground or background definition. #[derive(Debug, Clone, Hash)] pub enum Marginal { - /// Nothing, - None, /// Bare content. Content(Content), /// A closure mapping from a page number to content. - Func(Func, Span), + Func(Func), } impl Marginal { /// Resolve the marginal based on the page number. - pub fn resolve(&self, vt: &Vt, page: usize) -> SourceResult> { + pub fn resolve(&self, vt: &Vt, page: usize) -> SourceResult { Ok(match self { - Self::None => None, - Self::Content(content) => Some(content.clone()), - Self::Func(func, span) => { - let args = Args::new(*span, [Value::Int(page as i64)]); - Some(func.call_detached(vt.world(), args)?.display()) + Self::Content(content) => content.clone(), + Self::Func(func) => { + let args = Args::new(func.span(), [Value::Int(page as i64)]); + func.call_detached(vt.world(), args)?.display() } }) } } -impl Cast> for Marginal { - fn is(value: &Spanned) -> bool { - matches!( - &value.v, - Value::None | Value::Str(_) | Value::Content(_) | Value::Func(_) - ) - } - - fn cast(value: Spanned) -> StrResult { - match value.v { - Value::None => Ok(Self::None), - 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 => Self::error(v), - } - } - - fn describe() -> CastInfo { - CastInfo::Union(vec![ - CastInfo::Type("none"), - CastInfo::Type("content"), - CastInfo::Type("function"), - ]) - } +castable! { + Marginal, + v: Content => Self::Content(v), + v: Func => Self::Func(v), } /// Specification of a paper. diff --git a/library/src/meta/outline.rs b/library/src/meta/outline.rs index c3d17196b..4b70666b6 100644 --- a/library/src/meta/outline.rs +++ b/library/src/meta/outline.rs @@ -84,7 +84,12 @@ impl OutlineNode { } impl Prepare for OutlineNode { - fn prepare(&self, vt: &mut Vt, mut this: Content, _: StyleChain) -> Content { + fn prepare( + &self, + vt: &mut Vt, + mut this: Content, + _: StyleChain, + ) -> SourceResult { let headings = vt .locate(Selector::node::()) .into_iter() @@ -94,7 +99,7 @@ impl Prepare for OutlineNode { .collect(); this.push_field("headings", Value::Array(Array::from_vec(headings))); - this + Ok(this) } } @@ -151,32 +156,27 @@ impl Show for OutlineNode { // Add hidden ancestors numberings to realize the indent. if indent { - let text = ancestors + let hidden: Vec<_> = ancestors .iter() - .filter_map(|node| match node.field("numbers").unwrap() { - Value::Str(numbering) => { - Some(EcoString::from(numbering) + ' '.into()) - } - _ => None, - }) - .collect::(); + .map(|node| node.field("numbers").unwrap()) + .filter(|numbers| *numbers != Value::None) + .map(|numbers| numbers.display() + SpaceNode.pack()) + .collect(); - if !text.is_empty() { - seq.push(HideNode(TextNode::packed(text)).pack()); + if !hidden.is_empty() { + seq.push(HideNode(Content::sequence(hidden)).pack()); seq.push(SpaceNode.pack()); } } // Format the numbering. - let numbering = match node.field("numbers").unwrap() { - Value::Str(numbering) => { - TextNode::packed(EcoString::from(numbering) + ' '.into()) - } - _ => Content::empty(), + let mut start = heading.title.clone(); + let numbers = node.field("numbers").unwrap(); + if numbers != Value::None { + start = numbers.display() + SpaceNode.pack() + start; }; // Add the numbering and section name. - let start = numbering + heading.title.clone(); seq.push(start.linked(Destination::Internal(loc))); // Add filler symbols between the section name and page number. diff --git a/library/src/text/raw.rs b/library/src/text/raw.rs index c6026f898..2f0ec79ec 100644 --- a/library/src/text/raw.rs +++ b/library/src/text/raw.rs @@ -132,7 +132,12 @@ impl RawNode { } impl Prepare for RawNode { - fn prepare(&self, _: &mut Vt, mut this: Content, styles: StyleChain) -> Content { + fn prepare( + &self, + _: &mut Vt, + mut this: Content, + styles: StyleChain, + ) -> SourceResult { this.push_field( "lang", match styles.get(Self::LANG) { @@ -140,7 +145,7 @@ impl Prepare for RawNode { None => Value::None, }, ); - this + Ok(this) } } diff --git a/src/diag.rs b/src/diag.rs index 55f16b5fc..e0015fcc6 100644 --- a/src/diag.rs +++ b/src/diag.rs @@ -66,7 +66,9 @@ pub struct SourceError { impl SourceError { /// Create a new, bare error. + #[track_caller] pub fn new(span: Span, message: impl Into) -> Self { + assert!(!span.is_detached()); Self { span, pos: ErrorPos::Full, diff --git a/src/model/args.rs b/src/model/args.rs index 4aaaded4f..9ab07ed8c 100644 --- a/src/model/args.rs +++ b/src/model/args.rs @@ -150,7 +150,7 @@ impl Args { } /// Extract the positional arguments as an array. - pub fn to_positional(&self) -> Array { + pub fn to_pos(&self) -> Array { self.items .iter() .filter(|item| item.name.is_none()) diff --git a/src/model/array.rs b/src/model/array.rs index fb740a13c..28b9d1a03 100644 --- a/src/model/array.rs +++ b/src/model/array.rs @@ -5,7 +5,6 @@ use std::sync::Arc; use super::{ops, Args, Func, Value, Vm}; use crate::diag::{bail, At, SourceResult, StrResult}; -use crate::syntax::Spanned; use crate::util::{format_eco, ArcExt, EcoString}; /// Create a new [`Array`] from values. @@ -137,13 +136,13 @@ impl Array { } /// Return the first matching element. - pub fn find(&self, vm: &Vm, f: Spanned) -> SourceResult> { - if f.v.argc().map_or(false, |count| count != 1) { - bail!(f.span, "function must have exactly one parameter"); + pub fn find(&self, vm: &Vm, func: Func) -> SourceResult> { + if func.argc().map_or(false, |count| count != 1) { + bail!(func.span(), "function must have exactly one parameter"); } for item in self.iter() { - let args = Args::new(f.span, [item.clone()]); - if f.v.call(vm, args)?.cast::().at(f.span)? { + let args = Args::new(func.span(), [item.clone()]); + if func.call(vm, args)?.cast::().at(func.span())? { return Ok(Some(item.clone())); } } @@ -152,13 +151,13 @@ impl Array { } /// Return the index of the first matching element. - pub fn position(&self, vm: &Vm, f: Spanned) -> SourceResult> { - if f.v.argc().map_or(false, |count| count != 1) { - bail!(f.span, "function must have exactly one parameter"); + pub fn position(&self, vm: &Vm, func: Func) -> SourceResult> { + if func.argc().map_or(false, |count| count != 1) { + bail!(func.span(), "function must have exactly one parameter"); } for (i, item) in self.iter().enumerate() { - let args = Args::new(f.span, [item.clone()]); - if f.v.call(vm, args)?.cast::().at(f.span)? { + let args = Args::new(func.span(), [item.clone()]); + if func.call(vm, args)?.cast::().at(func.span())? { return Ok(Some(i as i64)); } } @@ -168,14 +167,14 @@ impl Array { /// Return a new array with only those elements for which the function /// returns true. - pub fn filter(&self, vm: &Vm, f: Spanned) -> SourceResult { - if f.v.argc().map_or(false, |count| count != 1) { - bail!(f.span, "function must have exactly one parameter"); + pub fn filter(&self, vm: &Vm, func: Func) -> SourceResult { + if func.argc().map_or(false, |count| count != 1) { + bail!(func.span(), "function must have exactly one parameter"); } let mut kept = vec![]; for item in self.iter() { - let args = Args::new(f.span, [item.clone()]); - if f.v.call(vm, args)?.cast::().at(f.span)? { + let args = Args::new(func.span(), [item.clone()]); + if func.call(vm, args)?.cast::().at(func.span())? { kept.push(item.clone()) } } @@ -183,45 +182,45 @@ impl Array { } /// Transform each item in the array with a function. - pub fn map(&self, vm: &Vm, f: Spanned) -> SourceResult { - if f.v.argc().map_or(false, |count| count < 1 || count > 2) { - bail!(f.span, "function must have one or two parameters"); + pub fn map(&self, vm: &Vm, func: Func) -> SourceResult { + if func.argc().map_or(false, |count| count < 1 || count > 2) { + bail!(func.span(), "function must have one or two parameters"); } - let enumerate = f.v.argc() == Some(2); + let enumerate = func.argc() == Some(2); self.iter() .enumerate() .map(|(i, item)| { - let mut args = Args::new(f.span, []); + let mut args = Args::new(func.span(), []); if enumerate { - args.push(f.span, Value::Int(i as i64)); + args.push(func.span(), Value::Int(i as i64)); } - args.push(f.span, item.clone()); - f.v.call(vm, args) + args.push(func.span(), item.clone()); + func.call(vm, args) }) .collect() } /// Fold all of the array's elements into one with a function. - pub fn fold(&self, vm: &Vm, init: Value, f: Spanned) -> SourceResult { - if f.v.argc().map_or(false, |count| count != 2) { - bail!(f.span, "function must have exactly two parameters"); + pub fn fold(&self, vm: &Vm, init: Value, func: Func) -> SourceResult { + if func.argc().map_or(false, |count| count != 2) { + bail!(func.span(), "function must have exactly two parameters"); } let mut acc = init; for item in self.iter() { - let args = Args::new(f.span, [acc, item.clone()]); - acc = f.v.call(vm, args)?; + let args = Args::new(func.span(), [acc, item.clone()]); + acc = func.call(vm, args)?; } Ok(acc) } /// Whether any element matches. - pub fn any(&self, vm: &Vm, f: Spanned) -> SourceResult { - if f.v.argc().map_or(false, |count| count != 1) { - bail!(f.span, "function must have exactly one parameter"); + pub fn any(&self, vm: &Vm, func: Func) -> SourceResult { + if func.argc().map_or(false, |count| count != 1) { + bail!(func.span(), "function must have exactly one parameter"); } for item in self.iter() { - let args = Args::new(f.span, [item.clone()]); - if f.v.call(vm, args)?.cast::().at(f.span)? { + let args = Args::new(func.span(), [item.clone()]); + if func.call(vm, args)?.cast::().at(func.span())? { return Ok(true); } } @@ -230,13 +229,13 @@ impl Array { } /// Whether all elements match. - pub fn all(&self, vm: &Vm, f: Spanned) -> SourceResult { - if f.v.argc().map_or(false, |count| count != 1) { - bail!(f.span, "function must have exactly one parameter"); + pub fn all(&self, vm: &Vm, func: Func) -> SourceResult { + if func.argc().map_or(false, |count| count != 1) { + bail!(func.span(), "function must have exactly one parameter"); } for item in self.iter() { - let args = Args::new(f.span, [item.clone()]); - if !f.v.call(vm, args)?.cast::().at(f.span)? { + let args = Args::new(func.span(), [item.clone()]); + if !func.call(vm, args)?.cast::().at(func.span())? { return Ok(false); } } diff --git a/src/model/dict.rs b/src/model/dict.rs index 83c16824f..a5dbeeae2 100644 --- a/src/model/dict.rs +++ b/src/model/dict.rs @@ -6,7 +6,6 @@ use std::sync::Arc; use super::{Args, Array, Func, Str, Value, Vm}; use crate::diag::{bail, SourceResult, StrResult}; use crate::syntax::is_ident; -use crate::syntax::Spanned; use crate::util::{format_eco, ArcExt, EcoString}; /// Create a new [`Dict`] from key-value pairs. @@ -107,14 +106,15 @@ impl Dict { } /// Transform each pair in the dictionary with a function. - pub fn map(&self, vm: &Vm, f: Spanned) -> SourceResult { - if f.v.argc().map_or(false, |count| count != 1) { - bail!(f.span, "function must have exactly two parameters"); + pub fn map(&self, vm: &Vm, func: Func) -> SourceResult { + if func.argc().map_or(false, |count| count != 2) { + bail!(func.span(), "function must have exactly two parameters"); } self.iter() .map(|(key, value)| { - let args = Args::new(f.span, [Value::Str(key.clone()), value.clone()]); - f.v.call(vm, args) + let args = + Args::new(func.span(), [Value::Str(key.clone()), value.clone()]); + func.call(vm, args) }) .collect() } diff --git a/src/model/eval.rs b/src/model/eval.rs index ab89f9c23..e9134114e 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -563,7 +563,11 @@ impl Eval for ast::Ident { type Output = Value; fn eval(&self, vm: &mut Vm) -> SourceResult { - vm.scopes.get(self).cloned().at(self.span()) + let value = vm.scopes.get(self).cloned().at(self.span())?; + Ok(match value { + Value::Func(func) => Value::Func(func.spanned(self.span())), + value => value, + }) } } @@ -912,15 +916,17 @@ impl Eval for ast::Closure { } } - // Define the actual function. - Ok(Value::Func(Func::from_closure(Closure { + // Define the closure function. + let closure = Closure { location: vm.location, name, captured, params, sink, body: self.body(), - }))) + }; + + Ok(Value::Func(Func::from_closure(closure, self.span()))) } } diff --git a/src/model/func.rs b/src/model/func.rs index 3fb8f4d4d..878b717f4 100644 --- a/src/model/func.rs +++ b/src/model/func.rs @@ -10,13 +10,13 @@ use super::{ }; use crate::diag::{bail, SourceResult, StrResult}; use crate::syntax::ast::{self, AstNode, Expr}; -use crate::syntax::{SourceId, SyntaxNode}; +use crate::syntax::{SourceId, Span, SyntaxNode}; use crate::util::EcoString; use crate::World; /// An evaluatable function. #[derive(Clone, Hash)] -pub struct Func(Arc); +pub struct Func(Arc, Span); /// The different kinds of function representations. #[derive(Hash)] @@ -40,27 +40,33 @@ impl Func { func: fn(&Vm, &mut Args) -> SourceResult, info: FuncInfo, ) -> Self { - Self(Arc::new(Repr::Native(Native { func, set: None, node: None, info }))) + Self( + Arc::new(Repr::Native(Native { func, set: None, node: None, info })), + Span::detached(), + ) } /// Create a new function from a native rust node. pub fn from_node(mut info: FuncInfo) -> Self { info.params.extend(T::properties()); - Self(Arc::new(Repr::Native(Native { - func: |ctx, args| { - let styles = T::set(args, true)?; - let content = T::construct(ctx, args)?; - Ok(Value::Content(content.styled_with_map(styles.scoped()))) - }, - set: Some(|args| T::set(args, false)), - node: Some(NodeId::of::()), - info, - }))) + Self( + Arc::new(Repr::Native(Native { + func: |ctx, args| { + let styles = T::set(args, true)?; + let content = T::construct(ctx, args)?; + Ok(Value::Content(content.styled_with_map(styles.scoped()))) + }, + set: Some(|args| T::set(args, false)), + node: Some(NodeId::of::()), + info, + })), + Span::detached(), + ) } /// Create a new function from a closure. - pub(super) fn from_closure(closure: Closure) -> Self { - Self(Arc::new(Repr::Closure(closure))) + pub(super) fn from_closure(closure: Closure, span: Span) -> Self { + Self(Arc::new(Repr::Closure(closure)), span) } /// The name of the function. @@ -81,6 +87,17 @@ impl Func { } } + /// The function's span. + pub fn span(&self) -> Span { + self.1 + } + + /// Attach a span to the function. + pub fn spanned(mut self, span: Span) -> Self { + self.1 = span; + self + } + /// The number of positional arguments this function takes, if known. pub fn argc(&self) -> Option { match self.0.as_ref() { @@ -121,7 +138,8 @@ impl Func { /// Apply the given arguments to the function. pub fn with(self, args: Args) -> Self { - Self(Arc::new(Repr::With(self, args))) + let span = self.1; + Self(Arc::new(Repr::With(self, args)), span) } /// Create a selector for this function's node type, filtering by node's diff --git a/src/model/methods.rs b/src/model/methods.rs index 8155685c6..3823df415 100644 --- a/src/model/methods.rs +++ b/src/model/methods.rs @@ -118,7 +118,7 @@ pub fn call( }, Value::Args(args) => match method { - "positional" => Value::Array(args.to_positional()), + "pos" => Value::Array(args.to_pos()), "named" => Value::Dict(args.to_named()), _ => return missing(), }, diff --git a/src/model/realize.rs b/src/model/realize.rs index 862bc8b6f..39c1fd422 100644 --- a/src/model/realize.rs +++ b/src/model/realize.rs @@ -136,7 +136,12 @@ fn try_apply( #[capability] pub trait Prepare { /// Prepare the node for show rule application. - fn prepare(&self, vt: &mut Vt, this: Content, styles: StyleChain) -> Content; + fn prepare( + &self, + vt: &mut Vt, + this: Content, + styles: StyleChain, + ) -> SourceResult; } /// The base recipe for a node. diff --git a/tests/typ/compiler/for.typ b/tests/typ/compiler/for.typ index 822f7423c..f63b870e1 100644 --- a/tests/typ/compiler/for.typ +++ b/tests/typ/compiler/for.typ @@ -33,7 +33,7 @@ #for v in (1, 2, 3, 4, 5, 6, 7) [#if v >= 2 and v <= 5 { repr(v) }] // Map captured arguments. -#let f1(..args) = args.positional().map(repr) +#let f1(..args) = args.pos().map(repr) #let f2(..args) = args.named().pairs((k, v) => repr(k) + ": " + repr(v)) #let f(..args) = (f1(..args) + f2(..args)).join(", ") #f(1, a: 2) diff --git a/tests/typ/compiler/return.typ b/tests/typ/compiler/return.typ index 0eea394e2..779a56ddc 100644 --- a/tests/typ/compiler/return.typ +++ b/tests/typ/compiler/return.typ @@ -57,7 +57,7 @@ // Test that the expression is evaluated to the end. #let sum(..args) = { let s = 0 - for v in args.positional() { + for v in args.pos() { s += v } s