mirror of
https://github.com/nushell/nushell
synced 2024-11-10 15:14:14 +00:00
add binary data handling to bits
commands (#11854)
# Description - enables `bits` commands to operate on binary data, where both inputs are binary and can vary in length - adds an `--endian` flag to `bits and`, `or`, `xor` for specifying endianness (for binary values of different lengths) # User-Facing Changes - `bits` commands will no longer error for non-int inputs - the default for `--number-bytes` is now `auto` (infer int size; changed from 8) # Tests + Formatting > addendum: first PR, please inform if any changes are needed
This commit is contained in:
parent
7b95e37bbe
commit
d3895d71db
12 changed files with 742 additions and 428 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2910,6 +2910,7 @@ version = "0.90.2"
|
|||
dependencies = [
|
||||
"fancy-regex",
|
||||
"heck",
|
||||
"itertools 0.12.0",
|
||||
"nu-ansi-term",
|
||||
"nu-cmd-base",
|
||||
"nu-cmd-lang",
|
||||
|
|
|
@ -30,6 +30,7 @@ nu-pretty-hex = { version = "0.90.2", path = "../nu-pretty-hex" }
|
|||
nu-json = { version = "0.90.2", path = "../nu-json" }
|
||||
serde_urlencoded = "0.7.1"
|
||||
v_htmlescape = "0.15.0"
|
||||
itertools = "0.12"
|
||||
|
||||
[features]
|
||||
extra = ["default"]
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use super::binary_op;
|
||||
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, SyntaxShape, Type, Value,
|
||||
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -17,17 +18,32 @@ impl Command for BitsAnd {
|
|||
Signature::build("bits and")
|
||||
.input_output_types(vec![
|
||||
(Type::Int, Type::Int),
|
||||
(Type::Binary, Type::Binary),
|
||||
(
|
||||
Type::List(Box::new(Type::Int)),
|
||||
Type::List(Box::new(Type::Int)),
|
||||
),
|
||||
(
|
||||
Type::List(Box::new(Type::Binary)),
|
||||
Type::List(Box::new(Type::Binary)),
|
||||
),
|
||||
])
|
||||
.required("target", SyntaxShape::Int, "target int to perform bit and")
|
||||
.required(
|
||||
"target",
|
||||
SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::Int]),
|
||||
"right-hand side of the operation",
|
||||
)
|
||||
.named(
|
||||
"endian",
|
||||
SyntaxShape::String,
|
||||
"byte encode endian, available options: native(default), little, big",
|
||||
Some('e'),
|
||||
)
|
||||
.category(Category::Bits)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Performs bitwise and for ints."
|
||||
"Performs bitwise and for ints or binary values."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
|
@ -42,14 +58,32 @@ impl Command for BitsAnd {
|
|||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let target: i64 = call.req(engine_state, stack, 0)?;
|
||||
let target: Value = call.req(engine_state, stack, 0)?;
|
||||
let endian = call.get_flag::<Spanned<String>>(engine_state, stack, "endian")?;
|
||||
|
||||
let little_endian = if let Some(endian) = endian {
|
||||
match endian.item.as_str() {
|
||||
"native" => cfg!(target_endian = "little"),
|
||||
"little" => true,
|
||||
"big" => false,
|
||||
_ => {
|
||||
return Err(ShellError::TypeMismatch {
|
||||
err_message: "Endian must be one of native, little, big".to_string(),
|
||||
span: endian.span,
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cfg!(target_endian = "little")
|
||||
};
|
||||
|
||||
// This doesn't match explicit nulls
|
||||
if matches!(input, PipelineData::Empty) {
|
||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||
}
|
||||
|
||||
input.map(
|
||||
move |value| operate(value, target, head),
|
||||
move |value| binary_op(&value, &target, little_endian, |(l, r)| l & r, head),
|
||||
engine_state.ctrlc.clone(),
|
||||
)
|
||||
}
|
||||
|
@ -57,40 +91,47 @@ impl Command for BitsAnd {
|
|||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Apply bits and to two numbers",
|
||||
description: "Apply bitwise and to two numbers",
|
||||
example: "2 | bits and 2",
|
||||
result: Some(Value::test_int(2)),
|
||||
},
|
||||
Example {
|
||||
description: "Apply logical and to a list of numbers",
|
||||
description: "Apply bitwise and to two binary values",
|
||||
example: "0x[ab cd] | bits and 0x[99 99]",
|
||||
result: Some(Value::test_binary([0x89, 0x89])),
|
||||
},
|
||||
Example {
|
||||
description: "Apply bitwise and to a list of numbers",
|
||||
example: "[4 3 2] | bits and 2",
|
||||
result: Some(Value::list(
|
||||
vec![Value::test_int(0), Value::test_int(2), Value::test_int(2)],
|
||||
Span::test_data(),
|
||||
)),
|
||||
result: Some(Value::test_list(vec![
|
||||
Value::test_int(0),
|
||||
Value::test_int(2),
|
||||
Value::test_int(2),
|
||||
])),
|
||||
},
|
||||
Example {
|
||||
description: "Apply bitwise and to a list of binary data",
|
||||
example: "[0x[7f ff] 0x[ff f0]] | bits and 0x[99 99]",
|
||||
result: Some(Value::test_list(vec![
|
||||
Value::test_binary([0x19, 0x99]),
|
||||
Value::test_binary([0x99, 0x90]),
|
||||
])),
|
||||
},
|
||||
Example {
|
||||
description:
|
||||
"Apply bitwise and to binary data of varying lengths with specified endianness",
|
||||
example: "0x[c0 ff ee] | bits and 0x[ff] --endian big",
|
||||
result: Some(Value::test_binary(vec![0x00, 0x00, 0xee])),
|
||||
},
|
||||
Example {
|
||||
description: "Apply bitwise and to input binary data smaller than the operand",
|
||||
example: "0x[ff] | bits and 0x[12 34 56] --endian little",
|
||||
result: Some(Value::test_binary(vec![0x12, 0x00, 0x00])),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn operate(value: Value, target: i64, head: Span) -> Value {
|
||||
let span = value.span();
|
||||
match value {
|
||||
Value::Int { val, .. } => Value::int(val & target, span),
|
||||
// Propagate errors by explicitly matching them before the final case.
|
||||
Value::Error { .. } => value,
|
||||
other => Value::error(
|
||||
ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "int".into(),
|
||||
wrong_type: other.get_type().to_string(),
|
||||
dst_span: head,
|
||||
src_span: other.span(),
|
||||
},
|
||||
head,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
|
|
@ -13,6 +13,7 @@ pub use and::BitsAnd;
|
|||
pub use bits_::Bits;
|
||||
pub use into::BitsInto;
|
||||
pub use not::BitsNot;
|
||||
use nu_protocol::{ShellError, Value};
|
||||
pub use or::BitsOr;
|
||||
pub use rotate_left::BitsRol;
|
||||
pub use rotate_right::BitsRor;
|
||||
|
@ -20,7 +21,8 @@ pub use shift_left::BitsShl;
|
|||
pub use shift_right::BitsShr;
|
||||
pub use xor::BitsXor;
|
||||
|
||||
use nu_protocol::Spanned;
|
||||
use nu_protocol::{Span, Spanned};
|
||||
use std::iter;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum NumberBytes {
|
||||
|
@ -29,7 +31,6 @@ enum NumberBytes {
|
|||
Four,
|
||||
Eight,
|
||||
Auto,
|
||||
Invalid,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
|
@ -44,17 +45,22 @@ enum InputNumType {
|
|||
SignedEight,
|
||||
}
|
||||
|
||||
fn get_number_bytes(number_bytes: Option<&Spanned<String>>) -> NumberBytes {
|
||||
match number_bytes.as_ref() {
|
||||
None => NumberBytes::Eight,
|
||||
Some(size) => match size.item.as_str() {
|
||||
"1" => NumberBytes::One,
|
||||
"2" => NumberBytes::Two,
|
||||
"4" => NumberBytes::Four,
|
||||
"8" => NumberBytes::Eight,
|
||||
"auto" => NumberBytes::Auto,
|
||||
_ => NumberBytes::Invalid,
|
||||
},
|
||||
fn get_number_bytes(
|
||||
number_bytes: Option<Spanned<usize>>,
|
||||
head: Span,
|
||||
) -> Result<NumberBytes, ShellError> {
|
||||
match number_bytes {
|
||||
None => Ok(NumberBytes::Auto),
|
||||
Some(Spanned { item: 1, .. }) => Ok(NumberBytes::One),
|
||||
Some(Spanned { item: 2, .. }) => Ok(NumberBytes::Two),
|
||||
Some(Spanned { item: 4, .. }) => Ok(NumberBytes::Four),
|
||||
Some(Spanned { item: 8, .. }) => Ok(NumberBytes::Eight),
|
||||
Some(Spanned { span, .. }) => Err(ShellError::UnsupportedInput {
|
||||
msg: "Only 1, 2, 4, or 8 bytes are supported as word sizes".to_string(),
|
||||
input: "value originates from here".to_string(),
|
||||
msg_span: head,
|
||||
input_span: span,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,7 +82,6 @@ fn get_input_num_type(val: i64, signed: bool, number_size: NumberBytes) -> Input
|
|||
InputNumType::SignedEight
|
||||
}
|
||||
}
|
||||
NumberBytes::Invalid => InputNumType::SignedFour,
|
||||
}
|
||||
} else {
|
||||
match number_size {
|
||||
|
@ -95,7 +100,68 @@ fn get_input_num_type(val: i64, signed: bool, number_size: NumberBytes) -> Input
|
|||
InputNumType::Eight
|
||||
}
|
||||
}
|
||||
NumberBytes::Invalid => InputNumType::Four,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn binary_op<F>(lhs: &Value, rhs: &Value, little_endian: bool, f: F, head: Span) -> Value
|
||||
where
|
||||
F: Fn((i64, i64)) -> i64,
|
||||
{
|
||||
let span = lhs.span();
|
||||
match (lhs, rhs) {
|
||||
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
||||
Value::int(f((*lhs, *rhs)), span)
|
||||
}
|
||||
(Value::Binary { val: lhs, .. }, Value::Binary { val: rhs, .. }) => {
|
||||
let (lhs, rhs, max_len, min_len) = match (lhs.len(), rhs.len()) {
|
||||
(max, min) if max > min => (lhs, rhs, max, min),
|
||||
(min, max) => (rhs, lhs, max, min),
|
||||
};
|
||||
|
||||
let pad = iter::repeat(0).take(max_len - min_len);
|
||||
|
||||
let mut a;
|
||||
let mut b;
|
||||
|
||||
let padded: &mut dyn Iterator<Item = u8> = if little_endian {
|
||||
a = rhs.iter().copied().chain(pad);
|
||||
&mut a
|
||||
} else {
|
||||
b = pad.chain(rhs.iter().copied());
|
||||
&mut b
|
||||
};
|
||||
|
||||
let bytes: Vec<u8> = lhs
|
||||
.iter()
|
||||
.copied()
|
||||
.zip(padded)
|
||||
.map(|(lhs, rhs)| f((lhs as i64, rhs as i64)) as u8)
|
||||
.collect();
|
||||
|
||||
Value::binary(bytes, span)
|
||||
}
|
||||
(Value::Binary { .. }, Value::Int { .. }) | (Value::Int { .. }, Value::Binary { .. }) => {
|
||||
Value::error(
|
||||
ShellError::PipelineMismatch {
|
||||
exp_input_type: "input, and argument, to be both int or both binary"
|
||||
.to_string(),
|
||||
dst_span: rhs.span(),
|
||||
src_span: span,
|
||||
},
|
||||
span,
|
||||
)
|
||||
}
|
||||
// Propagate errors by explicitly matching them before the final case.
|
||||
(e @ Value::Error { .. }, _) | (_, e @ Value::Error { .. }) => e.clone(),
|
||||
(other, Value::Int { .. } | Value::Binary { .. }) | (_, other) => Value::error(
|
||||
ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "int or binary".into(),
|
||||
wrong_type: other.get_type().to_string(),
|
||||
dst_span: head,
|
||||
src_span: other.span(),
|
||||
},
|
||||
span,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use super::{get_number_bytes, NumberBytes};
|
||||
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::ast::{Call, CellPath};
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||
|
@ -9,6 +10,18 @@ use nu_protocol::{
|
|||
#[derive(Clone)]
|
||||
pub struct BitsNot;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Arguments {
|
||||
signed: bool,
|
||||
number_size: NumberBytes,
|
||||
}
|
||||
|
||||
impl CmdArgument for Arguments {
|
||||
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Command for BitsNot {
|
||||
fn name(&self) -> &str {
|
||||
"bits not"
|
||||
|
@ -18,10 +31,15 @@ impl Command for BitsNot {
|
|||
Signature::build("bits not")
|
||||
.input_output_types(vec![
|
||||
(Type::Int, Type::Int),
|
||||
(Type::Binary, Type::Binary),
|
||||
(
|
||||
Type::List(Box::new(Type::Int)),
|
||||
Type::List(Box::new(Type::Int)),
|
||||
),
|
||||
(
|
||||
Type::List(Box::new(Type::Binary)),
|
||||
Type::List(Box::new(Type::Binary)),
|
||||
),
|
||||
])
|
||||
.allow_variants_without_examples(true)
|
||||
.switch(
|
||||
|
@ -31,7 +49,7 @@ impl Command for BitsNot {
|
|||
)
|
||||
.named(
|
||||
"number-bytes",
|
||||
SyntaxShape::String,
|
||||
SyntaxShape::Int,
|
||||
"the size of unsigned number in bytes, it can be 1, 2, 4, 8, auto",
|
||||
Some('n'),
|
||||
)
|
||||
|
@ -55,28 +73,21 @@ impl Command for BitsNot {
|
|||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let signed = call.has_flag(engine_state, stack, "signed")?;
|
||||
let number_bytes: Option<Spanned<String>> =
|
||||
let number_bytes: Option<Spanned<usize>> =
|
||||
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||
let bytes_len = get_number_bytes(number_bytes.as_ref());
|
||||
if let NumberBytes::Invalid = bytes_len {
|
||||
if let Some(val) = number_bytes {
|
||||
return Err(ShellError::UnsupportedInput {
|
||||
msg: "Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||
input: "value originates from here".to_string(),
|
||||
msg_span: head,
|
||||
input_span: val.span,
|
||||
});
|
||||
}
|
||||
}
|
||||
let number_size = get_number_bytes(number_bytes, head)?;
|
||||
|
||||
// This doesn't match explicit nulls
|
||||
if matches!(input, PipelineData::Empty) {
|
||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||
}
|
||||
input.map(
|
||||
move |value| operate(value, head, signed, bytes_len),
|
||||
engine_state.ctrlc.clone(),
|
||||
)
|
||||
|
||||
let args = Arguments {
|
||||
signed,
|
||||
number_size,
|
||||
};
|
||||
|
||||
operate(action, args, input, head, engine_state.ctrlc.clone())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
|
@ -86,9 +97,9 @@ impl Command for BitsNot {
|
|||
example: "[4 3 2] | bits not",
|
||||
result: Some(Value::list(
|
||||
vec![
|
||||
Value::test_int(140737488355323),
|
||||
Value::test_int(140737488355324),
|
||||
Value::test_int(140737488355325),
|
||||
Value::test_int(251),
|
||||
Value::test_int(252),
|
||||
Value::test_int(253),
|
||||
],
|
||||
Span::test_data(),
|
||||
)),
|
||||
|
@ -96,7 +107,7 @@ impl Command for BitsNot {
|
|||
Example {
|
||||
description:
|
||||
"Apply logical negation to a list of numbers, treat input as 2 bytes number",
|
||||
example: "[4 3 2] | bits not --number-bytes '2'",
|
||||
example: "[4 3 2] | bits not --number-bytes 2",
|
||||
result: Some(Value::list(
|
||||
vec![
|
||||
Value::test_int(65531),
|
||||
|
@ -119,14 +130,23 @@ impl Command for BitsNot {
|
|||
Span::test_data(),
|
||||
)),
|
||||
},
|
||||
Example {
|
||||
description: "Apply logical negation to binary data",
|
||||
example: "0x[ff 00 7f] | bits not",
|
||||
result: Some(Value::binary(vec![0x00, 0xff, 0x80], Span::test_data())),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn operate(value: Value, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
||||
let span = value.span();
|
||||
match value {
|
||||
fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
let Arguments {
|
||||
signed,
|
||||
number_size,
|
||||
} = *args;
|
||||
match input {
|
||||
Value::Int { val, .. } => {
|
||||
let val = *val;
|
||||
if signed || val < 0 {
|
||||
Value::int(!val, span)
|
||||
} else {
|
||||
|
@ -147,25 +167,24 @@ fn operate(value: Value, head: Span, signed: bool, number_size: NumberBytes) ->
|
|||
!val & 0x7F_FF_FF_FF_FF_FF
|
||||
}
|
||||
}
|
||||
// This case shouldn't happen here, as it's handled before
|
||||
Invalid => 0,
|
||||
};
|
||||
Value::int(out_val, span)
|
||||
}
|
||||
}
|
||||
other => match other {
|
||||
// Propagate errors inside the value
|
||||
Value::Error { .. } => other,
|
||||
_ => Value::error(
|
||||
ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "int".into(),
|
||||
wrong_type: other.get_type().to_string(),
|
||||
dst_span: head,
|
||||
src_span: other.span(),
|
||||
},
|
||||
head,
|
||||
),
|
||||
},
|
||||
Value::Binary { val, .. } => {
|
||||
Value::binary(val.iter().copied().map(|b| !b).collect::<Vec<_>>(), span)
|
||||
}
|
||||
// Propagate errors by explicitly matching them before the final case.
|
||||
Value::Error { .. } => input.clone(),
|
||||
other => Value::error(
|
||||
ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "int or binary".into(),
|
||||
wrong_type: other.get_type().to_string(),
|
||||
dst_span: other.span(),
|
||||
src_span: span,
|
||||
},
|
||||
span,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use super::binary_op;
|
||||
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, SyntaxShape, Type, Value,
|
||||
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -17,17 +18,33 @@ impl Command for BitsOr {
|
|||
Signature::build("bits or")
|
||||
.input_output_types(vec![
|
||||
(Type::Int, Type::Int),
|
||||
(Type::Binary, Type::Binary),
|
||||
(
|
||||
Type::List(Box::new(Type::Int)),
|
||||
Type::List(Box::new(Type::Int)),
|
||||
),
|
||||
(
|
||||
Type::List(Box::new(Type::Binary)),
|
||||
Type::List(Box::new(Type::Binary)),
|
||||
),
|
||||
])
|
||||
.required("target", SyntaxShape::Int, "target int to perform bit or")
|
||||
.allow_variants_without_examples(true)
|
||||
.required(
|
||||
"target",
|
||||
SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::Int]),
|
||||
"right-hand side of the operation",
|
||||
)
|
||||
.named(
|
||||
"endian",
|
||||
SyntaxShape::String,
|
||||
"byte encode endian, available options: native(default), little, big",
|
||||
Some('e'),
|
||||
)
|
||||
.category(Category::Bits)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Performs bitwise or for ints."
|
||||
"Performs bitwise or for ints or binary values."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
|
@ -42,14 +59,32 @@ impl Command for BitsOr {
|
|||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let target: i64 = call.req(engine_state, stack, 0)?;
|
||||
let target: Value = call.req(engine_state, stack, 0)?;
|
||||
let endian = call.get_flag::<Spanned<String>>(engine_state, stack, "endian")?;
|
||||
|
||||
let little_endian = if let Some(endian) = endian {
|
||||
match endian.item.as_str() {
|
||||
"native" => cfg!(target_endian = "little"),
|
||||
"little" => true,
|
||||
"big" => false,
|
||||
_ => {
|
||||
return Err(ShellError::TypeMismatch {
|
||||
err_message: "Endian must be one of native, little, big".to_string(),
|
||||
span: endian.span,
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cfg!(target_endian = "little")
|
||||
};
|
||||
|
||||
// This doesn't match explicit nulls
|
||||
if matches!(input, PipelineData::Empty) {
|
||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||
}
|
||||
|
||||
input.map(
|
||||
move |value| operate(value, target, head),
|
||||
move |value| binary_op(&value, &target, little_endian, |(l, r)| l | r, head),
|
||||
engine_state.ctrlc.clone(),
|
||||
)
|
||||
}
|
||||
|
@ -62,35 +97,34 @@ impl Command for BitsOr {
|
|||
result: Some(Value::test_int(6)),
|
||||
},
|
||||
Example {
|
||||
description: "Apply logical or to a list of numbers",
|
||||
description: "Apply bitwise or to a list of numbers",
|
||||
example: "[8 3 2] | bits or 2",
|
||||
result: Some(Value::list(
|
||||
vec![Value::test_int(10), Value::test_int(3), Value::test_int(2)],
|
||||
Span::test_data(),
|
||||
)),
|
||||
result: Some(Value::test_list(vec![
|
||||
Value::test_int(10),
|
||||
Value::test_int(3),
|
||||
Value::test_int(2),
|
||||
])),
|
||||
},
|
||||
Example {
|
||||
description: "Apply bitwise or to binary data",
|
||||
example: "0x[88 cc] | bits or 0x[42 32]",
|
||||
result: Some(Value::test_binary(vec![0xca, 0xfe])),
|
||||
},
|
||||
Example {
|
||||
description:
|
||||
"Apply bitwise or to binary data of varying lengths with specified endianness",
|
||||
example: "0x[c0 ff ee] | bits or 0x[ff] --endian big",
|
||||
result: Some(Value::test_binary(vec![0xc0, 0xff, 0xff])),
|
||||
},
|
||||
Example {
|
||||
description: "Apply bitwise or to input binary data smaller than the operor",
|
||||
example: "0x[ff] | bits or 0x[12 34 56] --endian little",
|
||||
result: Some(Value::test_binary(vec![0xff, 0x34, 0x56])),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn operate(value: Value, target: i64, head: Span) -> Value {
|
||||
let span = value.span();
|
||||
match value {
|
||||
Value::Int { val, .. } => Value::int(val | target, span),
|
||||
// Propagate errors by explicitly matching them before the final case.
|
||||
Value::Error { .. } => value,
|
||||
other => Value::error(
|
||||
ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "int".into(),
|
||||
wrong_type: other.get_type().to_string(),
|
||||
dst_span: head,
|
||||
src_span: other.span(),
|
||||
},
|
||||
head,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
|
|
@ -1,12 +1,26 @@
|
|||
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||
use itertools::Itertools;
|
||||
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::ast::CellPath;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::Spanned;
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||
};
|
||||
use num_traits::int::PrimInt;
|
||||
use std::fmt::Display;
|
||||
|
||||
struct Arguments {
|
||||
signed: bool,
|
||||
bits: usize,
|
||||
number_size: NumberBytes,
|
||||
}
|
||||
|
||||
impl CmdArgument for Arguments {
|
||||
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BitsRol;
|
||||
|
@ -20,11 +34,17 @@ impl Command for BitsRol {
|
|||
Signature::build("bits rol")
|
||||
.input_output_types(vec![
|
||||
(Type::Int, Type::Int),
|
||||
(Type::Binary, Type::Binary),
|
||||
(
|
||||
Type::List(Box::new(Type::Int)),
|
||||
Type::List(Box::new(Type::Int)),
|
||||
),
|
||||
(
|
||||
Type::List(Box::new(Type::Binary)),
|
||||
Type::List(Box::new(Type::Binary)),
|
||||
),
|
||||
])
|
||||
.allow_variants_without_examples(true)
|
||||
.required("bits", SyntaxShape::Int, "number of bits to rotate left")
|
||||
.switch(
|
||||
"signed",
|
||||
|
@ -33,7 +53,7 @@ impl Command for BitsRol {
|
|||
)
|
||||
.named(
|
||||
"number-bytes",
|
||||
SyntaxShape::String,
|
||||
SyntaxShape::Int,
|
||||
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
|
||||
Some('n'),
|
||||
)
|
||||
|
@ -41,7 +61,7 @@ impl Command for BitsRol {
|
|||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Bitwise rotate left for ints."
|
||||
"Bitwise rotate left for ints or binary values."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
|
@ -58,27 +78,22 @@ impl Command for BitsRol {
|
|||
let head = call.head;
|
||||
let bits: usize = call.req(engine_state, stack, 0)?;
|
||||
let signed = call.has_flag(engine_state, stack, "signed")?;
|
||||
let number_bytes: Option<Spanned<String>> =
|
||||
let number_bytes: Option<Spanned<usize>> =
|
||||
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||
let bytes_len = get_number_bytes(number_bytes.as_ref());
|
||||
if let NumberBytes::Invalid = bytes_len {
|
||||
if let Some(val) = number_bytes {
|
||||
return Err(ShellError::UnsupportedInput {
|
||||
msg: "Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||
input: "value originates from here".to_string(),
|
||||
msg_span: head,
|
||||
input_span: val.span,
|
||||
});
|
||||
}
|
||||
}
|
||||
let number_size = get_number_bytes(number_bytes, head)?;
|
||||
|
||||
// This doesn't match explicit nulls
|
||||
if matches!(input, PipelineData::Empty) {
|
||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||
}
|
||||
input.map(
|
||||
move |value| operate(value, bits, head, signed, bytes_len),
|
||||
engine_state.ctrlc.clone(),
|
||||
)
|
||||
|
||||
let args = Arguments {
|
||||
signed,
|
||||
number_size,
|
||||
bits,
|
||||
};
|
||||
|
||||
operate(action, args, input, head, engine_state.ctrlc.clone())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
|
@ -96,61 +111,82 @@ impl Command for BitsRol {
|
|||
Span::test_data(),
|
||||
)),
|
||||
},
|
||||
Example {
|
||||
description: "rotate left binary data",
|
||||
example: "0x[c0 ff ee] | bits rol 10",
|
||||
result: Some(Value::binary(vec![0xff, 0xbb, 0x03], Span::test_data())),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn get_rotate_left<T: Display + PrimInt>(val: T, bits: u32, span: Span) -> Value
|
||||
where
|
||||
i64: std::convert::TryFrom<T>,
|
||||
{
|
||||
let rotate_result = i64::try_from(val.rotate_left(bits));
|
||||
match rotate_result {
|
||||
Ok(val) => Value::int(val, span),
|
||||
Err(_) => Value::error(
|
||||
ShellError::GenericError {
|
||||
error: "Rotate left result beyond the range of 64 bit signed number".into(),
|
||||
msg: format!(
|
||||
"{val} of the specified number of bytes rotate left {bits} bits exceed limit"
|
||||
),
|
||||
span: Some(span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
},
|
||||
span,
|
||||
),
|
||||
}
|
||||
}
|
||||
fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
let Arguments {
|
||||
signed,
|
||||
number_size,
|
||||
bits,
|
||||
} = *args;
|
||||
|
||||
fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
||||
let span = value.span();
|
||||
match value {
|
||||
match input {
|
||||
Value::Int { val, .. } => {
|
||||
use InputNumType::*;
|
||||
// let bits = (((bits % 64) + 64) % 64) as u32;
|
||||
let val = *val;
|
||||
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, bits, span),
|
||||
}
|
||||
let input_num_type = get_input_num_type(val, signed, number_size);
|
||||
|
||||
let int = match input_num_type {
|
||||
One => (val as u8).rotate_left(bits) as i64,
|
||||
Two => (val as u16).rotate_left(bits) as i64,
|
||||
Four => (val as u32).rotate_left(bits) as i64,
|
||||
Eight => {
|
||||
let Ok(i) = i64::try_from((val as u64).rotate_left(bits)) else {
|
||||
return Value::error(
|
||||
ShellError::GenericError {
|
||||
error: "result out of range for specified number".into(),
|
||||
msg: format!(
|
||||
"rotating left by {bits} is out of range for the value {val}"
|
||||
),
|
||||
span: Some(span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
},
|
||||
span,
|
||||
);
|
||||
};
|
||||
i
|
||||
}
|
||||
SignedOne => (val as i8).rotate_left(bits) as i64,
|
||||
SignedTwo => (val as i16).rotate_left(bits) as i64,
|
||||
SignedFour => (val as i32).rotate_left(bits) as i64,
|
||||
SignedEight => val.rotate_left(bits),
|
||||
};
|
||||
|
||||
Value::int(int, span)
|
||||
}
|
||||
Value::Binary { val, .. } => {
|
||||
let byte_shift = bits / 8;
|
||||
let bit_rotate = bits % 8;
|
||||
|
||||
let mut bytes = val
|
||||
.iter()
|
||||
.copied()
|
||||
.circular_tuple_windows::<(u8, u8)>()
|
||||
.map(|(lhs, rhs)| (lhs << bit_rotate) | (rhs >> (8 - bit_rotate)))
|
||||
.collect::<Vec<u8>>();
|
||||
bytes.rotate_left(byte_shift);
|
||||
|
||||
Value::binary(bytes, span)
|
||||
}
|
||||
// Propagate errors by explicitly matching them before the final case.
|
||||
Value::Error { .. } => value,
|
||||
Value::Error { .. } => input.clone(),
|
||||
other => Value::error(
|
||||
ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "int".into(),
|
||||
exp_input_type: "int or binary".into(),
|
||||
wrong_type: other.get_type().to_string(),
|
||||
dst_span: head,
|
||||
dst_span: span,
|
||||
src_span: other.span(),
|
||||
},
|
||||
head,
|
||||
span,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,26 @@
|
|||
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||
use itertools::Itertools;
|
||||
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::ast::CellPath;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::Spanned;
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||
};
|
||||
use num_traits::int::PrimInt;
|
||||
use std::fmt::Display;
|
||||
|
||||
struct Arguments {
|
||||
signed: bool,
|
||||
bits: usize,
|
||||
number_size: NumberBytes,
|
||||
}
|
||||
|
||||
impl CmdArgument for Arguments {
|
||||
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BitsRor;
|
||||
|
@ -20,11 +34,17 @@ impl Command for BitsRor {
|
|||
Signature::build("bits ror")
|
||||
.input_output_types(vec![
|
||||
(Type::Int, Type::Int),
|
||||
(Type::Binary, Type::Binary),
|
||||
(
|
||||
Type::List(Box::new(Type::Int)),
|
||||
Type::List(Box::new(Type::Int)),
|
||||
),
|
||||
(
|
||||
Type::List(Box::new(Type::Binary)),
|
||||
Type::List(Box::new(Type::Binary)),
|
||||
),
|
||||
])
|
||||
.allow_variants_without_examples(true)
|
||||
.required("bits", SyntaxShape::Int, "number of bits to rotate right")
|
||||
.switch(
|
||||
"signed",
|
||||
|
@ -33,7 +53,7 @@ impl Command for BitsRor {
|
|||
)
|
||||
.named(
|
||||
"number-bytes",
|
||||
SyntaxShape::String,
|
||||
SyntaxShape::Int,
|
||||
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
|
||||
Some('n'),
|
||||
)
|
||||
|
@ -41,7 +61,7 @@ impl Command for BitsRor {
|
|||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Bitwise rotate right for ints."
|
||||
"Bitwise rotate right for ints or binary values."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
|
@ -58,103 +78,119 @@ impl Command for BitsRor {
|
|||
let head = call.head;
|
||||
let bits: usize = call.req(engine_state, stack, 0)?;
|
||||
let signed = call.has_flag(engine_state, stack, "signed")?;
|
||||
let number_bytes: Option<Spanned<String>> =
|
||||
let number_bytes: Option<Spanned<usize>> =
|
||||
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||
let bytes_len = get_number_bytes(number_bytes.as_ref());
|
||||
if let NumberBytes::Invalid = bytes_len {
|
||||
if let Some(val) = number_bytes {
|
||||
return Err(ShellError::UnsupportedInput {
|
||||
msg: "Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||
input: "value originates from here".to_string(),
|
||||
msg_span: head,
|
||||
input_span: val.span,
|
||||
});
|
||||
}
|
||||
}
|
||||
let number_size = get_number_bytes(number_bytes, head)?;
|
||||
|
||||
// This doesn't match explicit nulls
|
||||
if matches!(input, PipelineData::Empty) {
|
||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||
}
|
||||
input.map(
|
||||
move |value| operate(value, bits, head, signed, bytes_len),
|
||||
engine_state.ctrlc.clone(),
|
||||
)
|
||||
|
||||
let args = Arguments {
|
||||
signed,
|
||||
number_size,
|
||||
bits,
|
||||
};
|
||||
|
||||
operate(action, args, input, head, engine_state.ctrlc.clone())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Rotate right a number with 60 bits",
|
||||
example: "17 | bits ror 60",
|
||||
result: Some(Value::test_int(272)),
|
||||
description: "rotate right a number with 2 bits",
|
||||
example: "17 | bits ror 2",
|
||||
result: Some(Value::test_int(68)),
|
||||
},
|
||||
Example {
|
||||
description: "Rotate right a list of numbers of one byte",
|
||||
example: "[15 33 92] | bits ror 2 --number-bytes '1'",
|
||||
description: "rotate right a list of numbers of two bytes",
|
||||
example: "[15 33 92] | bits ror 2 --number-bytes 2",
|
||||
result: Some(Value::list(
|
||||
vec![
|
||||
Value::test_int(195),
|
||||
Value::test_int(72),
|
||||
Value::test_int(49155),
|
||||
Value::test_int(16392),
|
||||
Value::test_int(23),
|
||||
],
|
||||
Span::test_data(),
|
||||
)),
|
||||
},
|
||||
Example {
|
||||
description: "rotate right binary data",
|
||||
example: "0x[ff bb 03] | bits ror 10",
|
||||
result: Some(Value::binary(vec![0xc0, 0xff, 0xee], Span::test_data())),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn get_rotate_right<T: Display + PrimInt>(val: T, bits: u32, span: Span) -> Value
|
||||
where
|
||||
i64: std::convert::TryFrom<T>,
|
||||
{
|
||||
let rotate_result = i64::try_from(val.rotate_right(bits));
|
||||
match rotate_result {
|
||||
Ok(val) => Value::int(val, span),
|
||||
Err(_) => Value::error(
|
||||
ShellError::GenericError {
|
||||
error: "Rotate right result beyond the range of 64 bit signed number".into(),
|
||||
msg: format!(
|
||||
"{val} of the specified number of bytes rotate right {bits} bits exceed limit"
|
||||
),
|
||||
span: Some(span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
},
|
||||
span,
|
||||
),
|
||||
}
|
||||
}
|
||||
fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
let Arguments {
|
||||
signed,
|
||||
number_size,
|
||||
bits,
|
||||
} = *args;
|
||||
|
||||
fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
||||
let span = value.span();
|
||||
match value {
|
||||
match input {
|
||||
Value::Int { val, .. } => {
|
||||
use InputNumType::*;
|
||||
// let bits = (((bits % 64) + 64) % 64) as u32;
|
||||
let val = *val;
|
||||
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, bits, span),
|
||||
}
|
||||
let input_num_type = get_input_num_type(val, signed, number_size);
|
||||
|
||||
let int = match input_num_type {
|
||||
One => (val as u8).rotate_right(bits) as i64,
|
||||
Two => (val as u16).rotate_right(bits) as i64,
|
||||
Four => (val as u32).rotate_right(bits) as i64,
|
||||
Eight => {
|
||||
let Ok(i) = i64::try_from((val as u64).rotate_right(bits)) else {
|
||||
return Value::error(
|
||||
ShellError::GenericError {
|
||||
error: "result out of range for specified number".into(),
|
||||
msg: format!(
|
||||
"rotating right by {bits} is out of range for the value {val}"
|
||||
),
|
||||
span: Some(span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
},
|
||||
span,
|
||||
);
|
||||
};
|
||||
i
|
||||
}
|
||||
SignedOne => (val as i8).rotate_right(bits) as i64,
|
||||
SignedTwo => (val as i16).rotate_right(bits) as i64,
|
||||
SignedFour => (val as i32).rotate_right(bits) as i64,
|
||||
SignedEight => val.rotate_right(bits),
|
||||
};
|
||||
|
||||
Value::int(int, span)
|
||||
}
|
||||
Value::Binary { val, .. } => {
|
||||
let byte_shift = bits / 8;
|
||||
let bit_rotate = bits % 8;
|
||||
|
||||
let mut bytes = val
|
||||
.iter()
|
||||
.copied()
|
||||
.circular_tuple_windows::<(u8, u8)>()
|
||||
.map(|(lhs, rhs)| (lhs >> bit_rotate) | (rhs << (8 - bit_rotate)))
|
||||
.collect::<Vec<u8>>();
|
||||
bytes.rotate_right(byte_shift);
|
||||
|
||||
Value::binary(bytes, span)
|
||||
}
|
||||
// Propagate errors by explicitly matching them before the final case.
|
||||
Value::Error { .. } => value,
|
||||
Value::Error { .. } => input.clone(),
|
||||
other => Value::error(
|
||||
ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "int".into(),
|
||||
exp_input_type: "int or binary".into(),
|
||||
wrong_type: other.get_type().to_string(),
|
||||
dst_span: head,
|
||||
dst_span: span,
|
||||
src_span: other.span(),
|
||||
},
|
||||
head,
|
||||
span,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,27 @@
|
|||
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||
use itertools::Itertools;
|
||||
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::ast::CellPath;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::Spanned;
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||
};
|
||||
use num_traits::CheckedShl;
|
||||
use std::fmt::Display;
|
||||
use std::iter;
|
||||
|
||||
struct Arguments {
|
||||
signed: bool,
|
||||
bits: usize,
|
||||
number_size: NumberBytes,
|
||||
}
|
||||
|
||||
impl CmdArgument for Arguments {
|
||||
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BitsShl;
|
||||
|
@ -20,11 +35,17 @@ impl Command for BitsShl {
|
|||
Signature::build("bits shl")
|
||||
.input_output_types(vec![
|
||||
(Type::Int, Type::Int),
|
||||
(Type::Binary, Type::Binary),
|
||||
(
|
||||
Type::List(Box::new(Type::Int)),
|
||||
Type::List(Box::new(Type::Int)),
|
||||
),
|
||||
(
|
||||
Type::List(Box::new(Type::Binary)),
|
||||
Type::List(Box::new(Type::Binary)),
|
||||
),
|
||||
])
|
||||
.allow_variants_without_examples(true)
|
||||
.required("bits", SyntaxShape::Int, "number of bits to shift left")
|
||||
.switch(
|
||||
"signed",
|
||||
|
@ -33,7 +54,7 @@ impl Command for BitsShl {
|
|||
)
|
||||
.named(
|
||||
"number-bytes",
|
||||
SyntaxShape::String,
|
||||
SyntaxShape::Int,
|
||||
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
|
||||
Some('n'),
|
||||
)
|
||||
|
@ -41,7 +62,7 @@ impl Command for BitsShl {
|
|||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Bitwise shift left for ints."
|
||||
"Bitwise shift left for ints or binary values."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
|
@ -58,27 +79,22 @@ impl Command for BitsShl {
|
|||
let head = call.head;
|
||||
let bits: usize = call.req(engine_state, stack, 0)?;
|
||||
let signed = call.has_flag(engine_state, stack, "signed")?;
|
||||
let number_bytes: Option<Spanned<String>> =
|
||||
let number_bytes: Option<Spanned<usize>> =
|
||||
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||
let bytes_len = get_number_bytes(number_bytes.as_ref());
|
||||
if let NumberBytes::Invalid = bytes_len {
|
||||
if let Some(val) = number_bytes {
|
||||
return Err(ShellError::UnsupportedInput {
|
||||
msg: "Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||
input: "value originates from here".to_string(),
|
||||
msg_span: head,
|
||||
input_span: val.span,
|
||||
});
|
||||
}
|
||||
}
|
||||
let number_size = get_number_bytes(number_bytes, head)?;
|
||||
|
||||
// This doesn't match explicit nulls
|
||||
if matches!(input, PipelineData::Empty) {
|
||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||
}
|
||||
input.map(
|
||||
move |value| operate(value, bits, head, signed, bytes_len),
|
||||
engine_state.ctrlc.clone(),
|
||||
)
|
||||
|
||||
let args = Arguments {
|
||||
signed,
|
||||
number_size,
|
||||
bits,
|
||||
};
|
||||
|
||||
operate(action, args, input, head, engine_state.ctrlc.clone())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
|
@ -86,17 +102,17 @@ impl Command for BitsShl {
|
|||
Example {
|
||||
description: "Shift left a number by 7 bits",
|
||||
example: "2 | bits shl 7",
|
||||
result: Some(Value::test_int(256)),
|
||||
result: Some(Value::test_int(0)),
|
||||
},
|
||||
Example {
|
||||
description: "Shift left a number with 1 byte by 7 bits",
|
||||
example: "2 | bits shl 7 --number-bytes '1'",
|
||||
result: Some(Value::test_int(0)),
|
||||
description: "Shift left a number with 2 byte by 7 bits",
|
||||
example: "2 | bits shl 7 --number-bytes 2",
|
||||
result: Some(Value::test_int(256)),
|
||||
},
|
||||
Example {
|
||||
description: "Shift left a signed number by 1 bit",
|
||||
example: "0x7F | bits shl 1 --signed",
|
||||
result: Some(Value::test_int(254)),
|
||||
result: Some(Value::test_int(-2)),
|
||||
},
|
||||
Example {
|
||||
description: "Shift left a list of numbers",
|
||||
|
@ -106,75 +122,88 @@ impl Command for BitsShl {
|
|||
Span::test_data(),
|
||||
)),
|
||||
},
|
||||
Example {
|
||||
description: "Shift left a binary value",
|
||||
example: "0x[4f f4] | bits shl 4",
|
||||
result: Some(Value::binary(vec![0xff, 0x40], Span::test_data())),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn get_shift_left<T: CheckedShl + Display + Copy>(val: T, bits: u32, span: Span) -> Value
|
||||
where
|
||||
i64: std::convert::TryFrom<T>,
|
||||
{
|
||||
match val.checked_shl(bits) {
|
||||
Some(val) => {
|
||||
let shift_result = i64::try_from(val);
|
||||
match shift_result {
|
||||
Ok(val) => Value::int( val, span ),
|
||||
Err(_) => Value::error(
|
||||
ShellError::GenericError {
|
||||
error:"Shift left result beyond the range of 64 bit signed number".into(),
|
||||
msg: format!(
|
||||
"{val} of the specified number of bytes shift left {bits} bits exceed limit"
|
||||
),
|
||||
span: Some(span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
},
|
||||
span,
|
||||
),
|
||||
}
|
||||
}
|
||||
None => Value::error(
|
||||
ShellError::GenericError {
|
||||
error: "Shift left failed".into(),
|
||||
msg: format!("{val} shift left {bits} bits failed, you may shift too many bits"),
|
||||
span: Some(span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
},
|
||||
span,
|
||||
),
|
||||
}
|
||||
}
|
||||
fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
let Arguments {
|
||||
signed,
|
||||
number_size,
|
||||
bits,
|
||||
} = *args;
|
||||
|
||||
fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
||||
let span = value.span();
|
||||
match value {
|
||||
match input {
|
||||
Value::Int { val, .. } => {
|
||||
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_shift_left(val as u8, bits, span),
|
||||
Two => get_shift_left(val as u16, bits, span),
|
||||
Four => get_shift_left(val as u32, bits, span),
|
||||
Eight => get_shift_left(val as u64, bits, span),
|
||||
SignedOne => get_shift_left(val as i8, bits, span),
|
||||
SignedTwo => get_shift_left(val as i16, bits, span),
|
||||
SignedFour => get_shift_left(val as i32, bits, span),
|
||||
SignedEight => get_shift_left(val, bits, span),
|
||||
}
|
||||
let val = *val;
|
||||
let bits = bits as u64;
|
||||
|
||||
let input_num_type = get_input_num_type(val, signed, number_size);
|
||||
let int = match input_num_type {
|
||||
One => ((val as u8) << bits) as i64,
|
||||
Two => ((val as u16) << bits) as i64,
|
||||
Four => ((val as u32) << bits) as i64,
|
||||
Eight => {
|
||||
let Ok(i) = i64::try_from((val as u64) << bits) else {
|
||||
return Value::error(
|
||||
ShellError::GenericError {
|
||||
error: "result out of range for specified number".into(),
|
||||
msg: format!(
|
||||
"shifting left by {bits} is out of range for the value {val}"
|
||||
),
|
||||
span: Some(span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
},
|
||||
span,
|
||||
);
|
||||
};
|
||||
i
|
||||
}
|
||||
SignedOne => ((val as i8) << bits) as i64,
|
||||
SignedTwo => ((val as i16) << bits) as i64,
|
||||
SignedFour => ((val as i32) << bits) as i64,
|
||||
SignedEight => val << bits,
|
||||
};
|
||||
|
||||
Value::int(int, span)
|
||||
}
|
||||
Value::Binary { val, .. } => {
|
||||
let byte_shift = bits / 8;
|
||||
let bit_shift = bits % 8;
|
||||
|
||||
use itertools::Position::*;
|
||||
let bytes = val
|
||||
.iter()
|
||||
.copied()
|
||||
.skip(byte_shift)
|
||||
.circular_tuple_windows::<(u8, u8)>()
|
||||
.with_position()
|
||||
.map(|(pos, (lhs, rhs))| match pos {
|
||||
Last | Only => lhs << bit_shift,
|
||||
_ => (lhs << bit_shift) | (rhs >> bit_shift),
|
||||
})
|
||||
.chain(iter::repeat(0).take(byte_shift))
|
||||
.collect::<Vec<u8>>();
|
||||
|
||||
Value::binary(bytes, span)
|
||||
}
|
||||
// Propagate errors by explicitly matching them before the final case.
|
||||
Value::Error { .. } => value,
|
||||
Value::Error { .. } => input.clone(),
|
||||
other => Value::error(
|
||||
ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "int".into(),
|
||||
exp_input_type: "int or binary".into(),
|
||||
wrong_type: other.get_type().to_string(),
|
||||
dst_span: head,
|
||||
dst_span: span,
|
||||
src_span: other.span(),
|
||||
},
|
||||
head,
|
||||
span,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,27 @@
|
|||
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||
use itertools::Itertools;
|
||||
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::ast::CellPath;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::Spanned;
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||
};
|
||||
use num_traits::CheckedShr;
|
||||
use std::fmt::Display;
|
||||
use std::iter;
|
||||
|
||||
struct Arguments {
|
||||
signed: bool,
|
||||
bits: usize,
|
||||
number_size: NumberBytes,
|
||||
}
|
||||
|
||||
impl CmdArgument for Arguments {
|
||||
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BitsShr;
|
||||
|
@ -20,11 +35,17 @@ impl Command for BitsShr {
|
|||
Signature::build("bits shr")
|
||||
.input_output_types(vec![
|
||||
(Type::Int, Type::Int),
|
||||
(Type::Binary, Type::Binary),
|
||||
(
|
||||
Type::List(Box::new(Type::Int)),
|
||||
Type::List(Box::new(Type::Int)),
|
||||
),
|
||||
(
|
||||
Type::List(Box::new(Type::Binary)),
|
||||
Type::List(Box::new(Type::Binary)),
|
||||
),
|
||||
])
|
||||
.allow_variants_without_examples(true)
|
||||
.required("bits", SyntaxShape::Int, "number of bits to shift right")
|
||||
.switch(
|
||||
"signed",
|
||||
|
@ -33,7 +54,7 @@ impl Command for BitsShr {
|
|||
)
|
||||
.named(
|
||||
"number-bytes",
|
||||
SyntaxShape::String,
|
||||
SyntaxShape::Int,
|
||||
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
|
||||
Some('n'),
|
||||
)
|
||||
|
@ -41,7 +62,7 @@ impl Command for BitsShr {
|
|||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Bitwise shift right for ints."
|
||||
"Bitwise shift right for ints or binary values."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
|
@ -58,27 +79,22 @@ impl Command for BitsShr {
|
|||
let head = call.head;
|
||||
let bits: usize = call.req(engine_state, stack, 0)?;
|
||||
let signed = call.has_flag(engine_state, stack, "signed")?;
|
||||
let number_bytes: Option<Spanned<String>> =
|
||||
let number_bytes: Option<Spanned<usize>> =
|
||||
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||
let bytes_len = get_number_bytes(number_bytes.as_ref());
|
||||
if let NumberBytes::Invalid = bytes_len {
|
||||
if let Some(val) = number_bytes {
|
||||
return Err(ShellError::UnsupportedInput {
|
||||
msg: "Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||
input: "value originates from here".to_string(),
|
||||
msg_span: head,
|
||||
input_span: val.span,
|
||||
});
|
||||
}
|
||||
}
|
||||
let number_size = get_number_bytes(number_bytes, head)?;
|
||||
|
||||
// This doesn't match explicit nulls
|
||||
if matches!(input, PipelineData::Empty) {
|
||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||
}
|
||||
input.map(
|
||||
move |value| operate(value, bits, head, signed, bytes_len),
|
||||
engine_state.ctrlc.clone(),
|
||||
)
|
||||
|
||||
let args = Arguments {
|
||||
signed,
|
||||
number_size,
|
||||
bits,
|
||||
};
|
||||
|
||||
operate(action, args, input, head, engine_state.ctrlc.clone())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
|
@ -96,75 +112,75 @@ impl Command for BitsShr {
|
|||
Span::test_data(),
|
||||
)),
|
||||
},
|
||||
Example {
|
||||
description: "Shift right a binary value",
|
||||
example: "0x[4f f4] | bits shr 4",
|
||||
result: Some(Value::binary(vec![0x04, 0xff], Span::test_data())),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn get_shift_right<T: CheckedShr + Display + Copy>(val: T, bits: u32, span: Span) -> Value
|
||||
where
|
||||
i64: std::convert::TryFrom<T>,
|
||||
{
|
||||
match val.checked_shr(bits) {
|
||||
Some(val) => {
|
||||
let shift_result = i64::try_from(val);
|
||||
match shift_result {
|
||||
Ok(val) => Value::int( val, span ),
|
||||
Err(_) => Value::error(
|
||||
ShellError::GenericError {
|
||||
error: "Shift right result beyond the range of 64 bit signed number".into(),
|
||||
msg: format!(
|
||||
"{val} of the specified number of bytes shift right {bits} bits exceed limit"
|
||||
),
|
||||
span: Some(span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
},
|
||||
span,
|
||||
),
|
||||
}
|
||||
}
|
||||
None => Value::error(
|
||||
ShellError::GenericError {
|
||||
error: "Shift right failed".into(),
|
||||
msg: format!("{val} shift right {bits} bits failed, you may shift too many bits"),
|
||||
span: Some(span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
},
|
||||
span,
|
||||
),
|
||||
}
|
||||
}
|
||||
fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
let Arguments {
|
||||
signed,
|
||||
number_size,
|
||||
bits,
|
||||
} = *args;
|
||||
|
||||
fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
||||
let span = value.span();
|
||||
match value {
|
||||
match input {
|
||||
Value::Int { val, .. } => {
|
||||
use InputNumType::*;
|
||||
// let bits = (((bits % 64) + 64) % 64) as u32;
|
||||
let val = *val;
|
||||
let bits = bits as u32;
|
||||
let input_type = get_input_num_type(val, signed, number_size);
|
||||
match input_type {
|
||||
One => get_shift_right(val as u8, bits, span),
|
||||
Two => get_shift_right(val as u16, bits, span),
|
||||
Four => get_shift_right(val as u32, bits, span),
|
||||
Eight => get_shift_right(val as u64, bits, span),
|
||||
SignedOne => get_shift_right(val as i8, bits, span),
|
||||
SignedTwo => get_shift_right(val as i16, bits, span),
|
||||
SignedFour => get_shift_right(val as i32, bits, span),
|
||||
SignedEight => get_shift_right(val, bits, span),
|
||||
}
|
||||
let input_num_type = get_input_num_type(val, signed, number_size);
|
||||
|
||||
let int = match input_num_type {
|
||||
One => ((val as u8) >> bits) as i64,
|
||||
Two => ((val as u16) >> bits) as i64,
|
||||
Four => ((val as u32) >> bits) as i64,
|
||||
Eight => ((val as u64) >> bits) as i64,
|
||||
SignedOne => ((val as i8) >> bits) as i64,
|
||||
SignedTwo => ((val as i16) >> bits) as i64,
|
||||
SignedFour => ((val as i32) >> bits) as i64,
|
||||
SignedEight => val >> bits,
|
||||
};
|
||||
|
||||
Value::int(int, span)
|
||||
}
|
||||
Value::Binary { val, .. } => {
|
||||
let byte_shift = bits / 8;
|
||||
let bit_shift = bits % 8;
|
||||
|
||||
let len = val.len();
|
||||
use itertools::Position::*;
|
||||
let bytes = iter::repeat(0)
|
||||
.take(byte_shift)
|
||||
.chain(
|
||||
val.iter()
|
||||
.copied()
|
||||
.circular_tuple_windows::<(u8, u8)>()
|
||||
.with_position()
|
||||
.map(|(pos, (lhs, rhs))| match pos {
|
||||
First | Only => lhs >> bit_shift,
|
||||
_ => (lhs >> bit_shift) | (rhs << bit_shift),
|
||||
})
|
||||
.take(len - byte_shift),
|
||||
)
|
||||
.collect::<Vec<u8>>();
|
||||
|
||||
Value::binary(bytes, span)
|
||||
}
|
||||
// Propagate errors by explicitly matching them before the final case.
|
||||
Value::Error { .. } => value,
|
||||
Value::Error { .. } => input.clone(),
|
||||
other => Value::error(
|
||||
ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "int".into(),
|
||||
exp_input_type: "int or binary".into(),
|
||||
wrong_type: other.get_type().to_string(),
|
||||
dst_span: head,
|
||||
dst_span: span,
|
||||
src_span: other.span(),
|
||||
},
|
||||
head,
|
||||
span,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use super::binary_op;
|
||||
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, SyntaxShape, Type, Value,
|
||||
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -17,17 +18,33 @@ impl Command for BitsXor {
|
|||
Signature::build("bits xor")
|
||||
.input_output_types(vec![
|
||||
(Type::Int, Type::Int),
|
||||
(Type::Binary, Type::Binary),
|
||||
(
|
||||
Type::List(Box::new(Type::Int)),
|
||||
Type::List(Box::new(Type::Int)),
|
||||
),
|
||||
(
|
||||
Type::List(Box::new(Type::Binary)),
|
||||
Type::List(Box::new(Type::Binary)),
|
||||
),
|
||||
])
|
||||
.required("target", SyntaxShape::Int, "target int to perform bit xor")
|
||||
.allow_variants_without_examples(true)
|
||||
.required(
|
||||
"target",
|
||||
SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::Int]),
|
||||
"right-hand side of the operation",
|
||||
)
|
||||
.named(
|
||||
"endian",
|
||||
SyntaxShape::String,
|
||||
"byte encode endian, available options: native(default), little, big",
|
||||
Some('e'),
|
||||
)
|
||||
.category(Category::Bits)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Performs bitwise xor for ints."
|
||||
"Performs bitwise xor for ints or binary values."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
|
@ -42,13 +59,32 @@ impl Command for BitsXor {
|
|||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let target: i64 = call.req(engine_state, stack, 0)?;
|
||||
let target: Value = call.req(engine_state, stack, 0)?;
|
||||
let endian = call.get_flag::<Spanned<String>>(engine_state, stack, "endian")?;
|
||||
|
||||
let little_endian = if let Some(endian) = endian {
|
||||
match endian.item.as_str() {
|
||||
"native" => cfg!(target_endian = "little"),
|
||||
"little" => true,
|
||||
"big" => false,
|
||||
_ => {
|
||||
return Err(ShellError::TypeMismatch {
|
||||
err_message: "Endian must be one of native, little, big".to_string(),
|
||||
span: endian.span,
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cfg!(target_endian = "little")
|
||||
};
|
||||
|
||||
// This doesn't match explicit nulls
|
||||
if matches!(input, PipelineData::Empty) {
|
||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||
}
|
||||
|
||||
input.map(
|
||||
move |value| operate(value, target, head),
|
||||
move |value| binary_op(&value, &target, little_endian, |(l, r)| l ^ r, head),
|
||||
engine_state.ctrlc.clone(),
|
||||
)
|
||||
}
|
||||
|
@ -61,35 +97,34 @@ impl Command for BitsXor {
|
|||
result: Some(Value::test_int(0)),
|
||||
},
|
||||
Example {
|
||||
description: "Apply logical xor to a list of numbers",
|
||||
description: "Apply bitwise xor to a list of numbers",
|
||||
example: "[8 3 2] | bits xor 2",
|
||||
result: Some(Value::list(
|
||||
vec![Value::test_int(10), Value::test_int(1), Value::test_int(0)],
|
||||
Span::test_data(),
|
||||
)),
|
||||
result: Some(Value::test_list(vec![
|
||||
Value::test_int(10),
|
||||
Value::test_int(1),
|
||||
Value::test_int(0),
|
||||
])),
|
||||
},
|
||||
Example {
|
||||
description: "Apply bitwise xor to binary data",
|
||||
example: "0x[ca fe] | bits xor 0x[ba be]",
|
||||
result: Some(Value::test_binary(vec![0x70, 0x40])),
|
||||
},
|
||||
Example {
|
||||
description:
|
||||
"Apply bitwise xor to binary data of varying lengths with specified endianness",
|
||||
example: "0x[ca fe] | bits xor 0x[aa] --endian big",
|
||||
result: Some(Value::test_binary(vec![0xca, 0x54])),
|
||||
},
|
||||
Example {
|
||||
description: "Apply bitwise xor to input binary data smaller than the operand",
|
||||
example: "0x[ff] | bits xor 0x[12 34 56] --endian little",
|
||||
result: Some(Value::test_binary(vec![0xed, 0x34, 0x56])),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn operate(value: Value, target: i64, head: Span) -> Value {
|
||||
let span = value.span();
|
||||
match value {
|
||||
Value::Int { val, .. } => Value::int(val ^ target, span),
|
||||
// Propagate errors by explicitly matching them before the final case.
|
||||
Value::Error { .. } => value,
|
||||
other => Value::error(
|
||||
ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "int".into(),
|
||||
wrong_type: other.get_type().to_string(),
|
||||
dst_span: head,
|
||||
src_span: other.span(),
|
||||
},
|
||||
head,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
|
|
@ -65,7 +65,7 @@ fn bits_shift_left_negative() -> TestResult {
|
|||
fn bits_shift_left_list() -> TestResult {
|
||||
run_test(
|
||||
"[1 2 7 32 9 10] | bits shl 3 | str join '.'",
|
||||
"8.16.56.256.72.80",
|
||||
"8.16.56.0.72.80",
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -101,7 +101,7 @@ fn bits_rotate_left_negative() -> TestResult {
|
|||
fn bits_rotate_left_list() -> TestResult {
|
||||
run_test(
|
||||
"[1 2 7 32 9 10] | bits rol 3 | str join '.'",
|
||||
"8.16.56.256.72.80",
|
||||
"8.16.56.1.72.80",
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -119,6 +119,6 @@ fn bits_rotate_right_negative() -> TestResult {
|
|||
fn bits_rotate_right_list() -> TestResult {
|
||||
run_test(
|
||||
"[1 2 7 32 23 10] | bits ror 60 | str join '.'",
|
||||
"16.32.112.512.368.160",
|
||||
"16.32.112.2.113.160",
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue