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 BitsRol;
20
21impl Command for BitsRol {
22 fn name(&self) -> &str {
23 "bits rol"
24 }
25
26 fn signature(&self) -> Signature {
27 Signature::build("bits rol")
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 left.")
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 left for ints or binary values."
58 }
59
60 fn search_terms(&self) -> Vec<&str> {
61 vec!["rotate left"]
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 left a number with 2 bits",
96 example: "17 | bits rol 2",
97 result: Some(Value::test_int(68)),
98 },
99 Example {
100 description: "Rotate left a list of numbers with 2 bits",
101 example: "[5 3 2] | bits rol 2",
102 result: Some(Value::list(
103 vec![Value::test_int(20), Value::test_int(12), Value::test_int(8)],
104 Span::test_data(),
105 )),
106 },
107 Example {
108 description: "rotate left binary data",
109 example: "0x[c0 ff ee] | bits rol 10",
110 result: Some(Value::binary(vec![0xff, 0xbb, 0x03], Span::test_data())),
111 },
112 ]
113 }
114}
115
116fn action(input: &Value, args: &Arguments, span: Span) -> Value {
117 let Arguments {
118 signed,
119 number_size,
120 bits,
121 } = *args;
122 let bits_span = bits.span;
123 let bits = bits.item;
124
125 match input {
126 Value::Int { val, .. } => {
127 use InputNumType::*;
128 let val = *val;
129 let bits = bits as u32;
130 let input_num_type = get_input_num_type(val, signed, number_size);
131
132 if bits > input_num_type.num_bits() {
133 return Value::error(
134 ShellError::IncorrectValue {
135 msg: format!(
136 "Trying to rotate by more than the available bits ({})",
137 input_num_type.num_bits()
138 ),
139 val_span: bits_span,
140 call_span: span,
141 },
142 span,
143 );
144 }
145 let int = match input_num_type {
146 One => (val as u8).rotate_left(bits) as i64,
147 Two => (val as u16).rotate_left(bits) as i64,
148 Four => (val as u32).rotate_left(bits) as i64,
149 Eight => {
150 let Ok(i) = i64::try_from((val as u64).rotate_left(bits)) else {
151 return Value::error(
152 ShellError::Generic(GenericError::new(
153 "result out of range for specified number",
154 format!(
155 "rotating left by {bits} is out of range for the value {val}"
156 ),
157 span,
158 )),
159 span,
160 );
161 };
162 i
163 }
164 SignedOne => (val as i8).rotate_left(bits) as i64,
165 SignedTwo => (val as i16).rotate_left(bits) as i64,
166 SignedFour => (val as i32).rotate_left(bits) as i64,
167 SignedEight => val.rotate_left(bits),
168 };
169
170 Value::int(int, span)
171 }
172 Value::Binary { val, .. } => {
173 let len = val.len();
174 if bits > len * 8 {
175 return Value::error(
176 ShellError::IncorrectValue {
177 msg: format!(
178 "Trying to rotate by more than the available bits ({})",
179 len * 8
180 ),
181 val_span: bits_span,
182 call_span: span,
183 },
184 span,
185 );
186 }
187 let byte_shift = bits / 8;
188 let bit_rotate = bits % 8;
189
190 let bytes = if bit_rotate == 0 {
191 rotate_bytes_left(val, byte_shift)
192 } else {
193 rotate_bytes_and_bits_left(val, byte_shift, bit_rotate)
194 };
195
196 Value::binary(bytes, span)
197 }
198 Value::Error { .. } => input.clone(),
200 other => Value::error(
201 ShellError::OnlySupportsThisInputType {
202 exp_input_type: "int or binary".into(),
203 wrong_type: other.get_type().to_string(),
204 dst_span: span,
205 src_span: other.span(),
206 },
207 span,
208 ),
209 }
210}
211
212fn rotate_bytes_left(data: &[u8], byte_shift: usize) -> Vec<u8> {
213 let len = data.len();
214 let mut output = vec![0; len];
215 output[..len - byte_shift].copy_from_slice(&data[byte_shift..]);
216 output[len - byte_shift..].copy_from_slice(&data[..byte_shift]);
217 output
218}
219
220fn rotate_bytes_and_bits_left(data: &[u8], byte_shift: usize, bit_shift: usize) -> Vec<u8> {
221 debug_assert!(byte_shift < data.len());
222 debug_assert!(
223 (1..8).contains(&bit_shift),
224 "Bit shifts of 0 can't be handled by this impl and everything else should be part of the byteshift"
225 );
226 let mut bytes = Vec::with_capacity(data.len());
227 let mut next_index = byte_shift;
228 for _ in 0..data.len() {
229 let curr_byte = data[next_index];
230 next_index += 1;
231 if next_index == data.len() {
232 next_index = 0;
233 }
234 let next_byte = data[next_index];
235 let new_byte = (curr_byte << bit_shift) | (next_byte >> (8 - bit_shift));
236 bytes.push(new_byte);
237 }
238 bytes
239}
240
241#[cfg(test)]
242mod test {
243 use super::*;
244
245 #[test]
246 fn test_examples() -> nu_test_support::Result {
247 nu_test_support::test().examples(BitsRol)
248 }
249}