Skip to main content

nu_protocol/value/
range.rs

1//! A Range is an iterator over integers or floats.
2
3use crate::{ShellError, Signals, Span, Value, ast::RangeInclusion};
4use core::ops::Bound;
5use serde::{Deserialize, Serialize};
6use std::{cmp::Ordering, fmt::Display, str::FromStr};
7use winnow::Parser;
8
9mod int_range {
10    use crate::{FromValue, ShellError, Signals, Span, Value, ast::RangeInclusion};
11    use serde::{Deserialize, Serialize};
12    use std::{cmp::Ordering, fmt::Display, ops::Bound};
13
14    use super::Range;
15
16    #[derive(Debug, Clone, Copy, Serialize, Deserialize)]
17    pub struct IntRange {
18        pub(crate) start: i64,
19        pub(crate) step: i64,
20        pub(crate) end: Bound<i64>,
21    }
22
23    impl IntRange {
24        pub fn new(
25            start: Value,
26            next: Value,
27            end: Value,
28            inclusion: RangeInclusion,
29            span: Span,
30        ) -> Result<Self, ShellError> {
31            fn to_int(value: Value) -> Result<Option<i64>, ShellError> {
32                match value {
33                    Value::Int { val, .. } => Ok(Some(val)),
34                    Value::Nothing { .. } => Ok(None),
35                    val => Err(ShellError::CantConvert {
36                        to_type: "int".into(),
37                        from_type: val.get_type().to_string(),
38                        span: val.span(),
39                        help: None,
40                    }),
41                }
42            }
43
44            let start = to_int(start)?.unwrap_or(0);
45
46            let next_span = next.span();
47            let next = to_int(next)?;
48            if next.is_some_and(|next| next == start) {
49                return Err(ShellError::CannotCreateRange { span: next_span });
50            }
51
52            let end = to_int(end)?;
53
54            let step = match (next, end) {
55                (Some(next), Some(end)) => {
56                    if (next < start) != (end < start) {
57                        return Err(ShellError::CannotCreateRange { span });
58                    }
59                    next - start
60                }
61                (Some(next), None) => next - start,
62                (None, Some(end)) => {
63                    if end < start {
64                        -1
65                    } else {
66                        1
67                    }
68                }
69                (None, None) => 1,
70            };
71
72            let end = if let Some(end) = end {
73                match inclusion {
74                    RangeInclusion::Inclusive => Bound::Included(end),
75                    RangeInclusion::RightExclusive => Bound::Excluded(end),
76                }
77            } else {
78                Bound::Unbounded
79            };
80
81            Ok(Self { start, step, end })
82        }
83
84        pub fn start(&self) -> i64 {
85            self.start
86        }
87
88        // Resolves the absolute start position given the length of the input value
89        pub fn absolute_start(&self, len: u64) -> u64 {
90            match self.start {
91                start if start < 0 => len.saturating_sub(start.unsigned_abs()),
92                start => len.min(start.unsigned_abs()),
93            }
94        }
95
96        /// Returns the distance between the start and end of the range
97        /// The result will always be 0 or positive
98        pub fn distance(&self) -> Bound<u64> {
99            match self.end {
100                Bound::Unbounded => Bound::Unbounded,
101                Bound::Included(end) | Bound::Excluded(end) if self.start > end => {
102                    Bound::Excluded(0)
103                }
104                Bound::Included(end) => Bound::Included((end - self.start) as u64),
105                Bound::Excluded(end) => Bound::Excluded((end - self.start) as u64),
106            }
107        }
108
109        pub fn end(&self) -> Bound<i64> {
110            self.end
111        }
112
113        pub fn absolute_end(&self, len: u64) -> Bound<u64> {
114            match self.end {
115                Bound::Unbounded => Bound::Unbounded,
116                Bound::Included(i) => match i {
117                    _ if len == 0 => Bound::Excluded(0),
118                    i if i < 0 => Bound::Excluded(len.saturating_sub((i + 1).unsigned_abs())),
119                    i => Bound::Included((len.saturating_sub(1)).min(i.unsigned_abs())),
120                },
121                Bound::Excluded(i) => Bound::Excluded(match i {
122                    i if i < 0 => len.saturating_sub(i.unsigned_abs()),
123                    i => len.min(i.unsigned_abs()),
124                }),
125            }
126        }
127
128        pub fn absolute_bounds(&self, len: usize) -> (usize, Bound<usize>) {
129            let start = self.absolute_start(len as u64) as usize;
130            let end = self.absolute_end(len as u64).map(|e| e as usize);
131            match end {
132                Bound::Excluded(end) | Bound::Included(end) if end < start => {
133                    (start, Bound::Excluded(start))
134                }
135                Bound::Excluded(end) => (start, Bound::Excluded(end)),
136                Bound::Included(end) => (start, Bound::Included(end)),
137                Bound::Unbounded => (start, Bound::Unbounded),
138            }
139        }
140
141        pub fn step(&self) -> i64 {
142            self.step
143        }
144
145        pub fn is_unbounded(&self) -> bool {
146            self.end == Bound::Unbounded
147        }
148
149        pub fn is_relative(&self) -> bool {
150            self.is_start_relative() || self.is_end_relative()
151        }
152
153        pub fn is_start_relative(&self) -> bool {
154            self.start < 0
155        }
156
157        pub fn is_end_relative(&self) -> bool {
158            match self.end {
159                Bound::Included(end) | Bound::Excluded(end) => end < 0,
160                _ => false,
161            }
162        }
163
164        pub fn contains(&self, value: i64) -> bool {
165            if self.step < 0 {
166                // Decreasing range
167                if value > self.start {
168                    return false;
169                }
170                match self.end {
171                    Bound::Included(end) if value < end => return false,
172                    Bound::Excluded(end) if value <= end => return false,
173                    _ => {}
174                };
175            } else {
176                // Increasing range
177                if value < self.start {
178                    return false;
179                }
180                match self.end {
181                    Bound::Included(end) if value > end => return false,
182                    Bound::Excluded(end) if value >= end => return false,
183                    _ => {}
184                };
185            }
186            (value - self.start) % self.step == 0
187        }
188
189        pub fn into_range_iter(self, signals: Signals) -> Iter {
190            Iter {
191                current: Some(self.start),
192                step: self.step,
193                end: self.end,
194                signals,
195            }
196        }
197    }
198
199    impl Ord for IntRange {
200        fn cmp(&self, other: &Self) -> Ordering {
201            // Ranges are compared roughly according to their list representation.
202            // Compare in order:
203            // - the head element (start)
204            // - the tail elements (step)
205            // - the length (end)
206            self.start
207                .cmp(&other.start)
208                .then(self.step.cmp(&other.step))
209                .then_with(|| match (self.end, other.end) {
210                    (Bound::Included(l), Bound::Included(r))
211                    | (Bound::Excluded(l), Bound::Excluded(r)) => {
212                        let ord = l.cmp(&r);
213                        if self.step < 0 { ord.reverse() } else { ord }
214                    }
215                    (Bound::Included(l), Bound::Excluded(r)) => match l.cmp(&r) {
216                        Ordering::Equal => Ordering::Greater,
217                        ord if self.step < 0 => ord.reverse(),
218                        ord => ord,
219                    },
220                    (Bound::Excluded(l), Bound::Included(r)) => match l.cmp(&r) {
221                        Ordering::Equal => Ordering::Less,
222                        ord if self.step < 0 => ord.reverse(),
223                        ord => ord,
224                    },
225                    (Bound::Included(_), Bound::Unbounded) => Ordering::Less,
226                    (Bound::Excluded(_), Bound::Unbounded) => Ordering::Less,
227                    (Bound::Unbounded, Bound::Included(_)) => Ordering::Greater,
228                    (Bound::Unbounded, Bound::Excluded(_)) => Ordering::Greater,
229                    (Bound::Unbounded, Bound::Unbounded) => Ordering::Equal,
230                })
231        }
232    }
233
234    impl PartialOrd for IntRange {
235        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
236            Some(self.cmp(other))
237        }
238    }
239
240    impl PartialEq for IntRange {
241        fn eq(&self, other: &Self) -> bool {
242            self.start == other.start && self.step == other.step && self.end == other.end
243        }
244    }
245
246    impl Eq for IntRange {}
247
248    impl Display for IntRange {
249        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250            write!(f, "{}..", self.start)?;
251            if self.step != 1 {
252                write!(f, "{}..", self.start + self.step)?;
253            }
254            match self.end {
255                Bound::Included(end) => write!(f, "{end}"),
256                Bound::Excluded(end) => write!(f, "<{end}"),
257                Bound::Unbounded => Ok(()),
258            }
259        }
260    }
261
262    impl FromValue for IntRange {
263        fn from_value(v: Value) -> Result<Self, ShellError> {
264            let span = v.span();
265            let range = Range::from_value(v)?;
266            match range {
267                Range::IntRange(v) => Ok(v),
268                Range::FloatRange(_) => Err(ShellError::TypeMismatch {
269                    err_message: "expected an int range".into(),
270                    span,
271                }),
272            }
273        }
274    }
275
276    pub struct Iter {
277        current: Option<i64>,
278        step: i64,
279        end: Bound<i64>,
280        signals: Signals,
281    }
282
283    impl Iterator for Iter {
284        type Item = i64;
285
286        fn next(&mut self) -> Option<Self::Item> {
287            if let Some(current) = self.current {
288                let not_end = match (self.step < 0, self.end) {
289                    (true, Bound::Included(end)) => current >= end,
290                    (true, Bound::Excluded(end)) => current > end,
291                    (false, Bound::Included(end)) => current <= end,
292                    (false, Bound::Excluded(end)) => current < end,
293                    (_, Bound::Unbounded) => true, // will stop once integer overflows
294                };
295
296                if not_end && !self.signals.interrupted() {
297                    self.current = current.checked_add(self.step);
298                    Some(current)
299                } else {
300                    self.current = None;
301                    None
302                }
303            } else {
304                None
305            }
306        }
307    }
308}
309
310mod float_range {
311    use crate::{IntRange, Range, ShellError, Signals, Span, Value, ast::RangeInclusion};
312    use nu_utils::ObviousFloat;
313    use serde::{Deserialize, Serialize};
314    use std::{cmp::Ordering, fmt::Display, ops::Bound};
315
316    #[derive(Debug, Clone, Copy, Serialize, Deserialize)]
317    pub struct FloatRange {
318        pub(crate) start: f64,
319        pub(crate) step: f64,
320        pub(crate) end: Bound<f64>,
321    }
322
323    impl FloatRange {
324        pub fn new(
325            start: Value,
326            next: Value,
327            end: Value,
328            inclusion: RangeInclusion,
329            span: Span,
330        ) -> Result<Self, ShellError> {
331            fn to_float(value: Value) -> Result<Option<f64>, ShellError> {
332                match value {
333                    Value::Float { val, .. } => Ok(Some(val)),
334                    Value::Int { val, .. } => Ok(Some(val as f64)),
335                    Value::Nothing { .. } => Ok(None),
336                    val => Err(ShellError::CantConvert {
337                        to_type: "float".into(),
338                        from_type: val.get_type().to_string(),
339                        span: val.span(),
340                        help: None,
341                    }),
342                }
343            }
344
345            // `start` must be finite (not NaN or infinity).
346            // `next` must be finite and not equal to `start`.
347            // `end` must not be NaN (but can be infinite).
348            //
349            // TODO: better error messages for the restrictions above
350
351            let start_span = start.span();
352            let start = to_float(start)?.unwrap_or(0.0);
353            if !start.is_finite() {
354                return Err(ShellError::CannotCreateRange { span: start_span });
355            }
356
357            let end_span = end.span();
358            let end = to_float(end)?;
359            if end.is_some_and(f64::is_nan) {
360                return Err(ShellError::CannotCreateRange { span: end_span });
361            }
362
363            let next_span = next.span();
364            let next = to_float(next)?;
365            if next.is_some_and(|next| next == start || !next.is_finite()) {
366                return Err(ShellError::CannotCreateRange { span: next_span });
367            }
368
369            let step = match (next, end) {
370                (Some(next), Some(end)) => {
371                    if (next < start) != (end < start) {
372                        return Err(ShellError::CannotCreateRange { span });
373                    }
374                    next - start
375                }
376                (Some(next), None) => next - start,
377                (None, Some(end)) => {
378                    let diff = end - start;
379                    if diff == 0.0 {
380                        return Err(ShellError::CannotCreateRange { span });
381                    }
382                    if diff.abs() < 1.0 {
383                        // For float ranges with small differences, compute a natural
384                        // step based on the order of magnitude of the difference,
385                        // so that `0.1..0.3` yields 0.1, 0.2, 0.3.
386                        let magnitude = 10.0_f64.powf(diff.abs().log10().floor());
387                        diff.signum() * magnitude
388                    } else if diff > 0.0 {
389                        1.0
390                    } else {
391                        -1.0
392                    }
393                }
394                (None, None) => 1.0,
395            };
396
397            let end = if let Some(end) = end {
398                if end.is_infinite() {
399                    Bound::Unbounded
400                } else {
401                    match inclusion {
402                        RangeInclusion::Inclusive => Bound::Included(end),
403                        RangeInclusion::RightExclusive => Bound::Excluded(end),
404                    }
405                }
406            } else {
407                Bound::Unbounded
408            };
409
410            Ok(Self { start, step, end })
411        }
412
413        pub fn start(&self) -> f64 {
414            self.start
415        }
416
417        pub fn end(&self) -> Bound<f64> {
418            self.end
419        }
420
421        pub fn step(&self) -> f64 {
422            self.step
423        }
424
425        pub fn is_unbounded(&self) -> bool {
426            self.end == Bound::Unbounded
427        }
428
429        pub fn contains(&self, value: f64) -> bool {
430            if self.step < 0.0 {
431                // Decreasing range
432                if value > self.start {
433                    return false;
434                }
435                match self.end {
436                    Bound::Included(end) if value <= end => return false,
437                    Bound::Excluded(end) if value < end => return false,
438                    _ => {}
439                };
440            } else {
441                // Increasing range
442                if value < self.start {
443                    return false;
444                }
445                match self.end {
446                    Bound::Included(end) if value >= end => return false,
447                    Bound::Excluded(end) if value > end => return false,
448                    _ => {}
449                };
450            }
451            ((value - self.start) % self.step).abs() < f64::EPSILON
452        }
453
454        pub fn into_range_iter(self, signals: Signals) -> Iter {
455            // Determine rounding factor from the step's decimal precision.
456            // Only applies when step < 1.0 (fractional steps) to clean up
457            // floating-point artifacts like 0.30000000000000004.
458            let round_factor = if self.step.abs() >= 1.0 || self.step == 0.0 {
459                0.0 // sentinel: no rounding
460            } else {
461                let precision = (-self.step.abs().log10()).max(0.0).ceil() as i32;
462                10.0_f64.powi(precision)
463            };
464            Iter {
465                start: self.start,
466                step: self.step,
467                end: self.end,
468                iter: Some(0),
469                round_factor,
470                signals,
471            }
472        }
473    }
474
475    impl Ord for FloatRange {
476        fn cmp(&self, other: &Self) -> Ordering {
477            fn float_cmp(a: f64, b: f64) -> Ordering {
478                // There is no way a `FloatRange` can have NaN values:
479                // - `FloatRange::new` ensures no values are NaN.
480                // - `From<IntRange> for FloatRange` cannot give NaNs either.
481                // - There are no other ways to create a `FloatRange`.
482                // - There is no way to modify values of a `FloatRange`.
483                a.partial_cmp(&b).expect("not NaN")
484            }
485
486            // Ranges are compared roughly according to their list representation.
487            // Compare in order:
488            // - the head element (start)
489            // - the tail elements (step)
490            // - the length (end)
491            float_cmp(self.start, other.start)
492                .then(float_cmp(self.step, other.step))
493                .then_with(|| match (self.end, other.end) {
494                    (Bound::Included(l), Bound::Included(r))
495                    | (Bound::Excluded(l), Bound::Excluded(r)) => {
496                        let ord = float_cmp(l, r);
497                        if self.step < 0.0 { ord.reverse() } else { ord }
498                    }
499                    (Bound::Included(l), Bound::Excluded(r)) => match float_cmp(l, r) {
500                        Ordering::Equal => Ordering::Greater,
501                        ord if self.step < 0.0 => ord.reverse(),
502                        ord => ord,
503                    },
504                    (Bound::Excluded(l), Bound::Included(r)) => match float_cmp(l, r) {
505                        Ordering::Equal => Ordering::Less,
506                        ord if self.step < 0.0 => ord.reverse(),
507                        ord => ord,
508                    },
509                    (Bound::Included(_), Bound::Unbounded) => Ordering::Less,
510                    (Bound::Excluded(_), Bound::Unbounded) => Ordering::Less,
511                    (Bound::Unbounded, Bound::Included(_)) => Ordering::Greater,
512                    (Bound::Unbounded, Bound::Excluded(_)) => Ordering::Greater,
513                    (Bound::Unbounded, Bound::Unbounded) => Ordering::Equal,
514                })
515        }
516    }
517
518    impl PartialOrd for FloatRange {
519        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
520            Some(self.cmp(other))
521        }
522    }
523
524    impl PartialEq for FloatRange {
525        fn eq(&self, other: &Self) -> bool {
526            self.start == other.start && self.step == other.step && self.end == other.end
527        }
528    }
529
530    impl Eq for FloatRange {}
531
532    impl Display for FloatRange {
533        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
534            write!(f, "{}..", ObviousFloat(self.start))?;
535            if self.step != 1f64 {
536                write!(f, "{}..", ObviousFloat(self.start + self.step))?;
537            }
538            match self.end {
539                Bound::Included(end) => write!(f, "{}", ObviousFloat(end)),
540                Bound::Excluded(end) => write!(f, "<{}", ObviousFloat(end)),
541                Bound::Unbounded => Ok(()),
542            }
543        }
544    }
545
546    impl From<IntRange> for FloatRange {
547        fn from(range: IntRange) -> Self {
548            Self {
549                start: range.start as f64,
550                step: range.step as f64,
551                end: match range.end {
552                    Bound::Included(b) => Bound::Included(b as f64),
553                    Bound::Excluded(b) => Bound::Excluded(b as f64),
554                    Bound::Unbounded => Bound::Unbounded,
555                },
556            }
557        }
558    }
559
560    impl From<Range> for FloatRange {
561        fn from(range: Range) -> Self {
562            match range {
563                Range::IntRange(range) => range.into(),
564                Range::FloatRange(range) => range,
565            }
566        }
567    }
568
569    pub struct Iter {
570        start: f64,
571        step: f64,
572        end: Bound<f64>,
573        iter: Option<u64>,
574        round_factor: f64,
575        signals: Signals,
576    }
577
578    impl Iterator for Iter {
579        type Item = f64;
580
581        fn next(&mut self) -> Option<Self::Item> {
582            if let Some(iter) = self.iter {
583                let current = self.start + self.step * iter as f64;
584
585                // Snap only tiny floating-point drift (quotient very close to integer).
586                let quotient = current / self.step;
587                let value = if (quotient - quotient.round()).abs() < 1e-10 {
588                    quotient.round() * self.step
589                } else {
590                    current
591                };
592
593                // Round to step's decimal precision if applicable, to avoid
594                // displaying artifacts like 0.30000000000000004.
595                let value = if self.round_factor > 0.0 {
596                    (value * self.round_factor).round() / self.round_factor
597                } else {
598                    value
599                };
600
601                // Use an epsilon tolerance to handle floating-point precision
602                // issues in the end-bound comparison.
603                const EPS: f64 = f64::EPSILON * 100.0;
604                let not_end = match (self.step < 0.0, self.end) {
605                    (true, Bound::Included(end)) => value + EPS >= end,
606                    (true, Bound::Excluded(end)) => value - EPS > end,
607                    (false, Bound::Included(end)) => value <= end + EPS,
608                    (false, Bound::Excluded(end)) => value < end - EPS,
609                    (_, Bound::Unbounded) => value.is_finite(),
610                };
611
612                if not_end && !self.signals.interrupted() {
613                    self.iter = iter.checked_add(1);
614                    Some(value)
615                } else {
616                    self.iter = None;
617                    None
618                }
619            } else {
620                None
621            }
622        }
623    }
624}
625
626pub use float_range::FloatRange;
627pub use int_range::IntRange;
628
629#[derive(Debug, Clone, Copy)]
630pub enum Range {
631    IntRange(IntRange),
632    FloatRange(FloatRange),
633}
634
635impl Range {
636    pub fn new(
637        start: Value,
638        next: Value,
639        end: Value,
640        inclusion: RangeInclusion,
641        span: Span,
642    ) -> Result<Self, ShellError> {
643        // promote to float range if any Value is float
644        if matches!(start, Value::Float { .. })
645            || matches!(next, Value::Float { .. })
646            || matches!(end, Value::Float { .. })
647        {
648            FloatRange::new(start, next, end, inclusion, span).map(Self::FloatRange)
649        } else {
650            IntRange::new(start, next, end, inclusion, span).map(Self::IntRange)
651        }
652    }
653
654    pub fn new_int(
655        start: impl Into<Option<i64>>,
656        next: impl Into<Option<i64>>,
657        end: impl Into<Option<Bound<i64>>>,
658    ) -> Self {
659        let start = start.into().unwrap_or(0);
660        let end = end.into().unwrap_or(Bound::Unbounded);
661        let step = next.into().map(|next| next - start).unwrap_or(match end {
662            Bound::Unbounded => 1,
663            Bound::Included(end) | Bound::Excluded(end) if start <= end => 1,
664            _ => -1,
665        });
666        Range::IntRange(IntRange { start, step, end })
667    }
668
669    pub fn new_float(
670        start: impl Into<Option<f64>>,
671        next: impl Into<Option<f64>>,
672        end: impl Into<Option<Bound<f64>>>,
673    ) -> Self {
674        let start = start.into().unwrap_or(0.0);
675        let end = end.into().unwrap_or(Bound::Unbounded);
676        let step = next.into().map(|next| next - start).unwrap_or(match end {
677            Bound::Unbounded => 1.0,
678            Bound::Included(end) | Bound::Excluded(end) if start <= end => 1.0,
679            _ => -1.0,
680        });
681        Range::FloatRange(FloatRange { start, step, end })
682    }
683
684    pub fn contains(&self, value: &Value) -> bool {
685        match (self, value) {
686            (Self::IntRange(range), Value::Int { val, .. }) => range.contains(*val),
687            (Self::IntRange(range), Value::Float { val, .. }) => {
688                FloatRange::from(*range).contains(*val)
689            }
690            (Self::FloatRange(range), Value::Int { val, .. }) => range.contains(*val as f64),
691            (Self::FloatRange(range), Value::Float { val, .. }) => range.contains(*val),
692            _ => false,
693        }
694    }
695
696    pub fn is_bounded(&self) -> bool {
697        match self {
698            Range::IntRange(range) => range.end() != Bound::<i64>::Unbounded,
699            Range::FloatRange(range) => range.end() != Bound::<f64>::Unbounded,
700        }
701    }
702
703    pub fn into_range_iter(self, span: Span, signals: Signals) -> Iter {
704        match self {
705            Range::IntRange(range) => Iter::IntIter(range.into_range_iter(signals), span),
706            Range::FloatRange(range) => Iter::FloatIter(range.into_range_iter(signals), span),
707        }
708    }
709
710    /// Returns an estimate of the memory size used by this Range in bytes
711    pub fn memory_size(&self) -> usize {
712        std::mem::size_of::<Self>()
713    }
714}
715
716impl Ord for Range {
717    fn cmp(&self, other: &Self) -> Ordering {
718        match (self, other) {
719            (Range::IntRange(l), Range::IntRange(r)) => l.cmp(r),
720            (Range::FloatRange(l), Range::FloatRange(r)) => l.cmp(r),
721            (Range::IntRange(int), Range::FloatRange(float)) => FloatRange::from(*int).cmp(float),
722            (Range::FloatRange(float), Range::IntRange(int)) => float.cmp(&FloatRange::from(*int)),
723        }
724    }
725}
726
727impl PartialOrd for Range {
728    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
729        Some(self.cmp(other))
730    }
731}
732
733impl PartialEq for Range {
734    fn eq(&self, other: &Self) -> bool {
735        match (self, other) {
736            (Range::IntRange(l), Range::IntRange(r)) => l == r,
737            (Range::FloatRange(l), Range::FloatRange(r)) => l == r,
738            (Range::IntRange(int), Range::FloatRange(float)) => FloatRange::from(*int) == *float,
739            (Range::FloatRange(float), Range::IntRange(int)) => *float == FloatRange::from(*int),
740        }
741    }
742}
743
744impl Eq for Range {}
745
746impl Display for Range {
747    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
748        match self {
749            Range::IntRange(range) => write!(f, "{range}"),
750            Range::FloatRange(range) => write!(f, "{range}"),
751        }
752    }
753}
754
755#[derive(Debug, thiserror::Error)]
756#[error("could not parse range {attempted:?}")]
757pub struct RangeParseError {
758    attempted: String,
759}
760
761impl FromStr for Range {
762    type Err = RangeParseError;
763
764    fn from_str(s: &str) -> Result<Self, Self::Err> {
765        parse::range.parse(s).map_err(|_| RangeParseError {
766            attempted: s.to_owned(),
767        })
768    }
769}
770
771impl Serialize for Range {
772    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
773    where
774        S: serde::Serializer,
775    {
776        self.to_string().serialize(serializer)
777    }
778}
779
780impl<'de> Deserialize<'de> for Range {
781    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
782    where
783        D: serde::Deserializer<'de>,
784    {
785        let s = String::deserialize(deserializer)?;
786        Range::from_str(&s).map_err(serde::de::Error::custom)
787    }
788}
789
790impl From<IntRange> for Range {
791    fn from(range: IntRange) -> Self {
792        Self::IntRange(range)
793    }
794}
795
796impl From<FloatRange> for Range {
797    fn from(range: FloatRange) -> Self {
798        Self::FloatRange(range)
799    }
800}
801
802pub enum Iter {
803    IntIter(int_range::Iter, Span),
804    FloatIter(float_range::Iter, Span),
805}
806
807impl Iterator for Iter {
808    type Item = Value;
809
810    fn next(&mut self) -> Option<Self::Item> {
811        match self {
812            Iter::IntIter(iter, span) => iter.next().map(|val| Value::int(val, *span)),
813            Iter::FloatIter(iter, span) => iter.next().map(|val| Value::float(val, *span)),
814        }
815    }
816}
817
818mod parse {
819    use super::*;
820    use winnow::{
821        Result,
822        ascii::*,
823        combinator::*,
824        error::{StrContext, StrContextValue},
825    };
826
827    #[derive(Copy, Clone)]
828    enum Number {
829        Int(i64),
830        Float(f64),
831    }
832
833    // only simple numbers for now
834    fn number(input: &mut &str) -> Result<Number> {
835        fn float(input: &mut &str) -> Result<f64> {
836            (opt("-"), digit0, ".", digit1)
837                .take()
838                .parse_to()
839                .parse_next(input)
840        }
841
842        alt((float.map(Number::Float), dec_int.map(Number::Int))).parse_next(input)
843    }
844
845    struct Components {
846        start: Option<Number>,
847        step: Option<Number>,
848        end: Option<Number>,
849        exclusive: bool,
850    }
851
852    fn components(input: &mut &str) -> Result<Components> {
853        let start = opt(number).parse_next(input)?;
854        "..".parse_next(input)?;
855        if opt("<").parse_next(input)?.is_some() {
856            let end = opt(number).parse_next(input)?;
857            eof.parse_next(input)?;
858            return Ok(Components {
859                start,
860                step: None,
861                end,
862                exclusive: true,
863            });
864        }
865
866        if opt(eof).parse_next(input)?.is_some() {
867            return Ok(Components {
868                start,
869                step: None,
870                end: None,
871                exclusive: false,
872            });
873        }
874
875        let step_or_end = number.parse_next(input)?;
876        if opt(eof).parse_next(input)?.is_some() {
877            return Ok(Components {
878                start,
879                step: None,
880                end: step_or_end.into(),
881                exclusive: false,
882            });
883        }
884
885        "..".parse_next(input)?;
886        if opt("<").parse_next(input)?.is_some() {
887            let end = opt(number).parse_next(input)?;
888            eof.parse_next(input)?;
889            return Ok(Components {
890                start,
891                step: step_or_end.into(),
892                end,
893                exclusive: true,
894            });
895        }
896
897        let end = opt(number).parse_next(input)?;
898        eof.parse_next(input)?;
899        Ok(Components {
900            start,
901            step: step_or_end.into(),
902            end,
903            exclusive: false,
904        })
905    }
906
907    pub fn range(input: &mut &str) -> Result<Range> {
908        let components = components.parse_next(input)?;
909        if components.start.is_none() && components.end.is_none() {
910            fail.context(StrContext::Expected(StrContextValue::Description(
911                "needs bound either at start or end",
912            )))
913            .parse_next(input)?;
914        }
915
916        let use_float = matches!(components.start, Some(Number::Float(_)))
917            || matches!(components.step, Some(Number::Float(_)))
918            || matches!(components.end, Some(Number::Float(_)));
919
920        let range = if use_float {
921            let start = match components.start {
922                Some(Number::Float(start)) => Some(start),
923                Some(Number::Int(start)) => Some(start as f64),
924                None => None,
925            };
926
927            let step = match components.step {
928                Some(Number::Float(step)) => Some(step),
929                Some(Number::Int(step)) => Some(step as f64),
930                None => None,
931            };
932
933            let end = match (components.end, components.exclusive) {
934                (Some(Number::Float(end)), false) => Bound::Included(end),
935                (Some(Number::Float(end)), true) => Bound::Excluded(end),
936                (Some(Number::Int(end)), false) => Bound::Included(end as f64),
937                (Some(Number::Int(end)), true) => Bound::Excluded(end as f64),
938                (None, _) => Bound::Unbounded,
939            };
940
941            Range::new_float(start, step, end)
942        } else {
943            let start = match components.start {
944                Some(Number::Float(_)) => unreachable!("will use float if this is float"),
945                Some(Number::Int(start)) => Some(start),
946                None => None,
947            };
948
949            let step = match components.step {
950                Some(Number::Float(_)) => unreachable!("will use float if this is float"),
951                Some(Number::Int(step)) => Some(step),
952                None => None,
953            };
954
955            let end = match (components.end, components.exclusive) {
956                (Some(Number::Float(_)), _) => unreachable!("will use float if this is float"),
957                (Some(Number::Int(end)), false) => Bound::Included(end),
958                (Some(Number::Int(end)), true) => Bound::Excluded(end),
959                (None, _) => Bound::Unbounded,
960            };
961
962            Range::new_int(start, step, end)
963        };
964
965        Ok(range)
966    }
967}
968
969#[cfg(test)]
970mod tests {
971    use super::*;
972    use crate::Signals;
973
974    fn collect_float_range(start: f64, step: f64, end: f64, inclusive: bool) -> Vec<f64> {
975        let end = if inclusive {
976            Bound::Included(end)
977        } else {
978            Bound::Excluded(end)
979        };
980        let range = FloatRange { start, step, end };
981        range
982            .into_range_iter(Signals::empty())
983            .collect::<Vec<f64>>()
984    }
985
986    #[test]
987    fn float_range_small_step_inclusive() {
988        let result = collect_float_range(0.1, 0.1, 0.3, true);
989        assert_eq!(result.len(), 3);
990        assert!((result[0] - 0.1).abs() < 1e-15);
991        assert!((result[1] - 0.2).abs() < 1e-15);
992        assert!((result[2] - 0.3).abs() < 1e-15);
993    }
994
995    #[test]
996    fn float_range_tiny_step_inclusive() {
997        let result = collect_float_range(0.001, 0.001, 0.005, true);
998        assert_eq!(result.len(), 5);
999        assert!((result[0] - 0.001).abs() < 1e-15);
1000        assert!((result[1] - 0.002).abs() < 1e-15);
1001        assert!((result[2] - 0.003).abs() < 1e-15);
1002        assert!((result[3] - 0.004).abs() < 1e-15);
1003        assert!((result[4] - 0.005).abs() < 1e-15);
1004    }
1005
1006    #[test]
1007    fn float_range_integer_step_noninteger_start() {
1008        let result = collect_float_range(1.8, 1.0, 3.8, true);
1009        assert_eq!(result.len(), 3);
1010        assert!((result[0] - 1.8).abs() < 1e-15);
1011        assert!((result[1] - 2.8).abs() < 1e-15);
1012        assert!((result[2] - 3.8).abs() < 1e-15);
1013    }
1014
1015    #[test]
1016    fn float_range_decreasing() {
1017        let result = collect_float_range(0.3, -0.1, 0.1, true);
1018        assert_eq!(result.len(), 3);
1019        assert!((result[0] - 0.3).abs() < 1e-15);
1020        assert!((result[1] - 0.2).abs() < 1e-15);
1021        assert!((result[2] - 0.1).abs() < 1e-15);
1022    }
1023
1024    #[test]
1025    fn float_range_explicit_step_clean_values() {
1026        let result = collect_float_range(0.1, 0.2, 0.3, false);
1027        assert_eq!(result.len(), 1);
1028        assert!((result[0] - 0.1).abs() < 1e-15);
1029    }
1030
1031    #[test]
1032    fn float_range_rounds_last_value() {
1033        // 0.1 + 0.1*2 = 0.30000000000000004 without rounding;
1034        // verify rounding produces exactly 0.3
1035        let result = collect_float_range(0.1, 0.1, 0.3, true);
1036        assert_eq!(result[2], 0.3);
1037    }
1038
1039    #[test]
1040    fn float_range_clean_serialization() {
1041        // Verify all values in a small-step range are clean (no floating-point artifacts)
1042        let result = collect_float_range(0.0, 0.1, 0.5, true);
1043        assert_eq!(result.len(), 6);
1044        for (i, &val) in result.iter().enumerate() {
1045            let expected = i as f64 * 0.1;
1046            assert!(
1047                (val - expected).abs() < 1e-15,
1048                "at index {i}: expected {expected}, got {val}"
1049            );
1050        }
1051    }
1052}