mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-30 02:48:11 +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:
Generated
-1
@@ -3399,7 +3399,6 @@ version = "0.0.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"chumsky",
|
"chumsky",
|
||||||
"criterion",
|
"criterion",
|
||||||
"lazy_static",
|
|
||||||
"num-complex",
|
"num-complex",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ license = "MIT OR Apache-2.0"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = "2.0"
|
thiserror = "2.0"
|
||||||
lazy_static = "1.5"
|
|
||||||
num-complex = "0.4"
|
num-complex = "0.4"
|
||||||
chumsky = { version = "0.10", default-features = false, features = ["std"] }
|
chumsky = { version = "0.10", default-features = false, features = ["std"] }
|
||||||
|
|
||||||
|
|||||||
@@ -1,637 +1,421 @@
|
|||||||
use crate::value::{Number, Value};
|
use crate::value::{Number, Value};
|
||||||
use lazy_static::lazy_static;
|
use num_complex::ComplexFloat;
|
||||||
use num_complex::{Complex, ComplexFloat};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::f64::consts::{LN_2, PI};
|
use std::f64::consts::{LN_2, PI};
|
||||||
|
|
||||||
type FunctionImplementation = Box<dyn Fn(&[Value]) -> Option<Value> + Send + Sync>;
|
pub type BuiltinFunction = fn(&[Value]) -> Option<Value>;
|
||||||
lazy_static! {
|
|
||||||
pub static ref DEFAULT_FUNCTIONS: HashMap<&'static str, FunctionImplementation> = {
|
|
||||||
let mut map: HashMap<&'static str, FunctionImplementation> = HashMap::new();
|
|
||||||
|
|
||||||
map.insert(
|
/// Truncates both operands to nonnegative integers for `gcd`/`lcm`, or `None` when either is non-finite or beyond f64's exactly-representable integer range.
|
||||||
"sqrt",
|
fn integer_operands(a: f64, b: f64) -> Option<(u64, u64)> {
|
||||||
Box::new(|values| match values{
|
// The largest magnitude below which every integer is exactly representable in f64
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.sqrt()))),
|
const EXACT_INTEGER_LIMIT: f64 = (1_u64 << f64::MANTISSA_DIGITS) as f64;
|
||||||
[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,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
let (a, b) = (a.trunc(), b.trunc());
|
||||||
"cos",
|
if !a.is_finite() || !b.is_finite() || a.abs() > EXACT_INTEGER_LIMIT || b.abs() > EXACT_INTEGER_LIMIT {
|
||||||
Box::new(|values| match values {
|
return None;
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.cos()))),
|
}
|
||||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.cos()))),
|
Some(((a as i64).unsigned_abs(), (b as i64).unsigned_abs()))
|
||||||
_ => None,
|
}
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
fn euclidean_gcd(mut x: u64, mut y: u64) -> u64 {
|
||||||
"tan",
|
while y != 0 {
|
||||||
Box::new(|values| match values {
|
(x, y) = (y, x % y);
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.tan()))),
|
}
|
||||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.tan()))),
|
x
|
||||||
_ => None,
|
}
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
/// Looks up a built-in math function by name, returning a plain function pointer so dispatch avoids hashing and dynamic allocation.
|
||||||
"csc",
|
pub fn builtin_function(name: &str) -> Option<BuiltinFunction> {
|
||||||
Box::new(|values| match values {
|
Some(match name {
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.sin().recip()))),
|
"sin" => |values| match values {
|
||||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.sin().recip()))),
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.sin()))),
|
||||||
_ => None,
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.sin()))),
|
||||||
}),
|
_ => None,
|
||||||
);
|
},
|
||||||
|
|
||||||
map.insert(
|
"cos" => |values| match values {
|
||||||
"sec",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.cos()))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.cos()))),
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.cos().recip()))),
|
_ => None,
|
||||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.cos().recip()))),
|
},
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
"tan" => |values| match values {
|
||||||
"cot",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.tan()))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.tan()))),
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.tan().recip()))),
|
_ => None,
|
||||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.tan().recip()))),
|
},
|
||||||
_ => 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
|
// Inverse trig with legacy names and standard aliases
|
||||||
map.insert(
|
"invsin" => |values| match values {
|
||||||
"invsin",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.asin()))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.asin()))),
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.asin()))),
|
_ => None,
|
||||||
[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()))),
|
||||||
map.insert(
|
_ => None,
|
||||||
"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,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
"invcos" => |values| match values {
|
||||||
"invcos",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.acos()))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.acos()))),
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.acos()))),
|
_ => None,
|
||||||
[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()))),
|
||||||
map.insert(
|
_ => None,
|
||||||
"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,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
"invtan" => |values| match values {
|
||||||
"invtan",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.atan()))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.atan()))),
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.atan()))),
|
_ => None,
|
||||||
[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()))),
|
||||||
map.insert(
|
_ => None,
|
||||||
"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,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
"invcsc" => |values| match values {
|
||||||
"invcsc",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().asin()))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().asin()))),
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().asin()))),
|
_ => None,
|
||||||
[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()))),
|
||||||
map.insert(
|
_ => None,
|
||||||
"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,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
"invsec" => |values| match values {
|
||||||
"invsec",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().acos()))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().acos()))),
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().acos()))),
|
_ => None,
|
||||||
[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()))),
|
||||||
map.insert(
|
_ => None,
|
||||||
"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,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
"invcot" => |values| match values {
|
||||||
"invcot",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().atan()))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().atan()))),
|
||||||
[Value::Number(Number::Real(real))] => {
|
_ => None,
|
||||||
Some(Value::Number(Number::Real(real.recip().atan())))
|
},
|
||||||
}
|
"acot" => |values| match values {
|
||||||
[Value::Number(Number::Complex(complex))] => {
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().atan()))),
|
||||||
Some(Value::Number(Number::Complex(complex.recip().atan())))
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().atan()))),
|
||||||
}
|
_ => None,
|
||||||
_ => 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,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
// Hyperbolic Functions
|
// Hyperbolic Functions
|
||||||
map.insert(
|
"sinh" => |values| match values {
|
||||||
"sinh",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.sinh()))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.sinh()))),
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.sinh()))),
|
_ => None,
|
||||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.sinh()))),
|
},
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
"cosh" => |values| match values {
|
||||||
"cosh",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.cosh()))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.cosh()))),
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.cosh()))),
|
_ => None,
|
||||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.cosh()))),
|
},
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
"tanh" => |values| match values {
|
||||||
"tanh",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.tanh()))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.tanh()))),
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.tanh()))),
|
_ => None,
|
||||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.tanh()))),
|
},
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Reciprocal hyperbolic functions
|
// Reciprocal hyperbolic functions
|
||||||
map.insert(
|
"csch" => |values| match values {
|
||||||
"csch",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.sinh().recip()))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.sinh().recip()))),
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.sinh().recip()))),
|
_ => None,
|
||||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.sinh().recip()))),
|
},
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
"sech" => |values| match values {
|
||||||
"sech",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.cosh().recip()))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.cosh().recip()))),
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.cosh().recip()))),
|
_ => None,
|
||||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.cosh().recip()))),
|
},
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
"coth" => |values| match values {
|
||||||
"coth",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.tanh().recip()))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.tanh().recip()))),
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.tanh().recip()))),
|
_ => None,
|
||||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.tanh().recip()))),
|
},
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Inverse Hyperbolic Functions
|
// Inverse Hyperbolic Functions
|
||||||
map.insert(
|
"asinh" => |values| match values {
|
||||||
"asinh",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.asinh()))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.asinh()))),
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.asinh()))),
|
_ => None,
|
||||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.asinh()))),
|
},
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
"acosh" => |values| match values {
|
||||||
"acosh",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.acosh()))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.acosh()))),
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.acosh()))),
|
_ => None,
|
||||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.acosh()))),
|
},
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
"atanh" => |values| match values {
|
||||||
"atanh",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.atanh()))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.atanh()))),
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.atanh()))),
|
_ => None,
|
||||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.atanh()))),
|
},
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Inverse reciprocal hyperbolic functions
|
// Inverse reciprocal hyperbolic functions
|
||||||
map.insert(
|
"acsch" => |values| match values {
|
||||||
"acsch",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().asinh()))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().asinh()))),
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().asinh()))),
|
_ => None,
|
||||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().asinh()))),
|
},
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
"asech" => |values| match values {
|
||||||
"asech",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().acosh()))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().acosh()))),
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().acosh()))),
|
_ => None,
|
||||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().acosh()))),
|
},
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
"acoth" => |values| match values {
|
||||||
"acoth",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().atanh()))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().atanh()))),
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().atanh()))),
|
_ => None,
|
||||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().atanh()))),
|
},
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Logarithm Functions
|
// Logarithm Functions
|
||||||
map.insert(
|
"ln" => |values| match values {
|
||||||
"ln",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.ln()))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.ln()))),
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.ln()))),
|
_ => None,
|
||||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.ln()))),
|
},
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Exponential / power helpers
|
// Exponential / power helpers
|
||||||
map.insert(
|
"exp" => |values| match values {
|
||||||
"exp",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.exp()))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.exp()))),
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.exp()))),
|
_ => None,
|
||||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.exp()))),
|
},
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
"pow" => |values| match values {
|
||||||
"pow",
|
[Value::Number(Number::Real(x)), Value::Number(Number::Real(n))] => Some(Value::Number(Number::Real(x.powf(*n)))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Complex(x)), Value::Number(Number::Real(n))] => Some(Value::Number(Number::Complex(x.powf(*n)))),
|
||||||
[Value::Number(Number::Real(x)), Value::Number(Number::Real(n))] => {
|
[Value::Number(Number::Complex(x)), Value::Number(Number::Complex(n))] => Some(Value::Number(Number::Complex(x.powc(*n)))),
|
||||||
Some(Value::Number(Number::Real(x.powf(*n))))
|
_ => None,
|
||||||
}
|
},
|
||||||
[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" => |values| match values {
|
||||||
"root",
|
[Value::Number(Number::Real(x)), Value::Number(Number::Real(n))] => {
|
||||||
Box::new(|values| match values {
|
// Odd integer roots of negative reals are real, which powf alone would report as NaN
|
||||||
[Value::Number(Number::Real(x)), Value::Number(Number::Real(n))] => {
|
let root = if *x < 0. && n.rem_euclid(2.) == 1. { -(-x).powf(1. / *n) } else { x.powf(1. / *n) };
|
||||||
Some(Value::Number(Number::Real(x.powf(1. / *n))))
|
Some(Value::Number(Number::Real(root)))
|
||||||
}
|
}
|
||||||
[Value::Number(Number::Complex(x)), Value::Number(Number::Real(n))] => {
|
[Value::Number(Number::Complex(x)), Value::Number(Number::Real(n))] => Some(Value::Number(Number::Complex(x.powf(1. / *n)))),
|
||||||
Some(Value::Number(Number::Complex(x.powf(1. / *n))))
|
_ => None,
|
||||||
}
|
},
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
"log" => |values| match values {
|
||||||
"log",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.log10()))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.log10()))),
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.log10()))),
|
[Value::Number(n), Value::Number(base)] => {
|
||||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.log10()))),
|
// Custom base logarithm using change of base formula
|
||||||
[Value::Number(n), Value::Number(base)] => {
|
let compute_log = |x: f64, b: f64| -> f64 { x.ln() / b.ln() };
|
||||||
// Custom base logarithm using change of base formula
|
match (n, base) {
|
||||||
let compute_log = |x: f64, b: f64| -> f64 { x.ln() / b.ln() };
|
(Number::Real(x), Number::Real(b)) => Some(Value::Number(Number::Real(compute_log(*x, *b)))),
|
||||||
match (n, base) {
|
_ => None,
|
||||||
(Number::Real(x), Number::Real(b)) => Some(Value::Number(Number::Real(compute_log(*x, *b)))),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => None,
|
}
|
||||||
}),
|
_ => None,
|
||||||
);
|
},
|
||||||
|
|
||||||
map.insert(
|
"log2" => |values| match values {
|
||||||
"log2",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.log2()))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.ln() / LN_2))),
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.log2()))),
|
_ => None,
|
||||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex / LN_2))),
|
},
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Root Functions
|
// Root Functions
|
||||||
map.insert(
|
"sqrt" => |values| match values {
|
||||||
"sqrt",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.sqrt()))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.sqrt()))),
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.sqrt()))),
|
_ => None,
|
||||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.sqrt()))),
|
},
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
"cbrt" => |values| match values {
|
||||||
"cbrt",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.cbrt()))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.powf(1. / 3.)))),
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.cbrt()))),
|
_ => None,
|
||||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.powf(1./3.)))),
|
},
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Geometry Functions
|
// Geometry Functions
|
||||||
map.insert(
|
"hypot" => |values| match values {
|
||||||
"hypot",
|
[Value::Number(Number::Real(a)), Value::Number(Number::Real(b))] => Some(Value::Number(Number::Real(a.hypot(*b)))),
|
||||||
Box::new(|values| match values {
|
_ => None,
|
||||||
[Value::Number(Number::Real(a)), Value::Number(Number::Real(b))] => {
|
},
|
||||||
Some(Value::Number(Number::Real(a.hypot(*b))))
|
|
||||||
},
|
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
"atan2" => |values| match values {
|
||||||
"atan2",
|
[Value::Number(Number::Real(y)), Value::Number(Number::Real(x))] => Some(Value::Number(Number::Real(y.atan2(*x)))),
|
||||||
Box::new(|values| match values {
|
_ => None,
|
||||||
[Value::Number(Number::Real(y)), Value::Number(Number::Real(x))] => {
|
},
|
||||||
Some(Value::Number(Number::Real(y.atan2(*x))))
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Mapping Functions
|
// Mapping Functions
|
||||||
map.insert(
|
"abs" => |values| match values {
|
||||||
"abs",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.abs()))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Real(complex.abs()))),
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.abs()))),
|
_ => None,
|
||||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Real(complex.abs()))),
|
},
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
"floor" => |values| match values {
|
||||||
"floor",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.floor()))),
|
||||||
Box::new(|values| match values {
|
_ => None,
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.floor()))),
|
},
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
"ceil" => |values| match values {
|
||||||
"ceil",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.ceil()))),
|
||||||
Box::new(|values| match values {
|
_ => None,
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.ceil()))),
|
},
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
"round" => |values| match values {
|
||||||
"round",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.round()))),
|
||||||
Box::new(|values| match values {
|
_ => None,
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.round()))),
|
},
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
"clamp" => |values| match values {
|
||||||
"clamp",
|
[Value::Number(Number::Real(x)), Value::Number(Number::Real(min)), Value::Number(Number::Real(max))] => Some(Value::Number(Number::Real(x.clamp(*min, *max)))),
|
||||||
Box::new(|values| match values {
|
_ => None,
|
||||||
[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" => |values| match values {
|
||||||
"lerp",
|
[Value::Number(Number::Real(a)), Value::Number(Number::Real(b)), Value::Number(Number::Real(t))] => Some(Value::Number(Number::Real(a + (b - a) * t))),
|
||||||
Box::new(|values| match values {
|
_ => None,
|
||||||
[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" => |values| match values {
|
||||||
"remap",
|
[
|
||||||
Box::new(|values| match values {
|
Value::Number(Number::Real(value)),
|
||||||
[
|
Value::Number(Number::Real(in_a)),
|
||||||
Value::Number(Number::Real(value)),
|
Value::Number(Number::Real(in_b)),
|
||||||
Value::Number(Number::Real(in_a)),
|
Value::Number(Number::Real(out_a)),
|
||||||
Value::Number(Number::Real(in_b)),
|
Value::Number(Number::Real(out_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))))
|
||||||
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(
|
// Multiply in f64 so huge results can't overflow the integer range
|
||||||
"trunc",
|
let lcm = (x / euclidean_gcd(x, y)) as f64 * y as f64;
|
||||||
Box::new(|values| match values {
|
Some(Value::Number(Number::Real(lcm)))
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.trunc()))),
|
}
|
||||||
_ => None,
|
_ => 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,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Complex Number Functions
|
// Complex Number Functions
|
||||||
map.insert(
|
"real" => |values| match values {
|
||||||
"real",
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Real(complex.re))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(*real))),
|
||||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Real(complex.re))),
|
_ => None,
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(*real))),
|
},
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
"imag" => |values| match values {
|
||||||
"imag",
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Real(complex.im))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Real(_))] => Some(Value::Number(Number::Real(0.))),
|
||||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Real(complex.im))),
|
_ => None,
|
||||||
[Value::Number(Number::Real(_))] => Some(Value::Number(Number::Real(0.))),
|
},
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
"conj" => |values| match values {
|
||||||
"conj",
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.conj()))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(*real))),
|
||||||
[Value::Number(Number::Complex(complex))] => {
|
_ => None,
|
||||||
Some(Value::Number(Number::Complex(complex.conj())))
|
},
|
||||||
}
|
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(*real))),
|
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
"arg" => |values| match values {
|
||||||
"arg",
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Real(complex.arg()))),
|
||||||
Box::new(|values| match values {
|
[Value::Number(Number::Real(real))] => {
|
||||||
[Value::Number(Number::Complex(complex))] => {
|
let angle = if *real >= 0. { 0. } else { PI };
|
||||||
Some(Value::Number(Number::Real(complex.arg())))
|
Some(Value::Number(Number::Real(angle)))
|
||||||
}
|
}
|
||||||
[Value::Number(Number::Real(real))] => {
|
_ => None,
|
||||||
let angle = if *real >= 0. { 0. } else { PI };
|
},
|
||||||
Some(Value::Number(Number::Real(angle)))
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Logical Functions
|
// Logical Functions
|
||||||
map.insert(
|
"isnan" => |values| match values {
|
||||||
"isnan",
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(if real.is_nan() { 1. } else { 0. }))),
|
||||||
Box::new(|values| match values {
|
_ => None,
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(if real.is_nan() { 1. } else { 0. }))),
|
},
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
"eq" => |values| match values {
|
||||||
"eq",
|
[Value::Number(a), Value::Number(b)] => Some(Value::Number(Number::Real(if a == b { 1. } else { 0. }))),
|
||||||
Box::new(|values| match values {
|
_ => None,
|
||||||
[Value::Number(a), Value::Number(b)] => Some(Value::Number(Number::Real(if a == b { 1. } else { 0. }))),
|
},
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map.insert(
|
"greater" => |values| match values {
|
||||||
"greater",
|
[Value::Number(Number::Real(a)), Value::Number(Number::Real(b))] => Some(Value::Number(Number::Real(if a > b { 1. } else { 0. }))),
|
||||||
Box::new(|values| match values {
|
_ => None,
|
||||||
[Value::Number(Number::Real(a)), Value::Number(Number::Real(b))] => {
|
},
|
||||||
Some(Value::Number(Number::Real(if a > b { 1. } else { 0. })))
|
_ => return None,
|
||||||
},
|
})
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
map
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
use crate::ast::{Literal, Node};
|
use crate::ast::{BinaryOp, Literal, Node};
|
||||||
use crate::constants::DEFAULT_FUNCTIONS;
|
use crate::constants::builtin_function;
|
||||||
use crate::context::{EvalContext, FunctionProvider, ValueProvider};
|
use crate::context::{EvalContext, FunctionProvider, ValueProvider};
|
||||||
use crate::value::{Number, Value};
|
use crate::value::{Number, Value};
|
||||||
use num_complex::Complex;
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
@@ -12,8 +11,12 @@ pub enum EvalError {
|
|||||||
|
|
||||||
#[error("Missing function: {0}")]
|
#[error("Missing function: {0}")]
|
||||||
MissingFunction(String),
|
MissingFunction(String),
|
||||||
#[error("Wrong type for function call")]
|
|
||||||
|
#[error("Wrong argument types for function call")]
|
||||||
TypeError,
|
TypeError,
|
||||||
|
|
||||||
|
#[error("Unsupported operand types for operator")]
|
||||||
|
OperatorTypeError,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Node {
|
impl Node {
|
||||||
@@ -25,28 +28,43 @@ impl Node {
|
|||||||
},
|
},
|
||||||
|
|
||||||
Node::BinOp { lhs, op, rhs } => match (lhs.eval(context)?, rhs.eval(context)?) {
|
Node::BinOp { lhs, op, rhs } => match (lhs.eval(context)?, rhs.eval(context)?) {
|
||||||
(Value::Number(lhs), Value::Number(rhs)) => Ok(Value::Number(lhs.binary_op(*op, rhs).ok_or(EvalError::TypeError)?)),
|
(Value::Number(lhs), Value::Number(rhs)) => Ok(Value::Number(lhs.binary_op(*op, rhs).ok_or(EvalError::OperatorTypeError)?)),
|
||||||
},
|
},
|
||||||
Node::UnaryOp { expr, op } => match expr.eval(context)? {
|
Node::UnaryOp { expr, op } => match expr.eval(context)? {
|
||||||
Value::Number(num) => Ok(Value::Number(num.unary_op(*op))),
|
Value::Number(num) => Ok(Value::Number(num.unary_op(*op))),
|
||||||
},
|
},
|
||||||
Node::Var(name) => context.get_value(name).ok_or_else(|| EvalError::MissingValue(name.clone())),
|
Node::Var(name) => context.get_value(name).ok_or_else(|| EvalError::MissingValue(name.clone())),
|
||||||
Node::FnCall { name, expr } => {
|
Node::FnCall { name, expr } => {
|
||||||
let values = expr.iter().map(|expr| expr.eval(context)).collect::<Result<Vec<Value>, EvalError>>()?;
|
// Arguments land in a stack buffer when they fit (builtins take at most 5), avoiding a heap allocation per call
|
||||||
|
let mut stack_values = [Value::from_f64(0.); 5];
|
||||||
if let Some(function) = DEFAULT_FUNCTIONS.get(&name.as_str()) {
|
let heap_values: Vec<Value>;
|
||||||
function(&values).ok_or(EvalError::TypeError)
|
let values: &[Value] = if expr.len() <= stack_values.len() {
|
||||||
} else if let Some(val) = context.run_function(name, &values) {
|
for (slot, argument) in stack_values.iter_mut().zip(expr) {
|
||||||
Ok(val)
|
*slot = argument.eval(context)?;
|
||||||
|
}
|
||||||
|
&stack_values[..expr.len()]
|
||||||
} else {
|
} else {
|
||||||
context.get_value(name).ok_or_else(|| EvalError::MissingFunction(name.to_string()))
|
heap_values = expr.iter().map(|argument| argument.eval(context)).collect::<Result<Vec<Value>, EvalError>>()?;
|
||||||
|
&heap_values
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(function) = builtin_function(name) {
|
||||||
|
function(values).ok_or(EvalError::TypeError)
|
||||||
|
} else if let Some(val) = context.run_function(name, values) {
|
||||||
|
Ok(val)
|
||||||
|
} else if let Some(Value::Number(value)) = context.get_value(name)
|
||||||
|
&& let [Value::Number(argument)] = values
|
||||||
|
{
|
||||||
|
// A known value applied to one argument is implicit multiplication, so `x(2)` matches `2(3)` and `i(16)`
|
||||||
|
Ok(Value::Number(value.binary_op(BinaryOp::Mul, *argument).ok_or(EvalError::OperatorTypeError)?))
|
||||||
|
} else {
|
||||||
|
Err(EvalError::MissingFunction(name.to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Node::Conditional { condition, if_block, else_block } => {
|
Node::Conditional { condition, if_block, else_block } => {
|
||||||
let condition = match condition.eval(context)? {
|
// A NaN condition yields NaN rather than arbitrarily picking a branch
|
||||||
Value::Number(Number::Real(number)) => number != 0.,
|
let Value::Number(number) = condition.eval(context)?;
|
||||||
Value::Number(Number::Complex(number)) => number != Complex::ZERO,
|
let Some(condition) = number.as_bool() else { return Ok(Value::from_f64(f64::NAN)) };
|
||||||
};
|
|
||||||
|
|
||||||
if condition { if_block.eval(context) } else { else_block.eval(context) }
|
if condition { if_block.eval(context) } else { else_block.eval(context) }
|
||||||
}
|
}
|
||||||
@@ -57,9 +75,37 @@ impl Node {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::ast::{BinaryOp, Literal, Node, UnaryOp};
|
use crate::ast::{BinaryOp, Literal, Node, UnaryOp};
|
||||||
use crate::context::{EvalContext, ValueMap};
|
use crate::context::{EvalContext, NothingMap, ValueProvider};
|
||||||
use crate::value::Value;
|
use crate::value::Value;
|
||||||
|
|
||||||
|
struct SingleValue(f64);
|
||||||
|
|
||||||
|
impl ValueProvider for SingleValue {
|
||||||
|
fn get_value(&self, name: &str) -> Option<Value> {
|
||||||
|
(name == "x").then(|| Value::from_f64(self.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn known_value_with_one_argument_multiplies() {
|
||||||
|
// `x(2)` juxtaposes like `2(3)` and `i(16)` instead of silently discarding the argument
|
||||||
|
let call = Node::FnCall {
|
||||||
|
name: "x".to_string(),
|
||||||
|
expr: vec![Node::Lit(Literal::Float(2.))],
|
||||||
|
};
|
||||||
|
let result = call.eval(&EvalContext::new(SingleValue(5.), NothingMap)).unwrap();
|
||||||
|
assert_eq!(result, Value::from_f64(10.));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn known_value_with_multiple_arguments_is_an_error() {
|
||||||
|
let call = Node::FnCall {
|
||||||
|
name: "x".to_string(),
|
||||||
|
expr: vec![Node::Lit(Literal::Float(1.)), Node::Lit(Literal::Float(2.))],
|
||||||
|
};
|
||||||
|
assert!(call.eval(&EvalContext::new(SingleValue(5.), NothingMap)).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! eval_tests {
|
macro_rules! eval_tests {
|
||||||
($($name:ident: $expected:expr_2021 => $expr:expr_2021),* $(,)?) => {
|
($($name:ident: $expected:expr_2021 => $expr:expr_2021),* $(,)?) => {
|
||||||
$(
|
$(
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
use crate::ast::Literal;
|
use crate::ast::Literal;
|
||||||
use chumsky::input::{Input, ValueInput};
|
use chumsky::input::{Input, ValueInput};
|
||||||
use chumsky::prelude::*;
|
|
||||||
use chumsky::span::SimpleSpan;
|
use chumsky::span::SimpleSpan;
|
||||||
use chumsky::text::{ident, int};
|
|
||||||
use core::f64;
|
|
||||||
use num_complex::Complex64;
|
use num_complex::Complex64;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::iter::Peekable;
|
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::str::Chars;
|
|
||||||
|
|
||||||
pub type Span = SimpleSpan;
|
pub type Span = SimpleSpan;
|
||||||
|
|
||||||
@@ -40,6 +35,9 @@ pub enum Token<'src> {
|
|||||||
EqEq,
|
EqEq,
|
||||||
|
|
||||||
If,
|
If,
|
||||||
|
|
||||||
|
/// An unrecognized character; the parser never matches this, forcing a parse error rather than silently truncating the input.
|
||||||
|
Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'src> fmt::Display for Token<'src> {
|
impl<'src> fmt::Display for Token<'src> {
|
||||||
@@ -71,6 +69,8 @@ impl<'src> fmt::Display for Token<'src> {
|
|||||||
Token::EqEq => f.write_str("=="),
|
Token::EqEq => f.write_str("=="),
|
||||||
|
|
||||||
Token::If => f.write_str("if"),
|
Token::If => f.write_str("if"),
|
||||||
|
|
||||||
|
Token::Error => f.write_str("<error>"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,57 +162,64 @@ impl<'a> Lexer<'a> {
|
|||||||
&self.input[start..self.pos]
|
&self.input[start..self.pos]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lex_ident(&mut self) -> &'a str {
|
fn consume_digits(&mut self) -> (usize, f64) {
|
||||||
self.consume_while(|c| c.is_alphanumeric() || c == '_')
|
let mut value = 0_f64;
|
||||||
}
|
|
||||||
|
|
||||||
fn lex_uint(&mut self) -> Option<(u64, usize)> {
|
|
||||||
let mut v = 0u64;
|
|
||||||
let mut digits = 0;
|
let mut digits = 0;
|
||||||
while let Some(d) = self.peek().and_then(|c| c.to_digit(10)) {
|
while let Some(d) = self.peek().and_then(|c| c.to_digit(10)) {
|
||||||
v = v * 10 + d as u64;
|
value = value * 10. + d as f64;
|
||||||
digits += 1;
|
digits += 1;
|
||||||
self.bump();
|
self.bump();
|
||||||
}
|
}
|
||||||
(digits > 0).then_some((v, digits))
|
(digits, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A numeric literal cannot follow another operand across whitespace (`10 000`, `sqrt(4).5`), only constants/calls/parens may juxtapose
|
||||||
|
fn juxtaposes_with_preceding_operand(&self, literal_start: usize) -> bool {
|
||||||
|
let mut preceding = self.input[..literal_start].trim_end();
|
||||||
|
|
||||||
|
// A `!` run is postfix factorial only when an operand precedes it, otherwise it's a prefix logical not
|
||||||
|
while let Some(rest) = preceding.strip_suffix('!') {
|
||||||
|
preceding = rest.trim_end();
|
||||||
|
}
|
||||||
|
|
||||||
|
preceding.chars().next_back().is_some_and(|c| c.is_alphanumeric() || c == '.' || c == ')' || c == '∞')
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lex_number(&mut self) -> Option<f64> {
|
fn lex_number(&mut self) -> Option<f64> {
|
||||||
let start_pos = self.pos;
|
let start_pos = self.pos;
|
||||||
let (int_val, int_digits) = self.lex_uint().unwrap_or((0, 0));
|
let (int_digits, int_value) = self.consume_digits();
|
||||||
let mut got_digit = int_digits > 0;
|
let mut got_digit = int_digits > 0;
|
||||||
let mut num = int_val as f64;
|
let mut plain_integer = true;
|
||||||
|
|
||||||
if self.peek() == Some('.') {
|
if self.peek() == Some('.') {
|
||||||
self.bump();
|
self.bump();
|
||||||
if let Some((frac_val, frac_digits)) = self.lex_uint() {
|
plain_integer = false;
|
||||||
num += (frac_val as f64) / 10f64.powi(frac_digits as i32);
|
got_digit |= self.consume_digits().0 > 0;
|
||||||
got_digit = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches!(self.peek(), Some('e' | 'E')) {
|
if got_digit && matches!(self.peek(), Some('e' | 'E')) {
|
||||||
self.bump();
|
self.bump();
|
||||||
let sign = match self.peek() {
|
plain_integer = false;
|
||||||
Some('+') => {
|
if matches!(self.peek(), Some('+' | '-')) {
|
||||||
self.bump();
|
self.bump();
|
||||||
1
|
}
|
||||||
}
|
if self.consume_digits().0 == 0 {
|
||||||
Some('-') => {
|
|
||||||
self.bump();
|
|
||||||
-1
|
|
||||||
}
|
|
||||||
_ => 1,
|
|
||||||
};
|
|
||||||
if let Some((exp_val, _)) = self.lex_uint() {
|
|
||||||
num *= 10f64.powi(sign * exp_val as i32);
|
|
||||||
} else {
|
|
||||||
self.pos = start_pos;
|
self.pos = start_pos;
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
got_digit.then_some(num)
|
// A numeric literal cannot be glued directly to another by a stray decimal point or digit (e.g. `1..5`, `1.5.5`), so reject rather than letting it parse as implicit multiplication
|
||||||
|
if !got_digit || self.peek().is_some_and(|c| c == '.' || c.is_ascii_digit()) || self.juxtaposes_with_preceding_operand(start_pos) {
|
||||||
|
self.pos = start_pos;
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accumulation is exact up to 15 digits; longer or fractional literals get std's correctly-rounded parsing
|
||||||
|
if plain_integer && int_digits <= 15 {
|
||||||
|
return Some(int_value);
|
||||||
|
}
|
||||||
|
self.input[start_pos..self.pos].parse::<f64>().ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn skip_ws(&mut self) {
|
fn skip_ws(&mut self) {
|
||||||
@@ -231,7 +238,7 @@ impl<'a> Lexer<'a> {
|
|||||||
self.bump();
|
self.bump();
|
||||||
AndAnd
|
AndAnd
|
||||||
} else {
|
} else {
|
||||||
return None;
|
Error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'|' => {
|
'|' => {
|
||||||
@@ -239,7 +246,7 @@ impl<'a> Lexer<'a> {
|
|||||||
self.bump();
|
self.bump();
|
||||||
OrOr
|
OrOr
|
||||||
} else {
|
} else {
|
||||||
return None;
|
Error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,13 +294,29 @@ impl<'a> Lexer<'a> {
|
|||||||
self.bump();
|
self.bump();
|
||||||
EqEq
|
EqEq
|
||||||
} else {
|
} else {
|
||||||
return None;
|
Error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c if c.is_ascii_digit() || (c == '.' && self.peek().is_some_and(|c| c.is_ascii_digit())) => {
|
c if c.is_ascii_digit() || (c == '.' && self.peek().is_some_and(|c| c.is_ascii_digit())) => {
|
||||||
self.pos = start;
|
self.pos = start;
|
||||||
Float(self.lex_number()?)
|
match self.lex_number() {
|
||||||
|
Some(number) => Float(number),
|
||||||
|
// Consume the whole malformed numeric run so the error span covers it and lexing makes forward progress
|
||||||
|
None => {
|
||||||
|
self.pos = start;
|
||||||
|
let mut prev = '\0';
|
||||||
|
while let Some(c) = self.peek() {
|
||||||
|
let part_of_number = c.is_ascii_digit() || c == '.' || c == 'e' || c == 'E' || ((c == '+' || c == '-') && matches!(prev, 'e' | 'E'));
|
||||||
|
if !part_of_number {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
prev = c;
|
||||||
|
self.bump();
|
||||||
|
}
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
@@ -307,7 +330,7 @@ impl<'a> Lexer<'a> {
|
|||||||
} else if ch.is_alphanumeric() {
|
} else if ch.is_alphanumeric() {
|
||||||
Ident(ident)
|
Ident(ident)
|
||||||
} else {
|
} else {
|
||||||
return None;
|
Error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#![allow(unused)]
|
|
||||||
|
|
||||||
pub mod ast;
|
pub mod ast;
|
||||||
mod constants;
|
mod constants;
|
||||||
pub mod context;
|
pub mod context;
|
||||||
@@ -8,8 +6,7 @@ pub mod lexer;
|
|||||||
pub mod parser;
|
pub mod parser;
|
||||||
pub mod value;
|
pub mod value;
|
||||||
|
|
||||||
use ast::Unit;
|
use context::EvalContext;
|
||||||
use context::{EvalContext, ValueMap};
|
|
||||||
use executer::EvalError;
|
use executer::EvalError;
|
||||||
use parser::ParseError;
|
use parser::ParseError;
|
||||||
use value::Value;
|
use value::Value;
|
||||||
@@ -23,25 +20,53 @@ pub fn evaluate(expression: &str) -> Result<Result<Value, EvalError>, ParseError
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use ast::Unit;
|
|
||||||
use value::Number;
|
use value::Number;
|
||||||
|
|
||||||
const EPSILON: f64 = 1e-10_f64;
|
const EPSILON: f64 = 1e-10_f64;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn malformed_juxtaposed_numbers_fail_to_parse() {
|
||||||
|
// Two numbers cannot be glued together by a stray decimal point (they must not parse as implicit multiplication)
|
||||||
|
for input in ["1..5", "1.5.5", "1..", ".5.5"] {
|
||||||
|
assert!(evaluate(input).is_err(), "expected `{input}` to be a parse error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unrecognized_characters_fail_to_parse() {
|
||||||
|
// Unrecognized trailing input must be rejected rather than silently dropped after a valid prefix
|
||||||
|
for input in ["2@", "5#", "2 $ 3", "sqrt(4)@", "5 & 3", "5 | 3", "2 = 3"] {
|
||||||
|
assert!(evaluate(input).is_err(), "expected `{input}` to be a parse error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn juxtaposed_numbers_fail_to_parse() {
|
||||||
|
// Adjacent number literals like digit-grouped `10 000` must not silently multiply
|
||||||
|
for input in ["2 3", "10 000", "1 .5", "sqrt(4).5", "2 3 + 1"] {
|
||||||
|
assert!(evaluate(input).is_err(), "expected `{input}` to be a parse error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extremely_long_fraction_parses() {
|
||||||
|
let input = format!("0.{}", "1".repeat(320));
|
||||||
|
let value = evaluate(&input).unwrap().unwrap();
|
||||||
|
assert_eq!(value.as_real(), Some(1. / 9.));
|
||||||
|
}
|
||||||
|
|
||||||
fn run_end_to_end_test(input: &str, expected_value: Value) {
|
fn run_end_to_end_test(input: &str, expected_value: Value) {
|
||||||
let expr = match ast::Node::try_parse_from_str(input) {
|
let expr = match ast::Node::try_parse_from_str(input) {
|
||||||
Ok(expr) => expr,
|
Ok(expr) => expr,
|
||||||
Err(err) => panic!("failed to parse `{input}`: {err}"),
|
Err(err) => panic!("failed to parse `{input}`: {err}"),
|
||||||
};
|
};
|
||||||
dbg!(&expr);
|
|
||||||
let context = EvalContext::default();
|
let context = EvalContext::default();
|
||||||
|
|
||||||
let actual_value = match expr.eval(&context) {
|
let actual_value = match expr.eval(&context) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(err) => panic!("failed to evaluate {input} becuase of error {err}"),
|
Err(err) => panic!("failed to evaluate `{input}` because of error {err}"),
|
||||||
};
|
};
|
||||||
|
|
||||||
// compare
|
|
||||||
match (actual_value, expected_value) {
|
match (actual_value, expected_value) {
|
||||||
(Value::Number(Number::Complex(a)), Value::Number(Number::Complex(e))) => {
|
(Value::Number(Number::Complex(a)), Value::Number(Number::Complex(e))) => {
|
||||||
// real part
|
// real part
|
||||||
@@ -128,6 +153,17 @@ mod tests {
|
|||||||
exponent_single: "2^3" => 8.,
|
exponent_single: "2^3" => 8.,
|
||||||
exponent_mixed_operations: "2^3 + 4^2" => 24.,
|
exponent_mixed_operations: "2^3 + 4^2" => 24.,
|
||||||
exponent_nested: "2^(3+1)" => 16.,
|
exponent_nested: "2^(3+1)" => 16.,
|
||||||
|
exponent_right_associative: "2^2^3" => 256.,
|
||||||
|
exponent_unary_operand: "2^-1" => 0.5,
|
||||||
|
|
||||||
|
// Implicit multiplication binds like `*`/`/`: tighter than `+`, looser than `^`, left to right
|
||||||
|
implicit_multiplication_constant: "2pi" => 2. * std::f64::consts::PI,
|
||||||
|
implicit_multiplication_before_addition: "2pi + 1" => 2. * std::f64::consts::PI + 1.,
|
||||||
|
implicit_multiplication_shares_division: "1/2pi" => std::f64::consts::PI / 2.,
|
||||||
|
implicit_multiplication_left_to_right: "6/2pi" => 3. * std::f64::consts::PI,
|
||||||
|
implicit_multiplication_power_operand: "2pi^2" => 2. * std::f64::consts::PI.powi(2),
|
||||||
|
implicit_multiplication_function: "2sqrt(4)" => 4.,
|
||||||
|
implicit_multiplication_excludes_unary_minus: "2 -3" => -1.,
|
||||||
|
|
||||||
// Factorial (postfix !)
|
// Factorial (postfix !)
|
||||||
factorial_simple: "5!" => 120.,
|
factorial_simple: "5!" => 120.,
|
||||||
@@ -275,5 +311,37 @@ mod tests {
|
|||||||
|
|
||||||
// Complex nested expressions
|
// Complex nested expressions
|
||||||
if_nested_expr: "if((sqrt(16) + 2) * (sin(pi) + 1), 3 + 4 * 2, 5 - 2 / 1)" => 11.,
|
if_nested_expr: "if((sqrt(16) + 2) * (sin(pi) + 1), 3 + 4 * 2, 5 - 2 / 1)" => 11.,
|
||||||
|
|
||||||
|
// Overflow-safe evaluation
|
||||||
|
factorial_overflows_to_infinity: "171!" => f64::INFINITY,
|
||||||
|
factorial_huge_input: "10000000000000000000000!" => f64::INFINITY,
|
||||||
|
lcm_huge_no_overflow: "lcm(1099511627776, 1099511627775)" => 1099511627776. * 1099511627775.,
|
||||||
|
gcd_non_finite: "gcd(inf, 6)" => f64::NAN,
|
||||||
|
long_literal: "10000000000000000000000" => 1e22,
|
||||||
|
huge_exponent_saturates: "1e4294967296" => f64::INFINITY,
|
||||||
|
|
||||||
|
// Odd integer roots of negative values are real
|
||||||
|
root_negative_odd: "root(-8, 3)" => -2.,
|
||||||
|
root_negative_odd_reciprocal: "root(-8, -3)" => -0.5,
|
||||||
|
root_negative_even: "root(-4, 2)" => f64::NAN,
|
||||||
|
|
||||||
|
// NaN poisons conditions and logic instead of acting as a boolean
|
||||||
|
if_nan_condition: "if(sqrt(-1), 1, 2)" => f64::NAN,
|
||||||
|
nan_and: "sqrt(-1) && 1" => f64::NAN,
|
||||||
|
nan_or: "sqrt(-1) || 1" => f64::NAN,
|
||||||
|
nan_not: "!sqrt(-1)" => f64::NAN,
|
||||||
|
|
||||||
|
// Logic and equality span real and complex operands
|
||||||
|
mixed_equality: "1 == i" => 0.,
|
||||||
|
complex_equality: "i == i" => 1.,
|
||||||
|
mixed_and: "1 && i" => 1.,
|
||||||
|
mixed_nan_and: "sqrt(-1) && i" => f64::NAN,
|
||||||
|
|
||||||
|
// Correctly rounded literals via std parsing
|
||||||
|
seventeen_digit_literal: "999999999999999999" => 1e18,
|
||||||
|
long_fraction_literal: "0.1111111111111111111111111111111111111111" => 1. / 9.,
|
||||||
|
|
||||||
|
// Integer functions reject inputs beyond f64's exact integer range
|
||||||
|
gcd_beyond_exact_integers: "gcd(10000000000000000000, 2)" => f64::NAN,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
use crate::ast::{BinaryOp, Literal, Node, UnaryOp, Unit};
|
use crate::ast::{BinaryOp, Literal, Node, UnaryOp};
|
||||||
use crate::context::EvalContext;
|
|
||||||
use crate::lexer::{Lexer, Span, Token};
|
use crate::lexer::{Lexer, Span, Token};
|
||||||
use crate::value::{Complex, Number, Value};
|
use chumsky::error::LabelError;
|
||||||
use chumsky::container::Seq;
|
use chumsky::input::ValueInput;
|
||||||
use chumsky::input::{BorrowInput, ValueInput};
|
|
||||||
use chumsky::{Parser, prelude::*};
|
use chumsky::{Parser, prelude::*};
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use num_complex::ComplexFloat;
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::num::{ParseFloatError, ParseIntError};
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
/// One message per parse failure, each tagged with its byte range in the source expression.
|
/// One message per parse failure, each tagged with its byte range in the source expression.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -31,18 +25,23 @@ impl std::error::Error for ParseError {}
|
|||||||
|
|
||||||
impl Node {
|
impl Node {
|
||||||
pub fn try_parse_from_str(src: &str) -> Result<Node, ParseError> {
|
pub fn try_parse_from_str(src: &str) -> Result<Node, ParseError> {
|
||||||
let tokens = Lexer::new(src);
|
// Parse with zero-cost errors first (several times faster), then re-parse invalid input with rich errors to build the messages
|
||||||
|
if let Ok(ast) = parser::<Lexer, extra::Default>().parse(Lexer::new(src)).into_result() {
|
||||||
|
return Ok(ast);
|
||||||
|
}
|
||||||
|
|
||||||
match parser().parse(tokens).into_result() {
|
match parser::<Lexer, extra::Err<Rich<Token, Span>>>().parse(Lexer::new(src)).into_result() {
|
||||||
Ok(ast) => Ok(ast),
|
Ok(ast) => Ok(ast),
|
||||||
Err(parse_errs) => Err(ParseError(parse_errs.into_iter().map(|e| format!("{e} at {}", e.span())).collect())),
|
Err(parse_errs) => Err(ParseError(parse_errs.into_iter().map(|e| format!("{e} at {}", e.span())).collect())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parser<'src, I>() -> impl Parser<'src, I, Node, extra::Err<Rich<'src, Token<'src>, Span>>>
|
pub fn parser<'src, I, E>() -> impl Parser<'src, I, Node, E>
|
||||||
where
|
where
|
||||||
I: ValueInput<'src, Token = Token<'src>, Span = Span>,
|
I: ValueInput<'src, Token = Token<'src>, Span = Span>,
|
||||||
|
E: extra::ParserExtra<'src, I>,
|
||||||
|
E::Error: LabelError<'src, I, &'static str>,
|
||||||
{
|
{
|
||||||
recursive(|expr| {
|
recursive(|expr| {
|
||||||
let constant = select! {
|
let constant = select! {
|
||||||
@@ -52,31 +51,27 @@ where
|
|||||||
|
|
||||||
let args = expr.clone().separated_by(just(Token::Comma)).collect::<Vec<_>>().delimited_by(just(Token::LParen), just(Token::RParen));
|
let args = expr.clone().separated_by(just(Token::Comma)).collect::<Vec<_>>().delimited_by(just(Token::LParen), just(Token::RParen));
|
||||||
|
|
||||||
let if_expr = just(Token::If)
|
let if_expr = just(Token::If).ignore_then(args.clone()).try_map(|args: Vec<Node>, span| {
|
||||||
.ignore_then(args.clone()) // Parses (cond, a, b)
|
let [condition, if_block, else_block] = <[Node; 3]>::try_from(args).map_err(|_| LabelError::<I, _>::expected_found(["3 arguments in if(condition, a, b)"], None, span))?;
|
||||||
.try_map(|args: Vec<Node>, span| {
|
|
||||||
if args.len() != 3 {
|
Ok(Node::Conditional {
|
||||||
return Err(Rich::custom(span, "Expected 3 arguments in if(cond, a, b)"));
|
condition: Box::new(condition),
|
||||||
}
|
if_block: Box::new(if_block),
|
||||||
let mut iter = args.into_iter();
|
else_block: Box::new(else_block),
|
||||||
let cond = iter.next().unwrap();
|
})
|
||||||
let if_b = iter.next().unwrap();
|
});
|
||||||
let else_b = iter.next().unwrap();
|
|
||||||
Ok(Node::Conditional {
|
|
||||||
condition: Box::new(cond),
|
|
||||||
if_block: Box::new(if_b),
|
|
||||||
else_block: Box::new(else_b),
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
let ident = select! {Token::Ident(s) => s}.labelled("ident");
|
let ident = select! {Token::Ident(s) => s}.labelled("ident");
|
||||||
|
|
||||||
let call = ident.then(args).map(|(name, args): (&str, Vec<Node>)| Node::FnCall { name: name.to_string(), expr: args });
|
// An ident followed by parenthesized args is a function call, otherwise a variable
|
||||||
|
let call_or_var = ident.then(args.or_not()).map(|(name, args): (&str, Option<Vec<Node>>)| match args {
|
||||||
|
Some(args) => Node::FnCall { name: name.to_string(), expr: args },
|
||||||
|
None => Node::Var(name.to_string()),
|
||||||
|
});
|
||||||
|
|
||||||
let parens = expr.clone().delimited_by(just(Token::LParen), just(Token::RParen));
|
let parens = expr.clone().delimited_by(just(Token::LParen), just(Token::RParen));
|
||||||
let var = ident.map(|s| Node::Var(s.to_string()));
|
|
||||||
|
|
||||||
let atom = choice((constant, if_expr, call, parens, var)).labelled("atom").boxed();
|
let atom = choice((constant, if_expr, call_or_var, parens)).labelled("atom");
|
||||||
|
|
||||||
let add_op = choice((just(Token::Plus).to(BinaryOp::Add), just(Token::Minus).to(BinaryOp::Sub)));
|
let add_op = choice((just(Token::Plus).to(BinaryOp::Add), just(Token::Minus).to(BinaryOp::Sub)));
|
||||||
let mul_op = choice((just(Token::Star).to(BinaryOp::Mul), just(Token::Slash).to(BinaryOp::Div), just(Token::Modulo).to(BinaryOp::Modulo)));
|
let mul_op = choice((just(Token::Star).to(BinaryOp::Mul), just(Token::Slash).to(BinaryOp::Div), just(Token::Modulo).to(BinaryOp::Modulo)));
|
||||||
@@ -94,35 +89,34 @@ where
|
|||||||
));
|
));
|
||||||
|
|
||||||
// Postfix factorial: expr! → UnaryOp::Fac
|
// Postfix factorial: expr! → UnaryOp::Fac
|
||||||
let postfix = atom
|
let postfix = atom.clone().foldl(just(Token::Bang).repeated(), |expr, _| Node::UnaryOp {
|
||||||
.clone()
|
op: UnaryOp::Fac,
|
||||||
.foldl(just(Token::Bang).repeated(), |expr, _| Node::UnaryOp {
|
expr: Box::new(expr),
|
||||||
op: UnaryOp::Fac,
|
});
|
||||||
expr: Box::new(expr),
|
|
||||||
|
// Exponentiation is right-associative (`2^2^3` is `2^(2^3)`) and the exponent may carry unary signs like `2^-3`
|
||||||
|
let pow = recursive(|pow| {
|
||||||
|
let exponent = unary_op.clone().repeated().foldr(pow, |op, expr| Node::UnaryOp { op, expr: Box::new(expr) });
|
||||||
|
postfix.clone().then(pow_op.ignore_then(exponent).or_not()).map(|(base, exponent)| match exponent {
|
||||||
|
Some(exponent) => Node::BinOp {
|
||||||
|
lhs: Box::new(base),
|
||||||
|
op: BinaryOp::Pow,
|
||||||
|
rhs: Box::new(exponent),
|
||||||
|
},
|
||||||
|
None => base,
|
||||||
})
|
})
|
||||||
.boxed();
|
});
|
||||||
|
|
||||||
let pow = postfix.clone().foldl(
|
let unary = unary_op.clone().repeated().foldr(pow.clone(), |op, expr| Node::UnaryOp { op, expr: Box::new(expr) });
|
||||||
pow_op
|
|
||||||
.then(unary_op.clone().repeated().foldr(postfix, |op, expr| Node::UnaryOp { op, expr: Box::new(expr) }).boxed())
|
|
||||||
.repeated(),
|
|
||||||
|lhs, (op, rhs)| Node::BinOp {
|
|
||||||
lhs: Box::new(lhs),
|
|
||||||
op,
|
|
||||||
rhs: Box::new(rhs),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let unary = unary_op.repeated().foldr(pow, |op, expr| Node::UnaryOp { op, expr: Box::new(expr) }).boxed();
|
// Juxtaposed factors like `2pi` or `2sqrt(4)` multiply implicitly at the same precedence as `*` and `/`.
|
||||||
|
// The implicit operand is a `pow`, not a full unary, so `2 -3` stays a subtraction; the lexer rejects a bare number as the right operand (`10 000` is not `10*000`).
|
||||||
let product = unary
|
let implicit_mul = pow.map(|rhs| (BinaryOp::Mul, rhs));
|
||||||
.clone()
|
let product = unary.clone().foldl(choice((mul_op.then(unary), implicit_mul)).repeated(), |lhs, (op, rhs)| Node::BinOp {
|
||||||
.foldl(mul_op.then(unary).repeated(), |lhs, (op, rhs)| Node::BinOp {
|
lhs: Box::new(lhs),
|
||||||
lhs: Box::new(lhs),
|
op,
|
||||||
op,
|
rhs: Box::new(rhs),
|
||||||
rhs: Box::new(rhs),
|
});
|
||||||
})
|
|
||||||
.boxed();
|
|
||||||
|
|
||||||
let add = product.clone().foldl(add_op.then(product).repeated(), |lhs, (op, rhs)| Node::BinOp {
|
let add = product.clone().foldl(add_op.then(product).repeated(), |lhs, (op, rhs)| Node::BinOp {
|
||||||
lhs: Box::new(lhs),
|
lhs: Box::new(lhs),
|
||||||
@@ -136,15 +130,7 @@ where
|
|||||||
rhs: Box::new(rhs),
|
rhs: Box::new(rhs),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Chain comparisons like `a < b < c` by multiplying the boolean
|
let and = cmp.clone().foldl(and_op.then(cmp).repeated(), |lhs, (op, rhs)| Node::BinOp {
|
||||||
// (1. / 0.) results, preserving the existing semantics.
|
|
||||||
let chained_cmp = cmp.clone().foldl(cmp.repeated(), |lhs, rhs| Node::BinOp {
|
|
||||||
lhs: Box::new(lhs),
|
|
||||||
op: BinaryOp::Mul,
|
|
||||||
rhs: Box::new(rhs),
|
|
||||||
});
|
|
||||||
|
|
||||||
let and = chained_cmp.clone().foldl(and_op.then(chained_cmp).repeated(), |lhs, (op, rhs)| Node::BinOp {
|
|
||||||
lhs: Box::new(lhs),
|
lhs: Box::new(lhs),
|
||||||
op,
|
op,
|
||||||
rhs: Box::new(rhs),
|
rhs: Box::new(rhs),
|
||||||
@@ -161,6 +147,8 @@ where
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::value::Complex;
|
||||||
|
|
||||||
macro_rules! test_parser {
|
macro_rules! test_parser {
|
||||||
($($name:ident: $input:expr_2021 => $expected:expr_2021),* $(,)?) => {
|
($($name:ident: $input:expr_2021 => $expected:expr_2021),* $(,)?) => {
|
||||||
$(
|
$(
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
use crate::ast::{BinaryOp, UnaryOp};
|
use crate::ast::{BinaryOp, UnaryOp};
|
||||||
use num_complex::ComplexFloat;
|
|
||||||
use std::f64::consts::PI;
|
|
||||||
|
|
||||||
pub type Complex = num_complex::Complex<f64>;
|
pub type Complex = num_complex::Complex<f64>;
|
||||||
|
|
||||||
@@ -52,20 +50,38 @@ impl std::fmt::Display for Number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Number {
|
impl Number {
|
||||||
|
/// The value's truthiness for conditions and logic operators, or `None` for NaN values, which poison the result rather than acting as a boolean.
|
||||||
|
pub fn as_bool(self) -> Option<bool> {
|
||||||
|
match self {
|
||||||
|
Number::Real(real) => (!real.is_nan()).then_some(real != 0.),
|
||||||
|
Number::Complex(complex) => (!complex.re.is_nan() && !complex.im.is_nan()).then_some(complex != Complex::ZERO),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn binary_op(self, op: BinaryOp, other: Number) -> Option<Number> {
|
pub fn binary_op(self, op: BinaryOp, other: Number) -> Option<Number> {
|
||||||
|
// Logic and equality work uniformly across real and complex operands
|
||||||
|
match op {
|
||||||
|
BinaryOp::And | BinaryOp::Or => {
|
||||||
|
let (Some(lhs), Some(rhs)) = (self.as_bool(), other.as_bool()) else {
|
||||||
|
return Some(Number::Real(f64::NAN));
|
||||||
|
};
|
||||||
|
let result = if matches!(op, BinaryOp::And) { lhs && rhs } else { lhs || rhs };
|
||||||
|
return Some(Number::Real(result as u8 as f64));
|
||||||
|
}
|
||||||
|
BinaryOp::Eq | BinaryOp::Neq => {
|
||||||
|
let equal = match (self, other) {
|
||||||
|
(Number::Real(lhs), Number::Real(rhs)) => lhs == rhs,
|
||||||
|
(Number::Complex(lhs), Number::Complex(rhs)) => lhs == rhs,
|
||||||
|
(Number::Real(real), Number::Complex(complex)) | (Number::Complex(complex), Number::Real(real)) => complex == Complex::new(real, 0.),
|
||||||
|
};
|
||||||
|
return Some(Number::Real((equal != matches!(op, BinaryOp::Neq)) as u8 as f64));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(Number::Real(lhs), Number::Real(rhs)) => {
|
(Number::Real(lhs), Number::Real(rhs)) => {
|
||||||
let result = match op {
|
let result = match op {
|
||||||
BinaryOp::And => {
|
|
||||||
let l = lhs != 0.;
|
|
||||||
let r = rhs != 0.;
|
|
||||||
if l && r { 1. } else { 0. }
|
|
||||||
}
|
|
||||||
BinaryOp::Or => {
|
|
||||||
let l = lhs != 0.;
|
|
||||||
let r = rhs != 0.;
|
|
||||||
if l || r { 1. } else { 0. }
|
|
||||||
}
|
|
||||||
BinaryOp::Add => lhs + rhs,
|
BinaryOp::Add => lhs + rhs,
|
||||||
BinaryOp::Sub => lhs - rhs,
|
BinaryOp::Sub => lhs - rhs,
|
||||||
BinaryOp::Mul => lhs * rhs,
|
BinaryOp::Mul => lhs * rhs,
|
||||||
@@ -76,8 +92,7 @@ impl Number {
|
|||||||
BinaryOp::Lt => (lhs < rhs) as u8 as f64,
|
BinaryOp::Lt => (lhs < rhs) as u8 as f64,
|
||||||
BinaryOp::Geq => (lhs >= rhs) as u8 as f64,
|
BinaryOp::Geq => (lhs >= rhs) as u8 as f64,
|
||||||
BinaryOp::Gt => (lhs > rhs) as u8 as f64,
|
BinaryOp::Gt => (lhs > rhs) as u8 as f64,
|
||||||
BinaryOp::Neq => (lhs != rhs) as u8 as f64,
|
BinaryOp::And | BinaryOp::Or | BinaryOp::Eq | BinaryOp::Neq => unreachable!("handled above"),
|
||||||
BinaryOp::Eq => (lhs == rhs) as u8 as f64,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(Number::Real(result))
|
Some(Number::Real(result))
|
||||||
@@ -85,16 +100,6 @@ impl Number {
|
|||||||
|
|
||||||
(Number::Complex(lhs), Number::Complex(rhs)) => {
|
(Number::Complex(lhs), Number::Complex(rhs)) => {
|
||||||
let result = match op {
|
let result = match op {
|
||||||
BinaryOp::And => {
|
|
||||||
let l = lhs != Complex::new(0., 0.);
|
|
||||||
let r = rhs != Complex::new(0., 0.);
|
|
||||||
return Some(Number::Real(if l && r { 1. } else { 0. }));
|
|
||||||
}
|
|
||||||
BinaryOp::Or => {
|
|
||||||
let l = lhs != Complex::new(0., 0.);
|
|
||||||
let r = rhs != Complex::new(0., 0.);
|
|
||||||
return Some(Number::Real(if l || r { 1. } else { 0. }));
|
|
||||||
}
|
|
||||||
BinaryOp::Add => lhs + rhs,
|
BinaryOp::Add => lhs + rhs,
|
||||||
BinaryOp::Sub => lhs - rhs,
|
BinaryOp::Sub => lhs - rhs,
|
||||||
BinaryOp::Mul => lhs * rhs,
|
BinaryOp::Mul => lhs * rhs,
|
||||||
@@ -104,20 +109,7 @@ impl Number {
|
|||||||
BinaryOp::Leq | BinaryOp::Lt | BinaryOp::Geq | BinaryOp::Gt => {
|
BinaryOp::Leq | BinaryOp::Lt | BinaryOp::Geq | BinaryOp::Gt => {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
BinaryOp::Neq => {
|
BinaryOp::And | BinaryOp::Or | BinaryOp::Eq | BinaryOp::Neq => unreachable!("handled above"),
|
||||||
if lhs != rhs {
|
|
||||||
return Some(Number::Real(1.));
|
|
||||||
} else {
|
|
||||||
return Some(Number::Real(0.));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BinaryOp::Eq => {
|
|
||||||
if lhs == rhs {
|
|
||||||
return Some(Number::Real(1.));
|
|
||||||
} else {
|
|
||||||
return Some(Number::Real(0.));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
Some(Number::Complex(result))
|
Some(Number::Complex(result))
|
||||||
}
|
}
|
||||||
@@ -151,6 +143,13 @@ impl Number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn unary_op(self, op: UnaryOp) -> Number {
|
pub fn unary_op(self, op: UnaryOp) -> Number {
|
||||||
|
if matches!(op, UnaryOp::Not) {
|
||||||
|
return match self.as_bool() {
|
||||||
|
Some(boolean) => Number::Real(!boolean as u8 as f64),
|
||||||
|
None => Number::Real(f64::NAN),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Number::Real(real) => match op {
|
Number::Real(real) => match op {
|
||||||
UnaryOp::Neg => Number::Real(-real),
|
UnaryOp::Neg => Number::Real(-real),
|
||||||
@@ -165,27 +164,26 @@ impl Number {
|
|||||||
if truncated < 0. || (real - truncated).abs() > f64::EPSILON {
|
if truncated < 0. || (real - truncated).abs() > f64::EPSILON {
|
||||||
return Number::Real(f64::NAN);
|
return Number::Real(f64::NAN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return infinity above 170! since that overflows f64, which also keeps huge inputs from spinning the loop
|
||||||
let n = truncated as u64;
|
let n = truncated as u64;
|
||||||
|
if n > 170 {
|
||||||
|
return Number::Real(f64::INFINITY);
|
||||||
|
}
|
||||||
let mut acc = 1_f64;
|
let mut acc = 1_f64;
|
||||||
for k in 1..=n {
|
for k in 1..=n {
|
||||||
acc *= k as f64;
|
acc *= k as f64;
|
||||||
}
|
}
|
||||||
Number::Real(acc)
|
Number::Real(acc)
|
||||||
}
|
}
|
||||||
UnaryOp::Not => {
|
UnaryOp::Not => unreachable!("handled above"),
|
||||||
let is_zero = real == 0.;
|
|
||||||
Number::Real(if is_zero { 1. } else { 0. })
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
Number::Complex(complex) => match op {
|
Number::Complex(complex) => match op {
|
||||||
UnaryOp::Neg => Number::Complex(-complex),
|
UnaryOp::Neg => Number::Complex(-complex),
|
||||||
UnaryOp::Sqrt => Number::Complex(complex.sqrt()),
|
UnaryOp::Sqrt => Number::Complex(complex.sqrt()),
|
||||||
UnaryOp::Fac => Number::Complex(Complex::new(f64::NAN, f64::NAN)),
|
UnaryOp::Fac => Number::Complex(Complex::new(f64::NAN, f64::NAN)),
|
||||||
UnaryOp::Not => {
|
UnaryOp::Not => unreachable!("handled above"),
|
||||||
let is_zero = complex == Complex::new(0., 0.);
|
|
||||||
Number::Real(if is_zero { 1. } else { 0. })
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user