mirror of
https://github.com/nushell/nushell.git
synced 2025-06-05 01:26:42 +02:00
# Description The `ShellError` enum at the moment is kind of messy. Many variants are basic tuple structs where you always have to reference the implementation with its macro invocation to know which field serves which purpose. Furthermore we have both variants that are kind of redundant or either overly broad to be useful for the user to match on or overly specific with few uses. So I set out to start fixing the lacking documentation and naming to make it feasible to critically review the individual usages and fix those. Furthermore we can decide to join or split up variants that don't seem to be fit for purpose. Feel free to add review comments if you spot inconsistent use of `ShellError` variants. - Name fields on `ShellError::OperatorOverflow` - Name fields on `ShellError::PipelineMismatch` - Add doc to `ShellError::OnlySupportsThisInputType` - Name `ShellError::OnlySupportsThisInputType` - Name field on `ShellError::PipelineEmpty` - Comment about issues with `TypeMismatch*` - Fix a few `exp_input_type`s - Name fields on `ShellError::InvalidRange` # User-Facing Changes (None now, end goal more explicit and consistent error messages) # Tests + Formatting (No additional tests needed so far)
184 lines
6.2 KiB
Rust
184 lines
6.2 KiB
Rust
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
|
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, Spanned, SyntaxShape, Type, Value,
|
|
};
|
|
use num_traits::CheckedShl;
|
|
use std::fmt::Display;
|
|
|
|
#[derive(Clone)]
|
|
pub struct SubCommand;
|
|
|
|
impl Command for SubCommand {
|
|
fn name(&self) -> &str {
|
|
"bits shl"
|
|
}
|
|
|
|
fn signature(&self) -> Signature {
|
|
Signature::build("bits shl")
|
|
.input_output_types(vec![(Type::Int, Type::Int)])
|
|
.vectorizes_over_list(true)
|
|
.required("bits", SyntaxShape::Int, "number of bits to shift left")
|
|
.switch(
|
|
"signed",
|
|
"always treat input number as a signed number",
|
|
Some('s'),
|
|
)
|
|
.named(
|
|
"number-bytes",
|
|
SyntaxShape::String,
|
|
"the word size in number of bytes, it can be 1, 2, 4, 8, auto, default value `8`",
|
|
Some('n'),
|
|
)
|
|
.category(Category::Bits)
|
|
}
|
|
|
|
fn usage(&self) -> &str {
|
|
"Bitwise shift left for integers."
|
|
}
|
|
|
|
fn search_terms(&self) -> Vec<&str> {
|
|
vec!["shift left"]
|
|
}
|
|
|
|
fn run(
|
|
&self,
|
|
engine_state: &EngineState,
|
|
stack: &mut Stack,
|
|
call: &Call,
|
|
input: PipelineData,
|
|
) -> Result<PipelineData, ShellError> {
|
|
let head = call.head;
|
|
let bits: usize = call.req(engine_state, stack, 0)?;
|
|
let signed = call.has_flag("signed");
|
|
let number_bytes: Option<Spanned<String>> =
|
|
call.get_flag(engine_state, stack, "number-bytes")?;
|
|
let bytes_len = get_number_bytes(&number_bytes);
|
|
if let NumberBytes::Invalid = bytes_len {
|
|
if let Some(val) = number_bytes {
|
|
return Err(ShellError::UnsupportedInput(
|
|
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
|
"value originates from here".to_string(),
|
|
head,
|
|
val.span,
|
|
));
|
|
}
|
|
}
|
|
// 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(),
|
|
)
|
|
}
|
|
|
|
fn examples(&self) -> Vec<Example> {
|
|
vec![
|
|
Example {
|
|
description: "Shift left a number by 7 bits",
|
|
example: "2 | bits shl 7",
|
|
result: Some(Value::test_int(256)),
|
|
},
|
|
Example {
|
|
description: "Shift left a number with 1 byte by 7 bits",
|
|
example: "2 | bits shl 7 -n 1",
|
|
result: Some(Value::test_int(0)),
|
|
},
|
|
Example {
|
|
description: "Shift left a signed number by 1 bit",
|
|
example: "0x7F | bits shl 1 -s",
|
|
result: Some(Value::test_int(254)),
|
|
},
|
|
Example {
|
|
description: "Shift left a list of numbers",
|
|
example: "[5 3 2] | bits shl 2",
|
|
result: Some(Value::List {
|
|
vals: vec![Value::test_int(20), Value::test_int(12), Value::test_int(8)],
|
|
span: 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 {
|
|
error: ShellError::GenericError(
|
|
"Shift left result beyond the range of 64 bit signed number".to_string(),
|
|
format!(
|
|
"{val} of the specified number of bytes shift left {bits} bits exceed limit"
|
|
),
|
|
Some(span),
|
|
None,
|
|
Vec::new(),
|
|
),
|
|
},
|
|
}
|
|
}
|
|
None => Value::Error {
|
|
error: ShellError::GenericError(
|
|
"Shift left failed".to_string(),
|
|
format!("{val} shift left {bits} bits failed, you may shift too many bits"),
|
|
Some(span),
|
|
None,
|
|
Vec::new(),
|
|
),
|
|
},
|
|
}
|
|
}
|
|
|
|
fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: NumberBytes) -> Value {
|
|
match value {
|
|
Value::Int { val, span } => {
|
|
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),
|
|
}
|
|
}
|
|
// Propagate errors by explicitly matching them before the final case.
|
|
Value::Error { .. } => value,
|
|
other => Value::Error {
|
|
error: ShellError::OnlySupportsThisInputType {
|
|
exp_input_type: "integer".into(),
|
|
wrong_type: other.get_type().to_string(),
|
|
dst_span: head,
|
|
src_span: other.expect_span(),
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_examples() {
|
|
use crate::test_examples;
|
|
|
|
test_examples(SubCommand {})
|
|
}
|
|
}
|