Skip to main content

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::*;
5use nu_protocol::shell_error::generic::GenericError;
6
7use std::iter;
8
9struct Arguments {
10    signed: bool,
11    bits: Spanned<usize>,
12    number_size: NumberBytes,
13}
14
15impl CmdArgument for Arguments {
16    fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
17        None
18    }
19}
20
21#[derive(Clone)]
22pub struct BitsShl;
23
24impl Command for BitsShl {
25    fn name(&self) -> &str {
26        "bits shl"
27    }
28
29    fn signature(&self) -> Signature {
30        Signature::build("bits shl")
31            .input_output_types(vec![
32                (Type::Int, Type::Int),
33                (Type::Binary, Type::Binary),
34                (
35                    Type::List(Box::new(Type::Int)),
36                    Type::List(Box::new(Type::Int)),
37                ),
38                (
39                    Type::List(Box::new(Type::Binary)),
40                    Type::List(Box::new(Type::Binary)),
41                ),
42            ])
43            .allow_variants_without_examples(true)
44            .required("bits", SyntaxShape::Int, "Number of bits to shift left.")
45            .switch(
46                "signed",
47                "Always treat input number as a signed number.",
48                Some('s'),
49            )
50            .named(
51                "number-bytes",
52                SyntaxShape::Int,
53                "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).",
54                Some('n'),
55            )
56            .category(Category::Bits)
57    }
58
59    fn description(&self) -> &str {
60        "Bitwise shift left for ints or binary values."
61    }
62
63    fn search_terms(&self) -> Vec<&str> {
64        vec!["shift left"]
65    }
66
67    fn run(
68        &self,
69        engine_state: &EngineState,
70        stack: &mut Stack,
71        call: &Call,
72        input: PipelineData,
73    ) -> Result<PipelineData, ShellError> {
74        let head = call.head;
75        // This restricts to a positive shift value (our underlying operations do not
76        // permit them)
77        let bits: Spanned<usize> = call.req(engine_state, stack, 0)?;
78        let signed = call.has_flag(engine_state, stack, "signed")?;
79        let number_bytes: Option<Spanned<usize>> =
80            call.get_flag(engine_state, stack, "number-bytes")?;
81        let number_size = get_number_bytes(number_bytes, head)?;
82
83        // This doesn't match explicit nulls
84        if let PipelineData::Empty = input {
85            return Err(ShellError::PipelineEmpty { dst_span: head });
86        }
87
88        let args = Arguments {
89            signed,
90            number_size,
91            bits,
92        };
93
94        operate(action, args, input, head, engine_state.signals())
95    }
96
97    fn examples(&self) -> Vec<Example<'_>> {
98        vec![
99            Example {
100                description: "Shift left a number by 7 bits",
101                example: "2 | bits shl 7",
102                result: Some(Value::test_int(0)),
103            },
104            Example {
105                description: "Shift left a number with 2 byte by 7 bits",
106                example: "2 | bits shl 7 --number-bytes 2",
107                result: Some(Value::test_int(256)),
108            },
109            Example {
110                description: "Shift left a signed number by 1 bit",
111                example: "0x7F | bits shl 1 --signed",
112                result: Some(Value::test_int(-2)),
113            },
114            Example {
115                description: "Shift left a list of numbers",
116                example: "[5 3 2] | bits shl 2",
117                result: Some(Value::list(
118                    vec![Value::test_int(20), Value::test_int(12), Value::test_int(8)],
119                    Span::test_data(),
120                )),
121            },
122            Example {
123                description: "Shift left a binary value",
124                example: "0x[4f f4] | bits shl 4",
125                result: Some(Value::binary(vec![0xff, 0x40], Span::test_data())),
126            },
127        ]
128    }
129}
130
131fn action(input: &Value, args: &Arguments, span: Span) -> Value {
132    let Arguments {
133        signed,
134        number_size,
135        bits,
136    } = *args;
137    let bits_span = bits.span;
138    let bits = bits.item;
139
140    match input {
141        Value::Int { val, .. } => {
142            use InputNumType::*;
143            let val = *val;
144            let bits = bits as u32;
145
146            let input_num_type = get_input_num_type(val, signed, number_size);
147            if !input_num_type.is_permitted_bit_shift(bits) {
148                return Value::error(
149                    ShellError::IncorrectValue {
150                        msg: format!(
151                            "Trying to shift by more than the available bits (permitted < {})",
152                            input_num_type.num_bits()
153                        ),
154                        val_span: bits_span,
155                        call_span: span,
156                    },
157                    span,
158                );
159            }
160            let int = match input_num_type {
161                One => ((val as u8) << bits) as i64,
162                Two => ((val as u16) << bits) as i64,
163                Four => ((val as u32) << bits) as i64,
164                Eight => {
165                    let Ok(i) = i64::try_from((val as u64) << bits) else {
166                        return Value::error(
167                            ShellError::Generic(
168                                GenericError::new(
169                                    "result out of range for int",
170                                    format!(
171                                        "shifting left by {bits} is out of range for the value {val}"
172                                    ),
173                                    span,
174                                )
175                                .with_help("Ensure the result fits in a 64-bit signed integer."),
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() -> nu_test_support::Result {
263        nu_test_support::test().examples(BitsShl)
264    }
265}