use nu_protocol::{ShellError, Span, Value}; use std::cmp::Ordering; #[allow(dead_code)] pub enum Reduce { Summation, Product, Minimum, Maximum, } pub type ReducerFunction = Box, Span) -> Result + Send + Sync + 'static>; pub fn reducer_for(command: Reduce) -> ReducerFunction { match command { Reduce::Summation => Box::new(|_, values, head| sum(values, head)), Reduce::Product => Box::new(|_, values, head| product(values, head)), Reduce::Minimum => Box::new(|_, values, head| min(values, head)), Reduce::Maximum => Box::new(|_, values, head| max(values, head)), } } pub fn max(data: Vec, head: Span) -> Result { let mut biggest = data .first() .ok_or_else(|| ShellError::UnsupportedInput("Empty input".to_string(), Span::unknown()))? .clone(); for value in &data { if let Some(result) = value.partial_cmp(&biggest) { if result == Ordering::Greater { biggest = value.clone(); } } else { return Err(ShellError::OperatorMismatch { op_span: head, lhs_ty: biggest.get_type(), lhs_span: biggest.span()?, rhs_ty: value.get_type(), rhs_span: value.span()?, }); } } Ok(biggest) } pub fn min(data: Vec, head: Span) -> Result { let mut smallest = data .first() .ok_or_else(|| ShellError::UnsupportedInput("Empty input".to_string(), Span::unknown()))? .clone(); for value in &data { if let Some(result) = value.partial_cmp(&smallest) { if result == Ordering::Less { smallest = value.clone(); } } else { return Err(ShellError::OperatorMismatch { op_span: head, lhs_ty: smallest.get_type(), lhs_span: smallest.span()?, rhs_ty: value.get_type(), rhs_span: value.span()?, }); } } Ok(smallest) } pub fn sum(data: Vec, head: Span) -> Result { let initial_value = data.get(0); let mut acc = match initial_value { Some(Value::Filesize { span, .. }) => Ok(Value::Filesize { val: 0, span: *span, }), Some(Value::Duration { span, .. }) => Ok(Value::Duration { val: 0, span: *span, }), Some(Value::Int { span, .. }) | Some(Value::Float { span, .. }) => Ok(Value::Int { val: 0, span: *span, }), None => Err(ShellError::UnsupportedInput( "Empty input".to_string(), Span::unknown(), )), _ => Ok(Value::nothing()), }?; for value in &data { match value { Value::Int { .. } | Value::Float { .. } | Value::Filesize { .. } | Value::Duration { .. } => { let new_value = acc.add(head, value); if new_value.is_err() { return new_value; } acc = new_value.expect("This should never trigger") } other => { return Err(ShellError::UnsupportedInput( "Attempted to compute the sum of a value that cannot be summed".to_string(), other.span().unwrap_or_else(|_| Span::unknown()), )); } } } Ok(acc) } pub fn product(data: Vec, head: Span) -> Result { let initial_value = data.get(0); let mut acc = match initial_value { Some(Value::Int { span, .. }) | Some(Value::Float { span, .. }) => Ok(Value::Int { val: 1, span: *span, }), None => Err(ShellError::UnsupportedInput( "Empty input".to_string(), Span::unknown(), )), _ => Ok(Value::nothing()), }?; for value in &data { match value { Value::Int { .. } | Value::Float { .. } => { let new_value = acc.mul(head, value); if new_value.is_err() { return new_value; } acc = new_value.expect("This should never trigger") } other => { return Err(ShellError::UnsupportedInput( "Attempted to compute the product of a value that cannot be multiplied" .to_string(), other.span().unwrap_or_else(|_| Span::unknown()), )); } } } Ok(acc) }