diff --git a/crates/nu-cli/src/cli.rs b/crates/nu-cli/src/cli.rs index d770aa7e66..f744e77ae5 100644 --- a/crates/nu-cli/src/cli.rs +++ b/crates/nu-cli/src/cli.rs @@ -264,7 +264,6 @@ pub fn create_default_context( whole_stream_command(Cpy), whole_stream_command(Date), whole_stream_command(Cal), - whole_stream_command(Calc), whole_stream_command(Mkdir), whole_stream_command(Mv), whole_stream_command(Kill), @@ -367,6 +366,7 @@ pub fn create_default_context( whole_stream_command(AutoenvUnTrust), whole_stream_command(Math), whole_stream_command(MathAverage), + whole_stream_command(MathEval), whole_stream_command(MathMedian), whole_stream_command(MathMinimum), whole_stream_command(MathMode), diff --git a/crates/nu-cli/src/commands.rs b/crates/nu-cli/src/commands.rs index 52b4629228..8345bf2279 100644 --- a/crates/nu-cli/src/commands.rs +++ b/crates/nu-cli/src/commands.rs @@ -14,7 +14,6 @@ pub(crate) mod autoenv_untrust; pub(crate) mod autoview; pub(crate) mod build_string; pub(crate) mod cal; -pub(crate) mod calc; pub(crate) mod cd; pub(crate) mod char_; pub(crate) mod classified; @@ -134,7 +133,6 @@ pub(crate) use autoenv_trust::AutoenvTrust; pub(crate) use autoenv_untrust::AutoenvUnTrust; pub(crate) use build_string::BuildString; pub(crate) use cal::Cal; -pub(crate) use calc::Calc; pub(crate) use char_::Char; pub(crate) use compact::Compact; pub(crate) use config::{ @@ -192,8 +190,8 @@ pub(crate) use last::Last; pub(crate) use lines::Lines; pub(crate) use ls::Ls; pub(crate) use math::{ - Math, MathAverage, MathMaximum, MathMedian, MathMinimum, MathMode, MathStddev, MathSummation, - MathVariance, + Math, MathAverage, MathEval, MathMaximum, MathMedian, MathMinimum, MathMode, MathStddev, + MathSummation, MathVariance, }; pub(crate) use merge::Merge; pub(crate) use mkdir::Mkdir; diff --git a/crates/nu-cli/src/commands/calc.rs b/crates/nu-cli/src/commands/calc.rs deleted file mode 100644 index 9337e04940..0000000000 --- a/crates/nu-cli/src/commands/calc.rs +++ /dev/null @@ -1,88 +0,0 @@ -use crate::commands::WholeStreamCommand; -use crate::prelude::*; -use nu_errors::ShellError; -use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value}; - -pub struct Calc; - -#[async_trait] -impl WholeStreamCommand for Calc { - fn name(&self) -> &str { - "calc" - } - - fn usage(&self) -> &str { - "Parse a math expression into a number" - } - - async fn run( - &self, - args: CommandArgs, - registry: &CommandRegistry, - ) -> Result { - calc(args, registry).await - } - - fn examples(&self) -> Vec { - vec![Example { - description: "Calculate math in the pipeline", - example: "echo '10 / 4' | calc", - result: Some(vec![UntaggedValue::decimal(2.5).into()]), - }] - } -} - -pub async fn calc( - args: CommandArgs, - _registry: &CommandRegistry, -) -> Result { - let input = args.input; - let name = args.call_info.name_tag.span; - - Ok(input - .map(move |input| { - if let Ok(string) = input.as_string() { - match parse(&string, &input.tag) { - Ok(value) => ReturnSuccess::value(value), - Err(err) => Err(ShellError::labeled_error( - "Calculation error", - err, - &input.tag.span, - )), - } - } else { - Err(ShellError::labeled_error( - "Expected a string from pipeline", - "requires string input", - name, - )) - } - }) - .to_output_stream()) -} - -pub fn parse(math_expression: &str, tag: impl Into) -> Result { - use std::f64; - let num = meval::eval_str(math_expression); - match num { - Ok(num) => { - if num == f64::INFINITY || num == f64::NEG_INFINITY { - return Err(String::from("cannot represent result")); - } - Ok(UntaggedValue::from(Primitive::from(num)).into_value(tag)) - } - Err(error) => Err(error.to_string()), - } -} - -#[cfg(test)] -mod tests { - use super::Calc; - - #[test] - fn examples_work_as_expected() { - use crate::examples::test as test_examples; - - test_examples(Calc {}) - } -} diff --git a/crates/nu-cli/src/commands/math/eval.rs b/crates/nu-cli/src/commands/math/eval.rs new file mode 100644 index 0000000000..8c15466450 --- /dev/null +++ b/crates/nu-cli/src/commands/math/eval.rs @@ -0,0 +1,107 @@ +use crate::commands::WholeStreamCommand; +use crate::prelude::*; +use nu_errors::ShellError; +use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; +use nu_source::Tagged; + +pub struct SubCommand; + +#[derive(Deserialize)] +pub struct SubCommandArgs { + expression: Option>, +} + +#[async_trait] +impl WholeStreamCommand for SubCommand { + fn name(&self) -> &str { + "math eval" + } + + fn usage(&self) -> &str { + "Evaluate a math expression into a number" + } + + fn signature(&self) -> Signature { + Signature::build("math eval").desc(self.usage()).optional( + "math expression", + SyntaxShape::String, + "the math expression to evaluate", + ) + } + + async fn run( + &self, + args: CommandArgs, + registry: &CommandRegistry, + ) -> Result { + eval(args, registry).await + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Evalulate math in the pipeline", + example: "echo '10 / 4' | math eval", + result: Some(vec![UntaggedValue::decimal(2.5).into()]), + }] + } +} + +pub async fn eval( + args: CommandArgs, + registry: &CommandRegistry, +) -> Result { + let name = args.call_info.name_tag.span; + let (SubCommandArgs { expression }, input) = args.process(registry).await?; + + Ok(input + .map(move |x| { + if let Some(Tagged { + tag, + item: expression, + }) = &expression + { + UntaggedValue::string(expression).into_value(tag) + } else { + x + } + }) + .map(move |input| { + if let Ok(string) = input.as_string() { + match parse(&string, &input.tag) { + Ok(value) => ReturnSuccess::value(value), + Err(err) => Err(ShellError::labeled_error( + "Math evaluation error", + err, + &input.tag.span, + )), + } + } else { + Err(ShellError::labeled_error( + "Expected a string from pipeline", + "requires string input", + name, + )) + } + }) + .to_output_stream()) +} + +pub fn parse>(math_expression: &str, tag: T) -> Result { + match meval::eval_str(math_expression) { + Ok(num) if num.is_infinite() || num.is_nan() => Err("cannot represent result".to_string()), + Ok(num) => Ok(UntaggedValue::from(Primitive::from(num)).into_value(tag)), + Err(error) => Err(error.to_string().to_lowercase()), + } +} + +#[cfg(test)] +mod tests { + use super::SubCommand; + + #[test] + fn examples_work_as_expected() { + use crate::examples::test as test_examples; + + test_examples(SubCommand {}) + } +} diff --git a/crates/nu-cli/src/commands/math/mod.rs b/crates/nu-cli/src/commands/math/mod.rs index 772cd3fdda..a6c9e9a4d6 100644 --- a/crates/nu-cli/src/commands/math/mod.rs +++ b/crates/nu-cli/src/commands/math/mod.rs @@ -1,5 +1,6 @@ pub mod avg; pub mod command; +pub mod eval; pub mod max; pub mod median; pub mod min; @@ -11,6 +12,7 @@ pub mod variance; pub use avg::SubCommand as MathAverage; pub use command::Command as Math; +pub use eval::SubCommand as MathEval; pub use max::SubCommand as MathMaximum; pub use median::SubCommand as MathMedian; pub use min::SubCommand as MathMinimum; diff --git a/crates/nu-cli/tests/commands/calc.rs b/crates/nu-cli/tests/commands/calc.rs deleted file mode 100644 index 874ce68f97..0000000000 --- a/crates/nu-cli/tests/commands/calc.rs +++ /dev/null @@ -1,49 +0,0 @@ -use nu_test_support::{nu, pipeline}; - -#[test] -fn calculates_two_plus_two() { - let actual = nu!( - cwd: ".", pipeline( - r#" - echo "2 + 2" | calc - "# - )); - - assert!(actual.out.contains("4.0")); -} - -#[test] -fn calculates_two_to_the_power_six() { - let actual = nu!( - cwd: ".", pipeline( - r#" - echo "2 ^ 6" | calc - "# - )); - - assert!(actual.out.contains("64.0")); -} - -#[test] -fn calculates_three_multiplied_by_five() { - let actual = nu!( - cwd: ".", pipeline( - r#" - echo "3 * 5" | calc - "# - )); - - assert!(actual.out.contains("15.0")); -} - -#[test] -fn calculates_twenty_four_divided_by_two() { - let actual = nu!( - cwd: ".", pipeline( - r#" - echo "24 / 2" | calc - "# - )); - - assert!(actual.out.contains("12.0")); -} diff --git a/crates/nu-cli/tests/commands/math/eval.rs b/crates/nu-cli/tests/commands/math/eval.rs new file mode 100644 index 0000000000..084dc7b433 --- /dev/null +++ b/crates/nu-cli/tests/commands/math/eval.rs @@ -0,0 +1,73 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn evaluates_two_plus_two() { + let actual = nu!( + cwd: ".", pipeline( + r#" + math eval "2 + 2" + "# + )); + + assert!(actual.out.contains("4.0")); +} + +#[test] +fn evaluates_two_to_the_power_four() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo "2 ^ 4" | math eval + "# + )); + + assert!(actual.out.contains("16.0")); +} + +#[test] +fn evaluates_three_multiplied_by_five() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo "3 * 5" | math eval + "# + )); + + assert!(actual.out.contains("15.0")); +} + +#[test] +fn evaluates_twenty_four_divided_by_two() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo "24 / 2" | math eval + "# + )); + + assert!(actual.out.contains("12.0")); +} + +#[test] +fn evaluates_twenty_eight_minus_seven() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo "28 - 7" | math eval + "# + )); + + assert!(actual.out.contains("21")); +} + +#[test] +fn evaluates_pi() { + let actual = nu!( + cwd: ".", pipeline( + r#" + math eval pi + "# + )); + + assert!(actual.out.contains("3.14")); +} diff --git a/crates/nu-cli/tests/commands/math/mod.rs b/crates/nu-cli/tests/commands/math/mod.rs index 7b6d1c77bc..10ef1bb285 100644 --- a/crates/nu-cli/tests/commands/math/mod.rs +++ b/crates/nu-cli/tests/commands/math/mod.rs @@ -1,5 +1,7 @@ mod avg; +mod eval; mod median; +mod sum; use nu_test_support::{nu, pipeline}; diff --git a/crates/nu-cli/tests/commands/mod.rs b/crates/nu-cli/tests/commands/mod.rs index 9e1361227d..bdafa69704 100644 --- a/crates/nu-cli/tests/commands/mod.rs +++ b/crates/nu-cli/tests/commands/mod.rs @@ -4,7 +4,6 @@ mod autoenv; mod autoenv_trust; mod autoenv_untrust; mod cal; -mod calc; mod cd; mod compact; mod cp; diff --git a/docs/commands/calc.md b/docs/commands/math-eval.md similarity index 70% rename from docs/commands/calc.md rename to docs/commands/math-eval.md index fe1a84de3c..61ee433afe 100644 --- a/docs/commands/calc.md +++ b/docs/commands/math-eval.md @@ -1,6 +1,6 @@ -# calc +# math eval -calc is a command that takes a math expression from the pipeline and calculates that into a number. +math eval is a command that takes a math expression from the pipeline and evaluates that into a number. It also optionally takes the math expression as an argument. This command supports the following operations - @@ -27,62 +27,62 @@ constants: ## Examples ```shell -> echo "1+2+3" | calc -6.0 +> echo "1+2+3" | math eval +6.0u ``` ```shell -> echo "1-2+3" | calc +> echo "1-2+3" | math eval 2.0 ``` ```shell -> echo "-(-23)" | calc +> echo "-(-23)" | math eval 23.0 ``` ```shell -> echo "5^2" | calc +> echo "5^2" | math eval 25.0 ``` ```shell -> echo "5^3" | calc +> echo "5^3" | math eval 125.0 ``` ```shell -> echo "min(5,4,3,2,1,0,-100,45)" | calc +> echo "min(5,4,3,2,1,0,-100,45)" | math eval -100.0 ``` ```shell -> echo "max(5,4,3,2,1,0,-100,45)" | calc +> echo "max(5,4,3,2,1,0,-100,45)" | math eval 45.0 ``` ```shell -> echo sqrt(2) | calc +> echo sqrt(2) | math eval 1.414213562373095 ``` ```shell -> echo pi | calc +> echo pi | math eval 3.141592653589793 ``` ```shell -> echo e | calc +> echo e | math eval 2.718281828459045 ``` ```shell -> echo "sin(pi / 2)" | calc +> echo "sin(pi / 2)" | math eval 1.0 ``` ```shell -> echo "floor(5999/1000)" | calc +> echo "floor(5999/1000)" | math eval 5.0 ``` @@ -119,7 +119,7 @@ constants: ``` ```shell -> open abc.json | format "({size} + 500) * 4" | calc +> open abc.json | format "({size} + 500) * 4" | math eval ───┬─────────── # │ ───┼─────────── @@ -135,7 +135,7 @@ constants: ``` ```shell -> open abc.json | format "({size} - 1000) * 4" | calc +> open abc.json | format "({size} - 1000) * 4" | math eval ───┬──────────── # │ ───┼──────────── @@ -150,9 +150,9 @@ constants: ───┴──────────── ``` -Note that since `calc` uses floating-point numbers, the result may not always be precise. +Note that since `math eval` uses floating-point numbers, the result may not always be precise. ```shell -> echo "floor(5999999999999999999/1000000000000000000)" | calc +> echo "floor(5999999999999999999/1000000000000000000)" | math eval 6.0 ``` diff --git a/docs/commands/math.md b/docs/commands/math.md index 1b81e1ba7f..6784b500dd 100644 --- a/docs/commands/math.md +++ b/docs/commands/math.md @@ -4,6 +4,7 @@ Mathematical functions that generally only operate on a list of numbers (integer Currently the following functions are implemented: * `math avg`: Finds the average of a list of numbers or tables +* [`math eval`](math-eval.md): Evaluates a list of math expressions into numbers * `math min`: Finds the minimum within a list of numbers or tables * `math max`: Finds the maximum within a list of numbers or tables * `math median`: Finds the median of a list of numbers or tables @@ -152,11 +153,3 @@ To get the sum of the characters that make up your present working directory. > echo [] | math avg error: Error: Unexpected: Cannot perform aggregate math operation on empty data ``` - -Note `math` functions only work on list of numbers (integers, decimals, bytes) and tables of numbers, if any other types are piped into the function -then unexpected results can occur. - -```shell -> echo [1 2 a ] | math avg -0 -```