Numbering functions

This commit is contained in:
Laurenz 2022-12-30 19:40:29 +01:00
parent f70cea508c
commit a6d90c1bf1
19 changed files with 267 additions and 243 deletions

View File

@ -1,9 +1,8 @@
use std::str::FromStr; use std::str::FromStr;
use crate::compute::NumberingPattern; use crate::compute::{Numbering, NumberingPattern};
use crate::layout::{BlockNode, GridNode, ParNode, Spacing, TrackSizing}; use crate::layout::{BlockNode, GridNode, ParNode, Spacing, TrackSizing};
use crate::prelude::*; use crate::prelude::*;
use crate::text::TextNode;
/// # Numbered List /// # Numbered List
/// A numbered list. /// A numbered list.
@ -98,7 +97,7 @@ pub struct EnumNode {
#[node] #[node]
impl EnumNode { impl EnumNode {
/// How to number the enumeration. Accepts a /// How to number the enumeration. Accepts a
/// [numbering pattern](@numbering). /// [numbering pattern or function](@numbering).
/// ///
/// # Example /// # Example
/// ``` /// ```
@ -109,8 +108,8 @@ impl EnumNode {
/// + Style /// + Style
/// ``` /// ```
#[property(referenced)] #[property(referenced)]
pub const NUMBERING: EnumNumbering = pub const NUMBERING: Numbering =
EnumNumbering::Pattern(NumberingPattern::from_str("1.").unwrap()); Numbering::Pattern(NumberingPattern::from_str("1.").unwrap());
/// The indentation of each item's label. /// The indentation of each item's label.
#[property(resolve)] #[property(resolve)]
@ -188,7 +187,7 @@ impl Layout for EnumNode {
let mut number = NonZeroUsize::new(1).unwrap(); let mut number = NonZeroUsize::new(1).unwrap();
for ((n, item), map) in self.items.iter() { for ((n, item), map) in self.items.iter() {
number = n.unwrap_or(number); 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(Content::empty());
cells.push(resolved.styled_with_map(map.clone())); cells.push(resolved.styled_with_map(map.clone()));
cells.push(Content::empty()); cells.push(Content::empty());
@ -209,43 +208,3 @@ impl Layout for EnumNode {
.layout(vt, styles, regions) .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<Content> {
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<Spanned<Value>> for EnumNumbering {
fn is(value: &Spanned<Value>) -> bool {
matches!(&value.v, Value::Content(_) | Value::Func(_))
}
fn cast(value: Spanned<Value>) -> StrResult<Self> {
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")])
}
}

View File

@ -1,6 +1,6 @@
use typst::font::FontWeight; use typst::font::FontWeight;
use crate::compute::NumberingPattern; use crate::compute::Numbering;
use crate::layout::{BlockNode, VNode}; use crate::layout::{BlockNode, VNode};
use crate::prelude::*; use crate::prelude::*;
use crate::text::{SpaceNode, TextNode, TextSize}; use crate::text::{SpaceNode, TextNode, TextSize};
@ -15,8 +15,8 @@ use crate::text::{SpaceNode, TextNode, TextSize};
/// (not the document's title). /// (not the document's title).
/// ///
/// Typst can automatically number your headings for you. To enable numbering, /// Typst can automatically number your headings for you. To enable numbering,
/// specify how you want your headings to be numbered with a [numbering /// specify how you want your headings to be numbered with a
/// pattern](@numbering). /// [numbering pattern or function](@numbering).
/// ///
/// Independently from the numbering, Typst can also automatically generate an /// Independently from the numbering, Typst can also automatically generate an
/// [outline](@outline) of all headings for you. To exclude one or more headings /// [outline](@outline) of all headings for you. To exclude one or more headings
@ -60,7 +60,8 @@ pub struct HeadingNode {
#[node] #[node]
impl HeadingNode { 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 /// # Example
/// ``` /// ```
@ -71,7 +72,7 @@ impl HeadingNode {
/// === A sub-subsection /// === A sub-subsection
/// ``` /// ```
#[property(referenced)] #[property(referenced)]
pub const NUMBERING: Option<NumberingPattern> = None; pub const NUMBERING: Option<Numbering> = None;
/// Whether the heading should appear in the outline. /// Whether the heading should appear in the outline.
/// ///
@ -106,7 +107,12 @@ impl HeadingNode {
} }
impl Prepare for 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<Content> {
let my_id = vt.identify(&this); let my_id = vt.identify(&this);
let mut counter = HeadingCounter::new(); let mut counter = HeadingCounter::new();
@ -115,30 +121,32 @@ impl Prepare for HeadingNode {
break; break;
} }
if matches!(node.field("numbers"), Some(Value::Str(_))) { let numbers = node.field("numbers").unwrap();
if numbers != Value::None {
let heading = node.to::<Self>().unwrap(); let heading = node.to::<Self>().unwrap();
counter.advance(heading); counter.advance(heading);
} }
} }
let mut numbers = Value::None; let mut numbers = Value::None;
if let Some(pattern) = styles.get(Self::NUMBERING) { if let Some(numbering) = styles.get(Self::NUMBERING) {
numbers = Value::Str(pattern.apply(counter.advance(self)).into()); numbers = numbering.apply(vt.world(), counter.advance(self))?;
} }
this.push_field("outlined", Value::Bool(styles.get(Self::OUTLINED))); this.push_field("outlined", Value::Bool(styles.get(Self::OUTLINED)));
this.push_field("numbers", numbers); this.push_field("numbers", numbers);
let meta = Meta::Node(my_id, this.clone()); 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 { impl Show for HeadingNode {
fn show(&self, _: &mut Vt, this: &Content, _: StyleChain) -> SourceResult<Content> { fn show(&self, _: &mut Vt, this: &Content, _: StyleChain) -> SourceResult<Content> {
let mut realized = self.title.clone(); let mut realized = self.title.clone();
if let Some(Value::Str(numbering)) = this.field("numbers") { let numbers = this.field("numbers").unwrap();
realized = TextNode::packed(numbering) + SpaceNode.pack() + realized; if numbers != Value::None {
realized = numbers.display() + SpaceNode.pack() + realized;
} }
Ok(BlockNode(realized).pack()) Ok(BlockNode(realized).pack())
} }

View File

@ -199,7 +199,7 @@ pub enum Celled<T> {
/// A bare value, the same for all cells. /// A bare value, the same for all cells.
Value(T), Value(T),
/// A closure mapping from cell coordinates to a value. /// A closure mapping from cell coordinates to a value.
Func(Func, Span), Func(Func),
} }
impl<T: Cast + Clone> Celled<T> { impl<T: Cast + Clone> Celled<T> {
@ -207,24 +207,25 @@ impl<T: Cast + Clone> Celled<T> {
pub fn resolve(&self, vt: &Vt, x: usize, y: usize) -> SourceResult<T> { pub fn resolve(&self, vt: &Vt, x: usize, y: usize) -> SourceResult<T> {
Ok(match self { Ok(match self {
Self::Value(value) => value.clone(), Self::Value(value) => value.clone(),
Self::Func(func, span) => { Self::Func(func) => {
let args = Args::new(*span, [Value::Int(x as i64), Value::Int(y as i64)]); let args =
func.call_detached(vt.world(), args)?.cast().at(*span)? 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<T: Cast> Cast<Spanned<Value>> for Celled<T> { impl<T: Cast> Cast for Celled<T> {
fn is(value: &Spanned<Value>) -> bool { fn is(value: &Value) -> bool {
matches!(&value.v, Value::Func(_)) || T::is(&value.v) matches!(value, Value::Func(_)) || T::is(value)
} }
fn cast(value: Spanned<Value>) -> StrResult<Self> { fn cast(value: Value) -> StrResult<Self> {
match value.v { match value {
Value::Func(v) => Ok(Self::Func(v, value.span)), Value::Func(v) => Ok(Self::Func(v)),
v if T::is(&v) => Ok(Self::Value(T::cast(v)?)), v if T::is(&v) => Ok(Self::Value(T::cast(v)?)),
v => Self::error(v), v => <Self as Cast>::error(v),
} }
} }

View File

@ -218,10 +218,7 @@ fn convert_json(value: serde_json::Value) -> Value {
/// Format the user-facing JSON error message. /// Format the user-facing JSON error message.
fn format_json_error(error: serde_json::Error) -> String { fn format_json_error(error: serde_json::Error) -> String {
assert!(error.is_syntax() || error.is_eof()); assert!(error.is_syntax() || error.is_eof());
format!( format!("failed to parse json file: syntax error in line {}", error.line())
"failed to parse json file: syntax error in line {}",
error.line()
)
} }
/// # XML /// # XML
@ -252,22 +249,22 @@ fn format_json_error(error: serde_json::Error) -> String {
/// let author = findChild(elem, "author") /// let author = findChild(elem, "author")
/// let pars = findChild(elem, "content") /// let pars = findChild(elem, "content")
/// ///
/// heading((title.children)(0)) /// heading(title.children.first())
/// text(10pt, weight: "medium")[ /// text(10pt, weight: "medium")[
/// Published by /// Published by
/// {(author.children)(0)} /// {author.children.first()}
/// ] /// ]
/// ///
/// for p in pars.children { /// for p in pars.children {
/// if (type(p) == "dictionary") { /// if (type(p) == "dictionary") {
/// parbreak() /// parbreak()
/// (p.children)(0) /// p.children.first()
/// } /// }
/// } /// }
/// } /// }
/// ///
/// #let file = xml("example.xml") /// #let data = xml("example.xml")
/// #for child in file(0).children { /// #for child in data.first().children {
/// if (type(child) == "dictionary") { /// if (type(child) == "dictionary") {
/// article(child) /// article(child)
/// } /// }

View File

@ -35,53 +35,105 @@ pub fn lorem(args: &mut Args) -> SourceResult<Value> {
} }
/// # Numbering /// # 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 /// A numbering defines how a sequence of numbers should be displayed as
/// be rendered as text. The patterns consist of [counting /// content. It is defined either through a pattern string or an arbitrary
/// symbols](#parameters--pattern) for which the actual number is substituted, /// function.
/// their prefixes, and one suffix. The prefixes and the suffix are repeated as-is. ///
/// 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 /// ## Example
/// ``` /// ```
/// #numbering("1.1)", 1, 2, 3) \ /// #numbering("1.1)", 1, 2, 3) \
/// #numbering("1.a.i", 1, 2) \ /// #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 /// ## Parameters
/// - pattern: NumberingPattern (positional, required) /// - numbering: Numbering (positional, required)
/// A string that defines how the numbering works. /// Defines how the numbering works.
/// ///
/// **Counting symbols** are `1`, `a`, `A`, `i`, `I` and `*`. They are replaced /// **Counting symbols** are `1`, `a`, `A`, `i`, `I` and `*`. They are
/// by the number in the sequence, in the given case. /// replaced by the number in the sequence, in the given case.
/// ///
/// The `*` character means that symbols should be used to count, in the order /// The `*` character means that symbols should be used to count, in the
/// of `*`, `†`, `‡`, `§`, `¶`, and `‖`. If there are more than six items, the /// order of `*`, `†`, `‡`, `§`, `¶`, and `‖`. If there are more than six
/// number is represented using multiple symbols. /// items, the number is represented using multiple symbols.
/// ///
/// **Suffixes** are all characters after the last counting symbol. They are /// **Suffixes** are all characters after the last counting symbol. They are
/// repeated as-is at the end of any rendered number. /// repeated as-is at the end of any rendered number.
/// ///
/// **Prefixes** are all characters that are neither counting symbols nor /// **Prefixes** are all characters that are neither counting symbols nor
/// suffixes. They are repeated as-is at in front of their rendered equivalent /// suffixes. They are repeated as-is at in front of their rendered
/// of their counting symbol. /// 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) /// - 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 /// If `numbering` is a pattern and more numbers than counting symbols are
/// with its prefix is repeated. /// given, the last counting symbol with its prefix is repeated.
/// ///
/// - returns: string /// - returns: any
/// ///
/// ## Category /// ## Category
/// utility /// utility
#[func] #[func]
pub fn numbering(args: &mut Args) -> SourceResult<Value> { pub fn numbering(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
let pattern = args.expect::<NumberingPattern>("pattern")?; let numbering = args.expect::<Numbering>("pattern or function")?;
let numbers = args.all::<NonZeroUsize>()?; let numbers = args.all::<NonZeroUsize>()?;
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<dyn World>,
numbers: &[NonZeroUsize],
) -> SourceResult<Value> {
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. /// 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. /// Different kinds of numberings.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
enum NumberingKind { enum NumberingKind {

View File

@ -317,7 +317,8 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
// Prepare only if this is the first application for this node. // Prepare only if this is the first application for this node.
if let Some(node) = content.with::<dyn Prepare>() { if let Some(node) = content.with::<dyn Prepare>() {
if !content.is_prepared() { 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); let stored = self.scratch.content.alloc(prepared);
return self.accept(stored, styles); return self.accept(stored, styles);
} }

View File

@ -2,7 +2,6 @@ use std::str::FromStr;
use super::ColumnsNode; use super::ColumnsNode;
use crate::prelude::*; use crate::prelude::*;
use crate::text::TextNode;
/// # Page /// # Page
/// Layouts its child onto one or multiple pages. /// Layouts its child onto one or multiple pages.
@ -174,7 +173,7 @@ impl PageNode {
/// #lorem(18) /// #lorem(18)
/// ``` /// ```
#[property(referenced)] #[property(referenced)]
pub const HEADER: Marginal = Marginal::None; pub const HEADER: Option<Marginal> = None;
/// The page's footer. /// The page's footer.
/// ///
@ -199,7 +198,7 @@ impl PageNode {
/// #lorem(18) /// #lorem(18)
/// ``` /// ```
#[property(referenced)] #[property(referenced)]
pub const FOOTER: Marginal = Marginal::None; pub const FOOTER: Option<Marginal> = None;
/// Content in the page's background. /// Content in the page's background.
/// ///
@ -221,7 +220,7 @@ impl PageNode {
/// (of typesetting). /// (of typesetting).
/// ``` /// ```
#[property(referenced)] #[property(referenced)]
pub const BACKGROUND: Marginal = Marginal::None; pub const BACKGROUND: Option<Marginal> = None;
/// Content in the page's foreground. /// Content in the page's foreground.
/// ///
@ -239,7 +238,7 @@ impl PageNode {
/// not understand our approach... /// not understand our approach...
/// ``` /// ```
#[property(referenced)] #[property(referenced)]
pub const FOREGROUND: Marginal = Marginal::None; pub const FOREGROUND: Option<Marginal> = None;
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack()) Ok(Self(args.expect("body")?).pack())
@ -323,16 +322,17 @@ impl PageNode {
(foreground, Point::zero(), size), (foreground, Point::zero(), size),
(background, Point::zero(), size), (background, Point::zero(), size),
] { ] {
if let Some(content) = marginal.resolve(vt, page)? { 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 pod = Regions::one(area, area, Axes::splat(true));
let sub = content.layout(vt, styles, pod)?.into_frame(); let sub = content.layout(vt, styles, pod)?.into_frame();
if std::ptr::eq(marginal, background) { if in_background {
frame.prepend_frame(pos, sub); frame.prepend_frame(pos, sub);
} else { } else {
frame.push_frame(pos, sub); frame.push_frame(pos, sub);
} }
} }
}
page += 1; page += 1;
} }
@ -396,53 +396,29 @@ impl PagebreakNode {
/// A header, footer, foreground or background definition. /// A header, footer, foreground or background definition.
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash)]
pub enum Marginal { pub enum Marginal {
/// Nothing,
None,
/// Bare content. /// Bare content.
Content(Content), Content(Content),
/// A closure mapping from a page number to content. /// A closure mapping from a page number to content.
Func(Func, Span), Func(Func),
} }
impl Marginal { impl Marginal {
/// Resolve the marginal based on the page number. /// Resolve the marginal based on the page number.
pub fn resolve(&self, vt: &Vt, page: usize) -> SourceResult<Option<Content>> { pub fn resolve(&self, vt: &Vt, page: usize) -> SourceResult<Content> {
Ok(match self { Ok(match self {
Self::None => None, Self::Content(content) => content.clone(),
Self::Content(content) => Some(content.clone()), Self::Func(func) => {
Self::Func(func, span) => { let args = Args::new(func.span(), [Value::Int(page as i64)]);
let args = Args::new(*span, [Value::Int(page as i64)]); func.call_detached(vt.world(), args)?.display()
Some(func.call_detached(vt.world(), args)?.display())
} }
}) })
} }
} }
impl Cast<Spanned<Value>> for Marginal { castable! {
fn is(value: &Spanned<Value>) -> bool { Marginal,
matches!( v: Content => Self::Content(v),
&value.v, v: Func => Self::Func(v),
Value::None | Value::Str(_) | Value::Content(_) | Value::Func(_)
)
}
fn cast(value: Spanned<Value>) -> StrResult<Self> {
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"),
])
}
} }
/// Specification of a paper. /// Specification of a paper.

View File

@ -84,7 +84,12 @@ impl OutlineNode {
} }
impl Prepare for 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<Content> {
let headings = vt let headings = vt
.locate(Selector::node::<HeadingNode>()) .locate(Selector::node::<HeadingNode>())
.into_iter() .into_iter()
@ -94,7 +99,7 @@ impl Prepare for OutlineNode {
.collect(); .collect();
this.push_field("headings", Value::Array(Array::from_vec(headings))); 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. // Add hidden ancestors numberings to realize the indent.
if indent { if indent {
let text = ancestors let hidden: Vec<_> = ancestors
.iter() .iter()
.filter_map(|node| match node.field("numbers").unwrap() { .map(|node| node.field("numbers").unwrap())
Value::Str(numbering) => { .filter(|numbers| *numbers != Value::None)
Some(EcoString::from(numbering) + ' '.into()) .map(|numbers| numbers.display() + SpaceNode.pack())
} .collect();
_ => None,
})
.collect::<EcoString>();
if !text.is_empty() { if !hidden.is_empty() {
seq.push(HideNode(TextNode::packed(text)).pack()); seq.push(HideNode(Content::sequence(hidden)).pack());
seq.push(SpaceNode.pack()); seq.push(SpaceNode.pack());
} }
} }
// Format the numbering. // Format the numbering.
let numbering = match node.field("numbers").unwrap() { let mut start = heading.title.clone();
Value::Str(numbering) => { let numbers = node.field("numbers").unwrap();
TextNode::packed(EcoString::from(numbering) + ' '.into()) if numbers != Value::None {
} start = numbers.display() + SpaceNode.pack() + start;
_ => Content::empty(),
}; };
// Add the numbering and section name. // Add the numbering and section name.
let start = numbering + heading.title.clone();
seq.push(start.linked(Destination::Internal(loc))); seq.push(start.linked(Destination::Internal(loc)));
// Add filler symbols between the section name and page number. // Add filler symbols between the section name and page number.

View File

@ -132,7 +132,12 @@ impl RawNode {
} }
impl Prepare for 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<Content> {
this.push_field( this.push_field(
"lang", "lang",
match styles.get(Self::LANG) { match styles.get(Self::LANG) {
@ -140,7 +145,7 @@ impl Prepare for RawNode {
None => Value::None, None => Value::None,
}, },
); );
this Ok(this)
} }
} }

View File

@ -66,7 +66,9 @@ pub struct SourceError {
impl SourceError { impl SourceError {
/// Create a new, bare error. /// Create a new, bare error.
#[track_caller]
pub fn new(span: Span, message: impl Into<EcoString>) -> Self { pub fn new(span: Span, message: impl Into<EcoString>) -> Self {
assert!(!span.is_detached());
Self { Self {
span, span,
pos: ErrorPos::Full, pos: ErrorPos::Full,

View File

@ -150,7 +150,7 @@ impl Args {
} }
/// Extract the positional arguments as an array. /// Extract the positional arguments as an array.
pub fn to_positional(&self) -> Array { pub fn to_pos(&self) -> Array {
self.items self.items
.iter() .iter()
.filter(|item| item.name.is_none()) .filter(|item| item.name.is_none())

View File

@ -5,7 +5,6 @@ use std::sync::Arc;
use super::{ops, Args, Func, Value, Vm}; use super::{ops, Args, Func, Value, Vm};
use crate::diag::{bail, At, SourceResult, StrResult}; use crate::diag::{bail, At, SourceResult, StrResult};
use crate::syntax::Spanned;
use crate::util::{format_eco, ArcExt, EcoString}; use crate::util::{format_eco, ArcExt, EcoString};
/// Create a new [`Array`] from values. /// Create a new [`Array`] from values.
@ -137,13 +136,13 @@ impl Array {
} }
/// Return the first matching element. /// Return the first matching element.
pub fn find(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<Option<Value>> { pub fn find(&self, vm: &Vm, func: Func) -> SourceResult<Option<Value>> {
if f.v.argc().map_or(false, |count| count != 1) { if func.argc().map_or(false, |count| count != 1) {
bail!(f.span, "function must have exactly one parameter"); bail!(func.span(), "function must have exactly one parameter");
} }
for item in self.iter() { for item in self.iter() {
let args = Args::new(f.span, [item.clone()]); let args = Args::new(func.span(), [item.clone()]);
if f.v.call(vm, args)?.cast::<bool>().at(f.span)? { if func.call(vm, args)?.cast::<bool>().at(func.span())? {
return Ok(Some(item.clone())); return Ok(Some(item.clone()));
} }
} }
@ -152,13 +151,13 @@ impl Array {
} }
/// Return the index of the first matching element. /// Return the index of the first matching element.
pub fn position(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<Option<i64>> { pub fn position(&self, vm: &Vm, func: Func) -> SourceResult<Option<i64>> {
if f.v.argc().map_or(false, |count| count != 1) { if func.argc().map_or(false, |count| count != 1) {
bail!(f.span, "function must have exactly one parameter"); bail!(func.span(), "function must have exactly one parameter");
} }
for (i, item) in self.iter().enumerate() { for (i, item) in self.iter().enumerate() {
let args = Args::new(f.span, [item.clone()]); let args = Args::new(func.span(), [item.clone()]);
if f.v.call(vm, args)?.cast::<bool>().at(f.span)? { if func.call(vm, args)?.cast::<bool>().at(func.span())? {
return Ok(Some(i as i64)); return Ok(Some(i as i64));
} }
} }
@ -168,14 +167,14 @@ impl Array {
/// Return a new array with only those elements for which the function /// Return a new array with only those elements for which the function
/// returns true. /// returns true.
pub fn filter(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<Self> { pub fn filter(&self, vm: &Vm, func: Func) -> SourceResult<Self> {
if f.v.argc().map_or(false, |count| count != 1) { if func.argc().map_or(false, |count| count != 1) {
bail!(f.span, "function must have exactly one parameter"); bail!(func.span(), "function must have exactly one parameter");
} }
let mut kept = vec![]; let mut kept = vec![];
for item in self.iter() { for item in self.iter() {
let args = Args::new(f.span, [item.clone()]); let args = Args::new(func.span(), [item.clone()]);
if f.v.call(vm, args)?.cast::<bool>().at(f.span)? { if func.call(vm, args)?.cast::<bool>().at(func.span())? {
kept.push(item.clone()) kept.push(item.clone())
} }
} }
@ -183,45 +182,45 @@ impl Array {
} }
/// Transform each item in the array with a function. /// Transform each item in the array with a function.
pub fn map(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<Self> { pub fn map(&self, vm: &Vm, func: Func) -> SourceResult<Self> {
if f.v.argc().map_or(false, |count| count < 1 || count > 2) { if func.argc().map_or(false, |count| count < 1 || count > 2) {
bail!(f.span, "function must have one or two parameters"); 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() self.iter()
.enumerate() .enumerate()
.map(|(i, item)| { .map(|(i, item)| {
let mut args = Args::new(f.span, []); let mut args = Args::new(func.span(), []);
if enumerate { 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()); args.push(func.span(), item.clone());
f.v.call(vm, args) func.call(vm, args)
}) })
.collect() .collect()
} }
/// Fold all of the array's elements into one with a function. /// Fold all of the array's elements into one with a function.
pub fn fold(&self, vm: &Vm, init: Value, f: Spanned<Func>) -> SourceResult<Value> { pub fn fold(&self, vm: &Vm, init: Value, func: Func) -> SourceResult<Value> {
if f.v.argc().map_or(false, |count| count != 2) { if func.argc().map_or(false, |count| count != 2) {
bail!(f.span, "function must have exactly two parameters"); bail!(func.span(), "function must have exactly two parameters");
} }
let mut acc = init; let mut acc = init;
for item in self.iter() { for item in self.iter() {
let args = Args::new(f.span, [acc, item.clone()]); let args = Args::new(func.span(), [acc, item.clone()]);
acc = f.v.call(vm, args)?; acc = func.call(vm, args)?;
} }
Ok(acc) Ok(acc)
} }
/// Whether any element matches. /// Whether any element matches.
pub fn any(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<bool> { pub fn any(&self, vm: &Vm, func: Func) -> SourceResult<bool> {
if f.v.argc().map_or(false, |count| count != 1) { if func.argc().map_or(false, |count| count != 1) {
bail!(f.span, "function must have exactly one parameter"); bail!(func.span(), "function must have exactly one parameter");
} }
for item in self.iter() { for item in self.iter() {
let args = Args::new(f.span, [item.clone()]); let args = Args::new(func.span(), [item.clone()]);
if f.v.call(vm, args)?.cast::<bool>().at(f.span)? { if func.call(vm, args)?.cast::<bool>().at(func.span())? {
return Ok(true); return Ok(true);
} }
} }
@ -230,13 +229,13 @@ impl Array {
} }
/// Whether all elements match. /// Whether all elements match.
pub fn all(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<bool> { pub fn all(&self, vm: &Vm, func: Func) -> SourceResult<bool> {
if f.v.argc().map_or(false, |count| count != 1) { if func.argc().map_or(false, |count| count != 1) {
bail!(f.span, "function must have exactly one parameter"); bail!(func.span(), "function must have exactly one parameter");
} }
for item in self.iter() { for item in self.iter() {
let args = Args::new(f.span, [item.clone()]); let args = Args::new(func.span(), [item.clone()]);
if !f.v.call(vm, args)?.cast::<bool>().at(f.span)? { if !func.call(vm, args)?.cast::<bool>().at(func.span())? {
return Ok(false); return Ok(false);
} }
} }

View File

@ -6,7 +6,6 @@ use std::sync::Arc;
use super::{Args, Array, Func, Str, Value, Vm}; use super::{Args, Array, Func, Str, Value, Vm};
use crate::diag::{bail, SourceResult, StrResult}; use crate::diag::{bail, SourceResult, StrResult};
use crate::syntax::is_ident; use crate::syntax::is_ident;
use crate::syntax::Spanned;
use crate::util::{format_eco, ArcExt, EcoString}; use crate::util::{format_eco, ArcExt, EcoString};
/// Create a new [`Dict`] from key-value pairs. /// Create a new [`Dict`] from key-value pairs.
@ -107,14 +106,15 @@ impl Dict {
} }
/// Transform each pair in the dictionary with a function. /// Transform each pair in the dictionary with a function.
pub fn map(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<Array> { pub fn map(&self, vm: &Vm, func: Func) -> SourceResult<Array> {
if f.v.argc().map_or(false, |count| count != 1) { if func.argc().map_or(false, |count| count != 2) {
bail!(f.span, "function must have exactly two parameters"); bail!(func.span(), "function must have exactly two parameters");
} }
self.iter() self.iter()
.map(|(key, value)| { .map(|(key, value)| {
let args = Args::new(f.span, [Value::Str(key.clone()), value.clone()]); let args =
f.v.call(vm, args) Args::new(func.span(), [Value::Str(key.clone()), value.clone()]);
func.call(vm, args)
}) })
.collect() .collect()
} }

View File

@ -563,7 +563,11 @@ impl Eval for ast::Ident {
type Output = Value; type Output = Value;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
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. // Define the closure function.
Ok(Value::Func(Func::from_closure(Closure { let closure = Closure {
location: vm.location, location: vm.location,
name, name,
captured, captured,
params, params,
sink, sink,
body: self.body(), body: self.body(),
}))) };
Ok(Value::Func(Func::from_closure(closure, self.span())))
} }
} }

View File

@ -10,13 +10,13 @@ use super::{
}; };
use crate::diag::{bail, SourceResult, StrResult}; use crate::diag::{bail, SourceResult, StrResult};
use crate::syntax::ast::{self, AstNode, Expr}; use crate::syntax::ast::{self, AstNode, Expr};
use crate::syntax::{SourceId, SyntaxNode}; use crate::syntax::{SourceId, Span, SyntaxNode};
use crate::util::EcoString; use crate::util::EcoString;
use crate::World; use crate::World;
/// An evaluatable function. /// An evaluatable function.
#[derive(Clone, Hash)] #[derive(Clone, Hash)]
pub struct Func(Arc<Repr>); pub struct Func(Arc<Repr>, Span);
/// The different kinds of function representations. /// The different kinds of function representations.
#[derive(Hash)] #[derive(Hash)]
@ -40,13 +40,17 @@ impl Func {
func: fn(&Vm, &mut Args) -> SourceResult<Value>, func: fn(&Vm, &mut Args) -> SourceResult<Value>,
info: FuncInfo, info: FuncInfo,
) -> Self { ) -> 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. /// Create a new function from a native rust node.
pub fn from_node<T: Node>(mut info: FuncInfo) -> Self { pub fn from_node<T: Node>(mut info: FuncInfo) -> Self {
info.params.extend(T::properties()); info.params.extend(T::properties());
Self(Arc::new(Repr::Native(Native { Self(
Arc::new(Repr::Native(Native {
func: |ctx, args| { func: |ctx, args| {
let styles = T::set(args, true)?; let styles = T::set(args, true)?;
let content = T::construct(ctx, args)?; let content = T::construct(ctx, args)?;
@ -55,12 +59,14 @@ impl Func {
set: Some(|args| T::set(args, false)), set: Some(|args| T::set(args, false)),
node: Some(NodeId::of::<T>()), node: Some(NodeId::of::<T>()),
info, info,
}))) })),
Span::detached(),
)
} }
/// Create a new function from a closure. /// Create a new function from a closure.
pub(super) fn from_closure(closure: Closure) -> Self { pub(super) fn from_closure(closure: Closure, span: Span) -> Self {
Self(Arc::new(Repr::Closure(closure))) Self(Arc::new(Repr::Closure(closure)), span)
} }
/// The name of the function. /// 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. /// The number of positional arguments this function takes, if known.
pub fn argc(&self) -> Option<usize> { pub fn argc(&self) -> Option<usize> {
match self.0.as_ref() { match self.0.as_ref() {
@ -121,7 +138,8 @@ impl Func {
/// Apply the given arguments to the function. /// Apply the given arguments to the function.
pub fn with(self, args: Args) -> Self { 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 /// Create a selector for this function's node type, filtering by node's

View File

@ -118,7 +118,7 @@ pub fn call(
}, },
Value::Args(args) => match method { Value::Args(args) => match method {
"positional" => Value::Array(args.to_positional()), "pos" => Value::Array(args.to_pos()),
"named" => Value::Dict(args.to_named()), "named" => Value::Dict(args.to_named()),
_ => return missing(), _ => return missing(),
}, },

View File

@ -136,7 +136,12 @@ fn try_apply(
#[capability] #[capability]
pub trait Prepare { pub trait Prepare {
/// Prepare the node for show rule application. /// 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<Content>;
} }
/// The base recipe for a node. /// The base recipe for a node.

View File

@ -33,7 +33,7 @@
#for v in (1, 2, 3, 4, 5, 6, 7) [#if v >= 2 and v <= 5 { repr(v) }] #for v in (1, 2, 3, 4, 5, 6, 7) [#if v >= 2 and v <= 5 { repr(v) }]
// Map captured arguments. // 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 f2(..args) = args.named().pairs((k, v) => repr(k) + ": " + repr(v))
#let f(..args) = (f1(..args) + f2(..args)).join(", ") #let f(..args) = (f1(..args) + f2(..args)).join(", ")
#f(1, a: 2) #f(1, a: 2)

View File

@ -57,7 +57,7 @@
// Test that the expression is evaluated to the end. // Test that the expression is evaluated to the end.
#let sum(..args) = { #let sum(..args) = {
let s = 0 let s = 0
for v in args.positional() { for v in args.pos() {
s += v s += v
} }
s s