Skip to main content

nanvm_lib/vm/lambda/
mod.rs

1//! The three continuation types of `fjs/edag/README.md`'s Chains section,
2//! one per combination of its two bits — a receiver is live (P), a
3//! short-circuit region is open (O) — that is not a node boundary. A
4//! chain is opened by a node's entry point on [`Any`] (`dot`, `option_dot`,
5//! `option_call` in `vm/any`), continued by one method per step the README
6//! allows in that state, and closed by `end` or `end_call`, which alone
7//! answer the chain's `Result`. A step the README does not allow is a
8//! method that does not exist, so a spelling that disagrees with the
9//! README does not compile.
10//!
11//! None of the three is `Clone`, and every method consumes `self`: a
12//! continuation "cannot be lifted out as a shared node", so a chain is used
13//! once, and `#[must_use]` makes a chain used zero times a warning. What a
14//! terminal answers is an ordinary `Result<Any<A>, Any<A>>`, shareable as
15//! any value is — neither bit is ever the result of an `exp`.
16//!
17//! A step's order is *guard, thunk, operation*: a guarded step looks at the
18//! current value for nullishness first and skips its thunk on it; every
19//! callable validation follows argument evaluation, and a computed key
20//! precedes its property read. A member call resolves its property and
21//! runtime callee before forcing arguments, retaining lookup failures;
22//! resolving a noncallable value does not validate it yet.
23//!
24//! A property step holds its receiver and key, [`Member`], and the read
25//! waits for the exit: `end` reads, and a call exit resolves the callee —
26//! an object's own property or an array's element first, then the
27//! function the runtime value's `dot` answers for the receiver, then the
28//! receiver type's built-in member function ([`method`](method::method))
29//! for a name without a body, then the `TypeError` for calling `undefined`. A call step on a member
30//! takes the runtime value for that, `&runtime`, the parameter every
31//! compiled module receives (`fjs/js/runtime/README.md`, "How NaNVM finds
32//! it"); a step on a plain value needs none. No user function reads
33//! `this`, so `Function::call` takes no receiver and never will: the
34//! receiver is consumed by the built-in and handed to nothing. Which
35//! built-ins the table answers is `nanvm/lib/todo/member-functions.md`.
36
37use crate::vm::{Any, IVm};
38
39pub(crate) mod member;
40mod method;
41mod number;
42mod string;
43
44/// The shared runtime-only pairs and the completeness table `method`'s test
45/// checks it against, generated by `fjs/nanvm/methods` — a `#[path]`, since a
46/// `gen.` name is not a Rust identifier.
47#[path = "gen.methods.rs"]
48mod methods_table;
49
50pub(crate) use member::Member;
51
52/// What a chain's live state can do, whatever it holds: a bare value, the
53/// state after a call step or a `?.()` node, or a [`Member`], the receiver
54/// and key of a property step whose read waits for the exit.
55pub(crate) trait Live<A: IVm>: Sized {
56    /// The chain's current value: the value itself, or the property read.
57    fn value(self) -> Any<A>;
58    /// The guard of a `?.()` step: whether what the call would call is
59    /// nullish, or the throw of deciding, a member's body deciding for its
60    /// receiver, which the runtime value's `dot` answers.
61    fn is_nullish(&self, runtime: &Any<A>) -> Result<bool, Any<A>>;
62    /// Resolve before arguments, retaining lookup failures while postponing
63    /// callable validation. Bare values have no member lookup to perform.
64    fn resolve(&self, runtime: &Any<A>) -> Result<(), Any<A>>;
65    /// The call, its arguments evaluated: `Any::call` of a value, and a
66    /// member's resolution — the receiver's own property, the function
67    /// the runtime value's `dot` answers, the native for a name without a
68    /// body, the `TypeError`.
69    fn call(self, runtime: &Any<A>, args: Any<A>) -> Result<Any<A>, Any<A>>;
70}
71
72impl<A: IVm> Live<A> for Any<A> {
73    fn value(self) -> Any<A> {
74        self
75    }
76    fn is_nullish(&self, _runtime: &Any<A>) -> Result<bool, Any<A>> {
77        Ok(Any::is_nullish(self))
78    }
79    fn resolve(&self, _runtime: &Any<A>) -> Result<(), Any<A>> {
80        Ok(())
81    }
82    fn call(self, _runtime: &Any<A>, args: Any<A>) -> Result<Any<A>, Any<A>> {
83        Any::call(self, args)
84    }
85}
86
87impl<A: IVm> Live<A> for Member<A> {
88    fn value(self) -> Any<A> {
89        self.read()
90    }
91    fn is_nullish(&self, runtime: &Any<A>) -> Result<bool, Any<A>> {
92        Member::is_nullish(self, runtime)
93    }
94    fn resolve(&self, runtime: &Any<A>) -> Result<(), Any<A>> {
95        self.is_nullish(runtime).map(|_| ())
96    }
97    fn call(self, runtime: &Any<A>, args: Any<A>) -> Result<Any<A>, Any<A>> {
98        Member::call(self, runtime, args)
99    }
100}
101
102/// The state of a chain inside an open region: live, skipped by a guard, or
103/// thrown. Skipped and thrown pass through every step; `end` tells them
104/// apart.
105pub(crate) enum Region<A: IVm, T: Live<A>> {
106    Live(T),
107    Skipped,
108    Thrown(Any<A>),
109}
110
111impl<A: IVm, T: Live<A>> From<Result<T, Any<A>>> for Region<A, T> {
112    fn from(r: Result<T, Any<A>>) -> Self {
113        match r {
114            Ok(v) => Region::Live(v),
115            Err(e) => Region::Thrown(e),
116        }
117    }
118}
119
120impl<A: IVm, T: Live<A>> Region<A, T> {
121    /// The region closed: a skipped chain is `undefined`, the value a
122    /// short-circuit answers.
123    fn end(self) -> Result<Any<A>, Any<A>> {
124        match self {
125            Region::Live(v) => Ok(v.value()),
126            Region::Skipped => Ok(Any::undefined()),
127            Region::Thrown(e) => Err(e),
128        }
129    }
130    /// An unguarded step: `op` over a live state, nothing otherwise.
131    fn step<U: Live<A>>(self, op: impl FnOnce(T) -> Result<U, Any<A>>) -> Region<A, U> {
132        match self {
133            Region::Live(v) => op(v).into(),
134            Region::Skipped => Region::Skipped,
135            Region::Thrown(e) => Region::Thrown(e),
136        }
137    }
138    /// `|()`: resolve a member, force the arguments, then call. Callable
139    /// validation follows arguments; lookup failure precedes them.
140    fn call(
141        self,
142        runtime: &Any<A>,
143        args: impl FnOnce() -> Result<Any<A>, Any<A>>,
144    ) -> Region<A, Any<A>> {
145        self.step(|v| {
146            v.resolve(runtime)
147                .and_then(|()| args())
148                .and_then(|a| v.call(runtime, a))
149        })
150    }
151    /// `|.`: the key forced, then the step over the current value as the
152    /// receiver, whose nullish throw therefore comes second.
153    pub(crate) fn dot(self, key: impl FnOnce() -> Result<Any<A>, Any<A>>) -> Region<A, Member<A>> {
154        self.step(|v| key().and_then(|k| Member::new(v.value(), k)))
155    }
156    /// A guard: the region skipped where the live state is nullish, thrown
157    /// where deciding throws, and otherwise `step` over it; nothing for a
158    /// region already skipped or thrown. A member decides with the runtime
159    /// value, whose `dot` may answer a callee for its receiver.
160    fn guarded<U: Live<A>>(
161        self,
162        runtime: &Any<A>,
163        step: impl FnOnce(Self) -> Region<A, U>,
164    ) -> Region<A, U> {
165        match self {
166            Region::Live(v) => match v.is_nullish(runtime) {
167                Err(e) => Region::Thrown(e),
168                Ok(true) => Region::Skipped,
169                Ok(false) => step(Region::Live(v)),
170            },
171            Region::Skipped => Region::Skipped,
172            Region::Thrown(e) => Region::Thrown(e),
173        }
174    }
175    /// `|?.()`: a nullish callee skips the rest of the chain, its
176    /// arguments untouched; any other is called.
177    pub(crate) fn option_call(
178        self,
179        runtime: &Any<A>,
180        args: impl FnOnce() -> Result<Any<A>, Any<A>>,
181    ) -> Region<A, Any<A>> {
182        // The guard already performed the preargument resolution.
183        self.guarded(runtime, |live| {
184            live.step(|v| args().and_then(|a| v.call(runtime, a)))
185        })
186    }
187}
188
189impl<A: IVm> Region<A, Any<A>> {
190    /// The `?.` node's own step, on a plain value: a nullish receiver skips
191    /// the key and the rest of the chain; any other takes the step `|.`
192    /// takes. The guard is the value's own nullishness, which asks no
193    /// runtime.
194    pub(crate) fn option_dot(
195        self,
196        key: impl FnOnce() -> Result<Any<A>, Any<A>>,
197    ) -> Region<A, Member<A>> {
198        match self {
199            Region::Live(v) if Any::is_nullish(&v) => Region::Skipped,
200            live => live.dot(key),
201        }
202    }
203    /// `|()` on a plain value, the state after a call step or a `?.()`
204    /// node: `Any::call`, which asks no runtime.
205    fn call_value(self, args: impl FnOnce() -> Result<Any<A>, Any<A>>) -> Region<A, Any<A>> {
206        self.step(|v| args().and_then(|a| Any::call(v, a)))
207    }
208    /// `?.()` on a plain value, the node's own step and a `|?.()` step on
209    /// the state after a call: a nullish callee skips, any other is called.
210    pub(crate) fn option_call_value(
211        self,
212        args: impl FnOnce() -> Result<Any<A>, Any<A>>,
213    ) -> Region<A, Any<A>> {
214        match self {
215            Region::Live(v) if Any::is_nullish(&v) => Region::Skipped,
216            live => live.call_value(args),
217        }
218    }
219}
220
221/// A receiver is live and no region is open: the state after a `.` node,
222/// `a.b`. Its steps are `|()` (`end_call`) and `|?.()` (`option_call`);
223/// `end` is the bare node, the read with its receiver dropped — the one
224/// property read `nanvm-lib` has, `dot(a, key).end()`.
225///
226/// No region, so no skipped state: the interior is the step's own
227/// `Result`, its nullish-receiver throw waiting for a terminal to surface
228/// it — `a.b(...c)` on a nullish `a` throws at the access with `c`
229/// untouched, which is why `end_call` takes its arguments as a thunk.
230#[must_use]
231pub struct PropertyLambda<A: IVm>(pub(crate) Result<Member<A>, Any<A>>);
232
233impl<A: IVm> PropertyLambda<A> {
234    /// No continuation: `a.b`.
235    pub fn end(self) -> Result<Any<A>, Any<A>> {
236        self.0.map(Member::read)
237    }
238    /// `|()`, terminal: `a.b(...args)`, the property called on its
239    /// receiver — an own property, or a built-in member function. No
240    /// region is open, so this is also what a `|!()` would be — "there is
241    /// no bit to clear".
242    pub fn end_call(
243        self,
244        runtime: &Any<A>,
245        args: impl FnOnce() -> Result<Any<A>, Any<A>>,
246    ) -> Result<Any<A>, Any<A>> {
247        self.0.and_then(|m| {
248            m.resolve(runtime)
249                .and_then(|()| args())
250                .and_then(|a| m.call(runtime, a))
251        })
252    }
253    /// `|()` of an intrinsic's helper, `slice(s, a, b)`: the member call
254    /// that asks no runtime, the own property else the native else the
255    /// `TypeError`, since no body defines the name
256    /// ([`Member::call_native`]).
257    pub fn end_call_native(
258        self,
259        args: impl FnOnce() -> Result<Any<A>, Any<A>>,
260    ) -> Result<Any<A>, Any<A>> {
261        self.0.and_then(|m| args().and_then(|a| m.call_native(a)))
262    }
263    /// `|?.()`: `a.b?.(...args)`, opening a region.
264    pub fn option_call(
265        self,
266        runtime: &Any<A>,
267        args: impl FnOnce() -> Result<Any<A>, Any<A>>,
268    ) -> OptionLambda<A> {
269        OptionLambda(Region::from(self.0).option_call(runtime, args))
270    }
271}
272
273/// A region is open and no receiver is live: the state after a `?.()` node,
274/// `a?.(...b)`, or a `|()` or `|?.()` step inside a region. Its steps are
275/// `|()` (`call`) and `|.` (`dot`); no `|?.()`, and no `|!()` — there is no
276/// receiver for either to justify.
277#[must_use]
278pub struct OptionLambda<A: IVm>(pub(crate) Region<A, Any<A>>);
279
280impl<A: IVm> OptionLambda<A> {
281    /// No continuation: the region closes, a skipped chain is `undefined`.
282    pub fn end(self) -> Result<Any<A>, Any<A>> {
283        self.0.end()
284    }
285    /// `|()`: call the current value, inside the region.
286    pub fn call(self, args: impl FnOnce() -> Result<Any<A>, Any<A>>) -> OptionLambda<A> {
287        OptionLambda(self.0.call_value(args))
288    }
289    /// `|.`: a property of the current value, inside the region; the value
290    /// becomes the receiver.
291    pub fn dot(self, key: impl FnOnce() -> Result<Any<A>, Any<A>>) -> OptionPropertyLambda<A> {
292        OptionPropertyLambda(self.0.dot(key))
293    }
294}
295
296/// Both bits live: the state after a `?.` node, `a?.b`, or a `|.` step
297/// inside a region. Every step is legal here: `|()` (`call`), `|.` (`dot`),
298/// `|?.()` (`option_call`) and `|!()` (`end_call`).
299#[must_use]
300pub struct OptionPropertyLambda<A: IVm>(pub(crate) Region<A, Member<A>>);
301
302impl<A: IVm> OptionPropertyLambda<A> {
303    /// No continuation: `(a?.b)`, the region closed with nothing after it.
304    pub fn end(self) -> Result<Any<A>, Any<A>> {
305        self.0.end()
306    }
307    /// `|()`: `a?.b(...args)`, the property called on its receiver, skipped
308    /// with the region.
309    pub fn call(
310        self,
311        runtime: &Any<A>,
312        args: impl FnOnce() -> Result<Any<A>, Any<A>>,
313    ) -> OptionLambda<A> {
314        OptionLambda(self.0.call(runtime, args))
315    }
316    /// `|.`: `a?.b.c`, skipped with the region.
317    pub fn dot(self, key: impl FnOnce() -> Result<Any<A>, Any<A>>) -> OptionPropertyLambda<A> {
318        OptionPropertyLambda(self.0.dot(key))
319    }
320    /// `|?.()`: `a?.b?.(...args)`, its own guard on the callee.
321    pub fn option_call(
322        self,
323        runtime: &Any<A>,
324        args: impl FnOnce() -> Result<Any<A>, Any<A>>,
325    ) -> OptionLambda<A> {
326        OptionLambda(self.0.option_call(runtime, args))
327    }
328    /// `|!()`, terminal: `(a?.b)(...args)`. The parentheses close the
329    /// region first, so a skipped chain is `undefined` here and the call
330    /// happens regardless — the arguments are evaluated, then `undefined`
331    /// is called and throws. A live chain calls the property on its
332    /// receiver; a thrown chain stays thrown, arguments untouched.
333    pub fn end_call(
334        self,
335        runtime: &Any<A>,
336        args: impl FnOnce() -> Result<Any<A>, Any<A>>,
337    ) -> Result<Any<A>, Any<A>> {
338        match self.0 {
339            Region::Live(m) => m
340                .resolve(runtime)
341                .and_then(|()| args())
342                .and_then(|a| m.call(runtime, a)),
343            Region::Skipped => args().and_then(|a| Any::undefined().call(a)),
344            Region::Thrown(e) => Err(e),
345        }
346    }
347}
348
349#[cfg(test)]
350mod tests {
351    use crate::vm::test::function::{identity, returns, rt};
352    use crate::{
353        naive::Naive,
354        vm::{Any, ToAny, ToArray, ToObject, error},
355    };
356
357    type A = Naive;
358    type Thunk = Box<dyn FnOnce() -> Result<Any<A>, Any<A>>>;
359
360    /// A thunk that must not be forced.
361    fn boom() -> Result<Any<A>, Any<A>> {
362        Err("boom".into())
363    }
364    /// A fresh arguments array, for a chain whose answer is not the array.
365    fn args() -> Result<Any<A>, Any<A>> {
366        Ok([7.0.to_any()].to_array().to_any())
367    }
368    /// Asserts that `chain`, handed a thunk over a fresh arguments array,
369    /// answers that very array: a call of `identity` reached its callee.
370    /// Arrays compare by identity, so the array is made once and cloned.
371    fn answers_arguments(chain: impl FnOnce(Thunk) -> Result<Any<A>, Any<A>>) {
372        let a = args().unwrap();
373        let thunk = a.clone();
374        assert_eq!(chain(Box::new(|| Ok(thunk))), Ok(a));
375    }
376    /// `{ f: identity, n: 1, u: undefined }`
377    fn object() -> Any<A> {
378        [
379            ("f".into(), identity()),
380            ("n".into(), 1.0.to_any()),
381            ("u".into(), Any::undefined()),
382        ]
383        .to_object()
384        .to_any()
385    }
386    fn key(k: &str) -> impl FnOnce() -> Result<Any<A>, Any<A>> {
387        let k: Any<A> = k.into();
388        || Ok(k)
389    }
390
391    // `PropertyLambda`
392
393    /// `a.b` — `end` is the read, throw included.
394    #[test]
395    fn property_end() {
396        assert_eq!(object().dot("n".into()).end(), Ok(1.0.to_any()));
397        assert_eq!(
398            Any::<A>::undefined().dot("n".into()).end(),
399            Err(error::nullish_to_object())
400        );
401    }
402
403    /// `a.f(...c)`, and `a.b(...c)` on a nullish `a`: the throw waited in
404    /// the lambda and `c` was never evaluated.
405    #[test]
406    fn property_end_call() {
407        answers_arguments(|args| object().dot("f".into()).end_call(&rt(), args));
408        assert_eq!(
409            Any::undefined().dot("f".into()).end_call(&rt(), boom),
410            Err(error::nullish_to_object())
411        );
412    }
413
414    /// A live, non-callable callee: the arguments run before the
415    /// callability check, so theirs is the throw.
416    #[test]
417    fn property_end_call_forces_arguments_before_callability() {
418        assert_eq!(
419            object().dot("n".into()).end_call(&rt(), boom),
420            Err("boom".into())
421        );
422        assert_eq!(
423            object().dot("n".into()).end_call(&rt(), args),
424            Err(error::unexpected_type())
425        );
426    }
427
428    /// `a.b?.(...c)`: guarded on the property, then a region.
429    #[test]
430    fn property_option_call() {
431        answers_arguments(|args| object().dot("f".into()).option_call(&rt(), args).end());
432        assert_eq!(
433            object().dot("u".into()).option_call(&rt(), boom).end(),
434            Ok(Any::undefined())
435        );
436        assert_eq!(
437            object().dot("n".into()).option_call(&rt(), boom).end(),
438            Err("boom".into())
439        );
440        assert_eq!(
441            Any::undefined()
442                .dot("f".into())
443                .option_call(&rt(), boom)
444                .end(),
445            Err(error::nullish_to_object())
446        );
447    }
448
449    // `OptionLambda`
450
451    /// `a?.(...c).length` and `a?.().f(...d)`: steps inside the region on
452    /// a live value.
453    #[test]
454    fn option_steps_live() {
455        assert_eq!(
456            identity().option_call(args).dot(key("length")).end(),
457            Ok(1.0.to_any())
458        );
459        let returns_object: Any<A> = returns(object());
460        answers_arguments(|args| {
461            returns_object
462                .option_call(|| Ok([].to_array().to_any()))
463                .dot(key("f"))
464                .call(&rt(), args)
465                .end()
466        });
467    }
468
469    /// A skipped region passes through every step, thunks untouched, and
470    /// ends as `undefined`.
471    #[test]
472    fn option_steps_skipped() {
473        assert_eq!(
474            Any::undefined()
475                .option_call(boom)
476                .dot(boom)
477                .call(&rt(), boom)
478                .dot(boom)
479                .end(),
480            Ok(Any::undefined())
481        );
482    }
483
484    /// A thrown region passes through every step, thunks untouched, and
485    /// ends with its throw.
486    #[test]
487    fn option_steps_thrown() {
488        assert_eq!(
489            identity()
490                .option_call(boom)
491                .dot(key("x"))
492                .call(&rt(), args)
493                .end(),
494            Err("boom".into())
495        );
496    }
497
498    // `OptionPropertyLambda`
499
500    /// `a?.f(...c)`, `a?.n.x`, `a?.u?.(...c)`, `a?.f?.(...c)`: the three
501    /// non-terminal steps on a live chain.
502    #[test]
503    fn option_property_steps_live() {
504        answers_arguments(|args| object().option_dot(key("f")).call(&rt(), args).end());
505        assert_eq!(
506            object().option_dot(key("n")).dot(key("x")).end(),
507            Ok(Any::undefined())
508        );
509        assert_eq!(
510            object().option_dot(key("u")).option_call(&rt(), boom).end(),
511            Ok(Any::undefined())
512        );
513        answers_arguments(|args| object().option_dot(key("f")).option_call(&rt(), args).end());
514    }
515
516    /// `a?.b.c(...d).e?.(...f)` on a nullish `a`: one region, everything
517    /// skipped.
518    #[test]
519    fn option_property_steps_skipped() {
520        assert_eq!(
521            Any::undefined()
522                .option_dot(boom)
523                .dot(boom)
524                .call(&rt(), boom)
525                .dot(boom)
526                .option_call(&rt(), boom)
527                .end(),
528            Ok(Any::undefined())
529        );
530    }
531
532    /// `|()` and `|.` are unguarded: on a live but nullish current value
533    /// the thunk is forced and the operation throws — `a?.b()` with `a.b`
534    /// undefined throws, it does not answer `undefined`. One case per
535    /// unguarded step, so that turning any of them into a guarded step
536    /// fails a test.
537    #[test]
538    fn unguarded_steps_throw_on_nullish_current_value() {
539        let returns_undefined: Any<A> = returns(Any::undefined());
540        assert_eq!(
541            returns_undefined.clone().option_call(args).call(boom).end(),
542            Err("boom".into())
543        );
544        assert_eq!(
545            returns_undefined.option_call(args).dot(boom).end(),
546            Err("boom".into())
547        );
548        assert_eq!(
549            object().option_dot(key("u")).call(&rt(), boom).end(),
550            Err("boom".into())
551        );
552    }
553
554    /// `|.` on a live but nullish current value throws at the read — after
555    /// the key is forced, as JavaScript evaluates `u[k()]`.
556    #[test]
557    fn option_property_dot_forces_key_before_read() {
558        assert_eq!(
559            object().option_dot(key("u")).dot(boom).end(),
560            Err("boom".into())
561        );
562        assert_eq!(
563            object().option_dot(key("u")).dot(key("x")).end(),
564            Err(error::nullish_to_object())
565        );
566    }
567
568    /// `(a?.f)(...c)` live, and `(a?.b)(...c)` on a nullish `a`: the
569    /// parentheses end the region, so the arguments run and `undefined` is
570    /// called — the case the host engines disagree on, and the
571    /// specification's reading.
572    #[test]
573    fn option_property_end_call() {
574        answers_arguments(|args| object().option_dot(key("f")).end_call(&rt(), args));
575        assert_eq!(
576            Any::undefined().option_dot(boom).end_call(&rt(), boom),
577            Err("boom".into())
578        );
579        assert_eq!(
580            Any::undefined().option_dot(boom).end_call(&rt(), args),
581            Err(error::unexpected_type())
582        );
583    }
584
585    /// A thrown chain stays thrown through `end_call`, arguments untouched.
586    #[test]
587    fn option_property_end_call_thrown() {
588        assert_eq!(
589            object().option_dot(boom).end_call(&rt(), boom),
590            Err("boom".into())
591        );
592    }
593}