diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-03-10 12:55:21 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-03-10 12:55:21 +0100 |
| commit | 62f35602a87574dcc607f1637aeae1be574981ff (patch) | |
| tree | 363a1918006e06d7d79dc2ace5f8e59cd3b6bb19 /library/src/compute | |
| parent | c38d72383d2068361d635d6c1c78ba97aa917801 (diff) | |
New #[func] macro
Diffstat (limited to 'library/src/compute')
| -rw-r--r-- | library/src/compute/calc.rs | 443 | ||||
| -rw-r--r-- | library/src/compute/construct.rs | 264 | ||||
| -rw-r--r-- | library/src/compute/data.rs | 97 | ||||
| -rw-r--r-- | library/src/compute/foundations.rs | 83 |
4 files changed, 414 insertions, 473 deletions
diff --git a/library/src/compute/calc.rs b/library/src/compute/calc.rs index d4a4bcf6..25df817d 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::<AbsFunc>("abs"); - scope.def_func::<PowFunc>("pow"); - scope.def_func::<SqrtFunc>("sqrt"); - scope.def_func::<SinFunc>("sin"); - scope.def_func::<CosFunc>("cos"); - scope.def_func::<TanFunc>("tan"); - scope.def_func::<AsinFunc>("asin"); - scope.def_func::<AcosFunc>("acos"); - scope.def_func::<AtanFunc>("atan"); - scope.def_func::<SinhFunc>("sinh"); - scope.def_func::<CoshFunc>("cosh"); - scope.def_func::<TanhFunc>("tanh"); - scope.def_func::<LogFunc>("log"); - scope.def_func::<FloorFunc>("floor"); - scope.def_func::<CeilFunc>("ceil"); - scope.def_func::<RoundFunc>("round"); - scope.def_func::<ClampFunc>("clamp"); - scope.def_func::<MinFunc>("min"); - scope.def_func::<MaxFunc>("max"); - scope.def_func::<EvenFunc>("even"); - scope.def_func::<OddFunc>("odd"); - scope.def_func::<ModFunc>("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<Value> { - Ok(args.expect::<ToAbs>("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<Value> { - let base = args.expect::<Num>("base")?; - let exponent = args - .expect::<Spanned<Num>>("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<Num>, +) -> 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<Value> { /// #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<Value> { - let value = args.expect::<Spanned<Num>>("value")?; +pub fn sqrt( + /// The number whose square root to calculate. Must be non-negative. + value: Spanned<Num>, +) -> 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<Value> { /// #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<Value> { - let arg = args.expect::<AngleLike>("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<Value> { /// /// ## 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<Value> { - let arg = args.expect::<AngleLike>("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<Value> { /// #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<Value> { - let arg = args.expect::<AngleLike>("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<Value> { /// #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<Value> { - let Spanned { v, span } = args.expect::<Spanned<Num>>("value")?; - let val = v.float(); +pub fn asin( + /// The number whose arcsine to calculate. Must be between -1 and 1. + value: Spanned<Num>, +) -> 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<Value> { /// #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<Value> { - let Spanned { v, span } = args.expect::<Spanned<Num>>("value")?; - let val = v.float(); +pub fn acos( + /// The number whose arcsine to calculate. Must be between -1 and 1. + value: Spanned<Num>, +) -> 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<Value> { /// #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<Value> { - let value = args.expect::<Num>("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<Value> { /// #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<Value> { - let arg = args.expect::<AngleLike>("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<Value> { /// #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<Value> { - let arg = args.expect::<AngleLike>("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<Value> { /// #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<Value> { - let arg = args.expect::<AngleLike>("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<Value> { /// /// ## 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<Value> { - let value = args.expect::<f64>("value")?; - let base = args.named::<f64>("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<Value> { /// #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<Value> { - let value = args.expect::<Num>("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<Value> { /// #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<Value> { - let value = args.expect::<Num>("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<Value> { /// #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<Value> { - let value = args.expect::<Num>("value")?; - let digits = args.named::<u32>("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<Value> { /// #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<Value> { - let value = args.expect::<Num>("value")?; - let min = args.expect::<Num>("min")?; - let max = args.expect::<Spanned<Num>>("max")?; +pub fn clamp( + /// The number to clamp. + value: Num, + /// The inclusive minimum value. + min: Num, + /// The inclusive maximum value. + max: Spanned<Num>, +) -> 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<Value> { /// #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<Value> { - minmax(args, Ordering::Less) +pub fn min( + /// The sequence of values from which to extract the minimum. + /// Must not be empty. + #[variadic] + values: Vec<Spanned<Value>>, +) -> Value { + minmax(args.span, values, Ordering::Less)? } /// Determine the maximum of a sequence of values. @@ -523,24 +508,31 @@ pub fn min(args: &mut Args) -> SourceResult<Value> { /// #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<Value> { - minmax(args, Ordering::Greater) +pub fn max( + /// The sequence of values from which to extract the maximum. + /// Must not be empty. + #[variadic] + values: Vec<Spanned<Value>>, +) -> Value { + minmax(args.span, values, Ordering::Greater)? } /// Find the minimum or maximum of a sequence of values. -fn minmax(args: &mut Args, goal: Ordering) -> SourceResult<Value> { - let mut extremum = args.expect::<Value>("value")?; - for Spanned { v, span } in args.all::<Spanned<Value>>()? { +fn minmax( + span: Span, + values: Vec<Spanned<Value>>, + goal: Ordering, +) -> SourceResult<Value> { + let mut iter = values.into_iter(); + let Some(Spanned { v: mut extremum, ..}) = iter.next() else { + bail!(span, "expected at least one value"); + }; + + for Spanned { v, span } in iter { match v.partial_cmp(&extremum) { Some(ordering) => { if ordering == goal { @@ -555,6 +547,7 @@ fn minmax(args: &mut Args, goal: Ordering) -> SourceResult<Value> { ), } } + Ok(extremum) } @@ -567,17 +560,15 @@ fn minmax(args: &mut Args, goal: Ordering) -> SourceResult<Value> { /// #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<Value> { - Ok(Value::Bool(args.expect::<i64>("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<Value> { /// #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<Value> { - Ok(Value::Bool(args.expect::<i64>("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<Value> { /// #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<Value> { - let dividend = args.expect::<Num>("dividend")?; - let Spanned { v: divisor, span } = args.expect::<Spanned<Num>>("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<Num>, +) -> Value { + if divisor.v.float() == 0.0 { + bail!(divisor.span, "divisor must not be zero"); } - Ok(dividend.apply2(divisor, Rem::rem, Rem::rem)) + dividend.apply2(divisor.v, Rem::rem, Rem::rem) } /// A value which can be passed to functions that work with integers and floats. diff --git a/library/src/compute/construct.rs b/library/src/compute/construct.rs index db442327..4d6068a1 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<Value> { - Ok(Value::Int(args.expect::<ToInt>("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<Value> { - Ok(Value::Float(args.expect::<ToFloat>("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<Value> { - 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<Value> { /// #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<Value> { - Ok(Value::Color(if let Some(string) = args.find::<Spanned<EcoString>>()? { +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::<Spanned<EcoString>>()? { 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<Value> { 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<Value> { - 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<Value> { +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<Spanned<Variant>>, +) -> Value { let mut list = EcoVec::new(); - for Spanned { v, span } in args.all::<Spanned<Variant>>()? { + for Spanned { v, span } in variants { if list.iter().any(|(prev, _)| &v.0 == prev) { 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(<intro>) /// ``` /// -/// ## 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<Value> { - Ok(Value::Str(args.expect::<ToStr>("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<Value> { - 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<Value> { /// .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<Value> { - let Spanned { v, span } = args.expect::<Spanned<EcoString>>("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<EcoString>, +) -> 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<Value> { /// #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<Value> { +pub fn range( + /// The start of the range (inclusive). + #[external] + #[default] + start: i64, + /// The end of the range (exclusive). + #[external] + end: i64, + /// The distance between the generated numbers. + #[named] + #[default(NonZeroI64::new(1).unwrap())] + step: NonZeroI64, +) -> Value { let first = args.expect::<i64>("end")?; let (start, end) = match args.eat::<i64>()? { 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<Value> { x += step; } - Ok(Value::Array(array)) + Value::Array(array) } diff --git a/library/src/compute/data.rs b/library/src/compute/data.rs index 90d72ade..7addff78 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<Value> { - let Spanned { v: path, span } = args.expect::<Spanned<EcoString>>("path to file")?; - +pub fn read( + /// Path to a file. + path: Spanned<EcoString>, +) -> 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<Value> { /// ) /// ``` /// -/// ## 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<Value> { - let Spanned { v: path, span } = - args.expect::<Spanned<EcoString>>("path to csv file")?; - +pub fn csv( + /// Path to a CSV file. + path: Spanned<EcoString>, + /// 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>("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<Value> { 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<Value> { - let Spanned { v: path, span } = - args.expect::<Spanned<EcoString>>("path to json file")?; - +pub fn json( + /// Path to a JSON file. + path: Spanned<EcoString>, +) -> 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<Value> { - let Spanned { v: path, span } = - args.expect::<Spanned<EcoString>>("path to xml file")?; - +pub fn xml( + /// Path to an XML file. + path: Spanned<EcoString>, +) -> 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 710ec68e..41a6bc35 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<Value> { - Ok(args.expect::<Value>("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<Value> { /// #[*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<Value> { - Ok(args.expect::<Value>("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<Value> { /// #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<Value> { - match args.eat::<Value>()? { +pub fn panic( + /// The value (or message) to panic with. + #[default] + payload: Option<Value>, +) -> 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<Value> { /// #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<Value> { - let check = args.expect::<bool>("condition")?; - let message = args.named::<EcoString>("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<EcoString>, +) -> 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<Value> { /// #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<Value> { - let Spanned { v: text, span } = args.expect::<Spanned<String>>("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<String>, +) -> Value { + let Spanned { v: text, span } = source; + typst::eval::eval_code_str(vm.world(), &text, span)? } |
