Skip to main content

nanvm_lib/vm/unstable/
mod.rs

1#![doc = include_str!("README.md")]
2
3use crate::{
4    sign::Sign,
5    vm::{
6        Any, BigInt, Function, IVm, IteratorRecord, Number, String, ToAny, ToArray, ToObject,
7        ToString, error,
8    },
9};
10
11/// `v` spelled as ECMAScript's `Number::toString` does, the text a host puts
12/// in a message when it names a number a program passed.
13pub fn number_text(v: f64) -> std::string::String {
14    crate::vm::string_coercion::number_text::<crate::naive::Naive>(v)
15}
16
17/// An `Any` holding the string `v`.
18pub fn string_any<A: IVm>(v: &str) -> Any<A> {
19    v.into()
20}
21
22/// An object property key.
23pub fn string_key<A: IVm>(v: &str) -> String<A> {
24    v.into()
25}
26
27/// An `Any` holding the string of UTF-16 code units `v`: the spelling of a
28/// string no `&str` can hold, one with a lone surrogate.
29pub fn string_any_utf16<A: IVm>(v: &[u16]) -> Any<A> {
30    string_key_utf16::<A>(v).to_any()
31}
32
33/// An object property key of UTF-16 code units, for the same reason.
34pub fn string_key_utf16<A: IVm>(v: &[u16]) -> String<A> {
35    v.iter().copied().to_string()
36}
37
38/// An `Any` holding the bigint `v`.
39pub fn bigint_any<A: IVm>(v: i64) -> Any<A> {
40    Into::<BigInt<A>>::into(v).to_any()
41}
42
43/// An `Any` holding the bigint of any size whose magnitude is `words`, least
44/// significant word first, negated if `negative`: the spelling of a literal
45/// outside `i64`, which [`bigint_any`] cannot take. A zero magnitude is `0n`
46/// whatever the sign, and leading zero words are dropped.
47pub fn bigint_any_words<A: IVm>(negative: bool, words: &[u64]) -> Any<A> {
48    let sign = if negative {
49        Sign::Negative
50    } else {
51        Sign::Positive
52    };
53    BigInt::normalize_new(sign, words.iter().copied()).to_any()
54}
55
56/// An `Any` holding the number whose IEEE 754 bits are `v` — the one
57/// spelling `fjs/media/rust` gives every number, so a generated module holds
58/// the double a JavaScript engine held, bit for bit, `-0` included, except a
59/// `NaN`: the language has one, so every `NaN` arrives as the quiet,
60/// empty-payload one, whatever sign or payload the engine held it with.
61pub fn f64_any<A: IVm>(v: u64) -> Any<A> {
62    Number::from(f64::from_bits(v)).to_any()
63}
64
65/// A static function read from the runtime value by its id, `Object.is`:
66/// what a compiled module's read of a static spells,
67/// `static_read(&runtime, "Object.is")?` — the value's `static` function,
68/// called with the id — the one closure the runtime holds for it, so two
69/// reads are one function, as `Object.is === Object.is` is in JavaScript.
70/// An id the runtime answers no function for is refused
71/// ([`error::no_static_function`]): the parser admitted the id, so the
72/// runtime's table is incomplete, and `undefined` would be a plausible
73/// wrong answer. A throw of `static` itself is the read's.
74pub fn static_read<A: IVm>(runtime: &Any<A>, path: &str) -> Result<Any<A>, Any<A>> {
75    let read = runtime.clone().dot(string_any("static")).end()?;
76    let value = read.call([string_any(path)].to_array().to_any())?;
77    match Function::try_from(value) {
78        Ok(f) => Ok(f.to_any()),
79        Err(_) => Err(error::no_static_function(path)),
80    }
81}
82
83/// `===` as an operator result. `==` on `Any` is exactly JavaScript's `===`,
84/// but it yields a `bool` and so pins neither operand's `A`; this gives both
85/// the same one and lifts the answer into the `Result` every other operator
86/// returns, so a printed `===` is a statement like any other operator's.
87pub fn strict_eq<A: IVm>(a: Any<A>, b: Any<A>) -> Result<Any<A>, Any<A>> {
88    Ok((a == b).to_any())
89}
90
91/// `!==`, the negation of [`strict_eq`], for the same reason.
92pub fn strict_ne<A: IVm>(a: Any<A>, b: Any<A>) -> Result<Any<A>, Any<A>> {
93    Ok((a != b).to_any())
94}
95
96/// One item of an array literal or a call's argument list, as
97/// [`spread_array`] and [`spread_call`] read it: a value, or an established
98/// spread iterator ([`Any::get_iterator`]).
99pub enum ArrayItem<A: IVm> {
100    Value(Any<A>),
101    Spread(IteratorRecord<A>),
102}
103
104/// An item that is its own value, `a` in `[a, ...b]`.
105pub fn value_item<A: IVm>(v: Any<A>) -> ArrayItem<A> {
106    ArrayItem::Value(v)
107}
108
109/// A spread item, `...b` in `[a, ...b]`. Take its iterator before the next
110/// operand is evaluated: generated item lists propagate this `Result` with
111/// `?` at the item's position. Iteration itself cannot throw or run represented
112/// code for the VM's immutable arrays and strings ([`IteratorRecord`]).
113pub fn spread_item<A: IVm>(v: Any<A>) -> Result<ArrayItem<A>, Any<A>> {
114    v.get_iterator().map(ArrayItem::Spread)
115}
116
117/// Room for `additional` more elements, or the `RangeError` an array that
118/// cannot be backed is: an allocation the machine refuses is a failure the
119/// program can observe, not an abort of the process.
120fn reserve<A: IVm>(values: &mut Vec<Any<A>>, additional: usize) -> Result<(), Any<A>> {
121    values
122        .try_reserve(additional)
123        .map_err(|_| error::array_too_long())
124}
125
126/// The array an item list holds, a spread among them: each value an
127/// element, and each spread every value its operand iterates, in order. A
128/// spread of a value that is not iterable throws, so the array is an
129/// operation's `Result` where an array without a spread is a value.
130///
131/// Every spread's iterator is already established by [`spread_item`], and the
132/// array's length is bounded before anything is built: past JavaScript's limit,
133/// `2³² − 1` elements and `Array<A>`'s `u32` length, the result is the `RangeError`
134/// `ArrayCreate` throws, refused on the iterators' lower bounds — exact for
135/// an array — and, since a string's code points are only known by walking
136/// it, checked again as each element is added, so the build never passes
137/// the limit. Delaying the iteration is unobservable: neither an array nor a
138/// string runs code or throws while iterated. The elements' room is reserved up
139/// front and before every growth, so a length under the limit whose elements
140/// the machine cannot hold is the same `RangeError`, not an abort. The array
141/// built from them is the container constructor's own allocation, which is
142/// not fallible yet (`todo/131-non-panicking-allocator.md`): a machine that
143/// can hold the elements but not a second copy of them still aborts.
144pub fn spread_array<A: IVm>(
145    items: impl IntoIterator<Item = ArrayItem<A>>,
146) -> Result<Any<A>, Any<A>> {
147    let parts: Vec<_> = items.into_iter().collect();
148    let at_least: u64 = parts
149        .iter()
150        .map(|p| match p {
151            ArrayItem::Value(_) => 1,
152            ArrayItem::Spread(i) => i.size_hint().0 as u64,
153        })
154        .sum();
155    let limit = u64::from(u32::MAX);
156    if at_least > limit {
157        return Err(error::array_too_long());
158    }
159    let mut values = Vec::new();
160    reserve(&mut values, at_least as usize)?;
161    let mut push = |v: Any<A>| -> Result<(), Any<A>> {
162        if values.len() as u64 == limit {
163            return Err(error::array_too_long());
164        }
165        reserve(&mut values, 1)?;
166        values.push(v);
167        Ok(())
168    };
169    for part in parts {
170        match part {
171            ArrayItem::Value(v) => push(v)?,
172            ArrayItem::Spread(i) => i.into_iter().try_for_each(&mut push)?,
173        }
174    }
175    Ok(values.to_array().to_any())
176}
177
178/// `f(...)` over an argument list holding a spread: the arguments
179/// [`spread_array`] builds, then the call, so a spread's throw comes first,
180/// as JavaScript evaluates the arguments before it calls.
181pub fn spread_call<A: IVm>(
182    f: Any<A>,
183    items: impl IntoIterator<Item = ArrayItem<A>>,
184) -> Result<Any<A>, Any<A>> {
185    f.call(spread_array(items)?)
186}
187
188/// One entry of an object literal, as [`spread_object`] reads it: a
189/// property, a key and its value, or a spread whose entries the value
190/// gives ([`Any::object_spread`]).
191pub enum ObjectItem<A: IVm> {
192    Property(String<A>, Any<A>),
193    Spread(Any<A>),
194}
195
196/// A property entry, `k: v` in `{k: v, ...o}`. A computed key uses
197/// `property_item(key.to_string()?, value)`, establishing its string conversion
198/// before the value argument is evaluated. The VM has no symbols, so
199/// [`Any::to_string`] supplies `ToPropertyKey` for its values.
200/// A plain object becomes `"[object Object]"`; an own callable `toString`
201/// supplies the primitive, with `valueOf` as the fallback if needed. A throw or
202/// failure to produce a primitive propagates before the value starts.
203pub fn property_item<A: IVm>(k: String<A>, v: Any<A>) -> ObjectItem<A> {
204    ObjectItem::Property(k, v)
205}
206
207/// A spread entry, `...o` in `{k: v, ...o}`.
208pub fn spread_entries<A: IVm>(v: Any<A>) -> ObjectItem<A> {
209    ObjectItem::Spread(v)
210}
211
212/// The object an entry list holds, a spread among them: each property and
213/// each spread's entries appended in order to the raw property list, so a
214/// later key overwrites an earlier one's value and keeps its position, as
215/// JavaScript's does, through the view every reader takes
216/// ([`Object::own_entries`](crate::vm::Object::own_entries)). An object
217/// spread never throws, so this is a value, not a `Result`.
218pub fn spread_object<A: IVm>(items: impl IntoIterator<Item = ObjectItem<A>>) -> Any<A> {
219    let mut entries = Vec::new();
220    for item in items {
221        match item {
222            ObjectItem::Property(k, v) => entries.push((k, v)),
223            ObjectItem::Spread(v) => entries.extend(v.object_spread()),
224        }
225    }
226    entries.to_object().to_any()
227}
228
229#[cfg(test)]
230mod test {
231    use super::*;
232    use crate::{
233        common::sized_index::SizedIndex,
234        naive::Naive,
235        vm::{Array, IVm, Object, Unpacked},
236    };
237
238    #[test]
239    fn strings() {
240        let direct: Any<Naive> = "a".into();
241        assert_eq!(string_any::<Naive>("a"), direct);
242        let key: String<Naive> = "a".into();
243        assert_eq!(string_key::<Naive>("a"), key);
244    }
245
246    #[test]
247    fn bigints() {
248        assert_eq!(
249            bigint_any::<Naive>(-1),
250            Into::<BigInt<Naive>>::into(-1i64).to_any()
251        );
252    }
253
254    #[test]
255    fn bigints_of_any_size() {
256        let word = |negative, words: &[u64]| bigint_any_words::<Naive>(negative, words);
257        // Within `i64` it agrees with `bigint_any`, `i64::MIN` included.
258        assert_eq!(word(false, &[1]), bigint_any(1));
259        assert_eq!(word(true, &[1 << 63]), bigint_any(i64::MIN));
260        assert_eq!(word(false, &[i64::MAX as u64]), bigint_any(i64::MAX));
261        // Zero is `0n`, signed or padded.
262        assert_eq!(word(true, &[]), bigint_any(0));
263        assert_eq!(word(true, &[0, 0]), bigint_any(0));
264        // A word boundary: `2n ** 64n`, and its negation.
265        let two_64 = word(false, &[0, 1]);
266        assert_eq!(two_64, (bigint_any::<Naive>(1) << bigint_any(64)).unwrap());
267        assert_eq!(word(true, &[0, 1, 0]), (-two_64.clone()).unwrap());
268        assert_ne!(two_64, word(false, &[1, 1]));
269        // Several words, as the decimal `123456789012345678901234567890n`.
270        let big = word(false, &[0xC373_E0EE_4E3F_0AD2, 0x1_8EE9_0FF6]);
271        assert_eq!(
272            big.to_string().unwrap(),
273            "123456789012345678901234567890".into()
274        );
275    }
276
277    #[test]
278    fn numbers_as_text() {
279        assert_eq!(number_text(f64::NAN), "NaN");
280        assert_eq!(number_text(-0.0), "0");
281        assert_eq!(number_text(-1.5), "-1.5");
282        assert_eq!(number_text(1e21), "1e+21");
283        assert_eq!(number_text(1e-7), "1e-7");
284        // A tie between two candidates of one length goes to the even digit.
285        assert_eq!(
286            number_text(0x6_b5a4_ff2c_1a40_u64 as f64 + 0.25),
287            "1888570120608320.2"
288        );
289    }
290
291    #[test]
292    fn numbers_by_their_bits() {
293        let bits = |v: Any<Naive>| match v.into() {
294            Unpacked::Number(x) => f64::from(x).to_bits(),
295            _ => panic!("a number"),
296        };
297        assert_eq!(bits(f64_any(0x4002_6666_6666_6666)), 2.3f64.to_bits());
298        assert_eq!(bits(f64_any(0x8000_0000_0000_0000)), (-0f64).to_bits());
299        assert_eq!(bits(f64_any(0x7ff8_0000_0000_0000)), 0x7ff8_0000_0000_0000);
300    }
301
302    /// The two answer each other's negation, `NaN` against itself included:
303    /// `NaN === NaN` is `false` and `NaN !== NaN` is `true`, as in JavaScript.
304    #[test]
305    fn strict_equality() {
306        let one = || f64_any::<Naive>(0x3ff0_0000_0000_0000);
307        let nan = || f64_any::<Naive>(0x7ff8_0000_0000_0000);
308        assert_eq!(strict_eq(one(), one()), Ok(true.to_any()));
309        assert_eq!(strict_ne(one(), one()), Ok(false.to_any()));
310        assert_eq!(strict_eq(nan(), nan()), Ok(false.to_any()));
311        assert_eq!(strict_ne(nan(), nan()), Ok(true.to_any()));
312        assert_eq!(strict_eq(one(), string_any("1")), Ok(false.to_any()));
313    }
314
315    /// The elements of an array result, compared by value: `==` on two
316    /// arrays is `===`, identity.
317    fn elements(r: Result<Any<Naive>, Any<Naive>>) -> Vec<Any<Naive>> {
318        Array::try_from(r.unwrap()).unwrap().into_iter().collect()
319    }
320
321    /// `[1, ...'ab', ...[2]]` is `[1, 'a', 'b', 2]`, an empty spread adds
322    /// nothing, and a spread of what is not iterable throws.
323    #[test]
324    fn spread_arrays() {
325        let one = || f64_any::<Naive>(0x3ff0_0000_0000_0000);
326        let two = || f64_any::<Naive>(0x4000_0000_0000_0000);
327        let inner: Any<Naive> = [two()].to_array().to_any();
328        assert_eq!(
329            elements(spread_array([
330                value_item(one()),
331                spread_item(string_any("ab")).unwrap(),
332                spread_item(inner).unwrap(),
333            ])),
334            [one(), string_any("a"), string_any("b"), two()]
335        );
336        let empty: Any<Naive> = [].to_array().to_any();
337        assert!(
338            elements(spread_array([
339                spread_item(string_any("")).unwrap(),
340                spread_item(empty).unwrap()
341            ]))
342            .is_empty()
343        );
344        assert!(spread_item(one()).is_err());
345    }
346
347    /// Generated lists propagate each spread's admission before evaluating
348    /// the next operand, even when that operand would fail differently.
349    #[test]
350    fn spread_admission_precedes_later_operands() {
351        use crate::vm::Nullish;
352        use core::cell::Cell;
353
354        let reached = Cell::new(false);
355        let later = || -> Result<Any<Naive>, Any<Naive>> {
356            reached.set(true);
357            Err(string_any("later"))
358        };
359        let result: Result<Any<Naive>, Any<Naive>> =
360            (|| spread_array([spread_item(Nullish::Null.to_any())?, value_item(later()?)]))();
361        assert_eq!(result.err(), Some(error::not_iterable()));
362        assert!(!reached.get());
363    }
364
365    /// Past `2³² − 1` elements the result is the `RangeError`, refused on
366    /// the iterators' exact lengths before anything is allocated: 65,537
367    /// spreads of one 65,536-element array would be 2³² + 2¹⁶ elements.
368    #[test]
369    fn spread_array_too_long() {
370        let one = || f64_any::<Naive>(0x3ff0_0000_0000_0000);
371        let block: Any<Naive> = (0..65_536)
372            .map(|_| one())
373            .collect::<Vec<_>>()
374            .to_array()
375            .to_any();
376        let too_many = (0..65_537).map(|_| spread_item(block.clone()).unwrap());
377        assert_eq!(spread_array(too_many).err(), Some(error::array_too_long()));
378        let within = (0..2).map(|_| spread_item(block.clone()).unwrap());
379        assert_eq!(elements(spread_array(within)).len(), 131_072);
380    }
381
382    /// A length under the limit whose elements the machine cannot hold is
383    /// the `RangeError` too, not an abort: 65,535 spreads of one 65,537-element
384    /// array are exactly `2³² − 1` elements. Skipped where the machine can
385    /// reserve that room, as building it would take the machine's memory.
386    #[test]
387    fn spread_array_unbacked() {
388        let one = || f64_any::<Naive>(0x3ff0_0000_0000_0000);
389        let block: Any<Naive> = (0..65_537)
390            .map(|_| one())
391            .collect::<Vec<_>>()
392            .to_array()
393            .to_any();
394        let all = u32::MAX as usize;
395        if Vec::<Any<Naive>>::new().try_reserve(all).is_ok() {
396            return;
397        }
398        let spreads = (0..65_535).map(|_| spread_item(block.clone()).unwrap());
399        assert_eq!(spread_array(spreads).err(), Some(error::array_too_long()));
400    }
401
402    /// `reserve` is the one place a refused reservation becomes the
403    /// `RangeError`, and `usize::MAX` elements overflow the capacity of every
404    /// `Vec` on every platform, so this holds where `spread_array_unbacked`
405    /// skips itself. It does not show that `spread_array` calls it: that
406    /// takes an allocator that refuses a real request.
407    #[test]
408    fn reserve_refuses_what_no_vec_can_hold() {
409        let mut values: Vec<Any<Naive>> = Vec::new();
410        assert_eq!(
411            reserve(&mut values, usize::MAX),
412            Err(error::array_too_long())
413        );
414        assert!(values.is_empty());
415        assert_eq!(reserve(&mut values, 3), Ok(()));
416        assert!(values.capacity() >= 3);
417    }
418
419    /// The callee receives the spread values as its arguments, and a spread
420    /// that throws throws before the call, a callee that is not a function
421    /// included.
422    #[test]
423    fn spread_calls() {
424        use crate::vm::IVm;
425        let identity: Any<Naive> =
426            Naive::static_function(|_, args| Ok(args.to_any()), 0, [].to_array(), None).to_any();
427        assert_eq!(
428            elements(spread_call(
429                identity,
430                [spread_item(string_any("ab")).unwrap()]
431            )),
432            [string_any("a"), string_any("b")]
433        );
434        let one = || f64_any::<Naive>(0x3ff0_0000_0000_0000);
435        let bad_spread = (|| spread_call(one(), [spread_item(one())?]))();
436        assert_eq!(bad_spread.err(), Some(error::not_iterable()));
437        assert!(spread_call(one(), [spread_item(string_any("ab")).unwrap()]).is_err());
438    }
439
440    /// `{a: 1, ...{b: 2, a: 3}, ...'x'}` is `{0: 'x', a: 3, b: 2}`: the
441    /// spread's `a` overwrites the first one's value and keeps its
442    /// position, and the index key comes first; `{...null}` adds nothing.
443    #[test]
444    fn spread_objects() {
445        use crate::vm::{Nullish, Object, ToObject};
446        let one = || f64_any::<Naive>(0x3ff0_0000_0000_0000);
447        let two = || f64_any::<Naive>(0x4000_0000_0000_0000);
448        let three = || f64_any::<Naive>(0x4008_0000_0000_0000);
449        let inner: Any<Naive> = [(string_key("b"), two()), (string_key("a"), three())]
450            .to_object()
451            .to_any();
452        let result = spread_object([
453            property_item(string_key("a"), one()),
454            spread_entries(inner),
455            spread_entries(string_any("x")),
456            spread_entries(Nullish::Null.to_any()),
457        ]);
458        let entries = Object::try_from(result).unwrap().own_entries();
459        assert_eq!(
460            entries,
461            [
462                (string_key("0"), string_any("x")),
463                (string_key("a"), three()),
464                (string_key("b"), two()),
465            ]
466        );
467    }
468
469    /// A computed key is its `ToString`, as an own key: a number is its
470    /// text and `-0` is `"0"`; a key that cannot be converted throws before
471    /// the entry exists.
472    #[test]
473    fn computed_keys() {
474        let one = || f64_any::<Naive>(0x3ff0_0000_0000_0000);
475        let negative_zero = || f64_any::<Naive>(0x8000_0000_0000_0000);
476        let object = spread_object([
477            property_item(one().to_string().unwrap(), string_any("a")),
478            property_item(negative_zero().to_string().unwrap(), string_any("b")),
479            property_item(Any::undefined().to_string().unwrap(), string_any("c")),
480            property_item(string_any("1").to_string().unwrap(), string_any("d")),
481        ]);
482        let entries = Object::try_from(object).unwrap().own_entries();
483        assert_eq!(
484            entries,
485            [
486                (string_key("0"), string_any("b")),
487                (string_key("1"), string_any("d")),
488                (string_key("undefined"), string_any("c")),
489            ]
490        );
491        let plain = Object::<Naive>::default().to_any();
492        let item = property_item(plain.to_string().unwrap(), one());
493        let ObjectItem::Property(key, _) = item else {
494            panic!()
495        };
496        assert_eq!(key, string_key("[object Object]"));
497    }
498
499    /// An own conversion method supplies the key, and its errors propagate.
500    #[test]
501    fn computed_key_conversion() {
502        use core::cell::Cell;
503
504        let with_to_string = |method: Any<Naive>| -> Any<Naive> {
505            [(string_key("toString"), method)].to_object().to_any()
506        };
507        let method =
508            Naive::static_function(|_, _| Ok(string_any("own")), 0, [].to_array(), None).to_any();
509        let item = property_item(
510            with_to_string(method).to_string().unwrap(),
511            string_any("value"),
512        );
513        let object = Object::try_from(spread_object([item])).unwrap();
514        assert_eq!(
515            object.own_entries(),
516            [(string_key("own"), string_any("value"))]
517        );
518
519        let method = Naive::static_function(
520            |_, _| Err(string_any("key conversion failed")),
521            0,
522            [].to_array(),
523            None,
524        )
525        .to_any();
526        let reached = Cell::new(false);
527        let later = || -> Result<Any<Naive>, Any<Naive>> {
528            reached.set(true);
529            Err(string_any("value failed"))
530        };
531        let item = (|| -> Result<ObjectItem<Naive>, Any<Naive>> {
532            Ok(property_item(with_to_string(method).to_string()?, later()?))
533        })();
534        assert!(matches!(
535            item,
536            Err(error) if error == string_any("key conversion failed")
537        ));
538        assert!(!reached.get());
539        assert!(with_to_string(f64_any(0)).to_string().is_err());
540
541        let item = (|| -> Result<ObjectItem<Naive>, Any<Naive>> {
542            Ok(property_item(string_any("own").to_string()?, later()?))
543        })();
544        assert!(matches!(item, Err(error) if error == string_any("value failed")));
545        assert!(reached.get());
546    }
547
548    /// A string of code units holds what no `&str` can, a lone surrogate,
549    /// and agrees with the `&str` spelling where both exist.
550    #[test]
551    fn strings_of_code_units() {
552        let lone = string_key_utf16::<Naive>(&[0x61, 0xd800]);
553        assert_eq!(lone.length(), 2);
554        assert_eq!(lone[1], 0xd800);
555        assert_eq!(string_any_utf16::<Naive>(&[0x61, 0x62]), string_any("ab"));
556        assert_eq!(string_key_utf16::<Naive>(&[]), string_key(""));
557    }
558}