diff --git a/src/library/math.rs b/src/library/math.rs new file mode 100644 index 00000000..3392f664 --- /dev/null +++ b/src/library/math.rs @@ -0,0 +1,60 @@ +use std::cmp::Ordering; + +use super::*; + +/// `min`: The minimum of two values. +/// +/// # Positional parameters +/// - Values: variadic, must be comparable. +/// +/// # Return value +/// The minimum of the sequence of values. For equal elements, the first one is +/// returned. +pub fn min(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { + minmax(ctx, args, Ordering::Less) +} + +/// `max`: The maximum of two values. +/// +/// # Positional parameters +/// - Values: variadic, must be comparable. +/// +/// # Return value +/// The maximum of the sequence of values. For equal elements, the first one is +/// returned. +pub fn max(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { + minmax(ctx, args, Ordering::Greater) +} + +/// Find the minimum or maximum of a sequence of values. +fn minmax(ctx: &mut EvalContext, args: &mut FuncArgs, which: Ordering) -> Value { + let mut values = args.filter::(ctx); + let mut extremum = None; + + for value in &mut values { + if let Some(prev) = &extremum { + match value.cmp(&prev) { + Some(ord) if ord == which => extremum = Some(value), + Some(_) => {} + None => { + drop(values); + ctx.diag(error!( + args.span, + "cannot compare {} with {}", + prev.type_name(), + value.type_name(), + )); + return Value::Error; + } + } + } else { + extremum = Some(value); + } + } + + drop(values); + extremum.unwrap_or_else(|| { + args.require::(ctx, "value"); + Value::Error + }) +} diff --git a/src/library/mod.rs b/src/library/mod.rs index 5018f0b4..2cfe02ba 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -9,6 +9,7 @@ mod font; mod image; mod lang; mod markup; +mod math; mod pad; mod page; mod par; @@ -21,6 +22,7 @@ pub use basic::*; pub use font::*; pub use lang::*; pub use markup::*; +pub use math::*; pub use pad::*; pub use page::*; pub use par::*; @@ -69,6 +71,8 @@ pub fn _new() -> Scope { func!("h", h); func!("image", image); func!("lang", lang); + func!("max", max); + func!("min", min); func!("pad", pad); func!("page", page); func!("pagebreak", pagebreak); diff --git a/tests/typ/library/math.typ b/tests/typ/library/math.typ new file mode 100644 index 00000000..db234d9c --- /dev/null +++ b/tests/typ/library/math.typ @@ -0,0 +1,15 @@ +// Test math functions. +// Ref: false + +--- +// Test `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") + +// Error: 6 missing argument: value +#min() + +// Error: 11-18 cannot compare integer with string +#test(min(1, "hi"), error)