Skip to main content

nanvm_lib/vm/
runtime.rs

1//! The runtime, `fjs/js/runtime`, as a value: `npm run gen` compiles the
2//! module into `gen.runtime.rs` beside this file, through the runtime
3//! printer (`fjs/compiler/rust/runtime`) and the ordinary backend, as any
4//! module is compiled. [`runtime`] answers the `runtime` export object —
5//! three functions, `static` and `dot` over the module's tables, and `in`,
6//! the body of the `in` operator — built
7//! once by whoever runs compiled modules and handed to each as its
8//! parameter: a compiled module reads a static function from it,
9//! `static_read(&runtime, "Object.is")?`, which calls `static` with the id
10//! ([`unstable::static_read`](crate::vm::unstable::static_read)), a
11//! call step asks its `dot` for the function a member's body answers for
12//! the receiver, after the receiver's own property and before the native
13//! table ([`Member::call`](crate::vm::lambda::Member)), and an `in` node
14//! calls its `in` with the key and the value, the naive spelling a
15//! native answer may one day substitute
16//! (`todo/in-native-substitution.md`). Every static body
17//! is a closure carrying the NativeFunction text of its id, and
18//! `Object.entries` is the built-in itself, so the table is complete; the
19//! identity is the value's, two reads of a static one closure. No VM holds a runtime, and
20//! the library asks nothing of it but through the value it is given. The
21//! design: `fjs/js/runtime/README.md`, "How NaNVM finds it".
22
23/// The compiled runtime: a `#[path]`, since a `gen.` name is not a Rust
24/// identifier. Its one item, `runtime`, is re-exported from `vm`.
25#[path = "gen.runtime.rs"]
26pub(super) mod compiled;
27
28use crate::vm::{Any, IVm, Object, error};
29
30/// A compiled module: what `fjs compile` prints as
31/// `pub fn module<A: IVm>(runtime: Any<A>) -> Result<Any<A>, Any<A>>`, a
32/// function of the runtime value answering its export object or the value
33/// its initialization threw.
34pub type Module<A> = fn(Any<A>) -> Result<Any<A>, Any<A>>;
35
36/// `module` instantiated with the runtime value: its export object, or
37/// what it threw. A value that is not an object is refused with a
38/// `TypeError`, since no compiled module answers one; every consumer that
39/// runs a module reads its exports through this, so the refusal is said
40/// once.
41pub fn exports<A: IVm>(module: Module<A>) -> Result<Object<A>, Any<A>> {
42    Object::try_from(module(compiled::runtime::<A>()?)?).map_err(|_| error::not_an_export_object())
43}
44
45#[cfg(test)]
46mod tests {
47    use super::compiled::runtime;
48    use crate::vm::test::function::{returns, throws};
49    use crate::{
50        naive::Naive,
51        vm::{Any, BigInt, ToAny, ToArray, ToObject, error, unstable::static_read},
52    };
53
54    type A = Naive;
55
56    /// A module answering a fixed object, its throw, or a value that is no
57    /// export object.
58    fn object(_runtime: Any<A>) -> Result<Any<A>, Any<A>> {
59        Ok([("a".into(), 1.0.to_any())].to_object().to_any())
60    }
61    fn throwing(_runtime: Any<A>) -> Result<Any<A>, Any<A>> {
62        Err("boom".into())
63    }
64    fn number(_runtime: Any<A>) -> Result<Any<A>, Any<A>> {
65        Ok(42.0.to_any())
66    }
67
68    /// The export object is the module's answer; a throw is its value, and
69    /// a value that is no object is refused with one `TypeError`.
70    #[test]
71    fn exports() {
72        use crate::vm::exports;
73        let object = exports::<A>(object).unwrap();
74        assert_eq!(object.own_property(&"a".into()), Some(1.0.to_any()));
75        assert_eq!(exports::<A>(throwing).unwrap_err(), "boom".into());
76        assert_eq!(
77            exports::<A>(number).unwrap_err(),
78            error::not_an_export_object()
79        );
80    }
81
82    /// A property of the runtime value, by its key: `static`, `dot`.
83    fn at(value: &Any<A>, key: &str) -> Any<A> {
84        Any::dot(value.clone(), key.into()).end().unwrap()
85    }
86
87    /// The function the runtime's `static` answers for an id.
88    fn static_at(rt: &Any<A>, id: &str) -> Any<A> {
89        at(rt, "static")
90            .call([id.into()].to_array().to_any())
91            .unwrap()
92    }
93
94    /// The runtime as a value: the export object with its two functions.
95    /// `static` answers a body as a function — `Object.is`, SameValue,
96    /// with the NativeFunction text of its id and the `length` of its body
97    /// — one value however often it is asked, as the language's own
98    /// identity has it; `Object.entries` is the built-in itself, with its
99    /// text; an id without a body is `undefined`; and `dot` is the
100    /// dispatcher, answering an own property, a member's function of the
101    /// arguments — a body keeps its own text, since no program reads a
102    /// member as a value — or `undefined`.
103    #[test]
104    fn value() {
105        let rt: Any<A> = runtime::<A>().unwrap();
106        let is = static_at(&rt, "Object.is");
107        assert_eq!(
108            is.clone().to_string(),
109            Ok("function is() { [native code] }".into())
110        );
111        assert_eq!(
112            Any::dot(is.clone(), "length".into()).end(),
113            Ok(2.0.to_any())
114        );
115        let call = |a: Any<A>, b: Any<A>| is.clone().call([a, b].to_array().to_any());
116        assert_eq!(call((-0.0).to_any(), 0.0.to_any()), Ok(false.to_any()));
117        assert_eq!(
118            call(f64::NAN.to_any(), f64::NAN.to_any()),
119            Ok(true.to_any())
120        );
121        assert_eq!(call("a".into(), "a".into()), Ok(true.to_any()));
122        let again = static_at(&rt, "Object.is");
123        assert_eq!(is, again);
124        assert!(is.same_value(&again));
125        let is_nan = static_at(&rt, "Number.isNaN");
126        assert_eq!(
127            is_nan.clone().to_string(),
128            Ok("function isNaN() { [native code] }".into())
129        );
130        assert_ne!(is, is_nan);
131        let entries = static_at(&rt, "Object.entries");
132        assert_eq!(
133            entries.clone().to_string(),
134            Ok("function entries() { [native code] }".into())
135        );
136        assert_eq!(
137            Any::dot(entries.clone(), "length".into()).end(),
138            Ok(1.0.to_any())
139        );
140        let pairs = entries
141            .clone()
142            .call(["ab".into()].to_array().to_any())
143            .unwrap();
144        assert_eq!(pairs.to_json(), Ok(r#"[["0","a"],["1","b"]]"#.into()));
145        assert!(entries.call([].to_array().to_any()).is_err());
146        for id in ["Object.keys", "Math.max", "Object", "is", "toString", ""] {
147            assert!(static_at(&rt, id).is_nullish(), "{id}");
148        }
149        let dot = at(&rt, "dot");
150        let dispatch = |receiver: Any<A>, key: &str| {
151            dot.clone()
152                .call([receiver, key.into()].to_array().to_any())
153                .unwrap()
154        };
155        assert!(dispatch(1.0.to_any(), "startsWith").is_nullish());
156        assert!(dispatch("abc".into(), "slice").is_nullish());
157        let f = returns(7.0.to_any());
158        let own: Any<A> = [("startsWith".into(), f.clone())].to_object().to_any();
159        assert_eq!(dispatch(own, "startsWith"), f);
160        let on_abc = dispatch("abc".into(), "startsWith");
161        assert_ne!(
162            on_abc.clone().to_string(),
163            Ok("function startsWith() { [native code] }".into())
164        );
165        assert_eq!(
166            on_abc.call(["bc".into(), 1.0.to_any()].to_array().to_any()),
167            Ok(true.to_any())
168        );
169    }
170
171    /// A compiled read of a static: `static_read` asks the runtime's
172    /// `static` for the id, the one closure however often it is read, and
173    /// refuses an id the runtime answers no function for rather than
174    /// answer `undefined` — the one refusal whatever the id names.
175    #[test]
176    fn static_read_by_path() {
177        let rt: Any<A> = runtime::<A>().unwrap();
178        let is = static_read(&rt, "Object.is").unwrap();
179        assert_eq!(is, static_at(&rt, "Object.is"));
180        assert_eq!(static_read(&rt, "Object.is").unwrap(), is);
181        assert_eq!(
182            static_read(&rt, "Object.entries").unwrap(),
183            static_at(&rt, "Object.entries")
184        );
185        for path in ["Object.keys", "Math.max", "Object", "Math"] {
186            assert_eq!(static_read(&rt, path), Err(error::no_static_function(path)));
187        }
188    }
189
190    /// A call step reaches a body through the value: `"abc".startsWith`
191    /// through `dot`, before the native table, the receiver alone first and
192    /// then the arguments, and `?.()` sees the body's function as a callee
193    /// on a string and nothing on a number. The search is converted before
194    /// the position, as the body orders them: its own `toString`'s throw
195    /// wins over the bigint position's `TypeError`.
196    #[test]
197    fn member_call_through_the_value() {
198        let rt: Any<A> = runtime::<A>().unwrap();
199        let key: Any<A> = "startsWith".into();
200        assert_eq!(
201            1.0.to_any::<A>()
202                .dot(key.clone())
203                .option_call(&rt, || panic!("the arguments of a skipped call"))
204                .end(),
205            Ok(Any::undefined())
206        );
207        let abc: Any<A> = "abc".into();
208        let args = || Ok(["ab".into()].to_array().to_any());
209        assert_eq!(
210            abc.clone().dot(key.clone()).end_call(&rt, args),
211            Ok(true.to_any())
212        );
213        assert_eq!(
214            abc.clone().dot(key.clone()).option_call(&rt, args).end(),
215            Ok(true.to_any())
216        );
217        assert_eq!(
218            abc.clone()
219                .dot(key.clone())
220                .end_call(&rt, || Ok(["bc".into(), 1.0.to_any()].to_array().to_any())),
221            Ok(true.to_any())
222        );
223        assert!(
224            abc.clone()
225                .dot(key.clone())
226                .end_call(&rt, || Ok(["a".into(), BigInt::<A>::from(0i64).to_any()]
227                    .to_array()
228                    .to_any()))
229                .is_err()
230        );
231        let boom = throws("A".into());
232        let search: Any<A> = [("toString".into(), boom)].to_object().to_any();
233        assert_eq!(
234            abc.dot(key)
235                .end_call(&rt, || Ok([search, BigInt::<A>::from(0i64).to_any()]
236                    .to_array()
237                    .to_any())),
238            Err("A".into())
239        );
240    }
241}