fix(calc): fix log/ln naming, cache math context, strengthen sin(pi) test
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use evalexpr::eval_number_with_context;
|
use evalexpr::eval_number_with_context;
|
||||||
use k_launcher_kernel::{LaunchAction, Plugin, ResultId, ResultTitle, Score, SearchResult};
|
use k_launcher_kernel::{LaunchAction, Plugin, ResultId, ResultTitle, Score, SearchResult};
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
pub struct CalcPlugin;
|
pub struct CalcPlugin;
|
||||||
|
|
||||||
@@ -22,7 +23,7 @@ fn strip_numeric_separators(expr: &str) -> String {
|
|||||||
|
|
||||||
const MATH_FNS: &[&str] = &[
|
const MATH_FNS: &[&str] = &[
|
||||||
"sqrt", "sin", "cos", "tan", "asin", "acos", "atan",
|
"sqrt", "sin", "cos", "tan", "asin", "acos", "atan",
|
||||||
"log", "log2", "log10", "exp", "abs", "ceil", "floor", "round",
|
"ln", "log", "log2", "log10", "exp", "abs", "ceil", "floor", "round",
|
||||||
];
|
];
|
||||||
|
|
||||||
fn should_eval(query: &str) -> bool {
|
fn should_eval(query: &str) -> bool {
|
||||||
@@ -35,9 +36,10 @@ fn should_eval(query: &str) -> bool {
|
|||||||
|| MATH_FNS.iter().any(|f| q.starts_with(f))
|
|| MATH_FNS.iter().any(|f| q.starts_with(f))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn math_context() -> evalexpr::HashMapContext<evalexpr::DefaultNumericTypes> {
|
static MATH_CTX: LazyLock<evalexpr::HashMapContext<evalexpr::DefaultNumericTypes>> =
|
||||||
|
LazyLock::new(|| {
|
||||||
use evalexpr::*;
|
use evalexpr::*;
|
||||||
let ctx: HashMapContext<DefaultNumericTypes> = context_map! {
|
context_map! {
|
||||||
"pi" => float std::f64::consts::PI,
|
"pi" => float std::f64::consts::PI,
|
||||||
"e" => float std::f64::consts::E,
|
"e" => float std::f64::consts::E,
|
||||||
"sqrt" => Function::new(|a: &Value<DefaultNumericTypes>| Ok(Value::from_float(a.as_number()?.sqrt()))),
|
"sqrt" => Function::new(|a: &Value<DefaultNumericTypes>| Ok(Value::from_float(a.as_number()?.sqrt()))),
|
||||||
@@ -47,7 +49,8 @@ fn math_context() -> evalexpr::HashMapContext<evalexpr::DefaultNumericTypes> {
|
|||||||
"asin" => Function::new(|a: &Value<DefaultNumericTypes>| Ok(Value::from_float(a.as_number()?.asin()))),
|
"asin" => Function::new(|a: &Value<DefaultNumericTypes>| Ok(Value::from_float(a.as_number()?.asin()))),
|
||||||
"acos" => Function::new(|a: &Value<DefaultNumericTypes>| Ok(Value::from_float(a.as_number()?.acos()))),
|
"acos" => Function::new(|a: &Value<DefaultNumericTypes>| Ok(Value::from_float(a.as_number()?.acos()))),
|
||||||
"atan" => Function::new(|a: &Value<DefaultNumericTypes>| Ok(Value::from_float(a.as_number()?.atan()))),
|
"atan" => Function::new(|a: &Value<DefaultNumericTypes>| Ok(Value::from_float(a.as_number()?.atan()))),
|
||||||
"log" => Function::new(|a: &Value<DefaultNumericTypes>| Ok(Value::from_float(a.as_number()?.ln()))),
|
"ln" => Function::new(|a: &Value<DefaultNumericTypes>| Ok(Value::from_float(a.as_number()?.ln()))),
|
||||||
|
"log" => Function::new(|a: &Value<DefaultNumericTypes>| Ok(Value::from_float(a.as_number()?.log10()))),
|
||||||
"log2" => Function::new(|a: &Value<DefaultNumericTypes>| Ok(Value::from_float(a.as_number()?.log2()))),
|
"log2" => Function::new(|a: &Value<DefaultNumericTypes>| Ok(Value::from_float(a.as_number()?.log2()))),
|
||||||
"log10" => Function::new(|a: &Value<DefaultNumericTypes>| Ok(Value::from_float(a.as_number()?.log10()))),
|
"log10" => Function::new(|a: &Value<DefaultNumericTypes>| Ok(Value::from_float(a.as_number()?.log10()))),
|
||||||
"exp" => Function::new(|a: &Value<DefaultNumericTypes>| Ok(Value::from_float(a.as_number()?.exp()))),
|
"exp" => Function::new(|a: &Value<DefaultNumericTypes>| Ok(Value::from_float(a.as_number()?.exp()))),
|
||||||
@@ -56,9 +59,8 @@ fn math_context() -> evalexpr::HashMapContext<evalexpr::DefaultNumericTypes> {
|
|||||||
"floor" => Function::new(|a: &Value<DefaultNumericTypes>| Ok(Value::from_float(a.as_number()?.floor()))),
|
"floor" => Function::new(|a: &Value<DefaultNumericTypes>| Ok(Value::from_float(a.as_number()?.floor()))),
|
||||||
"round" => Function::new(|a: &Value<DefaultNumericTypes>| Ok(Value::from_float(a.as_number()?.round())))
|
"round" => Function::new(|a: &Value<DefaultNumericTypes>| Ok(Value::from_float(a.as_number()?.round())))
|
||||||
}
|
}
|
||||||
.unwrap();
|
.expect("static math context must be valid")
|
||||||
ctx
|
});
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Plugin for CalcPlugin {
|
impl Plugin for CalcPlugin {
|
||||||
@@ -73,7 +75,7 @@ impl Plugin for CalcPlugin {
|
|||||||
let raw = query.strip_prefix('=').unwrap_or(query);
|
let raw = query.strip_prefix('=').unwrap_or(query);
|
||||||
let expr_owned = strip_numeric_separators(raw);
|
let expr_owned = strip_numeric_separators(raw);
|
||||||
let expr = expr_owned.as_str();
|
let expr = expr_owned.as_str();
|
||||||
match eval_number_with_context(expr, &math_context()) {
|
match eval_number_with_context(expr, &*MATH_CTX) {
|
||||||
Ok(n) if n.is_finite() => {
|
Ok(n) if n.is_finite() => {
|
||||||
let value_str = if n.fract() == 0.0 {
|
let value_str = if n.fract() == 0.0 {
|
||||||
format!("{}", n as i64)
|
format!("{}", n as i64)
|
||||||
@@ -130,8 +132,10 @@ mod tests {
|
|||||||
async fn calc_sin_pi() {
|
async fn calc_sin_pi() {
|
||||||
let p = CalcPlugin::new();
|
let p = CalcPlugin::new();
|
||||||
let results = p.search("sin(pi)").await;
|
let results = p.search("sin(pi)").await;
|
||||||
// sin(pi) ≈ 0, but floating point; result should not be empty
|
|
||||||
assert!(!results.is_empty());
|
assert!(!results.is_empty());
|
||||||
|
let title = results[0].title.as_str();
|
||||||
|
let val: f64 = title.trim_start_matches("= ").parse().unwrap();
|
||||||
|
assert!(val.abs() < 1e-10, "sin(pi) should be near zero, got {val}");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|||||||
Reference in New Issue
Block a user