nushell/crates/nu-protocol/src/value/range.rs
nibon7 5ad3bfa31b
Auto format let-else block (#10214)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->

# Description
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->
rustfmt 1.6.0 has added support for formatting [let-else
statements](https://doc.rust-lang.org/rust-by-example/flow_control/let_else.html)

See https://github.com/rust-lang/rustfmt/blob/master/CHANGELOG.md#added

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use std testing; testing run-tests --path
crates/nu-std"` to run the tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2023-09-04 19:42:31 +12:00

247 lines
6.8 KiB
Rust

use serde::{Deserialize, Serialize};
use std::{
cmp::Ordering,
sync::{atomic::AtomicBool, Arc},
};
/// A Range is an iterator over integers.
use crate::{
ast::{RangeInclusion, RangeOperator},
*,
};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Range {
pub from: Value,
pub incr: Value,
pub to: Value,
pub inclusion: RangeInclusion,
}
impl Range {
pub fn new(
expr_span: Span,
from: Value,
next: Value,
to: Value,
operator: &RangeOperator,
) -> Result<Range, ShellError> {
// Select from & to values if they're not specified
// TODO: Replace the placeholder values with proper min/max for range based on data type
let from = if let Value::Nothing { .. } = from {
Value::int(0i64, expr_span)
} else {
from
};
let to = if let Value::Nothing { .. } = to {
if let Ok(Value::Bool { val: true, .. }) = next.lt(expr_span, &from, expr_span) {
Value::int(i64::MIN, expr_span)
} else {
Value::int(i64::MAX, expr_span)
}
} else {
to
};
// Check if the range counts up or down
let moves_up = matches!(
from.lte(expr_span, &to, expr_span),
Ok(Value::Bool { val: true, .. })
);
// Convert the next value into the increment
let incr = if let Value::Nothing { .. } = next {
if moves_up {
Value::int(1i64, expr_span)
} else {
Value::int(-1i64, expr_span)
}
} else {
next.sub(operator.next_op_span, &from, expr_span)?
};
let zero = Value::int(0i64, expr_span);
// Increment must be non-zero, otherwise we iterate forever
if matches!(
incr.eq(expr_span, &zero, expr_span),
Ok(Value::Bool { val: true, .. })
) {
return Err(ShellError::CannotCreateRange { span: expr_span });
}
// If to > from, then incr > 0, otherwise we iterate forever
if let (Value::Bool { val: true, .. }, Value::Bool { val: false, .. }) = (
to.gt(operator.span, &from, expr_span)?,
incr.gt(operator.next_op_span, &zero, expr_span)?,
) {
return Err(ShellError::CannotCreateRange { span: expr_span });
}
// If to < from, then incr < 0, otherwise we iterate forever
if let (Value::Bool { val: true, .. }, Value::Bool { val: false, .. }) = (
to.lt(operator.span, &from, expr_span)?,
incr.lt(operator.next_op_span, &zero, expr_span)?,
) {
return Err(ShellError::CannotCreateRange { span: expr_span });
}
Ok(Range {
from,
incr,
to,
inclusion: operator.inclusion,
})
}
#[inline]
fn moves_up(&self) -> bool {
self.from <= self.to
}
pub fn is_end_inclusive(&self) -> bool {
matches!(self.inclusion, RangeInclusion::Inclusive)
}
pub fn from(&self) -> Result<i64, ShellError> {
self.from.as_int()
}
pub fn to(&self) -> Result<i64, ShellError> {
let to = self.to.as_int()?;
if self.is_end_inclusive() {
Ok(to)
} else {
Ok(to - 1)
}
}
pub fn contains(&self, item: &Value) -> bool {
match (item.partial_cmp(&self.from), item.partial_cmp(&self.to)) {
(Some(Ordering::Greater | Ordering::Equal), Some(Ordering::Less)) => self.moves_up(),
(Some(Ordering::Less | Ordering::Equal), Some(Ordering::Greater)) => !self.moves_up(),
(Some(_), Some(Ordering::Equal)) => self.is_end_inclusive(),
(_, _) => false,
}
}
pub fn into_range_iter(
self,
ctrlc: Option<Arc<AtomicBool>>,
) -> Result<RangeIterator, ShellError> {
let span = self.from.span();
Ok(RangeIterator::new(self, ctrlc, span))
}
}
impl PartialOrd for Range {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match self.from.partial_cmp(&other.from) {
Some(core::cmp::Ordering::Equal) => {}
ord => return ord,
}
match self.incr.partial_cmp(&other.incr) {
Some(core::cmp::Ordering::Equal) => {}
ord => return ord,
}
match self.to.partial_cmp(&other.to) {
Some(core::cmp::Ordering::Equal) => {}
ord => return ord,
}
self.inclusion.partial_cmp(&other.inclusion)
}
}
pub struct RangeIterator {
curr: Value,
end: Value,
span: Span,
is_end_inclusive: bool,
moves_up: bool,
incr: Value,
done: bool,
ctrlc: Option<Arc<AtomicBool>>,
}
impl RangeIterator {
pub fn new(range: Range, ctrlc: Option<Arc<AtomicBool>>, span: Span) -> RangeIterator {
let moves_up = range.moves_up();
let is_end_inclusive = range.is_end_inclusive();
let start = match range.from {
Value::Nothing { .. } => Value::int(0, span),
x => x,
};
let end = match range.to {
Value::Nothing { .. } => Value::int(i64::MAX, span),
x => x,
};
RangeIterator {
moves_up,
curr: start,
end,
span,
is_end_inclusive,
done: false,
incr: range.incr,
ctrlc,
}
}
}
impl Iterator for RangeIterator {
type Item = Value;
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
if nu_utils::ctrl_c::was_pressed(&self.ctrlc) {
return None;
}
let ordering = if matches!(self.end, Value::Nothing { .. }) {
Some(Ordering::Less)
} else {
self.curr.partial_cmp(&self.end)
};
let Some(ordering) = ordering else {
self.done = true;
return Some(Value::error(
ShellError::CannotCreateRange { span: self.span },
self.span,
));
};
let desired_ordering = if self.moves_up {
Ordering::Less
} else {
Ordering::Greater
};
if (ordering == desired_ordering) || (self.is_end_inclusive && ordering == Ordering::Equal)
{
let next_value = self.curr.add(self.span, &self.incr, self.span);
let mut next = match next_value {
Ok(result) => result,
Err(error) => {
self.done = true;
return Some(Value::error(error, self.span));
}
};
std::mem::swap(&mut self.curr, &mut next);
Some(next)
} else {
None
}
}
}