nu_protocol/value/
range.rs

1//! A Range is an iterator over integers or floats.
2
3use crate::{ast::RangeInclusion, ShellError, Signals, Span, Value};
4use core::ops::Bound;
5use serde::{Deserialize, Serialize};
6use std::{cmp::Ordering, fmt::Display};
7
8mod int_range {
9    use crate::{ast::RangeInclusion, FromValue, ShellError, Signals, Span, Value};
10    use serde::{Deserialize, Serialize};
11    use std::{cmp::Ordering, fmt::Display, ops::Bound};
12
13    use super::Range;
14
15    #[derive(Debug, Clone, Copy, Serialize, Deserialize)]
16    pub struct IntRange {
17        pub(crate) start: i64,
18        pub(crate) step: i64,
19        pub(crate) end: Bound<i64>,
20    }
21
22    impl IntRange {
23        pub fn new(
24            start: Value,
25            next: Value,
26            end: Value,
27            inclusion: RangeInclusion,
28            span: Span,
29        ) -> Result<Self, ShellError> {
30            fn to_int(value: Value) -> Result<Option<i64>, ShellError> {
31                match value {
32                    Value::Int { val, .. } => Ok(Some(val)),
33                    Value::Nothing { .. } => Ok(None),
34                    val => Err(ShellError::CantConvert {
35                        to_type: "int".into(),
36                        from_type: val.get_type().to_string(),
37                        span: val.span(),
38                        help: None,
39                    }),
40                }
41            }
42
43            let start = to_int(start)?.unwrap_or(0);
44
45            let next_span = next.span();
46            let next = to_int(next)?;
47            if next.is_some_and(|next| next == start) {
48                return Err(ShellError::CannotCreateRange { span: next_span });
49            }
50
51            let end = to_int(end)?;
52
53            let step = match (next, end) {
54                (Some(next), Some(end)) => {
55                    if (next < start) != (end < start) {
56                        return Err(ShellError::CannotCreateRange { span });
57                    }
58                    next - start
59                }
60                (Some(next), None) => next - start,
61                (None, Some(end)) => {
62                    if end < start {
63                        -1
64                    } else {
65                        1
66                    }
67                }
68                (None, None) => 1,
69            };
70
71            let end = if let Some(end) = end {
72                match inclusion {
73                    RangeInclusion::Inclusive => Bound::Included(end),
74                    RangeInclusion::RightExclusive => Bound::Excluded(end),
75                }
76            } else {
77                Bound::Unbounded
78            };
79
80            Ok(Self { start, step, end })
81        }
82
83        pub fn start(&self) -> i64 {
84            self.start
85        }
86
87        // Resolves the absolute start position given the length of the input value
88        pub fn absolute_start(&self, len: u64) -> u64 {
89            match self.start {
90                start if start < 0 => len.saturating_sub(start.unsigned_abs()),
91                start => len.min(start.unsigned_abs()),
92            }
93        }
94
95        /// Returns the distance between the start and end of the range
96        /// The result will always be 0 or positive
97        pub fn distance(&self) -> Bound<u64> {
98            match self.end {
99                Bound::Unbounded => Bound::Unbounded,
100                Bound::Included(end) | Bound::Excluded(end) if self.start > end => {
101                    Bound::Excluded(0)
102                }
103                Bound::Included(end) => Bound::Included((end - self.start) as u64),
104                Bound::Excluded(end) => Bound::Excluded((end - self.start) as u64),
105            }
106        }
107
108        pub fn end(&self) -> Bound<i64> {
109            self.end
110        }
111
112        pub fn absolute_end(&self, len: u64) -> Bound<u64> {
113            match self.end {
114                Bound::Unbounded => Bound::Unbounded,
115                Bound::Included(i) => match i {
116                    _ if len == 0 => Bound::Excluded(0),
117                    i if i < 0 => Bound::Excluded(len.saturating_sub((i + 1).unsigned_abs())),
118                    i => Bound::Included((len.saturating_sub(1)).min(i.unsigned_abs())),
119                },
120                Bound::Excluded(i) => Bound::Excluded(match i {
121                    i if i < 0 => len.saturating_sub(i.unsigned_abs()),
122                    i => len.min(i.unsigned_abs()),
123                }),
124            }
125        }
126
127        pub fn absolute_bounds(&self, len: usize) -> (usize, Bound<usize>) {
128            let start = self.absolute_start(len as u64) as usize;
129            let end = self.absolute_end(len as u64).map(|e| e as usize);
130            match end {
131                Bound::Excluded(end) | Bound::Included(end) if end < start => {
132                    (start, Bound::Excluded(start))
133                }
134                Bound::Excluded(end) => (start, Bound::Excluded(end)),
135                Bound::Included(end) => (start, Bound::Included(end)),
136                Bound::Unbounded => (start, Bound::Unbounded),
137            }
138        }
139
140        pub fn step(&self) -> i64 {
141            self.step
142        }
143
144        pub fn is_unbounded(&self) -> bool {
145            self.end == Bound::Unbounded
146        }
147
148        pub fn is_relative(&self) -> bool {
149            self.is_start_relative() || self.is_end_relative()
150        }
151
152        pub fn is_start_relative(&self) -> bool {
153            self.start < 0
154        }
155
156        pub fn is_end_relative(&self) -> bool {
157            match self.end {
158                Bound::Included(end) | Bound::Excluded(end) => end < 0,
159                _ => false,
160            }
161        }
162
163        pub fn contains(&self, value: i64) -> bool {
164            if self.step < 0 {
165                // Decreasing range
166                if value > self.start {
167                    return false;
168                }
169                match self.end {
170                    Bound::Included(end) if value < end => return false,
171                    Bound::Excluded(end) if value <= end => return false,
172                    _ => {}
173                };
174            } else {
175                // Increasing range
176                if value < self.start {
177                    return false;
178                }
179                match self.end {
180                    Bound::Included(end) if value > end => return false,
181                    Bound::Excluded(end) if value >= end => return false,
182                    _ => {}
183                };
184            }
185            (value - self.start) % self.step == 0
186        }
187
188        pub fn into_range_iter(self, signals: Signals) -> Iter {
189            Iter {
190                current: Some(self.start),
191                step: self.step,
192                end: self.end,
193                signals,
194            }
195        }
196    }
197
198    impl Ord for IntRange {
199        fn cmp(&self, other: &Self) -> Ordering {
200            // Ranges are compared roughly according to their list representation.
201            // Compare in order:
202            // - the head element (start)
203            // - the tail elements (step)
204            // - the length (end)
205            self.start
206                .cmp(&other.start)
207                .then(self.step.cmp(&other.step))
208                .then_with(|| match (self.end, other.end) {
209                    (Bound::Included(l), Bound::Included(r))
210                    | (Bound::Excluded(l), Bound::Excluded(r)) => {
211                        let ord = l.cmp(&r);
212                        if self.step < 0 {
213                            ord.reverse()
214                        } else {
215                            ord
216                        }
217                    }
218                    (Bound::Included(l), Bound::Excluded(r)) => match l.cmp(&r) {
219                        Ordering::Equal => Ordering::Greater,
220                        ord if self.step < 0 => ord.reverse(),
221                        ord => ord,
222                    },
223                    (Bound::Excluded(l), Bound::Included(r)) => match l.cmp(&r) {
224                        Ordering::Equal => Ordering::Less,
225                        ord if self.step < 0 => ord.reverse(),
226                        ord => ord,
227                    },
228                    (Bound::Included(_), Bound::Unbounded) => Ordering::Less,
229                    (Bound::Excluded(_), Bound::Unbounded) => Ordering::Less,
230                    (Bound::Unbounded, Bound::Included(_)) => Ordering::Greater,
231                    (Bound::Unbounded, Bound::Excluded(_)) => Ordering::Greater,
232                    (Bound::Unbounded, Bound::Unbounded) => Ordering::Equal,
233                })
234        }
235    }
236
237    impl PartialOrd for IntRange {
238        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
239            Some(self.cmp(other))
240        }
241    }
242
243    impl PartialEq for IntRange {
244        fn eq(&self, other: &Self) -> bool {
245            self.start == other.start && self.step == other.step && self.end == other.end
246        }
247    }
248
249    impl Eq for IntRange {}
250
251    impl Display for IntRange {
252        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
253            write!(f, "{}..", self.start)?;
254            if self.step != 1 {
255                write!(f, "{}..", self.start + self.step)?;
256            }
257            match self.end {
258                Bound::Included(end) => write!(f, "{end}"),
259                Bound::Excluded(end) => write!(f, "<{end}"),
260                Bound::Unbounded => Ok(()),
261            }
262        }
263    }
264
265    impl FromValue for IntRange {
266        fn from_value(v: Value) -> Result<Self, ShellError> {
267            let span = v.span();
268            let range = Range::from_value(v)?;
269            match range {
270                Range::IntRange(v) => Ok(v),
271                Range::FloatRange(_) => Err(ShellError::TypeMismatch {
272                    err_message: "expected an int range".into(),
273                    span,
274                }),
275            }
276        }
277    }
278
279    pub struct Iter {
280        current: Option<i64>,
281        step: i64,
282        end: Bound<i64>,
283        signals: Signals,
284    }
285
286    impl Iterator for Iter {
287        type Item = i64;
288
289        fn next(&mut self) -> Option<Self::Item> {
290            if let Some(current) = self.current {
291                let not_end = match (self.step < 0, self.end) {
292                    (true, Bound::Included(end)) => current >= end,
293                    (true, Bound::Excluded(end)) => current > end,
294                    (false, Bound::Included(end)) => current <= end,
295                    (false, Bound::Excluded(end)) => current < end,
296                    (_, Bound::Unbounded) => true, // will stop once integer overflows
297                };
298
299                if not_end && !self.signals.interrupted() {
300                    self.current = current.checked_add(self.step);
301                    Some(current)
302                } else {
303                    self.current = None;
304                    None
305                }
306            } else {
307                None
308            }
309        }
310    }
311}
312
313mod float_range {
314    use crate::{
315        ast::RangeInclusion, format::ObviousFloat, IntRange, Range, ShellError, Signals, Span,
316        Value,
317    };
318    use serde::{Deserialize, Serialize};
319    use std::{cmp::Ordering, fmt::Display, ops::Bound};
320
321    #[derive(Debug, Clone, Copy, Serialize, Deserialize)]
322    pub struct FloatRange {
323        pub(crate) start: f64,
324        pub(crate) step: f64,
325        pub(crate) end: Bound<f64>,
326    }
327
328    impl FloatRange {
329        pub fn new(
330            start: Value,
331            next: Value,
332            end: Value,
333            inclusion: RangeInclusion,
334            span: Span,
335        ) -> Result<Self, ShellError> {
336            fn to_float(value: Value) -> Result<Option<f64>, ShellError> {
337                match value {
338                    Value::Float { val, .. } => Ok(Some(val)),
339                    Value::Int { val, .. } => Ok(Some(val as f64)),
340                    Value::Nothing { .. } => Ok(None),
341                    val => Err(ShellError::CantConvert {
342                        to_type: "float".into(),
343                        from_type: val.get_type().to_string(),
344                        span: val.span(),
345                        help: None,
346                    }),
347                }
348            }
349
350            // `start` must be finite (not NaN or infinity).
351            // `next` must be finite and not equal to `start`.
352            // `end` must not be NaN (but can be infinite).
353            //
354            // TODO: better error messages for the restrictions above
355
356            let start_span = start.span();
357            let start = to_float(start)?.unwrap_or(0.0);
358            if !start.is_finite() {
359                return Err(ShellError::CannotCreateRange { span: start_span });
360            }
361
362            let end_span = end.span();
363            let end = to_float(end)?;
364            if end.is_some_and(f64::is_nan) {
365                return Err(ShellError::CannotCreateRange { span: end_span });
366            }
367
368            let next_span = next.span();
369            let next = to_float(next)?;
370            if next.is_some_and(|next| next == start || !next.is_finite()) {
371                return Err(ShellError::CannotCreateRange { span: next_span });
372            }
373
374            let step = match (next, end) {
375                (Some(next), Some(end)) => {
376                    if (next < start) != (end < start) {
377                        return Err(ShellError::CannotCreateRange { span });
378                    }
379                    next - start
380                }
381                (Some(next), None) => next - start,
382                (None, Some(end)) => {
383                    if end < start {
384                        -1.0
385                    } else {
386                        1.0
387                    }
388                }
389                (None, None) => 1.0,
390            };
391
392            let end = if let Some(end) = end {
393                if end.is_infinite() {
394                    Bound::Unbounded
395                } else {
396                    match inclusion {
397                        RangeInclusion::Inclusive => Bound::Included(end),
398                        RangeInclusion::RightExclusive => Bound::Excluded(end),
399                    }
400                }
401            } else {
402                Bound::Unbounded
403            };
404
405            Ok(Self { start, step, end })
406        }
407
408        pub fn start(&self) -> f64 {
409            self.start
410        }
411
412        pub fn end(&self) -> Bound<f64> {
413            self.end
414        }
415
416        pub fn step(&self) -> f64 {
417            self.step
418        }
419
420        pub fn is_unbounded(&self) -> bool {
421            self.end == Bound::Unbounded
422        }
423
424        pub fn contains(&self, value: f64) -> bool {
425            if self.step < 0.0 {
426                // Decreasing range
427                if value > self.start {
428                    return false;
429                }
430                match self.end {
431                    Bound::Included(end) if value <= end => return false,
432                    Bound::Excluded(end) if value < end => return false,
433                    _ => {}
434                };
435            } else {
436                // Increasing range
437                if value < self.start {
438                    return false;
439                }
440                match self.end {
441                    Bound::Included(end) if value >= end => return false,
442                    Bound::Excluded(end) if value > end => return false,
443                    _ => {}
444                };
445            }
446            ((value - self.start) % self.step).abs() < f64::EPSILON
447        }
448
449        pub fn into_range_iter(self, signals: Signals) -> Iter {
450            Iter {
451                start: self.start,
452                step: self.step,
453                end: self.end,
454                iter: Some(0),
455                signals,
456            }
457        }
458    }
459
460    impl Ord for FloatRange {
461        fn cmp(&self, other: &Self) -> Ordering {
462            fn float_cmp(a: f64, b: f64) -> Ordering {
463                // There is no way a `FloatRange` can have NaN values:
464                // - `FloatRange::new` ensures no values are NaN.
465                // - `From<IntRange> for FloatRange` cannot give NaNs either.
466                // - There are no other ways to create a `FloatRange`.
467                // - There is no way to modify values of a `FloatRange`.
468                a.partial_cmp(&b).expect("not NaN")
469            }
470
471            // Ranges are compared roughly according to their list representation.
472            // Compare in order:
473            // - the head element (start)
474            // - the tail elements (step)
475            // - the length (end)
476            float_cmp(self.start, other.start)
477                .then(float_cmp(self.step, other.step))
478                .then_with(|| match (self.end, other.end) {
479                    (Bound::Included(l), Bound::Included(r))
480                    | (Bound::Excluded(l), Bound::Excluded(r)) => {
481                        let ord = float_cmp(l, r);
482                        if self.step < 0.0 {
483                            ord.reverse()
484                        } else {
485                            ord
486                        }
487                    }
488                    (Bound::Included(l), Bound::Excluded(r)) => match float_cmp(l, r) {
489                        Ordering::Equal => Ordering::Greater,
490                        ord if self.step < 0.0 => ord.reverse(),
491                        ord => ord,
492                    },
493                    (Bound::Excluded(l), Bound::Included(r)) => match float_cmp(l, r) {
494                        Ordering::Equal => Ordering::Less,
495                        ord if self.step < 0.0 => ord.reverse(),
496                        ord => ord,
497                    },
498                    (Bound::Included(_), Bound::Unbounded) => Ordering::Less,
499                    (Bound::Excluded(_), Bound::Unbounded) => Ordering::Less,
500                    (Bound::Unbounded, Bound::Included(_)) => Ordering::Greater,
501                    (Bound::Unbounded, Bound::Excluded(_)) => Ordering::Greater,
502                    (Bound::Unbounded, Bound::Unbounded) => Ordering::Equal,
503                })
504        }
505    }
506
507    impl PartialOrd for FloatRange {
508        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
509            Some(self.cmp(other))
510        }
511    }
512
513    impl PartialEq for FloatRange {
514        fn eq(&self, other: &Self) -> bool {
515            self.start == other.start && self.step == other.step && self.end == other.end
516        }
517    }
518
519    impl Eq for FloatRange {}
520
521    impl Display for FloatRange {
522        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
523            write!(f, "{}..", ObviousFloat(self.start))?;
524            if self.step != 1f64 {
525                write!(f, "{}..", ObviousFloat(self.start + self.step))?;
526            }
527            match self.end {
528                Bound::Included(end) => write!(f, "{}", ObviousFloat(end)),
529                Bound::Excluded(end) => write!(f, "<{}", ObviousFloat(end)),
530                Bound::Unbounded => Ok(()),
531            }
532        }
533    }
534
535    impl From<IntRange> for FloatRange {
536        fn from(range: IntRange) -> Self {
537            Self {
538                start: range.start as f64,
539                step: range.step as f64,
540                end: match range.end {
541                    Bound::Included(b) => Bound::Included(b as f64),
542                    Bound::Excluded(b) => Bound::Excluded(b as f64),
543                    Bound::Unbounded => Bound::Unbounded,
544                },
545            }
546        }
547    }
548
549    impl From<Range> for FloatRange {
550        fn from(range: Range) -> Self {
551            match range {
552                Range::IntRange(range) => range.into(),
553                Range::FloatRange(range) => range,
554            }
555        }
556    }
557
558    pub struct Iter {
559        start: f64,
560        step: f64,
561        end: Bound<f64>,
562        iter: Option<u64>,
563        signals: Signals,
564    }
565
566    impl Iterator for Iter {
567        type Item = f64;
568
569        fn next(&mut self) -> Option<Self::Item> {
570            if let Some(iter) = self.iter {
571                let current = self.start + self.step * iter as f64;
572
573                let not_end = match (self.step < 0.0, self.end) {
574                    (true, Bound::Included(end)) => current >= end,
575                    (true, Bound::Excluded(end)) => current > end,
576                    (false, Bound::Included(end)) => current <= end,
577                    (false, Bound::Excluded(end)) => current < end,
578                    (_, Bound::Unbounded) => current.is_finite(),
579                };
580
581                if not_end && !self.signals.interrupted() {
582                    self.iter = iter.checked_add(1);
583                    Some(current)
584                } else {
585                    self.iter = None;
586                    None
587                }
588            } else {
589                None
590            }
591        }
592    }
593}
594
595pub use float_range::FloatRange;
596pub use int_range::IntRange;
597
598#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
599pub enum Range {
600    IntRange(IntRange),
601    FloatRange(FloatRange),
602}
603
604impl Range {
605    pub fn new(
606        start: Value,
607        next: Value,
608        end: Value,
609        inclusion: RangeInclusion,
610        span: Span,
611    ) -> Result<Self, ShellError> {
612        // promote to float range if any Value is float
613        if matches!(start, Value::Float { .. })
614            || matches!(next, Value::Float { .. })
615            || matches!(end, Value::Float { .. })
616        {
617            FloatRange::new(start, next, end, inclusion, span).map(Self::FloatRange)
618        } else {
619            IntRange::new(start, next, end, inclusion, span).map(Self::IntRange)
620        }
621    }
622
623    pub fn contains(&self, value: &Value) -> bool {
624        match (self, value) {
625            (Self::IntRange(range), Value::Int { val, .. }) => range.contains(*val),
626            (Self::IntRange(range), Value::Float { val, .. }) => {
627                FloatRange::from(*range).contains(*val)
628            }
629            (Self::FloatRange(range), Value::Int { val, .. }) => range.contains(*val as f64),
630            (Self::FloatRange(range), Value::Float { val, .. }) => range.contains(*val),
631            _ => false,
632        }
633    }
634
635    pub fn is_bounded(&self) -> bool {
636        match self {
637            Range::IntRange(range) => range.end() != Bound::<i64>::Unbounded,
638            Range::FloatRange(range) => range.end() != Bound::<f64>::Unbounded,
639        }
640    }
641
642    pub fn into_range_iter(self, span: Span, signals: Signals) -> Iter {
643        match self {
644            Range::IntRange(range) => Iter::IntIter(range.into_range_iter(signals), span),
645            Range::FloatRange(range) => Iter::FloatIter(range.into_range_iter(signals), span),
646        }
647    }
648}
649
650impl Ord for Range {
651    fn cmp(&self, other: &Self) -> Ordering {
652        match (self, other) {
653            (Range::IntRange(l), Range::IntRange(r)) => l.cmp(r),
654            (Range::FloatRange(l), Range::FloatRange(r)) => l.cmp(r),
655            (Range::IntRange(int), Range::FloatRange(float)) => FloatRange::from(*int).cmp(float),
656            (Range::FloatRange(float), Range::IntRange(int)) => float.cmp(&FloatRange::from(*int)),
657        }
658    }
659}
660
661impl PartialOrd for Range {
662    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
663        Some(self.cmp(other))
664    }
665}
666
667impl PartialEq for Range {
668    fn eq(&self, other: &Self) -> bool {
669        match (self, other) {
670            (Range::IntRange(l), Range::IntRange(r)) => l == r,
671            (Range::FloatRange(l), Range::FloatRange(r)) => l == r,
672            (Range::IntRange(int), Range::FloatRange(float)) => FloatRange::from(*int) == *float,
673            (Range::FloatRange(float), Range::IntRange(int)) => *float == FloatRange::from(*int),
674        }
675    }
676}
677
678impl Eq for Range {}
679
680impl Display for Range {
681    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
682        match self {
683            Range::IntRange(range) => write!(f, "{range}"),
684            Range::FloatRange(range) => write!(f, "{range}"),
685        }
686    }
687}
688
689impl From<IntRange> for Range {
690    fn from(range: IntRange) -> Self {
691        Self::IntRange(range)
692    }
693}
694
695impl From<FloatRange> for Range {
696    fn from(range: FloatRange) -> Self {
697        Self::FloatRange(range)
698    }
699}
700
701pub enum Iter {
702    IntIter(int_range::Iter, Span),
703    FloatIter(float_range::Iter, Span),
704}
705
706impl Iterator for Iter {
707    type Item = Value;
708
709    fn next(&mut self) -> Option<Self::Item> {
710        match self {
711            Iter::IntIter(iter, span) => iter.next().map(|val| Value::int(val, *span)),
712            Iter::FloatIter(iter, span) => iter.next().map(|val| Value::float(val, *span)),
713        }
714    }
715}