Skip to main content

nanvm_lib/vm/
position.rs

1//! A position argument, read from the `Any` a built-in is passed to the index
2//! a collection can use. Arrays and strings read theirs alike, so neither owns
3//! the reading.
4
5use core::ops::Range;
6
7use crate::vm::{Any, IVm};
8
9impl<A: IVm> Any<A> {
10    /// `ToIntegerOrInfinity(ToNumber(self))`, the reading every index, count
11    /// and position argument starts from. The one throw is `ToNumber`'s, a
12    /// bigint's `TypeError` among them.
13    pub fn to_integer_or_infinity(self) -> Result<f64, Any<A>> {
14        Ok(self.to_number()?.to_integer_or_infinity().into())
15    }
16}
17
18/// The position a relative index argument names in a collection of `len`
19/// elements: [`Any::to_integer_or_infinity`] of the argument, counted from
20/// the end when negative, as `at`, `slice`, `includes`, `indexOf`,
21/// `lastIndexOf`, `toSpliced` and `with` all read one. The answer is
22/// unclamped, an infinity included, so each built-in clamps or range-checks
23/// it its own way; the arithmetic is on `f64`, which holds a length exactly.
24pub(crate) fn relative<A: IVm>(index: Any<A>, len: u32) -> Result<f64, Any<A>> {
25    let relative = index.to_integer_or_infinity()?;
26    Ok(if relative >= 0.0 {
27        relative
28    } else {
29        f64::from(len) + relative
30    })
31}
32
33/// A position clamped into `[0, len]`, the range a start position of
34/// `slice`, `includes`, `indexOf` and `toSpliced` is read into.
35pub(crate) fn clamped(k: f64, len: u32) -> u32 {
36    k.clamp(0.0, f64::from(len)) as u32
37}
38
39/// The index `k` names, when it is inside `0..len`: the element an `at`
40/// reads, or the one a `with` replaces.
41pub(crate) fn in_range(k: f64, len: u32) -> Option<u32> {
42    (0.0..f64::from(len)).contains(&k).then_some(k as u32)
43}
44
45/// `start..end` as `slice` reads them: two [`relative`] positions clamped
46/// into the collection, `start` converted first, and `end` the length when
47/// `undefined`. An `end` before `start` is the empty range at `start`.
48pub(crate) fn relative_range<A: IVm>(
49    start: Any<A>,
50    end: Any<A>,
51    len: u32,
52) -> Result<Range<u32>, Any<A>> {
53    let from = clamped(relative(start, len)?, len);
54    let to = end
55        .defined()
56        .map(|e| relative(e, len))
57        .transpose()?
58        .map_or(len, |k| clamped(k, len));
59    Ok(from..to.max(from))
60}
61
62#[cfg(test)]
63mod tests {
64    use super::{clamped, in_range, relative, relative_range};
65    use crate::{
66        naive::Naive,
67        vm::{Any, Nullish, ToAny, error, unstable::bigint_any},
68    };
69
70    type A = Naive;
71
72    fn r(v: Any<A>) -> Result<f64, Any<A>> {
73        relative(v, 3)
74    }
75
76    #[test]
77    fn to_integer_or_infinity() {
78        assert_eq!(1.7.to_any::<A>().to_integer_or_infinity(), Ok(1.0));
79        assert_eq!((-1.0f64).to_any::<A>().to_integer_or_infinity(), Ok(-1.0));
80        assert_eq!(Any::<A>::undefined().to_integer_or_infinity(), Ok(0.0));
81        let one: Any<A> = bigint_any(1);
82        assert_eq!(one.to_integer_or_infinity(), Err(error::bigint_to_number()));
83    }
84
85    #[test]
86    fn from_the_start_and_the_end() {
87        assert_eq!(r(1.0.to_any()), Ok(1.0));
88        assert_eq!(r((-1.0f64).to_any()), Ok(2.0));
89        assert_eq!(r((-4.0f64).to_any()), Ok(-1.0));
90        assert_eq!(r(1.7.to_any()), Ok(1.0));
91        assert_eq!(r("2".into()), Ok(2.0));
92        assert_eq!(r(Any::undefined()), Ok(0.0));
93        assert_eq!(r(f64::INFINITY.to_any()), Ok(f64::INFINITY));
94        assert_eq!(r(f64::NEG_INFINITY.to_any()), Ok(f64::NEG_INFINITY));
95    }
96
97    #[test]
98    fn bigint_throws() {
99        assert_eq!(r(bigint_any(1)), Err(error::bigint_to_number()));
100    }
101
102    #[test]
103    fn clamps_into_the_collection() {
104        assert_eq!(clamped(-1.0, 3), 0);
105        assert_eq!(clamped(f64::NEG_INFINITY, 3), 0);
106        assert_eq!(clamped(2.0, 3), 2);
107        assert_eq!(clamped(4.0, 3), 3);
108        assert_eq!(clamped(f64::INFINITY, 3), 3);
109    }
110
111    #[test]
112    fn inside_the_collection() {
113        assert_eq!(in_range(0.0, 3), Some(0));
114        assert_eq!(in_range(2.0, 3), Some(2));
115        assert_eq!(in_range(3.0, 3), None);
116        assert_eq!(in_range(-1.0, 3), None);
117        assert_eq!(in_range(f64::INFINITY, 3), None);
118        assert_eq!(in_range(f64::NEG_INFINITY, 3), None);
119        assert_eq!(in_range(0.0, 0), None);
120    }
121
122    #[test]
123    fn ranges() {
124        let range = |start: f64, end: Any<A>| relative_range(start.to_any(), end, 3);
125        assert_eq!(range(1.0, Any::undefined()), Ok(1..3));
126        assert_eq!(range(-2.0, (-1.0f64).to_any()), Ok(1..2));
127        assert_eq!(range(-9.0, 9.0.to_any()), Ok(0..3));
128        assert_eq!(range(2.0, 1.0.to_any()), Ok(2..2));
129        assert_eq!(range(0.0, Nullish::Null.to_any()), Ok(0..0));
130        assert_eq!(range(0.0, bigint_any(1)), Err(error::bigint_to_number()));
131    }
132}