use crate::error::StdlibError;
use crate::module::StdlibModule;
use crate::value::Value;
pub struct MathModule;
impl MathModule {
pub fn new() -> Self {
Self
}
}
impl Default for MathModule {
fn default() -> Self {
Self::new()
}
}
impl StdlibModule for MathModule {
fn name(&self) -> &'static str {
"math"
}
fn has_function(&self, function: &str) -> bool {
matches!(
function,
"abs"
| "min"
| "max"
| "floor"
| "ceil"
| "round"
| "round_to"
| "pow"
| "clamp"
| "sqrt"
| "PI"
| "E"
)
}
fn call(&self, function: &str, args: Vec<Value>) -> Result<Value, StdlibError> {
match function {
"abs" => self.abs(args),
"min" => self.min(args),
"max" => self.max(args),
"floor" => self.floor(args),
"ceil" => self.ceil(args),
"round" => self.round(args),
"round_to" => self.round_to(args),
"pow" => self.pow(args),
"clamp" => self.clamp(args),
"sqrt" => self.sqrt(args),
"PI" => self.pi(args),
"E" => self.e(args),
_ => Err(StdlibError::unknown_function("math", function)),
}
}
}
fn expect_one_number(fn_name: &str, args: &[Value]) -> Result<f64, StdlibError> {
if args.len() != 1 {
return Err(StdlibError::wrong_args(fn_name, 1, args.len()));
}
match &args[0] {
Value::Number(n) => Ok(*n),
other => Err(StdlibError::type_mismatch(
fn_name,
1,
"number",
other.type_name(),
)),
}
}
fn expect_two_numbers(fn_name: &str, args: &[Value]) -> Result<(f64, f64), StdlibError> {
if args.len() != 2 {
return Err(StdlibError::wrong_args(fn_name, 2, args.len()));
}
let a = match &args[0] {
Value::Number(n) => *n,
other => {
return Err(StdlibError::type_mismatch(
fn_name,
1,
"number",
other.type_name(),
));
}
};
let b = match &args[1] {
Value::Number(n) => *n,
other => {
return Err(StdlibError::type_mismatch(
fn_name,
2,
"number",
other.type_name(),
));
}
};
Ok((a, b))
}
fn nan_guard(fn_name: &str, result: f64) -> Result<Value, StdlibError> {
if result.is_nan() {
Err(StdlibError::RuntimeError(format!(
"{fn_name}: operation would produce NaN"
)))
} else if result.is_infinite() {
Err(StdlibError::RuntimeError(format!(
"{fn_name}: operation would produce infinity"
)))
} else {
Ok(Value::Number(result))
}
}
impl MathModule {
fn abs(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
let a = expect_one_number("math.abs", &args)?;
Ok(Value::Number(a.abs()))
}
fn min(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
let (a, b) = expect_two_numbers("math.min", &args)?;
Ok(Value::Number(a.min(b)))
}
fn max(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
let (a, b) = expect_two_numbers("math.max", &args)?;
Ok(Value::Number(a.max(b)))
}
fn floor(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
let a = expect_one_number("math.floor", &args)?;
Ok(Value::Number(a.floor()))
}
fn ceil(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
let a = expect_one_number("math.ceil", &args)?;
Ok(Value::Number(a.ceil()))
}
fn round(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
let a = expect_one_number("math.round", &args)?;
Ok(Value::Number((a + 0.5).floor()))
}
fn round_to(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
let (a, decimals) = expect_two_numbers("math.round_to", &args)?;
if decimals < 0.0 || decimals.fract() != 0.0 {
return Err(StdlibError::RuntimeError(
"math.round_to: decimals must be a non-negative integer".to_string(),
));
}
let factor = 10_f64.powi(decimals as i32);
let scaled = a * factor;
let rounded = (scaled + 0.5).floor();
let result = rounded / factor;
nan_guard("math.round_to", result)
}
fn pow(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
let (base, exp) = expect_two_numbers("math.pow", &args)?;
let result = base.powf(exp);
nan_guard("math.pow", result)
}
fn clamp(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
if args.len() != 3 {
return Err(StdlibError::wrong_args("math.clamp", 3, args.len()));
}
let value = match &args[0] {
Value::Number(n) => *n,
other => {
return Err(StdlibError::type_mismatch(
"math.clamp",
1,
"number",
other.type_name(),
));
}
};
let min = match &args[1] {
Value::Number(n) => *n,
other => {
return Err(StdlibError::type_mismatch(
"math.clamp",
2,
"number",
other.type_name(),
));
}
};
let max = match &args[2] {
Value::Number(n) => *n,
other => {
return Err(StdlibError::type_mismatch(
"math.clamp",
3,
"number",
other.type_name(),
));
}
};
if min > max {
return Err(StdlibError::RuntimeError(
"math.clamp: min must be <= max".to_string(),
));
}
Ok(Value::Number(value.clamp(min, max)))
}
fn sqrt(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
let a = expect_one_number("math.sqrt", &args)?;
if a < 0.0 {
return Err(StdlibError::RuntimeError(
"math.sqrt: cannot take square root of negative number".to_string(),
));
}
Ok(Value::Number(a.sqrt()))
}
fn pi(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
if !args.is_empty() {
return Err(StdlibError::wrong_args("math.PI", 0, args.len()));
}
Ok(Value::Number(std::f64::consts::PI))
}
fn e(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
if !args.is_empty() {
return Err(StdlibError::wrong_args("math.E", 0, args.len()));
}
Ok(Value::Number(std::f64::consts::E))
}
}