//! A Range is an iterator over integers or floats. use crate::{ast::RangeInclusion, ShellError, Signals, Span, Value}; use core::ops::Bound; use serde::{Deserialize, Serialize}; use std::{cmp::Ordering, fmt::Display}; mod int_range { use crate::{ast::RangeInclusion, FromValue, ShellError, Signals, Span, Value}; use serde::{Deserialize, Serialize}; use std::{cmp::Ordering, fmt::Display, ops::Bound}; use super::Range; #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct IntRange { pub(crate) start: i64, pub(crate) step: i64, pub(crate) end: Bound, } impl IntRange { pub fn new( start: Value, next: Value, end: Value, inclusion: RangeInclusion, span: Span, ) -> Result { fn to_int(value: Value) -> Result, 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 } // Resolves the absolute start position given the length of the input value pub fn absolute_start(&self, len: u64) -> u64 { match self.start { start if start < 0 => len.saturating_sub(start.unsigned_abs()), start => len.min(start.unsigned_abs()), } } /// Returns the distance between the start and end of the range /// The result will always be 0 or positive pub fn distance(&self) -> Bound { match self.end { Bound::Unbounded => Bound::Unbounded, Bound::Included(end) | Bound::Excluded(end) if self.start > end => { Bound::Excluded(0) } Bound::Included(end) => Bound::Included((end - self.start) as u64), Bound::Excluded(end) => Bound::Excluded((end - self.start) as u64), } } pub fn end(&self) -> Bound { self.end } pub fn absolute_end(&self, len: u64) -> Bound { match self.end { Bound::Unbounded => Bound::Unbounded, Bound::Included(i) => match i { _ if len == 0 => Bound::Excluded(0), i if i < 0 => Bound::Excluded(len.saturating_sub((i + 1).unsigned_abs())), i => Bound::Included((len.saturating_sub(1)).min(i.unsigned_abs())), }, Bound::Excluded(i) => Bound::Excluded(match i { i if i < 0 => len.saturating_sub(i.unsigned_abs()), i => len.min(i.unsigned_abs()), }), } } pub fn absolute_bounds(&self, len: usize) -> (usize, Bound) { let start = self.absolute_start(len as u64) as usize; let end = self.absolute_end(len as u64).map(|e| e as usize); match end { Bound::Excluded(end) | Bound::Included(end) if end < start => { (start, Bound::Excluded(start)) } Bound::Excluded(end) => (start, Bound::Excluded(end)), Bound::Included(end) => (start, Bound::Included(end)), Bound::Unbounded => (start, Bound::Unbounded), } } pub fn step(&self) -> i64 { self.step } pub fn is_unbounded(&self) -> bool { self.end == Bound::Unbounded } pub fn is_relative(&self) -> bool { self.is_start_relative() || self.is_end_relative() } pub fn is_start_relative(&self) -> bool { self.start < 0 } pub fn is_end_relative(&self) -> bool { match self.end { Bound::Included(end) | Bound::Excluded(end) => end < 0, _ => false, } } pub fn contains(&self, value: i64) -> bool { if self.step < 0 { // Decreasing range if value > self.start { return false; } match self.end { Bound::Included(end) if value < end => return false, Bound::Excluded(end) if value <= end => return false, _ => {} }; } else { // Increasing range if value < self.start { return false; } match self.end { Bound::Included(end) if value > end => return false, Bound::Excluded(end) if value >= end => return false, _ => {} }; } (value - self.start) % self.step == 0 } pub fn into_range_iter(self, signals: Signals) -> Iter { Iter { current: Some(self.start), step: self.step, end: self.end, signals, } } } 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 { 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 { write!(f, "{}..", self.start)?; if self.step != 1 { write!(f, "{}..", self.start + self.step)?; } match self.end { Bound::Included(end) => write!(f, "{end}"), Bound::Excluded(end) => write!(f, "<{end}"), Bound::Unbounded => Ok(()), } } } impl FromValue for IntRange { fn from_value(v: Value) -> Result { let span = v.span(); let range = Range::from_value(v)?; match range { Range::IntRange(v) => Ok(v), Range::FloatRange(_) => Err(ShellError::TypeMismatch { err_message: "expected an int range".into(), span, }), } } } pub struct Iter { current: Option, step: i64, end: Bound, signals: Signals, } impl Iterator for Iter { type Item = i64; fn next(&mut self) -> Option { 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 && !self.signals.interrupted() { self.current = current.checked_add(self.step); Some(current) } else { self.current = None; None } } else { None } } } } mod float_range { use crate::{ ast::RangeInclusion, format::ObviousFloat, IntRange, Range, ShellError, Signals, Span, Value, }; use serde::{Deserialize, Serialize}; use std::{cmp::Ordering, fmt::Display, ops::Bound}; #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct FloatRange { pub(crate) start: f64, pub(crate) step: f64, pub(crate) end: Bound, } impl FloatRange { pub fn new( start: Value, next: Value, end: Value, inclusion: RangeInclusion, span: Span, ) -> Result { fn to_float(value: Value) -> Result, 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 { 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 { // Decreasing range if value > self.start { return false; } match self.end { Bound::Included(end) if value <= end => return false, Bound::Excluded(end) if value < end => return false, _ => {} }; } else { // Increasing range if value < self.start { return false; } match self.end { Bound::Included(end) if value >= end => return false, Bound::Excluded(end) if value > end => return false, _ => {} }; } ((value - self.start) % self.step).abs() < f64::EPSILON } pub fn into_range_iter(self, signals: Signals) -> Iter { Iter { start: self.start, step: self.step, end: self.end, iter: Some(0), signals, } } } 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 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 { 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 { write!(f, "{}..", ObviousFloat(self.start))?; if self.step != 1f64 { write!(f, "{}..", ObviousFloat(self.start + self.step))?; } match self.end { Bound::Included(end) => write!(f, "{}", ObviousFloat(end)), Bound::Excluded(end) => write!(f, "<{}", ObviousFloat(end)), Bound::Unbounded => Ok(()), } } } impl From 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 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, iter: Option, signals: Signals, } impl Iterator for Iter { type Item = f64; fn next(&mut self) -> Option { 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 && !self.signals.interrupted() { 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( start: Value, next: Value, end: Value, inclusion: RangeInclusion, span: Span, ) -> Result { // 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 { IntRange::new(start, next, end, inclusion, span).map(Self::IntRange) } } 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) } (Self::FloatRange(range), Value::Int { val, .. }) => range.contains(*val as f64), (Self::FloatRange(range), Value::Float { val, .. }) => range.contains(*val), _ => false, } } pub fn is_bounded(&self) -> bool { match self { Range::IntRange(range) => range.end() != Bound::::Unbounded, Range::FloatRange(range) => range.end() != Bound::::Unbounded, } } pub fn into_range_iter(self, span: Span, signals: Signals) -> Iter { match self { Range::IntRange(range) => Iter::IntIter(range.into_range_iter(signals), span), Range::FloatRange(range) => Iter::FloatIter(range.into_range_iter(signals), 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 { Some(self.cmp(other)) } } 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 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 for Range { fn from(range: IntRange) -> Self { Self::IntRange(range) } } impl From 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 { 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)), } } }