summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--library/src/basics/enum.rs51
-rw-r--r--library/src/basics/heading.rs32
-rw-r--r--library/src/basics/table.rs23
-rw-r--r--library/src/compute/data.rs15
-rw-r--r--library/src/compute/utility.rs101
-rw-r--r--library/src/layout/mod.rs3
-rw-r--r--library/src/layout/page.rs70
-rw-r--r--library/src/meta/outline.rs38
-rw-r--r--library/src/text/raw.rs9
-rw-r--r--src/diag.rs2
-rw-r--r--src/model/args.rs2
-rw-r--r--src/model/array.rs77
-rw-r--r--src/model/dict.rs12
-rw-r--r--src/model/eval.rs14
-rw-r--r--src/model/func.rs50
-rw-r--r--src/model/methods.rs2
-rw-r--r--src/model/realize.rs7
-rw-r--r--tests/typ/compiler/for.typ2
-rw-r--r--tests/typ/compiler/return.typ2
19 files changed, 268 insertions, 244 deletions
diff --git a/library/src/basics/enum.rs b/library/src/basics/enum.rs
index f1784a7e..c5d4d8ae 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<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")])
- }
-}
diff --git a/library/src/basics/heading.rs b/library/src/basics/heading.rs
index 925d23a2..c2696dd6 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<NumberingPattern> = None;
+ pub const NUMBERING: Option<Numbering> = 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<Content> {
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::<Self>().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<Content> {
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 01b16898..20881830 100644
--- a/library/src/basics/table.rs
+++ b/library/src/basics/table.rs
@@ -199,7 +199,7 @@ pub enum Celled<T> {
/// 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<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> {
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<T: Cast> Cast<Spanned<Value>> for Celled<T> {
- fn is(value: &Spanned<Value>) -> bool {
- matches!(&value.v, Value::Func(_)) || T::is(&value.v)
+impl<T: Cast> Cast for Celled<T> {
+ fn is(value: &Value) -> bool {
+ matches!(value, Value::Func(_)) || T::is(value)
}
- fn cast(value: Spanned<Value>) -> StrResult<Self> {
- match value.v {
- Value::Func(v) => Ok(Self::Func(v, value.span)),
+ fn cast(value: Value) -> StrResult<Self> {
+ 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 => <Self as Cast>::error(v),
}
}
diff --git a/library/src/compute/data.rs b/library/src/compute/data.rs
index bcf64360..e3013389 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 78cf3953..414a62f5 100644
--- a/library/src/compute/utility.rs
+++ b/library/src/compute/utility.rs
@@ -35,53 +35,105 @@ pub fn lorem(args: &mut Args) -> SourceResult<Value> {
}
/// # 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<Value> {
- let pattern = args.expect::<NumberingPattern>("pattern")?;
+pub fn numbering(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
+ let numbering = args.expect::<Numbering>("pattern or function")?;
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.
@@ -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 91822be8..330db716 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::<dyn Prepare>() {
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 f4f9620c..4f5231de 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<Marginal> = 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<Marginal> = 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<Marginal> = 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<Marginal> = None;
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
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<Option<Content>> {
+ pub fn resolve(&self, vt: &Vt, page: usize) -> SourceResult<Content> {
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<Spanned<Value>> for Marginal {
- fn is(value: &Spanned<Value>) -> bool {
- matches!(
- &value.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"),
- ])
- }
+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 c3d17196..4b70666b 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<Content> {
let headings = vt
.locate(Selector::node::<HeadingNode>())
.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::<EcoString>();
-
- if !text.is_empty() {
- seq.push(HideNode(TextNode::packed(text)).pack());
+ .map(|node| node.field("numbers").unwrap())
+ .filter(|numbers| *numbers != Value::None)
+ .map(|numbers| numbers.display() + SpaceNode.pack())
+ .collect();
+
+ 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 c6026f89..2f0ec79e 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<Content> {
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 55f16b5f..e0015fcc 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<EcoString>) -> Self {
+ assert!(!span.is_detached());
Self {
span,
pos: ErrorPos::Full,
diff --git a/src/model/args.rs b/src/model/args.rs
index 4aaaded4..9ab07ed8 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 fb740a13..28b9d1a0 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<Func>) -> SourceResult<Option<Value>> {
- 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<Option<Value>> {
+ 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::<bool>().at(f.span)? {
+ let args = Args::new(func.span(), [item.clone()]);
+ if func.call(vm, args)?.cast::<bool>().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<Func>) -> SourceResult<Option<i64>> {
- 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<Option<i64>> {
+ 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::<bool>().at(f.span)? {
+ let args = Args::new(func.span(), [item.clone()]);
+ if func.call(vm, args)?.cast::<bool>().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<Func>) -> SourceResult<Self> {
- 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<Self> {
+ 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::<bool>().at(f.span)? {
+ let args = Args::new(func.span(), [item.clone()]);
+ if func.call(vm, args)?.cast::<bool>().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<Func>) -> SourceResult<Self> {
- 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<Self> {
+ 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<Func>) -> SourceResult<Value> {
- 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<Value> {
+ 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<Func>) -> SourceResult<bool> {
- 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<bool> {
+ 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::<bool>().at(f.span)? {
+ let args = Args::new(func.span(), [item.clone()]);
+ if func.call(vm, args)?.cast::<bool>().at(func.span())? {
return Ok(true);
}
}
@@ -230,13 +229,13 @@ impl Array {
}
/// Whether all elements match.
- pub fn all(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<bool> {
- 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<bool> {
+ 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::<bool>().at(f.span)? {
+ let args = Args::new(func.span(), [item.clone()]);
+ if !func.call(vm, args)?.cast::<bool>().at(func.span())? {
return Ok(false);
}
}
diff --git a/src/model/dict.rs b/src/model/dict.rs
index 83c16824..a5dbeeae 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<Func>) -> SourceResult<Array> {
- 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<Array> {
+ 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 ab89f9c2..e9134114 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<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.
- 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 3fb8f4d4..878b717f 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<Repr>);
+pub struct Func(Arc<Repr>, Span);
/// The different kinds of function representations.
#[derive(Hash)]
@@ -40,27 +40,33 @@ impl Func {
func: fn(&Vm, &mut Args) -> SourceResult<Value>,
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<T: 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::<T>()),
- 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::<T>()),
+ 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<usize> {
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 8155685c..3823df41 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 862bc8b6..39c1fd42 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<Content>;
}
/// The base recipe for a node.
diff --git a/tests/typ/compiler/for.typ b/tests/typ/compiler/for.typ
index 822f7423..f63b870e 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 0eea394e..779a56dd 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