diff --git a/library/src/basics/table.rs b/library/src/basics/table.rs index 208818301..6ee2b0b26 100644 --- a/library/src/basics/table.rs +++ b/library/src/basics/table.rs @@ -79,7 +79,7 @@ impl TableNode { /// # Example /// ``` /// #table( - /// fill: (col, _) => if odd(col) { luma(240) } else { white }, + /// fill: (col, _) => if calc.odd(col) { luma(240) } else { white }, /// align: (col, row) => /// if row == 0 { center } /// else if col == 0 { left } diff --git a/library/src/compute/calc.rs b/library/src/compute/calc.rs index 640ec3c53..c65d32a07 100644 --- a/library/src/compute/calc.rs +++ b/library/src/compute/calc.rs @@ -1,15 +1,45 @@ use std::cmp::Ordering; +use typst::model::{Module, Scope}; + use crate::prelude::*; +/// A module with computational functions. +pub fn calc() -> Module { + let mut scope = Scope::new(); + scope.def_func::("abs"); + scope.def_func::("pow"); + scope.def_func::("sqrt"); + scope.def_func::("sin"); + scope.def_func::("cos"); + scope.def_func::("tan"); + scope.def_func::("asin"); + scope.def_func::("acos"); + scope.def_func::("atan"); + scope.def_func::("sinh"); + scope.def_func::("cosh"); + scope.def_func::("tanh"); + scope.def_func::("log"); + scope.def_func::("floor"); + scope.def_func::("ceil"); + scope.def_func::("round"); + scope.def_func::("clamp"); + scope.def_func::("min"); + scope.def_func::("max"); + scope.def_func::("even"); + scope.def_func::("odd"); + scope.def_func::("mod"); + Module::new("calc").with_scope(scope) +} + /// # Absolute /// The absolute value of a numeric value. /// /// ## Example /// ``` -/// #abs(-5) \ -/// #abs(5pt - 2cm) \ -/// #abs(2fr) +/// #calc.abs(-5) \ +/// #calc.abs(5pt - 2cm) \ +/// #calc.abs(2fr) /// ``` /// /// ## Parameters @@ -37,13 +67,482 @@ castable! { v: Fr => Self(Value::Fraction(v.abs())), } +/// # Power +/// Raise a value to some exponent. +/// +/// ## Example +/// ``` +/// #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. +/// +/// ## Category +/// calculate +#[func] +pub fn pow(args: &mut Args) -> SourceResult { + let base = args.expect::("base")?; + let exponent = args + .expect::>("exponent") + .and_then(|n| match n.v { + Num::Int(i) if i > u32::MAX as i64 => bail!(n.span, "exponent too large"), + Num::Int(i) if i >= 0 => Ok(n), + Num::Float(f) if f >= 0.0 => Ok(n), + _ => bail!(n.span, "exponent must be non-negative"), + })? + .v; + + Ok(base.apply2(exponent, |a, b| a.pow(b as u32), |a, b| a.powf(b))) +} + +/// # Square Root +/// The square root of a number. +/// +/// ## Example +/// ``` +/// #calc.sqrt(16) \ +/// #calc.sqrt(2.5) +/// ``` +/// +/// ## Parameters +/// - value: Num (positional, required) +/// The number whose square root to calculate. Must be non-negative. +/// +/// ## Category +/// calculate +#[func] +pub fn sqrt(args: &mut Args) -> SourceResult { + let value = args.expect::>("value")?; + if value.v.is_negative() { + bail!(value.span, "cannot take square root of negative number"); + } + Ok(Value::Float(value.v.float().sqrt())) +} + +/// # Sine +/// Calculate the sine of an angle. When called with an integer or a number, +/// they will be interpreted as radians. +/// +/// ## Example +/// ``` +/// #assert(calc.sin(90deg) == calc.sin(-270deg)) +/// #calc.sin(1.5) \ +/// #calc.sin(90deg) +/// ``` +/// +/// ## Parameters +/// - angle: AngleLike (positional, required) +/// The angle whose sine to calculate. +/// +/// ## Category +/// calculate +#[func] +pub fn sin(args: &mut Args) -> SourceResult { + let arg = args.expect::("angle")?; + + Ok(Value::Float(match arg { + AngleLike::Angle(a) => a.sin(), + AngleLike::Int(n) => (n as f64).sin(), + AngleLike::Float(n) => n.sin(), + })) +} + +/// # Cosine +/// Calculate the cosine of an angle. When called with an integer or a number, +/// they will be interpreted as radians. +/// +/// ## Example +/// ``` +/// #calc.cos(90deg) +/// #calc.cos(1.5) \ +/// #calc.cos(90deg) +/// ``` +/// +/// ## Parameters +/// - angle: AngleLike (positional, required) +/// The angle whose cosine to calculate. +/// +/// ## Category +/// calculate +#[func] +pub fn cos(args: &mut Args) -> SourceResult { + let arg = args.expect::("angle")?; + + Ok(Value::Float(match arg { + AngleLike::Angle(a) => a.cos(), + AngleLike::Int(n) => (n as f64).cos(), + AngleLike::Float(n) => n.cos(), + })) +} + +/// # Tangent +/// Calculate the tangent of an angle. When called with an integer or a number, +/// they will be interpreted as radians. +/// +/// ## Example +/// ``` +/// #calc.tan(1.5) \ +/// #calc.tan(90deg) +/// ``` +/// +/// ## Parameters +/// - angle: AngleLike (positional, required) +/// The angle whose tangent to calculate. +/// +/// ## Category +/// calculate +#[func] +pub fn tan(args: &mut Args) -> SourceResult { + let arg = args.expect::("angle")?; + + Ok(Value::Float(match arg { + AngleLike::Angle(a) => a.tan(), + AngleLike::Int(n) => (n as f64).tan(), + AngleLike::Float(n) => n.tan(), + })) +} + +/// # Arcsine +/// Calculate the arcsine of a number. +/// +/// ## Example +/// ``` +/// #calc.asin(0) \ +/// #calc.asin(1) +/// ``` +/// +/// ## Parameters +/// - value: Num (positional, required) +/// The number whose arcsine to calculate. Must be between -1 and 1. +/// +/// ## Category +/// calculate +#[func] +pub fn asin(args: &mut Args) -> SourceResult { + let Spanned { v, span } = args.expect::>("value")?; + let val = v.float(); + if val < -1.0 || val > 1.0 { + bail!(span, "arcsin must be between -1 and 1"); + } + + Ok(Value::Angle(Angle::rad(val.asin()))) +} + +/// # Arccosine +/// Calculate the arccosine of a number. +/// +/// ## Example +/// ``` +/// #calc.acos(0) \ +/// #calc.acos(1) +/// ``` +/// +/// ## Parameters +/// - value: Num (positional, required) +/// The number whose arccosine to calculate. Must be between -1 and 1. +/// +/// ## Category +/// calculate +#[func] +pub fn acos(args: &mut Args) -> SourceResult { + let Spanned { v, span } = args.expect::>("value")?; + let val = v.float(); + if val < -1.0 || val > 1.0 { + bail!(span, "arccos must be between -1 and 1"); + } + + Ok(Value::Angle(Angle::rad(val.acos()))) +} + +/// # Arctangent +/// Calculate the arctangent of a number. +/// +/// ## Example +/// ``` +/// #calc.atan(0) \ +/// #calc.atan(1) +/// ``` +/// +/// ## Parameters +/// - value: Num (positional, required) +/// The number whose arctangent to calculate. +/// +/// ## Category +/// calculate +#[func] +pub fn atan(args: &mut Args) -> SourceResult { + let value = args.expect::("value")?; + + Ok(Value::Angle(Angle::rad(value.float().atan()))) +} + +/// # Hyperbolic sine +/// Calculate the hyperbolic sine of an angle. When called with an integer or +/// a number, they will be interpreted as radians. +/// +/// ## Example +/// ``` +/// #calc.sinh(0) \ +/// #calc.sinh(45deg) +/// ``` +/// +/// ## Parameters +/// - angle: AngleLike (positional, required) +/// The angle whose hyperbolic sine to calculate. +/// +/// ## Category +/// calculate +#[func] +pub fn sinh(args: &mut Args) -> SourceResult { + let arg = args.expect::("angle")?; + + Ok(Value::Float(match arg { + AngleLike::Angle(a) => a.to_rad(), + AngleLike::Int(n) => (n as f64).sinh(), + AngleLike::Float(n) => n.sinh(), + })) +} + +/// # Hyperbolic cosine +/// Calculate the hyperbolic cosine of an angle. When called with an integer or +/// a number, they will be interpreted as radians. +/// +/// ## Example +/// ``` +/// #calc.cosh(0) \ +/// #calc.cosh(45deg) +/// ``` +/// +/// ## Parameters +/// - angle: AngleLike (positional, required) +/// The angle whose hyperbolic cosine to calculate. +/// +/// ## Category +/// calculate +#[func] +pub fn cosh(args: &mut Args) -> SourceResult { + let arg = args.expect::("angle")?; + + Ok(Value::Float(match arg { + AngleLike::Angle(a) => a.to_rad(), + AngleLike::Int(n) => (n as f64).cosh(), + AngleLike::Float(n) => n.cosh(), + })) +} + +/// # Hyperbolic tangent +/// Calculate the hyperbolic tangent of an angle. When called with an integer or +/// a number, they will be interpreted as radians. +/// +/// ## Example +/// ``` +/// #calc.tanh(0) \ +/// #calc.tanh(45deg) +/// ``` +/// +/// ## Parameters +/// - angle: AngleLike (positional, required) +/// The angle whose hyperbolic tangent to calculate. +/// +/// ## Category +/// calculate +#[func] +pub fn tanh(args: &mut Args) -> SourceResult { + let arg = args.expect::("angle")?; + + Ok(Value::Float(match arg { + AngleLike::Angle(a) => a.to_rad(), + AngleLike::Int(n) => (n as f64).tanh(), + AngleLike::Float(n) => n.tanh(), + })) +} + +/// # Logarithm +/// Calculate the logarithm of a number. +/// If the base is not specified, the logarithm is calculated in base 10. +/// +/// ## Example +/// ``` +/// #calc.log(100) \ +/// ``` +/// +/// ## Parameters +/// - value: Num (positional, required) +/// The number whose logarithm to calculate. +/// - base: Num (named) +/// The base of the logarithm. +/// +/// ## Category +/// calculate +#[func] +pub fn log(args: &mut Args) -> SourceResult { + let value = args.expect::("value")?; + let base = args.named::("base")?.unwrap_or_else(|| Num::Int(10)); + + Ok(value.apply2(base, |a, b| a.ilog(b) as i64, |a, b| a.log(b))) +} + +/// # Round down +/// Round a number down to the nearest integer. +/// If the number is already an integer, it is returned unchanged. +/// +/// ## Example +/// ``` +/// #assert(calc.floor(3.14) == 3) +/// #assert(calc.floor(3) == 3) +/// #calc.floor(500.1) +/// ``` +/// +/// ## Parameters +/// - value: Num (positional, required) +/// The number to round down. +/// +/// ## Category +/// calculate +#[func] +pub fn floor(args: &mut Args) -> SourceResult { + let value = args.expect::("value")?; + + Ok(match value { + Num::Int(n) => Value::Int(n), + Num::Float(n) => Value::Int(n.floor() as i64), + }) +} + +/// # Round up +/// Round a number up to the nearest integer. +/// If the number is already an integer, it is returned unchanged. +/// +/// ## Example +/// ``` +/// #assert(calc.ceil(3.14) == 4) +/// #assert(calc.ceil(3) == 3) +/// #calc.ceil(500.1) +/// ``` +/// +/// ## Parameters +/// - value: Num (positional, required) +/// The number to round up. +/// +/// ## Category +/// calculate +#[func] +pub fn ceil(args: &mut Args) -> SourceResult { + let value = args.expect::("value")?; + + Ok(match value { + Num::Int(n) => Value::Int(n), + Num::Float(n) => Value::Int(n.ceil() as i64), + }) +} + +/// # Round +/// Round a number to the nearest integer. +/// Optionally, a number of decimal places can be specified. +/// +/// ## Example +/// ``` +/// #assert(calc.round(3.14) == 3) +/// #assert(calc.round(3.5) == 4) +/// #calc.round(3.1415, digits: 2) +/// ``` +/// +/// ## Parameters +/// - value: Num (positional, required) +/// The number to round. +/// - digits: i64 (named) +/// +/// ## Category +/// calculate +#[func] +pub fn round(args: &mut Args) -> SourceResult { + let value = args.expect::("value")?; + let digits = args.named::>("digits").and_then(|n| match n { + Some(Spanned { v, span }) if v < 0 => { + bail!(span, "digits must be non-negative") + } + Some(Spanned { v, span }) if v > i32::MAX as i64 => { + bail!(span, "digits must be less than {}", i32::MAX) + } + Some(Spanned { v, .. }) => Ok(v as i32), + None => Ok(0), + })?; + + Ok(match value { + Num::Int(n) if digits == 0 => Value::Int(n), + _ => { + let n = value.float(); + let factor = 10.0_f64.powi(digits) as f64; + Value::Float((n * factor).round() / factor) + } + }) +} + +/// # Clamp +/// Clamp a number between a minimum and maximum value. +/// +/// ## Example +/// ``` +/// #assert(calc.clamp(5, 0, 10) == 5) +/// #assert(calc.clamp(5, 6, 10) == 6) +/// #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. +/// +/// ## Category +/// calculate +#[func] +pub fn clamp(args: &mut Args) -> SourceResult { + let value = args.expect::("value")?; + let min = args.expect::("min")?; + let max = args.expect::>("max")?; + + if max.v.float() < min.float() { + bail!(max.span, "max must be greater than or equal to min") + } + + Ok(value.apply3( + min, + max.v, + |v, min, max| { + if v < min { + min + } else if v > max { + max + } else { + v + } + }, + |v, min, max| { + if v < min { + min + } else if v > max { + max + } else { + v + } + }, + )) +} + /// # Minimum /// The minimum of a sequence of values. /// /// ## Example /// ``` -/// #min(1, -3, -5, 20, 3, 6) \ -/// #min("typst", "in", "beta") +/// #calc.min(1, -3, -5, 20, 3, 6) \ +/// #calc.min("typst", "in", "beta") /// ``` /// /// ## Parameters @@ -65,8 +564,8 @@ pub fn min(args: &mut Args) -> SourceResult { /// /// ## Example /// ``` -/// #max(1, -3, -5, 20, 3, 6) \ -/// #max("typst", "in", "beta") +/// #calc.max(1, -3, -5, 20, 3, 6) \ +/// #calc.max("typst", "in", "beta") /// ``` /// /// ## Parameters @@ -109,9 +608,9 @@ fn minmax(args: &mut Args, goal: Ordering) -> SourceResult { /// /// ## Example /// ``` -/// #even(4) \ -/// #even(5) \ -/// #range(10).filter(even) +/// #calc.even(4) \ +/// #calc.even(5) \ +/// #range(10).filter(calc.even) /// ``` /// /// ## Parameters @@ -132,9 +631,9 @@ pub fn even(args: &mut Args) -> SourceResult { /// /// ## Example /// ``` -/// #odd(4) \ -/// #odd(5) \ -/// #range(10).filter(odd) +/// #calc.odd(4) \ +/// #calc.odd(5) \ +/// #range(10).filter(calc.odd) /// ``` /// /// @@ -156,15 +655,15 @@ pub fn odd(args: &mut Args) -> SourceResult { /// /// ## Example /// ``` -/// #mod(20, 6) \ -/// #mod(1.75, 0.5) +/// #calc.mod(20, 6) \ +/// #calc.mod(1.75, 0.5) /// ``` /// /// ## Parameters -/// - dividend: ToMod (positional, required) +/// - dividend: Num (positional, required) /// The dividend of the modulus. /// -/// - divisor: ToMod (positional, required) +/// - divisor: Num (positional, required) /// The divisor of the modulus. /// /// - returns: integer or float @@ -200,10 +699,69 @@ pub fn mod_(args: &mut Args) -> SourceResult { } /// A value which can be passed to the `mod` function. -struct ToMod; +#[derive(Debug, Copy, Clone)] +enum Num { + Int(i64), + Float(f64), +} + +impl Num { + fn apply2( + self, + other: Self, + int: impl FnOnce(i64, i64) -> i64, + float: impl FnOnce(f64, f64) -> f64, + ) -> Value { + match (self, other) { + (Self::Int(a), Self::Int(b)) => Value::Int(int(a, b)), + (a, b) => Value::Float(float(a.float(), b.float())), + } + } + + fn apply3( + self, + other: Self, + third: Self, + int: impl FnOnce(i64, i64, i64) -> i64, + float: impl FnOnce(f64, f64, f64) -> f64, + ) -> Value { + match (self, other, third) { + (Self::Int(a), Self::Int(b), Self::Int(c)) => Value::Int(int(a, b, c)), + (a, b, c) => Value::Float(float(a.float(), b.float(), c.float())), + } + } + + fn float(self) -> f64 { + match self { + Self::Int(v) => v as f64, + Self::Float(v) => v, + } + } + + fn is_negative(self) -> bool { + match self { + Self::Int(v) => v < 0, + Self::Float(v) => v < 0.0, + } + } +} castable! { - ToMod, - _: i64 => Self, - _: f64 => Self, + Num, + v: i64 => Self::Int(v), + v: f64 => Self::Float(v), +} + +/// A value that can be passed to a trigonometric function. +enum AngleLike { + Int(i64), + Float(f64), + Angle(Angle), +} + +castable! { + AngleLike, + v: i64 => Self::Int(v), + v: f64 => Self::Float(v), + v: Angle => Self::Angle(v), } diff --git a/library/src/lib.rs b/library/src/lib.rs index 08ff171a9..a2f52549a 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -19,12 +19,13 @@ use self::layout::LayoutRoot; pub fn build() -> Library { let sym = text::sym(); let math = math::module(&sym); - let global = global(sym, math.clone()); + let calc = compute::calc(); + let global = global(sym, math.clone(), calc); Library { global, math, styles: styles(), items: items() } } /// Construct the module with global definitions. -fn global(sym: Module, math: Module) -> Module { +fn global(sym: Module, math: Module, calc: Module) -> Module { let mut global = Scope::deduplicating(); // Basics. @@ -106,12 +107,6 @@ fn global(sym: Module, math: Module) -> Module { global.def_func::("label"); global.def_func::("regex"); global.def_func::("range"); - global.def_func::("abs"); - global.def_func::("min"); - global.def_func::("max"); - global.def_func::("even"); - global.def_func::("odd"); - global.def_func::("mod"); global.def_func::("read"); global.def_func::("csv"); global.def_func::("json"); @@ -119,6 +114,9 @@ fn global(sym: Module, math: Module) -> Module { global.def_func::("lorem"); global.def_func::("numbering"); + // Calc. + global.define("calc", calc); + // Colors. global.define("black", Color::BLACK); global.define("gray", Color::GRAY); diff --git a/src/geom/angle.rs b/src/geom/angle.rs index 8e80d72be..c03810d9e 100644 --- a/src/geom/angle.rs +++ b/src/geom/angle.rs @@ -64,6 +64,11 @@ impl Angle { pub fn cos(self) -> f64 { self.to_rad().cos() } + + /// Get the tangent of this angle in radians. + pub fn tan(self) -> f64 { + self.to_rad().tan() + } } impl Numeric for Angle { diff --git a/tests/typ/basics/enum.typ b/tests/typ/basics/enum.typ index c1ce17b72..02eb03c25 100644 --- a/tests/typ/basics/enum.typ +++ b/tests/typ/basics/enum.typ @@ -43,7 +43,7 @@ spacing: 0.65em - 3pt, tight: false, numbering: n => text( - fill: (red, green, blue).at(mod(n, 3)), + fill: (red, green, blue).at(calc.mod(n, 3)), numbering("A", n), ), [Red], [Green], [Blue], diff --git a/tests/typ/basics/table.typ b/tests/typ/basics/table.typ index e806b1aa8..5d423e037 100644 --- a/tests/typ/basics/table.typ +++ b/tests/typ/basics/table.typ @@ -2,7 +2,7 @@ --- #set page(height: 70pt) -#set table(fill: (x, y) => if even(x + y) { rgb("aaa") }) +#set table(fill: (x, y) => if calc.even(x + y) { rgb("aaa") }) #table( columns: (1fr,) * 3, diff --git a/tests/typ/compiler/array.typ b/tests/typ/compiler/array.typ index 3d4d6106c..2fea86327 100644 --- a/tests/typ/compiler/array.typ +++ b/tests/typ/compiler/array.typ @@ -148,8 +148,8 @@ --- // Test the `filter` method. -#test(().filter(even), ()) -#test((1, 2, 3, 4).filter(even), (2, 4)) +#test(().filter(calc.even), ()) +#test((1, 2, 3, 4).filter(calc.even), (2, 4)) #test((7, 3, 2, 5, 1).filter(x => x < 5), (3, 2, 1)) --- diff --git a/tests/typ/compiler/break-continue.typ b/tests/typ/compiler/break-continue.typ index 566234a7a..e1041551f 100644 --- a/tests/typ/compiler/break-continue.typ +++ b/tests/typ/compiler/break-continue.typ @@ -41,7 +41,7 @@ #while x < 8 { i += 1 - if mod(i, 3) == 0 { + if calc.mod(i, 3) == 0 { continue } x += i @@ -55,7 +55,7 @@ #let x = for i in range(5) { "a" - if mod(i, 3) == 0 { + if calc.mod(i, 3) == 0 { "_" continue } diff --git a/tests/typ/compiler/spread.typ b/tests/typ/compiler/spread.typ index ce3d8cea3..e57247395 100644 --- a/tests/typ/compiler/spread.typ +++ b/tests/typ/compiler/spread.typ @@ -37,9 +37,9 @@ // Test spreading array and dictionary. #{ let more = (3, -3, 6, 10) - test(min(1, 2, ..more), -3) - test(max(..more, 9), 10) - test(max(..more, 11), 11) + test(calc.min(1, 2, ..more), -3) + test(calc.max(..more, 9), 10) + test(calc.max(..more, 11), 11) } #{ @@ -56,8 +56,8 @@ #f(..for x in () []) --- -// Error: 8-14 cannot spread string -#min(.."nope") +// Error: 13-19 cannot spread string +#calc.min(.."nope") --- // Error: 10-14 expected identifier, found boolean diff --git a/tests/typ/compiler/string.typ b/tests/typ/compiler/string.typ index 576990749..d96213b63 100644 --- a/tests/typ/compiler/string.typ +++ b/tests/typ/compiler/string.typ @@ -88,7 +88,7 @@ let caps = match.captures time += 60 * int(caps.at(0)) + int(caps.at(1)) } - str(int(time / 60)) + ":" + str(mod(time, 60)) + str(int(time / 60)) + ":" + str(calc.mod(time, 60)) } #test(timesum(""), "0:0") diff --git a/tests/typ/compute/calc.typ b/tests/typ/compute/calc.typ index be207a05d..195e3285d 100644 --- a/tests/typ/compute/calc.typ +++ b/tests/typ/compute/calc.typ @@ -30,55 +30,55 @@ --- // Test the `abs` function. -#test(abs(-3), 3) -#test(abs(3), 3) -#test(abs(-0.0), 0.0) -#test(abs(0.0), -0.0) -#test(abs(-3.14), 3.14) -#test(abs(50%), 50%) -#test(abs(-25%), 25%) +#test(calc.abs(-3), 3) +#test(calc.abs(3), 3) +#test(calc.abs(-0.0), 0.0) +#test(calc.abs(0.0), -0.0) +#test(calc.abs(-3.14), 3.14) +#test(calc.abs(50%), 50%) +#test(calc.abs(-25%), 25%) --- -// Error: 6-17 expected integer, float, length, angle, ratio, or fraction, found string -#abs("no number") +// Error: 11-22 expected integer, float, length, angle, ratio, or fraction, found string +#calc.abs("no number") --- // Test the `even` and `odd` functions. -#test(even(2), true) -#test(odd(2), false) -#test(odd(-1), true) -#test(even(-11), false) +#test(calc.even(2), true) +#test(calc.odd(2), false) +#test(calc.odd(-1), true) +#test(calc.even(-11), false) --- // Test the `mod` function. -#test(mod(1, 1), 0) -#test(mod(5, 3), 2) -#test(mod(5, -3), 2) -#test(mod(22.5, 10), 2.5) -#test(mod(9, 4.5), 0) +#test(calc.mod(1, 1), 0) +#test(calc.mod(5, 3), 2) +#test(calc.mod(5, -3), 2) +#test(calc.mod(22.5, 10), 2.5) +#test(calc.mod(9, 4.5), 0) --- -// Error: 9-10 divisor must not be zero -#mod(5, 0) +// Error: 14-15 divisor must not be zero +#calc.mod(5, 0) --- -// Error: 11-14 divisor must not be zero -#mod(3.0, 0.0) +// Error: 16-19 divisor must not be zero +#calc.mod(3.0, 0.0) --- // Test the `min` and `max` functions. -#test(min(2, -4), -4) -#test(min(3.5, 1e2, -0.1, 3), -0.1) -#test(max(-3, 11), 11) -#test(min("hi"), "hi") +#test(calc.min(2, -4), -4) +#test(calc.min(3.5, 1e2, -0.1, 3), -0.1) +#test(calc.max(-3, 11), 11) +#test(calc.min("hi"), "hi") --- -// Error: 5-7 missing argument: value -#min() +// Error: 10-12 missing argument: value +#calc.min() --- -// Error: 9-13 cannot compare integer and string -#min(1, "hi") +// Error: 14-18 cannot compare integer and string +#calc.min(1, "hi") --- // Test the `range` function. diff --git a/tests/typ/layout/par.typ b/tests/typ/layout/par.typ index 264209b87..45b60cf57 100644 --- a/tests/typ/layout/par.typ +++ b/tests/typ/layout/par.typ @@ -19,7 +19,7 @@ It is the east, and Juliet is the sun. #set block(spacing: 100pt) #show table: set block(above: 5pt, below: 5pt) Hello -#table(columns: 4, fill: (x, y) => if odd(x + y) { silver })[A][B][C][D] +#table(columns: 4, fill: (x, y) => if calc.odd(x + y) { silver })[A][B][C][D] --- // While we're at it, test the larger block spacing wins.