1use super::{InputNumType, NumberBytes, get_input_num_type, get_number_bytes};
2use nu_cmd_base::input_handler::{CmdArgument, operate};
3use nu_engine::command_prelude::*;
4use nu_protocol::shell_error::generic::GenericError;
5
6struct Arguments {
7 signed: bool,
8 bits: Spanned<usize>,
9 number_size: NumberBytes,
10}
11
12impl CmdArgument for Arguments {
13 fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
14 None
15 }
16}
17
18#[derive(Clone)]
19pub struct BitsRor;
20
21impl Command for BitsRor {
22 fn name(&self) -> &str {
23 "bits ror"
24 }
25
26 fn signature(&self) -> Signature {
27 Signature::build("bits ror")
28 .input_output_types(vec![
29 (Type::Int, Type::Int),
30 (Type::Binary, Type::Binary),
31 (
32 Type::List(Box::new(Type::Int)),
33 Type::List(Box::new(Type::Int)),
34 ),
35 (
36 Type::List(Box::new(Type::Binary)),
37 Type::List(Box::new(Type::Binary)),
38 ),
39 ])
40 .allow_variants_without_examples(true)
41 .required("bits", SyntaxShape::Int, "Number of bits to rotate right.")
42 .switch(
43 "signed",
44 "Always treat input number as a signed number.",
45 Some('s'),
46 )
47 .named(
48 "number-bytes",
49 SyntaxShape::Int,
50 "The word size in number of bytes. Must be `1`, `2`, `4`, or `8` (defaults to the smallest of those that fits the input number).",
51 Some('n'),
52 )
53 .category(Category::Bits)
54 }
55
56 fn description(&self) -> &str {
57 "Bitwise rotate right for ints or binary values."
58 }
59
60 fn search_terms(&self) -> Vec<&str> {
61 vec!["rotate right"]
62 }
63
64 fn run(
65 &self,
66 engine_state: &EngineState,
67 stack: &mut Stack,
68 call: &Call,
69 input: PipelineData,
70 ) -> Result<PipelineData, ShellError> {
71 let head = call.head;
72 let bits = call.req(engine_state, stack, 0)?;
73 let signed = call.has_flag(engine_state, stack, "signed")?;
74 let number_bytes: Option<Spanned<usize>> =
75 call.get_flag(engine_state, stack, "number-bytes")?;
76 let number_size = get_number_bytes(number_bytes, head)?;
77
78 if let PipelineData::Empty = input {
80 return Err(ShellError::PipelineEmpty { dst_span: head });
81 }
82
83 let args = Arguments {
84 signed,
85 number_size,
86 bits,
87 };
88
89 operate(action, args, input, head, engine_state.signals())
90 }
91
92 fn examples(&self) -> Vec<Example<'_>> {
93 vec![
94 Example {
95 description: "rotate right a number with 2 bits",
96 example: "17 | bits ror 2",
97 result: Some(Value::test_int(68)),
98 },
99 Example {
100 description: "rotate right a list of numbers of two bytes",
101 example: "[15 33 92] | bits ror 2 --number-bytes 2",
102 result: Some(Value::list(
103 vec![
104 Value::test_int(49155),
105 Value::test_int(16392),
106 Value::test_int(23),
107 ],
108 Span::test_data(),
109 )),
110 },
111 Example {
112 description: "rotate right binary data",
113 example: "0x[ff bb 03] | bits ror 10",
114 result: Some(Value::binary(vec![0xc0, 0xff, 0xee], Span::test_data())),
115 },
116 ]
117 }
118}
119
120fn action(input: &Value, args: &Arguments, span: Span) -> Value {
121 let Arguments {
122 signed,
123 number_size,
124 bits,
125 } = *args;
126 let bits_span = bits.span;
127 let bits = bits.item;
128
129 match input {
130 Value::Int { val, .. } => {
131 use InputNumType::*;
132 let val = *val;
133 let bits = bits as u32;
134 let input_num_type = get_input_num_type(val, signed, number_size);
135
136 if bits > input_num_type.num_bits() {
137 return Value::error(
138 ShellError::IncorrectValue {
139 msg: format!(
140 "Trying to rotate by more than the available bits ({})",
141 input_num_type.num_bits()
142 ),
143 val_span: bits_span,
144 call_span: span,
145 },
146 span,
147 );
148 }
149 let int = match input_num_type {
150 One => (val as u8).rotate_right(bits) as i64,
151 Two => (val as u16).rotate_right(bits) as i64,
152 Four => (val as u32).rotate_right(bits) as i64,
153 Eight => {
154 let Ok(i) = i64::try_from((val as u64).rotate_right(bits)) else {
155 return Value::error(
156 ShellError::Generic(GenericError::new(
157 "result out of range for specified number",
158 format!(
159 "rotating right by {bits} is out of range for the value {val}"
160 ),
161 span,
162 )),
163 span,
164 );
165 };
166 i
167 }
168 SignedOne => (val as i8).rotate_right(bits) as i64,
169 SignedTwo => (val as i16).rotate_right(bits) as i64,
170 SignedFour => (val as i32).rotate_right(bits) as i64,
171 SignedEight => val.rotate_right(bits),
172 };
173
174 Value::int(int, span)
175 }
176 Value::Binary { val, .. } => {
177 let len = val.len();
178 if bits > len * 8 {
179 return Value::error(
180 ShellError::IncorrectValue {
181 msg: format!(
182 "Trying to rotate by more than the available bits ({})",
183 len * 8
184 ),
185 val_span: bits_span,
186 call_span: span,
187 },
188 span,
189 );
190 }
191 let byte_shift = bits / 8;
192 let bit_rotate = bits % 8;
193
194 let bytes = if bit_rotate == 0 {
195 rotate_bytes_right(val, byte_shift)
196 } else {
197 rotate_bytes_and_bits_right(val, byte_shift, bit_rotate)
198 };
199
200 Value::binary(bytes, span)
201 }
202 Value::Error { .. } => input.clone(),
204 other => Value::error(
205 ShellError::OnlySupportsThisInputType {
206 exp_input_type: "int or binary".into(),
207 wrong_type: other.get_type().to_string(),
208 dst_span: span,
209 src_span: other.span(),
210 },
211 span,
212 ),
213 }
214}
215
216fn rotate_bytes_right(data: &[u8], byte_shift: usize) -> Vec<u8> {
217 let len = data.len();
218 let mut output = vec![0; len];
219 output[byte_shift..].copy_from_slice(&data[..len - byte_shift]);
220 output[..byte_shift].copy_from_slice(&data[len - byte_shift..]);
221 output
222}
223
224fn rotate_bytes_and_bits_right(data: &[u8], byte_shift: usize, bit_shift: usize) -> Vec<u8> {
225 debug_assert!(byte_shift < data.len());
226 debug_assert!(
227 (1..8).contains(&bit_shift),
228 "Bit shifts of 0 can't be handled by this impl and everything else should be part of the byteshift"
229 );
230 let mut bytes = Vec::with_capacity(data.len());
231 let mut previous_index = data.len() - byte_shift - 1;
232 for _ in 0..data.len() {
233 let previous_byte = data[previous_index];
234 previous_index += 1;
235 if previous_index == data.len() {
236 previous_index = 0;
237 }
238 let curr_byte = data[previous_index];
239 let rotated_byte = (curr_byte >> bit_shift) | (previous_byte << (8 - bit_shift));
240 bytes.push(rotated_byte);
241 }
242
243 bytes
244}
245#[cfg(test)]
246mod test {
247 use super::*;
248
249 #[test]
250 fn test_examples() -> nu_test_support::Result {
251 nu_test_support::test().examples(BitsRor)
252 }
253}