mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Numbering functions
This commit is contained in:
parent
f70cea508c
commit
a6d90c1bf1
@ -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")])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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())
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
/// }
|
/// }
|
||||||
|
@ -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 {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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())
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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(),
|
||||||
},
|
},
|
||||||
|
@ -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.
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user