Skip to main content

nanvm_lib/vm/any/
relational.rs

1use core::cmp::Ordering;
2
3use crate::vm::{
4    Any, IVm, ToAny, Unpacked, error, numeric_coercion::to_numeric, primitive::Primitive,
5    primitive_coercion::ToPrimitivePreferredType, string_numeric_literal::string_to_bigint,
6};
7
8impl<A: IVm> Any<A> {
9    /// `<`. Throws where `ToPrimitive` does, and where the text of a function
10    /// without text would be compared (`error::function_text`).
11    pub fn lt(self, rhs: Self) -> Result<Self, Self> {
12        Ok(matches!(compare(self, rhs)?, Some(Ordering::Less)).to_any())
13    }
14
15    /// `>`.
16    pub fn gt(self, rhs: Self) -> Result<Self, Self> {
17        Ok(matches!(compare(self, rhs)?, Some(Ordering::Greater)).to_any())
18    }
19
20    /// `<=`. Not `!(x > y)`: a `NaN` anywhere gives `false`.
21    pub fn le(self, rhs: Self) -> Result<Self, Self> {
22        Ok(matches!(compare(self, rhs)?, Some(Ordering::Less | Ordering::Equal)).to_any())
23    }
24
25    /// `>=`. Not `!(x < y)`: a `NaN` anywhere gives `false`.
26    pub fn ge(self, rhs: Self) -> Result<Self, Self> {
27        Ok(matches!(
28            compare(self, rhs)?,
29            Some(Ordering::Greater | Ordering::Equal)
30        )
31        .to_any())
32    }
33}
34
35/// <https://tc39.es/ecma262/#sec-islessthan>, answered as the order of `x`
36/// relative to `y`, so `>` and `<=` read it rather than ask `<` of swapped
37/// operands. `None` is the spec's `undefined` — comparisons that involve
38/// `NaN`, directly or via a string that fails `StringToBigInt` against a
39/// `BigInt` — which every operator answers `false`.
40///
41/// Both operands' `ToPrimitive` run left first, the spec's `LeftFirst` flag.
42/// The order is observable, since an object's own `valueOf` or `toString` may
43/// throw: `a > b` with both throwing throws `a`'s.
44fn compare<A: IVm>(x: Any<A>, y: Any<A>) -> Result<Option<Ordering>, Any<A>> {
45    let px = to_primitive_or_text(x)?;
46    match (px, to_primitive_or_text(y)?) {
47        // A function's text against a string compares the texts.
48        (None, None) | (None, Some(Primitive::String(_))) | (Some(Primitive::String(_)), None) => {
49            Err(error::function_text())
50        }
51        // Against anything else, the text is numeric: `NaN` for a number
52        // and no `StringToBigInt` for a bigint, so `undefined` either way.
53        (None, Some(_)) | (Some(_), None) => Ok(None),
54        (Some(px), Some(py)) => compare_primitives(px, py),
55    }
56}
57
58/// `ToPrimitive(v, number)`, where `None` is the text of a function that has
59/// none (`error::function_text`). A function's text is a string that neither
60/// `StringToNumber` nor `StringToBigInt` accepts, so `compare` answers a
61/// function without text against a number or a bigint, and refuses it only
62/// against a string.
63fn to_primitive_or_text<A: IVm>(v: Any<A>) -> Result<Option<Primitive<A>>, Any<A>> {
64    match v.clone().into() {
65        Unpacked::Function(f) => Ok(f.text().ok().map(Primitive::String)),
66        _ => v
67            .to_primitive(Some(ToPrimitivePreferredType::Number))
68            .map(Some),
69    }
70}
71
72fn compare_primitives<A: IVm>(
73    px: Primitive<A>,
74    py: Primitive<A>,
75) -> Result<Option<Ordering>, Any<A>> {
76    match (px, py) {
77        (Primitive::String(sx), Primitive::String(sy)) => Ok(Some(sx.cmp(&sy))),
78        (Primitive::BigInt(bx), Primitive::String(sy)) => {
79            let s: std::string::String = sy.into();
80            Ok(string_to_bigint(&s)?.map(|by| bx.cmp(&by)))
81        }
82        (Primitive::String(sx), Primitive::BigInt(by)) => {
83            let s: std::string::String = sx.into();
84            Ok(string_to_bigint(&s)?.map(|bx| bx.cmp(&by)))
85        }
86        (px, py) => Ok(to_numeric(px)?.compare(&to_numeric(py)?)),
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use crate::{
93        naive::Naive,
94        vm::{Any, BigInt, ToAny},
95    };
96
97    type A = Naive;
98
99    fn n(v: f64) -> Any<A> {
100        v.to_any()
101    }
102
103    fn big(v: i64) -> Any<A> {
104        Into::<BigInt<A>>::into(v).to_any()
105    }
106
107    fn bool_of(r: Result<Any<A>, Any<A>>) -> bool {
108        bool::try_from(r.unwrap()).unwrap()
109    }
110
111    #[test]
112    fn number_lt_number() {
113        assert!(bool_of(n(1.0).lt(n(2.0))));
114        assert!(!bool_of(n(2.0).lt(n(1.0))));
115    }
116
117    #[test]
118    fn nan_is_never_less() {
119        assert!(!bool_of(n(f64::NAN).lt(n(1.0))));
120        assert!(!bool_of(n(1.0).lt(n(f64::NAN))));
121        assert!(!bool_of(n(f64::NAN).le(n(1.0))));
122        assert!(!bool_of(n(1.0).le(n(f64::NAN))));
123    }
124
125    #[test]
126    fn nan_is_never_greater() {
127        assert!(!bool_of(n(f64::NAN).gt(n(1.0))));
128        assert!(!bool_of(n(1.0).gt(n(f64::NAN))));
129        assert!(!bool_of(n(f64::NAN).ge(n(1.0))));
130        assert!(!bool_of(big(1).ge(n(f64::NAN))));
131    }
132
133    #[test]
134    fn number_vs_bigint_each_operator() {
135        assert!(bool_of(n(5.5).gt(big(5))));
136        assert!(!bool_of(n(5.0).gt(big(5))));
137        assert!(bool_of(n(5.0).ge(big(5))));
138        assert!(!bool_of(n(4.5).ge(big(5))));
139        assert!(bool_of(n(4.5).lt(big(5))));
140        assert!(bool_of(n(5.0).le(big(5))));
141        assert!(bool_of(n(f64::INFINITY).gt(big(5))));
142        assert!(bool_of(n(f64::NEG_INFINITY).lt(big(5))));
143    }
144
145    #[test]
146    fn string_lexicographic() {
147        let s = |v: &str| -> Any<A> { v.into() };
148        assert!(bool_of(s("10").lt(s("9"))));
149        assert!(!bool_of(s("9").lt(s("10"))));
150    }
151
152    #[test]
153    fn bigint_vs_number_exact() {
154        assert!(bool_of(big(5).lt(n(5.5))));
155        assert!(!bool_of(big(5).lt(n(5.0))));
156        assert!(bool_of(big(5).le(n(5.0))));
157        assert!(bool_of(big(5).lt(n(f64::INFINITY))));
158        assert!(!bool_of(big(5).lt(n(f64::NEG_INFINITY))));
159    }
160
161    #[test]
162    fn large_bigint_vs_large_number_exact() {
163        // 2^60 vs 2^60 + 2048 as a whole f64 (both exactly representable):
164        // rounding the `BigInt` down to `f64` first would be wrong here,
165        // since a naive `bi.to_f64() < f` could compare equal instead.
166        assert!(bool_of(
167            big(1i64 << 60).lt(n((1u64 << 60) as f64 + 2_048.0))
168        ));
169    }
170
171    #[test]
172    fn string_to_bigint_comparison() {
173        let s = |v: &str| -> Any<A> { v.into() };
174        assert!(bool_of(s("10").lt(big(20))));
175        assert!(bool_of(big(20).lt(s("30"))));
176        assert!(!bool_of(s("abc").lt(big(20))));
177        assert!(!bool_of(big(20).lt(s("abc"))));
178    }
179}