diff --git a/Cargo.lock b/Cargo.lock index c515fb2cc..ba62d6b76 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2488,7 +2488,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.16", ] [[package]] diff --git a/docs/src/lib.rs b/docs/src/lib.rs index 4cb47283f..bc3be0cc2 100644 --- a/docs/src/lib.rs +++ b/docs/src/lib.rs @@ -415,7 +415,8 @@ fn func_model( name = name .strip_prefix(parent) .or(name.strip_prefix(parent.strip_suffix('s').unwrap_or(parent))) - .unwrap_or(name); + .unwrap_or(name) + .trim_matches('-'); } path.push(name); @@ -429,6 +430,13 @@ fn func_model( }) .collect(); + let mut returns = vec![]; + casts(resolver, &mut returns, &mut vec![], &info.returns); + returns.sort_by_key(|ty| type_index(ty)); + if returns == ["none"] { + returns.clear(); + } + FuncModel { path, display: info.display, @@ -437,7 +445,7 @@ fn func_model( element: func.element().is_some(), details: Html::markdown_with_id_base(resolver, docs, id_base), params: info.params.iter().map(|param| param_model(resolver, param)).collect(), - returns: info.returns.clone(), + returns, methods: method_models(resolver, info.docs), scope, } diff --git a/library/src/compute/calc.rs b/library/src/compute/calc.rs index f485f8171..0fc458912 100644 --- a/library/src/compute/calc.rs +++ b/library/src/compute/calc.rs @@ -11,38 +11,38 @@ use crate::prelude::*; /// A module with computational functions. pub fn module() -> Module { let mut scope = Scope::new(); - 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("atan2", atan2); - scope.define("sinh", sinh); - scope.define("cosh", cosh); - scope.define("tanh", tanh); - scope.define("log", log); - scope.define("fact", fact); - scope.define("perm", perm); - scope.define("binom", binom); - scope.define("gcd", gcd); - scope.define("lcm", lcm); - scope.define("floor", floor); - scope.define("ceil", ceil); - scope.define("trunc", trunc); - scope.define("fract", fract); - 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("rem", rem); - scope.define("mod", mod_); - scope.define("quo", quo); + scope.define("abs", abs_func()); + scope.define("pow", pow_func()); + scope.define("sqrt", sqrt_func()); + scope.define("sin", sin_func()); + scope.define("cos", cos_func()); + scope.define("tan", tan_func()); + scope.define("asin", asin_func()); + scope.define("acos", acos_func()); + scope.define("atan", atan_func()); + scope.define("atan2", atan2_func()); + scope.define("sinh", sinh_func()); + scope.define("cosh", cosh_func()); + scope.define("tanh", tanh_func()); + scope.define("log", log_func()); + scope.define("fact", fact_func()); + scope.define("perm", perm_func()); + scope.define("binom", binom_func()); + scope.define("gcd", gcd_func()); + scope.define("lcm", lcm_func()); + scope.define("floor", floor_func()); + scope.define("ceil", ceil_func()); + scope.define("trunc", trunc_func()); + scope.define("fract", fract_func()); + scope.define("round", round_func()); + scope.define("clamp", clamp_func()); + scope.define("min", min_func()); + scope.define("max", max_func()); + scope.define("even", even_func()); + scope.define("odd", odd_func()); + scope.define("rem", rem_func()); + scope.define("mod", mod_func()); + scope.define("quo", quo_func()); scope.define("inf", Value::Float(f64::INFINITY)); scope.define("nan", Value::Float(f64::NAN)); scope.define("pi", Value::Float(std::f64::consts::PI)); @@ -61,7 +61,6 @@ pub fn module() -> Module { /// /// Display: Absolute /// Category: calculate -/// Returns: any #[func] pub fn abs( /// The value whose absolute value to calculate. @@ -71,12 +70,12 @@ pub fn abs( } /// A value of which the absolute value can be taken. -struct ToAbs(Value); +pub struct ToAbs(Value); -cast_from_value! { +cast! { ToAbs, - v: i64 => Self(Value::Int(v.abs())), - v: f64 => Self(Value::Float(v.abs())), + v: i64 => Self(v.abs().into_value()), + v: f64 => Self(v.abs().into_value()), v: Length => Self(Value::Length(v.try_abs() .ok_or("cannot take absolute value of this length")?)), v: Angle => Self(Value::Angle(v.abs())), @@ -93,17 +92,18 @@ cast_from_value! { /// /// Display: Power /// Category: calculate -/// Returns: integer or float #[func] pub fn pow( /// The base of the power. base: Num, /// The exponent of the power. Must be non-negative. exponent: Spanned, -) -> Value { + /// The callsite span. + span: Span, +) -> SourceResult { match exponent.v { _ if exponent.v.float() == 0.0 && base.float() == 0.0 => { - bail!(args.span, "zero to the power of zero is undefined") + bail!(span, "zero to the power of zero is undefined") } Num::Int(i) if i32::try_from(i).is_err() => { bail!(exponent.span, "exponent is too large") @@ -119,16 +119,16 @@ pub fn pow( .checked_pow(b as u32) .map(Num::Int) .ok_or("the result is too large") - .at(args.span)?, + .at(span)?, (a, Num::Int(b)) => Num::Float(a.float().powi(b as i32)), (a, b) => Num::Float(a.float().powf(b.float())), }; if result.float().is_nan() { - bail!(args.span, "the result is not a real number") + bail!(span, "the result is not a real number") } - result.value() + Ok(result) } /// Calculate the square root of a number. @@ -141,16 +141,15 @@ pub fn pow( /// /// Display: Square Root /// Category: calculate -/// Returns: float #[func] pub fn sqrt( /// The number whose square root to calculate. Must be non-negative. value: Spanned, -) -> Value { +) -> SourceResult { if value.v.float() < 0.0 { bail!(value.span, "cannot take square root of negative number"); } - Value::Float(value.v.float().sqrt()) + Ok(value.v.float().sqrt()) } /// Calculate the sine of an angle. @@ -167,17 +166,16 @@ pub fn sqrt( /// /// Display: Sine /// Category: calculate -/// Returns: float #[func] pub fn sin( /// The angle whose sine to calculate. angle: AngleLike, -) -> Value { - Value::Float(match angle { +) -> f64 { + 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. @@ -194,17 +192,16 @@ pub fn sin( /// /// Display: Cosine /// Category: calculate -/// Returns: float #[func] pub fn cos( /// The angle whose cosine to calculate. angle: AngleLike, -) -> Value { - Value::Float(match angle { +) -> f64 { + 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. @@ -220,17 +217,16 @@ pub fn cos( /// /// Display: Tangent /// Category: calculate -/// Returns: float #[func] pub fn tan( /// The angle whose tangent to calculate. angle: AngleLike, -) -> Value { - Value::Float(match angle { +) -> f64 { + 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. @@ -243,17 +239,16 @@ pub fn tan( /// /// Display: Arcsine /// Category: calculate -/// Returns: angle #[func] pub fn asin( /// The number whose arcsine to calculate. Must be between -1 and 1. value: Spanned, -) -> Value { +) -> SourceResult { let val = value.v.float(); if val < -1.0 || val > 1.0 { bail!(value.span, "value must be between -1 and 1"); } - Value::Angle(Angle::rad(val.asin())) + Ok(Angle::rad(val.asin())) } /// Calculate the arccosine of a number. @@ -266,17 +261,16 @@ pub fn asin( /// /// Display: Arccosine /// Category: calculate -/// Returns: angle #[func] pub fn acos( /// The number whose arcsine to calculate. Must be between -1 and 1. value: Spanned, -) -> Value { +) -> SourceResult { let val = value.v.float(); if val < -1.0 || val > 1.0 { bail!(value.span, "value must be between -1 and 1"); } - Value::Angle(Angle::rad(val.acos())) + Ok(Angle::rad(val.acos())) } /// Calculate the arctangent of a number. @@ -289,13 +283,12 @@ pub fn acos( /// /// Display: Arctangent /// Category: calculate -/// Returns: angle #[func] pub fn atan( /// The number whose arctangent to calculate. value: Num, -) -> Value { - Value::Angle(Angle::rad(value.float().atan())) +) -> Angle { + Angle::rad(value.float().atan()) } /// Calculate the four-quadrant arctangent of a coordinate. @@ -310,15 +303,14 @@ pub fn atan( /// /// Display: Four-quadrant Arctangent /// Category: calculate -/// Returns: angle #[func] pub fn atan2( /// The X coordinate. x: Num, /// The Y coordinate. y: Num, -) -> Value { - Value::Angle(Angle::rad(f64::atan2(y.float(), x.float()))) +) -> Angle { + Angle::rad(f64::atan2(y.float(), x.float())) } /// Calculate the hyperbolic sine of an angle. @@ -333,17 +325,16 @@ pub fn atan2( /// /// Display: Hyperbolic sine /// Category: calculate -/// Returns: float #[func] pub fn sinh( /// The angle whose hyperbolic sine to calculate. angle: AngleLike, -) -> Value { - Value::Float(match angle { +) -> f64 { + 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. @@ -358,17 +349,16 @@ pub fn sinh( /// /// Display: Hyperbolic cosine /// Category: calculate -/// Returns: float #[func] pub fn cosh( /// The angle whose hyperbolic cosine to calculate. angle: AngleLike, -) -> Value { - Value::Float(match angle { +) -> f64 { + 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. @@ -383,17 +373,16 @@ pub fn cosh( /// /// Display: Hyperbolic tangent /// Category: calculate -/// Returns: float #[func] pub fn tanh( /// The angle whose hyperbolic tangent to calculate. angle: AngleLike, -) -> Value { - Value::Float(match angle { +) -> f64 { + 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. @@ -407,7 +396,6 @@ pub fn tanh( /// /// Display: Logarithm /// Category: calculate -/// Returns: float #[func] pub fn log( /// The number whose logarithm to calculate. Must be strictly positive. @@ -416,7 +404,9 @@ pub fn log( #[named] #[default(Spanned::new(10.0, Span::detached()))] base: Spanned, -) -> Value { + /// The callsite span. + span: Span, +) -> SourceResult { let number = value.v.float(); if number <= 0.0 { bail!(value.span, "value must be strictly positive") @@ -435,10 +425,10 @@ pub fn log( }; if result.is_infinite() || result.is_nan() { - bail!(args.span, "the result is not a real number") + bail!(span, "the result is not a real number") } - Value::Float(result) + Ok(result) } /// Calculate the factorial of a number. @@ -450,33 +440,12 @@ pub fn log( /// /// Display: Factorial /// Category: calculate -/// Returns: integer #[func] pub fn fact( /// The number whose factorial to calculate. Must be non-negative. number: u64, -) -> Value { - factorial_range(1, number) - .map(Value::Int) - .ok_or("the result is too large") - .at(args.span)? -} - -/// Calculates the product of a range of numbers. Used to calculate -/// permutations. Returns None if the result is larger than `i64::MAX` -fn factorial_range(start: u64, end: u64) -> Option { - // By convention - if end + 1 < start { - return Some(0); - } - - let real_start: u64 = cmp::max(1, start); - let mut count: u64 = 1; - for i in real_start..=end { - count = count.checked_mul(i)?; - } - - i64::try_from(count).ok() +) -> StrResult { + Ok(fact_impl(1, number).ok_or("the result is too large")?) } /// Calculate a permutation. @@ -488,23 +457,36 @@ fn factorial_range(start: u64, end: u64) -> Option { /// /// Display: Permutation /// Category: calculate -/// Returns: integer #[func] pub fn perm( /// The base number. Must be non-negative. base: u64, /// The number of permutations. Must be non-negative. numbers: u64, -) -> Value { +) -> StrResult { // By convention. if base < numbers { - return Ok(Value::Int(0)); + return Ok(0); } - factorial_range(base - numbers + 1, base) - .map(Value::Int) - .ok_or("the result is too large") - .at(args.span)? + Ok(fact_impl(base - numbers + 1, base).ok_or("the result is too large")?) +} + +/// Calculates the product of a range of numbers. Used to calculate +/// permutations. Returns None if the result is larger than `i64::MAX` +fn fact_impl(start: u64, end: u64) -> Option { + // By convention + if end + 1 < start { + return Some(0); + } + + let real_start: u64 = cmp::max(1, start); + let mut count: u64 = 1; + for i in real_start..=end { + count = count.checked_mul(i)?; + } + + count.try_into().ok() } /// Calculate a binomial coefficient. @@ -516,24 +498,20 @@ pub fn perm( /// /// Display: Binomial /// Category: calculate -/// Returns: integer #[func] pub fn binom( /// The upper coefficient. Must be non-negative. n: u64, /// The lower coefficient. Must be non-negative. k: u64, -) -> Value { - binomial(n, k) - .map(Value::Int) - .ok_or("the result is too large") - .at(args.span)? +) -> StrResult { + Ok(binom_impl(n, k).ok_or("the result is too large")?) } /// Calculates a binomial coefficient, with `n` the upper coefficient and `k` /// the lower coefficient. Returns `None` if the result is larger than /// `i64::MAX` -fn binomial(n: u64, k: u64) -> Option { +fn binom_impl(n: u64, k: u64) -> Option { if k > n { return Some(0); } @@ -549,7 +527,7 @@ fn binomial(n: u64, k: u64) -> Option { result = result.checked_mul(n - i)?.checked_div(i + 1)?; } - i64::try_from(result).ok() + result.try_into().ok() } /// Calculate the greatest common divisor of two integers. @@ -561,20 +539,14 @@ fn binomial(n: u64, k: u64) -> Option { /// /// Display: Greatest Common Divisor /// Category: calculate -/// Returns: integer #[func] pub fn gcd( /// The first integer. a: i64, /// The second integer. b: i64, -) -> Value { - Value::Int(calculate_gcd(a, b)) -} - -/// Calculates the greatest common divisor of two integers -/// It is always non-negative. -fn calculate_gcd(mut a: i64, mut b: i64) -> i64 { +) -> i64 { + let (mut a, mut b) = (a, b); while b != 0 { let temp = b; b = a % b; @@ -593,29 +565,21 @@ fn calculate_gcd(mut a: i64, mut b: i64) -> i64 { /// /// Display: Least Common Multiple /// Category: calculate -/// Returns: integer #[func] pub fn lcm( /// The first integer. a: i64, /// The second integer. b: i64, -) -> Value { - calculate_lcm(a, b) - .map(Value::Int) - .ok_or("the return value is too large") - .at(args.span)? -} - -/// Calculates the least common multiple between two non-zero integers -/// Returns None if the value cannot be computed. -/// It is always non-negative. -fn calculate_lcm(a: i64, b: i64) -> Option { +) -> StrResult { if a == b { - return Some(a.abs()); + return Ok(a.abs()); } - a.checked_div(calculate_gcd(a, b))?.checked_mul(b).map(|v| v.abs()) + Ok(a.checked_div(gcd(a, b)) + .and_then(|gcd| gcd.checked_mul(b)) + .map(|v| v.abs()) + .ok_or("the return value is too large")?) } /// Round a number down to the nearest integer. @@ -631,15 +595,14 @@ fn calculate_lcm(a: i64, b: i64) -> Option { /// /// Display: Round down /// Category: calculate -/// Returns: integer #[func] pub fn floor( /// The number to round down. value: Num, -) -> Value { +) -> i64 { match value { - Num::Int(n) => Value::Int(n), - Num::Float(n) => Value::Int(n.floor() as i64), + Num::Int(n) => n, + Num::Float(n) => n.floor() as i64, } } @@ -656,15 +619,14 @@ pub fn floor( /// /// Display: Round up /// Category: calculate -/// Returns: integer #[func] pub fn ceil( /// The number to round up. value: Num, -) -> Value { +) -> i64 { match value { - Num::Int(n) => Value::Int(n), - Num::Float(n) => Value::Int(n.ceil() as i64), + Num::Int(n) => n, + Num::Float(n) => n.ceil() as i64, } } @@ -681,16 +643,15 @@ pub fn ceil( /// /// Display: Truncate /// Category: calculate -/// Returns: integer #[func] pub fn trunc( /// The number to truncate. value: Num, -) -> Value { - Value::Int(match value { +) -> i64 { + match value { Num::Int(n) => n, Num::Float(n) => n.trunc() as i64, - }) + } } /// Returns the fractional part of a number. @@ -705,15 +666,14 @@ pub fn trunc( /// /// Display: Fractional /// Category: calculate -/// Returns: integer or float #[func] pub fn fract( /// The number to truncate. value: Num, -) -> Value { +) -> Num { match value { - Num::Int(_) => Value::Int(0), - Num::Float(n) => Value::Float(n.fract()), + Num::Int(_) => Num::Int(0), + Num::Float(n) => Num::Float(n.fract()), } } @@ -730,7 +690,6 @@ pub fn fract( /// /// Display: Round /// Category: calculate -/// Returns: integer or float #[func] pub fn round( /// The number to round. @@ -739,13 +698,13 @@ pub fn round( #[named] #[default(0)] digits: i64, -) -> Value { +) -> Num { match value { - Num::Int(n) if digits == 0 => Value::Int(n), + Num::Int(n) if digits == 0 => Num::Int(n), _ => { let n = value.float(); let factor = 10.0_f64.powi(digits as i32); - Value::Float((n * factor).round() / factor) + Num::Float((n * factor).round() / factor) } } } @@ -761,7 +720,6 @@ pub fn round( /// /// Display: Clamp /// Category: calculate -/// Returns: integer or float #[func] pub fn clamp( /// The number to clamp. @@ -770,11 +728,11 @@ pub fn clamp( min: Num, /// The inclusive maximum value. max: Spanned, -) -> Value { +) -> SourceResult { if max.v.float() < min.float() { bail!(max.span, "max must be greater than or equal to min") } - value.apply3(min, max.v, i64::clamp, f64::clamp).value() + Ok(value.apply3(min, max.v, i64::clamp, f64::clamp)) } /// Determine the minimum of a sequence of values. @@ -787,15 +745,16 @@ pub fn clamp( /// /// Display: Minimum /// Category: calculate -/// Returns: any #[func] 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)? + /// The callsite span. + span: Span, +) -> SourceResult { + minmax(span, values, Ordering::Less) } /// Determine the maximum of a sequence of values. @@ -808,15 +767,16 @@ pub fn min( /// /// Display: Maximum /// Category: calculate -/// Returns: any #[func] 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)? + /// The callsite span. + span: Span, +) -> SourceResult { + minmax(span, values, Ordering::Greater) } /// Find the minimum or maximum of a sequence of values. @@ -851,13 +811,12 @@ fn minmax( /// /// Display: Even /// Category: calculate -/// Returns: boolean #[func] pub fn even( /// The number to check for evenness. value: i64, -) -> Value { - Value::Bool(value % 2 == 0) +) -> bool { + value % 2 == 0 } /// Determine whether an integer is odd. @@ -871,13 +830,12 @@ pub fn even( /// /// Display: Odd /// Category: calculate -/// Returns: boolean #[func] pub fn odd( /// The number to check for oddness. value: i64, -) -> Value { - Value::Bool(value % 2 != 0) +) -> bool { + value % 2 != 0 } /// Calculate the remainder of two numbers. @@ -890,18 +848,17 @@ pub fn odd( /// /// Display: Remainder /// Category: calculate -/// Returns: integer or float #[func] pub fn rem( /// The dividend of the remainder. dividend: Num, /// The divisor of the remainder. divisor: Spanned, -) -> Value { +) -> SourceResult { if divisor.v.float() == 0.0 { bail!(divisor.span, "divisor must not be zero"); } - dividend.apply2(divisor.v, Rem::rem, Rem::rem).value() + Ok(dividend.apply2(divisor.v, Rem::rem, Rem::rem)) } /// Calculate the modulus of two numbers. (Deprecated) @@ -911,18 +868,17 @@ pub fn rem( /// /// Display: Modulus /// Category: calculate -/// Returns: integer or float #[func] pub fn mod_( /// The dividend of the remainder. dividend: Num, /// The divisor of the remainder. divisor: Spanned, -) -> Value { +) -> SourceResult { if divisor.v.float() == 0.0 { bail!(divisor.span, "divisor must not be zero"); } - dividend.apply2(divisor.v, Rem::rem, Rem::rem).value() + Ok(dividend.apply2(divisor.v, Rem::rem, Rem::rem)) } /// Calculate the quotient of two numbers. @@ -935,33 +891,29 @@ pub fn mod_( /// /// Display: Quotient /// Category: calculate -/// Returns: integer or float #[func] pub fn quo( /// The dividend of the quotient. dividend: Num, /// The divisor of the quotient. divisor: Spanned, -) -> Value { +) -> SourceResult { if divisor.v.float() == 0.0 { bail!(divisor.span, "divisor must not be zero"); } - Value::Int(match dividend.apply2(divisor.v, Div::div, Div::div) { - Num::Int(i) => i, - Num::Float(f) => f.floor() as i64, // Note: the result should be an integer but floats doesn't have the same precision as i64. - }) + Ok(floor(dividend.apply2(divisor.v, Div::div, Div::div))) } /// A value which can be passed to functions that work with integers and floats. #[derive(Debug, Copy, Clone)] -enum Num { +pub enum Num { Int(i64), Float(f64), } impl Num { - fn apply2( + pub fn apply2( self, other: Self, int: impl FnOnce(i64, i64) -> i64, @@ -973,7 +925,7 @@ impl Num { } } - fn apply3( + pub fn apply3( self, other: Self, third: Self, @@ -986,35 +938,32 @@ impl Num { } } - fn float(self) -> f64 { + pub fn float(self) -> f64 { match self { Self::Int(v) => v as f64, Self::Float(v) => v, } } - - fn value(self) -> Value { - match self { - Self::Int(v) => Value::Int(v), - Self::Float(v) => Value::Float(v), - } - } } -cast_from_value! { +cast! { Num, + self => match self { + Self::Int(v) => v.into_value(), + Self::Float(v) => v.into_value(), + }, v: i64 => Self::Int(v), v: f64 => Self::Float(v), } /// A value that can be passed to a trigonometric function. -enum AngleLike { +pub enum AngleLike { Int(i64), Float(f64), Angle(Angle), } -cast_from_value! { +cast! { AngleLike, v: i64 => Self::Int(v), v: f64 => Self::Float(v), diff --git a/library/src/compute/construct.rs b/library/src/compute/construct.rs index d11eb3980..5d2d35ae0 100644 --- a/library/src/compute/construct.rs +++ b/library/src/compute/construct.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use time::{Month, PrimitiveDateTime}; -use typst::eval::{Datetime, Dynamic, Regex}; +use typst::eval::{Datetime, Regex}; use crate::prelude::*; @@ -23,19 +23,18 @@ use crate::prelude::*; /// /// Display: Integer /// Category: construct -/// Returns: integer #[func] pub fn int( /// The value that should be converted to an integer. value: ToInt, -) -> Value { - Value::Int(value.0) +) -> i64 { + value.0 } /// A value that can be cast to an integer. -struct ToInt(i64); +pub struct ToInt(i64); -cast_from_value! { +cast! { ToInt, v: bool => Self(v as i64), v: i64 => Self(v), @@ -63,19 +62,18 @@ cast_from_value! { /// /// Display: Float /// Category: construct -/// Returns: float #[func] pub fn float( /// The value that should be converted to a float. value: ToFloat, -) -> Value { - Value::Float(value.0) +) -> f64 { + value.0 } /// A value that can be cast to a float. -struct ToFloat(f64); +pub struct ToFloat(f64); -cast_from_value! { +cast! { ToFloat, v: bool => Self(v as i64 as f64), v: i64 => Self(v as f64), @@ -95,13 +93,12 @@ cast_from_value! { /// /// Display: Luma /// Category: construct -/// Returns: color #[func] pub fn luma( /// The gray component. gray: Component, -) -> Value { - Value::Color(LumaColor::new(gray.0).into()) +) -> Color { + LumaColor::new(gray.0).into() } /// Create an RGB(A) color. @@ -121,7 +118,6 @@ pub fn luma( /// /// Display: RGB /// Category: construct -/// Returns: color #[func] pub fn rgb( /// The color in hexadecimal notation. @@ -150,11 +146,14 @@ pub fn rgb( /// The alpha component. #[external] alpha: Component, -) -> Value { - Value::Color(if let Some(string) = args.find::>()? { + /// The arguments. + args: Args, +) -> SourceResult { + let mut args = args; + Ok(if let Some(string) = args.find::>()? { match RgbaColor::from_str(&string.v) { Ok(color) => color.into(), - Err(msg) => bail!(string.span, msg), + Err(msg) => bail!(string.span, "{msg}"), } } else { let Component(r) = args.expect("red component")?; @@ -166,9 +165,9 @@ pub fn rgb( } /// An integer or ratio component. -struct Component(u8); +pub struct Component(u8); -cast_from_value! { +cast! { Component, v: i64 => match v { 0 ..= 255 => Self(v as u8), @@ -208,10 +207,9 @@ cast_from_value! { /// /// Display: Datetime /// Category: construct -/// Returns: datetime #[func] #[scope( - scope.define("today", datetime_today); + scope.define("today", datetime_today_func()); scope )] pub fn datetime( @@ -233,95 +231,77 @@ pub fn datetime( /// The second of the datetime. #[named] second: Option, -) -> Value { +) -> StrResult { let time = match (hour, minute, second) { (Some(hour), Some(minute), Some(second)) => { match time::Time::from_hms(hour.0, minute.0, second.0) { Ok(time) => Some(time), - Err(_) => bail!(args.span, "time is invalid"), + Err(_) => bail!("time is invalid"), } } (None, None, None) => None, - _ => bail!(args.span, "time is incomplete"), + _ => bail!("time is incomplete"), }; let date = match (year, month, day) { (Some(year), Some(month), Some(day)) => { match time::Date::from_calendar_date(year.0, month.0, day.0) { Ok(date) => Some(date), - Err(_) => bail!(args.span, "date is invalid"), + Err(_) => bail!("date is invalid"), } } (None, None, None) => None, - _ => bail!(args.span, "date is incomplete"), + _ => bail!("date is incomplete"), }; - match (date, time) { - (Some(date), Some(time)) => Value::Dyn(Dynamic::new(Datetime::Datetime( - PrimitiveDateTime::new(date, time), - ))), - (Some(date), None) => Value::Dyn(Dynamic::new(Datetime::Date(date))), - (None, Some(time)) => Value::Dyn(Dynamic::new(Datetime::Time(time))), - (None, None) => { - bail!(args.span, "at least one of date or time must be fully specified") + Ok(match (date, time) { + (Some(date), Some(time)) => { + Datetime::Datetime(PrimitiveDateTime::new(date, time)) } - } + (Some(date), None) => Datetime::Date(date), + (None, Some(time)) => Datetime::Time(time), + (None, None) => { + bail!("at least one of date or time must be fully specified") + } + }) } -struct YearComponent(i32); -struct MonthComponent(Month); -struct DayComponent(u8); -struct HourComponent(u8); -struct MinuteComponent(u8); -struct SecondComponent(u8); +pub struct YearComponent(i32); +pub struct MonthComponent(Month); +pub struct DayComponent(u8); +pub struct HourComponent(u8); +pub struct MinuteComponent(u8); +pub struct SecondComponent(u8); -cast_from_value!( +cast! { YearComponent, - v: i64 => match i32::try_from(v) { - Ok(n) => Self(n), - _ => Err("year is invalid")? - } -); + v: i32 => Self(v), +} -cast_from_value!( +cast! { MonthComponent, - v: i64 => match u8::try_from(v).ok().and_then(|n1| Month::try_from(n1).ok()).map(Self) { - Some(m) => m, - _ => Err("month is invalid")? - } -); + v: u8 => Self(Month::try_from(v).map_err(|_| "month is invalid")?) +} -cast_from_value!( +cast! { DayComponent, - v: i64 => match u8::try_from(v) { - Ok(n) => Self(n), - _ => Err("day is invalid")? - } -); + v: u8 => Self(v), +} -cast_from_value!( +cast! { HourComponent, - v: i64 => match u8::try_from(v) { - Ok(n) => Self(n), - _ => Err("hour is invalid")? - } -); + v: u8 => Self(v), +} -cast_from_value!( +cast! { MinuteComponent, - v: i64 => match u8::try_from(v) { - Ok(n) => Self(n), - _ => Err("minute is invalid")? - } -); + v: u8 => Self(v), +} -cast_from_value!( +cast! { SecondComponent, - v: i64 => match u8::try_from(v) { - Ok(n) => Self(n), - _ => Err("second is invalid")? - } -); + v: u8 => Self(v), +} /// Returns the current date. /// @@ -333,7 +313,6 @@ cast_from_value!( /// /// Display: Today /// Category: construct -/// Returns: datetime #[func] pub fn datetime_today( /// An offset to apply to the current UTC date. If set to `{auto}`, the @@ -341,13 +320,13 @@ pub fn datetime_today( #[named] #[default] offset: Smart, -) -> Value { - let current_date = match vm.vt.world.today(offset.as_custom()) { - Some(d) => d, - None => bail!(args.span, "unable to get the current date"), - }; - - Value::Dyn(Dynamic::new(current_date)) + /// The virtual machine. + vt: &mut Vt, +) -> StrResult { + Ok(vt + .world + .today(offset.as_custom()) + .ok_or("unable to get the current date")?) } /// Create a CMYK color. @@ -365,7 +344,6 @@ pub fn datetime_today( /// /// Display: CMYK /// Category: construct -/// Returns: color #[func] pub fn cmyk( /// The cyan component. @@ -376,14 +354,14 @@ pub fn cmyk( yellow: RatioComponent, /// The key component. key: RatioComponent, -) -> Value { - Value::Color(CmykColor::new(cyan.0, magenta.0, yellow.0, key.0).into()) +) -> Color { + CmykColor::new(cyan.0, magenta.0, yellow.0, key.0).into() } /// A component that must be a ratio. -struct RatioComponent(u8); +pub struct RatioComponent(u8); -cast_from_value! { +cast! { RatioComponent, v: Ratio => if (0.0 ..= 1.0).contains(&v.get()) { Self((v.get() * 255.0).round() as u8) @@ -413,7 +391,6 @@ cast_from_value! { /// /// Display: Symbol /// Category: construct -/// Returns: symbol #[func] pub fn symbol( /// The variants of the symbol. @@ -425,10 +402,12 @@ pub fn symbol( /// all attached modifiers and the minimum number of other modifiers. #[variadic] variants: Vec>, -) -> Value { + /// The callsite span. + span: Span, +) -> SourceResult { let mut list = Vec::new(); if variants.is_empty() { - bail!(args.span, "expected at least one variant"); + bail!(span, "expected at least one variant"); } for Spanned { v, span } in variants { if list.iter().any(|(prev, _)| &v.0 == prev) { @@ -436,13 +415,13 @@ pub fn symbol( } list.push((v.0, v.1)); } - Value::Symbol(Symbol::runtime(list.into_boxed_slice())) + Ok(Symbol::runtime(list.into_boxed_slice())) } /// A value that can be cast to a symbol. -struct Variant(EcoString, char); +pub struct Variant(EcoString, char); -cast_from_value! { +cast! { Variant, c: char => Self(EcoString::new(), c), array: Array => { @@ -476,11 +455,10 @@ cast_from_value! { /// /// Display: String /// Category: construct -/// Returns: string #[func] #[scope( - scope.define("to-unicode", to_unicode); - scope.define("from-unicode", from_unicode); + scope.define("to-unicode", str_to_unicode_func()); + scope.define("from-unicode", str_from_unicode_func()); scope )] pub fn str( @@ -490,13 +468,13 @@ pub fn str( #[named] #[default(Spanned::new(10, Span::detached()))] base: Spanned, -) -> Value { - match value { +) -> SourceResult { + Ok(match value { ToStr::Str(s) => { if base.v != 10 { bail!(base.span, "base is only supported for integers"); } - Value::Str(s) + s } ToStr::Int(n) => { if base.v < 2 || base.v > 36 { @@ -504,18 +482,18 @@ pub fn str( } int_to_base(n, base.v).into() } - } + }) } /// A value that can be cast to a string. -enum ToStr { +pub enum ToStr { /// A string value ready to be used as-is. Str(Str), /// An integer about to be formatted in a given base. Int(i64), } -cast_from_value! { +cast! { ToStr, v: i64 => Self::Int(v), v: f64 => Self::Str(format_str!("{}", v)), @@ -572,16 +550,14 @@ fn int_to_base(mut n: i64, base: i64) -> EcoString { /// /// Display: String To Unicode /// Category: construct -/// Returns: int #[func] -pub fn to_unicode( +pub fn str_to_unicode( /// The character that should be converted. value: char, -) -> Value { - Value::Int(From::::from(value.into())) +) -> u32 { + value.into() } -#[func] /// Converts a unicode code point into its corresponding string. /// /// ```example @@ -590,18 +566,18 @@ pub fn to_unicode( /// /// Display: Sting From Unicode /// Category: construct -/// Returns: string -pub fn from_unicode( +#[func] +pub fn str_from_unicode( /// The code point that should be converted. value: CodePoint, -) -> Value { - Value::Str(format_str!("{}", value.0)) +) -> Str { + format_str!("{}", value.0) } /// The numeric representation of a single unicode code point. -struct CodePoint(char); +pub struct CodePoint(char); -cast_from_value! { +cast! { CodePoint, v: i64 => { Self(v.try_into().ok().and_then(|v: u32| v.try_into().ok()).ok_or_else( @@ -631,13 +607,12 @@ cast_from_value! { /// /// Display: Label /// Category: construct -/// Returns: label #[func] pub fn label( /// The name of the label. name: EcoString, -) -> Value { - Value::Label(Label(name)) +) -> Label { + Label(name) } /// Create a regular expression from a string. @@ -663,7 +638,6 @@ pub fn label( /// /// Display: Regex /// Category: construct -/// Returns: regex #[func] pub fn regex( /// The regular expression as a string. @@ -677,8 +651,8 @@ pub fn regex( /// and extract its text to use it for your regular expressions: /// ```{regex(`\d+\.\d+\.\d+`.text)}```. regex: Spanned, -) -> Value { - Regex::new(®ex.v).at(regex.span)?.into() +) -> SourceResult { + Regex::new(®ex.v).at(regex.span) } /// Create an array consisting of a sequence of numbers. @@ -698,7 +672,6 @@ pub fn regex( /// /// Display: Range /// Category: construct -/// Returns: array #[func] pub fn range( /// The start of the range (inclusive). @@ -712,7 +685,10 @@ pub fn range( #[named] #[default(NonZeroI64::new(1).unwrap())] step: NonZeroI64, -) -> Value { + /// The arguments. + args: Args, +) -> SourceResult { + let mut args = args; let first = args.expect::("end")?; let (start, end) = match args.eat::()? { Some(second) => (first, second), @@ -729,7 +705,7 @@ pub fn range( x += step; } - Value::Array(array) + Ok(array) } #[cfg(test)] diff --git a/library/src/compute/data.rs b/library/src/compute/data.rs index e39184587..b236536df 100644 --- a/library/src/compute/data.rs +++ b/library/src/compute/data.rs @@ -14,21 +14,22 @@ use crate::prelude::*; /// #raw(text, lang: "html") /// ``` /// -/// Display: Plain text +/// Display: Read /// Category: data-loading -/// Returns: string #[func] pub fn read( /// Path to a file. path: Spanned, -) -> Value { + /// The virtual machine. + vm: &mut Vm, +) -> SourceResult { 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(|_| "file is not valid utf-8") .at(span)?; - Value::Str(text.into()) + Ok(text.into()) } /// Read structured data from a CSV file. @@ -51,7 +52,6 @@ pub fn read( /// /// Display: CSV /// Category: data-loading -/// Returns: array #[func] pub fn csv( /// Path to a CSV file. @@ -61,7 +61,9 @@ pub fn csv( #[named] #[default] delimiter: Delimiter, -) -> Value { + /// The virtual machine. + vm: &mut Vm, +) -> SourceResult { let Spanned { v: path, span } = path; let path = vm.locate(&path).at(span)?; let data = vm.world().file(&path).at(span)?; @@ -74,22 +76,30 @@ pub fn csv( let mut array = Array::new(); for (line, result) in reader.records().enumerate() { - // Original solution use line from error, but that is incorrect with has_headers set to false - // See issue: https://github.com/BurntSushi/rust-csv/issues/184 + // Original solution use line from error, but that is incorrect with + // `has_headers` set to `false`. See issue: + // https://github.com/BurntSushi/rust-csv/issues/184 let line = line + 1; // Counting lines from 1 let row = result.map_err(|err| format_csv_error(err, line)).at(span)?; - let sub = row.into_iter().map(|field| Value::Str(field.into())).collect(); + let sub = row.into_iter().map(|field| field.into_value()).collect(); array.push(Value::Array(sub)) } - Value::Array(array) + Ok(array) } /// The delimiter to use when parsing CSV files. -struct Delimiter(char); +pub struct Delimiter(char); -cast_from_value! { +impl Default for Delimiter { + fn default() -> Self { + Self(',') + } +} + +cast! { Delimiter, + self => self.0.into_value(), v: EcoString => { let mut chars = v.chars(); let first = chars.next().ok_or("delimiter must not be empty")?; @@ -105,16 +115,6 @@ cast_from_value! { }, } -cast_to_value! { - v: Delimiter => v.0.into() -} - -impl Default for Delimiter { - fn default() -> Self { - Self(',') - } -} - /// Format the user-facing CSV error message. fn format_csv_error(error: csv::Error, line: usize) -> EcoString { match error.kind() { @@ -168,38 +168,39 @@ fn format_csv_error(error: csv::Error, line: usize) -> EcoString { /// /// Display: JSON /// Category: data-loading -/// Returns: array or dictionary #[func] pub fn json( /// Path to a JSON file. path: Spanned, -) -> Value { + /// The virtual machine. + vm: &mut Vm, +) -> SourceResult { 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)?; - convert_json(value) + Ok(convert_json(value)) } /// Convert a JSON value to a Typst value. fn convert_json(value: serde_json::Value) -> Value { match value { serde_json::Value::Null => Value::None, - serde_json::Value::Bool(v) => Value::Bool(v), + serde_json::Value::Bool(v) => v.into_value(), serde_json::Value::Number(v) => match v.as_i64() { - Some(int) => Value::Int(int), - None => Value::Float(v.as_f64().unwrap_or(f64::NAN)), + Some(int) => int.into_value(), + None => v.as_f64().unwrap_or(f64::NAN).into_value(), }, - serde_json::Value::String(v) => Value::Str(v.into()), + serde_json::Value::String(v) => v.into_value(), serde_json::Value::Array(v) => { - Value::Array(v.into_iter().map(convert_json).collect()) + v.into_iter().map(convert_json).collect::().into_value() } - serde_json::Value::Object(v) => Value::Dict( - v.into_iter() - .map(|(key, value)| (key.into(), convert_json(value))) - .collect(), - ), + serde_json::Value::Object(v) => v + .into_iter() + .map(|(key, value)| (key.into(), convert_json(value))) + .collect::() + .into_value(), } } @@ -233,12 +234,13 @@ fn format_json_error(error: serde_json::Error) -> EcoString { /// /// Display: TOML /// Category: data-loading -/// Returns: dictionary #[func] pub fn toml( /// Path to a TOML file. path: Spanned, -) -> Value { + /// The virtual machine. + vm: &mut Vm, +) -> SourceResult { let Spanned { v: path, span } = path; let path = vm.locate(&path).at(span)?; let data = vm.world().file(&path).at(span)?; @@ -248,24 +250,26 @@ pub fn toml( .at(span)?; let value: toml::Value = toml::from_str(raw).map_err(format_toml_error).at(span)?; - convert_toml(value) + Ok(convert_toml(value)) } /// Convert a TOML value to a Typst value. fn convert_toml(value: toml::Value) -> Value { match value { - toml::Value::String(v) => Value::Str(v.into()), - toml::Value::Integer(v) => Value::Int(v), - toml::Value::Float(v) => Value::Float(v), - toml::Value::Boolean(v) => Value::Bool(v), - toml::Value::Array(v) => Value::Array(v.into_iter().map(convert_toml).collect()), - toml::Value::Table(v) => Value::Dict( - v.into_iter() - .map(|(key, value)| (key.into(), convert_toml(value))) - .collect(), - ), + toml::Value::String(v) => v.into_value(), + toml::Value::Integer(v) => v.into_value(), + toml::Value::Float(v) => v.into_value(), + toml::Value::Boolean(v) => v.into_value(), + toml::Value::Array(v) => { + v.into_iter().map(convert_toml).collect::().into_value() + } + toml::Value::Table(v) => v + .into_iter() + .map(|(key, value)| (key.into(), convert_toml(value))) + .collect::() + .into_value(), // TODO: Make it use native date/time object(s) once it is implemented. - toml::Value::Datetime(v) => Value::Str(v.to_string().into()), + toml::Value::Datetime(v) => v.to_string().into_value(), } } @@ -323,39 +327,40 @@ fn format_toml_error(error: toml::de::Error) -> EcoString { /// /// Display: YAML /// Category: data-loading -/// Returns: array or dictionary or none or boolean or integer or float or string #[func] pub fn yaml( /// Path to a YAML file. path: Spanned, -) -> Value { + /// The virtual machine. + vm: &mut Vm, +) -> SourceResult { let Spanned { v: path, span } = path; let path = vm.locate(&path).at(span)?; let data = vm.world().file(&path).at(span)?; let value: serde_yaml::Value = serde_yaml::from_slice(&data).map_err(format_yaml_error).at(span)?; - convert_yaml(value) + Ok(convert_yaml(value)) } /// Convert a YAML value to a Typst value. fn convert_yaml(value: serde_yaml::Value) -> Value { match value { serde_yaml::Value::Null => Value::None, - serde_yaml::Value::Bool(v) => Value::Bool(v), + serde_yaml::Value::Bool(v) => v.into_value(), serde_yaml::Value::Number(v) => match v.as_i64() { - Some(int) => Value::Int(int), - None => Value::Float(v.as_f64().unwrap_or(f64::NAN)), + Some(int) => int.into_value(), + None => v.as_f64().unwrap_or(f64::NAN).into_value(), }, - serde_yaml::Value::String(v) => Value::Str(v.into()), + serde_yaml::Value::String(v) => v.into_value(), serde_yaml::Value::Sequence(v) => { - Value::Array(v.into_iter().map(convert_yaml).collect()) + v.into_iter().map(convert_yaml).collect::().into_value() } - serde_yaml::Value::Mapping(v) => Value::Dict( - v.into_iter() - .map(|(key, value)| (convert_yaml_key(key), convert_yaml(value))) - .filter_map(|(key, value)| key.map(|key| (key, value))) - .collect(), - ), + serde_yaml::Value::Mapping(v) => v + .into_iter() + .map(|(key, value)| (convert_yaml_key(key), convert_yaml(value))) + .filter_map(|(key, value)| key.map(|key| (key, value))) + .collect::() + .into_value(), } } @@ -425,24 +430,25 @@ fn format_yaml_error(error: serde_yaml::Error) -> EcoString { /// /// Display: XML /// Category: data-loading -/// Returns: array #[func] pub fn xml( /// Path to an XML file. path: Spanned, -) -> Value { + /// The virtual machine. + vm: &mut Vm, +) -> SourceResult { 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)?; - convert_xml(document.root()) + Ok(convert_xml(document.root())) } /// Convert an XML node to a Typst value. fn convert_xml(node: roxmltree::Node) -> Value { if node.is_text() { - return Value::Str(node.text().unwrap_or_default().into()); + return node.text().unwrap_or_default().into_value(); } let children: Array = node.children().map(convert_xml).collect(); @@ -453,7 +459,7 @@ fn convert_xml(node: roxmltree::Node) -> Value { let tag: Str = node.tag_name().name().into(); let attrs: Dict = node .attributes() - .map(|attr| (attr.name().into(), attr.value().into())) + .map(|attr| (attr.name().into(), attr.value().into_value())) .collect(); Value::Dict(dict! { diff --git a/library/src/compute/foundations.rs b/library/src/compute/foundations.rs index 6a92c2a80..7964ac454 100644 --- a/library/src/compute/foundations.rs +++ b/library/src/compute/foundations.rs @@ -16,12 +16,11 @@ use crate::prelude::*; /// /// Display: Type /// Category: foundations -/// Returns: string #[func] pub fn type_( /// The value whose type's to determine. value: Value, -) -> Value { +) -> Str { value.type_name().into() } @@ -44,13 +43,12 @@ pub fn type_( /// /// Display: Representation /// Category: foundations -/// Returns: string #[func] pub fn repr( /// The value whose string representation to produce. value: Value, -) -> Value { - value.repr().into() +) -> Str { + value.repr() } /// Fail with an error. @@ -63,13 +61,12 @@ pub fn repr( /// /// Display: Panic /// Category: foundations -/// Returns: #[func] pub fn panic( /// The values to panic with. #[variadic] values: Vec, -) -> Value { +) -> StrResult { let mut msg = EcoString::from("panicked"); if !values.is_empty() { msg.push_str(" with: "); @@ -80,7 +77,7 @@ pub fn panic( msg.push_str(&value.repr()); } } - bail!(args.span, msg); + Err(msg) } /// Ensure that a condition is fulfilled. @@ -98,11 +95,10 @@ pub fn panic( /// /// Display: Assert /// Category: foundations -/// Returns: #[func] #[scope( - scope.define("eq", assert_eq); - scope.define("ne", assert_ne); + scope.define("eq", assert_eq_func()); + scope.define("ne", assert_ne_func()); scope )] pub fn assert( @@ -110,17 +106,16 @@ pub fn assert( condition: bool, /// The error message when the assertion fails. #[named] - #[default] message: Option, -) -> Value { +) -> StrResult { if !condition { if let Some(message) = message { - bail!(args.span, "assertion failed: {}", message); + bail!("assertion failed: {message}"); } else { - bail!(args.span, "assertion failed"); + bail!("assertion failed"); } } - Value::None + Ok(NoneValue) } /// Ensure that two values are equal. @@ -135,7 +130,6 @@ pub fn assert( /// /// Display: Assert Equals /// Category: foundations -/// Returns: #[func] pub fn assert_eq( /// The first value to compare. @@ -147,22 +141,16 @@ pub fn assert_eq( /// An optional message to display on error instead of the representations /// of the compared values. #[named] - #[default] message: Option, -) -> Value { +) -> StrResult { if left != right { if let Some(message) = message { - bail!(args.span, "equality assertion failed: {}", message); + bail!("equality assertion failed: {message}"); } else { - bail!( - args.span, - "equality assertion failed: value {:?} was not equal to {:?}", - left, - right - ); + bail!("equality assertion failed: value {left:?} was not equal to {right:?}"); } } - Value::None + Ok(NoneValue) } /// Ensure that two values are not equal. @@ -177,7 +165,6 @@ pub fn assert_eq( /// /// Display: Assert Not Equals /// Category: foundations -/// Returns: #[func] pub fn assert_ne( /// The first value to compare. @@ -189,22 +176,16 @@ pub fn assert_ne( /// An optional message to display on error instead of the representations /// of the compared values. #[named] - #[default] message: Option, -) -> Value { +) -> StrResult { if left == right { if let Some(message) = message { - bail!(args.span, "inequality assertion failed: {}", message); + bail!("inequality assertion failed: {message}"); } else { - bail!( - args.span, - "inequality assertion failed: value {:?} was equal to {:?}", - left, - right - ); + bail!("inequality assertion failed: value {left:?} was equal to {right:?}"); } } - Value::None + Ok(NoneValue) } /// Evaluate a string as Typst code. @@ -220,14 +201,15 @@ pub fn assert_ne( /// /// Display: Evaluate /// Category: foundations -/// Returns: any #[func] pub fn eval( /// A string of Typst code to evaluate. /// /// The code in the string cannot interact with the file system. source: Spanned, -) -> Value { + /// The virtual machine. + vm: &mut Vm, +) -> SourceResult { let Spanned { v: text, span } = source; - typst::eval::eval_string(vm.world(), &text, span)? + typst::eval::eval_string(vm.world(), &text, span) } diff --git a/library/src/compute/mod.rs b/library/src/compute/mod.rs index 90730e02e..e9e4870c7 100644 --- a/library/src/compute/mod.rs +++ b/library/src/compute/mod.rs @@ -13,27 +13,27 @@ use crate::prelude::*; /// Hook up all compute definitions. pub(super) fn define(global: &mut Scope) { - global.define("type", type_); - global.define("repr", repr); - global.define("panic", panic); - global.define("assert", assert); - global.define("eval", eval); - global.define("int", int); - global.define("float", float); - global.define("luma", luma); - global.define("rgb", rgb); - global.define("cmyk", cmyk); - global.define("datetime", datetime); - global.define("symbol", symbol); - global.define("str", str); - global.define("label", label); - global.define("regex", regex); - global.define("range", range); - global.define("read", read); - global.define("csv", csv); - global.define("json", json); - global.define("toml", toml); - global.define("yaml", yaml); - global.define("xml", xml); + global.define("type", type_func()); + global.define("repr", repr_func()); + global.define("panic", panic_func()); + global.define("assert", assert_func()); + global.define("eval", eval_func()); + global.define("int", int_func()); + global.define("float", float_func()); + global.define("luma", luma_func()); + global.define("rgb", rgb_func()); + global.define("cmyk", cmyk_func()); + global.define("datetime", datetime_func()); + global.define("symbol", symbol_func()); + global.define("str", str_func()); + global.define("label", label_func()); + global.define("regex", regex_func()); + global.define("range", range_func()); + global.define("read", read_func()); + global.define("csv", csv_func()); + global.define("json", json_func()); + global.define("toml", toml_func()); + global.define("yaml", yaml_func()); + global.define("xml", xml_func()); global.define("calc", calc::module()); } diff --git a/library/src/layout/container.rs b/library/src/layout/container.rs index b7d0ba2fb..d28c8b5ee 100644 --- a/library/src/layout/container.rs +++ b/library/src/layout/container.rs @@ -1,3 +1,5 @@ +use typst::eval::AutoValue; + use super::VElem; use crate::layout::Spacing; use crate::prelude::*; @@ -482,17 +484,14 @@ impl> From for Sizing { } } -cast_from_value! { +cast! { Sizing, - _: Smart => Self::Auto, + self => match self { + Self::Auto => Value::Auto, + Self::Rel(rel) => rel.into_value(), + Self::Fr(fr) => fr.into_value(), + }, + _: AutoValue => Self::Auto, v: Rel => Self::Rel(v), v: Fr => Self::Fr(v), } - -cast_to_value! { - v: Sizing => match v { - Sizing::Auto => Value::Auto, - Sizing::Rel(rel) => Value::Relative(rel), - Sizing::Fr(fr) => Value::Fraction(fr), - } -} diff --git a/library/src/layout/enum.rs b/library/src/layout/enum.rs index 0fd0ebb2b..62f4c351e 100644 --- a/library/src/layout/enum.rs +++ b/library/src/layout/enum.rs @@ -283,7 +283,7 @@ pub struct EnumItem { pub body: Content, } -cast_from_value! { +cast! { EnumItem, array: Array => { let mut iter = array.into_iter(); @@ -298,15 +298,12 @@ cast_from_value! { struct Parent(usize); -cast_from_value! { +cast! { Parent, + self => self.0.into_value(), v: usize => Self(v), } -cast_to_value! { - v: Parent => v.0.into() -} - impl Fold for Parent { type Output = Vec; diff --git a/library/src/layout/grid.rs b/library/src/layout/grid.rs index 3c299eb02..ea155acdc 100644 --- a/library/src/layout/grid.rs +++ b/library/src/layout/grid.rs @@ -129,17 +129,14 @@ impl Layout for GridElem { #[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] pub struct TrackSizings(pub Vec); -cast_from_value! { +cast! { TrackSizings, + self => self.0.into_value(), sizing: Sizing => Self(vec![sizing]), count: NonZeroUsize => Self(vec![Sizing::Auto; count.get()]), values: Array => Self(values.into_iter().map(Value::cast).collect::>()?), } -cast_to_value! { - v: TrackSizings => v.0.into() -} - /// Performs grid layout. pub struct GridLayouter<'a, 'v> { /// The core context. diff --git a/library/src/layout/list.rs b/library/src/layout/list.rs index 1e42d51b5..b308c2ce8 100644 --- a/library/src/layout/list.rs +++ b/library/src/layout/list.rs @@ -174,7 +174,7 @@ pub struct ListItem { pub body: Content, } -cast_from_value! { +cast! { ListItem, v: Content => v.to::().cloned().unwrap_or_else(|| Self::new(v.clone())), } @@ -193,13 +193,21 @@ impl ListMarker { Self::Content(list) => { list.get(depth).or(list.last()).cloned().unwrap_or_default() } - Self::Func(func) => func.call_vt(vt, [Value::Int(depth as i64)])?.display(), + Self::Func(func) => func.call_vt(vt, [depth])?.display(), }) } } -cast_from_value! { +cast! { ListMarker, + self => match self { + Self::Content(vec) => if vec.len() == 1 { + vec.into_iter().next().unwrap().into_value() + } else { + vec.into_value() + }, + Self::Func(func) => func.into_value(), + }, v: Content => Self::Content(vec![v]), array: Array => { if array.is_empty() { @@ -210,28 +218,14 @@ cast_from_value! { v: Func => Self::Func(v), } -cast_to_value! { - v: ListMarker => match v { - ListMarker::Content(vec) => if vec.len() == 1 { - vec.into_iter().next().unwrap().into() - } else { - vec.into() - }, - ListMarker::Func(func) => func.into(), - } -} - struct Depth; -cast_from_value! { +cast! { Depth, + self => Value::None, _: Value => Self, } -cast_to_value! { - _: Depth => Value::None -} - impl Fold for Depth { type Output = usize; diff --git a/library/src/layout/measure.rs b/library/src/layout/measure.rs index c7a192431..4cbd16986 100644 --- a/library/src/layout/measure.rs +++ b/library/src/layout/measure.rs @@ -39,17 +39,18 @@ use crate::prelude::*; /// /// Display: Measure /// Category: layout -/// Returns: dictionary #[func] pub fn measure( /// The content whose size to measure. content: Content, /// The styles with which to layout the content. styles: Styles, -) -> Value { + /// The virtual machine. + vm: &mut Vm, +) -> SourceResult { let pod = Regions::one(Axes::splat(Abs::inf()), Axes::splat(false)); let styles = StyleChain::new(&styles); let frame = content.measure(&mut vm.vt, styles, pod)?.into_frame(); let Size { x, y } = frame.size(); - dict! { "width" => x, "height" => y }.into() + Ok(dict! { "width" => x, "height" => y }) } diff --git a/library/src/layout/mod.rs b/library/src/layout/mod.rs index 2d5b18c87..9b6ba2049 100644 --- a/library/src/layout/mod.rs +++ b/library/src/layout/mod.rs @@ -87,7 +87,19 @@ pub(super) fn define(global: &mut Scope) { global.define("scale", ScaleElem::func()); global.define("rotate", RotateElem::func()); global.define("hide", HideElem::func()); - global.define("measure", measure); + global.define("measure", measure_func()); + global.define("ltr", Dir::LTR); + global.define("rtl", Dir::RTL); + global.define("ttb", Dir::TTB); + global.define("btt", Dir::BTT); + global.define("start", GenAlign::Start); + global.define("end", GenAlign::End); + global.define("left", GenAlign::Specific(Align::Left)); + global.define("center", GenAlign::Specific(Align::Center)); + global.define("right", GenAlign::Specific(Align::Right)); + global.define("top", GenAlign::Specific(Align::Top)); + global.define("horizon", GenAlign::Specific(Align::Horizon)); + global.define("bottom", GenAlign::Specific(Align::Bottom)); } /// Root-level layout. diff --git a/library/src/layout/page.rs b/library/src/layout/page.rs index 6510bd58c..26ebbc533 100644 --- a/library/src/layout/page.rs +++ b/library/src/layout/page.rs @@ -341,7 +341,7 @@ impl PageElem { let footer_descent = self.footer_descent(styles); let numbering_meta = FrameItem::Meta( - Meta::PageNumbering(self.numbering(styles).into()), + Meta::PageNumbering(self.numbering(styles).into_value()), Size::zero(), ); @@ -451,24 +451,21 @@ impl Marginal { pub fn resolve(&self, vt: &mut Vt, page: usize) -> SourceResult { Ok(match self { Self::Content(content) => content.clone(), - Self::Func(func) => func.call_vt(vt, [Value::Int(page as i64)])?.display(), + Self::Func(func) => func.call_vt(vt, [page])?.display(), }) } } -cast_from_value! { +cast! { Marginal, + self => match self { + Self::Content(v) => v.into_value(), + Self::Func(v) => v.into_value(), + }, v: Content => Self::Content(v), v: Func => Self::Func(v), } -cast_to_value! { - v: Marginal => match v { - Marginal::Content(v) => v.into(), - Marginal::Func(v) => v.into(), - } -} - /// Specification of a paper. #[derive(Debug, Copy, Clone, Hash)] pub struct Paper { @@ -517,17 +514,14 @@ macro_rules! papers { } } - cast_from_value! { + cast! { Paper, + self => self.name.into_value(), $( /// Produces a paper of the respective size. $name => Self::$var, )* } - - cast_to_value! { - v: Paper => v.name.into() - } }; } diff --git a/library/src/layout/spacing.rs b/library/src/layout/spacing.rs index 588f9f290..0cbf66411 100644 --- a/library/src/layout/spacing.rs +++ b/library/src/layout/spacing.rs @@ -157,7 +157,7 @@ impl Behave for VElem { } } -cast_from_value! { +cast! { VElem, v: Content => v.to::().cloned().ok_or("expected `v` element")?, } @@ -221,23 +221,20 @@ impl PartialOrd for Spacing { } } -cast_from_value! { +cast! { Spacing, + self => match self { + Self::Rel(rel) => { + if rel.rel.is_zero() { + rel.abs.into_value() + } else if rel.abs.is_zero() { + rel.rel.into_value() + } else { + rel.into_value() + } + } + Self::Fr(fr) => fr.into_value(), + }, v: Rel => Self::Rel(v), v: Fr => Self::Fr(v), } - -cast_to_value! { - v: Spacing => match v { - Spacing::Rel(rel) => { - if rel.rel.is_zero() { - Value::Length(rel.abs) - } else if rel.abs.is_zero() { - Value::Ratio(rel.rel) - } else { - Value::Relative(rel) - } - } - Spacing::Fr(fr) => Value::Fraction(fr), - } -} diff --git a/library/src/layout/stack.rs b/library/src/layout/stack.rs index 2ce2cc28b..f8670026e 100644 --- a/library/src/layout/stack.rs +++ b/library/src/layout/stack.rs @@ -90,19 +90,16 @@ impl Debug for StackChild { } } -cast_from_value! { +cast! { StackChild, + self => match self { + Self::Spacing(spacing) => spacing.into_value(), + Self::Block(content) => content.into_value(), + }, v: Spacing => Self::Spacing(v), v: Content => Self::Block(v), } -cast_to_value! { - v: StackChild => match v { - StackChild::Spacing(spacing) => spacing.into(), - StackChild::Block(content) => content.into(), - } -} - /// Performs stack layout. struct StackLayouter<'a> { /// The stacking direction. diff --git a/library/src/layout/table.rs b/library/src/layout/table.rs index 9d5a83d22..74370cb2e 100644 --- a/library/src/layout/table.rs +++ b/library/src/layout/table.rs @@ -1,3 +1,5 @@ +use typst::eval::{CastInfo, Reflect}; + use crate::layout::{AlignElem, GridLayouter, TrackSizings}; use crate::meta::{Figurable, LocalName}; use crate::prelude::*; @@ -243,15 +245,12 @@ pub enum Celled { Array(Vec), } -impl Celled { +impl Celled { /// Resolve the value based on the cell position. pub fn resolve(&self, vt: &mut Vt, x: usize, y: usize) -> SourceResult { Ok(match self { Self::Value(value) => value.clone(), - Self::Func(func) => func - .call_vt(vt, [Value::Int(x as i64), Value::Int(y as i64)])? - .cast() - .at(func.span())?, + Self::Func(func) => func.call_vt(vt, [x, y])?.cast().at(func.span())?, Self::Array(array) => x .checked_rem(array.len()) .and_then(|i| array.get(i)) @@ -267,33 +266,35 @@ impl Default for Celled { } } -impl Cast for Celled { - fn is(value: &Value) -> bool { - matches!(value, Value::Array(_) | Value::Func(_)) || T::is(value) - } - - fn cast(value: Value) -> StrResult { - match value { - Value::Func(v) => Ok(Self::Func(v)), - Value::Array(array) => { - Ok(Self::Array(array.into_iter().map(T::cast).collect::>()?)) - } - v if T::is(&v) => Ok(Self::Value(T::cast(v)?)), - v => ::error(v), - } - } - +impl Reflect for Celled { fn describe() -> CastInfo { - T::describe() + CastInfo::Type("array") + CastInfo::Type("function") + T::describe() + Array::describe() + Func::describe() + } + + fn castable(value: &Value) -> bool { + Array::castable(value) || Func::castable(value) || T::castable(value) } } -impl> From> for Value { - fn from(celled: Celled) -> Self { - match celled { - Celled::Value(value) => value.into(), - Celled::Func(func) => func.into(), - Celled::Array(arr) => arr.into(), +impl IntoValue for Celled { + fn into_value(self) -> Value { + match self { + Self::Value(value) => value.into_value(), + Self::Func(func) => func.into_value(), + Self::Array(arr) => arr.into_value(), + } + } +} + +impl FromValue for Celled { + fn from_value(value: Value) -> StrResult { + match value { + Value::Func(v) => Ok(Self::Func(v)), + Value::Array(array) => Ok(Self::Array( + array.into_iter().map(T::from_value).collect::>()?, + )), + v if T::castable(&v) => Ok(Self::Value(T::from_value(v)?)), + v => Err(Self::error(&v)), } } } diff --git a/library/src/layout/terms.rs b/library/src/layout/terms.rs index 43127d97f..0cfe98d97 100644 --- a/library/src/layout/terms.rs +++ b/library/src/layout/terms.rs @@ -147,7 +147,7 @@ pub struct TermItem { pub description: Content, } -cast_from_value! { +cast! { TermItem, array: Array => { let mut iter = array.into_iter(); diff --git a/library/src/lib.rs b/library/src/lib.rs index 0f8346f08..0bd88501c 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -1,6 +1,7 @@ //! Typst's standard library. #![allow(clippy::wildcard_in_or_patterns)] +#![allow(clippy::manual_range_contains)] #![allow(clippy::comparison_chain)] pub mod compute; @@ -15,7 +16,7 @@ pub mod visualize; use typst::diag::At; use typst::eval::{LangItems, Library, Module, Scope}; -use typst::geom::{Align, Color, Dir, GenAlign, Smart}; +use typst::geom::Smart; use typst::model::{Element, Styles}; use self::layout::LayoutRoot; @@ -41,40 +42,6 @@ fn global(math: Module) -> Module { symbols::define(&mut global); global.define("math", math); - // Colors. - global.define("black", Color::BLACK); - global.define("gray", Color::GRAY); - global.define("silver", Color::SILVER); - global.define("white", Color::WHITE); - global.define("navy", Color::NAVY); - global.define("blue", Color::BLUE); - global.define("aqua", Color::AQUA); - global.define("teal", Color::TEAL); - global.define("eastern", Color::EASTERN); - global.define("purple", Color::PURPLE); - global.define("fuchsia", Color::FUCHSIA); - global.define("maroon", Color::MAROON); - global.define("red", Color::RED); - global.define("orange", Color::ORANGE); - global.define("yellow", Color::YELLOW); - global.define("olive", Color::OLIVE); - global.define("green", Color::GREEN); - global.define("lime", Color::LIME); - - // Other constants. - global.define("ltr", Dir::LTR); - global.define("rtl", Dir::RTL); - global.define("ttb", Dir::TTB); - global.define("btt", Dir::BTT); - global.define("start", GenAlign::Start); - global.define("end", GenAlign::End); - global.define("left", GenAlign::Specific(Align::Left)); - global.define("center", GenAlign::Specific(Align::Center)); - global.define("right", GenAlign::Specific(Align::Right)); - global.define("top", GenAlign::Specific(Align::Top)); - global.define("horizon", GenAlign::Specific(Align::Horizon)); - global.define("bottom", GenAlign::Specific(Align::Bottom)); - Module::new("global").with_scope(global) } diff --git a/library/src/math/accent.rs b/library/src/math/accent.rs index 7878782b7..4a12f5c55 100644 --- a/library/src/math/accent.rs +++ b/library/src/math/accent.rs @@ -128,15 +128,12 @@ impl Accent { } } -cast_from_value! { +cast! { Accent, + self => self.0.into_value(), v: char => Self::new(v), v: Content => match v.to::() { Some(elem) => Value::Str(elem.text().into()).cast()?, None => Err("expected text")?, }, } - -cast_to_value! { - v: Accent => v.0.into() -} diff --git a/library/src/math/delimited.rs b/library/src/math/delimited.rs index 403f79227..08d18a5e4 100644 --- a/library/src/math/delimited.rs +++ b/library/src/math/delimited.rs @@ -113,12 +113,11 @@ fn scale( /// /// Display: Floor /// Category: math -/// Returns: content #[func] pub fn floor( /// The expression to floor. body: Content, -) -> Value { +) -> Content { delimited(body, '⌊', '⌋') } @@ -131,12 +130,11 @@ pub fn floor( /// /// Display: Ceil /// Category: math -/// Returns: content #[func] pub fn ceil( /// The expression to ceil. body: Content, -) -> Value { +) -> Content { delimited(body, '⌈', '⌉') } @@ -149,12 +147,11 @@ pub fn ceil( /// /// Display: Round /// Category: math -/// Returns: content #[func] pub fn round( /// The expression to round. body: Content, -) -> Value { +) -> Content { delimited(body, '⌊', '⌉') } @@ -168,12 +165,11 @@ pub fn round( /// /// Display: Abs /// Category: math -/// Returns: content #[func] pub fn abs( /// The expression to take the absolute value of. body: Content, -) -> Value { +) -> Content { delimited(body, '|', '|') } @@ -186,21 +182,19 @@ pub fn abs( /// /// Display: Norm /// Category: math -/// Returns: content #[func] pub fn norm( /// The expression to take the norm of. body: Content, -) -> Value { +) -> Content { delimited(body, '‖', '‖') } -fn delimited(body: Content, left: char, right: char) -> Value { +fn delimited(body: Content, left: char, right: char) -> Content { LrElem::new(Content::sequence([ TextElem::packed(left), body, TextElem::packed(right), ])) .pack() - .into() } diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs index 3ea96cfad..4ab23f239 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -59,11 +59,11 @@ pub fn module() -> Module { // Grouping. math.define("lr", LrElem::func()); - math.define("abs", abs); - math.define("norm", norm); - math.define("floor", floor); - math.define("ceil", ceil); - math.define("round", round); + math.define("abs", abs_func()); + math.define("norm", norm_func()); + math.define("floor", floor_func()); + math.define("ceil", ceil_func()); + math.define("round", round_func()); // Attachments and accents. math.define("attach", AttachElem::func()); @@ -86,24 +86,24 @@ pub fn module() -> Module { math.define("cases", CasesElem::func()); // Roots. - math.define("sqrt", sqrt); + math.define("sqrt", sqrt_func()); math.define("root", RootElem::func()); // Styles. - math.define("upright", upright); - math.define("bold", bold); - math.define("italic", italic); - math.define("serif", serif); - math.define("sans", sans); - math.define("cal", cal); - math.define("frak", frak); - math.define("mono", mono); - math.define("bb", bb); + math.define("upright", upright_func()); + math.define("bold", bold_func()); + math.define("italic", italic_func()); + math.define("serif", serif_func()); + math.define("sans", sans_func()); + math.define("cal", cal_func()); + math.define("frak", frak_func()); + math.define("mono", mono_func()); + math.define("bb", bb_func()); - math.define("display", display); - math.define("inline", inline); - math.define("script", script); - math.define("sscript", sscript); + math.define("display", display_func()); + math.define("inline", inline_func()); + math.define("script", script_func()); + math.define("sscript", sscript_func()); // Text operators. math.define("op", OpElem::func()); @@ -197,9 +197,7 @@ impl Synthesize for EquationElem { let supplement = match self.supplement(styles) { Smart::Auto => TextElem::packed(self.local_name_in(styles)), Smart::Custom(None) => Content::empty(), - Smart::Custom(Some(supplement)) => { - supplement.resolve(vt, [self.clone().into()])? - } + Smart::Custom(Some(supplement)) => supplement.resolve(vt, [self.clone()])?, }; self.push_block(self.block(styles)); diff --git a/library/src/math/root.rs b/library/src/math/root.rs index 05f3a2a7e..d1c5f46a0 100644 --- a/library/src/math/root.rs +++ b/library/src/math/root.rs @@ -9,13 +9,12 @@ use super::*; /// /// Display: Square Root /// Category: math -/// Returns: content #[func] pub fn sqrt( /// The expression to take the square root of. radicand: Content, -) -> Value { - RootElem::new(radicand).pack().into() +) -> Content { + RootElem::new(radicand).pack() } /// A general root. diff --git a/library/src/math/row.rs b/library/src/math/row.rs index 9b693bc1c..6e666e897 100644 --- a/library/src/math/row.rs +++ b/library/src/math/row.rs @@ -233,3 +233,22 @@ impl> From for MathRow { Self(vec![fragment.into()]) } } + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +enum LeftRightAlternator { + Left, + Right, +} + +impl Iterator for LeftRightAlternator { + type Item = LeftRightAlternator; + + fn next(&mut self) -> Option { + let r = Some(*self); + match self { + Self::Left => *self = Self::Right, + Self::Right => *self = Self::Left, + } + r + } +} diff --git a/library/src/math/style.rs b/library/src/math/style.rs index e0e1ccadf..05ff64dcc 100644 --- a/library/src/math/style.rs +++ b/library/src/math/style.rs @@ -9,13 +9,12 @@ use super::*; /// /// Display: Bold /// Category: math -/// Returns: content #[func] pub fn bold( /// The content to style. body: Content, -) -> Value { - MathStyleElem::new(body).with_bold(Some(true)).pack().into() +) -> Content { + MathStyleElem::new(body).with_bold(Some(true)).pack() } /// Upright (non-italic) font style in math. @@ -27,13 +26,12 @@ pub fn bold( /// /// Display: Upright /// Category: math -/// Returns: content #[func] pub fn upright( /// The content to style. body: Content, -) -> Value { - MathStyleElem::new(body).with_italic(Some(false)).pack().into() +) -> Content { + MathStyleElem::new(body).with_italic(Some(false)).pack() } /// Italic font style in math. @@ -42,13 +40,12 @@ pub fn upright( /// /// Display: Italic /// Category: math -/// Returns: content #[func] pub fn italic( /// The content to style. body: Content, -) -> Value { - MathStyleElem::new(body).with_italic(Some(true)).pack().into() +) -> Content { + MathStyleElem::new(body).with_italic(Some(true)).pack() } /// Serif (roman) font style in math. /// @@ -56,16 +53,12 @@ pub fn italic( /// /// Display: Serif /// Category: math -/// Returns: content #[func] pub fn serif( /// The content to style. body: Content, -) -> Value { - MathStyleElem::new(body) - .with_variant(Some(MathVariant::Serif)) - .pack() - .into() +) -> Content { + MathStyleElem::new(body).with_variant(Some(MathVariant::Serif)).pack() } /// Sans-serif font style in math. @@ -77,16 +70,12 @@ pub fn serif( /// /// Display: Sans-serif /// Category: math -/// Returns: content #[func] pub fn sans( /// The content to style. body: Content, -) -> Value { - MathStyleElem::new(body) - .with_variant(Some(MathVariant::Sans)) - .pack() - .into() +) -> Content { + MathStyleElem::new(body).with_variant(Some(MathVariant::Sans)).pack() } /// Calligraphic font style in math. @@ -98,16 +87,12 @@ pub fn sans( /// /// Display: Calligraphic /// Category: math -/// Returns: content #[func] pub fn cal( /// The content to style. body: Content, -) -> Value { - MathStyleElem::new(body) - .with_variant(Some(MathVariant::Cal)) - .pack() - .into() +) -> Content { + MathStyleElem::new(body).with_variant(Some(MathVariant::Cal)).pack() } /// Fraktur font style in math. @@ -119,16 +104,12 @@ pub fn cal( /// /// Display: Fraktur /// Category: math -/// Returns: content #[func] pub fn frak( /// The content to style. body: Content, -) -> Value { - MathStyleElem::new(body) - .with_variant(Some(MathVariant::Frak)) - .pack() - .into() +) -> Content { + MathStyleElem::new(body).with_variant(Some(MathVariant::Frak)).pack() } /// Monospace font style in math. @@ -140,16 +121,12 @@ pub fn frak( /// /// Display: Monospace /// Category: math -/// Returns: content #[func] pub fn mono( /// The content to style. body: Content, -) -> Value { - MathStyleElem::new(body) - .with_variant(Some(MathVariant::Mono)) - .pack() - .into() +) -> Content { + MathStyleElem::new(body).with_variant(Some(MathVariant::Mono)).pack() } /// Blackboard bold (double-struck) font style in math. @@ -166,16 +143,12 @@ pub fn mono( /// /// Display: Blackboard Bold /// Category: math -/// Returns: content #[func] pub fn bb( /// The content to style. body: Content, -) -> Value { - MathStyleElem::new(body) - .with_variant(Some(MathVariant::Bb)) - .pack() - .into() +) -> Content { + MathStyleElem::new(body).with_variant(Some(MathVariant::Bb)).pack() } /// Forced display style in math. @@ -189,7 +162,6 @@ pub fn bb( /// /// Display: Display Size /// Category: math -/// Returns: content #[func] pub fn display( /// The content to size. @@ -199,12 +171,11 @@ pub fn display( #[named] #[default(false)] cramp: bool, -) -> Value { +) -> Content { MathStyleElem::new(body) .with_size(Some(MathSize::Display)) .with_cramp(Some(cramp)) .pack() - .into() } /// Forced inline (text) style in math. @@ -219,7 +190,6 @@ pub fn display( /// /// Display: Inline Size /// Category: math -/// Returns: content #[func] pub fn inline( /// The content to size. @@ -229,12 +199,11 @@ pub fn inline( #[named] #[default(false)] cramp: bool, -) -> Value { +) -> Content { MathStyleElem::new(body) .with_size(Some(MathSize::Text)) .with_cramp(Some(cramp)) .pack() - .into() } /// Forced script style in math. @@ -248,7 +217,6 @@ pub fn inline( /// /// Display: Script Size /// Category: math -/// Returns: content #[func] pub fn script( /// The content to size. @@ -258,12 +226,11 @@ pub fn script( #[named] #[default(true)] cramp: bool, -) -> Value { +) -> Content { MathStyleElem::new(body) .with_size(Some(MathSize::Script)) .with_cramp(Some(cramp)) .pack() - .into() } /// Forced second script style in math. @@ -278,7 +245,6 @@ pub fn script( /// /// Display: Script-Script Size /// Category: math -/// Returns: content #[func] pub fn sscript( /// The content to size. @@ -288,12 +254,11 @@ pub fn sscript( #[named] #[default(true)] cramp: bool, -) -> Value { +) -> Content { MathStyleElem::new(body) .with_size(Some(MathSize::ScriptScript)) .with_cramp(Some(cramp)) .pack() - .into() } /// A font variant in math. diff --git a/library/src/meta/bibliography.rs b/library/src/meta/bibliography.rs index a2491bb59..5e5145b1a 100644 --- a/library/src/meta/bibliography.rs +++ b/library/src/meta/bibliography.rs @@ -84,16 +84,13 @@ pub struct BibliographyElem { #[derive(Debug, Default, Clone, Hash)] pub struct BibPaths(Vec); -cast_from_value! { +cast! { BibPaths, + self => self.0.into_value(), v: EcoString => Self(vec![v]), v: Array => Self(v.into_iter().map(Value::cast).collect::>()?), } -cast_to_value! { - v: BibPaths => v.0.into() -} - impl BibliographyElem { /// Find the document's bibliography. pub fn find(introspector: Tracked) -> StrResult { @@ -374,7 +371,7 @@ impl Show for CiteElem { } } -cast_from_value! { +cast! { CiteElem, v: Content => v.to::().cloned().ok_or("expected citation")?, } diff --git a/library/src/meta/context.rs b/library/src/meta/context.rs index ad4663056..d599c63e3 100644 --- a/library/src/meta/context.rs +++ b/library/src/meta/context.rs @@ -46,7 +46,6 @@ use crate::prelude::*; /// /// Display: Locate /// Category: meta -/// Returns: content #[func] pub fn locate( /// A function that receives a `location`. Its return value is displayed @@ -56,8 +55,8 @@ pub fn locate( /// `locate` appears in the document. That makes it possible to generate /// content that depends on its own location in the document. func: Func, -) -> Value { - LocateElem::new(func).pack().into() +) -> Content { + LocateElem::new(func).pack() } /// Executes a `locate` call. @@ -79,7 +78,7 @@ impl Show for LocateElem { } let location = self.0.location().unwrap(); - Ok(self.func().call_vt(vt, [location.into()])?.display()) + Ok(self.func().call_vt(vt, [location])?.display()) } } @@ -102,7 +101,6 @@ impl Show for LocateElem { /// /// Display: Style /// Category: meta -/// Returns: content #[func] pub fn style( /// A function to call with the styles. Its return value is displayed @@ -112,8 +110,8 @@ pub fn style( /// `style` appears in the document. That makes it possible to generate /// content that depends on the style context it appears in. func: Func, -) -> Value { - StyleElem::new(func).pack().into() +) -> Content { + StyleElem::new(func).pack() } /// Executes a style access. @@ -130,7 +128,7 @@ struct StyleElem { impl Show for StyleElem { #[tracing::instrument(name = "StyleElem::show", skip_all)] fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult { - Ok(self.func().call_vt(vt, [styles.to_map().into()])?.display()) + Ok(self.func().call_vt(vt, [styles.to_map()])?.display()) } } @@ -177,7 +175,6 @@ impl Show for StyleElem { /// /// Display: Layout /// Category: meta -/// Returns: content #[func] pub fn layout( /// A function to call with the outer container's size. Its return value is @@ -190,8 +187,8 @@ pub fn layout( /// `layout` appears in the document. That makes it possible to generate /// content that depends on the size of the container it is inside of. func: Func, -) -> Value { - LayoutElem::new(func).pack().into() +) -> Content { + LayoutElem::new(func).pack() } /// Executes a `layout` call. @@ -213,16 +210,13 @@ impl Layout for LayoutElem { styles: StyleChain, regions: Regions, ) -> SourceResult { - // Gets the current region's base size, which will be the size of the outer container, - // or of the page if there is no such container. + // Gets the current region's base size, which will be the size of the + // outer container, or of the page if there is no such container. let Size { x, y } = regions.base(); - let size_dict = dict! { "width" => x, "height" => y }.into(); - let result = self .func() - .call_vt(vt, [size_dict])? // calls func(size) + .call_vt(vt, [dict! { "width" => x, "height" => y }])? .display(); - result.layout(vt, styles, regions) } } diff --git a/library/src/meta/counter.rs b/library/src/meta/counter.rs index 46992d389..ef4646ab2 100644 --- a/library/src/meta/counter.rs +++ b/library/src/meta/counter.rs @@ -277,7 +277,6 @@ use crate::prelude::*; /// /// Display: Counter /// Category: meta -/// Returns: counter #[func] pub fn counter( /// The key that identifies this counter. @@ -288,8 +287,8 @@ pub fn counter( /// - If this is an element function or selector, counts through its elements, /// - If this is the [`page`]($func/page) function, counts through pages. key: CounterKey, -) -> Value { - Value::dynamic(Counter::new(key)) +) -> Counter { + Counter::new(key) } /// Counts through pages, elements, and more. @@ -317,17 +316,17 @@ impl Counter { span: Span, ) -> SourceResult { let value = match method { - "display" => { - self.display(args.eat()?, args.named("both")?.unwrap_or(false)).into() - } + "display" => self + .display(args.eat()?, args.named("both")?.unwrap_or(false)) + .into_value(), "step" => self .update(CounterUpdate::Step( args.named("level")?.unwrap_or(NonZeroUsize::ONE), )) - .into(), - "update" => self.update(args.expect("value or function")?).into(), - "at" => self.at(&mut vm.vt, args.expect("location")?)?.into(), - "final" => self.final_(&mut vm.vt, args.expect("location")?)?.into(), + .into_value(), + "update" => self.update(args.expect("value or function")?).into_value(), + "at" => self.at(&mut vm.vt, args.expect("location")?)?.into_value(), + "final" => self.final_(&mut vm.vt, args.expect("location")?)?.into_value(), _ => bail!(span, "type counter has no method `{}`", method), }; args.finish()?; @@ -342,10 +341,7 @@ impl Counter { /// Get the value of the state at the given location. pub fn at(&self, vt: &mut Vt, location: Location) -> SourceResult { let sequence = self.sequence(vt)?; - let offset = vt - .introspector - .query(&Selector::before(self.selector(), location, true)) - .len(); + let offset = vt.introspector.query(&self.selector().before(location, true)).len(); let (mut state, page) = sequence[offset].clone(); if self.is_page() { let delta = vt.introspector.page(location).get().saturating_sub(page.get()); @@ -479,8 +475,8 @@ impl Debug for Counter { } } -cast_from_value! { - Counter: "counter", +cast! { + type Counter: "counter", } /// Identifies a counter. @@ -495,7 +491,7 @@ pub enum CounterKey { Str(Str), } -cast_from_value! { +cast! { CounterKey, v: Str => Self::Str(v), label: Label => Self::Selector(Selector::Label(label)), @@ -503,7 +499,7 @@ cast_from_value! { if v == PageElem::func() { Self::Page } else { - Self::Selector(LocatableSelector::cast(Value::from(v))?.0) + Self::Selector(LocatableSelector::from_value(v.into_value())?.0) } }, selector: LocatableSelector => Self::Selector(selector.0), @@ -536,8 +532,8 @@ impl Debug for CounterUpdate { } } -cast_from_value! { - CounterUpdate: "counter update", +cast! { + type CounterUpdate: "counter update", v: CounterState => Self::Set(v), v: Func => Self::Func(v), } @@ -559,10 +555,7 @@ impl CounterState { CounterUpdate::Set(state) => *self = state, CounterUpdate::Step(level) => self.step(level, 1), CounterUpdate::Func(func) => { - *self = func - .call_vt(vt, self.0.iter().copied().map(Into::into))? - .cast() - .at(func.span())? + *self = func.call_vt(vt, self.0.iter().copied())?.cast().at(func.span())? } } Ok(()) @@ -593,8 +586,9 @@ impl CounterState { } } -cast_from_value! { +cast! { CounterState, + self => Value::Array(self.0.into_iter().map(IntoValue::into_value).collect()), num: usize => Self(smallvec![num]), array: Array => Self(array .into_iter() @@ -602,10 +596,6 @@ cast_from_value! { .collect::>()?), } -cast_to_value! { - v: CounterState => Value::Array(v.0.into_iter().map(Into::into).collect()) -} - /// Executes a display of a state. /// /// Display: State diff --git a/library/src/meta/document.rs b/library/src/meta/document.rs index 1ce900ed2..db036e0ab 100644 --- a/library/src/meta/document.rs +++ b/library/src/meta/document.rs @@ -78,12 +78,9 @@ impl LayoutRoot for DocumentElem { #[derive(Debug, Default, Clone, Hash)] pub struct Author(Vec); -cast_from_value! { +cast! { Author, + self => self.0.into_value(), v: EcoString => Self(vec![v]), v: Array => Self(v.into_iter().map(Value::cast).collect::>()?), } - -cast_to_value! { - v: Author => v.0.into() -} diff --git a/library/src/meta/figure.rs b/library/src/meta/figure.rs index 670878321..0d2187706 100644 --- a/library/src/meta/figure.rs +++ b/library/src/meta/figure.rs @@ -209,7 +209,7 @@ impl Synthesize for FigureElem { }; let target = descendant.unwrap_or_else(|| self.body()); - supplement.resolve(vt, [target.into()])? + supplement.resolve(vt, [target])? } }; @@ -312,8 +312,8 @@ impl FigureElem { self.counter(), self.numbering(StyleChain::default()), ) { - let numbers = - counter.at(vt, self.0.location().unwrap())?.display(vt, &numbering)?; + let loc = self.0.location().unwrap(); + let numbers = counter.at(vt, loc)?.display(vt, &numbering)?; if !supplement.is_empty() { supplement += TextElem::packed("\u{a0}"); @@ -335,19 +335,16 @@ pub enum FigureKind { Name(EcoString), } -cast_from_value! { +cast! { FigureKind, + self => match self { + Self::Elem(v) => v.into_value(), + Self::Name(v) => v.into_value(), + }, v: ElemFunc => Self::Elem(v), v: EcoString => Self::Name(v), } -cast_to_value! { - v: FigureKind => match v { - FigureKind::Elem(v) => v.into(), - FigureKind::Name(v) => v.into(), - } -} - /// An element that can be auto-detected in a figure. /// /// This trait is used to determine the type of a figure. diff --git a/library/src/meta/footnote.rs b/library/src/meta/footnote.rs index 22de91c3f..1c95716c8 100644 --- a/library/src/meta/footnote.rs +++ b/library/src/meta/footnote.rs @@ -211,7 +211,7 @@ impl Finalize for FootnoteEntry { } } -cast_from_value! { +cast! { FootnoteElem, v: Content => v.to::().cloned().unwrap_or_else(|| Self::new(v.clone())), } diff --git a/library/src/meta/heading.rs b/library/src/meta/heading.rs index d6ad7044d..7a333e365 100644 --- a/library/src/meta/heading.rs +++ b/library/src/meta/heading.rs @@ -104,9 +104,7 @@ impl Synthesize for HeadingElem { let supplement = match self.supplement(styles) { Smart::Auto => TextElem::packed(self.local_name_in(styles)), Smart::Custom(None) => Content::empty(), - Smart::Custom(Some(supplement)) => { - supplement.resolve(vt, [self.clone().into()])? - } + Smart::Custom(Some(supplement)) => supplement.resolve(vt, [self.clone()])?, }; self.push_level(self.level(styles)); @@ -164,7 +162,7 @@ impl Count for HeadingElem { } } -cast_from_value! { +cast! { HeadingElem, v: Content => v.to::().ok_or("expected heading")?.clone(), } diff --git a/library/src/meta/link.rs b/library/src/meta/link.rs index 93b9999db..43f6a34d6 100644 --- a/library/src/meta/link.rs +++ b/library/src/meta/link.rs @@ -125,19 +125,16 @@ pub enum LinkTarget { Label(Label), } -cast_from_value! { +cast! { LinkTarget, + self => match self { + Self::Dest(v) => v.into_value(), + Self::Label(v) => v.into_value(), + }, v: Destination => Self::Dest(v), v: Label => Self::Label(v), } -cast_to_value! { - v: LinkTarget => match v { - LinkTarget::Dest(v) => v.into(), - LinkTarget::Label(v) => v.into(), - } -} - impl From for LinkTarget { fn from(dest: Destination) -> Self { Self::Dest(dest) diff --git a/library/src/meta/mod.rs b/library/src/meta/mod.rs index 724e5d20d..dcac63795 100644 --- a/library/src/meta/mod.rs +++ b/library/src/meta/mod.rs @@ -42,14 +42,14 @@ pub(super) fn define(global: &mut Scope) { global.define("footnote", FootnoteElem::func()); global.define("cite", CiteElem::func()); global.define("bibliography", BibliographyElem::func()); - global.define("locate", locate); - global.define("style", style); - global.define("layout", layout); - global.define("counter", counter); - global.define("numbering", numbering); - global.define("state", state); - global.define("query", query); - global.define("selector", selector); + global.define("locate", locate_func()); + global.define("style", style_func()); + global.define("layout", layout_func()); + global.define("counter", counter_func()); + global.define("numbering", numbering_func()); + global.define("state", state_func()); + global.define("query", query_func()); + global.define("selector", selector_func()); } /// The named with which an element is referenced. diff --git a/library/src/meta/numbering.rs b/library/src/meta/numbering.rs index fbe7306c2..ef0d0f70a 100644 --- a/library/src/meta/numbering.rs +++ b/library/src/meta/numbering.rs @@ -32,14 +32,13 @@ use crate::text::Case; /// /// Display: Numbering /// Category: meta -/// Returns: any #[func] 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. + /// **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 @@ -52,9 +51,9 @@ pub fn numbering( /// 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 + /// 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. @@ -65,8 +64,10 @@ pub fn numbering( /// given, the last counting symbol with its prefix is repeated. #[variadic] numbers: Vec, -) -> Value { - numbering.apply_vm(vm, &numbers)? + /// The virtual machine. + vm: &mut Vm, +) -> SourceResult { + numbering.apply_vm(vm, &numbers) } /// How to number a sequence of things. @@ -84,8 +85,7 @@ impl Numbering { Ok(match self { Self::Pattern(pattern) => Value::Str(pattern.apply(numbers).into()), Self::Func(func) => { - let args = - Args::new(func.span(), numbers.iter().map(|&n| Value::Int(n as i64))); + let args = Args::new(func.span(), numbers.iter().copied()); func.call_vm(vm, args)? } }) @@ -95,9 +95,7 @@ impl Numbering { pub fn apply_vt(&self, vt: &mut Vt, numbers: &[usize]) -> SourceResult { Ok(match self { Self::Pattern(pattern) => Value::Str(pattern.apply(numbers).into()), - Self::Func(func) => { - func.call_vt(vt, numbers.iter().map(|&n| Value::Int(n as i64)))? - } + Self::Func(func) => func.call_vt(vt, numbers.iter().copied())?, }) } @@ -116,19 +114,16 @@ impl From for Numbering { } } -cast_from_value! { +cast! { Numbering, + self => match self { + Self::Pattern(pattern) => pattern.into_value(), + Self::Func(func) => func.into_value(), + }, v: NumberingPattern => Self::Pattern(v), v: Func => Self::Func(v), } -cast_to_value! { - v: Numbering => match v { - Numbering::Pattern(pattern) => pattern.into(), - Numbering::Func(func) => func.into(), - } -} - /// How to turn a number into text. /// /// A pattern consists of a prefix, followed by one of `1`, `a`, `A`, `i`, @@ -230,15 +225,11 @@ impl FromStr for NumberingPattern { } } -cast_from_value! { +cast! { NumberingPattern, - v: Str => v.parse()?, -} - -cast_to_value! { - v: NumberingPattern => { + self => { let mut pat = EcoString::new(); - for (prefix, kind, case) in &v.pieces { + for (prefix, kind, case) in &self.pieces { pat.push_str(prefix); let mut c = kind.to_char(); if *case == Case::Upper { @@ -246,9 +237,10 @@ cast_to_value! { } pat.push(c); } - pat.push_str(&v.suffix); - pat.into() - } + pat.push_str(&self.suffix); + pat.into_value() + }, + v: Str => v.parse()?, } /// Different kinds of numberings. diff --git a/library/src/meta/outline.rs b/library/src/meta/outline.rs index 089eceb24..894c3b8b9 100644 --- a/library/src/meta/outline.rs +++ b/library/src/meta/outline.rs @@ -367,19 +367,14 @@ impl OutlineIndent { // Length => indent with some fixed spacing per level Some(Smart::Custom(OutlineIndent::Length(length))) => { - let Ok(depth): Result = ancestors.len().try_into() else { - bail!(span, "outline element depth too large"); - }; - - let hspace = HElem::new(*length).pack().repeat(depth).unwrap(); - seq.push(hspace); + seq.push(HElem::new(*length).pack().repeat(ancestors.len())); } // Function => call function with the current depth and take // the returned content Some(Smart::Custom(OutlineIndent::Function(func))) => { let depth = ancestors.len(); - let returned = func.call_vt(vt, [depth.into()])?; + let returned = func.call_vt(vt, [depth])?; let Ok(returned) = returned.cast::() else { bail!( span, @@ -396,17 +391,14 @@ impl OutlineIndent { } } -cast_from_value! { +cast! { OutlineIndent, - b: bool => OutlineIndent::Bool(b), - s: Spacing => OutlineIndent::Length(s), - f: Func => OutlineIndent::Function(f), -} - -cast_to_value! { - v: OutlineIndent => match v { - OutlineIndent::Bool(b) => b.into(), - OutlineIndent::Length(s) => s.into(), - OutlineIndent::Function(f) => f.into() - } + self => match self { + Self::Bool(v) => v.into_value(), + Self::Length(v) => v.into_value(), + Self::Function(v) => v.into_value() + }, + v: bool => OutlineIndent::Bool(v), + v: Spacing => OutlineIndent::Length(v), + v: Func => OutlineIndent::Function(v), } diff --git a/library/src/meta/query.rs b/library/src/meta/query.rs index ad7274f12..7f839f972 100644 --- a/library/src/meta/query.rs +++ b/library/src/meta/query.rs @@ -98,7 +98,6 @@ use crate::prelude::*; /// /// Display: Query /// Category: meta -/// Returns: array #[func] pub fn query( /// Can be an element function like a `heading` or `figure`, a `{