1use crate::math::utils::ensure_bounded;
2use nu_engine::command_prelude::*;
3
4#[derive(Clone)]
5pub struct MathSqrt;
6
7impl Command for MathSqrt {
8 fn name(&self) -> &str {
9 "math sqrt"
10 }
11
12 fn signature(&self) -> Signature {
13 Signature::build("math sqrt")
14 .input_output_types(vec![
15 (Type::Number, Type::Float),
16 (
17 Type::List(Box::new(Type::Number)),
18 Type::List(Box::new(Type::Float)),
19 ),
20 (Type::Range, Type::List(Box::new(Type::Number))),
21 ])
22 .allow_variants_without_examples(true)
23 .category(Category::Math)
24 }
25
26 fn description(&self) -> &str {
27 "Returns the square root of the input number."
28 }
29
30 fn search_terms(&self) -> Vec<&str> {
31 vec!["square", "root"]
32 }
33
34 fn is_const(&self) -> bool {
35 true
36 }
37
38 fn run(
39 &self,
40 engine_state: &EngineState,
41 _stack: &mut Stack,
42 call: &Call,
43 input: PipelineData,
44 ) -> Result<PipelineData, ShellError> {
45 let head = call.head;
46 if matches!(input, PipelineData::Empty) {
48 return Err(ShellError::PipelineEmpty { dst_span: head });
49 }
50 if let PipelineData::Value(
51 Value::Range {
52 ref val,
53 internal_span,
54 },
55 ..,
56 ) = input
57 {
58 ensure_bounded(val.as_ref(), internal_span, head)?;
59 }
60 input.map(move |value| operate(value, head), engine_state.signals())
61 }
62
63 fn run_const(
64 &self,
65 working_set: &StateWorkingSet,
66 call: &Call,
67 input: PipelineData,
68 ) -> Result<PipelineData, ShellError> {
69 let head = call.head;
70 if matches!(input, PipelineData::Empty) {
72 return Err(ShellError::PipelineEmpty { dst_span: head });
73 }
74 if let PipelineData::Value(
75 Value::Range {
76 ref val,
77 internal_span,
78 },
79 ..,
80 ) = input
81 {
82 ensure_bounded(val.as_ref(), internal_span, head)?;
83 }
84 input.map(
85 move |value| operate(value, head),
86 working_set.permanent().signals(),
87 )
88 }
89
90 fn examples(&self) -> Vec<Example> {
91 vec![Example {
92 description: "Compute the square root of each number in a list",
93 example: "[9 16] | math sqrt",
94 result: Some(Value::list(
95 vec![Value::test_float(3.0), Value::test_float(4.0)],
96 Span::test_data(),
97 )),
98 }]
99 }
100}
101
102fn operate(value: Value, head: Span) -> Value {
103 let span = value.span();
104 match value {
105 Value::Int { val, .. } => {
106 let squared = (val as f64).sqrt();
107 if squared.is_nan() {
108 return error_negative_sqrt(head, span);
109 }
110 Value::float(squared, span)
111 }
112 Value::Float { val, .. } => {
113 let squared = val.sqrt();
114 if squared.is_nan() {
115 return error_negative_sqrt(head, span);
116 }
117 Value::float(squared, span)
118 }
119 Value::Error { .. } => value,
120 other => Value::error(
121 ShellError::OnlySupportsThisInputType {
122 exp_input_type: "numeric".into(),
123 wrong_type: other.get_type().to_string(),
124 dst_span: head,
125 src_span: other.span(),
126 },
127 head,
128 ),
129 }
130}
131
132fn error_negative_sqrt(head: Span, span: Span) -> Value {
133 Value::error(
134 ShellError::UnsupportedInput {
135 msg: String::from("Can't square root a negative number"),
136 input: "value originates from here".into(),
137 msg_span: head,
138 input_span: span,
139 },
140 span,
141 )
142}
143
144#[cfg(test)]
145mod test {
146 use super::*;
147
148 #[test]
149 fn test_examples() {
150 use crate::test_examples;
151
152 test_examples(MathSqrt {})
153 }
154}