From 48f1c3a49efc92f702f5e14b5d7b7e3665bdc804 Mon Sep 17 00:00:00 2001 From: Justin Ma Date: Fri, 5 Aug 2022 21:40:01 +0800 Subject: [PATCH] add `bits ror` and `bits rol` commands (#6224) --- crates/nu-command/src/bits/mod.rs | 4 + crates/nu-command/src/bits/rotate_left.rs | 156 ++++++++++++++++++++ crates/nu-command/src/bits/rotate_right.rs | 160 +++++++++++++++++++++ crates/nu-command/src/default_context.rs | 2 + src/tests/test_bits.rs | 30 ++++ 5 files changed, 352 insertions(+) create mode 100644 crates/nu-command/src/bits/rotate_left.rs create mode 100644 crates/nu-command/src/bits/rotate_right.rs diff --git a/crates/nu-command/src/bits/mod.rs b/crates/nu-command/src/bits/mod.rs index 902f349618..b70bb73b55 100644 --- a/crates/nu-command/src/bits/mod.rs +++ b/crates/nu-command/src/bits/mod.rs @@ -2,6 +2,8 @@ mod and; mod bits_; mod not; mod or; +mod rotate_left; +mod rotate_right; mod shift_left; mod shift_right; mod xor; @@ -12,6 +14,8 @@ pub use and::SubCommand as BitsAnd; pub use bits_::Bits; pub use not::SubCommand as BitsNot; pub use or::SubCommand as BitsOr; +pub use rotate_left::SubCommand as BitsRotateLeft; +pub use rotate_right::SubCommand as BitsRotateRight; pub use shift_left::SubCommand as BitsShiftLeft; pub use shift_right::SubCommand as BitsShiftRight; pub use xor::SubCommand as BitsXor; diff --git a/crates/nu-command/src/bits/rotate_left.rs b/crates/nu-command/src/bits/rotate_left.rs new file mode 100644 index 0000000000..f7a11522e1 --- /dev/null +++ b/crates/nu-command/src/bits/rotate_left.rs @@ -0,0 +1,156 @@ +use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes}; +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value, +}; +use num_traits::int::PrimInt; +use std::fmt::Display; + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "bits rol" + } + + fn signature(&self) -> Signature { + Signature::build("bits rol") + .required("bits", SyntaxShape::Int, "number of bits to rotate left") + .switch( + "signed", + "always treat input number as a signed number", + Some('s'), + ) + .named( + "number-bytes", + SyntaxShape::String, + "the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`", + Some('n'), + ) + .category(Category::Bits) + } + + fn usage(&self) -> &str { + "Bitwise rotate left for integers" + } + + fn search_terms(&self) -> Vec<&str> { + vec!["rotate left"] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let head = call.head; + let bits: usize = call.req(engine_state, stack, 0)?; + let signed = call.has_flag("signed"); + let number_bytes: Option> = + call.get_flag(engine_state, stack, "number-bytes")?; + let bytes_len = get_number_bytes(&number_bytes); + if let NumberBytes::Invalid = bytes_len { + if let Some(val) = number_bytes { + return Err(ShellError::UnsupportedInput( + "Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(), + val.span, + )); + } + } + + input.map( + move |value| operate(value, bits, head, signed, bytes_len), + engine_state.ctrlc.clone(), + ) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Rotate left a number with 2 bits", + example: "17 | bits rol 2", + result: Some(Value::Int { + val: 68, + span: Span::test_data(), + }), + }, + Example { + description: "Rotate left a list of numbers with 2 bits", + example: "[5 3 2] | bits rol 2", + result: Some(Value::List { + vals: vec![Value::test_int(20), Value::test_int(12), Value::test_int(8)], + span: Span::test_data(), + }), + }, + ] + } +} + +fn get_rotate_left(val: T, bits: u32, span: Span) -> Value +where + i64: std::convert::TryFrom, +{ + let rotate_result = i64::try_from(val.rotate_left(bits)); + match rotate_result { + Ok(val) => Value::Int { val, span }, + Err(_) => Value::Error { + error: ShellError::GenericError( + "Rotate left result beyond the range of 64 bit signed number".to_string(), + format!( + "{} of the specified number of bytes rotate left {} bits exceed limit", + val, bits + ), + Some(span), + None, + Vec::new(), + ), + }, + } +} + +fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value { + match value { + Value::Int { val, span } => { + use InputNumType::*; + // let bits = (((bits % 64) + 64) % 64) as u32; + let bits = bits as u32; + let input_type = get_input_num_type(val, signed, number_size); + match input_type { + One => get_rotate_left(val as u8, bits, span), + Two => get_rotate_left(val as u16, bits, span), + Four => get_rotate_left(val as u32, bits, span), + Eight => get_rotate_left(val as u64, bits, span), + SignedOne => get_rotate_left(val as i8, bits, span), + SignedTwo => get_rotate_left(val as i16, bits, span), + SignedFour => get_rotate_left(val as i32, bits, span), + SignedEight => get_rotate_left(val as i64, bits, span), + } + } + other => Value::Error { + error: ShellError::UnsupportedInput( + format!( + "Only integer values are supported, input type: {:?}", + other.get_type() + ), + other.span().unwrap_or(head), + ), + }, + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } +} diff --git a/crates/nu-command/src/bits/rotate_right.rs b/crates/nu-command/src/bits/rotate_right.rs new file mode 100644 index 0000000000..2b5c62fb37 --- /dev/null +++ b/crates/nu-command/src/bits/rotate_right.rs @@ -0,0 +1,160 @@ +use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes}; +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value, +}; +use num_traits::int::PrimInt; +use std::fmt::Display; + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "bits ror" + } + + fn signature(&self) -> Signature { + Signature::build("bits ror") + .required("bits", SyntaxShape::Int, "number of bits to rotate right") + .switch( + "signed", + "always treat input number as a signed number", + Some('s'), + ) + .named( + "number-bytes", + SyntaxShape::String, + "the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`", + Some('n'), + ) + .category(Category::Bits) + } + + fn usage(&self) -> &str { + "Bitwise rotate right for integers" + } + + fn search_terms(&self) -> Vec<&str> { + vec!["rotate right"] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let head = call.head; + let bits: usize = call.req(engine_state, stack, 0)?; + let signed = call.has_flag("signed"); + let number_bytes: Option> = + call.get_flag(engine_state, stack, "number-bytes")?; + let bytes_len = get_number_bytes(&number_bytes); + if let NumberBytes::Invalid = bytes_len { + if let Some(val) = number_bytes { + return Err(ShellError::UnsupportedInput( + "Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(), + val.span, + )); + } + } + + input.map( + move |value| operate(value, bits, head, signed, bytes_len), + engine_state.ctrlc.clone(), + ) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Rotate right a number with 60 bits", + example: "17 | bits ror 60", + result: Some(Value::Int { + val: 272, + span: Span::test_data(), + }), + }, + Example { + description: "Rotate right a list of numbers of one byte", + example: "[15 33 92] | bits ror 2 -n 1", + result: Some(Value::List { + vals: vec![ + Value::test_int(195), + Value::test_int(72), + Value::test_int(23), + ], + span: Span::test_data(), + }), + }, + ] + } +} + +fn get_rotate_right(val: T, bits: u32, span: Span) -> Value +where + i64: std::convert::TryFrom, +{ + let rotate_result = i64::try_from(val.rotate_right(bits)); + match rotate_result { + Ok(val) => Value::Int { val, span }, + Err(_) => Value::Error { + error: ShellError::GenericError( + "Rotate right result beyond the range of 64 bit signed number".to_string(), + format!( + "{} of the specified number of bytes rotate right {} bits exceed limit", + val, bits + ), + Some(span), + None, + Vec::new(), + ), + }, + } +} + +fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value { + match value { + Value::Int { val, span } => { + use InputNumType::*; + // let bits = (((bits % 64) + 64) % 64) as u32; + let bits = bits as u32; + let input_type = get_input_num_type(val, signed, number_size); + match input_type { + One => get_rotate_right(val as u8, bits, span), + Two => get_rotate_right(val as u16, bits, span), + Four => get_rotate_right(val as u32, bits, span), + Eight => get_rotate_right(val as u64, bits, span), + SignedOne => get_rotate_right(val as i8, bits, span), + SignedTwo => get_rotate_right(val as i16, bits, span), + SignedFour => get_rotate_right(val as i32, bits, span), + SignedEight => get_rotate_right(val as i64, bits, span), + } + } + other => Value::Error { + error: ShellError::UnsupportedInput( + format!( + "Only integer values are supported, input type: {:?}", + other.get_type() + ), + other.span().unwrap_or(head), + ), + }, + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } +} diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index a270267d38..747ee873eb 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -214,6 +214,8 @@ pub fn create_default_context() -> EngineState { BitsNot, BitsOr, BitsXor, + BitsRotateLeft, + BitsRotateRight, BitsShiftLeft, BitsShiftRight, } diff --git a/src/tests/test_bits.rs b/src/tests/test_bits.rs index 23c6bdfb7b..41455eea8d 100644 --- a/src/tests/test_bits.rs +++ b/src/tests/test_bits.rs @@ -74,3 +74,33 @@ fn bits_shift_right_negative() -> TestResult { fn bits_shift_right_list() -> TestResult { run_test("[12 98 7 64 900 10] | bits shr 3 | str collect '.'", "1.12.0.8.112.1") } + +#[test] +fn bits_rotate_left() -> TestResult { + run_test("2 | bits rol 3", "16") +} + +#[test] +fn bits_rotate_left_negative() -> TestResult { + run_test("-3 | bits rol 5", "-65") +} + +#[test] +fn bits_rotate_left_list() -> TestResult { + run_test("[1 2 7 32 9 10] | bits rol 3 | str collect '.'", "8.16.56.256.72.80") +} + +#[test] +fn bits_rotate_right() -> TestResult { + run_test("2 | bits ror 62", "8") +} + +#[test] +fn bits_rotate_right_negative() -> TestResult { + run_test("-3 | bits ror 60", "-33") +} + +#[test] +fn bits_rotate_right_list() -> TestResult { + run_test("[1 2 7 32 23 10] | bits ror 60 | str collect '.'", "16.32.112.512.368.160") +}