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:
Ian Manske
2024-04-06 14:04:56 +00:00
committed by GitHub
parent 75fedcc8dd
commit 7a7d43344e
27 changed files with 1126 additions and 798 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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