nu_cmd_extra/extra/bits/
shift_left.rs

1use super::{InputNumType, NumberBytes, get_input_num_type, get_number_bytes};
2use itertools::Itertools;
3use nu_cmd_base::input_handler::{CmdArgument, operate};
4use nu_engine::command_prelude::*;
5
6use std::iter;
7
8struct Arguments {
9    signed: bool,
10    bits: Spanned<usize>,
11    number_size: NumberBytes,
12}
13
14impl CmdArgument for Arguments {
15    fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
16        None
17    }
18}
19
20#[derive(Clone)]
21pub struct BitsShl;
22
23impl Command for BitsShl {
24    fn name(&self) -> &str {
25        "bits shl"
26    }
27
28    fn signature(&self) -> Signature {
29        Signature::build("bits shl")
30            .input_output_types(vec![
31                (Type::Int, Type::Int),
32                (Type::Binary, Type::Binary),
33                (
34                    Type::List(Box::new(Type::Int)),
35                    Type::List(Box::new(Type::Int)),
36                ),
37                (
38                    Type::List(Box::new(Type::Binary)),
39                    Type::List(Box::new(Type::Binary)),
40                ),
41            ])
42            .allow_variants_without_examples(true)
43            .required("bits", SyntaxShape::Int, "Number of bits to shift left.")
44            .switch(
45                "signed",
46                "always treat input number as a signed number",
47                Some('s'),
48            )
49            .named(
50                "number-bytes",
51                SyntaxShape::Int,
52                "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)",
53                Some('n'),
54            )
55            .category(Category::Bits)
56    }
57
58    fn description(&self) -> &str {
59        "Bitwise shift left for ints or binary values."
60    }
61
62    fn search_terms(&self) -> Vec<&str> {
63        vec!["shift left"]
64    }
65
66    fn run(
67        &self,
68        engine_state: &EngineState,
69        stack: &mut Stack,
70        call: &Call,
71        input: PipelineData,
72    ) -> Result<PipelineData, ShellError> {
73        let head = call.head;
74        // This restricts to a positive shift value (our underlying operations do not
75        // permit them)
76        let bits: Spanned<usize> = call.req(engine_state, stack, 0)?;
77        let signed = call.has_flag(engine_state, stack, "signed")?;
78        let number_bytes: Option<Spanned<usize>> =
79            call.get_flag(engine_state, stack, "number-bytes")?;
80        let number_size = get_number_bytes(number_bytes, head)?;
81
82        // This doesn't match explicit nulls
83        if let PipelineData::Empty = input {
84            return Err(ShellError::PipelineEmpty { dst_span: head });
85        }
86
87        let args = Arguments {
88            signed,
89            number_size,
90            bits,
91        };
92
93        operate(action, args, input, head, engine_state.signals())
94    }
95
96    fn examples(&self) -> Vec<Example<'_>> {
97        vec![
98            Example {
99                description: "Shift left a number by 7 bits",
100                example: "2 | bits shl 7",
101                result: Some(Value::test_int(0)),
102            },
103            Example {
104                description: "Shift left a number with 2 byte by 7 bits",
105                example: "2 | bits shl 7 --number-bytes 2",
106                result: Some(Value::test_int(256)),
107            },
108            Example {
109                description: "Shift left a signed number by 1 bit",
110                example: "0x7F | bits shl 1 --signed",
111                result: Some(Value::test_int(-2)),
112            },
113            Example {
114                description: "Shift left a list of numbers",
115                example: "[5 3 2] | bits shl 2",
116                result: Some(Value::list(
117                    vec![Value::test_int(20), Value::test_int(12), Value::test_int(8)],
118                    Span::test_data(),
119                )),
120            },
121            Example {
122                description: "Shift left a binary value",
123                example: "0x[4f f4] | bits shl 4",
124                result: Some(Value::binary(vec![0xff, 0x40], Span::test_data())),
125            },
126        ]
127    }
128}
129
130fn action(input: &Value, args: &Arguments, span: Span) -> Value {
131    let Arguments {
132        signed,
133        number_size,
134        bits,
135    } = *args;
136    let bits_span = bits.span;
137    let bits = bits.item;
138
139    match input {
140        Value::Int { val, .. } => {
141            use InputNumType::*;
142            let val = *val;
143            let bits = bits as u32;
144
145            let input_num_type = get_input_num_type(val, signed, number_size);
146            if !input_num_type.is_permitted_bit_shift(bits) {
147                return Value::error(
148                    ShellError::IncorrectValue {
149                        msg: format!(
150                            "Trying to shift by more than the available bits (permitted < {})",
151                            input_num_type.num_bits()
152                        ),
153                        val_span: bits_span,
154                        call_span: span,
155                    },
156                    span,
157                );
158            }
159            let int = match input_num_type {
160                One => ((val as u8) << bits) as i64,
161                Two => ((val as u16) << bits) as i64,
162                Four => ((val as u32) << bits) as i64,
163                Eight => {
164                    let Ok(i) = i64::try_from((val as u64) << bits) else {
165                        return Value::error(
166                            ShellError::GenericError {
167                                error: "result out of range for int".into(),
168                                msg: format!(
169                                    "shifting left by {bits} is out of range for the value {val}"
170                                ),
171                                span: Some(span),
172                                help: Some(
173                                    "Ensure the result fits in a 64-bit signed integer.".into(),
174                                ),
175                                inner: vec![],
176                            },
177                            span,
178                        );
179                    };
180                    i
181                }
182                SignedOne => ((val as i8) << bits) as i64,
183                SignedTwo => ((val as i16) << bits) as i64,
184                SignedFour => ((val as i32) << bits) as i64,
185                SignedEight => val << bits,
186            };
187
188            Value::int(int, span)
189        }
190        Value::Binary { val, .. } => {
191            let byte_shift = bits / 8;
192            let bit_shift = bits % 8;
193
194            // This is purely for symmetry with the int case and the fact that the
195            // shift right implementation in its current form panicked with an overflow
196            if bits > val.len() * 8 {
197                return Value::error(
198                    ShellError::IncorrectValue {
199                        msg: format!(
200                            "Trying to shift by more than the available bits ({})",
201                            val.len() * 8
202                        ),
203                        val_span: bits_span,
204                        call_span: span,
205                    },
206                    span,
207                );
208            }
209            let bytes = if bit_shift == 0 {
210                shift_bytes_left(val, byte_shift)
211            } else {
212                shift_bytes_and_bits_left(val, byte_shift, bit_shift)
213            };
214
215            Value::binary(bytes, span)
216        }
217        // Propagate errors by explicitly matching them before the final case.
218        Value::Error { .. } => input.clone(),
219        other => Value::error(
220            ShellError::OnlySupportsThisInputType {
221                exp_input_type: "int or binary".into(),
222                wrong_type: other.get_type().to_string(),
223                dst_span: span,
224                src_span: other.span(),
225            },
226            span,
227        ),
228    }
229}
230
231fn shift_bytes_left(data: &[u8], byte_shift: usize) -> Vec<u8> {
232    let len = data.len();
233    let mut output = vec![0; len];
234    output[..len - byte_shift].copy_from_slice(&data[byte_shift..]);
235    output
236}
237
238fn shift_bytes_and_bits_left(data: &[u8], byte_shift: usize, bit_shift: usize) -> Vec<u8> {
239    use itertools::Position::*;
240    debug_assert!(
241        (1..8).contains(&bit_shift),
242        "Bit shifts of 0 can't be handled by this impl and everything else should be part of the byteshift"
243    );
244    data.iter()
245        .copied()
246        .skip(byte_shift)
247        .circular_tuple_windows::<(u8, u8)>()
248        .with_position()
249        .map(|(pos, (lhs, rhs))| match pos {
250            Last | Only => lhs << bit_shift,
251            _ => (lhs << bit_shift) | (rhs >> (8 - bit_shift)),
252        })
253        .chain(iter::repeat_n(0, byte_shift))
254        .collect::<Vec<u8>>()
255}
256
257#[cfg(test)]
258mod test {
259    use super::*;
260
261    #[test]
262    fn test_examples() {
263        use crate::test_examples;
264
265        test_examples(BitsShl {})
266    }
267}