New #[func] macro

This commit is contained in:
Laurenz 2023-03-10 12:55:21 +01:00
parent c38d72383d
commit 62f35602a8
58 changed files with 1106 additions and 1177 deletions

View File

@ -10,28 +10,28 @@ use crate::prelude::*;
/// A module with computational functions. /// A module with computational functions.
pub fn module() -> Module { pub fn module() -> Module {
let mut scope = Scope::new(); let mut scope = Scope::new();
scope.def_func::<AbsFunc>("abs"); scope.define("abs", abs);
scope.def_func::<PowFunc>("pow"); scope.define("pow", pow);
scope.def_func::<SqrtFunc>("sqrt"); scope.define("sqrt", sqrt);
scope.def_func::<SinFunc>("sin"); scope.define("sin", sin);
scope.def_func::<CosFunc>("cos"); scope.define("cos", cos);
scope.def_func::<TanFunc>("tan"); scope.define("tan", tan);
scope.def_func::<AsinFunc>("asin"); scope.define("asin", asin);
scope.def_func::<AcosFunc>("acos"); scope.define("acos", acos);
scope.def_func::<AtanFunc>("atan"); scope.define("atan", atan);
scope.def_func::<SinhFunc>("sinh"); scope.define("sinh", sinh);
scope.def_func::<CoshFunc>("cosh"); scope.define("cosh", cosh);
scope.def_func::<TanhFunc>("tanh"); scope.define("tanh", tanh);
scope.def_func::<LogFunc>("log"); scope.define("log", log);
scope.def_func::<FloorFunc>("floor"); scope.define("floor", floor);
scope.def_func::<CeilFunc>("ceil"); scope.define("ceil", ceil);
scope.def_func::<RoundFunc>("round"); scope.define("round", round);
scope.def_func::<ClampFunc>("clamp"); scope.define("clamp", clamp);
scope.def_func::<MinFunc>("min"); scope.define("min", min);
scope.def_func::<MaxFunc>("max"); scope.define("max", max);
scope.def_func::<EvenFunc>("even"); scope.define("even", even);
scope.def_func::<OddFunc>("odd"); scope.define("odd", odd);
scope.def_func::<ModFunc>("mod"); scope.define("mod", mod_);
scope.define("inf", Value::Float(f64::INFINITY)); scope.define("inf", Value::Float(f64::INFINITY));
scope.define("nan", Value::Float(f64::NAN)); scope.define("nan", Value::Float(f64::NAN));
scope.define("pi", Value::Float(std::f64::consts::PI)); scope.define("pi", Value::Float(std::f64::consts::PI));
@ -48,15 +48,15 @@ pub fn module() -> Module {
/// #calc.abs(2fr) /// #calc.abs(2fr)
/// ``` /// ```
/// ///
/// ## Parameters
/// - value: `ToAbs` (positional, required)
/// The value whose absolute value to calculate.
///
/// Display: Absolute /// Display: Absolute
/// Category: calculate /// Category: calculate
/// Returns: any
#[func] #[func]
pub fn abs(args: &mut Args) -> SourceResult<Value> { pub fn abs(
Ok(args.expect::<ToAbs>("value")?.0) /// The value whose absolute value to calculate.
value: ToAbs,
) -> Value {
value.0
} }
/// A value of which the absolute value can be taken. /// A value of which the absolute value can be taken.
@ -80,27 +80,27 @@ cast_from_value! {
/// #calc.pow(2, 3) /// #calc.pow(2, 3)
/// ``` /// ```
/// ///
/// ## Parameters
/// - base: `Num` (positional, required)
/// The base of the power.
/// - exponent: `Num` (positional, required)
/// The exponent of the power. Must be non-negative.
///
/// Display: Power /// Display: Power
/// Category: calculate /// Category: calculate
/// Returns: integer or float
#[func] #[func]
pub fn pow(args: &mut Args) -> SourceResult<Value> { pub fn pow(
let base = args.expect::<Num>("base")?; /// The base of the power.
let exponent = args base: Num,
.expect::<Spanned<Num>>("exponent") /// The exponent of the power. Must be non-negative.
.and_then(|n| match n.v { exponent: Spanned<Num>,
Num::Int(i) if i > u32::MAX as i64 => bail!(n.span, "exponent too large"), ) -> Value {
Num::Int(i) if i >= 0 => Ok(n), let exponent = match exponent.v {
Num::Float(f) if f >= 0.0 => Ok(n), Num::Int(i) if i > u32::MAX as i64 => {
_ => bail!(n.span, "exponent must be non-negative"), bail!(exponent.span, "exponent too large");
})? }
.v; Num::Int(0..) => exponent.v,
Ok(base.apply2(exponent, |a, b| a.pow(b as u32), f64::powf)) Num::Float(f) if f >= 0.0 => exponent.v,
_ => {
bail!(exponent.span, "exponent must be non-negative");
}
};
base.apply2(exponent, |a, b| a.pow(b as u32), f64::powf)
} }
/// Calculate the square root of a number. /// Calculate the square root of a number.
@ -111,19 +111,18 @@ pub fn pow(args: &mut Args) -> SourceResult<Value> {
/// #calc.sqrt(2.5) /// #calc.sqrt(2.5)
/// ``` /// ```
/// ///
/// ## Parameters
/// - value: `Num` (positional, required)
/// The number whose square root to calculate. Must be non-negative.
///
/// Display: Square Root /// Display: Square Root
/// Category: calculate /// Category: calculate
/// Returns: float
#[func] #[func]
pub fn sqrt(args: &mut Args) -> SourceResult<Value> { pub fn sqrt(
let value = args.expect::<Spanned<Num>>("value")?; /// The number whose square root to calculate. Must be non-negative.
value: Spanned<Num>,
) -> Value {
if value.v.float() < 0.0 { if value.v.float() < 0.0 {
bail!(value.span, "cannot take square root of negative number"); bail!(value.span, "cannot take square root of negative number");
} }
Ok(Value::Float(value.v.float().sqrt())) Value::Float(value.v.float().sqrt())
} }
/// Calculate the sine of an angle. /// Calculate the sine of an angle.
@ -138,20 +137,19 @@ pub fn sqrt(args: &mut Args) -> SourceResult<Value> {
/// #calc.sin(90deg) /// #calc.sin(90deg)
/// ``` /// ```
/// ///
/// ## Parameters
/// - angle: `AngleLike` (positional, required)
/// The angle whose sine to calculate.
///
/// Display: Sine /// Display: Sine
/// Category: calculate /// Category: calculate
/// Returns: float
#[func] #[func]
pub fn sin(args: &mut Args) -> SourceResult<Value> { pub fn sin(
let arg = args.expect::<AngleLike>("angle")?; /// The angle whose sine to calculate.
Ok(Value::Float(match arg { angle: AngleLike,
) -> Value {
Value::Float(match angle {
AngleLike::Angle(a) => a.sin(), AngleLike::Angle(a) => a.sin(),
AngleLike::Int(n) => (n as f64).sin(), AngleLike::Int(n) => (n as f64).sin(),
AngleLike::Float(n) => n.sin(), AngleLike::Float(n) => n.sin(),
})) })
} }
/// Calculate the cosine of an angle. /// Calculate the cosine of an angle.
@ -161,25 +159,24 @@ pub fn sin(args: &mut Args) -> SourceResult<Value> {
/// ///
/// ## Example /// ## Example
/// ```example /// ```example
/// #calc.cos(90deg) /// #calc.cos(90deg) \
/// #calc.cos(1.5) \ /// #calc.cos(1.5) \
/// #calc.cos(90deg) /// #calc.cos(90deg)
/// ``` /// ```
/// ///
/// ## Parameters
/// - angle: `AngleLike` (positional, required)
/// The angle whose cosine to calculate.
///
/// Display: Cosine /// Display: Cosine
/// Category: calculate /// Category: calculate
/// Returns: float
#[func] #[func]
pub fn cos(args: &mut Args) -> SourceResult<Value> { pub fn cos(
let arg = args.expect::<AngleLike>("angle")?; /// The angle whose cosine to calculate.
Ok(Value::Float(match arg { angle: AngleLike,
) -> Value {
Value::Float(match angle {
AngleLike::Angle(a) => a.cos(), AngleLike::Angle(a) => a.cos(),
AngleLike::Int(n) => (n as f64).cos(), AngleLike::Int(n) => (n as f64).cos(),
AngleLike::Float(n) => n.cos(), AngleLike::Float(n) => n.cos(),
})) })
} }
/// Calculate the tangent of an angle. /// Calculate the tangent of an angle.
@ -193,20 +190,19 @@ pub fn cos(args: &mut Args) -> SourceResult<Value> {
/// #calc.tan(90deg) /// #calc.tan(90deg)
/// ``` /// ```
/// ///
/// ## Parameters
/// - angle: `AngleLike` (positional, required)
/// The angle whose tangent to calculate.
///
/// Display: Tangent /// Display: Tangent
/// Category: calculate /// Category: calculate
/// Returns: float
#[func] #[func]
pub fn tan(args: &mut Args) -> SourceResult<Value> { pub fn tan(
let arg = args.expect::<AngleLike>("angle")?; /// The angle whose tangent to calculate.
Ok(Value::Float(match arg { angle: AngleLike,
) -> Value {
Value::Float(match angle {
AngleLike::Angle(a) => a.tan(), AngleLike::Angle(a) => a.tan(),
AngleLike::Int(n) => (n as f64).tan(), AngleLike::Int(n) => (n as f64).tan(),
AngleLike::Float(n) => n.tan(), AngleLike::Float(n) => n.tan(),
})) })
} }
/// Calculate the arcsine of a number. /// Calculate the arcsine of a number.
@ -217,20 +213,19 @@ pub fn tan(args: &mut Args) -> SourceResult<Value> {
/// #calc.asin(1) /// #calc.asin(1)
/// ``` /// ```
/// ///
/// ## Parameters
/// - value: `Num` (positional, required)
/// The number whose arcsine to calculate. Must be between -1 and 1.
///
/// Display: Arcsine /// Display: Arcsine
/// Category: calculate /// Category: calculate
/// Returns: angle
#[func] #[func]
pub fn asin(args: &mut Args) -> SourceResult<Value> { pub fn asin(
let Spanned { v, span } = args.expect::<Spanned<Num>>("value")?; /// The number whose arcsine to calculate. Must be between -1 and 1.
let val = v.float(); value: Spanned<Num>,
) -> Value {
let val = value.v.float();
if val < -1.0 || val > 1.0 { if val < -1.0 || val > 1.0 {
bail!(span, "arcsin must be between -1 and 1"); bail!(value.span, "arcsin must be between -1 and 1");
} }
Ok(Value::Angle(Angle::rad(val.asin()))) Value::Angle(Angle::rad(val.asin()))
} }
/// Calculate the arccosine of a number. /// Calculate the arccosine of a number.
@ -241,20 +236,19 @@ pub fn asin(args: &mut Args) -> SourceResult<Value> {
/// #calc.acos(1) /// #calc.acos(1)
/// ``` /// ```
/// ///
/// ## Parameters
/// - value: `Num` (positional, required)
/// The number whose arccosine to calculate. Must be between -1 and 1.
///
/// Display: Arccosine /// Display: Arccosine
/// Category: calculate /// Category: calculate
/// Returns: angle
#[func] #[func]
pub fn acos(args: &mut Args) -> SourceResult<Value> { pub fn acos(
let Spanned { v, span } = args.expect::<Spanned<Num>>("value")?; /// The number whose arcsine to calculate. Must be between -1 and 1.
let val = v.float(); value: Spanned<Num>,
) -> Value {
let val = value.v.float();
if val < -1.0 || val > 1.0 { if val < -1.0 || val > 1.0 {
bail!(span, "arccos must be between -1 and 1"); bail!(value.span, "arccos must be between -1 and 1");
} }
Ok(Value::Angle(Angle::rad(val.acos()))) Value::Angle(Angle::rad(val.acos()))
} }
/// Calculate the arctangent of a number. /// Calculate the arctangent of a number.
@ -265,16 +259,15 @@ pub fn acos(args: &mut Args) -> SourceResult<Value> {
/// #calc.atan(1) /// #calc.atan(1)
/// ``` /// ```
/// ///
/// ## Parameters
/// - value: `Num` (positional, required)
/// The number whose arctangent to calculate.
///
/// Display: Arctangent /// Display: Arctangent
/// Category: calculate /// Category: calculate
/// Returns: angle
#[func] #[func]
pub fn atan(args: &mut Args) -> SourceResult<Value> { pub fn atan(
let value = args.expect::<Num>("value")?; /// The number whose arctangent to calculate.
Ok(Value::Angle(Angle::rad(value.float().atan()))) value: Num,
) -> Value {
Value::Angle(Angle::rad(value.float().atan()))
} }
/// Calculate the hyperbolic sine of an angle. /// Calculate the hyperbolic sine of an angle.
@ -287,20 +280,19 @@ pub fn atan(args: &mut Args) -> SourceResult<Value> {
/// #calc.sinh(45deg) /// #calc.sinh(45deg)
/// ``` /// ```
/// ///
/// ## Parameters
/// - angle: `AngleLike` (positional, required)
/// The angle whose hyperbolic sine to calculate.
///
/// Display: Hyperbolic sine /// Display: Hyperbolic sine
/// Category: calculate /// Category: calculate
/// Returns: float
#[func] #[func]
pub fn sinh(args: &mut Args) -> SourceResult<Value> { pub fn sinh(
let arg = args.expect::<AngleLike>("angle")?; /// The angle whose hyperbolic sine to calculate.
Ok(Value::Float(match arg { angle: AngleLike,
) -> Value {
Value::Float(match angle {
AngleLike::Angle(a) => a.to_rad().sinh(), AngleLike::Angle(a) => a.to_rad().sinh(),
AngleLike::Int(n) => (n as f64).sinh(), AngleLike::Int(n) => (n as f64).sinh(),
AngleLike::Float(n) => n.sinh(), AngleLike::Float(n) => n.sinh(),
})) })
} }
/// Calculate the hyperbolic cosine of an angle. /// Calculate the hyperbolic cosine of an angle.
@ -313,20 +305,19 @@ pub fn sinh(args: &mut Args) -> SourceResult<Value> {
/// #calc.cosh(45deg) /// #calc.cosh(45deg)
/// ``` /// ```
/// ///
/// ## Parameters
/// - angle: `AngleLike` (positional, required)
/// The angle whose hyperbolic cosine to calculate.
///
/// Display: Hyperbolic cosine /// Display: Hyperbolic cosine
/// Category: calculate /// Category: calculate
/// Returns: float
#[func] #[func]
pub fn cosh(args: &mut Args) -> SourceResult<Value> { pub fn cosh(
let arg = args.expect::<AngleLike>("angle")?; /// The angle whose hyperbolic cosine to calculate.
Ok(Value::Float(match arg { angle: AngleLike,
) -> Value {
Value::Float(match angle {
AngleLike::Angle(a) => a.to_rad().cosh(), AngleLike::Angle(a) => a.to_rad().cosh(),
AngleLike::Int(n) => (n as f64).cosh(), AngleLike::Int(n) => (n as f64).cosh(),
AngleLike::Float(n) => n.cosh(), AngleLike::Float(n) => n.cosh(),
})) })
} }
/// Calculate the hyperbolic tangent of an angle. /// Calculate the hyperbolic tangent of an angle.
@ -339,20 +330,19 @@ pub fn cosh(args: &mut Args) -> SourceResult<Value> {
/// #calc.tanh(45deg) /// #calc.tanh(45deg)
/// ``` /// ```
/// ///
/// ## Parameters
/// - angle: `AngleLike` (positional, required)
/// The angle whose hyperbolic tangent to calculate.
///
/// Display: Hyperbolic tangent /// Display: Hyperbolic tangent
/// Category: calculate /// Category: calculate
/// Returns: float
#[func] #[func]
pub fn tanh(args: &mut Args) -> SourceResult<Value> { pub fn tanh(
let arg = args.expect::<AngleLike>("angle")?; /// The angle whose hyperbolic tangent to calculate.
Ok(Value::Float(match arg { angle: AngleLike,
) -> Value {
Value::Float(match angle {
AngleLike::Angle(a) => a.to_rad().tanh(), AngleLike::Angle(a) => a.to_rad().tanh(),
AngleLike::Int(n) => (n as f64).tanh(), AngleLike::Int(n) => (n as f64).tanh(),
AngleLike::Float(n) => n.tanh(), AngleLike::Float(n) => n.tanh(),
})) })
} }
/// Calculate the logarithm of a number. /// Calculate the logarithm of a number.
@ -361,22 +351,22 @@ pub fn tanh(args: &mut Args) -> SourceResult<Value> {
/// ///
/// ## Example /// ## Example
/// ```example /// ```example
/// #calc.log(100) \ /// #calc.log(100)
/// ``` /// ```
/// ///
/// ## Parameters
/// - value: `Num` (positional, required)
/// The number whose logarithm to calculate.
/// - base: `Num` (named)
/// The base of the logarithm.
///
/// Display: Logarithm /// Display: Logarithm
/// Category: calculate /// Category: calculate
/// Returns: float
#[func] #[func]
pub fn log(args: &mut Args) -> SourceResult<Value> { pub fn log(
let value = args.expect::<f64>("value")?; /// The number whose logarithm to calculate.
let base = args.named::<f64>("base")?.unwrap_or_else(|| 10.0); value: f64,
Ok(Value::Float(value.log(base))) /// The base of the logarithm.
#[named]
#[default(10.0)]
base: f64,
) -> Value {
Value::Float(value.log(base))
} }
/// Round a number down to the nearest integer. /// Round a number down to the nearest integer.
@ -390,19 +380,18 @@ pub fn log(args: &mut Args) -> SourceResult<Value> {
/// #calc.floor(500.1) /// #calc.floor(500.1)
/// ``` /// ```
/// ///
/// ## Parameters
/// - value: `Num` (positional, required)
/// The number to round down.
///
/// Display: Round down /// Display: Round down
/// Category: calculate /// Category: calculate
/// Returns: integer
#[func] #[func]
pub fn floor(args: &mut Args) -> SourceResult<Value> { pub fn floor(
let value = args.expect::<Num>("value")?; /// The number to round down.
Ok(match value { value: Num,
) -> Value {
match value {
Num::Int(n) => Value::Int(n), Num::Int(n) => Value::Int(n),
Num::Float(n) => Value::Int(n.floor() as i64), Num::Float(n) => Value::Int(n.floor() as i64),
}) }
} }
/// Round a number up to the nearest integer. /// Round a number up to the nearest integer.
@ -416,19 +405,18 @@ pub fn floor(args: &mut Args) -> SourceResult<Value> {
/// #calc.ceil(500.1) /// #calc.ceil(500.1)
/// ``` /// ```
/// ///
/// ## Parameters
/// - value: `Num` (positional, required)
/// The number to round up.
///
/// Display: Round up /// Display: Round up
/// Category: calculate /// Category: calculate
/// Returns: integer
#[func] #[func]
pub fn ceil(args: &mut Args) -> SourceResult<Value> { pub fn ceil(
let value = args.expect::<Num>("value")?; /// The number to round up.
Ok(match value { value: Num,
) -> Value {
match value {
Num::Int(n) => Value::Int(n), Num::Int(n) => Value::Int(n),
Num::Float(n) => Value::Int(n.ceil() as i64), Num::Float(n) => Value::Int(n.ceil() as i64),
}) }
} }
/// Round a number to the nearest integer. /// Round a number to the nearest integer.
@ -442,25 +430,26 @@ pub fn ceil(args: &mut Args) -> SourceResult<Value> {
/// #calc.round(3.1415, digits: 2) /// #calc.round(3.1415, digits: 2)
/// ``` /// ```
/// ///
/// ## Parameters
/// - value: `Num` (positional, required)
/// The number to round.
/// - digits: `i64` (named)
///
/// Display: Round /// Display: Round
/// Category: calculate /// Category: calculate
/// Returns: integer or float
#[func] #[func]
pub fn round(args: &mut Args) -> SourceResult<Value> { pub fn round(
let value = args.expect::<Num>("value")?; /// The number to round.
let digits = args.named::<u32>("digits")?.unwrap_or(0); value: Num,
Ok(match value { /// The number of decimal places.
#[named]
#[default(0)]
digits: i64,
) -> Value {
match value {
Num::Int(n) if digits == 0 => Value::Int(n), Num::Int(n) if digits == 0 => Value::Int(n),
_ => { _ => {
let n = value.float(); let n = value.float();
let factor = 10.0_f64.powi(digits as i32) as f64; let factor = 10.0_f64.powi(digits as i32) as f64;
Value::Float((n * factor).round() / factor) Value::Float((n * factor).round() / factor)
} }
}) }
} }
/// Clamp a number between a minimum and maximum value. /// Clamp a number between a minimum and maximum value.
@ -472,25 +461,22 @@ pub fn round(args: &mut Args) -> SourceResult<Value> {
/// #calc.clamp(5, 0, 4) /// #calc.clamp(5, 0, 4)
/// ``` /// ```
/// ///
/// ## Parameters
/// - value: `Num` (positional, required)
/// The number to clamp.
/// - min: `Num` (positional, required)
/// The inclusive minimum value.
/// - max: `Num` (positional, required)
/// The inclusive maximum value.
///
/// Display: Clamp /// Display: Clamp
/// Category: calculate /// Category: calculate
/// Returns: integer or float
#[func] #[func]
pub fn clamp(args: &mut Args) -> SourceResult<Value> { pub fn clamp(
let value = args.expect::<Num>("value")?; /// The number to clamp.
let min = args.expect::<Num>("min")?; value: Num,
let max = args.expect::<Spanned<Num>>("max")?; /// The inclusive minimum value.
min: Num,
/// The inclusive maximum value.
max: Spanned<Num>,
) -> Value {
if max.v.float() < min.float() { if max.v.float() < min.float() {
bail!(max.span, "max must be greater than or equal to min") bail!(max.span, "max must be greater than or equal to min")
} }
Ok(value.apply3(min, max.v, i64::clamp, f64::clamp)) value.apply3(min, max.v, i64::clamp, f64::clamp)
} }
/// Determine the minimum of a sequence of values. /// Determine the minimum of a sequence of values.
@ -501,18 +487,17 @@ pub fn clamp(args: &mut Args) -> SourceResult<Value> {
/// #calc.min("typst", "in", "beta") /// #calc.min("typst", "in", "beta")
/// ``` /// ```
/// ///
/// ## Parameters
/// - values: `Value` (positional, variadic)
/// The sequence of values from which to extract the minimum.
/// Must not be empty.
///
/// - returns: any
///
/// Display: Minimum /// Display: Minimum
/// Category: calculate /// Category: calculate
/// Returns: any
#[func] #[func]
pub fn min(args: &mut Args) -> SourceResult<Value> { pub fn min(
minmax(args, Ordering::Less) /// The sequence of values from which to extract the minimum.
/// Must not be empty.
#[variadic]
values: Vec<Spanned<Value>>,
) -> Value {
minmax(args.span, values, Ordering::Less)?
} }
/// Determine the maximum of a sequence of values. /// Determine the maximum of a sequence of values.
@ -523,24 +508,31 @@ pub fn min(args: &mut Args) -> SourceResult<Value> {
/// #calc.max("typst", "in", "beta") /// #calc.max("typst", "in", "beta")
/// ``` /// ```
/// ///
/// ## Parameters
/// - values: `Value` (positional, variadic)
/// The sequence of values from which to extract the maximum.
/// Must not be empty.
///
/// - returns: any
///
/// Display: Maximum /// Display: Maximum
/// Category: calculate /// Category: calculate
/// Returns: any
#[func] #[func]
pub fn max(args: &mut Args) -> SourceResult<Value> { pub fn max(
minmax(args, Ordering::Greater) /// The sequence of values from which to extract the maximum.
/// Must not be empty.
#[variadic]
values: Vec<Spanned<Value>>,
) -> Value {
minmax(args.span, values, Ordering::Greater)?
} }
/// Find the minimum or maximum of a sequence of values. /// Find the minimum or maximum of a sequence of values.
fn minmax(args: &mut Args, goal: Ordering) -> SourceResult<Value> { fn minmax(
let mut extremum = args.expect::<Value>("value")?; span: Span,
for Spanned { v, span } in args.all::<Spanned<Value>>()? { values: Vec<Spanned<Value>>,
goal: Ordering,
) -> SourceResult<Value> {
let mut iter = values.into_iter();
let Some(Spanned { v: mut extremum, ..}) = iter.next() else {
bail!(span, "expected at least one value");
};
for Spanned { v, span } in iter {
match v.partial_cmp(&extremum) { match v.partial_cmp(&extremum) {
Some(ordering) => { Some(ordering) => {
if ordering == goal { if ordering == goal {
@ -555,6 +547,7 @@ fn minmax(args: &mut Args, goal: Ordering) -> SourceResult<Value> {
), ),
} }
} }
Ok(extremum) Ok(extremum)
} }
@ -567,17 +560,15 @@ fn minmax(args: &mut Args, goal: Ordering) -> SourceResult<Value> {
/// #range(10).filter(calc.even) /// #range(10).filter(calc.even)
/// ``` /// ```
/// ///
/// ## Parameters
/// - value: `i64` (positional, required)
/// The number to check for evenness.
///
/// - returns: boolean
///
/// Display: Even /// Display: Even
/// Category: calculate /// Category: calculate
/// Returns: boolean
#[func] #[func]
pub fn even(args: &mut Args) -> SourceResult<Value> { pub fn even(
Ok(Value::Bool(args.expect::<i64>("value")? % 2 == 0)) /// The number to check for evenness.
value: i64,
) -> Value {
Value::Bool(value % 2 == 0)
} }
/// Determine whether an integer is odd. /// Determine whether an integer is odd.
@ -589,18 +580,15 @@ pub fn even(args: &mut Args) -> SourceResult<Value> {
/// #range(10).filter(calc.odd) /// #range(10).filter(calc.odd)
/// ``` /// ```
/// ///
///
/// ## Parameters
/// - value: `i64` (positional, required)
/// The number to check for oddness.
///
/// - returns: boolean
///
/// Display: Odd /// Display: Odd
/// Category: calculate /// Category: calculate
/// Returns: boolean
#[func] #[func]
pub fn odd(args: &mut Args) -> SourceResult<Value> { pub fn odd(
Ok(Value::Bool(args.expect::<i64>("value")? % 2 != 0)) /// The number to check for oddness.
value: i64,
) -> Value {
Value::Bool(value % 2 != 0)
} }
/// Calculate the modulus of two numbers. /// Calculate the modulus of two numbers.
@ -611,25 +599,20 @@ pub fn odd(args: &mut Args) -> SourceResult<Value> {
/// #calc.mod(1.75, 0.5) /// #calc.mod(1.75, 0.5)
/// ``` /// ```
/// ///
/// ## Parameters
/// - dividend: `Num` (positional, required)
/// The dividend of the modulus.
///
/// - divisor: `Num` (positional, required)
/// The divisor of the modulus.
///
/// - returns: integer or float
///
/// Display: Modulus /// Display: Modulus
/// Category: calculate /// Category: calculate
/// Returns: integer or float
#[func] #[func]
pub fn mod_(args: &mut Args) -> SourceResult<Value> { pub fn mod_(
let dividend = args.expect::<Num>("dividend")?; /// The dividend of the modulus.
let Spanned { v: divisor, span } = args.expect::<Spanned<Num>>("divisor")?; dividend: Num,
if divisor.float() == 0.0 { /// The divisor of the modulus.
bail!(span, "divisor must not be zero"); divisor: Spanned<Num>,
) -> Value {
if divisor.v.float() == 0.0 {
bail!(divisor.span, "divisor must not be zero");
} }
Ok(dividend.apply2(divisor, Rem::rem, Rem::rem)) dividend.apply2(divisor.v, Rem::rem, Rem::rem)
} }
/// A value which can be passed to functions that work with integers and floats. /// A value which can be passed to functions that work with integers and floats.

View File

@ -1,3 +1,4 @@
use std::num::NonZeroI64;
use std::str::FromStr; use std::str::FromStr;
use ecow::EcoVec; use ecow::EcoVec;
@ -19,17 +20,15 @@ use crate::prelude::*;
/// #{ int("27") + int("4") } /// #{ int("27") + int("4") }
/// ``` /// ```
/// ///
/// ## Parameters
/// - value: `ToInt` (positional, required)
/// The value that should be converted to an integer.
///
/// - returns: integer
///
/// Display: Integer /// Display: Integer
/// Category: construct /// Category: construct
/// Returns: integer
#[func] #[func]
pub fn int(args: &mut Args) -> SourceResult<Value> { pub fn int(
Ok(Value::Int(args.expect::<ToInt>("value")?.0)) /// The value that should be converted to an integer.
value: ToInt,
) -> Value {
Value::Int(value.0)
} }
/// A value that can be cast to an integer. /// A value that can be cast to an integer.
@ -59,17 +58,15 @@ cast_from_value! {
/// #float("1e5") /// #float("1e5")
/// ``` /// ```
/// ///
/// ## Parameters
/// - value: `ToFloat` (positional, required)
/// The value that should be converted to a float.
///
/// - returns: float
///
/// Display: Float /// Display: Float
/// Category: construct /// Category: construct
/// Returns: float
#[func] #[func]
pub fn float(args: &mut Args) -> SourceResult<Value> { pub fn float(
Ok(Value::Float(args.expect::<ToFloat>("value")?.0)) /// The value that should be converted to a float.
value: ToFloat,
) -> Value {
Value::Float(value.0)
} }
/// A value that can be cast to a float. /// A value that can be cast to a float.
@ -92,18 +89,15 @@ cast_from_value! {
/// } /// }
/// ``` /// ```
/// ///
/// ## Parameters
/// - gray: `Component` (positional, required)
/// The gray component.
///
/// - returns: color
///
/// Display: Luma /// Display: Luma
/// Category: construct /// Category: construct
/// Returns: color
#[func] #[func]
pub fn luma(args: &mut Args) -> SourceResult<Value> { pub fn luma(
let Component(luma) = args.expect("gray component")?; /// The gray component.
Ok(Value::Color(LumaColor::new(luma).into())) gray: Component,
) -> Value {
Value::Color(LumaColor::new(gray.0).into())
} }
/// Create an RGB(A) color. /// Create an RGB(A) color.
@ -121,8 +115,11 @@ pub fn luma(args: &mut Args) -> SourceResult<Value> {
/// #square(fill: rgb(25%, 13%, 65%)) /// #square(fill: rgb(25%, 13%, 65%))
/// ``` /// ```
/// ///
/// ## Parameters /// Display: RGB
/// - hex: `EcoString` (positional) /// Category: construct
/// Returns: color
#[func]
pub fn rgb(
/// The color in hexadecimal notation. /// The color in hexadecimal notation.
/// ///
/// Accepts three, four, six or eight hexadecimal digits and optionally /// Accepts three, four, six or eight hexadecimal digits and optionally
@ -135,26 +132,27 @@ pub fn luma(args: &mut Args) -> SourceResult<Value> {
/// *Typst* /// *Typst*
/// ] /// ]
/// ``` /// ```
/// #[external]
/// - red: `Component` (positional) #[default]
hex: EcoString,
/// The red component. /// The red component.
/// #[external]
/// - green: `Component` (positional) #[default]
red: Component,
/// The green component. /// The green component.
/// #[external]
/// - blue: `Component` (positional) #[default]
green: Component,
/// The blue component. /// The blue component.
/// #[external]
/// - alpha: `Component` (positional) #[default]
blue: Component,
/// The alpha component. /// The alpha component.
/// #[external]
/// - returns: color #[default]
/// alpha: Component,
/// Display: RGBA ) -> Value {
/// Category: construct Value::Color(if let Some(string) = args.find::<Spanned<EcoString>>()? {
#[func]
pub fn rgb(args: &mut Args) -> SourceResult<Value> {
Ok(Value::Color(if let Some(string) = args.find::<Spanned<EcoString>>()? {
match RgbaColor::from_str(&string.v) { match RgbaColor::from_str(&string.v) {
Ok(color) => color.into(), Ok(color) => color.into(),
Err(msg) => bail!(string.span, msg), Err(msg) => bail!(string.span, msg),
@ -165,7 +163,7 @@ pub fn rgb(args: &mut Args) -> SourceResult<Value> {
let Component(b) = args.expect("blue component")?; let Component(b) = args.expect("blue component")?;
let Component(a) = args.eat()?.unwrap_or(Component(255)); let Component(a) = args.eat()?.unwrap_or(Component(255));
RgbaColor::new(r, g, b, a).into() RgbaColor::new(r, g, b, a).into()
})) })
} }
/// An integer or ratio component. /// An integer or ratio component.
@ -197,30 +195,21 @@ cast_from_value! {
/// ) /// )
/// ```` /// ````
/// ///
/// ## Parameters
/// - cyan: `RatioComponent` (positional, required)
/// The cyan component.
///
/// - magenta: `RatioComponent` (positional, required)
/// The magenta component.
///
/// - yellow: `RatioComponent` (positional, required)
/// The yellow component.
///
/// - key: `RatioComponent` (positional, required)
/// The key component.
///
/// - returns: color
///
/// Display: CMYK /// Display: CMYK
/// Category: construct /// Category: construct
/// Returns: color
#[func] #[func]
pub fn cmyk(args: &mut Args) -> SourceResult<Value> { pub fn cmyk(
let RatioComponent(c) = args.expect("cyan component")?; /// The cyan component.
let RatioComponent(m) = args.expect("magenta component")?; cyan: RatioComponent,
let RatioComponent(y) = args.expect("yellow component")?; /// The magenta component.
let RatioComponent(k) = args.expect("key component")?; magenta: RatioComponent,
Ok(Value::Color(CmykColor::new(c, m, y, k).into())) /// The yellow component.
yellow: RatioComponent,
/// The key component.
key: RatioComponent,
) -> Value {
Value::Color(CmykColor::new(cyan.0, magenta.0, yellow.0, key.0).into())
} }
/// A component that must be a ratio. /// A component that must be a ratio.
@ -254,8 +243,11 @@ cast_from_value! {
/// #envelope.fly /// #envelope.fly
/// ``` /// ```
/// ///
/// ## Parameters /// Display: Symbol
/// - variants: `Variant` (positional, variadic) /// Category: construct
/// Returns: symbol
#[func]
pub fn symbol(
/// The variants of the symbol. /// The variants of the symbol.
/// ///
/// Can be a just a string consisting of a single character for the /// Can be a just a string consisting of a single character for the
@ -263,21 +255,17 @@ cast_from_value! {
/// and the symbol. Individual modifiers should be separated by dots. When /// and the symbol. Individual modifiers should be separated by dots. When
/// displaying a symbol, Typst selects the first from the variants that have /// displaying a symbol, Typst selects the first from the variants that have
/// all attached modifiers and the minimum number of other modifiers. /// all attached modifiers and the minimum number of other modifiers.
/// #[variadic]
/// - returns: symbol variants: Vec<Spanned<Variant>>,
/// ) -> Value {
/// Display: Symbol
/// Category: construct
#[func]
pub fn symbol(args: &mut Args) -> SourceResult<Value> {
let mut list = EcoVec::new(); let mut list = EcoVec::new();
for Spanned { v, span } in args.all::<Spanned<Variant>>()? { for Spanned { v, span } in variants {
if list.iter().any(|(prev, _)| &v.0 == prev) { if list.iter().any(|(prev, _)| &v.0 == prev) {
bail!(span, "duplicate variant"); bail!(span, "duplicate variant");
} }
list.push((v.0, v.1)); list.push((v.0, v.1));
} }
Ok(Value::Symbol(Symbol::runtime(list))) Value::Symbol(Symbol::runtime(list))
} }
/// A value that can be cast to a symbol. /// A value that can be cast to a symbol.
@ -309,17 +297,15 @@ cast_from_value! {
/// #str(<intro>) /// #str(<intro>)
/// ``` /// ```
/// ///
/// ## Parameters
/// - value: `ToStr` (positional, required)
/// The value that should be converted to a string.
///
/// - returns: string
///
/// Display: String /// Display: String
/// Category: construct /// Category: construct
/// Returns: string
#[func] #[func]
pub fn str(args: &mut Args) -> SourceResult<Value> { pub fn str(
Ok(Value::Str(args.expect::<ToStr>("value")?.0)) /// The value that should be converted to a string.
value: ToStr,
) -> Value {
Value::Str(value.0)
} }
/// A value that can be cast to a string. /// A value that can be cast to a string.
@ -352,17 +338,15 @@ cast_from_value! {
/// This function also has dedicated syntax: You can create a label by enclosing /// This function also has dedicated syntax: You can create a label by enclosing
/// its name in angle brackets. This works both in markup and code. /// its name in angle brackets. This works both in markup and code.
/// ///
/// ## Parameters
/// - name: `EcoString` (positional, required)
/// The name of the label.
///
/// - returns: label
///
/// Display: Label /// Display: Label
/// Category: construct /// Category: construct
/// Returns: label
#[func] #[func]
pub fn label(args: &mut Args) -> SourceResult<Value> { pub fn label(
Ok(Value::Label(Label(args.expect("string")?))) /// The name of the label.
name: EcoString,
) -> Value {
Value::Label(Label(name))
} }
/// Create a regular expression from a string. /// Create a regular expression from a string.
@ -386,23 +370,20 @@ pub fn label(args: &mut Args) -> SourceResult<Value> {
/// .split(regex("[,;]"))) /// .split(regex("[,;]")))
/// ``` /// ```
/// ///
/// ## Parameters /// Display: Regex
/// - regex: `EcoString` (positional, required) /// Category: construct
/// Returns: regex
#[func]
pub fn regex(
/// The regular expression as a string. /// The regular expression as a string.
/// ///
/// Most regex escape sequences just work because they are not valid Typst /// Most regex escape sequences just work because they are not valid Typst
/// escape sequences. To produce regex escape sequences that are also valid in /// escape sequences. To produce regex escape sequences that are also valid in
/// Typst (e.g. `[\\]`), you need to escape twice. Thus, to match a verbatim /// Typst (e.g. `[\\]`), you need to escape twice. Thus, to match a verbatim
/// backslash, you would need to write `{regex("\\\\")}`. /// backslash, you would need to write `{regex("\\\\")}`.
/// regex: Spanned<EcoString>,
/// - returns: regex ) -> Value {
/// Regex::new(&regex.v).at(regex.span)?.into()
/// Display: Regex
/// Category: construct
#[func]
pub fn regex(args: &mut Args) -> SourceResult<Value> {
let Spanned { v, span } = args.expect::<Spanned<EcoString>>("regular expression")?;
Ok(Regex::new(&v).at(span)?.into())
} }
/// Create an array consisting of a sequence of numbers. /// Create an array consisting of a sequence of numbers.
@ -420,33 +401,30 @@ pub fn regex(args: &mut Args) -> SourceResult<Value> {
/// #range(5, 2, step: -1) /// #range(5, 2, step: -1)
/// ``` /// ```
/// ///
/// ## Parameters
/// - start: `i64` (positional)
/// The start of the range (inclusive).
///
/// - end: `i64` (positional, required)
/// The end of the range (exclusive).
///
/// - step: `i64` (named)
/// The distance between the generated numbers.
///
/// - returns: array
///
/// Display: Range /// Display: Range
/// Category: construct /// Category: construct
/// Returns: array
#[func] #[func]
pub fn range(args: &mut Args) -> SourceResult<Value> { pub fn range(
/// The start of the range (inclusive).
#[external]
#[default]
start: i64,
/// The end of the range (exclusive).
#[external]
end: i64,
/// The distance between the generated numbers.
#[named]
#[default(NonZeroI64::new(1).unwrap())]
step: NonZeroI64,
) -> Value {
let first = args.expect::<i64>("end")?; let first = args.expect::<i64>("end")?;
let (start, end) = match args.eat::<i64>()? { let (start, end) = match args.eat::<i64>()? {
Some(second) => (first, second), Some(second) => (first, second),
None => (0, first), None => (0, first),
}; };
let step: i64 = match args.named("step")? { let step = step.get();
Some(Spanned { v: 0, span }) => bail!(span, "step must not be zero"),
Some(Spanned { v, .. }) => v,
None => 1,
};
let mut x = start; let mut x = start;
let mut array = Array::new(); let mut array = Array::new();
@ -456,5 +434,5 @@ pub fn range(args: &mut Args) -> SourceResult<Value> {
x += step; x += step;
} }
Ok(Value::Array(array)) Value::Array(array)
} }

View File

@ -16,25 +16,21 @@ use crate::prelude::*;
/// #raw(text, lang: "html") /// #raw(text, lang: "html")
/// ``` /// ```
/// ///
/// ## Parameters
/// - path: `EcoString` (positional, required)
/// Path to a file.
///
/// - returns: string
///
/// Display: Plain text /// Display: Plain text
/// Category: data-loading /// Category: data-loading
/// Returns: string
#[func] #[func]
pub fn read(vm: &Vm, args: &mut Args) -> SourceResult<Value> { pub fn read(
let Spanned { v: path, span } = args.expect::<Spanned<EcoString>>("path to file")?; /// Path to a file.
path: Spanned<EcoString>,
) -> Value {
let Spanned { v: path, span } = path;
let path = vm.locate(&path).at(span)?; let path = vm.locate(&path).at(span)?;
let data = vm.world().file(&path).at(span)?; let data = vm.world().file(&path).at(span)?;
let text = String::from_utf8(data.to_vec()) let text = String::from_utf8(data.to_vec())
.map_err(|_| "file is not valid utf-8") .map_err(|_| "file is not valid utf-8")
.at(span)?; .at(span)?;
Ok(Value::Str(text.into())) Value::Str(text.into())
} }
/// Read structured data from a CSV file. /// Read structured data from a CSV file.
@ -55,33 +51,27 @@ pub fn read(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
/// ) /// )
/// ``` /// ```
/// ///
/// ## Parameters /// Display: CSV
/// - path: `EcoString` (positional, required) /// Category: data-loading
/// Returns: array
#[func]
pub fn csv(
/// Path to a CSV file. /// Path to a CSV file.
/// path: Spanned<EcoString>,
/// - delimiter: `Delimiter` (named)
/// The delimiter that separates columns in the CSV file. /// The delimiter that separates columns in the CSV file.
/// Must be a single ASCII character. /// Must be a single ASCII character.
/// Defaults to a comma. /// Defaults to a comma.
/// #[named]
/// - returns: array #[default]
/// delimiter: Delimiter,
/// Display: CSV ) -> Value {
/// Category: data-loading let Spanned { v: path, span } = path;
#[func]
pub fn csv(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
let Spanned { v: path, span } =
args.expect::<Spanned<EcoString>>("path to csv file")?;
let path = vm.locate(&path).at(span)?; let path = vm.locate(&path).at(span)?;
let data = vm.world().file(&path).at(span)?; let data = vm.world().file(&path).at(span)?;
let mut builder = csv::ReaderBuilder::new(); let mut builder = csv::ReaderBuilder::new();
builder.has_headers(false); builder.has_headers(false);
if let Some(delimiter) = args.named::<Delimiter>("delimiter")? {
builder.delimiter(delimiter.0); builder.delimiter(delimiter.0);
}
let mut reader = builder.from_reader(data.as_slice()); let mut reader = builder.from_reader(data.as_slice());
let mut array = Array::new(); let mut array = Array::new();
@ -92,7 +82,7 @@ pub fn csv(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
array.push(Value::Array(sub)) array.push(Value::Array(sub))
} }
Ok(Value::Array(array)) Value::Array(array)
} }
/// The delimiter to use when parsing CSV files. /// The delimiter to use when parsing CSV files.
@ -115,6 +105,12 @@ cast_from_value! {
}, },
} }
impl Default for Delimiter {
fn default() -> Self {
Self(b',')
}
}
/// Format the user-facing CSV error message. /// Format the user-facing CSV error message.
fn format_csv_error(error: csv::Error) -> String { fn format_csv_error(error: csv::Error) -> String {
match error.kind() { match error.kind() {
@ -170,25 +166,20 @@ fn format_csv_error(error: csv::Error) -> String {
/// #forecast(json("tuesday.json")) /// #forecast(json("tuesday.json"))
/// ``` /// ```
/// ///
/// ## Parameters
/// - path: `EcoString` (positional, required)
/// Path to a JSON file.
///
/// - returns: dictionary or array
///
/// Display: JSON /// Display: JSON
/// Category: data-loading /// Category: data-loading
/// Returns: array or dictionary
#[func] #[func]
pub fn json(vm: &Vm, args: &mut Args) -> SourceResult<Value> { pub fn json(
let Spanned { v: path, span } = /// Path to a JSON file.
args.expect::<Spanned<EcoString>>("path to json file")?; path: Spanned<EcoString>,
) -> Value {
let Spanned { v: path, span } = path;
let path = vm.locate(&path).at(span)?; let path = vm.locate(&path).at(span)?;
let data = vm.world().file(&path).at(span)?; let data = vm.world().file(&path).at(span)?;
let value: serde_json::Value = let value: serde_json::Value =
serde_json::from_slice(&data).map_err(format_json_error).at(span)?; serde_json::from_slice(&data).map_err(format_json_error).at(span)?;
convert_json(value)
Ok(convert_json(value))
} }
/// Convert a JSON value to a Typst value. /// Convert a JSON value to a Typst value.
@ -268,26 +259,20 @@ fn format_json_error(error: serde_json::Error) -> String {
/// } /// }
/// ``` /// ```
/// ///
/// ## Parameters
/// - path: `EcoString` (positional, required)
/// Path to an XML file.
///
/// - returns: array
///
/// Display: XML /// Display: XML
/// Category: data-loading /// Category: data-loading
/// Returns: array
#[func] #[func]
pub fn xml(vm: &Vm, args: &mut Args) -> SourceResult<Value> { pub fn xml(
let Spanned { v: path, span } = /// Path to an XML file.
args.expect::<Spanned<EcoString>>("path to xml file")?; path: Spanned<EcoString>,
) -> Value {
let Spanned { v: path, span } = path;
let path = vm.locate(&path).at(span)?; let path = vm.locate(&path).at(span)?;
let data = vm.world().file(&path).at(span)?; let data = vm.world().file(&path).at(span)?;
let text = std::str::from_utf8(&data).map_err(FileError::from).at(span)?; let text = std::str::from_utf8(&data).map_err(FileError::from).at(span)?;
let document = roxmltree::Document::parse(text).map_err(format_xml_error).at(span)?; let document = roxmltree::Document::parse(text).map_err(format_xml_error).at(span)?;
convert_xml(document.root())
Ok(convert_xml(document.root()))
} }
/// Convert an XML node to a Typst value. /// Convert an XML node to a Typst value.

View File

@ -14,17 +14,15 @@ use crate::prelude::*;
/// #type(x => x + 1) /// #type(x => x + 1)
/// ``` /// ```
/// ///
/// ## Parameters
/// - value: `Value` (positional, required)
/// The value whose type's to determine.
///
/// - returns: string
///
/// Display: Type /// Display: Type
/// Category: foundations /// Category: foundations
/// Returns: string
#[func] #[func]
pub fn type_(args: &mut Args) -> SourceResult<Value> { pub fn type_(
Ok(args.expect::<Value>("value")?.type_name().into()) /// The value whose type's to determine.
value: Value,
) -> Value {
value.type_name().into()
} }
/// The string representation of a value. /// The string representation of a value.
@ -41,17 +39,15 @@ pub fn type_(args: &mut Args) -> SourceResult<Value> {
/// #[*Hi*] vs #repr([*Hi*]) /// #[*Hi*] vs #repr([*Hi*])
/// ``` /// ```
/// ///
/// ## Parameters
/// - value: `Value` (positional, required)
/// The value whose string representation to produce.
///
/// - returns: string
///
/// Display: Representation /// Display: Representation
/// Category: foundations /// Category: foundations
/// Returns: string
#[func] #[func]
pub fn repr(args: &mut Args) -> SourceResult<Value> { pub fn repr(
Ok(args.expect::<Value>("value")?.repr().into()) /// The value whose string representation to produce.
value: Value,
) -> Value {
value.repr().into()
} }
/// Fail with an error. /// Fail with an error.
@ -62,15 +58,16 @@ pub fn repr(args: &mut Args) -> SourceResult<Value> {
/// #panic("this is wrong") /// #panic("this is wrong")
/// ``` /// ```
/// ///
/// ## Parameters
/// - payload: `Value` (positional)
/// The value (or message) to panic with.
///
/// Display: Panic /// Display: Panic
/// Category: foundations /// Category: foundations
/// Returns:
#[func] #[func]
pub fn panic(args: &mut Args) -> SourceResult<Value> { pub fn panic(
match args.eat::<Value>()? { /// The value (or message) to panic with.
#[default]
payload: Option<Value>,
) -> Value {
match payload {
Some(v) => bail!(args.span, "panicked with: {}", v.repr()), Some(v) => bail!(args.span, "panicked with: {}", v.repr()),
None => bail!(args.span, "panicked"), None => bail!(args.span, "panicked"),
} }
@ -86,26 +83,26 @@ pub fn panic(args: &mut Args) -> SourceResult<Value> {
/// #assert(1 < 2, message: "math broke") /// #assert(1 < 2, message: "math broke")
/// ``` /// ```
/// ///
/// ## Parameters
/// - condition: `bool` (positional, required)
/// The condition that must be true for the assertion to pass.
/// - message: `EcoString` (named)
/// The error message when the assertion fails.
///
/// Display: Assert /// Display: Assert
/// Category: foundations /// Category: foundations
/// Returns:
#[func] #[func]
pub fn assert(args: &mut Args) -> SourceResult<Value> { pub fn assert(
let check = args.expect::<bool>("condition")?; /// The condition that must be true for the assertion to pass.
let message = args.named::<EcoString>("message")?; condition: bool,
if !check { /// The error message when the assertion fails.
#[named]
#[default]
message: Option<EcoString>,
) -> Value {
if !condition {
if let Some(message) = message { if let Some(message) = message {
bail!(args.span, "assertion failed: {}", message); bail!(args.span, "assertion failed: {}", message);
} else { } else {
bail!(args.span, "assertion failed"); bail!(args.span, "assertion failed");
} }
} }
Ok(Value::None) Value::None
} }
/// Evaluate a string as Typst code. /// Evaluate a string as Typst code.
@ -119,18 +116,16 @@ pub fn assert(args: &mut Args) -> SourceResult<Value> {
/// #eval("[*Strong text*]") /// #eval("[*Strong text*]")
/// ``` /// ```
/// ///
/// ## Parameters /// Display: Evaluate
/// - source: `String` (positional, required) /// Category: foundations
/// Returns: any
#[func]
pub fn eval(
/// A string of Typst code to evaluate. /// A string of Typst code to evaluate.
/// ///
/// The code in the string cannot interact with the file system. /// The code in the string cannot interact with the file system.
/// source: Spanned<String>,
/// - returns: any ) -> Value {
/// let Spanned { v: text, span } = source;
/// Display: Evaluate typst::eval::eval_code_str(vm.world(), &text, span)?
/// Category: foundations
#[func]
pub fn eval(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
let Spanned { v: text, span } = args.expect::<Spanned<String>>("source")?;
typst::eval::eval_code_str(vm.world(), &text, span)
} }

View File

@ -53,7 +53,6 @@ pub struct AlignNode {
pub alignment: Axes<Option<GenAlign>>, pub alignment: Axes<Option<GenAlign>>,
/// The content to align. /// The content to align.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }

View File

@ -45,7 +45,6 @@ pub struct ColumnsNode {
pub gutter: Rel<Length>, pub gutter: Rel<Length>,
/// The content that should be layouted into the columns. /// The content that should be layouted into the columns.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }

View File

@ -181,20 +181,6 @@ impl Layout for BoxNode {
/// More text. /// More text.
/// ``` /// ```
/// ///
/// ## Parameters
/// - spacing: `Spacing` (named, settable)
/// The spacing around this block. This is shorthand to set `above` and
/// `below` to the same value.
///
/// ```example
/// #set align(center)
/// #show math.formula: set block(above: 8pt, below: 16pt)
///
/// This sum of $x$ and $y$:
/// $ x + y = z $
/// A second paragraph.
/// ```
///
/// Display: Block /// Display: Block
/// Category: layout /// Category: layout
#[node(Layout)] #[node(Layout)]
@ -270,6 +256,20 @@ pub struct BlockNode {
#[fold] #[fold]
pub outset: Sides<Option<Rel<Length>>>, pub outset: Sides<Option<Rel<Length>>>,
/// The spacing around this block. This is shorthand to set `above` and
/// `below` to the same value.
///
/// ```example
/// #set align(center)
/// #show math.formula: set block(above: 8pt, below: 16pt)
///
/// This sum of $x$ and $y$:
/// $ x + y = z $
/// A second paragraph.
/// ```
#[external]
pub spacing: Spacing,
/// The spacing between this block and its predecessor. Takes precedence /// The spacing between this block and its predecessor. Takes precedence
/// over `spacing`. Can be used in combination with a show rule to adjust /// over `spacing`. Can be used in combination with a show rule to adjust
/// the spacing around arbitrary block-level elements. /// the spacing around arbitrary block-level elements.

View File

@ -228,7 +228,6 @@ pub struct EnumItem {
pub number: Option<NonZeroUsize>, pub number: Option<NonZeroUsize>,
/// The item's body. /// The item's body.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }

View File

@ -59,12 +59,6 @@ use super::Sizing;
/// ) /// )
/// ``` /// ```
/// ///
/// ## Parameters
/// - gutter: `TrackSizings` (named, settable)
/// Defines the gaps between rows & columns.
///
/// If there are more gutters than defined sizes, the last gutter is repeated.
///
/// Display: Grid /// Display: Grid
/// Category: layout /// Category: layout
#[node(Layout)] #[node(Layout)]
@ -83,6 +77,12 @@ pub struct GridNode {
/// repeated until there are no more cells. /// repeated until there are no more cells.
pub rows: TrackSizings, pub rows: TrackSizings,
/// Defines the gaps between rows & columns.
///
/// If there are more gutters than defined sizes, the last gutter is repeated.
#[external]
pub gutter: TrackSizings,
/// Defines the gaps between columns. Takes precedence over `gutter`. /// Defines the gaps between columns. Takes precedence over `gutter`.
#[parse( #[parse(
let gutter = args.named("gutter")?; let gutter = args.named("gutter")?;

View File

@ -18,7 +18,6 @@ use crate::prelude::*;
#[node(Show)] #[node(Show)]
pub struct HideNode { pub struct HideNode {
/// The content to hide. /// The content to hide.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }

View File

@ -163,7 +163,6 @@ impl Layout for ListNode {
#[node] #[node]
pub struct ListItem { pub struct ListItem {
/// The item's body. /// The item's body.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }

View File

@ -15,16 +15,6 @@ use crate::prelude::*;
/// measured in words per minute._ /// measured in words per minute._
/// ``` /// ```
/// ///
/// ## Parameters
/// - x: `Rel<Length>` (named, settable)
/// The horizontal padding. Both `left` and `right` take precedence over this.
///
/// - y: `Rel<Length>` (named, settable)
/// The vertical padding. Both `top` and `bottom` take precedence over this.
///
/// - rest: `Rel<Length>` (named, settable)
/// The padding for all sides. All other parameters take precedence over this.
///
/// Display: Padding /// Display: Padding
/// Category: layout /// Category: layout
#[node(Layout)] #[node(Layout)]
@ -50,8 +40,21 @@ pub struct PadNode {
#[parse(args.named("bottom")?.or(y))] #[parse(args.named("bottom")?.or(y))]
pub bottom: Rel<Length>, pub bottom: Rel<Length>,
/// The horizontal padding. Both `left` and `right` take precedence over
/// this.
#[external]
pub x: Rel<Length>,
/// The vertical padding. Both `top` and `bottom` take precedence over this.
#[external]
pub y: Rel<Length>,
/// The padding for all sides. All other parameters take precedence over
/// this.
#[external]
pub rest: Rel<Length>,
/// The content to pad at the sides. /// The content to pad at the sides.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }

View File

@ -20,15 +20,15 @@ use crate::prelude::*;
/// There you go, US friends! /// There you go, US friends!
/// ``` /// ```
/// ///
/// ## Parameters
/// - paper: `Paper` (positional, named, settable)
/// A standard paper size to set width and height. When this is not specified,
/// Typst defaults to `{"a4"}` paper.
///
/// Display: Page /// Display: Page
/// Category: layout /// Category: layout
#[node] #[node]
pub struct PageNode { pub struct PageNode {
/// A standard paper size to set width and height. When this is not
/// specified, Typst defaults to `{"a4"}` paper.
#[external]
pub paper: Paper,
/// The width of the page. /// The width of the page.
/// ///
/// ```example /// ```example
@ -232,7 +232,6 @@ pub struct PageNode {
/// Multiple pages will be created if the content does not fit on a single /// Multiple pages will be created if the content does not fit on a single
/// page. A new page with the page properties prior to the function invocation /// page. A new page with the page properties prior to the function invocation
/// will be created after the body has been typeset. /// will be created after the body has been typeset.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }

View File

@ -35,10 +35,6 @@ use crate::text::{
/// three integers. Then, we ... /// three integers. Then, we ...
/// ``` /// ```
/// ///
/// ## Parameters
/// - body: `Content` (positional, required)
/// The contents of the paragraph.
///
/// Display: Paragraph /// Display: Paragraph
/// Category: layout /// Category: layout
#[node(Construct)] #[node(Construct)]
@ -99,6 +95,10 @@ pub struct ParNode {
#[default] #[default]
pub linebreaks: Smart<Linebreaks>, pub linebreaks: Smart<Linebreaks>,
/// The contents of the paragraph.
#[external]
pub body: Content,
/// The paragraph's children. /// The paragraph's children.
#[internal] #[internal]
#[variadic] #[variadic]

View File

@ -49,7 +49,6 @@ pub struct PlaceNode {
pub dy: Rel<Length>, pub dy: Rel<Length>,
/// The content to place. /// The content to place.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }

View File

@ -26,7 +26,6 @@ use super::AlignNode;
#[node(Layout)] #[node(Layout)]
pub struct RepeatNode { pub struct RepeatNode {
/// The content to repeat. /// The content to repeat.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }

View File

@ -24,7 +24,6 @@ use crate::prelude::*;
#[node(Behave)] #[node(Behave)]
pub struct HNode { pub struct HNode {
/// How much spacing to insert. /// How much spacing to insert.
#[positional]
#[required] #[required]
pub amount: Spacing, pub amount: Spacing,
@ -84,8 +83,14 @@ impl Behave for HNode {
/// ) /// )
/// ``` /// ```
/// ///
/// ## Parameters /// Display: Spacing (V)
/// - weak: `bool` (named, settable) /// Category: layout
#[node(Behave)]
pub struct VNode {
/// How much spacing to insert.
#[required]
pub amount: Spacing,
/// If true, the spacing collapses at the start or end of a flow. Moreover, /// If true, the spacing collapses at the start or end of a flow. Moreover,
/// from multiple adjacent weak spacings all but the largest one collapse. /// from multiple adjacent weak spacings all but the largest one collapse.
/// Weak spacings will always collapse adjacent paragraph spacing, even if the /// Weak spacings will always collapse adjacent paragraph spacing, even if the
@ -99,15 +104,8 @@ impl Behave for HNode {
/// #v(4pt, weak: true) /// #v(4pt, weak: true)
/// The proof is simple: /// The proof is simple:
/// ``` /// ```
/// #[external]
/// Display: Spacing (V) pub weak: bool,
/// Category: layout
#[node(Behave)]
pub struct VNode {
/// How much spacing to insert.
#[positional]
#[required]
pub amount: Spacing,
/// The node's weakness level, see also [`Behaviour`]. /// The node's weakness level, see also [`Behaviour`].
#[internal] #[internal]

View File

@ -29,35 +29,33 @@ use crate::prelude::*;
/// ) /// )
/// ``` /// ```
/// ///
/// ## Parameters
/// - gutter: `TrackSizings` (named, settable)
/// Defines the gaps between rows & columns.
/// See the [grid documentation]($func/grid) for more information on gutters.
///
/// Display: Table /// Display: Table
/// Category: layout /// Category: layout
#[node(Layout)] #[node(Layout)]
pub struct TableNode { pub struct TableNode {
/// Defines the column sizes. /// Defines the column sizes. See the [grid documentation]($func/grid) for
/// See the [grid documentation]($func/grid) for more information on track /// more information on track sizing.
/// sizing.
pub columns: TrackSizings, pub columns: TrackSizings,
/// Defines the row sizes. /// Defines the row sizes. See the [grid documentation]($func/grid) for more
/// See the [grid documentation]($func/grid) for more information on track /// information on track sizing.
/// sizing.
pub rows: TrackSizings, pub rows: TrackSizings,
/// Defines the gaps between columns. Takes precedence over `gutter`. /// Defines the gaps between rows & columns. See the [grid
/// See the [grid documentation]($func/grid) for more information on gutters. /// documentation]($func/grid) for more information on gutters.
#[external]
pub gutter: TrackSizings,
/// Defines the gaps between columns. Takes precedence over `gutter`. See
/// the [grid documentation]($func/grid) for more information on gutters.
#[parse( #[parse(
let gutter = args.named("gutter")?; let gutter = args.named("gutter")?;
args.named("column-gutter")?.or_else(|| gutter.clone()) args.named("column-gutter")?.or_else(|| gutter.clone())
)] )]
pub column_gutter: TrackSizings, pub column_gutter: TrackSizings,
/// Defines the gaps between rows. Takes precedence over `gutter`. /// Defines the gaps between rows. Takes precedence over `gutter`. See the
/// See the [grid documentation]($func/grid) for more information on gutters. /// [grid documentation]($func/grid) for more information on gutters.
#[parse(args.named("row-gutter")?.or_else(|| gutter.clone()))] #[parse(args.named("row-gutter")?.or_else(|| gutter.clone()))]
pub row_gutter: TrackSizings, pub row_gutter: TrackSizings,

View File

@ -126,12 +126,10 @@ impl Layout for TermsNode {
#[node] #[node]
pub struct TermItem { pub struct TermItem {
/// The term described by the list item. /// The term described by the list item.
#[positional]
#[required] #[required]
pub term: Content, pub term: Content,
/// The description of the term. /// The description of the term.
#[positional]
#[required] #[required]
pub description: Content, pub description: Content,
} }

View File

@ -32,7 +32,6 @@ pub struct MoveNode {
pub dy: Rel<Length>, pub dy: Rel<Length>,
/// The content to move. /// The content to move.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }
@ -101,7 +100,6 @@ pub struct RotateNode {
pub origin: Axes<Option<GenAlign>>, pub origin: Axes<Option<GenAlign>>,
/// The content to rotate. /// The content to rotate.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }
@ -170,7 +168,6 @@ pub struct ScaleNode {
pub origin: Axes<Option<GenAlign>>, pub origin: Axes<Option<GenAlign>>,
/// The content to scale. /// The content to scale.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }

View File

@ -29,91 +29,91 @@ fn global(math: Module, calc: Module) -> Module {
let mut global = Scope::deduplicating(); let mut global = Scope::deduplicating();
// Text. // Text.
global.def_func::<text::TextNode>("text"); global.define("text", text::TextNode::func());
global.def_func::<text::LinebreakNode>("linebreak"); global.define("linebreak", text::LinebreakNode::func());
global.def_func::<text::SmartQuoteNode>("smartquote"); global.define("smartquote", text::SmartQuoteNode::func());
global.def_func::<text::StrongNode>("strong"); global.define("strong", text::StrongNode::func());
global.def_func::<text::EmphNode>("emph"); global.define("emph", text::EmphNode::func());
global.def_func::<text::LowerFunc>("lower"); global.define("lower", text::lower);
global.def_func::<text::UpperFunc>("upper"); global.define("upper", text::upper);
global.def_func::<text::SmallcapsFunc>("smallcaps"); global.define("smallcaps", text::smallcaps);
global.def_func::<text::SubNode>("sub"); global.define("sub", text::SubNode::func());
global.def_func::<text::SuperNode>("super"); global.define("super", text::SuperNode::func());
global.def_func::<text::UnderlineNode>("underline"); global.define("underline", text::UnderlineNode::func());
global.def_func::<text::StrikeNode>("strike"); global.define("strike", text::StrikeNode::func());
global.def_func::<text::OverlineNode>("overline"); global.define("overline", text::OverlineNode::func());
global.def_func::<text::RawNode>("raw"); global.define("raw", text::RawNode::func());
global.def_func::<text::LoremFunc>("lorem"); global.define("lorem", text::lorem);
// Math. // Math.
global.define("math", math); global.define("math", math);
// Layout. // Layout.
global.def_func::<layout::PageNode>("page"); global.define("page", layout::PageNode::func());
global.def_func::<layout::PagebreakNode>("pagebreak"); global.define("pagebreak", layout::PagebreakNode::func());
global.def_func::<layout::VNode>("v"); global.define("v", layout::VNode::func());
global.def_func::<layout::ParNode>("par"); global.define("par", layout::ParNode::func());
global.def_func::<layout::ParbreakNode>("parbreak"); global.define("parbreak", layout::ParbreakNode::func());
global.def_func::<layout::HNode>("h"); global.define("h", layout::HNode::func());
global.def_func::<layout::BoxNode>("box"); global.define("box", layout::BoxNode::func());
global.def_func::<layout::BlockNode>("block"); global.define("block", layout::BlockNode::func());
global.def_func::<layout::ListNode>("list"); global.define("list", layout::ListNode::func());
global.def_func::<layout::EnumNode>("enum"); global.define("enum", layout::EnumNode::func());
global.def_func::<layout::TermsNode>("terms"); global.define("terms", layout::TermsNode::func());
global.def_func::<layout::TableNode>("table"); global.define("table", layout::TableNode::func());
global.def_func::<layout::StackNode>("stack"); global.define("stack", layout::StackNode::func());
global.def_func::<layout::GridNode>("grid"); global.define("grid", layout::GridNode::func());
global.def_func::<layout::ColumnsNode>("columns"); global.define("columns", layout::ColumnsNode::func());
global.def_func::<layout::ColbreakNode>("colbreak"); global.define("colbreak", layout::ColbreakNode::func());
global.def_func::<layout::PlaceNode>("place"); global.define("place", layout::PlaceNode::func());
global.def_func::<layout::AlignNode>("align"); global.define("align", layout::AlignNode::func());
global.def_func::<layout::PadNode>("pad"); global.define("pad", layout::PadNode::func());
global.def_func::<layout::RepeatNode>("repeat"); global.define("repeat", layout::RepeatNode::func());
global.def_func::<layout::MoveNode>("move"); global.define("move", layout::MoveNode::func());
global.def_func::<layout::ScaleNode>("scale"); global.define("scale", layout::ScaleNode::func());
global.def_func::<layout::RotateNode>("rotate"); global.define("rotate", layout::RotateNode::func());
global.def_func::<layout::HideNode>("hide"); global.define("hide", layout::HideNode::func());
// Visualize. // Visualize.
global.def_func::<visualize::ImageNode>("image"); global.define("image", visualize::ImageNode::func());
global.def_func::<visualize::LineNode>("line"); global.define("line", visualize::LineNode::func());
global.def_func::<visualize::RectNode>("rect"); global.define("rect", visualize::RectNode::func());
global.def_func::<visualize::SquareNode>("square"); global.define("square", visualize::SquareNode::func());
global.def_func::<visualize::EllipseNode>("ellipse"); global.define("ellipse", visualize::EllipseNode::func());
global.def_func::<visualize::CircleNode>("circle"); global.define("circle", visualize::CircleNode::func());
// Meta. // Meta.
global.def_func::<meta::DocumentNode>("document"); global.define("document", meta::DocumentNode::func());
global.def_func::<meta::RefNode>("ref"); global.define("ref", meta::RefNode::func());
global.def_func::<meta::LinkNode>("link"); global.define("link", meta::LinkNode::func());
global.def_func::<meta::OutlineNode>("outline"); global.define("outline", meta::OutlineNode::func());
global.def_func::<meta::HeadingNode>("heading"); global.define("heading", meta::HeadingNode::func());
global.def_func::<meta::NumberingFunc>("numbering"); global.define("numbering", meta::numbering);
// Symbols. // Symbols.
global.define("sym", symbols::sym()); global.define("sym", symbols::sym());
global.define("emoji", symbols::emoji()); global.define("emoji", symbols::emoji());
// Compute. // Compute.
global.def_func::<compute::TypeFunc>("type"); global.define("type", compute::type_);
global.def_func::<compute::ReprFunc>("repr"); global.define("repr", compute::repr);
global.def_func::<compute::PanicFunc>("panic"); global.define("panic", compute::panic);
global.def_func::<compute::AssertFunc>("assert"); global.define("assert", compute::assert);
global.def_func::<compute::EvalFunc>("eval"); global.define("eval", compute::eval);
global.def_func::<compute::IntFunc>("int"); global.define("int", compute::int);
global.def_func::<compute::FloatFunc>("float"); global.define("float", compute::float);
global.def_func::<compute::LumaFunc>("luma"); global.define("luma", compute::luma);
global.def_func::<compute::RgbFunc>("rgb"); global.define("rgb", compute::rgb);
global.def_func::<compute::CmykFunc>("cmyk"); global.define("cmyk", compute::cmyk);
global.def_func::<compute::SymbolFunc>("symbol"); global.define("symbol", compute::symbol);
global.def_func::<compute::StrFunc>("str"); global.define("str", compute::str);
global.def_func::<compute::LabelFunc>("label"); global.define("label", compute::label);
global.def_func::<compute::RegexFunc>("regex"); global.define("regex", compute::regex);
global.def_func::<compute::RangeFunc>("range"); global.define("range", compute::range);
global.def_func::<compute::ReadFunc>("read"); global.define("read", compute::read);
global.def_func::<compute::CsvFunc>("csv"); global.define("csv", compute::csv);
global.def_func::<compute::JsonFunc>("json"); global.define("json", compute::json);
global.def_func::<compute::XmlFunc>("xml"); global.define("xml", compute::xml);
// Calc. // Calc.
global.define("calc", calc); global.define("calc", calc);

View File

@ -24,7 +24,6 @@ pub struct AccentNode {
/// ```example /// ```example
/// $arrow(A B C)$ /// $arrow(A B C)$
/// ``` /// ```
#[positional]
#[required] #[required]
pub base: Content, pub base: Content,
@ -47,7 +46,6 @@ pub struct AccentNode {
/// | Caron | `caron` | `ˇ` | /// | Caron | `caron` | `ˇ` |
/// | Right arrow | `arrow`, `->` | `→` | /// | Right arrow | `arrow`, `->` | `→` |
/// | Left arrow | `arrow.l`, `<-` | `←` | /// | Left arrow | `arrow.l`, `<-` | `←` |
#[positional]
#[required] #[required]
pub accent: Accent, pub accent: Accent,
} }

View File

@ -16,7 +16,6 @@ use super::*;
#[node(LayoutMath)] #[node(LayoutMath)]
pub struct AttachNode { pub struct AttachNode {
/// The base to which things are attached. /// The base to which things are attached.
#[positional]
#[required] #[required]
pub base: Content, pub base: Content,
@ -79,7 +78,6 @@ impl LayoutMath for AttachNode {
#[node(LayoutMath)] #[node(LayoutMath)]
pub struct ScriptsNode { pub struct ScriptsNode {
/// The base to attach the scripts to. /// The base to attach the scripts to.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }
@ -102,7 +100,6 @@ impl LayoutMath for ScriptsNode {
#[node(LayoutMath)] #[node(LayoutMath)]
pub struct LimitsNode { pub struct LimitsNode {
/// The base to attach the limits to. /// The base to attach the limits to.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }

View File

@ -24,7 +24,6 @@ pub struct LrNode {
pub size: Smart<Rel<Length>>, pub size: Smart<Rel<Length>>,
/// The delimited content, including the delimiters. /// The delimited content, including the delimiters.
#[positional]
#[required] #[required]
#[parse( #[parse(
let mut body = Content::empty(); let mut body = Content::empty();
@ -111,15 +110,15 @@ fn scale(
/// $ floor(x/2) $ /// $ floor(x/2) $
/// ``` /// ```
/// ///
/// ## Parameters
/// - body: `Content` (positional, required)
/// The expression to floor.
///
/// Display: Floor /// Display: Floor
/// Category: math /// Category: math
/// Returns: content
#[func] #[func]
pub fn floor(args: &mut Args) -> SourceResult<Value> { pub fn floor(
delimited(args, '⌊', '⌋') /// The expression to floor.
body: Content,
) -> Value {
delimited(body, '⌊', '⌋')
} }
/// Ceil an expression. /// Ceil an expression.
@ -129,15 +128,15 @@ pub fn floor(args: &mut Args) -> SourceResult<Value> {
/// $ ceil(x/2) $ /// $ ceil(x/2) $
/// ``` /// ```
/// ///
/// ## Parameters
/// - body: `Content` (positional, required)
/// The expression to ceil.
///
/// Display: Ceil /// Display: Ceil
/// Category: math /// Category: math
/// Returns: content
#[func] #[func]
pub fn ceil(args: &mut Args) -> SourceResult<Value> { pub fn ceil(
delimited(args, '⌈', '⌉') /// The expression to ceil.
body: Content,
) -> Value {
delimited(body, '⌈', '⌉')
} }
/// Take the absolute value of an expression. /// Take the absolute value of an expression.
@ -147,15 +146,16 @@ pub fn ceil(args: &mut Args) -> SourceResult<Value> {
/// $ abs(x/2) $ /// $ abs(x/2) $
/// ``` /// ```
/// ///
/// ## Parameters
/// - body: `Content` (positional, required)
/// The expression to take the absolute value of.
/// ///
/// Display: Abs /// Display: Abs
/// Category: math /// Category: math
/// Returns: content
#[func] #[func]
pub fn abs(args: &mut Args) -> SourceResult<Value> { pub fn abs(
delimited(args, '|', '|') /// The expression to take the absolute value of.
body: Content,
) -> Value {
delimited(body, '|', '|')
} }
/// Take the norm of an expression. /// Take the norm of an expression.
@ -165,24 +165,24 @@ pub fn abs(args: &mut Args) -> SourceResult<Value> {
/// $ norm(x/2) $ /// $ norm(x/2) $
/// ``` /// ```
/// ///
/// ## Parameters
/// - body: `Content` (positional, required)
/// The expression to take the norm of.
///
/// Display: Norm /// Display: Norm
/// Category: math /// Category: math
/// Returns: content
#[func] #[func]
pub fn norm(args: &mut Args) -> SourceResult<Value> { pub fn norm(
delimited(args, '‖', '‖') /// The expression to take the norm of.
body: Content,
) -> Value {
delimited(body, '‖', '‖')
} }
fn delimited(args: &mut Args, left: char, right: char) -> SourceResult<Value> { fn delimited(body: Content, left: char, right: char) -> Value {
Ok(Value::Content( Value::Content(
LrNode::new(Content::sequence(vec![ LrNode::new(Content::sequence(vec![
TextNode::packed(left), TextNode::packed(left),
args.expect::<Content>("body")?, body,
TextNode::packed(right), TextNode::packed(right),
])) ]))
.pack(), .pack(),
)) )
} }

View File

@ -22,12 +22,10 @@ const FRAC_AROUND: Em = Em::new(0.1);
#[node(LayoutMath)] #[node(LayoutMath)]
pub struct FracNode { pub struct FracNode {
/// The fraction's numerator. /// The fraction's numerator.
#[positional]
#[required] #[required]
pub num: Content, pub num: Content,
/// The fraction's denominator. /// The fraction's denominator.
#[positional]
#[required] #[required]
pub denom: Content, pub denom: Content,
} }
@ -50,12 +48,10 @@ impl LayoutMath for FracNode {
#[node(LayoutMath)] #[node(LayoutMath)]
pub struct BinomNode { pub struct BinomNode {
/// The binomial's upper index. /// The binomial's upper index.
#[positional]
#[required] #[required]
pub upper: Content, pub upper: Content,
/// The binomial's lower index. /// The binomial's lower index.
#[positional]
#[required] #[required]
pub lower: Content, pub lower: Content,
} }

View File

@ -47,52 +47,52 @@ use crate::text::{
/// Create a module with all math definitions. /// Create a module with all math definitions.
pub fn module() -> Module { pub fn module() -> Module {
let mut math = Scope::deduplicating(); let mut math = Scope::deduplicating();
math.def_func::<FormulaNode>("formula"); math.define("formula", FormulaNode::func());
math.def_func::<TextNode>("text"); math.define("text", TextNode::func());
// Grouping. // Grouping.
math.def_func::<LrNode>("lr"); math.define("lr", LrNode::func());
math.def_func::<AbsFunc>("abs"); math.define("abs", abs);
math.def_func::<NormFunc>("norm"); math.define("norm", norm);
math.def_func::<FloorFunc>("floor"); math.define("floor", floor);
math.def_func::<CeilFunc>("ceil"); math.define("ceil", ceil);
// Attachments and accents. // Attachments and accents.
math.def_func::<AttachNode>("attach"); math.define("attach", AttachNode::func());
math.def_func::<ScriptsNode>("scripts"); math.define("scripts", ScriptsNode::func());
math.def_func::<LimitsNode>("limits"); math.define("limits", LimitsNode::func());
math.def_func::<AccentNode>("accent"); math.define("accent", AccentNode::func());
math.def_func::<UnderlineNode>("underline"); math.define("underline", UnderlineNode::func());
math.def_func::<OverlineNode>("overline"); math.define("overline", OverlineNode::func());
math.def_func::<UnderbraceNode>("underbrace"); math.define("underbrace", UnderbraceNode::func());
math.def_func::<OverbraceNode>("overbrace"); math.define("overbrace", OverbraceNode::func());
math.def_func::<UnderbracketNode>("underbracket"); math.define("underbracket", UnderbracketNode::func());
math.def_func::<OverbracketNode>("overbracket"); math.define("overbracket", OverbracketNode::func());
// Fractions and matrix-likes. // Fractions and matrix-likes.
math.def_func::<FracNode>("frac"); math.define("frac", FracNode::func());
math.def_func::<BinomNode>("binom"); math.define("binom", BinomNode::func());
math.def_func::<VecNode>("vec"); math.define("vec", VecNode::func());
math.def_func::<MatNode>("mat"); math.define("mat", MatNode::func());
math.def_func::<CasesNode>("cases"); math.define("cases", CasesNode::func());
// Roots. // Roots.
math.def_func::<SqrtNode>("sqrt"); math.define("sqrt", SqrtNode::func());
math.def_func::<RootNode>("root"); math.define("root", RootNode::func());
// Styles. // Styles.
math.def_func::<UprightNode>("upright"); math.define("upright", UprightNode::func());
math.def_func::<BoldNode>("bold"); math.define("bold", BoldNode::func());
math.def_func::<ItalicNode>("italic"); math.define("italic", ItalicNode::func());
math.def_func::<SerifNode>("serif"); math.define("serif", SerifNode::func());
math.def_func::<SansNode>("sans"); math.define("sans", SansNode::func());
math.def_func::<CalNode>("cal"); math.define("cal", CalNode::func());
math.def_func::<FrakNode>("frak"); math.define("frak", FrakNode::func());
math.def_func::<MonoNode>("mono"); math.define("mono", MonoNode::func());
math.def_func::<BbNode>("bb"); math.define("bb", BbNode::func());
// Text operators. // Text operators.
math.def_func::<OpNode>("op"); math.define("op", OpNode::func());
op::define(&mut math); op::define(&mut math);
// Spacings. // Spacings.
@ -139,7 +139,6 @@ pub struct FormulaNode {
pub block: bool, pub block: bool,
/// The content of the formula. /// The content of the formula.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }

View File

@ -23,7 +23,6 @@ use super::*;
#[node(LayoutMath)] #[node(LayoutMath)]
pub struct OpNode { pub struct OpNode {
/// The operator's text. /// The operator's text.
#[positional]
#[required] #[required]
pub text: EcoString, pub text: EcoString,

View File

@ -12,7 +12,6 @@ use super::*;
#[node(LayoutMath)] #[node(LayoutMath)]
pub struct SqrtNode { pub struct SqrtNode {
/// The expression to take the square root of. /// The expression to take the square root of.
#[positional]
#[required] #[required]
pub radicand: Content, pub radicand: Content,
} }
@ -35,12 +34,10 @@ impl LayoutMath for SqrtNode {
#[node(LayoutMath)] #[node(LayoutMath)]
pub struct RootNode { pub struct RootNode {
/// Which root of the radicand to take. /// Which root of the radicand to take.
#[positional]
#[required] #[required]
index: Content, index: Content,
/// The expression to take the root of. /// The expression to take the root of.
#[positional]
#[required] #[required]
radicand: Content, radicand: Content,
} }

View File

@ -12,7 +12,6 @@ use super::*;
#[node(LayoutMath)] #[node(LayoutMath)]
pub struct BoldNode { pub struct BoldNode {
/// The piece of formula to style. /// The piece of formula to style.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }
@ -38,7 +37,6 @@ impl LayoutMath for BoldNode {
#[node(LayoutMath)] #[node(LayoutMath)]
pub struct UprightNode { pub struct UprightNode {
/// The piece of formula to style. /// The piece of formula to style.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }
@ -61,7 +59,6 @@ impl LayoutMath for UprightNode {
#[node(LayoutMath)] #[node(LayoutMath)]
pub struct ItalicNode { pub struct ItalicNode {
/// The piece of formula to style. /// The piece of formula to style.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }
@ -84,7 +81,6 @@ impl LayoutMath for ItalicNode {
#[node(LayoutMath)] #[node(LayoutMath)]
pub struct SerifNode { pub struct SerifNode {
/// The piece of formula to style. /// The piece of formula to style.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }
@ -110,7 +106,6 @@ impl LayoutMath for SerifNode {
#[node(LayoutMath)] #[node(LayoutMath)]
pub struct SansNode { pub struct SansNode {
/// The piece of formula to style. /// The piece of formula to style.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }
@ -136,7 +131,6 @@ impl LayoutMath for SansNode {
#[node(LayoutMath)] #[node(LayoutMath)]
pub struct CalNode { pub struct CalNode {
/// The piece of formula to style. /// The piece of formula to style.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }
@ -162,7 +156,6 @@ impl LayoutMath for CalNode {
#[node(LayoutMath)] #[node(LayoutMath)]
pub struct FrakNode { pub struct FrakNode {
/// The piece of formula to style. /// The piece of formula to style.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }
@ -188,7 +181,6 @@ impl LayoutMath for FrakNode {
#[node(LayoutMath)] #[node(LayoutMath)]
pub struct MonoNode { pub struct MonoNode {
/// The piece of formula to style. /// The piece of formula to style.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }
@ -219,7 +211,6 @@ impl LayoutMath for MonoNode {
#[node(LayoutMath)] #[node(LayoutMath)]
pub struct BbNode { pub struct BbNode {
/// The piece of formula to style. /// The piece of formula to style.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }

View File

@ -16,7 +16,6 @@ const BRACKET_GAP: Em = Em::new(0.25);
#[node(LayoutMath)] #[node(LayoutMath)]
pub struct UnderlineNode { pub struct UnderlineNode {
/// The content above the line. /// The content above the line.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }
@ -39,7 +38,6 @@ impl LayoutMath for UnderlineNode {
#[node(LayoutMath)] #[node(LayoutMath)]
pub struct OverlineNode { pub struct OverlineNode {
/// The content below the line. /// The content below the line.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }
@ -62,7 +60,6 @@ impl LayoutMath for OverlineNode {
#[node(LayoutMath)] #[node(LayoutMath)]
pub struct UnderbraceNode { pub struct UnderbraceNode {
/// The content above the brace. /// The content above the brace.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
@ -89,7 +86,6 @@ impl LayoutMath for UnderbraceNode {
#[node(LayoutMath)] #[node(LayoutMath)]
pub struct OverbraceNode { pub struct OverbraceNode {
/// The content below the brace. /// The content below the brace.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
@ -116,7 +112,6 @@ impl LayoutMath for OverbraceNode {
#[node(LayoutMath)] #[node(LayoutMath)]
pub struct UnderbracketNode { pub struct UnderbracketNode {
/// The content above the bracket. /// The content above the bracket.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
@ -143,7 +138,6 @@ impl LayoutMath for UnderbracketNode {
#[node(LayoutMath)] #[node(LayoutMath)]
pub struct OverbracketNode { pub struct OverbracketNode {
/// The content below the bracket. /// The content below the bracket.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,

View File

@ -74,7 +74,6 @@ pub struct HeadingNode {
pub outlined: bool, pub outlined: bool,
/// The heading's title. /// The heading's title.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }

View File

@ -46,7 +46,6 @@ pub struct LinkNode {
/// ] /// ]
/// ``` /// ```
/// ///
#[positional]
#[required] #[required]
#[parse( #[parse(
let dest = args.expect::<Destination>("destination")?; let dest = args.expect::<Destination>("destination")?;
@ -59,7 +58,6 @@ pub struct LinkNode {
/// The content that should become a link. If `dest` is an URL string, the /// The content that should become a link. If `dest` is an URL string, the
/// parameter can be omitted. In this case, the URL will be shown as the /// parameter can be omitted. In this case, the URL will be shown as the
/// link. /// link.
#[positional]
#[required] #[required]
#[parse(match &dest { #[parse(match &dest {
Destination::Url(url) => match args.eat()? { Destination::Url(url) => match args.eat()? {

View File

@ -27,8 +27,11 @@ use crate::text::Case;
/// ) /// )
/// ``` /// ```
/// ///
/// ## Parameters /// Display: Numbering
/// - numbering: `Numbering` (positional, required) /// Category: meta
/// Returns: any
#[func]
pub fn numbering(
/// Defines how the numbering works. /// Defines how the numbering works.
/// ///
/// **Counting symbols** are `1`, `a`, `A`, `i`, `I` and `*`. They are /// **Counting symbols** are `1`, `a`, `A`, `i`, `I` and `*`. They are
@ -51,22 +54,15 @@ use crate::text::Case;
/// particularly useful in itself, it means that you can just give arbitrary /// particularly useful in itself, it means that you can just give arbitrary
/// numberings to the `numbering` function without caring whether they are /// numberings to the `numbering` function without caring whether they are
/// defined as a pattern or function. /// defined as a pattern or function.
/// numbering: Numbering,
/// - numbers: `NonZeroUsize` (positional, variadic)
/// The numbers to apply the numbering to. Must be positive. /// The numbers to apply the numbering to. Must be positive.
/// ///
/// If `numbering` is a pattern and more numbers than counting symbols are /// If `numbering` is a pattern and more numbers than counting symbols are
/// given, the last counting symbol with its prefix is repeated. /// given, the last counting symbol with its prefix is repeated.
/// #[variadic]
/// - returns: any numbers: Vec<NonZeroUsize>,
/// ) -> Value {
/// Display: Numbering numbering.apply(vm.world(), &numbers)?
/// Category: meta
#[func]
pub fn numbering(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
let numbering = args.expect::<Numbering>("pattern or function")?;
let numbers = args.all::<NonZeroUsize>()?;
numbering.apply(vm.world(), &numbers)
} }
/// How to number an enumeration. /// How to number an enumeration.

View File

@ -20,7 +20,6 @@ use crate::text::TextNode;
#[node(Show)] #[node(Show)]
pub struct RefNode { pub struct RefNode {
/// The label that should be referenced. /// The label that should be referenced.
#[positional]
#[required] #[required]
pub target: EcoString, pub target: EcoString,
} }

View File

@ -61,7 +61,6 @@ pub struct UnderlineNode {
pub evade: bool, pub evade: bool,
/// The content to underline. /// The content to underline.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }
@ -141,7 +140,6 @@ pub struct OverlineNode {
pub evade: bool, pub evade: bool,
/// The content to add a line over. /// The content to add a line over.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }
@ -206,7 +204,6 @@ pub struct StrikeNode {
pub extent: Length, pub extent: Length,
/// The content to strike through. /// The content to strike through.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }

View File

@ -94,7 +94,6 @@ pub struct StrongNode {
pub delta: i64, pub delta: i64,
/// The content to strongly emphasize. /// The content to strongly emphasize.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }
@ -155,7 +154,6 @@ impl Fold for Delta {
#[node(Show)] #[node(Show)]
pub struct EmphNode { pub struct EmphNode {
/// The content to emphasize. /// The content to emphasize.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }
@ -196,15 +194,15 @@ impl Fold for Toggle {
/// #lower[already low] /// #lower[already low]
/// ``` /// ```
/// ///
/// ## Parameters
/// - text: `ToCase` (positional, required)
/// The text to convert to lowercase.
///
/// Display: Lowercase /// Display: Lowercase
/// Category: text /// Category: text
/// Returns: string or content
#[func] #[func]
pub fn lower(args: &mut Args) -> SourceResult<Value> { pub fn lower(
case(Case::Lower, args) /// The text to convert to lowercase.
text: ToCase,
) -> Value {
case(text, Case::Lower)
} }
/// Convert text or content to uppercase. /// Convert text or content to uppercase.
@ -216,23 +214,23 @@ pub fn lower(args: &mut Args) -> SourceResult<Value> {
/// #upper[ALREADY HIGH] /// #upper[ALREADY HIGH]
/// ``` /// ```
/// ///
/// ## Parameters
/// - text: `ToCase` (positional, required)
/// The text to convert to uppercase.
///
/// Display: Uppercase /// Display: Uppercase
/// Category: text /// Category: text
/// Returns: string or content
#[func] #[func]
pub fn upper(args: &mut Args) -> SourceResult<Value> { pub fn upper(
case(Case::Upper, args) /// The text to convert to uppercase.
text: ToCase,
) -> Value {
case(text, Case::Upper)
} }
/// Change the case of text. /// Change the case of text.
fn case(case: Case, args: &mut Args) -> SourceResult<Value> { fn case(text: ToCase, case: Case) -> Value {
Ok(match args.expect("string or content")? { match text {
ToCase::Str(v) => Value::Str(case.apply(&v).into()), ToCase::Str(v) => Value::Str(case.apply(&v).into()),
ToCase::Content(v) => Value::Content(v.styled(TextNode::set_case(Some(case)))), ToCase::Content(v) => Value::Content(v.styled(TextNode::set_case(Some(case)))),
}) }
} }
/// A value whose case can be changed. /// A value whose case can be changed.
@ -302,16 +300,15 @@ cast_to_value! {
/// #lorem(40) /// #lorem(40)
/// ``` /// ```
/// ///
/// ## Parameters
/// - text: `Content` (positional, required)
/// The text to display to small capitals.
///
/// Display: Small Capitals /// Display: Small Capitals
/// Category: text /// Category: text
/// Returns: content
#[func] #[func]
pub fn smallcaps(args: &mut Args) -> SourceResult<Value> { pub fn smallcaps(
let body: Content = args.expect("content")?; /// The text to display to small capitals.
Ok(Value::Content(body.styled(TextNode::set_smallcaps(true)))) body: Content,
) -> Value {
Value::Content(body.styled(TextNode::set_smallcaps(true)))
} }
/// Create blind text. /// Create blind text.
@ -330,16 +327,13 @@ pub fn smallcaps(args: &mut Args) -> SourceResult<Value> {
/// #lorem(15) /// #lorem(15)
/// ``` /// ```
/// ///
/// ## Parameters
/// - words: `usize` (positional, required)
/// The length of the blind text in words.
///
/// - returns: string
///
/// Display: Blind Text /// Display: Blind Text
/// Category: text /// Category: text
/// Returns: string
#[func] #[func]
pub fn lorem(args: &mut Args) -> SourceResult<Value> { pub fn lorem(
let words: usize = args.expect("number of words")?; /// The length of the blind text in words.
Ok(Value::Str(lipsum::lipsum(words).replace("--", "").into())) words: usize,
) -> Value {
Value::Str(lipsum::lipsum(words).replace("--", "").into())
} }

View File

@ -38,10 +38,6 @@ use crate::prelude::*;
/// ]) /// ])
/// ``` /// ```
/// ///
/// ## Parameters
/// - body: `Content` (positional, required)
/// Content in which all text is styled according to the other arguments.
///
/// Display: Text /// Display: Text
/// Category: text /// Category: text
#[node(Construct)] #[node(Construct)]
@ -447,9 +443,13 @@ pub struct TextNode {
#[fold] #[fold]
pub features: FontFeatures, pub features: FontFeatures,
/// Content in which all text is styled according to the other arguments.
#[external]
#[required]
pub body: Content,
/// The text. /// The text.
#[internal] #[internal]
#[positional]
#[required] #[required]
pub text: EcoString, pub text: EcoString,

View File

@ -57,7 +57,6 @@ pub struct RawNode {
/// 1 + 2 + 3 + 4 + 5 /// 1 + 2 + 3 + 4 + 5
/// ``` /// ```
/// ```` /// ````
#[positional]
#[required] #[required]
pub text: EcoString, pub text: EcoString,

View File

@ -42,7 +42,6 @@ pub struct SubNode {
pub size: TextSize, pub size: TextSize,
/// The text to display in subscript. /// The text to display in subscript.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }
@ -110,7 +109,6 @@ pub struct SuperNode {
pub size: TextSize, pub size: TextSize,
/// The text to display in superscript. /// The text to display in superscript.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }

View File

@ -23,7 +23,6 @@ use crate::prelude::*;
#[node(Layout)] #[node(Layout)]
pub struct ImageNode { pub struct ImageNode {
/// Path to an image file. /// Path to an image file.
#[positional]
#[required] #[required]
#[parse( #[parse(
let Spanned { v: path, span } = let Spanned { v: path, span } =

View File

@ -21,15 +21,15 @@ pub struct LineNode {
/// The offset from `start` where the line ends. /// The offset from `start` where the line ends.
#[resolve] #[resolve]
pub end: Smart<Axes<Rel<Length>>>, pub end: Option<Axes<Rel<Length>>>,
/// The line's length. Mutually exclusive with `end`. /// The line's length. This is only respected if `end` is `none`.
#[resolve] #[resolve]
#[default(Abs::pt(30.0).into())] #[default(Abs::pt(30.0).into())]
pub length: Rel<Length>, pub length: Rel<Length>,
/// The angle at which the line points away from the origin. Mutually /// The angle at which the line points away from the origin. This is only
/// exclusive with `end`. /// respected if `end` is `none`.
pub angle: Angle, pub angle: Angle,
/// How to stroke the line. This can be: /// How to stroke the line. This can be:

View File

@ -176,15 +176,15 @@ impl Layout for RectNode {
/// ] /// ]
/// ``` /// ```
/// ///
/// ## Parameters
/// - size: `Smart<Length>` (named, settable)
/// The square's side length. This is mutually exclusive with `width` and
/// `height`.
///
/// Display: Square /// Display: Square
/// Category: visualize /// Category: visualize
#[node(Layout)] #[node(Layout)]
pub struct SquareNode { pub struct SquareNode {
/// The square's side length. This is mutually exclusive with `width` and
/// `height`.
#[external]
pub size: Smart<Length>,
/// The square's width. This is mutually exclusive with `size` and `height`. /// The square's width. This is mutually exclusive with `size` and `height`.
/// ///
/// In contrast to `size`, this can be relative to the parent container's /// In contrast to `size`, this can be relative to the parent container's
@ -367,15 +367,15 @@ impl Layout for EllipseNode {
/// ] /// ]
/// ``` /// ```
/// ///
/// ## Parameters
/// - radius: `Length` (named, settable)
/// The circle's radius. This is mutually exclusive with `width` and
/// `height`.
///
/// Display: Circle /// Display: Circle
/// Category: visualize /// Category: visualize
#[node(Layout)] #[node(Layout)]
pub struct CircleNode { pub struct CircleNode {
/// The circle's radius. This is mutually exclusive with `width` and
/// `height`.
#[external]
pub radius: Length,
/// The circle's width. This is mutually exclusive with `radius` and /// The circle's width. This is mutually exclusive with `radius` and
/// `height`. /// `height`.
/// ///

View File

@ -13,5 +13,5 @@ bench = false
[dependencies] [dependencies]
proc-macro2 = "1" proc-macro2 = "1"
quote = "1" quote = "1"
syn = { version = "1", features = ["full"] } syn = { version = "1", features = ["full", "extra-traits"] }
unscanny = "0.1" unscanny = "0.1"

View File

@ -1,167 +1,151 @@
use quote::ToTokens;
use super::*; use super::*;
/// Expand the `#[func]` macro. /// Expand the `#[func]` macro.
pub fn func(mut item: syn::Item) -> Result<TokenStream> { pub fn func(item: syn::ItemFn) -> Result<TokenStream> {
let attrs = match &mut item { let func = prepare(&item)?;
syn::Item::Struct(item) => &mut item.attrs, Ok(create(&func))
syn::Item::Fn(item) => &mut item.attrs, }
_ => bail!(item, "expected struct or fn"),
struct Func {
name: String,
display: String,
category: String,
docs: String,
vis: syn::Visibility,
ident: Ident,
params: Vec<Param>,
returns: Vec<String>,
body: syn::Block,
}
struct Param {
name: String,
docs: String,
external: bool,
named: bool,
variadic: bool,
default: Option<syn::Expr>,
ident: Ident,
ty: syn::Type,
}
fn prepare(item: &syn::ItemFn) -> Result<Func> {
let sig = &item.sig;
let mut params = vec![];
for input in &sig.inputs {
let syn::FnArg::Typed(typed) = input else {
bail!(input, "self is not allowed here");
}; };
let docs = documentation(&attrs); let syn::Pat::Ident(syn::PatIdent {
by_ref: None,
let mut lines: Vec<_> = docs.lines().collect(); mutability: None,
let Some(category) = lines.pop().and_then(|s| s.strip_prefix("Category: ")) else { ident,
bail!(item, "expected category"); ..
}; }) = &*typed.pat else {
let Some(display) = lines.pop().and_then(|s| s.strip_prefix("Display: ")) else { bail!(typed.pat, "expected identifier");
bail!(item, "expected display name");
}; };
let mut docs = lines.join("\n"); if sig.output.to_token_stream().to_string() != "-> Value" {
let (params, returns) = params(&mut docs)?; bail!(sig.output, "must return `Value`");
let docs = docs.trim();
attrs.retain(|attr| !attr.path.is_ident("doc"));
attrs.push(parse_quote! { #[doc = #docs] });
let info = quote! {
::typst::eval::FuncInfo {
name,
display: #display,
category: #category,
docs: #docs,
params: ::std::vec![#(#params),*],
returns: ::std::vec![#(#returns),*]
}
};
if let syn::Item::Fn(item) = &item {
let vis = &item.vis;
let ident = &item.sig.ident;
let s = ident.to_string();
let mut chars = s.trim_end_matches('_').chars();
let ty = quote::format_ident!(
"{}{}Func",
chars.next().unwrap().to_ascii_uppercase(),
chars.as_str()
);
let full = if item.sig.inputs.len() == 1 {
quote! { |_, args| #ident(args) }
} else {
quote! { #ident }
};
Ok(quote! {
#item
#[doc(hidden)]
#vis enum #ty {}
impl::typst::eval::FuncType for #ty {
fn create_func(name: &'static str) -> ::typst::eval::Func {
::typst::eval::Func::from_fn(#full, #info)
}
}
})
} else {
let (ident, generics) = match &item {
syn::Item::Struct(s) => (&s.ident, &s.generics),
syn::Item::Enum(s) => (&s.ident, &s.generics),
_ => bail!(item, "only structs, enums, and functions are supported"),
};
let (params, args, clause) = generics.split_for_impl();
Ok(quote! {
#item
impl #params ::typst::eval::FuncType for #ident #args #clause {
fn create_func(name: &'static str) -> ::typst::eval::Func {
::typst::eval::Func::from_node::<Self>(#info)
}
}
})
}
} }
/// Extract a section. let mut attrs = typed.attrs.clone();
fn section(docs: &mut String, title: &str, level: usize) -> Option<String> { params.push(Param {
let hashtags = "#".repeat(level); name: kebab_case(ident),
let needle = format!("\n{hashtags} {title}\n"); docs: documentation(&attrs),
let start = docs.find(&needle)?; external: has_attr(&mut attrs, "external"),
let rest = &docs[start..]; named: has_attr(&mut attrs, "named"),
let len = rest[1..] variadic: has_attr(&mut attrs, "variadic"),
.find("\n# ") default: parse_attr(&mut attrs, "default")?.map(|expr| {
.or_else(|| rest[1..].find("\n## ")) expr.unwrap_or_else(
.or_else(|| rest[1..].find("\n### ")) || parse_quote! { ::std::default::Default::default() },
.map(|x| 1 + x) )
.unwrap_or(rest.len()); }),
let end = start + len; ident: ident.clone(),
let section = docs[start + needle.len()..end].trim().to_owned(); ty: (*typed.ty).clone(),
docs.replace_range(start..end, ""); });
Some(section)
validate_attrs(&attrs)?;
} }
/// Parse the parameter section. let docs = documentation(&item.attrs);
fn params(docs: &mut String) -> Result<(Vec<TokenStream>, Vec<String>)> { let mut lines = docs.split("\n").collect();
let Some(section) = section(docs, "Parameters", 2) else { let returns = meta_line(&mut lines, "Returns")?
return Ok((vec![], vec![]));
};
let mut s = Scanner::new(&section);
let mut infos = vec![];
let mut returns = vec![];
while s.eat_if('-') {
let mut named = false;
let mut positional = false;
let mut required = false;
let mut variadic = false;
let mut settable = false;
s.eat_whitespace();
let name = s.eat_until(':');
s.expect(": ");
if name == "returns" {
returns = s
.eat_until('\n')
.split(" or ") .split(" or ")
.map(str::trim)
.map(Into::into) .map(Into::into)
.collect(); .collect();
s.eat_whitespace(); let category = meta_line(&mut lines, "Category")?.into();
continue; let display = meta_line(&mut lines, "Display")?.into();
let docs = lines.join("\n").trim().into();
let func = Func {
name: sig.ident.to_string().replace('_', ""),
display,
category,
docs,
vis: item.vis.clone(),
ident: sig.ident.clone(),
params,
returns,
body: (*item.block).clone(),
};
validate_attrs(&item.attrs)?;
Ok(func)
} }
s.expect('`'); fn create(func: &Func) -> TokenStream {
let ty: syn::Type = syn::parse_str(s.eat_until('`'))?; let Func {
s.expect('`'); name,
s.eat_whitespace(); display,
s.expect('('); category,
docs,
for part in s.eat_until(')').split(',').map(str::trim).filter(|s| !s.is_empty()) { vis,
match part { ident,
"positional" => positional = true, params,
"named" => named = true, returns,
"required" => required = true, body,
"variadic" => variadic = true, ..
"settable" => settable = true, } = func;
_ => bail!(callsite, "unknown parameter flag {:?}", part), let handlers = params.iter().filter(|param| !param.external).map(create_param_parser);
let params = params.iter().map(create_param_info);
quote! {
#[doc = #docs]
#vis fn #ident() -> ::typst::eval::NativeFunc {
::typst::eval::NativeFunc {
func: |vm, args| {
#(#handlers)*
#[allow(unreachable_code)]
Ok(#body)
},
info: ::typst::eval::Lazy::new(|| typst::eval::FuncInfo {
name: #name,
display: #display,
docs: #docs,
params: ::std::vec![#(#params),*],
returns: ::std::vec![#(#returns),*],
category: #category,
}),
}
}
} }
} }
if (!named && !positional) || (variadic && !positional) { /// Create a parameter info for a field.
bail!(callsite, "invalid combination of parameter flags"); fn create_param_info(param: &Param) -> TokenStream {
} let Param { name, docs, named, variadic, ty, default, .. } = param;
let positional = !named;
s.expect(')'); let required = default.is_none();
let ty = if *variadic {
let docs = dedent(s.eat_until("\n-").trim()); quote! { <#ty as ::typst::eval::Variadics>::Inner }
let docs = docs.trim(); } else {
quote! { #ty }
infos.push(quote! { };
quote! {
::typst::eval::ParamInfo { ::typst::eval::ParamInfo {
name: #name, name: #name,
docs: #docs, docs: #docs,
@ -172,12 +156,28 @@ fn params(docs: &mut String) -> Result<(Vec<TokenStream>, Vec<String>)> {
named: #named, named: #named,
variadic: #variadic, variadic: #variadic,
required: #required, required: #required,
settable: #settable, settable: false,
}
} }
});
s.eat_whitespace();
} }
Ok((infos, returns)) /// Create argument parsing code for a parameter.
fn create_param_parser(param: &Param) -> TokenStream {
let Param { name, ident, ty, .. } = param;
let mut value = if param.variadic {
quote! { args.all()? }
} else if param.named {
quote! { args.named(#name)? }
} else if param.default.is_some() {
quote! { args.eat()? }
} else {
quote! { args.expect(#name)? }
};
if let Some(default) = &param.default {
value = quote! { #value.unwrap_or_else(|| #default) }
}
quote! { let #ident: #ty = #value; }
} }

View File

@ -16,14 +16,13 @@ use syn::ext::IdentExt;
use syn::parse::{Parse, ParseStream, Parser}; use syn::parse::{Parse, ParseStream, Parser};
use syn::punctuated::Punctuated; use syn::punctuated::Punctuated;
use syn::{parse_quote, Ident, Result, Token}; use syn::{parse_quote, Ident, Result, Token};
use unscanny::Scanner;
use self::util::*; use self::util::*;
/// Implement `FuncType` for a type or function. /// Turns a function into a `NativeFunc`.
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn func(_: BoundaryStream, item: BoundaryStream) -> BoundaryStream { pub fn func(_: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
let item = syn::parse_macro_input!(item as syn::Item); let item = syn::parse_macro_input!(item as syn::ItemFn);
func::func(item).unwrap_or_else(|err| err.to_compile_error()).into() func::func(item).unwrap_or_else(|err| err.to_compile_error()).into()
} }

View File

@ -7,45 +7,35 @@ pub fn node(stream: TokenStream, body: syn::ItemStruct) -> Result<TokenStream> {
} }
struct Node { struct Node {
attrs: Vec<syn::Attribute>, name: String,
display: String,
category: String,
docs: String,
vis: syn::Visibility, vis: syn::Visibility,
ident: Ident, ident: Ident,
name: String,
capable: Vec<Ident>, capable: Vec<Ident>,
fields: Vec<Field>, fields: Vec<Field>,
} }
impl Node {
fn inherent(&self) -> impl Iterator<Item = &Field> {
self.fields.iter().filter(|field| field.inherent())
}
fn settable(&self) -> impl Iterator<Item = &Field> {
self.fields.iter().filter(|field| field.settable())
}
}
struct Field { struct Field {
attrs: Vec<syn::Attribute>,
vis: syn::Visibility,
name: String, name: String,
ident: Ident, docs: String,
ident_in: Ident,
with_ident: Ident,
set_ident: Ident,
internal: bool, internal: bool,
external: bool,
positional: bool, positional: bool,
required: bool, required: bool,
variadic: bool, variadic: bool,
fold: bool, fold: bool,
resolve: bool, resolve: bool,
parse: Option<FieldParser>, parse: Option<FieldParser>,
default: syn::Expr,
vis: syn::Visibility,
ident: Ident,
ident_in: Ident,
with_ident: Ident,
set_ident: Ident,
ty: syn::Type, ty: syn::Type,
output: syn::Type, output: syn::Type,
default: syn::Expr,
} }
impl Field { impl Field {
@ -87,34 +77,30 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> {
let mut attrs = field.attrs.clone(); let mut attrs = field.attrs.clone();
let variadic = has_attr(&mut attrs, "variadic"); let variadic = has_attr(&mut attrs, "variadic");
let required = has_attr(&mut attrs, "required") || variadic;
let positional = has_attr(&mut attrs, "positional") || required;
let mut field = Field { let mut field = Field {
vis: field.vis.clone(),
name: kebab_case(&ident), name: kebab_case(&ident),
ident: ident.clone(), docs: documentation(&attrs),
ident_in: Ident::new(&format!("{}_in", ident), ident.span()),
with_ident: Ident::new(&format!("with_{}", ident), ident.span()),
set_ident: Ident::new(&format!("set_{}", ident), ident.span()),
internal: has_attr(&mut attrs, "internal"), internal: has_attr(&mut attrs, "internal"),
positional: has_attr(&mut attrs, "positional") || variadic, external: has_attr(&mut attrs, "external"),
required: has_attr(&mut attrs, "required") || variadic, positional,
required,
variadic, variadic,
fold: has_attr(&mut attrs, "fold"), fold: has_attr(&mut attrs, "fold"),
resolve: has_attr(&mut attrs, "resolve"), resolve: has_attr(&mut attrs, "resolve"),
parse: parse_attr(&mut attrs, "parse")?.flatten(), parse: parse_attr(&mut attrs, "parse")?.flatten(),
ty: field.ty.clone(),
output: field.ty.clone(),
default: parse_attr(&mut attrs, "default")? default: parse_attr(&mut attrs, "default")?
.flatten() .flatten()
.unwrap_or_else(|| parse_quote! { ::std::default::Default::default() }), .unwrap_or_else(|| parse_quote! { ::std::default::Default::default() }),
vis: field.vis.clone(),
attrs: { ident: ident.clone(),
validate_attrs(&attrs)?; ident_in: Ident::new(&format!("{}_in", ident), ident.span()),
attrs with_ident: Ident::new(&format!("with_{}", ident), ident.span()),
}, set_ident: Ident::new(&format!("set_{}", ident), ident.span()),
ty: field.ty.clone(),
output: field.ty.clone(),
}; };
if field.required && (field.fold || field.resolve) { if field.required && (field.fold || field.resolve) {
@ -134,6 +120,7 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> {
field.output = parse_quote! { <#output as ::typst::model::Fold>::Output }; field.output = parse_quote! { <#output as ::typst::model::Fold>::Output };
} }
validate_attrs(&attrs)?;
fields.push(field); fields.push(field);
} }
@ -142,32 +129,39 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> {
.into_iter() .into_iter()
.collect(); .collect();
let attrs = body.attrs.clone(); let docs = documentation(&body.attrs);
Ok(Node { let mut lines = docs.split("\n").collect();
let category = meta_line(&mut lines, "Category")?.into();
let display = meta_line(&mut lines, "Display")?.into();
let docs = lines.join("\n").trim().into();
let node = Node {
name: body.ident.to_string().trim_end_matches("Node").to_lowercase(),
display,
category,
docs,
vis: body.vis.clone(), vis: body.vis.clone(),
ident: body.ident.clone(), ident: body.ident.clone(),
name: body.ident.to_string().trim_end_matches("Node").to_lowercase(),
capable, capable,
fields, fields,
attrs: { };
validate_attrs(&attrs)?;
attrs validate_attrs(&body.attrs)?;
}, Ok(node)
})
} }
/// Produce the node's definition. /// Produce the node's definition.
fn create(node: &Node) -> TokenStream { fn create(node: &Node) -> TokenStream {
let attrs = &node.attrs; let Node { vis, ident, docs, .. } = node;
let vis = &node.vis; let all = node.fields.iter().filter(|field| !field.external);
let ident = &node.ident; let settable = all.clone().filter(|field| field.settable());
// Inherent methods and functions. // Inherent methods and functions.
let new = create_new_func(node); let new = create_new_func(node);
let field_methods = node.fields.iter().map(create_field_method); let field_methods = all.clone().map(create_field_method);
let field_in_methods = node.settable().map(create_field_in_method); let field_in_methods = settable.clone().map(create_field_in_method);
let with_fields_methods = node.fields.iter().map(create_with_field_method); let with_fields_methods = all.map(create_with_field_method);
let field_style_methods = node.settable().map(create_set_field_method); let field_style_methods = settable.map(create_set_field_method);
// Trait implementations. // Trait implementations.
let construct = node let construct = node
@ -179,8 +173,7 @@ fn create(node: &Node) -> TokenStream {
let node = create_node_impl(node); let node = create_node_impl(node);
quote! { quote! {
#(#attrs)* #[doc = #docs]
#[::typst::eval::func]
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash)]
#[repr(transparent)] #[repr(transparent)]
#vis struct #ident(::typst::model::Content); #vis struct #ident(::typst::model::Content);
@ -212,10 +205,11 @@ fn create(node: &Node) -> TokenStream {
/// Create the `new` function for the node. /// Create the `new` function for the node.
fn create_new_func(node: &Node) -> TokenStream { fn create_new_func(node: &Node) -> TokenStream {
let params = node.inherent().map(|Field { ident, ty, .. }| { let relevant = node.fields.iter().filter(|field| !field.external && field.inherent());
let params = relevant.clone().map(|Field { ident, ty, .. }| {
quote! { #ident: #ty } quote! { #ident: #ty }
}); });
let builder_calls = node.inherent().map(|Field { ident, with_ident, .. }| { let builder_calls = relevant.map(|Field { ident, with_ident, .. }| {
quote! { .#with_ident(#ident) } quote! { .#with_ident(#ident) }
}); });
quote! { quote! {
@ -229,10 +223,10 @@ fn create_new_func(node: &Node) -> TokenStream {
/// Create an accessor methods for a field. /// Create an accessor methods for a field.
fn create_field_method(field: &Field) -> TokenStream { fn create_field_method(field: &Field) -> TokenStream {
let Field { attrs, vis, ident, name, output, .. } = field; let Field { vis, docs, ident, name, output, .. } = field;
if field.inherent() { if field.inherent() {
quote! { quote! {
#(#attrs)* #[doc = #docs]
#vis fn #ident(&self) -> #output { #vis fn #ident(&self) -> #output {
self.0.cast_required_field(#name) self.0.cast_required_field(#name)
} }
@ -241,7 +235,7 @@ fn create_field_method(field: &Field) -> TokenStream {
let access = let access =
create_style_chain_access(field, quote! { self.0.field(#name).cloned() }); create_style_chain_access(field, quote! { self.0.field(#name).cloned() });
quote! { quote! {
#(#attrs)* #[doc = #docs]
#vis fn #ident(&self, styles: ::typst::model::StyleChain) -> #output { #vis fn #ident(&self, styles: ::typst::model::StyleChain) -> #output {
#access #access
} }
@ -312,8 +306,7 @@ fn create_set_field_method(field: &Field) -> TokenStream {
/// Create the node's `Node` implementation. /// Create the node's `Node` implementation.
fn create_node_impl(node: &Node) -> TokenStream { fn create_node_impl(node: &Node) -> TokenStream {
let ident = &node.ident; let Node { ident, name, display, category, docs, .. } = node;
let name = &node.name;
let vtable_func = create_vtable_func(node); let vtable_func = create_vtable_func(node);
let infos = node let infos = node
.fields .fields
@ -322,10 +315,6 @@ fn create_node_impl(node: &Node) -> TokenStream {
.map(create_param_info); .map(create_param_info);
quote! { quote! {
impl ::typst::model::Node for #ident { impl ::typst::model::Node for #ident {
fn pack(self) -> ::typst::model::Content {
self.0
}
fn id() -> ::typst::model::NodeId { fn id() -> ::typst::model::NodeId {
static META: ::typst::model::NodeMeta = ::typst::model::NodeMeta { static META: ::typst::model::NodeMeta = ::typst::model::NodeMeta {
name: #name, name: #name,
@ -334,8 +323,24 @@ fn create_node_impl(node: &Node) -> TokenStream {
::typst::model::NodeId::from_meta(&META) ::typst::model::NodeId::from_meta(&META)
} }
fn params() -> ::std::vec::Vec<::typst::eval::ParamInfo> { fn pack(self) -> ::typst::model::Content {
::std::vec![#(#infos),*] self.0
}
fn func() -> ::typst::eval::NodeFunc {
::typst::eval::NodeFunc {
id: Self::id(),
construct: <Self as ::typst::model::Construct>::construct,
set: <Self as ::typst::model::Set>::set,
info: ::typst::eval::Lazy::new(|| typst::eval::FuncInfo {
name: #name,
display: #display,
docs: #docs,
params: ::std::vec![#(#infos),*],
returns: ::std::vec!["content"],
category: #category,
}),
}
} }
} }
} }
@ -366,11 +371,14 @@ fn create_vtable_func(node: &Node) -> TokenStream {
/// Create a parameter info for a field. /// Create a parameter info for a field.
fn create_param_info(field: &Field) -> TokenStream { fn create_param_info(field: &Field) -> TokenStream {
let Field { name, positional, variadic, required, ty, .. } = field; let Field { name, docs, positional, variadic, required, ty, .. } = field;
let named = !positional; let named = !positional;
let settable = field.settable(); let settable = field.settable();
let docs = documentation(&field.attrs); let ty = if *variadic {
let docs = docs.trim(); quote! { <#ty as ::typst::eval::Variadics>::Inner }
} else {
quote! { #ty }
};
quote! { quote! {
::typst::eval::ParamInfo { ::typst::eval::ParamInfo {
name: #name, name: #name,
@ -393,7 +401,7 @@ fn create_construct_impl(node: &Node) -> TokenStream {
let handlers = node let handlers = node
.fields .fields
.iter() .iter()
.filter(|field| !field.internal || field.parse.is_some()) .filter(|field| !field.external && (!field.internal || field.parse.is_some()))
.map(|field| { .map(|field| {
let with_ident = &field.with_ident; let with_ident = &field.with_ident;
let (prefix, value) = create_field_parser(field); let (prefix, value) = create_field_parser(field);
@ -432,7 +440,11 @@ fn create_set_impl(node: &Node) -> TokenStream {
let handlers = node let handlers = node
.fields .fields
.iter() .iter()
.filter(|field| field.settable() && (!field.internal || field.parse.is_some())) .filter(|field| {
!field.external
&& field.settable()
&& (!field.internal || field.parse.is_some())
})
.map(|field| { .map(|field| {
let set_ident = &field.set_ident; let set_ident = &field.set_ident;
let (prefix, value) = create_field_parser(field); let (prefix, value) = create_field_parser(field);
@ -459,11 +471,11 @@ fn create_set_impl(node: &Node) -> TokenStream {
/// Create argument parsing code for a field. /// Create argument parsing code for a field.
fn create_field_parser(field: &Field) -> (TokenStream, TokenStream) { fn create_field_parser(field: &Field) -> (TokenStream, TokenStream) {
let name = &field.name;
if let Some(FieldParser { prefix, expr }) = &field.parse { if let Some(FieldParser { prefix, expr }) = &field.parse {
return (quote! { #(#prefix);* }, quote! { #expr }); return (quote! { #(#prefix);* }, quote! { #expr });
} }
let name = &field.name;
let value = if field.variadic { let value = if field.variadic {
quote! { args.all()? } quote! { args.all()? }
} else if field.required { } else if field.required {

View File

@ -58,14 +58,6 @@ pub fn kebab_case(name: &Ident) -> String {
name.to_string().to_lowercase().replace('_', "-") name.to_string().to_lowercase().replace('_', "-")
} }
/// Dedent documentation text.
pub fn dedent(text: &str) -> String {
text.lines()
.map(|s| s.strip_prefix(" ").unwrap_or(s))
.collect::<Vec<_>>()
.join("\n")
}
/// Extract documentation comments from an attribute list. /// Extract documentation comments from an attribute list.
pub fn documentation(attrs: &[syn::Attribute]) -> String { pub fn documentation(attrs: &[syn::Attribute]) -> String {
let mut doc = String::new(); let mut doc = String::new();
@ -86,3 +78,11 @@ pub fn documentation(attrs: &[syn::Attribute]) -> String {
doc.trim().into() doc.trim().into()
} }
/// Extract a line of metadata from documentation.
pub fn meta_line<'a>(lines: &mut Vec<&'a str>, key: &str) -> Result<&'a str> {
match lines.pop().and_then(|line| line.strip_prefix(&format!("{key}:"))) {
Some(value) => Ok(value.trim()),
None => bail!(callsite, "missing metadata key: {}", key),
}
}

View File

@ -1,6 +1,6 @@
pub use typst_macros::{cast_from_value, cast_to_value}; pub use typst_macros::{cast_from_value, cast_to_value};
use std::num::NonZeroUsize; use std::num::{NonZeroI64, NonZeroUsize};
use std::ops::Add; use std::ops::Add;
use ecow::EcoString; use ecow::EcoString;
@ -127,6 +127,20 @@ cast_to_value! {
v: NonZeroUsize => Value::Int(v.get() as i64) v: NonZeroUsize => Value::Int(v.get() as i64)
} }
cast_from_value! {
NonZeroI64,
int: i64 => int.try_into()
.map_err(|_| if int <= 0 {
"number must be positive"
} else {
"number too large"
})?,
}
cast_to_value! {
v: NonZeroI64 => Value::Int(v.get())
}
cast_from_value! { cast_from_value! {
char, char,
string: Str => { string: Str => {
@ -211,6 +225,16 @@ impl<T: Into<Value>> From<Vec<T>> for Value {
} }
} }
/// A container for a variadic argument.
pub trait Variadics {
/// The contained type.
type Inner;
}
impl<T> Variadics for Vec<T> {
type Inner = T;
}
/// Describes a possible value for a cast. /// Describes a possible value for a cast.
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash)]
pub enum CastInfo { pub enum CastInfo {

View File

@ -6,10 +6,14 @@ use std::sync::Arc;
use comemo::{Prehashed, Track, Tracked, TrackedMut}; use comemo::{Prehashed, Track, Tracked, TrackedMut};
use ecow::EcoString; use ecow::EcoString;
use once_cell::sync::Lazy;
use super::{Args, CastInfo, Dict, Eval, Flow, Route, Scope, Scopes, Tracer, Value, Vm}; use super::{
cast_to_value, Args, CastInfo, Dict, Eval, Flow, Route, Scope, Scopes, Tracer, Value,
Vm,
};
use crate::diag::{bail, SourceResult, StrResult}; use crate::diag::{bail, SourceResult, StrResult};
use crate::model::{Node, NodeId, Selector, StyleMap}; use crate::model::{Content, NodeId, Selector, StyleMap};
use crate::syntax::ast::{self, AstNode, Expr}; use crate::syntax::ast::{self, AstNode, Expr};
use crate::syntax::{SourceId, Span, SyntaxNode}; use crate::syntax::{SourceId, Span, SyntaxNode};
use crate::util::hash128; use crate::util::hash128;
@ -22,8 +26,10 @@ pub struct Func(Arc<Prehashed<Repr>>, Span);
/// The different kinds of function representations. /// The different kinds of function representations.
#[derive(Hash)] #[derive(Hash)]
enum Repr { enum Repr {
/// A native rust function. /// A native Rust function.
Native(Native), Native(NativeFunc),
/// A function for a node.
Node(NodeFunc),
/// A user-defined closure. /// A user-defined closure.
Closure(Closure), Closure(Closure),
/// A nested function with pre-applied arguments. /// A nested function with pre-applied arguments.
@ -31,50 +37,11 @@ enum Repr {
} }
impl Func { impl Func {
/// Create a new function from a type that can be turned into a function.
pub fn from_type<T: FuncType>(name: &'static str) -> Self {
T::create_func(name)
}
/// Create a new function from a native rust function.
pub fn from_fn(
func: fn(&Vm, &mut Args) -> SourceResult<Value>,
info: FuncInfo,
) -> Self {
Self(
Arc::new(Prehashed::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::params());
Self(
Arc::new(Prehashed::new(Repr::Native(Native {
func: |vm, args| T::construct(vm, args).map(Value::Content),
set: Some(T::set),
node: Some(NodeId::of::<T>()),
info,
}))),
Span::detached(),
)
}
/// Create a new function from a closure.
pub(super) fn from_closure(closure: Closure, span: Span) -> Self {
Self(Arc::new(Prehashed::new(Repr::Closure(closure))), span)
}
/// The name of the function. /// The name of the function.
pub fn name(&self) -> Option<&str> { pub fn name(&self) -> Option<&str> {
match &**self.0 { match &**self.0 {
Repr::Native(native) => Some(native.info.name), Repr::Native(native) => Some(native.info.name),
Repr::Node(node) => Some(node.info.name),
Repr::Closure(closure) => closure.name.as_deref(), Repr::Closure(closure) => closure.name.as_deref(),
Repr::With(func, _) => func.name(), Repr::With(func, _) => func.name(),
} }
@ -84,6 +51,7 @@ impl Func {
pub fn info(&self) -> Option<&FuncInfo> { pub fn info(&self) -> Option<&FuncInfo> {
match &**self.0 { match &**self.0 {
Repr::Native(native) => Some(&native.info), Repr::Native(native) => Some(&native.info),
Repr::Node(node) => Some(&node.info),
Repr::With(func, _) => func.info(), Repr::With(func, _) => func.info(),
_ => None, _ => None,
} }
@ -119,6 +87,11 @@ impl Func {
args.finish()?; args.finish()?;
Ok(value) Ok(value)
} }
Repr::Node(node) => {
let value = (node.construct)(vm, &mut args)?;
args.finish()?;
Ok(Value::Content(value))
}
Repr::Closure(closure) => { Repr::Closure(closure) => {
// Determine the route inside the closure. // Determine the route inside the closure.
let fresh = Route::new(closure.location); let fresh = Route::new(closure.location);
@ -172,8 +145,8 @@ impl Func {
/// Execute the function's set rule and return the resulting style map. /// Execute the function's set rule and return the resulting style map.
pub fn set(&self, mut args: Args) -> SourceResult<StyleMap> { pub fn set(&self, mut args: Args) -> SourceResult<StyleMap> {
Ok(match &**self.0 { Ok(match &**self.0 {
Repr::Native(Native { set: Some(set), .. }) => { Repr::Node(node) => {
let styles = set(&mut args)?; let styles = (node.set)(&mut args)?;
args.finish()?; args.finish()?;
styles styles
} }
@ -183,13 +156,13 @@ impl Func {
/// Create a selector for this function's node type. /// Create a selector for this function's node type.
pub fn select(&self, fields: Option<Dict>) -> StrResult<Selector> { pub fn select(&self, fields: Option<Dict>) -> StrResult<Selector> {
match **self.0 { match &**self.0 {
Repr::Native(Native { node: Some(id), .. }) => { Repr::Node(node) => {
if id == item!(text_id) { if node.id == item!(text_id) {
Err("to select text, please use a string or regex instead")?; Err("to select text, please use a string or regex instead")?;
} }
Ok(Selector::Node(id, fields)) Ok(Selector::Node(node.id, fields))
} }
_ => Err("this function is not selectable")?, _ => Err("this function is not selectable")?,
} }
@ -211,32 +184,75 @@ impl PartialEq for Func {
} }
} }
/// Types that can be turned into functions. impl From<Repr> for Func {
pub trait FuncType { fn from(repr: Repr) -> Self {
/// Create a function with the given name from this type. Self(Arc::new(Prehashed::new(repr)), Span::detached())
fn create_func(name: &'static str) -> Func; }
} }
/// A function defined by a native rust function or node. /// A native Rust function.
struct Native { pub struct NativeFunc {
/// The function pointer. /// The function's implementation.
func: fn(&Vm, &mut Args) -> SourceResult<Value>, pub func: fn(&Vm, &mut Args) -> SourceResult<Value>,
/// The set rule. /// Details about the function.
set: Option<fn(&mut Args) -> SourceResult<StyleMap>>, pub info: Lazy<FuncInfo>,
/// The id of the node to customize with this function's show rule.
node: Option<NodeId>,
/// Documentation of the function.
info: FuncInfo,
} }
impl Hash for Native { impl Hash for NativeFunc {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
(self.func as usize).hash(state); (self.func as usize).hash(state);
self.set.map(|set| set as usize).hash(state);
self.node.hash(state);
} }
} }
impl From<NativeFunc> for Func {
fn from(native: NativeFunc) -> Self {
Repr::Native(native).into()
}
}
cast_to_value! {
v: NativeFunc => Value::Func(v.into())
}
impl<F> From<F> for Value
where
F: Fn() -> NativeFunc,
{
fn from(f: F) -> Self {
f().into()
}
}
/// A function defined by a native Rust node.
pub struct NodeFunc {
/// The node's id.
pub id: NodeId,
/// The node's constructor.
pub construct: fn(&Vm, &mut Args) -> SourceResult<Content>,
/// The node's set rule.
pub set: fn(&mut Args) -> SourceResult<StyleMap>,
/// Details about the function.
pub info: Lazy<FuncInfo>,
}
impl Hash for NodeFunc {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
(self.construct as usize).hash(state);
(self.set as usize).hash(state);
}
}
impl From<NodeFunc> for Func {
fn from(node: NodeFunc) -> Self {
Repr::Node(node).into()
}
}
cast_to_value! {
v: NodeFunc => Value::Func(v.into())
}
/// Details about a function. /// Details about a function.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct FuncInfo { pub struct FuncInfo {
@ -375,6 +391,16 @@ impl Closure {
} }
} }
impl From<Closure> for Func {
fn from(closure: Closure) -> Self {
Repr::Closure(closure).into()
}
}
cast_to_value! {
v: Closure => Value::Func(v.into())
}
/// A visitor that determines which variables to capture for a closure. /// A visitor that determines which variables to capture for a closure.
pub(super) struct CapturesVisitor<'a> { pub(super) struct CapturesVisitor<'a> {
external: &'a Scopes<'a>, external: &'a Scopes<'a>,

View File

@ -20,6 +20,9 @@ mod ops;
mod scope; mod scope;
mod symbol; mod symbol;
#[doc(hidden)]
pub use once_cell::sync::Lazy;
pub use self::args::*; pub use self::args::*;
pub use self::array::*; pub use self::array::*;
pub use self::cast::*; pub use self::cast::*;
@ -1152,7 +1155,7 @@ impl Eval for ast::Closure {
body: self.body(), body: self.body(),
}; };
Ok(Value::Func(Func::from_closure(closure, self.span()))) Ok(Value::Func(Func::from(closure).spanned(self.span())))
} }
} }

View File

@ -4,7 +4,7 @@ use std::hash::Hash;
use ecow::EcoString; use ecow::EcoString;
use super::{Func, FuncType, Library, Value}; use super::{Library, Value};
use crate::diag::StrResult; use crate::diag::StrResult;
/// A stack of scopes. /// A stack of scopes.
@ -96,11 +96,6 @@ impl Scope {
self.0.insert(name, Slot::new(value.into(), Kind::Normal)); self.0.insert(name, Slot::new(value.into(), Kind::Normal));
} }
/// Define a function through a native rust function.
pub fn def_func<T: FuncType>(&mut self, name: &'static str) {
self.define(name, Func::from_type::<T>(name));
}
/// Define a captured, immutable binding. /// Define a captured, immutable binding.
pub fn define_captured( pub fn define_captured(
&mut self, &mut self,

View File

@ -627,10 +627,6 @@ fn param_completions(
} }
} }
if callee.as_str() == "text" {
ctx.font_completions();
}
if ctx.before.ends_with(',') { if ctx.before.ends_with(',') {
ctx.enrich(" ", ""); ctx.enrich(" ", "");
} }
@ -653,7 +649,7 @@ fn named_param_value_completions(
ctx.cast_completions(&param.cast); ctx.cast_completions(&param.cast);
if callee.as_str() == "text" && name == "family" { if callee.as_str() == "text" && name == "font" {
ctx.font_completions(); ctx.font_completions();
} }

View File

@ -9,7 +9,7 @@ use ecow::{EcoString, EcoVec};
use super::{node, Guard, Recipe, Style, StyleMap}; use super::{node, Guard, Recipe, Style, StyleMap};
use crate::diag::{SourceResult, StrResult}; use crate::diag::{SourceResult, StrResult};
use crate::eval::{cast_from_value, Args, Cast, ParamInfo, Value, Vm}; use crate::eval::{cast_from_value, Args, Cast, NodeFunc, Value, Vm};
use crate::syntax::Span; use crate::syntax::Span;
use crate::World; use crate::World;
@ -330,12 +330,10 @@ impl Sum for Content {
#[node] #[node]
pub struct StyledNode { pub struct StyledNode {
/// The styles. /// The styles.
#[positional]
#[required] #[required]
pub map: StyleMap, pub map: StyleMap,
/// The styled content. /// The styled content.
#[positional]
#[required] #[required]
pub body: Content, pub body: Content,
} }
@ -353,7 +351,6 @@ cast_from_value! {
/// Category: special /// Category: special
#[node] #[node]
pub struct SequenceNode { pub struct SequenceNode {
#[positional]
#[variadic] #[variadic]
pub children: Vec<Content>, pub children: Vec<Content>,
} }
@ -370,14 +367,14 @@ impl Debug for Label {
/// A constructable, stylable content node. /// A constructable, stylable content node.
pub trait Node: Construct + Set + Sized + 'static { pub trait Node: Construct + Set + Sized + 'static {
/// Pack a node into type-erased content.
fn pack(self) -> Content;
/// The node's ID. /// The node's ID.
fn id() -> NodeId; fn id() -> NodeId;
/// List the fields of the node. /// Pack a node into type-erased content.
fn params() -> Vec<ParamInfo>; fn pack(self) -> Content;
/// The node's function.
fn func() -> NodeFunc;
} }
/// A unique identifier for a node. /// A unique identifier for a node.
@ -425,6 +422,7 @@ pub struct NodeMeta {
pub vtable: fn(of: TypeId) -> Option<*const ()>, pub vtable: fn(of: TypeId) -> Option<*const ()>,
} }
/// A node's constructor function.
pub trait Construct { pub trait Construct {
/// Construct a node from the arguments. /// Construct a node from the arguments.
/// ///
@ -433,6 +431,7 @@ pub trait Construct {
fn construct(vm: &Vm, args: &mut Args) -> SourceResult<Content>; fn construct(vm: &Vm, args: &mut Args) -> SourceResult<Content>;
} }
/// A node's set rule.
pub trait Set { pub trait Set {
/// Parse relevant arguments into style properties for this node. /// Parse relevant arguments into style properties for this node.
fn set(args: &mut Args) -> SourceResult<StyleMap>; fn set(args: &mut Args) -> SourceResult<StyleMap>;

View File

@ -11,6 +11,4 @@ pub use self::realize::*;
pub use self::styles::*; pub use self::styles::*;
pub use self::typeset::*; pub use self::typeset::*;
#[doc(hidden)]
pub use once_cell;
pub use typst_macros::node; pub use typst_macros::node;

View File

@ -86,7 +86,7 @@ impl Debug for StyleMap {
} }
} }
/// A single style property, recipe or barrier. /// A single style property or recipe.
#[derive(Clone, Hash)] #[derive(Clone, Hash)]
pub enum Style { pub enum Style {
/// A style property originating from a set rule or constructor. /// A style property originating from a set rule or constructor.

View File

@ -11,7 +11,7 @@ use comemo::{Prehashed, Track};
use elsa::FrozenVec; use elsa::FrozenVec;
use once_cell::unsync::OnceCell; use once_cell::unsync::OnceCell;
use tiny_skia as sk; use tiny_skia as sk;
use typst::diag::{bail, FileError, FileResult, SourceResult}; use typst::diag::{bail, FileError, FileResult};
use typst::doc::{Document, Element, Frame, Meta}; use typst::doc::{Document, Element, Frame, Meta};
use typst::eval::{func, Library, Value}; use typst::eval::{func, Library, Value};
use typst::font::{Font, FontBook}; use typst::font::{Font, FontBook};
@ -148,29 +148,29 @@ impl Args {
fn library() -> Library { fn library() -> Library {
/// Display: Test /// Display: Test
/// Category: test /// Category: test
/// Returns:
#[func] #[func]
fn test(args: &mut typst::eval::Args) -> SourceResult<Value> { fn test(lhs: Value, rhs: Value) -> Value {
let lhs = args.expect::<Value>("left-hand side")?;
let rhs = args.expect::<Value>("right-hand side")?;
if lhs != rhs { if lhs != rhs {
bail!(args.span, "Assertion failed: {:?} != {:?}", lhs, rhs,); bail!(args.span, "Assertion failed: {:?} != {:?}", lhs, rhs,);
} }
Ok(Value::None) Value::None
} }
/// Display: Print /// Display: Print
/// Category: test /// Category: test
/// Returns:
#[func] #[func]
fn print(args: &mut typst::eval::Args) -> SourceResult<Value> { fn print(#[variadic] values: Vec<Value>) -> Value {
print!("> "); print!("> ");
for (i, value) in args.all::<Value>()?.into_iter().enumerate() { for (i, value) in values.into_iter().enumerate() {
if i > 0 { if i > 0 {
print!(", ") print!(", ")
} }
print!("{value:?}"); print!("{value:?}");
} }
println!(); println!();
Ok(Value::None) Value::None
} }
let mut lib = typst_library::build(); let mut lib = typst_library::build();
@ -187,8 +187,8 @@ fn library() -> Library {
lib.styles.set(TextNode::set_size(TextSize(Abs::pt(10.0).into()))); lib.styles.set(TextNode::set_size(TextSize(Abs::pt(10.0).into())));
// Hook up helpers into the global scope. // Hook up helpers into the global scope.
lib.global.scope_mut().def_func::<TestFunc>("test"); lib.global.scope_mut().define("test", test);
lib.global.scope_mut().def_func::<PrintFunc>("print"); lib.global.scope_mut().define("print", print);
lib.global lib.global
.scope_mut() .scope_mut()
.define("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF)); .define("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF));

View File

@ -77,7 +77,7 @@
#test(calc.min("hi"), "hi") #test(calc.min("hi"), "hi")
--- ---
// Error: 10-12 missing argument: value // Error: 10-12 expected at least one value
#calc.min() #calc.min()
--- ---
@ -109,5 +109,5 @@
#range(4, step: "one") #range(4, step: "one")
--- ---
// Error: 18-19 step must not be zero // Error: 18-19 number must be positive
#range(10, step: 0) #range(10, step: 0)

View File

@ -28,5 +28,5 @@
} }
--- ---
// Error: 7-9 missing argument: number of words // Error: 7-9 missing argument: words
#lorem() #lorem()