mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-21 00:38:12 +08:00
Fix the math parser's implicit multiplication precedence and other regressions from the rewrite (#4383)
* Fix parsing regressions, make parsing 2.5x faster than the old pest parser, and clean up the math-parser rewrite * Fix review findings: whitespace-juxtaposed numbers, mixed real/complex logic, correctly rounded literals, unified NaN truthiness, and gcd/lcm range checks
This commit is contained in:
@@ -1,637 +1,421 @@
|
||||
use crate::value::{Number, Value};
|
||||
use lazy_static::lazy_static;
|
||||
use num_complex::{Complex, ComplexFloat};
|
||||
use std::collections::HashMap;
|
||||
use num_complex::ComplexFloat;
|
||||
use std::f64::consts::{LN_2, PI};
|
||||
|
||||
type FunctionImplementation = Box<dyn Fn(&[Value]) -> Option<Value> + Send + Sync>;
|
||||
lazy_static! {
|
||||
pub static ref DEFAULT_FUNCTIONS: HashMap<&'static str, FunctionImplementation> = {
|
||||
let mut map: HashMap<&'static str, FunctionImplementation> = HashMap::new();
|
||||
pub type BuiltinFunction = fn(&[Value]) -> Option<Value>;
|
||||
|
||||
map.insert(
|
||||
"sqrt",
|
||||
Box::new(|values| match values{
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.sqrt()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.sqrt()))),
|
||||
_ => None,
|
||||
})
|
||||
);
|
||||
map.insert(
|
||||
"sin",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.sin()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.sin()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
/// Truncates both operands to nonnegative integers for `gcd`/`lcm`, or `None` when either is non-finite or beyond f64's exactly-representable integer range.
|
||||
fn integer_operands(a: f64, b: f64) -> Option<(u64, u64)> {
|
||||
// The largest magnitude below which every integer is exactly representable in f64
|
||||
const EXACT_INTEGER_LIMIT: f64 = (1_u64 << f64::MANTISSA_DIGITS) as f64;
|
||||
|
||||
map.insert(
|
||||
"cos",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.cos()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.cos()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
let (a, b) = (a.trunc(), b.trunc());
|
||||
if !a.is_finite() || !b.is_finite() || a.abs() > EXACT_INTEGER_LIMIT || b.abs() > EXACT_INTEGER_LIMIT {
|
||||
return None;
|
||||
}
|
||||
Some(((a as i64).unsigned_abs(), (b as i64).unsigned_abs()))
|
||||
}
|
||||
|
||||
map.insert(
|
||||
"tan",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.tan()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.tan()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
fn euclidean_gcd(mut x: u64, mut y: u64) -> u64 {
|
||||
while y != 0 {
|
||||
(x, y) = (y, x % y);
|
||||
}
|
||||
x
|
||||
}
|
||||
|
||||
map.insert(
|
||||
"csc",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.sin().recip()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.sin().recip()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
/// Looks up a built-in math function by name, returning a plain function pointer so dispatch avoids hashing and dynamic allocation.
|
||||
pub fn builtin_function(name: &str) -> Option<BuiltinFunction> {
|
||||
Some(match name {
|
||||
"sin" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.sin()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.sin()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"sec",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.cos().recip()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.cos().recip()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"cos" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.cos()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.cos()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"cot",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.tan().recip()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.tan().recip()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"tan" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.tan()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.tan()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
"csc" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.sin().recip()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.sin().recip()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
"sec" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.cos().recip()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.cos().recip()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
"cot" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.tan().recip()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.tan().recip()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
// Inverse trig with legacy names and standard aliases
|
||||
map.insert(
|
||||
"invsin",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.asin()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.asin()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
"asin",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.asin()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.asin()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"invsin" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.asin()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.asin()))),
|
||||
_ => None,
|
||||
},
|
||||
"asin" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.asin()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.asin()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"invcos",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.acos()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.acos()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
"acos",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.acos()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.acos()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"invcos" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.acos()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.acos()))),
|
||||
_ => None,
|
||||
},
|
||||
"acos" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.acos()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.acos()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"invtan",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.atan()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.atan()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
"atan",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.atan()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.atan()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"invtan" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.atan()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.atan()))),
|
||||
_ => None,
|
||||
},
|
||||
"atan" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.atan()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.atan()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"invcsc",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().asin()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().asin()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
"acsc",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().asin()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().asin()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"invcsc" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().asin()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().asin()))),
|
||||
_ => None,
|
||||
},
|
||||
"acsc" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().asin()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().asin()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"invsec",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().acos()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().acos()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
"asec",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().acos()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().acos()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"invsec" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().acos()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().acos()))),
|
||||
_ => None,
|
||||
},
|
||||
"asec" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().acos()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().acos()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"invcot",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => {
|
||||
Some(Value::Number(Number::Real(real.recip().atan())))
|
||||
}
|
||||
[Value::Number(Number::Complex(complex))] => {
|
||||
Some(Value::Number(Number::Complex(complex.recip().atan())))
|
||||
}
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
"acot",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => {
|
||||
Some(Value::Number(Number::Real(real.recip().atan())))
|
||||
}
|
||||
[Value::Number(Number::Complex(complex))] => {
|
||||
Some(Value::Number(Number::Complex(complex.recip().atan())))
|
||||
}
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"invcot" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().atan()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().atan()))),
|
||||
_ => None,
|
||||
},
|
||||
"acot" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().atan()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().atan()))),
|
||||
_ => None,
|
||||
},
|
||||
// Hyperbolic Functions
|
||||
map.insert(
|
||||
"sinh",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.sinh()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.sinh()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"sinh" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.sinh()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.sinh()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"cosh",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.cosh()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.cosh()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"cosh" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.cosh()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.cosh()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"tanh",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.tanh()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.tanh()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"tanh" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.tanh()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.tanh()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
// Reciprocal hyperbolic functions
|
||||
map.insert(
|
||||
"csch",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.sinh().recip()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.sinh().recip()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"csch" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.sinh().recip()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.sinh().recip()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"sech",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.cosh().recip()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.cosh().recip()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"sech" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.cosh().recip()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.cosh().recip()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"coth",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.tanh().recip()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.tanh().recip()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"coth" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.tanh().recip()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.tanh().recip()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
// Inverse Hyperbolic Functions
|
||||
map.insert(
|
||||
"asinh",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.asinh()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.asinh()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"asinh" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.asinh()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.asinh()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"acosh",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.acosh()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.acosh()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"acosh" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.acosh()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.acosh()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"atanh",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.atanh()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.atanh()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"atanh" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.atanh()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.atanh()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
// Inverse reciprocal hyperbolic functions
|
||||
map.insert(
|
||||
"acsch",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().asinh()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().asinh()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"acsch" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().asinh()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().asinh()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"asech",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().acosh()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().acosh()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"asech" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().acosh()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().acosh()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"acoth",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().atanh()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().atanh()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"acoth" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().atanh()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().atanh()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
// Logarithm Functions
|
||||
map.insert(
|
||||
"ln",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.ln()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.ln()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"ln" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.ln()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.ln()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
// Exponential / power helpers
|
||||
map.insert(
|
||||
"exp",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.exp()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.exp()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"exp" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.exp()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.exp()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"pow",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(x)), Value::Number(Number::Real(n))] => {
|
||||
Some(Value::Number(Number::Real(x.powf(*n))))
|
||||
}
|
||||
[Value::Number(Number::Complex(x)), Value::Number(Number::Real(n))] => {
|
||||
Some(Value::Number(Number::Complex(x.powf(*n))))
|
||||
}
|
||||
[Value::Number(Number::Complex(x)), Value::Number(Number::Complex(n))] => {
|
||||
Some(Value::Number(Number::Complex(x.powc(*n))))
|
||||
}
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"pow" => |values| match values {
|
||||
[Value::Number(Number::Real(x)), Value::Number(Number::Real(n))] => Some(Value::Number(Number::Real(x.powf(*n)))),
|
||||
[Value::Number(Number::Complex(x)), Value::Number(Number::Real(n))] => Some(Value::Number(Number::Complex(x.powf(*n)))),
|
||||
[Value::Number(Number::Complex(x)), Value::Number(Number::Complex(n))] => Some(Value::Number(Number::Complex(x.powc(*n)))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"root",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(x)), Value::Number(Number::Real(n))] => {
|
||||
Some(Value::Number(Number::Real(x.powf(1. / *n))))
|
||||
}
|
||||
[Value::Number(Number::Complex(x)), Value::Number(Number::Real(n))] => {
|
||||
Some(Value::Number(Number::Complex(x.powf(1. / *n))))
|
||||
}
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"root" => |values| match values {
|
||||
[Value::Number(Number::Real(x)), Value::Number(Number::Real(n))] => {
|
||||
// Odd integer roots of negative reals are real, which powf alone would report as NaN
|
||||
let root = if *x < 0. && n.rem_euclid(2.) == 1. { -(-x).powf(1. / *n) } else { x.powf(1. / *n) };
|
||||
Some(Value::Number(Number::Real(root)))
|
||||
}
|
||||
[Value::Number(Number::Complex(x)), Value::Number(Number::Real(n))] => Some(Value::Number(Number::Complex(x.powf(1. / *n)))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"log",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.log10()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.log10()))),
|
||||
[Value::Number(n), Value::Number(base)] => {
|
||||
// Custom base logarithm using change of base formula
|
||||
let compute_log = |x: f64, b: f64| -> f64 { x.ln() / b.ln() };
|
||||
match (n, base) {
|
||||
(Number::Real(x), Number::Real(b)) => Some(Value::Number(Number::Real(compute_log(*x, *b)))),
|
||||
_ => None,
|
||||
}
|
||||
"log" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.log10()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.log10()))),
|
||||
[Value::Number(n), Value::Number(base)] => {
|
||||
// Custom base logarithm using change of base formula
|
||||
let compute_log = |x: f64, b: f64| -> f64 { x.ln() / b.ln() };
|
||||
match (n, base) {
|
||||
(Number::Real(x), Number::Real(b)) => Some(Value::Number(Number::Real(compute_log(*x, *b)))),
|
||||
_ => None,
|
||||
}
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"log2",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.log2()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex / LN_2))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"log2" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.log2()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.ln() / LN_2))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
// Root Functions
|
||||
map.insert(
|
||||
"sqrt",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.sqrt()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.sqrt()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"sqrt" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.sqrt()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.sqrt()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"cbrt",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.cbrt()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.powf(1./3.)))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"cbrt" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.cbrt()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.powf(1. / 3.)))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
// Geometry Functions
|
||||
map.insert(
|
||||
"hypot",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(a)), Value::Number(Number::Real(b))] => {
|
||||
Some(Value::Number(Number::Real(a.hypot(*b))))
|
||||
},
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"hypot" => |values| match values {
|
||||
[Value::Number(Number::Real(a)), Value::Number(Number::Real(b))] => Some(Value::Number(Number::Real(a.hypot(*b)))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"atan2",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(y)), Value::Number(Number::Real(x))] => {
|
||||
Some(Value::Number(Number::Real(y.atan2(*x))))
|
||||
}
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"atan2" => |values| match values {
|
||||
[Value::Number(Number::Real(y)), Value::Number(Number::Real(x))] => Some(Value::Number(Number::Real(y.atan2(*x)))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
// Mapping Functions
|
||||
map.insert(
|
||||
"abs",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.abs()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Real(complex.abs()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"abs" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.abs()))),
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Real(complex.abs()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"floor",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.floor()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"floor" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.floor()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"ceil",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.ceil()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"ceil" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.ceil()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"round",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.round()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"round" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.round()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"clamp",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(x)), Value::Number(Number::Real(min)), Value::Number(Number::Real(max))] => {
|
||||
Some(Value::Number(Number::Real(x.clamp(*min, *max))))
|
||||
},
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"clamp" => |values| match values {
|
||||
[Value::Number(Number::Real(x)), Value::Number(Number::Real(min)), Value::Number(Number::Real(max))] => Some(Value::Number(Number::Real(x.clamp(*min, *max)))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"lerp",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(a)), Value::Number(Number::Real(b)), Value::Number(Number::Real(t))] => {
|
||||
Some(Value::Number(Number::Real(a + (b - a) * t)))
|
||||
},
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"lerp" => |values| match values {
|
||||
[Value::Number(Number::Real(a)), Value::Number(Number::Real(b)), Value::Number(Number::Real(t))] => Some(Value::Number(Number::Real(a + (b - a) * t))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"remap",
|
||||
Box::new(|values| match values {
|
||||
[
|
||||
Value::Number(Number::Real(value)),
|
||||
Value::Number(Number::Real(in_a)),
|
||||
Value::Number(Number::Real(in_b)),
|
||||
Value::Number(Number::Real(out_a)),
|
||||
Value::Number(Number::Real(out_b)),
|
||||
] => {
|
||||
let t = (*value - *in_a) / (*in_b - *in_a);
|
||||
Some(Value::Number(Number::Real(out_a + t * (out_b - out_a))))
|
||||
"remap" => |values| match values {
|
||||
[
|
||||
Value::Number(Number::Real(value)),
|
||||
Value::Number(Number::Real(in_a)),
|
||||
Value::Number(Number::Real(in_b)),
|
||||
Value::Number(Number::Real(out_a)),
|
||||
Value::Number(Number::Real(out_b)),
|
||||
] => {
|
||||
let t = (*value - *in_a) / (*in_b - *in_a);
|
||||
Some(Value::Number(Number::Real(out_a + t * (out_b - out_a))))
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
|
||||
"trunc" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.trunc()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
"fract" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.fract()))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
"sign" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => {
|
||||
let s = if *real > 0. {
|
||||
1.
|
||||
} else if *real < 0. {
|
||||
-1.
|
||||
} else {
|
||||
0.
|
||||
};
|
||||
Some(Value::Number(Number::Real(s)))
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
|
||||
"gcd" => |values| match values {
|
||||
[Value::Number(Number::Real(a)), Value::Number(Number::Real(b))] => {
|
||||
let gcd = integer_operands(*a, *b).map_or(f64::NAN, |(x, y)| euclidean_gcd(x, y) as f64);
|
||||
Some(Value::Number(Number::Real(gcd)))
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
|
||||
"lcm" => |values| match values {
|
||||
[Value::Number(Number::Real(a)), Value::Number(Number::Real(b))] => {
|
||||
let Some((x, y)) = integer_operands(*a, *b) else {
|
||||
return Some(Value::Number(Number::Real(f64::NAN)));
|
||||
};
|
||||
if x == 0 || y == 0 {
|
||||
return Some(Value::Number(Number::Real(0.)));
|
||||
}
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
|
||||
map.insert(
|
||||
"trunc",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.trunc()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
|
||||
map.insert(
|
||||
"fract",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.fract()))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
|
||||
map.insert(
|
||||
"sign",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => {
|
||||
let s = if *real > 0. {
|
||||
1.
|
||||
} else if *real < 0. {
|
||||
-1.
|
||||
} else {
|
||||
0.
|
||||
};
|
||||
Some(Value::Number(Number::Real(s)))
|
||||
}
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
|
||||
map.insert(
|
||||
"gcd",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(a)), Value::Number(Number::Real(b))] => {
|
||||
let mut x = a.trunc() as i64;
|
||||
let mut y = b.trunc() as i64;
|
||||
if x == 0 && y == 0 {
|
||||
return Some(Value::Number(Number::Real(0.)));
|
||||
}
|
||||
x = x.abs();
|
||||
y = y.abs();
|
||||
while y != 0 {
|
||||
let r = x % y;
|
||||
x = y;
|
||||
y = r;
|
||||
}
|
||||
Some(Value::Number(Number::Real(x as f64)))
|
||||
}
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
|
||||
map.insert(
|
||||
"lcm",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(a)), Value::Number(Number::Real(b))] => {
|
||||
let mut x = a.trunc() as i64;
|
||||
let mut y = b.trunc() as i64;
|
||||
x = x.abs();
|
||||
y = y.abs();
|
||||
if x == 0 || y == 0 {
|
||||
return Some(Value::Number(Number::Real(0.)));
|
||||
}
|
||||
|
||||
// gcd
|
||||
let mut gx = x;
|
||||
let mut gy = y;
|
||||
while gy != 0 {
|
||||
let r = gx % gy;
|
||||
gx = gy;
|
||||
gy = r;
|
||||
}
|
||||
let lcm = (x / gx) * y;
|
||||
Some(Value::Number(Number::Real(lcm as f64)))
|
||||
}
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
// Multiply in f64 so huge results can't overflow the integer range
|
||||
let lcm = (x / euclidean_gcd(x, y)) as f64 * y as f64;
|
||||
Some(Value::Number(Number::Real(lcm)))
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
|
||||
// Complex Number Functions
|
||||
map.insert(
|
||||
"real",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Real(complex.re))),
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(*real))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"real" => |values| match values {
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Real(complex.re))),
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(*real))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"imag",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Real(complex.im))),
|
||||
[Value::Number(Number::Real(_))] => Some(Value::Number(Number::Real(0.))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"imag" => |values| match values {
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Real(complex.im))),
|
||||
[Value::Number(Number::Real(_))] => Some(Value::Number(Number::Real(0.))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"conj",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Complex(complex))] => {
|
||||
Some(Value::Number(Number::Complex(complex.conj())))
|
||||
}
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(*real))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"conj" => |values| match values {
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.conj()))),
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(*real))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"arg",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Complex(complex))] => {
|
||||
Some(Value::Number(Number::Real(complex.arg())))
|
||||
}
|
||||
[Value::Number(Number::Real(real))] => {
|
||||
let angle = if *real >= 0. { 0. } else { PI };
|
||||
Some(Value::Number(Number::Real(angle)))
|
||||
}
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"arg" => |values| match values {
|
||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Real(complex.arg()))),
|
||||
[Value::Number(Number::Real(real))] => {
|
||||
let angle = if *real >= 0. { 0. } else { PI };
|
||||
Some(Value::Number(Number::Real(angle)))
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
|
||||
// Logical Functions
|
||||
map.insert(
|
||||
"isnan",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(if real.is_nan() { 1. } else { 0. }))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"isnan" => |values| match values {
|
||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(if real.is_nan() { 1. } else { 0. }))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"eq",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(a), Value::Number(b)] => Some(Value::Number(Number::Real(if a == b { 1. } else { 0. }))),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
"eq" => |values| match values {
|
||||
[Value::Number(a), Value::Number(b)] => Some(Value::Number(Number::Real(if a == b { 1. } else { 0. }))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
map.insert(
|
||||
"greater",
|
||||
Box::new(|values| match values {
|
||||
[Value::Number(Number::Real(a)), Value::Number(Number::Real(b))] => {
|
||||
Some(Value::Number(Number::Real(if a > b { 1. } else { 0. })))
|
||||
},
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
|
||||
map
|
||||
};
|
||||
"greater" => |values| match values {
|
||||
[Value::Number(Number::Real(a)), Value::Number(Number::Real(b))] => Some(Value::Number(Number::Real(if a > b { 1. } else { 0. }))),
|
||||
_ => None,
|
||||
},
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user