diff --git a/library/src/compute/calc.rs b/library/src/compute/calc.rs index d4a4bcf6f..25df817d1 100644 --- a/library/src/compute/calc.rs +++ b/library/src/compute/calc.rs @@ -10,28 +10,28 @@ use crate::prelude::*; /// A module with computational functions. pub fn module() -> Module { let mut scope = Scope::new(); - scope.def_func::("abs"); - scope.def_func::("pow"); - scope.def_func::("sqrt"); - scope.def_func::("sin"); - scope.def_func::("cos"); - scope.def_func::("tan"); - scope.def_func::("asin"); - scope.def_func::("acos"); - scope.def_func::("atan"); - scope.def_func::("sinh"); - scope.def_func::("cosh"); - scope.def_func::("tanh"); - scope.def_func::("log"); - scope.def_func::("floor"); - scope.def_func::("ceil"); - scope.def_func::("round"); - scope.def_func::("clamp"); - scope.def_func::("min"); - scope.def_func::("max"); - scope.def_func::("even"); - scope.def_func::("odd"); - scope.def_func::("mod"); + scope.define("abs", abs); + scope.define("pow", pow); + scope.define("sqrt", sqrt); + scope.define("sin", sin); + scope.define("cos", cos); + scope.define("tan", tan); + scope.define("asin", asin); + scope.define("acos", acos); + scope.define("atan", atan); + scope.define("sinh", sinh); + scope.define("cosh", cosh); + scope.define("tanh", tanh); + scope.define("log", log); + scope.define("floor", floor); + scope.define("ceil", ceil); + scope.define("round", round); + scope.define("clamp", clamp); + scope.define("min", min); + scope.define("max", max); + scope.define("even", even); + scope.define("odd", odd); + scope.define("mod", mod_); scope.define("inf", Value::Float(f64::INFINITY)); scope.define("nan", Value::Float(f64::NAN)); scope.define("pi", Value::Float(std::f64::consts::PI)); @@ -48,15 +48,15 @@ pub fn module() -> Module { /// #calc.abs(2fr) /// ``` /// -/// ## Parameters -/// - value: `ToAbs` (positional, required) -/// The value whose absolute value to calculate. -/// /// Display: Absolute /// Category: calculate +/// Returns: any #[func] -pub fn abs(args: &mut Args) -> SourceResult { - Ok(args.expect::("value")?.0) +pub fn abs( + /// The value whose absolute value to calculate. + value: ToAbs, +) -> Value { + value.0 } /// A value of which the absolute value can be taken. @@ -80,27 +80,27 @@ cast_from_value! { /// #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 /// Category: calculate +/// Returns: integer or float #[func] -pub fn pow(args: &mut Args) -> SourceResult { - let base = args.expect::("base")?; - let exponent = args - .expect::>("exponent") - .and_then(|n| match n.v { - Num::Int(i) if i > u32::MAX as i64 => bail!(n.span, "exponent too large"), - Num::Int(i) if i >= 0 => Ok(n), - Num::Float(f) if f >= 0.0 => Ok(n), - _ => bail!(n.span, "exponent must be non-negative"), - })? - .v; - Ok(base.apply2(exponent, |a, b| a.pow(b as u32), f64::powf)) +pub fn pow( + /// The base of the power. + base: Num, + /// The exponent of the power. Must be non-negative. + exponent: Spanned, +) -> Value { + let exponent = match exponent.v { + Num::Int(i) if i > u32::MAX as i64 => { + bail!(exponent.span, "exponent too large"); + } + Num::Int(0..) => exponent.v, + 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. @@ -111,19 +111,18 @@ pub fn pow(args: &mut Args) -> SourceResult { /// #calc.sqrt(2.5) /// ``` /// -/// ## Parameters -/// - value: `Num` (positional, required) -/// The number whose square root to calculate. Must be non-negative. -/// /// Display: Square Root /// Category: calculate +/// Returns: float #[func] -pub fn sqrt(args: &mut Args) -> SourceResult { - let value = args.expect::>("value")?; +pub fn sqrt( + /// The number whose square root to calculate. Must be non-negative. + value: Spanned, +) -> Value { if value.v.float() < 0.0 { 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. @@ -138,20 +137,19 @@ pub fn sqrt(args: &mut Args) -> SourceResult { /// #calc.sin(90deg) /// ``` /// -/// ## Parameters -/// - angle: `AngleLike` (positional, required) -/// The angle whose sine to calculate. -/// /// Display: Sine /// Category: calculate +/// Returns: float #[func] -pub fn sin(args: &mut Args) -> SourceResult { - let arg = args.expect::("angle")?; - Ok(Value::Float(match arg { +pub fn sin( + /// The angle whose sine to calculate. + angle: AngleLike, +) -> Value { + Value::Float(match angle { AngleLike::Angle(a) => a.sin(), AngleLike::Int(n) => (n as f64).sin(), AngleLike::Float(n) => n.sin(), - })) + }) } /// Calculate the cosine of an angle. @@ -161,25 +159,24 @@ pub fn sin(args: &mut Args) -> SourceResult { /// /// ## Example /// ```example -/// #calc.cos(90deg) +/// #calc.cos(90deg) \ /// #calc.cos(1.5) \ /// #calc.cos(90deg) /// ``` /// -/// ## Parameters -/// - angle: `AngleLike` (positional, required) -/// The angle whose cosine to calculate. -/// /// Display: Cosine /// Category: calculate +/// Returns: float #[func] -pub fn cos(args: &mut Args) -> SourceResult { - let arg = args.expect::("angle")?; - Ok(Value::Float(match arg { +pub fn cos( + /// The angle whose cosine to calculate. + angle: AngleLike, +) -> Value { + Value::Float(match angle { AngleLike::Angle(a) => a.cos(), AngleLike::Int(n) => (n as f64).cos(), AngleLike::Float(n) => n.cos(), - })) + }) } /// Calculate the tangent of an angle. @@ -193,20 +190,19 @@ pub fn cos(args: &mut Args) -> SourceResult { /// #calc.tan(90deg) /// ``` /// -/// ## Parameters -/// - angle: `AngleLike` (positional, required) -/// The angle whose tangent to calculate. -/// /// Display: Tangent /// Category: calculate +/// Returns: float #[func] -pub fn tan(args: &mut Args) -> SourceResult { - let arg = args.expect::("angle")?; - Ok(Value::Float(match arg { +pub fn tan( + /// The angle whose tangent to calculate. + angle: AngleLike, +) -> Value { + Value::Float(match angle { AngleLike::Angle(a) => a.tan(), AngleLike::Int(n) => (n as f64).tan(), AngleLike::Float(n) => n.tan(), - })) + }) } /// Calculate the arcsine of a number. @@ -217,20 +213,19 @@ pub fn tan(args: &mut Args) -> SourceResult { /// #calc.asin(1) /// ``` /// -/// ## Parameters -/// - value: `Num` (positional, required) -/// The number whose arcsine to calculate. Must be between -1 and 1. -/// /// Display: Arcsine /// Category: calculate +/// Returns: angle #[func] -pub fn asin(args: &mut Args) -> SourceResult { - let Spanned { v, span } = args.expect::>("value")?; - let val = v.float(); +pub fn asin( + /// The number whose arcsine to calculate. Must be between -1 and 1. + value: Spanned, +) -> Value { + let val = value.v.float(); 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. @@ -241,20 +236,19 @@ pub fn asin(args: &mut Args) -> SourceResult { /// #calc.acos(1) /// ``` /// -/// ## Parameters -/// - value: `Num` (positional, required) -/// The number whose arccosine to calculate. Must be between -1 and 1. -/// /// Display: Arccosine /// Category: calculate +/// Returns: angle #[func] -pub fn acos(args: &mut Args) -> SourceResult { - let Spanned { v, span } = args.expect::>("value")?; - let val = v.float(); +pub fn acos( + /// The number whose arcsine to calculate. Must be between -1 and 1. + value: Spanned, +) -> Value { + let val = value.v.float(); 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. @@ -265,16 +259,15 @@ pub fn acos(args: &mut Args) -> SourceResult { /// #calc.atan(1) /// ``` /// -/// ## Parameters -/// - value: `Num` (positional, required) -/// The number whose arctangent to calculate. -/// /// Display: Arctangent /// Category: calculate +/// Returns: angle #[func] -pub fn atan(args: &mut Args) -> SourceResult { - let value = args.expect::("value")?; - Ok(Value::Angle(Angle::rad(value.float().atan()))) +pub fn atan( + /// The number whose arctangent to calculate. + value: Num, +) -> Value { + Value::Angle(Angle::rad(value.float().atan())) } /// Calculate the hyperbolic sine of an angle. @@ -287,20 +280,19 @@ pub fn atan(args: &mut Args) -> SourceResult { /// #calc.sinh(45deg) /// ``` /// -/// ## Parameters -/// - angle: `AngleLike` (positional, required) -/// The angle whose hyperbolic sine to calculate. -/// /// Display: Hyperbolic sine /// Category: calculate +/// Returns: float #[func] -pub fn sinh(args: &mut Args) -> SourceResult { - let arg = args.expect::("angle")?; - Ok(Value::Float(match arg { +pub fn sinh( + /// The angle whose hyperbolic sine to calculate. + angle: AngleLike, +) -> Value { + Value::Float(match angle { AngleLike::Angle(a) => a.to_rad().sinh(), AngleLike::Int(n) => (n as f64).sinh(), AngleLike::Float(n) => n.sinh(), - })) + }) } /// Calculate the hyperbolic cosine of an angle. @@ -313,20 +305,19 @@ pub fn sinh(args: &mut Args) -> SourceResult { /// #calc.cosh(45deg) /// ``` /// -/// ## Parameters -/// - angle: `AngleLike` (positional, required) -/// The angle whose hyperbolic cosine to calculate. -/// /// Display: Hyperbolic cosine /// Category: calculate +/// Returns: float #[func] -pub fn cosh(args: &mut Args) -> SourceResult { - let arg = args.expect::("angle")?; - Ok(Value::Float(match arg { +pub fn cosh( + /// The angle whose hyperbolic cosine to calculate. + angle: AngleLike, +) -> Value { + Value::Float(match angle { AngleLike::Angle(a) => a.to_rad().cosh(), AngleLike::Int(n) => (n as f64).cosh(), AngleLike::Float(n) => n.cosh(), - })) + }) } /// Calculate the hyperbolic tangent of an angle. @@ -339,20 +330,19 @@ pub fn cosh(args: &mut Args) -> SourceResult { /// #calc.tanh(45deg) /// ``` /// -/// ## Parameters -/// - angle: `AngleLike` (positional, required) -/// The angle whose hyperbolic tangent to calculate. -/// /// Display: Hyperbolic tangent /// Category: calculate +/// Returns: float #[func] -pub fn tanh(args: &mut Args) -> SourceResult { - let arg = args.expect::("angle")?; - Ok(Value::Float(match arg { +pub fn tanh( + /// The angle whose hyperbolic tangent to calculate. + angle: AngleLike, +) -> Value { + Value::Float(match angle { AngleLike::Angle(a) => a.to_rad().tanh(), AngleLike::Int(n) => (n as f64).tanh(), AngleLike::Float(n) => n.tanh(), - })) + }) } /// Calculate the logarithm of a number. @@ -361,22 +351,22 @@ pub fn tanh(args: &mut Args) -> SourceResult { /// /// ## 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 /// Category: calculate +/// Returns: float #[func] -pub fn log(args: &mut Args) -> SourceResult { - let value = args.expect::("value")?; - let base = args.named::("base")?.unwrap_or_else(|| 10.0); - Ok(Value::Float(value.log(base))) +pub fn log( + /// The number whose logarithm to calculate. + value: f64, + /// 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. @@ -390,19 +380,18 @@ pub fn log(args: &mut Args) -> SourceResult { /// #calc.floor(500.1) /// ``` /// -/// ## Parameters -/// - value: `Num` (positional, required) -/// The number to round down. -/// /// Display: Round down /// Category: calculate +/// Returns: integer #[func] -pub fn floor(args: &mut Args) -> SourceResult { - let value = args.expect::("value")?; - Ok(match value { +pub fn floor( + /// The number to round down. + value: Num, +) -> Value { + match value { Num::Int(n) => Value::Int(n), Num::Float(n) => Value::Int(n.floor() as i64), - }) + } } /// Round a number up to the nearest integer. @@ -416,19 +405,18 @@ pub fn floor(args: &mut Args) -> SourceResult { /// #calc.ceil(500.1) /// ``` /// -/// ## Parameters -/// - value: `Num` (positional, required) -/// The number to round up. -/// /// Display: Round up /// Category: calculate +/// Returns: integer #[func] -pub fn ceil(args: &mut Args) -> SourceResult { - let value = args.expect::("value")?; - Ok(match value { +pub fn ceil( + /// The number to round up. + value: Num, +) -> Value { + match value { Num::Int(n) => Value::Int(n), Num::Float(n) => Value::Int(n.ceil() as i64), - }) + } } /// Round a number to the nearest integer. @@ -442,25 +430,26 @@ pub fn ceil(args: &mut Args) -> SourceResult { /// #calc.round(3.1415, digits: 2) /// ``` /// -/// ## Parameters -/// - value: `Num` (positional, required) -/// The number to round. -/// - digits: `i64` (named) -/// /// Display: Round /// Category: calculate +/// Returns: integer or float #[func] -pub fn round(args: &mut Args) -> SourceResult { - let value = args.expect::("value")?; - let digits = args.named::("digits")?.unwrap_or(0); - Ok(match value { +pub fn round( + /// The number to round. + value: Num, + /// The number of decimal places. + #[named] + #[default(0)] + digits: i64, +) -> Value { + match value { Num::Int(n) if digits == 0 => Value::Int(n), _ => { let n = value.float(); let factor = 10.0_f64.powi(digits as i32) as f64; Value::Float((n * factor).round() / factor) } - }) + } } /// Clamp a number between a minimum and maximum value. @@ -472,25 +461,22 @@ pub fn round(args: &mut Args) -> SourceResult { /// #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 /// Category: calculate +/// Returns: integer or float #[func] -pub fn clamp(args: &mut Args) -> SourceResult { - let value = args.expect::("value")?; - let min = args.expect::("min")?; - let max = args.expect::>("max")?; +pub fn clamp( + /// The number to clamp. + value: Num, + /// The inclusive minimum value. + min: Num, + /// The inclusive maximum value. + max: Spanned, +) -> Value { if max.v.float() < min.float() { 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. @@ -501,18 +487,17 @@ pub fn clamp(args: &mut Args) -> SourceResult { /// #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 /// Category: calculate +/// Returns: any #[func] -pub fn min(args: &mut Args) -> SourceResult { - minmax(args, Ordering::Less) +pub fn min( + /// The sequence of values from which to extract the minimum. + /// Must not be empty. + #[variadic] + values: Vec>, +) -> Value { + minmax(args.span, values, Ordering::Less)? } /// Determine the maximum of a sequence of values. @@ -523,24 +508,31 @@ pub fn min(args: &mut Args) -> SourceResult { /// #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 /// Category: calculate +/// Returns: any #[func] -pub fn max(args: &mut Args) -> SourceResult { - minmax(args, Ordering::Greater) +pub fn max( + /// The sequence of values from which to extract the maximum. + /// Must not be empty. + #[variadic] + values: Vec>, +) -> Value { + minmax(args.span, values, Ordering::Greater)? } /// Find the minimum or maximum of a sequence of values. -fn minmax(args: &mut Args, goal: Ordering) -> SourceResult { - let mut extremum = args.expect::("value")?; - for Spanned { v, span } in args.all::>()? { +fn minmax( + span: Span, + values: Vec>, + goal: Ordering, +) -> SourceResult { + 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) { Some(ordering) => { if ordering == goal { @@ -555,6 +547,7 @@ fn minmax(args: &mut Args, goal: Ordering) -> SourceResult { ), } } + Ok(extremum) } @@ -567,17 +560,15 @@ fn minmax(args: &mut Args, goal: Ordering) -> SourceResult { /// #range(10).filter(calc.even) /// ``` /// -/// ## Parameters -/// - value: `i64` (positional, required) -/// The number to check for evenness. -/// -/// - returns: boolean -/// /// Display: Even /// Category: calculate +/// Returns: boolean #[func] -pub fn even(args: &mut Args) -> SourceResult { - Ok(Value::Bool(args.expect::("value")? % 2 == 0)) +pub fn even( + /// The number to check for evenness. + value: i64, +) -> Value { + Value::Bool(value % 2 == 0) } /// Determine whether an integer is odd. @@ -589,18 +580,15 @@ pub fn even(args: &mut Args) -> SourceResult { /// #range(10).filter(calc.odd) /// ``` /// -/// -/// ## Parameters -/// - value: `i64` (positional, required) -/// The number to check for oddness. -/// -/// - returns: boolean -/// /// Display: Odd /// Category: calculate +/// Returns: boolean #[func] -pub fn odd(args: &mut Args) -> SourceResult { - Ok(Value::Bool(args.expect::("value")? % 2 != 0)) +pub fn odd( + /// The number to check for oddness. + value: i64, +) -> Value { + Value::Bool(value % 2 != 0) } /// Calculate the modulus of two numbers. @@ -611,25 +599,20 @@ pub fn odd(args: &mut Args) -> SourceResult { /// #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 /// Category: calculate +/// Returns: integer or float #[func] -pub fn mod_(args: &mut Args) -> SourceResult { - let dividend = args.expect::("dividend")?; - let Spanned { v: divisor, span } = args.expect::>("divisor")?; - if divisor.float() == 0.0 { - bail!(span, "divisor must not be zero"); +pub fn mod_( + /// The dividend of the modulus. + dividend: Num, + /// The divisor of the modulus. + divisor: Spanned, +) -> 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. diff --git a/library/src/compute/construct.rs b/library/src/compute/construct.rs index db4423271..4d6068a11 100644 --- a/library/src/compute/construct.rs +++ b/library/src/compute/construct.rs @@ -1,3 +1,4 @@ +use std::num::NonZeroI64; use std::str::FromStr; use ecow::EcoVec; @@ -19,17 +20,15 @@ use crate::prelude::*; /// #{ int("27") + int("4") } /// ``` /// -/// ## Parameters -/// - value: `ToInt` (positional, required) -/// The value that should be converted to an integer. -/// -/// - returns: integer -/// /// Display: Integer /// Category: construct +/// Returns: integer #[func] -pub fn int(args: &mut Args) -> SourceResult { - Ok(Value::Int(args.expect::("value")?.0)) +pub fn int( + /// 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. @@ -59,17 +58,15 @@ cast_from_value! { /// #float("1e5") /// ``` /// -/// ## Parameters -/// - value: `ToFloat` (positional, required) -/// The value that should be converted to a float. -/// -/// - returns: float -/// /// Display: Float /// Category: construct +/// Returns: float #[func] -pub fn float(args: &mut Args) -> SourceResult { - Ok(Value::Float(args.expect::("value")?.0)) +pub fn float( + /// 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. @@ -92,18 +89,15 @@ cast_from_value! { /// } /// ``` /// -/// ## Parameters -/// - gray: `Component` (positional, required) -/// The gray component. -/// -/// - returns: color -/// /// Display: Luma /// Category: construct +/// Returns: color #[func] -pub fn luma(args: &mut Args) -> SourceResult { - let Component(luma) = args.expect("gray component")?; - Ok(Value::Color(LumaColor::new(luma).into())) +pub fn luma( + /// The gray component. + gray: Component, +) -> Value { + Value::Color(LumaColor::new(gray.0).into()) } /// Create an RGB(A) color. @@ -121,40 +115,44 @@ pub fn luma(args: &mut Args) -> SourceResult { /// #square(fill: rgb(25%, 13%, 65%)) /// ``` /// -/// ## Parameters -/// - hex: `EcoString` (positional) -/// The color in hexadecimal notation. -/// -/// Accepts three, four, six or eight hexadecimal digits and optionally -/// a leading hashtag. -/// -/// If this string is given, the individual components should not be given. -/// -/// ```example -/// #text(16pt, rgb("#239dad"))[ -/// *Typst* -/// ] -/// ``` -/// -/// - red: `Component` (positional) -/// The red component. -/// -/// - green: `Component` (positional) -/// The green component. -/// -/// - blue: `Component` (positional) -/// The blue component. -/// -/// - alpha: `Component` (positional) -/// The alpha component. -/// -/// - returns: color -/// -/// Display: RGBA +/// Display: RGB /// Category: construct +/// Returns: color #[func] -pub fn rgb(args: &mut Args) -> SourceResult { - Ok(Value::Color(if let Some(string) = args.find::>()? { +pub fn rgb( + /// The color in hexadecimal notation. + /// + /// Accepts three, four, six or eight hexadecimal digits and optionally + /// a leading hashtag. + /// + /// If this string is given, the individual components should not be given. + /// + /// ```example + /// #text(16pt, rgb("#239dad"))[ + /// *Typst* + /// ] + /// ``` + #[external] + #[default] + hex: EcoString, + /// The red component. + #[external] + #[default] + red: Component, + /// The green component. + #[external] + #[default] + green: Component, + /// The blue component. + #[external] + #[default] + blue: Component, + /// The alpha component. + #[external] + #[default] + alpha: Component, +) -> Value { + Value::Color(if let Some(string) = args.find::>()? { match RgbaColor::from_str(&string.v) { Ok(color) => color.into(), Err(msg) => bail!(string.span, msg), @@ -165,7 +163,7 @@ pub fn rgb(args: &mut Args) -> SourceResult { let Component(b) = args.expect("blue component")?; let Component(a) = args.eat()?.unwrap_or(Component(255)); RgbaColor::new(r, g, b, a).into() - })) + }) } /// 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 /// Category: construct +/// Returns: color #[func] -pub fn cmyk(args: &mut Args) -> SourceResult { - let RatioComponent(c) = args.expect("cyan component")?; - let RatioComponent(m) = args.expect("magenta component")?; - let RatioComponent(y) = args.expect("yellow component")?; - let RatioComponent(k) = args.expect("key component")?; - Ok(Value::Color(CmykColor::new(c, m, y, k).into())) +pub fn cmyk( + /// The cyan component. + cyan: RatioComponent, + /// The magenta component. + magenta: RatioComponent, + /// 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. @@ -254,30 +243,29 @@ cast_from_value! { /// #envelope.fly /// ``` /// -/// ## Parameters -/// - variants: `Variant` (positional, variadic) -/// The variants of the symbol. -/// -/// Can be a just a string consisting of a single character for the -/// modifierless variant or an array with two strings specifying the modifiers -/// and the symbol. Individual modifiers should be separated by dots. When -/// displaying a symbol, Typst selects the first from the variants that have -/// all attached modifiers and the minimum number of other modifiers. -/// -/// - returns: symbol -/// /// Display: Symbol /// Category: construct +/// Returns: symbol #[func] -pub fn symbol(args: &mut Args) -> SourceResult { +pub fn symbol( + /// The variants of the symbol. + /// + /// Can be a just a string consisting of a single character for the + /// modifierless variant or an array with two strings specifying the modifiers + /// and the symbol. Individual modifiers should be separated by dots. When + /// displaying a symbol, Typst selects the first from the variants that have + /// all attached modifiers and the minimum number of other modifiers. + #[variadic] + variants: Vec>, +) -> Value { let mut list = EcoVec::new(); - for Spanned { v, span } in args.all::>()? { + for Spanned { v, span } in variants { if list.iter().any(|(prev, _)| &v.0 == prev) { bail!(span, "duplicate variant"); } 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. @@ -309,17 +297,15 @@ cast_from_value! { /// #str() /// ``` /// -/// ## Parameters -/// - value: `ToStr` (positional, required) -/// The value that should be converted to a string. -/// -/// - returns: string -/// /// Display: String /// Category: construct +/// Returns: string #[func] -pub fn str(args: &mut Args) -> SourceResult { - Ok(Value::Str(args.expect::("value")?.0)) +pub fn str( + /// 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. @@ -352,17 +338,15 @@ cast_from_value! { /// 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. /// -/// ## Parameters -/// - name: `EcoString` (positional, required) -/// The name of the label. -/// -/// - returns: label -/// /// Display: Label /// Category: construct +/// Returns: label #[func] -pub fn label(args: &mut Args) -> SourceResult { - Ok(Value::Label(Label(args.expect("string")?))) +pub fn label( + /// The name of the label. + name: EcoString, +) -> Value { + Value::Label(Label(name)) } /// Create a regular expression from a string. @@ -386,23 +370,20 @@ pub fn label(args: &mut Args) -> SourceResult { /// .split(regex("[,;]"))) /// ``` /// -/// ## Parameters -/// - regex: `EcoString` (positional, required) -/// The regular expression as a string. -/// -/// Most regex escape sequences just work because they are not valid Typst -/// 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 -/// backslash, you would need to write `{regex("\\\\")}`. -/// -/// - returns: regex -/// /// Display: Regex /// Category: construct +/// Returns: regex #[func] -pub fn regex(args: &mut Args) -> SourceResult { - let Spanned { v, span } = args.expect::>("regular expression")?; - Ok(Regex::new(&v).at(span)?.into()) +pub fn regex( + /// The regular expression as a string. + /// + /// Most regex escape sequences just work because they are not valid Typst + /// 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 + /// backslash, you would need to write `{regex("\\\\")}`. + regex: Spanned, +) -> Value { + Regex::new(®ex.v).at(regex.span)?.into() } /// Create an array consisting of a sequence of numbers. @@ -420,33 +401,30 @@ pub fn regex(args: &mut Args) -> SourceResult { /// #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 /// Category: construct +/// Returns: array #[func] -pub fn range(args: &mut Args) -> SourceResult { +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::("end")?; let (start, end) = match args.eat::()? { Some(second) => (first, second), None => (0, first), }; - let step: i64 = match args.named("step")? { - Some(Spanned { v: 0, span }) => bail!(span, "step must not be zero"), - Some(Spanned { v, .. }) => v, - None => 1, - }; + let step = step.get(); let mut x = start; let mut array = Array::new(); @@ -456,5 +434,5 @@ pub fn range(args: &mut Args) -> SourceResult { x += step; } - Ok(Value::Array(array)) + Value::Array(array) } diff --git a/library/src/compute/data.rs b/library/src/compute/data.rs index 90d72adee..7addff783 100644 --- a/library/src/compute/data.rs +++ b/library/src/compute/data.rs @@ -16,25 +16,21 @@ use crate::prelude::*; /// #raw(text, lang: "html") /// ``` /// -/// ## Parameters -/// - path: `EcoString` (positional, required) -/// Path to a file. -/// -/// - returns: string -/// /// Display: Plain text /// Category: data-loading +/// Returns: string #[func] -pub fn read(vm: &Vm, args: &mut Args) -> SourceResult { - let Spanned { v: path, span } = args.expect::>("path to file")?; - +pub fn read( + /// Path to a file. + path: Spanned, +) -> Value { + let Spanned { v: path, span } = path; let path = vm.locate(&path).at(span)?; let data = vm.world().file(&path).at(span)?; - let text = String::from_utf8(data.to_vec()) .map_err(|_| "file is not valid utf-8") .at(span)?; - Ok(Value::Str(text.into())) + Value::Str(text.into()) } /// Read structured data from a CSV file. @@ -55,33 +51,27 @@ pub fn read(vm: &Vm, args: &mut Args) -> SourceResult { /// ) /// ``` /// -/// ## Parameters -/// - path: `EcoString` (positional, required) -/// Path to a CSV file. -/// -/// - delimiter: `Delimiter` (named) -/// The delimiter that separates columns in the CSV file. -/// Must be a single ASCII character. -/// Defaults to a comma. -/// -/// - returns: array -/// /// Display: CSV /// Category: data-loading +/// Returns: array #[func] -pub fn csv(vm: &Vm, args: &mut Args) -> SourceResult { - let Spanned { v: path, span } = - args.expect::>("path to csv file")?; - +pub fn csv( + /// Path to a CSV file. + path: Spanned, + /// The delimiter that separates columns in the CSV file. + /// Must be a single ASCII character. + /// Defaults to a comma. + #[named] + #[default] + delimiter: Delimiter, +) -> Value { + let Spanned { v: path, span } = path; let path = vm.locate(&path).at(span)?; let data = vm.world().file(&path).at(span)?; let mut builder = csv::ReaderBuilder::new(); builder.has_headers(false); - - if let Some(delimiter) = args.named::("delimiter")? { - builder.delimiter(delimiter.0); - } + builder.delimiter(delimiter.0); let mut reader = builder.from_reader(data.as_slice()); let mut array = Array::new(); @@ -92,7 +82,7 @@ pub fn csv(vm: &Vm, args: &mut Args) -> SourceResult { array.push(Value::Array(sub)) } - Ok(Value::Array(array)) + Value::Array(array) } /// 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. fn format_csv_error(error: csv::Error) -> String { match error.kind() { @@ -170,25 +166,20 @@ fn format_csv_error(error: csv::Error) -> String { /// #forecast(json("tuesday.json")) /// ``` /// -/// ## Parameters -/// - path: `EcoString` (positional, required) -/// Path to a JSON file. -/// -/// - returns: dictionary or array -/// /// Display: JSON /// Category: data-loading +/// Returns: array or dictionary #[func] -pub fn json(vm: &Vm, args: &mut Args) -> SourceResult { - let Spanned { v: path, span } = - args.expect::>("path to json file")?; - +pub fn json( + /// Path to a JSON file. + path: Spanned, +) -> Value { + let Spanned { v: path, span } = path; let path = vm.locate(&path).at(span)?; let data = vm.world().file(&path).at(span)?; let value: serde_json::Value = serde_json::from_slice(&data).map_err(format_json_error).at(span)?; - - Ok(convert_json(value)) + convert_json(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 /// Category: data-loading +/// Returns: array #[func] -pub fn xml(vm: &Vm, args: &mut Args) -> SourceResult { - let Spanned { v: path, span } = - args.expect::>("path to xml file")?; - +pub fn xml( + /// Path to an XML file. + path: Spanned, +) -> Value { + let Spanned { v: path, span } = path; let path = vm.locate(&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 document = roxmltree::Document::parse(text).map_err(format_xml_error).at(span)?; - - Ok(convert_xml(document.root())) + convert_xml(document.root()) } /// Convert an XML node to a Typst value. diff --git a/library/src/compute/foundations.rs b/library/src/compute/foundations.rs index 710ec68ed..41a6bc359 100644 --- a/library/src/compute/foundations.rs +++ b/library/src/compute/foundations.rs @@ -14,17 +14,15 @@ use crate::prelude::*; /// #type(x => x + 1) /// ``` /// -/// ## Parameters -/// - value: `Value` (positional, required) -/// The value whose type's to determine. -/// -/// - returns: string -/// /// Display: Type /// Category: foundations +/// Returns: string #[func] -pub fn type_(args: &mut Args) -> SourceResult { - Ok(args.expect::("value")?.type_name().into()) +pub fn type_( + /// The value whose type's to determine. + value: Value, +) -> Value { + value.type_name().into() } /// The string representation of a value. @@ -41,17 +39,15 @@ pub fn type_(args: &mut Args) -> SourceResult { /// #[*Hi*] vs #repr([*Hi*]) /// ``` /// -/// ## Parameters -/// - value: `Value` (positional, required) -/// The value whose string representation to produce. -/// -/// - returns: string -/// /// Display: Representation /// Category: foundations +/// Returns: string #[func] -pub fn repr(args: &mut Args) -> SourceResult { - Ok(args.expect::("value")?.repr().into()) +pub fn repr( + /// The value whose string representation to produce. + value: Value, +) -> Value { + value.repr().into() } /// Fail with an error. @@ -62,15 +58,16 @@ pub fn repr(args: &mut Args) -> SourceResult { /// #panic("this is wrong") /// ``` /// -/// ## Parameters -/// - payload: `Value` (positional) -/// The value (or message) to panic with. -/// /// Display: Panic /// Category: foundations +/// Returns: #[func] -pub fn panic(args: &mut Args) -> SourceResult { - match args.eat::()? { +pub fn panic( + /// The value (or message) to panic with. + #[default] + payload: Option, +) -> Value { + match payload { Some(v) => bail!(args.span, "panicked with: {}", v.repr()), None => bail!(args.span, "panicked"), } @@ -86,26 +83,26 @@ pub fn panic(args: &mut Args) -> SourceResult { /// #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 /// Category: foundations +/// Returns: #[func] -pub fn assert(args: &mut Args) -> SourceResult { - let check = args.expect::("condition")?; - let message = args.named::("message")?; - if !check { +pub fn assert( + /// The condition that must be true for the assertion to pass. + condition: bool, + /// The error message when the assertion fails. + #[named] + #[default] + message: Option, +) -> Value { + if !condition { if let Some(message) = message { bail!(args.span, "assertion failed: {}", message); } else { bail!(args.span, "assertion failed"); } } - Ok(Value::None) + Value::None } /// Evaluate a string as Typst code. @@ -119,18 +116,16 @@ pub fn assert(args: &mut Args) -> SourceResult { /// #eval("[*Strong text*]") /// ``` /// -/// ## Parameters -/// - source: `String` (positional, required) -/// A string of Typst code to evaluate. -/// -/// The code in the string cannot interact with the file system. -/// -/// - returns: any -/// /// Display: Evaluate /// Category: foundations +/// Returns: any #[func] -pub fn eval(vm: &Vm, args: &mut Args) -> SourceResult { - let Spanned { v: text, span } = args.expect::>("source")?; - typst::eval::eval_code_str(vm.world(), &text, span) +pub fn eval( + /// A string of Typst code to evaluate. + /// + /// The code in the string cannot interact with the file system. + source: Spanned, +) -> Value { + let Spanned { v: text, span } = source; + typst::eval::eval_code_str(vm.world(), &text, span)? } diff --git a/library/src/layout/align.rs b/library/src/layout/align.rs index 88815dc9f..cf6d08f27 100644 --- a/library/src/layout/align.rs +++ b/library/src/layout/align.rs @@ -53,7 +53,6 @@ pub struct AlignNode { pub alignment: Axes>, /// The content to align. - #[positional] #[required] pub body: Content, } diff --git a/library/src/layout/columns.rs b/library/src/layout/columns.rs index 58b369c61..7704e9c41 100644 --- a/library/src/layout/columns.rs +++ b/library/src/layout/columns.rs @@ -45,7 +45,6 @@ pub struct ColumnsNode { pub gutter: Rel, /// The content that should be layouted into the columns. - #[positional] #[required] pub body: Content, } diff --git a/library/src/layout/container.rs b/library/src/layout/container.rs index 31a80aa24..009063f0a 100644 --- a/library/src/layout/container.rs +++ b/library/src/layout/container.rs @@ -181,20 +181,6 @@ impl Layout for BoxNode { /// 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 /// Category: layout #[node(Layout)] @@ -270,6 +256,20 @@ pub struct BlockNode { #[fold] pub outset: Sides>>, + /// 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 /// over `spacing`. Can be used in combination with a show rule to adjust /// the spacing around arbitrary block-level elements. diff --git a/library/src/layout/enum.rs b/library/src/layout/enum.rs index ee09d339a..33b297e79 100644 --- a/library/src/layout/enum.rs +++ b/library/src/layout/enum.rs @@ -228,7 +228,6 @@ pub struct EnumItem { pub number: Option, /// The item's body. - #[positional] #[required] pub body: Content, } diff --git a/library/src/layout/grid.rs b/library/src/layout/grid.rs index 34514eac8..b6e86afda 100644 --- a/library/src/layout/grid.rs +++ b/library/src/layout/grid.rs @@ -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 /// Category: layout #[node(Layout)] @@ -83,6 +77,12 @@ pub struct GridNode { /// repeated until there are no more cells. 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`. #[parse( let gutter = args.named("gutter")?; diff --git a/library/src/layout/hide.rs b/library/src/layout/hide.rs index 43d9a2a8d..626284459 100644 --- a/library/src/layout/hide.rs +++ b/library/src/layout/hide.rs @@ -18,7 +18,6 @@ use crate::prelude::*; #[node(Show)] pub struct HideNode { /// The content to hide. - #[positional] #[required] pub body: Content, } diff --git a/library/src/layout/list.rs b/library/src/layout/list.rs index 57b653c06..6d6058684 100644 --- a/library/src/layout/list.rs +++ b/library/src/layout/list.rs @@ -163,7 +163,6 @@ impl Layout for ListNode { #[node] pub struct ListItem { /// The item's body. - #[positional] #[required] pub body: Content, } diff --git a/library/src/layout/pad.rs b/library/src/layout/pad.rs index 7d0bbe045..e81715602 100644 --- a/library/src/layout/pad.rs +++ b/library/src/layout/pad.rs @@ -15,16 +15,6 @@ use crate::prelude::*; /// measured in words per minute._ /// ``` /// -/// ## Parameters -/// - x: `Rel` (named, settable) -/// The horizontal padding. Both `left` and `right` take precedence over this. -/// -/// - y: `Rel` (named, settable) -/// The vertical padding. Both `top` and `bottom` take precedence over this. -/// -/// - rest: `Rel` (named, settable) -/// The padding for all sides. All other parameters take precedence over this. -/// /// Display: Padding /// Category: layout #[node(Layout)] @@ -50,8 +40,21 @@ pub struct PadNode { #[parse(args.named("bottom")?.or(y))] pub bottom: Rel, + /// The horizontal padding. Both `left` and `right` take precedence over + /// this. + #[external] + pub x: Rel, + + /// The vertical padding. Both `top` and `bottom` take precedence over this. + #[external] + pub y: Rel, + + /// The padding for all sides. All other parameters take precedence over + /// this. + #[external] + pub rest: Rel, + /// The content to pad at the sides. - #[positional] #[required] pub body: Content, } diff --git a/library/src/layout/page.rs b/library/src/layout/page.rs index 5fe3c90ad..e469bf10c 100644 --- a/library/src/layout/page.rs +++ b/library/src/layout/page.rs @@ -20,15 +20,15 @@ use crate::prelude::*; /// 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 /// Category: layout #[node] 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. /// /// ```example @@ -232,7 +232,6 @@ pub struct PageNode { /// 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 /// will be created after the body has been typeset. - #[positional] #[required] pub body: Content, } diff --git a/library/src/layout/par.rs b/library/src/layout/par.rs index 93cca4523..e5644a2e9 100644 --- a/library/src/layout/par.rs +++ b/library/src/layout/par.rs @@ -35,10 +35,6 @@ use crate::text::{ /// three integers. Then, we ... /// ``` /// -/// ## Parameters -/// - body: `Content` (positional, required) -/// The contents of the paragraph. -/// /// Display: Paragraph /// Category: layout #[node(Construct)] @@ -99,6 +95,10 @@ pub struct ParNode { #[default] pub linebreaks: Smart, + /// The contents of the paragraph. + #[external] + pub body: Content, + /// The paragraph's children. #[internal] #[variadic] diff --git a/library/src/layout/place.rs b/library/src/layout/place.rs index 8d7aa2299..bfabd0f3d 100644 --- a/library/src/layout/place.rs +++ b/library/src/layout/place.rs @@ -49,7 +49,6 @@ pub struct PlaceNode { pub dy: Rel, /// The content to place. - #[positional] #[required] pub body: Content, } diff --git a/library/src/layout/repeat.rs b/library/src/layout/repeat.rs index 0fd9ad83e..c8f63ac33 100644 --- a/library/src/layout/repeat.rs +++ b/library/src/layout/repeat.rs @@ -26,7 +26,6 @@ use super::AlignNode; #[node(Layout)] pub struct RepeatNode { /// The content to repeat. - #[positional] #[required] pub body: Content, } diff --git a/library/src/layout/spacing.rs b/library/src/layout/spacing.rs index c11a2f060..dbdf0c11e 100644 --- a/library/src/layout/spacing.rs +++ b/library/src/layout/spacing.rs @@ -24,7 +24,6 @@ use crate::prelude::*; #[node(Behave)] pub struct HNode { /// How much spacing to insert. - #[positional] #[required] pub amount: Spacing, @@ -84,31 +83,30 @@ impl Behave for HNode { /// ) /// ``` /// -/// ## Parameters -/// - weak: `bool` (named, settable) -/// 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. -/// Weak spacings will always collapse adjacent paragraph spacing, even if the -/// paragraph spacing is larger. -/// -/// ```example -/// The following theorem is -/// foundational to the field: -/// #v(4pt, weak: true) -/// $ x^2 + y^2 = r^2 $ -/// #v(4pt, weak: true) -/// The proof is simple: -/// ``` -/// /// Display: Spacing (V) /// Category: layout #[node(Behave)] pub struct VNode { /// How much spacing to insert. - #[positional] #[required] pub amount: Spacing, + /// 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. + /// Weak spacings will always collapse adjacent paragraph spacing, even if the + /// paragraph spacing is larger. + /// + /// ```example + /// The following theorem is + /// foundational to the field: + /// #v(4pt, weak: true) + /// $ x^2 + y^2 = r^2 $ + /// #v(4pt, weak: true) + /// The proof is simple: + /// ``` + #[external] + pub weak: bool, + /// The node's weakness level, see also [`Behaviour`]. #[internal] #[parse(args.named("weak")?.map(|v: bool| v as usize))] diff --git a/library/src/layout/table.rs b/library/src/layout/table.rs index 59635119b..fabe8c332 100644 --- a/library/src/layout/table.rs +++ b/library/src/layout/table.rs @@ -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 /// Category: layout #[node(Layout)] pub struct TableNode { - /// Defines the column sizes. - /// See the [grid documentation]($func/grid) for more information on track - /// sizing. + /// Defines the column sizes. See the [grid documentation]($func/grid) for + /// more information on track sizing. pub columns: TrackSizings, - /// Defines the row sizes. - /// See the [grid documentation]($func/grid) for more information on track - /// sizing. + /// Defines the row sizes. See the [grid documentation]($func/grid) for more + /// information on track sizing. pub rows: TrackSizings, - /// Defines the gaps between columns. Takes precedence over `gutter`. - /// See the [grid documentation]($func/grid) for more information on gutters. + /// Defines the gaps between rows & columns. See the [grid + /// 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( let gutter = args.named("gutter")?; args.named("column-gutter")?.or_else(|| gutter.clone()) )] pub column_gutter: TrackSizings, - /// Defines the gaps between rows. Takes precedence over `gutter`. - /// See the [grid documentation]($func/grid) for more information on gutters. + /// Defines the gaps between rows. Takes precedence over `gutter`. See the + /// [grid documentation]($func/grid) for more information on gutters. #[parse(args.named("row-gutter")?.or_else(|| gutter.clone()))] pub row_gutter: TrackSizings, diff --git a/library/src/layout/terms.rs b/library/src/layout/terms.rs index 8ab4edc6f..b2f45446f 100644 --- a/library/src/layout/terms.rs +++ b/library/src/layout/terms.rs @@ -126,12 +126,10 @@ impl Layout for TermsNode { #[node] pub struct TermItem { /// The term described by the list item. - #[positional] #[required] pub term: Content, /// The description of the term. - #[positional] #[required] pub description: Content, } diff --git a/library/src/layout/transform.rs b/library/src/layout/transform.rs index 4521da329..2afe82010 100644 --- a/library/src/layout/transform.rs +++ b/library/src/layout/transform.rs @@ -32,7 +32,6 @@ pub struct MoveNode { pub dy: Rel, /// The content to move. - #[positional] #[required] pub body: Content, } @@ -101,7 +100,6 @@ pub struct RotateNode { pub origin: Axes>, /// The content to rotate. - #[positional] #[required] pub body: Content, } @@ -170,7 +168,6 @@ pub struct ScaleNode { pub origin: Axes>, /// The content to scale. - #[positional] #[required] pub body: Content, } diff --git a/library/src/lib.rs b/library/src/lib.rs index bcbd703de..ad8a2ac4c 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -29,91 +29,91 @@ fn global(math: Module, calc: Module) -> Module { let mut global = Scope::deduplicating(); // Text. - global.def_func::("text"); - global.def_func::("linebreak"); - global.def_func::("smartquote"); - global.def_func::("strong"); - global.def_func::("emph"); - global.def_func::("lower"); - global.def_func::("upper"); - global.def_func::("smallcaps"); - global.def_func::("sub"); - global.def_func::("super"); - global.def_func::("underline"); - global.def_func::("strike"); - global.def_func::("overline"); - global.def_func::("raw"); - global.def_func::("lorem"); + global.define("text", text::TextNode::func()); + global.define("linebreak", text::LinebreakNode::func()); + global.define("smartquote", text::SmartQuoteNode::func()); + global.define("strong", text::StrongNode::func()); + global.define("emph", text::EmphNode::func()); + global.define("lower", text::lower); + global.define("upper", text::upper); + global.define("smallcaps", text::smallcaps); + global.define("sub", text::SubNode::func()); + global.define("super", text::SuperNode::func()); + global.define("underline", text::UnderlineNode::func()); + global.define("strike", text::StrikeNode::func()); + global.define("overline", text::OverlineNode::func()); + global.define("raw", text::RawNode::func()); + global.define("lorem", text::lorem); // Math. global.define("math", math); // Layout. - global.def_func::("page"); - global.def_func::("pagebreak"); - global.def_func::("v"); - global.def_func::("par"); - global.def_func::("parbreak"); - global.def_func::("h"); - global.def_func::("box"); - global.def_func::("block"); - global.def_func::("list"); - global.def_func::("enum"); - global.def_func::("terms"); - global.def_func::("table"); - global.def_func::("stack"); - global.def_func::("grid"); - global.def_func::("columns"); - global.def_func::("colbreak"); - global.def_func::("place"); - global.def_func::("align"); - global.def_func::("pad"); - global.def_func::("repeat"); - global.def_func::("move"); - global.def_func::("scale"); - global.def_func::("rotate"); - global.def_func::("hide"); + global.define("page", layout::PageNode::func()); + global.define("pagebreak", layout::PagebreakNode::func()); + global.define("v", layout::VNode::func()); + global.define("par", layout::ParNode::func()); + global.define("parbreak", layout::ParbreakNode::func()); + global.define("h", layout::HNode::func()); + global.define("box", layout::BoxNode::func()); + global.define("block", layout::BlockNode::func()); + global.define("list", layout::ListNode::func()); + global.define("enum", layout::EnumNode::func()); + global.define("terms", layout::TermsNode::func()); + global.define("table", layout::TableNode::func()); + global.define("stack", layout::StackNode::func()); + global.define("grid", layout::GridNode::func()); + global.define("columns", layout::ColumnsNode::func()); + global.define("colbreak", layout::ColbreakNode::func()); + global.define("place", layout::PlaceNode::func()); + global.define("align", layout::AlignNode::func()); + global.define("pad", layout::PadNode::func()); + global.define("repeat", layout::RepeatNode::func()); + global.define("move", layout::MoveNode::func()); + global.define("scale", layout::ScaleNode::func()); + global.define("rotate", layout::RotateNode::func()); + global.define("hide", layout::HideNode::func()); // Visualize. - global.def_func::("image"); - global.def_func::("line"); - global.def_func::("rect"); - global.def_func::("square"); - global.def_func::("ellipse"); - global.def_func::("circle"); + global.define("image", visualize::ImageNode::func()); + global.define("line", visualize::LineNode::func()); + global.define("rect", visualize::RectNode::func()); + global.define("square", visualize::SquareNode::func()); + global.define("ellipse", visualize::EllipseNode::func()); + global.define("circle", visualize::CircleNode::func()); // Meta. - global.def_func::("document"); - global.def_func::("ref"); - global.def_func::("link"); - global.def_func::("outline"); - global.def_func::("heading"); - global.def_func::("numbering"); + global.define("document", meta::DocumentNode::func()); + global.define("ref", meta::RefNode::func()); + global.define("link", meta::LinkNode::func()); + global.define("outline", meta::OutlineNode::func()); + global.define("heading", meta::HeadingNode::func()); + global.define("numbering", meta::numbering); // Symbols. global.define("sym", symbols::sym()); global.define("emoji", symbols::emoji()); // Compute. - global.def_func::("type"); - global.def_func::("repr"); - global.def_func::("panic"); - global.def_func::("assert"); - global.def_func::("eval"); - global.def_func::("int"); - global.def_func::("float"); - global.def_func::("luma"); - global.def_func::("rgb"); - global.def_func::("cmyk"); - global.def_func::("symbol"); - global.def_func::("str"); - global.def_func::("label"); - global.def_func::("regex"); - global.def_func::("range"); - global.def_func::("read"); - global.def_func::("csv"); - global.def_func::("json"); - global.def_func::("xml"); + global.define("type", compute::type_); + global.define("repr", compute::repr); + global.define("panic", compute::panic); + global.define("assert", compute::assert); + global.define("eval", compute::eval); + global.define("int", compute::int); + global.define("float", compute::float); + global.define("luma", compute::luma); + global.define("rgb", compute::rgb); + global.define("cmyk", compute::cmyk); + global.define("symbol", compute::symbol); + global.define("str", compute::str); + global.define("label", compute::label); + global.define("regex", compute::regex); + global.define("range", compute::range); + global.define("read", compute::read); + global.define("csv", compute::csv); + global.define("json", compute::json); + global.define("xml", compute::xml); // Calc. global.define("calc", calc); diff --git a/library/src/math/accent.rs b/library/src/math/accent.rs index 164247de2..03caf4113 100644 --- a/library/src/math/accent.rs +++ b/library/src/math/accent.rs @@ -24,7 +24,6 @@ pub struct AccentNode { /// ```example /// $arrow(A B C)$ /// ``` - #[positional] #[required] pub base: Content, @@ -47,7 +46,6 @@ pub struct AccentNode { /// | Caron | `caron` | `ˇ` | /// | Right arrow | `arrow`, `->` | `→` | /// | Left arrow | `arrow.l`, `<-` | `←` | - #[positional] #[required] pub accent: Accent, } diff --git a/library/src/math/attach.rs b/library/src/math/attach.rs index c2d7703db..7d8749f2a 100644 --- a/library/src/math/attach.rs +++ b/library/src/math/attach.rs @@ -16,7 +16,6 @@ use super::*; #[node(LayoutMath)] pub struct AttachNode { /// The base to which things are attached. - #[positional] #[required] pub base: Content, @@ -79,7 +78,6 @@ impl LayoutMath for AttachNode { #[node(LayoutMath)] pub struct ScriptsNode { /// The base to attach the scripts to. - #[positional] #[required] pub body: Content, } @@ -102,7 +100,6 @@ impl LayoutMath for ScriptsNode { #[node(LayoutMath)] pub struct LimitsNode { /// The base to attach the limits to. - #[positional] #[required] pub body: Content, } diff --git a/library/src/math/delimited.rs b/library/src/math/delimited.rs index f9d22c432..6f468af7c 100644 --- a/library/src/math/delimited.rs +++ b/library/src/math/delimited.rs @@ -24,7 +24,6 @@ pub struct LrNode { pub size: Smart>, /// The delimited content, including the delimiters. - #[positional] #[required] #[parse( let mut body = Content::empty(); @@ -111,15 +110,15 @@ fn scale( /// $ floor(x/2) $ /// ``` /// -/// ## Parameters -/// - body: `Content` (positional, required) -/// The expression to floor. -/// /// Display: Floor /// Category: math +/// Returns: content #[func] -pub fn floor(args: &mut Args) -> SourceResult { - delimited(args, '⌊', '⌋') +pub fn floor( + /// The expression to floor. + body: Content, +) -> Value { + delimited(body, '⌊', '⌋') } /// Ceil an expression. @@ -129,15 +128,15 @@ pub fn floor(args: &mut Args) -> SourceResult { /// $ ceil(x/2) $ /// ``` /// -/// ## Parameters -/// - body: `Content` (positional, required) -/// The expression to ceil. -/// /// Display: Ceil /// Category: math +/// Returns: content #[func] -pub fn ceil(args: &mut Args) -> SourceResult { - delimited(args, '⌈', '⌉') +pub fn ceil( + /// The expression to ceil. + body: Content, +) -> Value { + delimited(body, '⌈', '⌉') } /// Take the absolute value of an expression. @@ -147,15 +146,16 @@ pub fn ceil(args: &mut Args) -> SourceResult { /// $ abs(x/2) $ /// ``` /// -/// ## Parameters -/// - body: `Content` (positional, required) -/// The expression to take the absolute value of. /// /// Display: Abs /// Category: math +/// Returns: content #[func] -pub fn abs(args: &mut Args) -> SourceResult { - delimited(args, '|', '|') +pub fn abs( + /// The expression to take the absolute value of. + body: Content, +) -> Value { + delimited(body, '|', '|') } /// Take the norm of an expression. @@ -165,24 +165,24 @@ pub fn abs(args: &mut Args) -> SourceResult { /// $ norm(x/2) $ /// ``` /// -/// ## Parameters -/// - body: `Content` (positional, required) -/// The expression to take the norm of. -/// /// Display: Norm /// Category: math +/// Returns: content #[func] -pub fn norm(args: &mut Args) -> SourceResult { - delimited(args, '‖', '‖') +pub fn norm( + /// The expression to take the norm of. + body: Content, +) -> Value { + delimited(body, '‖', '‖') } -fn delimited(args: &mut Args, left: char, right: char) -> SourceResult { - Ok(Value::Content( +fn delimited(body: Content, left: char, right: char) -> Value { + Value::Content( LrNode::new(Content::sequence(vec![ TextNode::packed(left), - args.expect::("body")?, + body, TextNode::packed(right), ])) .pack(), - )) + ) } diff --git a/library/src/math/frac.rs b/library/src/math/frac.rs index c1f4065b0..0624832a7 100644 --- a/library/src/math/frac.rs +++ b/library/src/math/frac.rs @@ -22,12 +22,10 @@ const FRAC_AROUND: Em = Em::new(0.1); #[node(LayoutMath)] pub struct FracNode { /// The fraction's numerator. - #[positional] #[required] pub num: Content, /// The fraction's denominator. - #[positional] #[required] pub denom: Content, } @@ -50,12 +48,10 @@ impl LayoutMath for FracNode { #[node(LayoutMath)] pub struct BinomNode { /// The binomial's upper index. - #[positional] #[required] pub upper: Content, /// The binomial's lower index. - #[positional] #[required] pub lower: Content, } diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs index 0c9dc3385..8f89e0285 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -47,52 +47,52 @@ use crate::text::{ /// Create a module with all math definitions. pub fn module() -> Module { let mut math = Scope::deduplicating(); - math.def_func::("formula"); - math.def_func::("text"); + math.define("formula", FormulaNode::func()); + math.define("text", TextNode::func()); // Grouping. - math.def_func::("lr"); - math.def_func::("abs"); - math.def_func::("norm"); - math.def_func::("floor"); - math.def_func::("ceil"); + math.define("lr", LrNode::func()); + math.define("abs", abs); + math.define("norm", norm); + math.define("floor", floor); + math.define("ceil", ceil); // Attachments and accents. - math.def_func::("attach"); - math.def_func::("scripts"); - math.def_func::("limits"); - math.def_func::("accent"); - math.def_func::("underline"); - math.def_func::("overline"); - math.def_func::("underbrace"); - math.def_func::("overbrace"); - math.def_func::("underbracket"); - math.def_func::("overbracket"); + math.define("attach", AttachNode::func()); + math.define("scripts", ScriptsNode::func()); + math.define("limits", LimitsNode::func()); + math.define("accent", AccentNode::func()); + math.define("underline", UnderlineNode::func()); + math.define("overline", OverlineNode::func()); + math.define("underbrace", UnderbraceNode::func()); + math.define("overbrace", OverbraceNode::func()); + math.define("underbracket", UnderbracketNode::func()); + math.define("overbracket", OverbracketNode::func()); // Fractions and matrix-likes. - math.def_func::("frac"); - math.def_func::("binom"); - math.def_func::("vec"); - math.def_func::("mat"); - math.def_func::("cases"); + math.define("frac", FracNode::func()); + math.define("binom", BinomNode::func()); + math.define("vec", VecNode::func()); + math.define("mat", MatNode::func()); + math.define("cases", CasesNode::func()); // Roots. - math.def_func::("sqrt"); - math.def_func::("root"); + math.define("sqrt", SqrtNode::func()); + math.define("root", RootNode::func()); // Styles. - math.def_func::("upright"); - math.def_func::("bold"); - math.def_func::("italic"); - math.def_func::("serif"); - math.def_func::("sans"); - math.def_func::("cal"); - math.def_func::("frak"); - math.def_func::("mono"); - math.def_func::("bb"); + math.define("upright", UprightNode::func()); + math.define("bold", BoldNode::func()); + math.define("italic", ItalicNode::func()); + math.define("serif", SerifNode::func()); + math.define("sans", SansNode::func()); + math.define("cal", CalNode::func()); + math.define("frak", FrakNode::func()); + math.define("mono", MonoNode::func()); + math.define("bb", BbNode::func()); // Text operators. - math.def_func::("op"); + math.define("op", OpNode::func()); op::define(&mut math); // Spacings. @@ -139,7 +139,6 @@ pub struct FormulaNode { pub block: bool, /// The content of the formula. - #[positional] #[required] pub body: Content, } diff --git a/library/src/math/op.rs b/library/src/math/op.rs index aa2e4cf7d..bd81aa0e7 100644 --- a/library/src/math/op.rs +++ b/library/src/math/op.rs @@ -23,7 +23,6 @@ use super::*; #[node(LayoutMath)] pub struct OpNode { /// The operator's text. - #[positional] #[required] pub text: EcoString, diff --git a/library/src/math/root.rs b/library/src/math/root.rs index e190c65f3..cb01e6a17 100644 --- a/library/src/math/root.rs +++ b/library/src/math/root.rs @@ -12,7 +12,6 @@ use super::*; #[node(LayoutMath)] pub struct SqrtNode { /// The expression to take the square root of. - #[positional] #[required] pub radicand: Content, } @@ -35,12 +34,10 @@ impl LayoutMath for SqrtNode { #[node(LayoutMath)] pub struct RootNode { /// Which root of the radicand to take. - #[positional] #[required] index: Content, /// The expression to take the root of. - #[positional] #[required] radicand: Content, } diff --git a/library/src/math/style.rs b/library/src/math/style.rs index 43c1f391c..60bad6a58 100644 --- a/library/src/math/style.rs +++ b/library/src/math/style.rs @@ -12,7 +12,6 @@ use super::*; #[node(LayoutMath)] pub struct BoldNode { /// The piece of formula to style. - #[positional] #[required] pub body: Content, } @@ -38,7 +37,6 @@ impl LayoutMath for BoldNode { #[node(LayoutMath)] pub struct UprightNode { /// The piece of formula to style. - #[positional] #[required] pub body: Content, } @@ -61,7 +59,6 @@ impl LayoutMath for UprightNode { #[node(LayoutMath)] pub struct ItalicNode { /// The piece of formula to style. - #[positional] #[required] pub body: Content, } @@ -84,7 +81,6 @@ impl LayoutMath for ItalicNode { #[node(LayoutMath)] pub struct SerifNode { /// The piece of formula to style. - #[positional] #[required] pub body: Content, } @@ -110,7 +106,6 @@ impl LayoutMath for SerifNode { #[node(LayoutMath)] pub struct SansNode { /// The piece of formula to style. - #[positional] #[required] pub body: Content, } @@ -136,7 +131,6 @@ impl LayoutMath for SansNode { #[node(LayoutMath)] pub struct CalNode { /// The piece of formula to style. - #[positional] #[required] pub body: Content, } @@ -162,7 +156,6 @@ impl LayoutMath for CalNode { #[node(LayoutMath)] pub struct FrakNode { /// The piece of formula to style. - #[positional] #[required] pub body: Content, } @@ -188,7 +181,6 @@ impl LayoutMath for FrakNode { #[node(LayoutMath)] pub struct MonoNode { /// The piece of formula to style. - #[positional] #[required] pub body: Content, } @@ -219,7 +211,6 @@ impl LayoutMath for MonoNode { #[node(LayoutMath)] pub struct BbNode { /// The piece of formula to style. - #[positional] #[required] pub body: Content, } diff --git a/library/src/math/underover.rs b/library/src/math/underover.rs index 2aabf1328..a723ae978 100644 --- a/library/src/math/underover.rs +++ b/library/src/math/underover.rs @@ -16,7 +16,6 @@ const BRACKET_GAP: Em = Em::new(0.25); #[node(LayoutMath)] pub struct UnderlineNode { /// The content above the line. - #[positional] #[required] pub body: Content, } @@ -39,7 +38,6 @@ impl LayoutMath for UnderlineNode { #[node(LayoutMath)] pub struct OverlineNode { /// The content below the line. - #[positional] #[required] pub body: Content, } @@ -62,7 +60,6 @@ impl LayoutMath for OverlineNode { #[node(LayoutMath)] pub struct UnderbraceNode { /// The content above the brace. - #[positional] #[required] pub body: Content, @@ -89,7 +86,6 @@ impl LayoutMath for UnderbraceNode { #[node(LayoutMath)] pub struct OverbraceNode { /// The content below the brace. - #[positional] #[required] pub body: Content, @@ -116,7 +112,6 @@ impl LayoutMath for OverbraceNode { #[node(LayoutMath)] pub struct UnderbracketNode { /// The content above the bracket. - #[positional] #[required] pub body: Content, @@ -143,7 +138,6 @@ impl LayoutMath for UnderbracketNode { #[node(LayoutMath)] pub struct OverbracketNode { /// The content below the bracket. - #[positional] #[required] pub body: Content, diff --git a/library/src/meta/heading.rs b/library/src/meta/heading.rs index 09cbc8b16..3a8d811c4 100644 --- a/library/src/meta/heading.rs +++ b/library/src/meta/heading.rs @@ -74,7 +74,6 @@ pub struct HeadingNode { pub outlined: bool, /// The heading's title. - #[positional] #[required] pub body: Content, } diff --git a/library/src/meta/link.rs b/library/src/meta/link.rs index d4d4d8ca1..572c55b43 100644 --- a/library/src/meta/link.rs +++ b/library/src/meta/link.rs @@ -46,7 +46,6 @@ pub struct LinkNode { /// ] /// ``` /// - #[positional] #[required] #[parse( let dest = args.expect::("destination")?; @@ -59,7 +58,6 @@ pub struct LinkNode { /// 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 /// link. - #[positional] #[required] #[parse(match &dest { Destination::Url(url) => match args.eat()? { diff --git a/library/src/meta/numbering.rs b/library/src/meta/numbering.rs index d3e1ee4d5..4e6e1aedd 100644 --- a/library/src/meta/numbering.rs +++ b/library/src/meta/numbering.rs @@ -27,46 +27,42 @@ use crate::text::Case; /// ) /// ``` /// -/// ## Parameters -/// - numbering: `Numbering` (positional, required) -/// Defines how the numbering works. -/// -/// **Counting symbols** are `1`, `a`, `A`, `i`, `I` and `*`. They are -/// replaced by the number in the sequence, in the given case. -/// -/// The `*` character means that symbols should be used to count, in the -/// order of `*`, `†`, `‡`, `§`, `¶`, and `‖`. If there are more than six -/// items, the number is represented using multiple symbols. -/// -/// **Suffixes** are all characters after the last counting symbol. They are -/// repeated as-is at the end of any rendered number. -/// -/// **Prefixes** are all characters that are neither counting symbols nor -/// suffixes. They are repeated as-is at in front of their rendered -/// equivalent of their counting symbol. -/// -/// This parameter can also be an arbitrary function that gets each number as -/// an individual argument. When given a function, the `numbering` function -/// just forwards the arguments to that function. While this is not -/// particularly useful in itself, it means that you can just give arbitrary -/// numberings to the `numbering` function without caring whether they are -/// defined as a pattern or function. -/// -/// - numbers: `NonZeroUsize` (positional, variadic) -/// The numbers to apply the numbering to. Must be positive. -/// -/// If `numbering` is a pattern and more numbers than counting symbols are -/// given, the last counting symbol with its prefix is repeated. -/// -/// - returns: any -/// /// Display: Numbering /// Category: meta +/// Returns: any #[func] -pub fn numbering(vm: &Vm, args: &mut Args) -> SourceResult { - let numbering = args.expect::("pattern or function")?; - let numbers = args.all::()?; - numbering.apply(vm.world(), &numbers) +pub fn numbering( + /// Defines how the numbering works. + /// + /// **Counting symbols** are `1`, `a`, `A`, `i`, `I` and `*`. They are + /// replaced by the number in the sequence, in the given case. + /// + /// The `*` character means that symbols should be used to count, in the + /// order of `*`, `†`, `‡`, `§`, `¶`, and `‖`. If there are more than six + /// items, the number is represented using multiple symbols. + /// + /// **Suffixes** are all characters after the last counting symbol. They are + /// repeated as-is at the end of any rendered number. + /// + /// **Prefixes** are all characters that are neither counting symbols nor + /// suffixes. They are repeated as-is at in front of their rendered + /// equivalent of their counting symbol. + /// + /// This parameter can also be an arbitrary function that gets each number as + /// an individual argument. When given a function, the `numbering` function + /// just forwards the arguments to that function. While this is not + /// particularly useful in itself, it means that you can just give arbitrary + /// numberings to the `numbering` function without caring whether they are + /// defined as a pattern or function. + numbering: Numbering, + /// The numbers to apply the numbering to. Must be positive. + /// + /// If `numbering` is a pattern and more numbers than counting symbols are + /// given, the last counting symbol with its prefix is repeated. + #[variadic] + numbers: Vec, +) -> Value { + numbering.apply(vm.world(), &numbers)? } /// How to number an enumeration. diff --git a/library/src/meta/reference.rs b/library/src/meta/reference.rs index 55051b5e4..20354556a 100644 --- a/library/src/meta/reference.rs +++ b/library/src/meta/reference.rs @@ -20,7 +20,6 @@ use crate::text::TextNode; #[node(Show)] pub struct RefNode { /// The label that should be referenced. - #[positional] #[required] pub target: EcoString, } diff --git a/library/src/text/deco.rs b/library/src/text/deco.rs index 2a552226f..4dadf45a8 100644 --- a/library/src/text/deco.rs +++ b/library/src/text/deco.rs @@ -61,7 +61,6 @@ pub struct UnderlineNode { pub evade: bool, /// The content to underline. - #[positional] #[required] pub body: Content, } @@ -141,7 +140,6 @@ pub struct OverlineNode { pub evade: bool, /// The content to add a line over. - #[positional] #[required] pub body: Content, } @@ -206,7 +204,6 @@ pub struct StrikeNode { pub extent: Length, /// The content to strike through. - #[positional] #[required] pub body: Content, } diff --git a/library/src/text/misc.rs b/library/src/text/misc.rs index 64ab5bd20..60521f12c 100644 --- a/library/src/text/misc.rs +++ b/library/src/text/misc.rs @@ -94,7 +94,6 @@ pub struct StrongNode { pub delta: i64, /// The content to strongly emphasize. - #[positional] #[required] pub body: Content, } @@ -155,7 +154,6 @@ impl Fold for Delta { #[node(Show)] pub struct EmphNode { /// The content to emphasize. - #[positional] #[required] pub body: Content, } @@ -196,15 +194,15 @@ impl Fold for Toggle { /// #lower[already low] /// ``` /// -/// ## Parameters -/// - text: `ToCase` (positional, required) -/// The text to convert to lowercase. -/// /// Display: Lowercase /// Category: text +/// Returns: string or content #[func] -pub fn lower(args: &mut Args) -> SourceResult { - case(Case::Lower, args) +pub fn lower( + /// The text to convert to lowercase. + text: ToCase, +) -> Value { + case(text, Case::Lower) } /// Convert text or content to uppercase. @@ -216,23 +214,23 @@ pub fn lower(args: &mut Args) -> SourceResult { /// #upper[ALREADY HIGH] /// ``` /// -/// ## Parameters -/// - text: `ToCase` (positional, required) -/// The text to convert to uppercase. -/// /// Display: Uppercase /// Category: text +/// Returns: string or content #[func] -pub fn upper(args: &mut Args) -> SourceResult { - case(Case::Upper, args) +pub fn upper( + /// The text to convert to uppercase. + text: ToCase, +) -> Value { + case(text, Case::Upper) } /// Change the case of text. -fn case(case: Case, args: &mut Args) -> SourceResult { - Ok(match args.expect("string or content")? { +fn case(text: ToCase, case: Case) -> Value { + match text { ToCase::Str(v) => Value::Str(case.apply(&v).into()), ToCase::Content(v) => Value::Content(v.styled(TextNode::set_case(Some(case)))), - }) + } } /// A value whose case can be changed. @@ -302,16 +300,15 @@ cast_to_value! { /// #lorem(40) /// ``` /// -/// ## Parameters -/// - text: `Content` (positional, required) -/// The text to display to small capitals. -/// /// Display: Small Capitals /// Category: text +/// Returns: content #[func] -pub fn smallcaps(args: &mut Args) -> SourceResult { - let body: Content = args.expect("content")?; - Ok(Value::Content(body.styled(TextNode::set_smallcaps(true)))) +pub fn smallcaps( + /// The text to display to small capitals. + body: Content, +) -> Value { + Value::Content(body.styled(TextNode::set_smallcaps(true))) } /// Create blind text. @@ -330,16 +327,13 @@ pub fn smallcaps(args: &mut Args) -> SourceResult { /// #lorem(15) /// ``` /// -/// ## Parameters -/// - words: `usize` (positional, required) -/// The length of the blind text in words. -/// -/// - returns: string -/// /// Display: Blind Text /// Category: text +/// Returns: string #[func] -pub fn lorem(args: &mut Args) -> SourceResult { - let words: usize = args.expect("number of words")?; - Ok(Value::Str(lipsum::lipsum(words).replace("--", "–").into())) +pub fn lorem( + /// The length of the blind text in words. + words: usize, +) -> Value { + Value::Str(lipsum::lipsum(words).replace("--", "–").into()) } diff --git a/library/src/text/mod.rs b/library/src/text/mod.rs index 83f4e2d74..a81ef3d71 100644 --- a/library/src/text/mod.rs +++ b/library/src/text/mod.rs @@ -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 /// Category: text #[node(Construct)] @@ -447,9 +443,13 @@ pub struct TextNode { #[fold] pub features: FontFeatures, + /// Content in which all text is styled according to the other arguments. + #[external] + #[required] + pub body: Content, + /// The text. #[internal] - #[positional] #[required] pub text: EcoString, diff --git a/library/src/text/raw.rs b/library/src/text/raw.rs index 36a0fc784..3768e65ed 100644 --- a/library/src/text/raw.rs +++ b/library/src/text/raw.rs @@ -57,7 +57,6 @@ pub struct RawNode { /// 1 + 2 + 3 + 4 + 5 /// ``` /// ```` - #[positional] #[required] pub text: EcoString, diff --git a/library/src/text/shift.rs b/library/src/text/shift.rs index ccdb01979..acd46d4e1 100644 --- a/library/src/text/shift.rs +++ b/library/src/text/shift.rs @@ -42,7 +42,6 @@ pub struct SubNode { pub size: TextSize, /// The text to display in subscript. - #[positional] #[required] pub body: Content, } @@ -110,7 +109,6 @@ pub struct SuperNode { pub size: TextSize, /// The text to display in superscript. - #[positional] #[required] pub body: Content, } diff --git a/library/src/visualize/image.rs b/library/src/visualize/image.rs index 1fdb418b4..78f477f69 100644 --- a/library/src/visualize/image.rs +++ b/library/src/visualize/image.rs @@ -23,7 +23,6 @@ use crate::prelude::*; #[node(Layout)] pub struct ImageNode { /// Path to an image file. - #[positional] #[required] #[parse( let Spanned { v: path, span } = diff --git a/library/src/visualize/line.rs b/library/src/visualize/line.rs index 015abbb0f..b39170f0f 100644 --- a/library/src/visualize/line.rs +++ b/library/src/visualize/line.rs @@ -21,15 +21,15 @@ pub struct LineNode { /// The offset from `start` where the line ends. #[resolve] - pub end: Smart>>, + pub end: Option>>, - /// The line's length. Mutually exclusive with `end`. + /// The line's length. This is only respected if `end` is `none`. #[resolve] #[default(Abs::pt(30.0).into())] pub length: Rel, - /// The angle at which the line points away from the origin. Mutually - /// exclusive with `end`. + /// The angle at which the line points away from the origin. This is only + /// respected if `end` is `none`. pub angle: Angle, /// How to stroke the line. This can be: diff --git a/library/src/visualize/shape.rs b/library/src/visualize/shape.rs index ab9538465..02b45ed59 100644 --- a/library/src/visualize/shape.rs +++ b/library/src/visualize/shape.rs @@ -176,15 +176,15 @@ impl Layout for RectNode { /// ] /// ``` /// -/// ## Parameters -/// - size: `Smart` (named, settable) -/// The square's side length. This is mutually exclusive with `width` and -/// `height`. -/// /// Display: Square /// Category: visualize #[node(Layout)] pub struct SquareNode { + /// The square's side length. This is mutually exclusive with `width` and + /// `height`. + #[external] + pub size: Smart, + /// 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 @@ -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 /// Category: visualize #[node(Layout)] 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 /// `height`. /// diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 1a06b1d3a..8b4280fee 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -13,5 +13,5 @@ bench = false [dependencies] proc-macro2 = "1" quote = "1" -syn = { version = "1", features = ["full"] } +syn = { version = "1", features = ["full", "extra-traits"] } unscanny = "0.1" diff --git a/macros/src/func.rs b/macros/src/func.rs index 843c193eb..87324120f 100644 --- a/macros/src/func.rs +++ b/macros/src/func.rs @@ -1,183 +1,183 @@ +use quote::ToTokens; + use super::*; /// Expand the `#[func]` macro. -pub fn func(mut item: syn::Item) -> Result { - let attrs = match &mut item { - syn::Item::Struct(item) => &mut item.attrs, - syn::Item::Fn(item) => &mut item.attrs, - _ => bail!(item, "expected struct or fn"), - }; - - let docs = documentation(&attrs); - - let mut lines: Vec<_> = docs.lines().collect(); - let Some(category) = lines.pop().and_then(|s| s.strip_prefix("Category: ")) else { - bail!(item, "expected category"); - }; - let Some(display) = lines.pop().and_then(|s| s.strip_prefix("Display: ")) else { - bail!(item, "expected display name"); - }; - - let mut docs = lines.join("\n"); - let (params, returns) = params(&mut docs)?; - 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::(#info) - } - } - }) - } +pub fn func(item: syn::ItemFn) -> Result { + let func = prepare(&item)?; + Ok(create(&func)) } -/// Extract a section. -fn section(docs: &mut String, title: &str, level: usize) -> Option { - let hashtags = "#".repeat(level); - let needle = format!("\n{hashtags} {title}\n"); - let start = docs.find(&needle)?; - let rest = &docs[start..]; - let len = rest[1..] - .find("\n# ") - .or_else(|| rest[1..].find("\n## ")) - .or_else(|| rest[1..].find("\n### ")) - .map(|x| 1 + x) - .unwrap_or(rest.len()); - let end = start + len; - let section = docs[start + needle.len()..end].trim().to_owned(); - docs.replace_range(start..end, ""); - Some(section) +struct Func { + name: String, + display: String, + category: String, + docs: String, + vis: syn::Visibility, + ident: Ident, + params: Vec, + returns: Vec, + body: syn::Block, } -/// Parse the parameter section. -fn params(docs: &mut String) -> Result<(Vec, Vec)> { - let Some(section) = section(docs, "Parameters", 2) else { - return Ok((vec![], vec![])); - }; +struct Param { + name: String, + docs: String, + external: bool, + named: bool, + variadic: bool, + default: Option, + ident: Ident, + ty: syn::Type, +} - let mut s = Scanner::new(§ion); - let mut infos = vec![]; - let mut returns = vec![]; +fn prepare(item: &syn::ItemFn) -> Result { + let sig = &item.sig; - while s.eat_if('-') { - let mut named = false; - let mut positional = false; - let mut required = false; - let mut variadic = false; - let mut settable = false; + let mut params = vec![]; + for input in &sig.inputs { + let syn::FnArg::Typed(typed) = input else { + bail!(input, "self is not allowed here"); + }; - s.eat_whitespace(); - let name = s.eat_until(':'); - s.expect(": "); + let syn::Pat::Ident(syn::PatIdent { + by_ref: None, + mutability: None, + ident, + .. + }) = &*typed.pat else { + bail!(typed.pat, "expected identifier"); + }; - if name == "returns" { - returns = s - .eat_until('\n') - .split(" or ") - .map(str::trim) - .map(Into::into) - .collect(); - s.eat_whitespace(); - continue; + if sig.output.to_token_stream().to_string() != "-> Value" { + bail!(sig.output, "must return `Value`"); } - s.expect('`'); - let ty: syn::Type = syn::parse_str(s.eat_until('`'))?; - s.expect('`'); - s.eat_whitespace(); - s.expect('('); - - for part in s.eat_until(')').split(',').map(str::trim).filter(|s| !s.is_empty()) { - match part { - "positional" => positional = true, - "named" => named = true, - "required" => required = true, - "variadic" => variadic = true, - "settable" => settable = true, - _ => bail!(callsite, "unknown parameter flag {:?}", part), - } - } - - if (!named && !positional) || (variadic && !positional) { - bail!(callsite, "invalid combination of parameter flags"); - } - - s.expect(')'); - - let docs = dedent(s.eat_until("\n-").trim()); - let docs = docs.trim(); - - infos.push(quote! { - ::typst::eval::ParamInfo { - name: #name, - docs: #docs, - cast: <#ty as ::typst::eval::Cast< - ::typst::syntax::Spanned<::typst::eval::Value> - >>::describe(), - positional: #positional, - named: #named, - variadic: #variadic, - required: #required, - settable: #settable, - } + let mut attrs = typed.attrs.clone(); + params.push(Param { + name: kebab_case(ident), + docs: documentation(&attrs), + external: has_attr(&mut attrs, "external"), + named: has_attr(&mut attrs, "named"), + variadic: has_attr(&mut attrs, "variadic"), + default: parse_attr(&mut attrs, "default")?.map(|expr| { + expr.unwrap_or_else( + || parse_quote! { ::std::default::Default::default() }, + ) + }), + ident: ident.clone(), + ty: (*typed.ty).clone(), }); - s.eat_whitespace(); + validate_attrs(&attrs)?; } - Ok((infos, returns)) + let docs = documentation(&item.attrs); + let mut lines = docs.split("\n").collect(); + let returns = meta_line(&mut lines, "Returns")? + .split(" or ") + .map(Into::into) + .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 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) +} + +fn create(func: &Func) -> TokenStream { + let Func { + name, + display, + category, + docs, + vis, + ident, + params, + returns, + body, + .. + } = func; + 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, + }), + } + } + } +} + +/// Create a parameter info for a field. +fn create_param_info(param: &Param) -> TokenStream { + let Param { name, docs, named, variadic, ty, default, .. } = param; + let positional = !named; + let required = default.is_none(); + let ty = if *variadic { + quote! { <#ty as ::typst::eval::Variadics>::Inner } + } else { + quote! { #ty } + }; + quote! { + ::typst::eval::ParamInfo { + name: #name, + docs: #docs, + cast: <#ty as ::typst::eval::Cast< + ::typst::syntax::Spanned<::typst::eval::Value> + >>::describe(), + positional: #positional, + named: #named, + variadic: #variadic, + required: #required, + settable: false, + } + } +} + +/// 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) = ¶m.default { + value = quote! { #value.unwrap_or_else(|| #default) } + } + + quote! { let #ident: #ty = #value; } } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index c1a8b2ae1..889eaa7bf 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -16,14 +16,13 @@ use syn::ext::IdentExt; use syn::parse::{Parse, ParseStream, Parser}; use syn::punctuated::Punctuated; use syn::{parse_quote, Ident, Result, Token}; -use unscanny::Scanner; use self::util::*; -/// Implement `FuncType` for a type or function. +/// Turns a function into a `NativeFunc`. #[proc_macro_attribute] 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() } diff --git a/macros/src/node.rs b/macros/src/node.rs index 739cc79df..92faf7ddc 100644 --- a/macros/src/node.rs +++ b/macros/src/node.rs @@ -7,45 +7,35 @@ pub fn node(stream: TokenStream, body: syn::ItemStruct) -> Result { } struct Node { - attrs: Vec, + name: String, + display: String, + category: String, + docs: String, vis: syn::Visibility, ident: Ident, - name: String, capable: Vec, fields: Vec, } -impl Node { - fn inherent(&self) -> impl Iterator { - self.fields.iter().filter(|field| field.inherent()) - } - - fn settable(&self) -> impl Iterator { - self.fields.iter().filter(|field| field.settable()) - } -} - struct Field { - attrs: Vec, - vis: syn::Visibility, - name: String, - ident: Ident, - ident_in: Ident, - with_ident: Ident, - set_ident: Ident, - + docs: String, internal: bool, + external: bool, positional: bool, required: bool, variadic: bool, fold: bool, resolve: bool, parse: Option, - + default: syn::Expr, + vis: syn::Visibility, + ident: Ident, + ident_in: Ident, + with_ident: Ident, + set_ident: Ident, ty: syn::Type, output: syn::Type, - default: syn::Expr, } impl Field { @@ -87,34 +77,30 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result { let mut attrs = field.attrs.clone(); 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 { - vis: field.vis.clone(), - name: kebab_case(&ident), - ident: ident.clone(), - 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()), - + docs: documentation(&attrs), internal: has_attr(&mut attrs, "internal"), - positional: has_attr(&mut attrs, "positional") || variadic, - required: has_attr(&mut attrs, "required") || variadic, + external: has_attr(&mut attrs, "external"), + positional, + required, variadic, fold: has_attr(&mut attrs, "fold"), resolve: has_attr(&mut attrs, "resolve"), parse: parse_attr(&mut attrs, "parse")?.flatten(), - - ty: field.ty.clone(), - output: field.ty.clone(), default: parse_attr(&mut attrs, "default")? .flatten() .unwrap_or_else(|| parse_quote! { ::std::default::Default::default() }), - - attrs: { - validate_attrs(&attrs)?; - attrs - }, + vis: field.vis.clone(), + ident: ident.clone(), + 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()), + ty: field.ty.clone(), + output: field.ty.clone(), }; if field.required && (field.fold || field.resolve) { @@ -134,6 +120,7 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result { field.output = parse_quote! { <#output as ::typst::model::Fold>::Output }; } + validate_attrs(&attrs)?; fields.push(field); } @@ -142,32 +129,39 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result { .into_iter() .collect(); - let attrs = body.attrs.clone(); - Ok(Node { + let docs = documentation(&body.attrs); + 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(), ident: body.ident.clone(), - name: body.ident.to_string().trim_end_matches("Node").to_lowercase(), capable, fields, - attrs: { - validate_attrs(&attrs)?; - attrs - }, - }) + }; + + validate_attrs(&body.attrs)?; + Ok(node) } /// Produce the node's definition. fn create(node: &Node) -> TokenStream { - let attrs = &node.attrs; - let vis = &node.vis; - let ident = &node.ident; + let Node { vis, ident, docs, .. } = node; + let all = node.fields.iter().filter(|field| !field.external); + let settable = all.clone().filter(|field| field.settable()); // Inherent methods and functions. let new = create_new_func(node); - let field_methods = node.fields.iter().map(create_field_method); - let field_in_methods = node.settable().map(create_field_in_method); - let with_fields_methods = node.fields.iter().map(create_with_field_method); - let field_style_methods = node.settable().map(create_set_field_method); + let field_methods = all.clone().map(create_field_method); + let field_in_methods = settable.clone().map(create_field_in_method); + let with_fields_methods = all.map(create_with_field_method); + let field_style_methods = settable.map(create_set_field_method); // Trait implementations. let construct = node @@ -179,8 +173,7 @@ fn create(node: &Node) -> TokenStream { let node = create_node_impl(node); quote! { - #(#attrs)* - #[::typst::eval::func] + #[doc = #docs] #[derive(Debug, Clone, Hash)] #[repr(transparent)] #vis struct #ident(::typst::model::Content); @@ -212,10 +205,11 @@ fn create(node: &Node) -> TokenStream { /// Create the `new` function for the node. 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 } }); - let builder_calls = node.inherent().map(|Field { ident, with_ident, .. }| { + let builder_calls = relevant.map(|Field { ident, with_ident, .. }| { quote! { .#with_ident(#ident) } }); quote! { @@ -229,10 +223,10 @@ fn create_new_func(node: &Node) -> TokenStream { /// Create an accessor methods for a field. 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() { quote! { - #(#attrs)* + #[doc = #docs] #vis fn #ident(&self) -> #output { self.0.cast_required_field(#name) } @@ -241,7 +235,7 @@ fn create_field_method(field: &Field) -> TokenStream { let access = create_style_chain_access(field, quote! { self.0.field(#name).cloned() }); quote! { - #(#attrs)* + #[doc = #docs] #vis fn #ident(&self, styles: ::typst::model::StyleChain) -> #output { #access } @@ -312,8 +306,7 @@ fn create_set_field_method(field: &Field) -> TokenStream { /// Create the node's `Node` implementation. fn create_node_impl(node: &Node) -> TokenStream { - let ident = &node.ident; - let name = &node.name; + let Node { ident, name, display, category, docs, .. } = node; let vtable_func = create_vtable_func(node); let infos = node .fields @@ -322,10 +315,6 @@ fn create_node_impl(node: &Node) -> TokenStream { .map(create_param_info); quote! { impl ::typst::model::Node for #ident { - fn pack(self) -> ::typst::model::Content { - self.0 - } - fn id() -> ::typst::model::NodeId { static META: ::typst::model::NodeMeta = ::typst::model::NodeMeta { name: #name, @@ -334,8 +323,24 @@ fn create_node_impl(node: &Node) -> TokenStream { ::typst::model::NodeId::from_meta(&META) } - fn params() -> ::std::vec::Vec<::typst::eval::ParamInfo> { - ::std::vec![#(#infos),*] + fn pack(self) -> ::typst::model::Content { + self.0 + } + + fn func() -> ::typst::eval::NodeFunc { + ::typst::eval::NodeFunc { + id: Self::id(), + construct: ::construct, + 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. 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 settable = field.settable(); - let docs = documentation(&field.attrs); - let docs = docs.trim(); + let ty = if *variadic { + quote! { <#ty as ::typst::eval::Variadics>::Inner } + } else { + quote! { #ty } + }; quote! { ::typst::eval::ParamInfo { name: #name, @@ -393,7 +401,7 @@ fn create_construct_impl(node: &Node) -> TokenStream { let handlers = node .fields .iter() - .filter(|field| !field.internal || field.parse.is_some()) + .filter(|field| !field.external && (!field.internal || field.parse.is_some())) .map(|field| { let with_ident = &field.with_ident; let (prefix, value) = create_field_parser(field); @@ -432,7 +440,11 @@ fn create_set_impl(node: &Node) -> TokenStream { let handlers = node .fields .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| { let set_ident = &field.set_ident; 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. fn create_field_parser(field: &Field) -> (TokenStream, TokenStream) { - let name = &field.name; if let Some(FieldParser { prefix, expr }) = &field.parse { return (quote! { #(#prefix);* }, quote! { #expr }); } + let name = &field.name; let value = if field.variadic { quote! { args.all()? } } else if field.required { diff --git a/macros/src/util.rs b/macros/src/util.rs index c8c56a054..d94ba932e 100644 --- a/macros/src/util.rs +++ b/macros/src/util.rs @@ -58,14 +58,6 @@ pub fn kebab_case(name: &Ident) -> String { 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::>() - .join("\n") -} - /// Extract documentation comments from an attribute list. pub fn documentation(attrs: &[syn::Attribute]) -> String { let mut doc = String::new(); @@ -86,3 +78,11 @@ pub fn documentation(attrs: &[syn::Attribute]) -> String { 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), + } +} diff --git a/src/eval/cast.rs b/src/eval/cast.rs index 840ceb058..806f7e92b 100644 --- a/src/eval/cast.rs +++ b/src/eval/cast.rs @@ -1,6 +1,6 @@ pub use typst_macros::{cast_from_value, cast_to_value}; -use std::num::NonZeroUsize; +use std::num::{NonZeroI64, NonZeroUsize}; use std::ops::Add; use ecow::EcoString; @@ -127,6 +127,20 @@ cast_to_value! { 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! { char, string: Str => { @@ -211,6 +225,16 @@ impl> From> for Value { } } +/// A container for a variadic argument. +pub trait Variadics { + /// The contained type. + type Inner; +} + +impl Variadics for Vec { + type Inner = T; +} + /// Describes a possible value for a cast. #[derive(Debug, Clone, Hash)] pub enum CastInfo { diff --git a/src/eval/func.rs b/src/eval/func.rs index 6f98e3166..79ae142cb 100644 --- a/src/eval/func.rs +++ b/src/eval/func.rs @@ -6,10 +6,14 @@ use std::sync::Arc; use comemo::{Prehashed, Track, Tracked, TrackedMut}; 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::model::{Node, NodeId, Selector, StyleMap}; +use crate::model::{Content, NodeId, Selector, StyleMap}; use crate::syntax::ast::{self, AstNode, Expr}; use crate::syntax::{SourceId, Span, SyntaxNode}; use crate::util::hash128; @@ -22,8 +26,10 @@ pub struct Func(Arc>, Span); /// The different kinds of function representations. #[derive(Hash)] enum Repr { - /// A native rust function. - Native(Native), + /// A native Rust function. + Native(NativeFunc), + /// A function for a node. + Node(NodeFunc), /// A user-defined closure. Closure(Closure), /// A nested function with pre-applied arguments. @@ -31,50 +37,11 @@ enum Repr { } impl Func { - /// Create a new function from a type that can be turned into a function. - pub fn from_type(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, - 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(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::()), - 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. pub fn name(&self) -> Option<&str> { match &**self.0 { Repr::Native(native) => Some(native.info.name), + Repr::Node(node) => Some(node.info.name), Repr::Closure(closure) => closure.name.as_deref(), Repr::With(func, _) => func.name(), } @@ -84,6 +51,7 @@ impl Func { pub fn info(&self) -> Option<&FuncInfo> { match &**self.0 { Repr::Native(native) => Some(&native.info), + Repr::Node(node) => Some(&node.info), Repr::With(func, _) => func.info(), _ => None, } @@ -119,6 +87,11 @@ impl Func { args.finish()?; Ok(value) } + Repr::Node(node) => { + let value = (node.construct)(vm, &mut args)?; + args.finish()?; + Ok(Value::Content(value)) + } Repr::Closure(closure) => { // Determine the route inside the closure. let fresh = Route::new(closure.location); @@ -172,8 +145,8 @@ impl Func { /// Execute the function's set rule and return the resulting style map. pub fn set(&self, mut args: Args) -> SourceResult { Ok(match &**self.0 { - Repr::Native(Native { set: Some(set), .. }) => { - let styles = set(&mut args)?; + Repr::Node(node) => { + let styles = (node.set)(&mut args)?; args.finish()?; styles } @@ -183,13 +156,13 @@ impl Func { /// Create a selector for this function's node type. pub fn select(&self, fields: Option) -> StrResult { - match **self.0 { - Repr::Native(Native { node: Some(id), .. }) => { - if id == item!(text_id) { + match &**self.0 { + Repr::Node(node) => { + if node.id == item!(text_id) { 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")?, } @@ -211,32 +184,75 @@ impl PartialEq for Func { } } -/// Types that can be turned into functions. -pub trait FuncType { - /// Create a function with the given name from this type. - fn create_func(name: &'static str) -> Func; +impl From for Func { + fn from(repr: Repr) -> Self { + Self(Arc::new(Prehashed::new(repr)), Span::detached()) + } } -/// A function defined by a native rust function or node. -struct Native { - /// The function pointer. - func: fn(&Vm, &mut Args) -> SourceResult, - /// The set rule. - set: Option SourceResult>, - /// The id of the node to customize with this function's show rule. - node: Option, - /// Documentation of the function. - info: FuncInfo, +/// A native Rust function. +pub struct NativeFunc { + /// The function's implementation. + pub func: fn(&Vm, &mut Args) -> SourceResult, + /// Details about the function. + pub info: Lazy, } -impl Hash for Native { +impl Hash for NativeFunc { fn hash(&self, state: &mut H) { (self.func as usize).hash(state); - self.set.map(|set| set as usize).hash(state); - self.node.hash(state); } } +impl From for Func { + fn from(native: NativeFunc) -> Self { + Repr::Native(native).into() + } +} + +cast_to_value! { + v: NativeFunc => Value::Func(v.into()) +} + +impl From 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, + /// The node's set rule. + pub set: fn(&mut Args) -> SourceResult, + /// Details about the function. + pub info: Lazy, +} + +impl Hash for NodeFunc { + fn hash(&self, state: &mut H) { + self.id.hash(state); + (self.construct as usize).hash(state); + (self.set as usize).hash(state); + } +} + +impl From for Func { + fn from(node: NodeFunc) -> Self { + Repr::Node(node).into() + } +} + +cast_to_value! { + v: NodeFunc => Value::Func(v.into()) +} + /// Details about a function. #[derive(Debug, Clone)] pub struct FuncInfo { @@ -375,6 +391,16 @@ impl Closure { } } +impl From 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. pub(super) struct CapturesVisitor<'a> { external: &'a Scopes<'a>, diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 0e0828e38..fcfda2634 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -20,6 +20,9 @@ mod ops; mod scope; mod symbol; +#[doc(hidden)] +pub use once_cell::sync::Lazy; + pub use self::args::*; pub use self::array::*; pub use self::cast::*; @@ -1152,7 +1155,7 @@ impl Eval for ast::Closure { body: self.body(), }; - Ok(Value::Func(Func::from_closure(closure, self.span()))) + Ok(Value::Func(Func::from(closure).spanned(self.span()))) } } diff --git a/src/eval/scope.rs b/src/eval/scope.rs index f6bd2164e..d15900634 100644 --- a/src/eval/scope.rs +++ b/src/eval/scope.rs @@ -4,7 +4,7 @@ use std::hash::Hash; use ecow::EcoString; -use super::{Func, FuncType, Library, Value}; +use super::{Library, Value}; use crate::diag::StrResult; /// A stack of scopes. @@ -96,11 +96,6 @@ impl Scope { self.0.insert(name, Slot::new(value.into(), Kind::Normal)); } - /// Define a function through a native rust function. - pub fn def_func(&mut self, name: &'static str) { - self.define(name, Func::from_type::(name)); - } - /// Define a captured, immutable binding. pub fn define_captured( &mut self, diff --git a/src/ide/complete.rs b/src/ide/complete.rs index f5eece93d..2e810ee98 100644 --- a/src/ide/complete.rs +++ b/src/ide/complete.rs @@ -627,10 +627,6 @@ fn param_completions( } } - if callee.as_str() == "text" { - ctx.font_completions(); - } - if ctx.before.ends_with(',') { ctx.enrich(" ", ""); } @@ -653,7 +649,7 @@ fn named_param_value_completions( ctx.cast_completions(¶m.cast); - if callee.as_str() == "text" && name == "family" { + if callee.as_str() == "text" && name == "font" { ctx.font_completions(); } diff --git a/src/model/content.rs b/src/model/content.rs index 6b4f5e5d4..d845ce1e7 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -9,7 +9,7 @@ use ecow::{EcoString, EcoVec}; use super::{node, Guard, Recipe, Style, StyleMap}; 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::World; @@ -330,12 +330,10 @@ impl Sum for Content { #[node] pub struct StyledNode { /// The styles. - #[positional] #[required] pub map: StyleMap, /// The styled content. - #[positional] #[required] pub body: Content, } @@ -353,7 +351,6 @@ cast_from_value! { /// Category: special #[node] pub struct SequenceNode { - #[positional] #[variadic] pub children: Vec, } @@ -370,14 +367,14 @@ impl Debug for Label { /// A constructable, stylable content node. pub trait Node: Construct + Set + Sized + 'static { - /// Pack a node into type-erased content. - fn pack(self) -> Content; - /// The node's ID. fn id() -> NodeId; - /// List the fields of the node. - fn params() -> Vec; + /// Pack a node into type-erased content. + fn pack(self) -> Content; + + /// The node's function. + fn func() -> NodeFunc; } /// A unique identifier for a node. @@ -425,6 +422,7 @@ pub struct NodeMeta { pub vtable: fn(of: TypeId) -> Option<*const ()>, } +/// A node's constructor function. pub trait Construct { /// Construct a node from the arguments. /// @@ -433,6 +431,7 @@ pub trait Construct { fn construct(vm: &Vm, args: &mut Args) -> SourceResult; } +/// A node's set rule. pub trait Set { /// Parse relevant arguments into style properties for this node. fn set(args: &mut Args) -> SourceResult; diff --git a/src/model/mod.rs b/src/model/mod.rs index 07329e3f3..6015c3650 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -11,6 +11,4 @@ pub use self::realize::*; pub use self::styles::*; pub use self::typeset::*; -#[doc(hidden)] -pub use once_cell; pub use typst_macros::node; diff --git a/src/model/styles.rs b/src/model/styles.rs index 0b74e1621..3239bb177 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -86,7 +86,7 @@ impl Debug for StyleMap { } } -/// A single style property, recipe or barrier. +/// A single style property or recipe. #[derive(Clone, Hash)] pub enum Style { /// A style property originating from a set rule or constructor. diff --git a/tests/src/tests.rs b/tests/src/tests.rs index b35ddccef..8a25af15b 100644 --- a/tests/src/tests.rs +++ b/tests/src/tests.rs @@ -11,7 +11,7 @@ use comemo::{Prehashed, Track}; use elsa::FrozenVec; use once_cell::unsync::OnceCell; 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::eval::{func, Library, Value}; use typst::font::{Font, FontBook}; @@ -148,29 +148,29 @@ impl Args { fn library() -> Library { /// Display: Test /// Category: test + /// Returns: #[func] - fn test(args: &mut typst::eval::Args) -> SourceResult { - let lhs = args.expect::("left-hand side")?; - let rhs = args.expect::("right-hand side")?; + fn test(lhs: Value, rhs: Value) -> Value { if lhs != rhs { bail!(args.span, "Assertion failed: {:?} != {:?}", lhs, rhs,); } - Ok(Value::None) + Value::None } /// Display: Print /// Category: test + /// Returns: #[func] - fn print(args: &mut typst::eval::Args) -> SourceResult { + fn print(#[variadic] values: Vec) -> Value { print!("> "); - for (i, value) in args.all::()?.into_iter().enumerate() { + for (i, value) in values.into_iter().enumerate() { if i > 0 { print!(", ") } print!("{value:?}"); } println!(); - Ok(Value::None) + Value::None } 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()))); // Hook up helpers into the global scope. - lib.global.scope_mut().def_func::("test"); - lib.global.scope_mut().def_func::("print"); + lib.global.scope_mut().define("test", test); + lib.global.scope_mut().define("print", print); lib.global .scope_mut() .define("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF)); diff --git a/tests/typ/compute/calc.typ b/tests/typ/compute/calc.typ index 675794937..ee0637070 100644 --- a/tests/typ/compute/calc.typ +++ b/tests/typ/compute/calc.typ @@ -77,7 +77,7 @@ #test(calc.min("hi"), "hi") --- -// Error: 10-12 missing argument: value +// Error: 10-12 expected at least one value #calc.min() --- @@ -109,5 +109,5 @@ #range(4, step: "one") --- -// Error: 18-19 step must not be zero +// Error: 18-19 number must be positive #range(10, step: 0) diff --git a/tests/typ/text/lorem.typ b/tests/typ/text/lorem.typ index 944fd5be4..92dfbba41 100644 --- a/tests/typ/text/lorem.typ +++ b/tests/typ/text/lorem.typ @@ -28,5 +28,5 @@ } --- -// Error: 7-9 missing argument: number of words +// Error: 7-9 missing argument: words #lorem()