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:
moonlander 2024-02-28 12:43:50 +00:00 committed by GitHub
parent 7b95e37bbe
commit d3895d71db
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 742 additions and 428 deletions

1
Cargo.lock generated
View file

@ -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",

View file

@ -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"]

View file

@ -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::*;

View file

@ -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,
),
}
}

View file

@ -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,
),
}
}

View file

@ -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::*;

View file

@ -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,
),
}
}

View file

@ -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,
),
}
}

View file

@ -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,
),
}
}

View file

@ -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,
),
}
}

View file

@ -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::*;

View file

@ -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",
)
}