mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 15:06:03 +02:00
Range
refactor (#12405)
# Description Currently, `Range` is a struct with a `from`, `to`, and `incr` field, which are all type `Value`. This PR changes `Range` to be an enum over `IntRange` and `FloatRange` for better type safety / stronger compile time guarantees. Fixes: #11778 Fixes: #11777 Fixes: #11776 Fixes: #11775 Fixes: #11774 Fixes: #11773 Fixes: #11769. # User-Facing Changes Hopefully none, besides bug fixes. Although, the `serde` representation might have changed.
This commit is contained in:
@ -109,7 +109,7 @@ impl Display for Operator {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Serialize, Deserialize)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum RangeInclusion {
|
||||
Inclusive,
|
||||
RightExclusive,
|
||||
|
@ -168,8 +168,9 @@ pub trait Eval {
|
||||
} else {
|
||||
Value::nothing(expr.span)
|
||||
};
|
||||
|
||||
Ok(Value::range(
|
||||
Range::new(expr.span, from, next, to, operator)?,
|
||||
Range::new(from, next, to, operator.inclusion, expr.span)?,
|
||||
expr.span,
|
||||
))
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ pub use stream::*;
|
||||
use crate::{
|
||||
ast::{Call, PathMember},
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
format_error, Config, ShellError, Span, Value,
|
||||
format_error, Config, Range, ShellError, Span, Value,
|
||||
};
|
||||
use nu_utils::{stderr_write_all_and_flush, stdout_write_all_and_flush};
|
||||
use std::{
|
||||
@ -404,7 +404,7 @@ impl PipelineData {
|
||||
/// It returns Err if the `self` cannot be converted to an iterator.
|
||||
pub fn into_iter_strict(self, span: Span) -> Result<PipelineIterator, ShellError> {
|
||||
match self {
|
||||
PipelineData::Value(val, metadata) => match val {
|
||||
PipelineData::Value(value, metadata) => match value {
|
||||
Value::List { vals, .. } => Ok(PipelineIterator(PipelineData::ListStream(
|
||||
ListStream::from_stream(vals.into_iter(), None),
|
||||
metadata,
|
||||
@ -416,13 +416,11 @@ impl PipelineData {
|
||||
),
|
||||
metadata,
|
||||
))),
|
||||
Value::Range { val, .. } => match val.into_range_iter(None) {
|
||||
Ok(iter) => Ok(PipelineIterator(PipelineData::ListStream(
|
||||
ListStream::from_stream(iter, None),
|
||||
Value::Range { val, .. } => Ok(PipelineIterator(PipelineData::ListStream(
|
||||
ListStream::from_stream(val.into_range_iter(value.span(), None), None),
|
||||
metadata,
|
||||
))),
|
||||
Err(error) => Err(error),
|
||||
},
|
||||
)))
|
||||
,
|
||||
// Propagate errors by explicitly matching them before the final case.
|
||||
Value::Error { error, .. } => Err(*error),
|
||||
other => Err(ShellError::OnlySupportsThisInputType {
|
||||
@ -560,8 +558,21 @@ impl PipelineData {
|
||||
F: FnMut(Value) -> Value + 'static + Send,
|
||||
{
|
||||
match self {
|
||||
PipelineData::Value(Value::List { vals, .. }, ..) => {
|
||||
Ok(vals.into_iter().map(f).into_pipeline_data(ctrlc))
|
||||
PipelineData::Value(value, ..) => {
|
||||
let span = value.span();
|
||||
match value {
|
||||
Value::List { vals, .. } => {
|
||||
Ok(vals.into_iter().map(f).into_pipeline_data(ctrlc))
|
||||
}
|
||||
Value::Range { val, .. } => Ok(val
|
||||
.into_range_iter(span, ctrlc.clone())
|
||||
.map(f)
|
||||
.into_pipeline_data(ctrlc)),
|
||||
value => match f(value) {
|
||||
Value::Error { error, .. } => Err(*error),
|
||||
v => Ok(v.into_pipeline_data()),
|
||||
},
|
||||
}
|
||||
}
|
||||
PipelineData::Empty => Ok(PipelineData::Empty),
|
||||
PipelineData::ListStream(stream, ..) => Ok(stream.map(f).into_pipeline_data(ctrlc)),
|
||||
@ -582,15 +593,6 @@ impl PipelineData {
|
||||
Ok(f(Value::binary(collected.item, collected.span)).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
PipelineData::Value(Value::Range { val, .. }, ..) => Ok(val
|
||||
.into_range_iter(ctrlc.clone())?
|
||||
.map(f)
|
||||
.into_pipeline_data(ctrlc)),
|
||||
PipelineData::Value(v, ..) => match f(v) {
|
||||
Value::Error { error, .. } => Err(*error),
|
||||
v => Ok(v.into_pipeline_data()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -608,8 +610,18 @@ impl PipelineData {
|
||||
{
|
||||
match self {
|
||||
PipelineData::Empty => Ok(PipelineData::Empty),
|
||||
PipelineData::Value(Value::List { vals, .. }, ..) => {
|
||||
Ok(vals.into_iter().flat_map(f).into_pipeline_data(ctrlc))
|
||||
PipelineData::Value(value, ..) => {
|
||||
let span = value.span();
|
||||
match value {
|
||||
Value::List { vals, .. } => {
|
||||
Ok(vals.into_iter().flat_map(f).into_pipeline_data(ctrlc))
|
||||
}
|
||||
Value::Range { val, .. } => Ok(val
|
||||
.into_range_iter(span, ctrlc.clone())
|
||||
.flat_map(f)
|
||||
.into_pipeline_data(ctrlc)),
|
||||
value => Ok(f(value).into_iter().into_pipeline_data(ctrlc)),
|
||||
}
|
||||
}
|
||||
PipelineData::ListStream(stream, ..) => {
|
||||
Ok(stream.flat_map(f).into_pipeline_data(ctrlc))
|
||||
@ -635,11 +647,6 @@ impl PipelineData {
|
||||
.into_pipeline_data(ctrlc))
|
||||
}
|
||||
}
|
||||
PipelineData::Value(Value::Range { val, .. }, ..) => Ok(val
|
||||
.into_range_iter(ctrlc.clone())?
|
||||
.flat_map(f)
|
||||
.into_pipeline_data(ctrlc)),
|
||||
PipelineData::Value(v, ..) => Ok(f(v).into_iter().into_pipeline_data(ctrlc)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -654,8 +661,24 @@ impl PipelineData {
|
||||
{
|
||||
match self {
|
||||
PipelineData::Empty => Ok(PipelineData::Empty),
|
||||
PipelineData::Value(Value::List { vals, .. }, ..) => {
|
||||
Ok(vals.into_iter().filter(f).into_pipeline_data(ctrlc))
|
||||
PipelineData::Value(value, ..) => {
|
||||
let span = value.span();
|
||||
match value {
|
||||
Value::List { vals, .. } => {
|
||||
Ok(vals.into_iter().filter(f).into_pipeline_data(ctrlc))
|
||||
}
|
||||
Value::Range { val, .. } => Ok(val
|
||||
.into_range_iter(span, ctrlc.clone())
|
||||
.filter(f)
|
||||
.into_pipeline_data(ctrlc)),
|
||||
value => {
|
||||
if f(&value) {
|
||||
Ok(value.into_pipeline_data())
|
||||
} else {
|
||||
Ok(Value::nothing(span).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PipelineData::ListStream(stream, ..) => Ok(stream.filter(f).into_pipeline_data(ctrlc)),
|
||||
PipelineData::ExternalStream { stdout: None, .. } => Ok(PipelineData::Empty),
|
||||
@ -687,17 +710,6 @@ impl PipelineData {
|
||||
}
|
||||
}
|
||||
}
|
||||
PipelineData::Value(Value::Range { val, .. }, ..) => Ok(val
|
||||
.into_range_iter(ctrlc.clone())?
|
||||
.filter(f)
|
||||
.into_pipeline_data(ctrlc)),
|
||||
PipelineData::Value(v, ..) => {
|
||||
if f(&v) {
|
||||
Ok(v.into_pipeline_data())
|
||||
} else {
|
||||
Ok(Value::nothing(v.span()).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -793,47 +805,43 @@ impl PipelineData {
|
||||
/// converting `to json` or `to nuon`.
|
||||
/// `1..3 | to XX -> [1,2,3]`
|
||||
pub fn try_expand_range(self) -> Result<PipelineData, ShellError> {
|
||||
let input = match self {
|
||||
PipelineData::Value(v, metadata) => match v {
|
||||
Value::Range { val, .. } => {
|
||||
let span = val.to.span();
|
||||
match (&val.to, &val.from) {
|
||||
(Value::Float { val, .. }, _) | (_, Value::Float { val, .. }) => {
|
||||
if *val == f64::INFINITY || *val == f64::NEG_INFINITY {
|
||||
return Err(ShellError::GenericError {
|
||||
error: "Cannot create range".into(),
|
||||
msg: "Infinity is not allowed when converting to json".into(),
|
||||
span: Some(span),
|
||||
help: Some("Consider removing infinity".into()),
|
||||
inner: vec![],
|
||||
});
|
||||
match self {
|
||||
PipelineData::Value(v, metadata) => {
|
||||
let span = v.span();
|
||||
match v {
|
||||
Value::Range { val, .. } => {
|
||||
match val {
|
||||
Range::IntRange(range) => {
|
||||
if range.is_unbounded() {
|
||||
return Err(ShellError::GenericError {
|
||||
error: "Cannot create range".into(),
|
||||
msg: "Unbounded ranges are not allowed when converting to this format".into(),
|
||||
span: Some(span),
|
||||
help: Some("Consider using ranges with valid start and end point.".into()),
|
||||
inner: vec![],
|
||||
});
|
||||
}
|
||||
}
|
||||
Range::FloatRange(range) => {
|
||||
if range.is_unbounded() {
|
||||
return Err(ShellError::GenericError {
|
||||
error: "Cannot create range".into(),
|
||||
msg: "Unbounded ranges are not allowed when converting to this format".into(),
|
||||
span: Some(span),
|
||||
help: Some("Consider using ranges with valid start and end point.".into()),
|
||||
inner: vec![],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
(Value::Int { val, .. }, _) => {
|
||||
if *val == i64::MAX || *val == i64::MIN {
|
||||
return Err(ShellError::GenericError {
|
||||
error: "Cannot create range".into(),
|
||||
msg: "Unbounded ranges are not allowed when converting to json"
|
||||
.into(),
|
||||
span: Some(span),
|
||||
help: Some(
|
||||
"Consider using ranges with valid start and end point."
|
||||
.into(),
|
||||
),
|
||||
inner: vec![],
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
let range_values: Vec<Value> = val.into_range_iter(span, None).collect();
|
||||
Ok(PipelineData::Value(Value::list(range_values, span), None))
|
||||
}
|
||||
let range_values: Vec<Value> = val.into_range_iter(None)?.collect();
|
||||
PipelineData::Value(Value::list(range_values, span), None)
|
||||
x => Ok(PipelineData::Value(x, metadata)),
|
||||
}
|
||||
x => PipelineData::Value(x, metadata),
|
||||
},
|
||||
_ => self,
|
||||
};
|
||||
Ok(input)
|
||||
}
|
||||
_ => Ok(self),
|
||||
}
|
||||
}
|
||||
|
||||
/// Consume and print self data immediately.
|
||||
@ -948,26 +956,17 @@ impl IntoIterator for PipelineData {
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
match self {
|
||||
PipelineData::Value(v, metadata) => {
|
||||
let span = v.span();
|
||||
match v {
|
||||
PipelineData::Value(value, metadata) => {
|
||||
let span = value.span();
|
||||
match value {
|
||||
Value::List { vals, .. } => PipelineIterator(PipelineData::ListStream(
|
||||
ListStream::from_stream(vals.into_iter(), None),
|
||||
metadata,
|
||||
)),
|
||||
Value::Range { val, .. } => match val.into_range_iter(None) {
|
||||
Ok(iter) => PipelineIterator(PipelineData::ListStream(
|
||||
ListStream::from_stream(iter, None),
|
||||
metadata,
|
||||
)),
|
||||
Err(error) => PipelineIterator(PipelineData::ListStream(
|
||||
ListStream::from_stream(
|
||||
std::iter::once(Value::error(error, span)),
|
||||
None,
|
||||
),
|
||||
metadata,
|
||||
)),
|
||||
},
|
||||
Value::Range { val, .. } => PipelineIterator(PipelineData::ListStream(
|
||||
ListStream::from_stream(val.into_range_iter(span, None), None),
|
||||
metadata,
|
||||
)),
|
||||
x => PipelineIterator(PipelineData::Value(x, metadata)),
|
||||
}
|
||||
}
|
||||
|
@ -442,7 +442,7 @@ impl FromValue for Spanned<DateTime<FixedOffset>> {
|
||||
impl FromValue for Range {
|
||||
fn from_value(v: Value) -> Result<Self, ShellError> {
|
||||
match v {
|
||||
Value::Range { val, .. } => Ok(*val),
|
||||
Value::Range { val, .. } => Ok(val),
|
||||
v => Err(ShellError::CantConvert {
|
||||
to_type: "range".into(),
|
||||
from_type: v.get_type().to_string(),
|
||||
@ -457,7 +457,7 @@ impl FromValue for Spanned<Range> {
|
||||
fn from_value(v: Value) -> Result<Self, ShellError> {
|
||||
let span = v.span();
|
||||
match v {
|
||||
Value::Range { val, .. } => Ok(Spanned { item: *val, span }),
|
||||
Value::Range { val, .. } => Ok(Spanned { item: val, span }),
|
||||
v => Err(ShellError::CantConvert {
|
||||
to_type: "range".into(),
|
||||
from_type: v.get_type().to_string(),
|
||||
|
@ -14,11 +14,11 @@ pub use filesize::*;
|
||||
pub use from_value::FromValue;
|
||||
pub use glob::*;
|
||||
pub use lazy_record::LazyRecord;
|
||||
pub use range::*;
|
||||
pub use range::{FloatRange, IntRange, Range};
|
||||
pub use record::Record;
|
||||
|
||||
use crate::{
|
||||
ast::{Bits, Boolean, CellPath, Comparison, Math, Operator, PathMember, RangeInclusion},
|
||||
ast::{Bits, Boolean, CellPath, Comparison, Math, Operator, PathMember},
|
||||
did_you_mean,
|
||||
engine::{Closure, EngineState},
|
||||
BlockId, Config, ShellError, Span, Type,
|
||||
@ -36,6 +36,7 @@ use std::{
|
||||
borrow::Cow,
|
||||
cmp::Ordering,
|
||||
fmt::{Debug, Display, Write},
|
||||
ops::Bound,
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
@ -87,7 +88,7 @@ pub enum Value {
|
||||
internal_span: Span,
|
||||
},
|
||||
Range {
|
||||
val: Box<Range>,
|
||||
val: Range,
|
||||
// note: spans are being refactored out of Value
|
||||
// please use .span() instead of matching this span value
|
||||
#[serde(rename = "span")]
|
||||
@ -197,7 +198,7 @@ impl Clone for Value {
|
||||
internal_span: *internal_span,
|
||||
},
|
||||
Value::Range { val, internal_span } => Value::Range {
|
||||
val: val.clone(),
|
||||
val: *val,
|
||||
internal_span: *internal_span,
|
||||
},
|
||||
Value::Float { val, internal_span } => Value::float(*val, *internal_span),
|
||||
@ -345,9 +346,9 @@ impl Value {
|
||||
}
|
||||
|
||||
/// Returns a reference to the inner [`Range`] value or an error if this `Value` is not a range
|
||||
pub fn as_range(&self) -> Result<&Range, ShellError> {
|
||||
pub fn as_range(&self) -> Result<Range, ShellError> {
|
||||
if let Value::Range { val, .. } = self {
|
||||
Ok(val.as_ref())
|
||||
Ok(*val)
|
||||
} else {
|
||||
self.cant_convert_to("range")
|
||||
}
|
||||
@ -356,7 +357,7 @@ impl Value {
|
||||
/// Unwraps the inner [`Range`] value or returns an error if this `Value` is not a range
|
||||
pub fn into_range(self) -> Result<Range, ShellError> {
|
||||
if let Value::Range { val, .. } = self {
|
||||
Ok(*val)
|
||||
Ok(val)
|
||||
} else {
|
||||
self.cant_convert_to("range")
|
||||
}
|
||||
@ -921,13 +922,7 @@ impl Value {
|
||||
)
|
||||
}
|
||||
},
|
||||
Value::Range { val, .. } => {
|
||||
format!(
|
||||
"{}..{}",
|
||||
val.from.to_expanded_string(", ", config),
|
||||
val.to.to_expanded_string(", ", config)
|
||||
)
|
||||
}
|
||||
Value::Range { val, .. } => val.to_string(),
|
||||
Value::String { val, .. } => val.clone(),
|
||||
Value::Glob { val, .. } => val.clone(),
|
||||
Value::List { vals: val, .. } => format!(
|
||||
@ -1108,7 +1103,9 @@ impl Value {
|
||||
}
|
||||
}
|
||||
Value::Range { val, .. } => {
|
||||
if let Some(item) = val.into_range_iter(None)?.nth(*count) {
|
||||
if let Some(item) =
|
||||
val.into_range_iter(current.span(), None).nth(*count)
|
||||
{
|
||||
current = item;
|
||||
} else if *optional {
|
||||
return Ok(Value::nothing(*origin_span)); // short-circuit
|
||||
@ -1856,12 +1853,6 @@ impl Value {
|
||||
f(self)?;
|
||||
// Check for contained values
|
||||
match self {
|
||||
// Any values that can contain other values need to be handled recursively
|
||||
Value::Range { ref mut val, .. } => {
|
||||
val.from.recurse_mut(f)?;
|
||||
val.to.recurse_mut(f)?;
|
||||
val.incr.recurse_mut(f)
|
||||
}
|
||||
Value::Record { ref mut val, .. } => val
|
||||
.iter_mut()
|
||||
.try_for_each(|(_, rec_value)| rec_value.recurse_mut(f)),
|
||||
@ -1882,6 +1873,7 @@ impl Value {
|
||||
| Value::Filesize { .. }
|
||||
| Value::Duration { .. }
|
||||
| Value::Date { .. }
|
||||
| Value::Range { .. }
|
||||
| Value::String { .. }
|
||||
| Value::Glob { .. }
|
||||
| Value::Block { .. }
|
||||
@ -1975,7 +1967,7 @@ impl Value {
|
||||
|
||||
pub fn range(val: Range, span: Span) -> Value {
|
||||
Value::Range {
|
||||
val: Box::new(val),
|
||||
val,
|
||||
internal_span: span,
|
||||
}
|
||||
}
|
||||
@ -2185,12 +2177,11 @@ impl Value {
|
||||
Value::test_filesize(0),
|
||||
Value::test_duration(0),
|
||||
Value::test_date(DateTime::UNIX_EPOCH.into()),
|
||||
Value::test_range(Range {
|
||||
from: Value::test_nothing(),
|
||||
incr: Value::test_nothing(),
|
||||
to: Value::test_nothing(),
|
||||
inclusion: RangeInclusion::Inclusive,
|
||||
}),
|
||||
Value::test_range(Range::IntRange(IntRange {
|
||||
start: 0,
|
||||
step: 1,
|
||||
end: Bound::Excluded(0),
|
||||
})),
|
||||
Value::test_float(0.0),
|
||||
Value::test_string(String::new()),
|
||||
Value::test_record(Record::new()),
|
||||
|
@ -1,246 +1,625 @@
|
||||
//! A Range is an iterator over integers or floats.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
fmt::Display,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
|
||||
/// A Range is an iterator over integers.
|
||||
use crate::{
|
||||
ast::{RangeInclusion, RangeOperator},
|
||||
*,
|
||||
};
|
||||
use crate::{ast::RangeInclusion, ShellError, Span, Value};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Range {
|
||||
pub from: Value,
|
||||
pub incr: Value,
|
||||
pub to: Value,
|
||||
pub inclusion: RangeInclusion,
|
||||
mod int_range {
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
fmt::Display,
|
||||
ops::Bound,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{ast::RangeInclusion, ShellError, Span, Value};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
pub struct IntRange {
|
||||
pub(crate) start: i64,
|
||||
pub(crate) step: i64,
|
||||
pub(crate) end: Bound<i64>,
|
||||
}
|
||||
|
||||
impl IntRange {
|
||||
pub fn new(
|
||||
start: Value,
|
||||
next: Value,
|
||||
end: Value,
|
||||
inclusion: RangeInclusion,
|
||||
span: Span,
|
||||
) -> Result<Self, ShellError> {
|
||||
fn to_int(value: Value) -> Result<Option<i64>, ShellError> {
|
||||
match value {
|
||||
Value::Int { val, .. } => Ok(Some(val)),
|
||||
Value::Nothing { .. } => Ok(None),
|
||||
val => Err(ShellError::CantConvert {
|
||||
to_type: "int".into(),
|
||||
from_type: val.get_type().to_string(),
|
||||
span: val.span(),
|
||||
help: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
let start = to_int(start)?.unwrap_or(0);
|
||||
|
||||
let next_span = next.span();
|
||||
let next = to_int(next)?;
|
||||
if next.is_some_and(|next| next == start) {
|
||||
return Err(ShellError::CannotCreateRange { span: next_span });
|
||||
}
|
||||
|
||||
let end = to_int(end)?;
|
||||
|
||||
let step = match (next, end) {
|
||||
(Some(next), Some(end)) => {
|
||||
if (next < start) != (end < start) {
|
||||
return Err(ShellError::CannotCreateRange { span });
|
||||
}
|
||||
next - start
|
||||
}
|
||||
(Some(next), None) => next - start,
|
||||
(None, Some(end)) => {
|
||||
if end < start {
|
||||
-1
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
(None, None) => 1,
|
||||
};
|
||||
|
||||
let end = if let Some(end) = end {
|
||||
match inclusion {
|
||||
RangeInclusion::Inclusive => Bound::Included(end),
|
||||
RangeInclusion::RightExclusive => Bound::Excluded(end),
|
||||
}
|
||||
} else {
|
||||
Bound::Unbounded
|
||||
};
|
||||
|
||||
Ok(Self { start, step, end })
|
||||
}
|
||||
|
||||
pub fn start(&self) -> i64 {
|
||||
self.start
|
||||
}
|
||||
|
||||
pub fn end(&self) -> Bound<i64> {
|
||||
self.end
|
||||
}
|
||||
|
||||
pub fn step(&self) -> i64 {
|
||||
self.step
|
||||
}
|
||||
|
||||
pub fn is_unbounded(&self) -> bool {
|
||||
self.end == Bound::Unbounded
|
||||
}
|
||||
|
||||
pub fn contains(&self, value: i64) -> bool {
|
||||
if self.step < 0 {
|
||||
value <= self.start
|
||||
&& match self.end {
|
||||
Bound::Included(end) => value >= end,
|
||||
Bound::Excluded(end) => value > end,
|
||||
Bound::Unbounded => true,
|
||||
}
|
||||
} else {
|
||||
self.start <= value
|
||||
&& match self.end {
|
||||
Bound::Included(end) => value <= end,
|
||||
Bound::Excluded(end) => value < end,
|
||||
Bound::Unbounded => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_range_iter(self, ctrlc: Option<Arc<AtomicBool>>) -> Iter {
|
||||
Iter {
|
||||
current: Some(self.start),
|
||||
step: self.step,
|
||||
end: self.end,
|
||||
ctrlc,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for IntRange {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
// Ranges are compared roughly according to their list representation.
|
||||
// Compare in order:
|
||||
// - the head element (start)
|
||||
// - the tail elements (step)
|
||||
// - the length (end)
|
||||
self.start
|
||||
.cmp(&other.start)
|
||||
.then(self.step.cmp(&other.step))
|
||||
.then_with(|| match (self.end, other.end) {
|
||||
(Bound::Included(l), Bound::Included(r))
|
||||
| (Bound::Excluded(l), Bound::Excluded(r)) => {
|
||||
let ord = l.cmp(&r);
|
||||
if self.step < 0 {
|
||||
ord.reverse()
|
||||
} else {
|
||||
ord
|
||||
}
|
||||
}
|
||||
(Bound::Included(l), Bound::Excluded(r)) => match l.cmp(&r) {
|
||||
Ordering::Equal => Ordering::Greater,
|
||||
ord if self.step < 0 => ord.reverse(),
|
||||
ord => ord,
|
||||
},
|
||||
(Bound::Excluded(l), Bound::Included(r)) => match l.cmp(&r) {
|
||||
Ordering::Equal => Ordering::Less,
|
||||
ord if self.step < 0 => ord.reverse(),
|
||||
ord => ord,
|
||||
},
|
||||
(Bound::Included(_), Bound::Unbounded) => Ordering::Less,
|
||||
(Bound::Excluded(_), Bound::Unbounded) => Ordering::Less,
|
||||
(Bound::Unbounded, Bound::Included(_)) => Ordering::Greater,
|
||||
(Bound::Unbounded, Bound::Excluded(_)) => Ordering::Greater,
|
||||
(Bound::Unbounded, Bound::Unbounded) => Ordering::Equal,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for IntRange {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for IntRange {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.start == other.start && self.step == other.step && self.end == other.end
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for IntRange {}
|
||||
|
||||
impl Display for IntRange {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
// what about self.step?
|
||||
let start = self.start;
|
||||
match self.end {
|
||||
Bound::Included(end) => write!(f, "{start}..{end}"),
|
||||
Bound::Excluded(end) => write!(f, "{start}..<{end}"),
|
||||
Bound::Unbounded => write!(f, "{start}.."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Iter {
|
||||
current: Option<i64>,
|
||||
step: i64,
|
||||
end: Bound<i64>,
|
||||
ctrlc: Option<Arc<AtomicBool>>,
|
||||
}
|
||||
|
||||
impl Iterator for Iter {
|
||||
type Item = i64;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some(current) = self.current {
|
||||
let not_end = match (self.step < 0, self.end) {
|
||||
(true, Bound::Included(end)) => current >= end,
|
||||
(true, Bound::Excluded(end)) => current > end,
|
||||
(false, Bound::Included(end)) => current <= end,
|
||||
(false, Bound::Excluded(end)) => current < end,
|
||||
(_, Bound::Unbounded) => true, // will stop once integer overflows
|
||||
};
|
||||
|
||||
if not_end && !nu_utils::ctrl_c::was_pressed(&self.ctrlc) {
|
||||
self.current = current.checked_add(self.step);
|
||||
Some(current)
|
||||
} else {
|
||||
self.current = None;
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod float_range {
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
fmt::Display,
|
||||
ops::Bound,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{ast::RangeInclusion, IntRange, Range, ShellError, Span, Value};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
pub struct FloatRange {
|
||||
pub(crate) start: f64,
|
||||
pub(crate) step: f64,
|
||||
pub(crate) end: Bound<f64>,
|
||||
}
|
||||
|
||||
impl FloatRange {
|
||||
pub fn new(
|
||||
start: Value,
|
||||
next: Value,
|
||||
end: Value,
|
||||
inclusion: RangeInclusion,
|
||||
span: Span,
|
||||
) -> Result<Self, ShellError> {
|
||||
fn to_float(value: Value) -> Result<Option<f64>, ShellError> {
|
||||
match value {
|
||||
Value::Float { val, .. } => Ok(Some(val)),
|
||||
Value::Int { val, .. } => Ok(Some(val as f64)),
|
||||
Value::Nothing { .. } => Ok(None),
|
||||
val => Err(ShellError::CantConvert {
|
||||
to_type: "float".into(),
|
||||
from_type: val.get_type().to_string(),
|
||||
span: val.span(),
|
||||
help: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// `start` must be finite (not NaN or infinity).
|
||||
// `next` must be finite and not equal to `start`.
|
||||
// `end` must not be NaN (but can be infinite).
|
||||
//
|
||||
// TODO: better error messages for the restrictions above
|
||||
|
||||
let start_span = start.span();
|
||||
let start = to_float(start)?.unwrap_or(0.0);
|
||||
if !start.is_finite() {
|
||||
return Err(ShellError::CannotCreateRange { span: start_span });
|
||||
}
|
||||
|
||||
let end_span = end.span();
|
||||
let end = to_float(end)?;
|
||||
if end.is_some_and(f64::is_nan) {
|
||||
return Err(ShellError::CannotCreateRange { span: end_span });
|
||||
}
|
||||
|
||||
let next_span = next.span();
|
||||
let next = to_float(next)?;
|
||||
if next.is_some_and(|next| next == start || !next.is_finite()) {
|
||||
return Err(ShellError::CannotCreateRange { span: next_span });
|
||||
}
|
||||
|
||||
let step = match (next, end) {
|
||||
(Some(next), Some(end)) => {
|
||||
if (next < start) != (end < start) {
|
||||
return Err(ShellError::CannotCreateRange { span });
|
||||
}
|
||||
next - start
|
||||
}
|
||||
(Some(next), None) => next - start,
|
||||
(None, Some(end)) => {
|
||||
if end < start {
|
||||
-1.0
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
}
|
||||
(None, None) => 1.0,
|
||||
};
|
||||
|
||||
let end = if let Some(end) = end {
|
||||
if end.is_infinite() {
|
||||
Bound::Unbounded
|
||||
} else {
|
||||
match inclusion {
|
||||
RangeInclusion::Inclusive => Bound::Included(end),
|
||||
RangeInclusion::RightExclusive => Bound::Excluded(end),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Bound::Unbounded
|
||||
};
|
||||
|
||||
Ok(Self { start, step, end })
|
||||
}
|
||||
|
||||
pub fn start(&self) -> f64 {
|
||||
self.start
|
||||
}
|
||||
|
||||
pub fn end(&self) -> Bound<f64> {
|
||||
self.end
|
||||
}
|
||||
|
||||
pub fn step(&self) -> f64 {
|
||||
self.step
|
||||
}
|
||||
|
||||
pub fn is_unbounded(&self) -> bool {
|
||||
self.end == Bound::Unbounded
|
||||
}
|
||||
|
||||
pub fn contains(&self, value: f64) -> bool {
|
||||
if self.step < 0.0 {
|
||||
value <= self.start
|
||||
&& match self.end {
|
||||
Bound::Included(end) => value >= end,
|
||||
Bound::Excluded(end) => value > end,
|
||||
Bound::Unbounded => true,
|
||||
}
|
||||
} else {
|
||||
self.start <= value
|
||||
&& match self.end {
|
||||
Bound::Included(end) => value <= end,
|
||||
Bound::Excluded(end) => value < end,
|
||||
Bound::Unbounded => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_range_iter(self, ctrlc: Option<Arc<AtomicBool>>) -> Iter {
|
||||
Iter {
|
||||
start: self.start,
|
||||
step: self.step,
|
||||
end: self.end,
|
||||
iter: Some(0),
|
||||
ctrlc,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for FloatRange {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
fn float_cmp(a: f64, b: f64) -> Ordering {
|
||||
// There is no way a `FloatRange` can have NaN values:
|
||||
// - `FloatRange::new` ensures no values are NaN.
|
||||
// - `From<IntRange> for FloatRange` cannot give NaNs either.
|
||||
// - There are no other ways to create a `FloatRange`.
|
||||
// - There is no way to modify values of a `FloatRange`.
|
||||
a.partial_cmp(&b).expect("not NaN")
|
||||
}
|
||||
|
||||
// Ranges are compared roughly according to their list representation.
|
||||
// Compare in order:
|
||||
// - the head element (start)
|
||||
// - the tail elements (step)
|
||||
// - the length (end)
|
||||
float_cmp(self.start, other.start)
|
||||
.then(float_cmp(self.step, other.step))
|
||||
.then_with(|| match (self.end, other.end) {
|
||||
(Bound::Included(l), Bound::Included(r))
|
||||
| (Bound::Excluded(l), Bound::Excluded(r)) => {
|
||||
let ord = float_cmp(l, r);
|
||||
if self.step < 0.0 {
|
||||
ord.reverse()
|
||||
} else {
|
||||
ord
|
||||
}
|
||||
}
|
||||
(Bound::Included(l), Bound::Excluded(r)) => match float_cmp(l, r) {
|
||||
Ordering::Equal => Ordering::Greater,
|
||||
ord if self.step < 0.0 => ord.reverse(),
|
||||
ord => ord,
|
||||
},
|
||||
(Bound::Excluded(l), Bound::Included(r)) => match float_cmp(l, r) {
|
||||
Ordering::Equal => Ordering::Less,
|
||||
ord if self.step < 0.0 => ord.reverse(),
|
||||
ord => ord,
|
||||
},
|
||||
(Bound::Included(_), Bound::Unbounded) => Ordering::Less,
|
||||
(Bound::Excluded(_), Bound::Unbounded) => Ordering::Less,
|
||||
(Bound::Unbounded, Bound::Included(_)) => Ordering::Greater,
|
||||
(Bound::Unbounded, Bound::Excluded(_)) => Ordering::Greater,
|
||||
(Bound::Unbounded, Bound::Unbounded) => Ordering::Equal,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for FloatRange {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for FloatRange {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.start == other.start && self.step == other.step && self.end == other.end
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for FloatRange {}
|
||||
|
||||
impl Display for FloatRange {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
// what about self.step?
|
||||
let start = self.start;
|
||||
match self.end {
|
||||
Bound::Included(end) => write!(f, "{start}..{end}"),
|
||||
Bound::Excluded(end) => write!(f, "{start}..<{end}"),
|
||||
Bound::Unbounded => write!(f, "{start}.."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IntRange> for FloatRange {
|
||||
fn from(range: IntRange) -> Self {
|
||||
Self {
|
||||
start: range.start as f64,
|
||||
step: range.step as f64,
|
||||
end: match range.end {
|
||||
Bound::Included(b) => Bound::Included(b as f64),
|
||||
Bound::Excluded(b) => Bound::Excluded(b as f64),
|
||||
Bound::Unbounded => Bound::Unbounded,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Range> for FloatRange {
|
||||
fn from(range: Range) -> Self {
|
||||
match range {
|
||||
Range::IntRange(range) => range.into(),
|
||||
Range::FloatRange(range) => range,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Iter {
|
||||
start: f64,
|
||||
step: f64,
|
||||
end: Bound<f64>,
|
||||
iter: Option<u64>,
|
||||
ctrlc: Option<Arc<AtomicBool>>,
|
||||
}
|
||||
|
||||
impl Iterator for Iter {
|
||||
type Item = f64;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some(iter) = self.iter {
|
||||
let current = self.start + self.step * iter as f64;
|
||||
|
||||
let not_end = match (self.step < 0.0, self.end) {
|
||||
(true, Bound::Included(end)) => current >= end,
|
||||
(true, Bound::Excluded(end)) => current > end,
|
||||
(false, Bound::Included(end)) => current <= end,
|
||||
(false, Bound::Excluded(end)) => current < end,
|
||||
(_, Bound::Unbounded) => current.is_finite(),
|
||||
};
|
||||
|
||||
if not_end && !nu_utils::ctrl_c::was_pressed(&self.ctrlc) {
|
||||
self.iter = iter.checked_add(1);
|
||||
Some(current)
|
||||
} else {
|
||||
self.iter = None;
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub use float_range::FloatRange;
|
||||
pub use int_range::IntRange;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
pub enum Range {
|
||||
IntRange(IntRange),
|
||||
FloatRange(FloatRange),
|
||||
}
|
||||
|
||||
impl Range {
|
||||
pub fn new(
|
||||
expr_span: Span,
|
||||
from: Value,
|
||||
start: 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)
|
||||
end: Value,
|
||||
inclusion: RangeInclusion,
|
||||
span: Span,
|
||||
) -> Result<Self, ShellError> {
|
||||
// promote to float range if any Value is float
|
||||
if matches!(start, Value::Float { .. })
|
||||
|| matches!(next, Value::Float { .. })
|
||||
|| matches!(end, Value::Float { .. })
|
||||
{
|
||||
FloatRange::new(start, next, end, inclusion, span).map(Self::FloatRange)
|
||||
} else {
|
||||
from
|
||||
};
|
||||
IntRange::new(start, next, end, inclusion, span).map(Self::IntRange)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
pub fn contains(&self, value: &Value) -> bool {
|
||||
match (self, value) {
|
||||
(Self::IntRange(range), Value::Int { val, .. }) => range.contains(*val),
|
||||
(Self::IntRange(range), Value::Float { val, .. }) => {
|
||||
FloatRange::from(*range).contains(*val)
|
||||
}
|
||||
} 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)
|
||||
(Self::FloatRange(range), Value::Int { val, .. }) => range.contains(*val as f64),
|
||||
(Self::FloatRange(range), Value::Float { val, .. }) => range.contains(*val),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
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, span: Span, ctrlc: Option<Arc<AtomicBool>>) -> Iter {
|
||||
match self {
|
||||
Range::IntRange(range) => Iter::IntIter(range.into_range_iter(ctrlc), span),
|
||||
Range::FloatRange(range) => Iter::FloatIter(range.into_range_iter(ctrlc), span),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 Ord for Range {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
match (self, other) {
|
||||
(Range::IntRange(l), Range::IntRange(r)) => l.cmp(r),
|
||||
(Range::FloatRange(l), Range::FloatRange(r)) => l.cmp(r),
|
||||
(Range::IntRange(int), Range::FloatRange(float)) => FloatRange::from(*int).cmp(float),
|
||||
(Range::FloatRange(float), Range::IntRange(int)) => float.cmp(&FloatRange::from(*int)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
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 PartialEq for Range {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Range::IntRange(l), Range::IntRange(r)) => l == r,
|
||||
(Range::FloatRange(l), Range::FloatRange(r)) => l == r,
|
||||
(Range::IntRange(int), Range::FloatRange(float)) => FloatRange::from(*int) == *float,
|
||||
(Range::FloatRange(float), Range::IntRange(int)) => *float == FloatRange::from(*int),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for RangeIterator {
|
||||
impl Eq for Range {}
|
||||
|
||||
impl Display for Range {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Range::IntRange(range) => write!(f, "{range}"),
|
||||
Range::FloatRange(range) => write!(f, "{range}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IntRange> for Range {
|
||||
fn from(range: IntRange) -> Self {
|
||||
Self::IntRange(range)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FloatRange> for Range {
|
||||
fn from(range: FloatRange) -> Self {
|
||||
Self::FloatRange(range)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Iter {
|
||||
IntIter(int_range::Iter, Span),
|
||||
FloatIter(float_range::Iter, Span),
|
||||
}
|
||||
|
||||
impl Iterator for Iter {
|
||||
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
|
||||
match self {
|
||||
Iter::IntIter(iter, span) => iter.next().map(|val| Value::int(val, *span)),
|
||||
Iter::FloatIter(iter, span) => iter.next().map(|val| Value::float(val, *span)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user