Calls

Status: implemented

  • A function is called as JavaScript calls one: f(a, b) with no receiver, and o.m(a) with o as the receiver. A call is a step after a value, as a property access is, and what a step applies to is everything written before it — so f(1)(2) calls what f(1) returns, and o.m(1).n(2) calls n on what o.m(1) returned. The arguments are the list an array holds, a trailing comma and a spread included: f(...a, 4) passes a's elements and then 4, and (...r) => f(...r) forwards the values and never the array, since every callee builds its own rest array from the arguments.

    Parentheses around the property do not drop the receiver: (o.m)(a) passes o as surely as o.m(a) does, since the parentheses keep the property reference — only detaching the value loses it, as (0, o.m)(a) does with the comma operator. (o.m)(a) is in the language (grouping) and is the same program as o.m(a), down to the graph it compiles to; the detached spelling is the comma operator's (comma), which calls the function o.m holds as a plain one, and every other call written on a property is a call with a receiver.

    A method call's property is the access's, but its key is judged by the call rule and not the read rule (property access): a member function on fjs/js/prototype's prohibitedCalls is a compilation error, every other prototype name but length is a call the VM answers by the receiver's type, and the read of either stays refused. The represented interpreters and the native VM implement the admitted member functions, including the function text the exception adopts.

    The graph outputs preserve a call: the EDAG carries its node, generated Rust runs it through nanvm-lib, and FunctionalScript writes f(a) or a.b(c). JSON and DataJS output evaluate it and serialize its result, provided the selected data contains no function (output).

Examples

The compiler demo has no programs for this page yet: its accepted and refused examples are a follow-up.

The demo on this page runs sample.f.js, this page's example as a function of one number — a call of a call, and a method call with a spread argument — with every output fjs compile makes of it: the expression graph (EDAG), the Rust and the WebAssembly that Rust builds to. Its Run button calls the default export with 1 and 3, as JavaScript and as WebAssembly, and shows what each call returned or threw.

const add = (...x) => (...y) => x[0] + y[0];
const o = { m: (...v) => [v.length, ...v] };
export default (...a) => [add(a[0])(10), o.m(...[a[0], 4], 5)];

Reference

Demo

In FunctionalScript

fjs compile accepts this module, and it runs as JavaScript and as WebAssembly alike.

A FunctionalScript module and what fjs compile makes of it: the expression graph it lowers to, the Rust it compiles to on the NaNVM runtime, and the WebAssembly that Rust builds to. Run calls its default export with 1, then 3, first as the JavaScript it already is and then as WebAssembly, and shows what each call returned or threw and how long one call takes, timed over 1000 calls.

FunctionalScript source:

/**
 * The example of [`README.md`](./README.md) (Calls) as a function of one
 * number, for the page's WASM demo: a call of a call, and a method call with
 * a spread argument.
 *
 * @module
 */

/** @type {(...x: readonly number[]) => (...y: readonly number[]) => number} */
const add = (...x) => (...y) => x[0] + y[0];
const o = { m: (/** @type {readonly number[]} */ ...v) => [v.length, ...v] };
/** @type {(...a: readonly number[]) => unknown} */
export default (...a) => [add(a[0])(10), o.m(...[a[0], 4], 5)];

Expression graph (EDAG) that fjs compile lowers it to:

=>lengthframe 0frame 1body0=>{}[]=>lengthbody0=>{}m[]01=>lengthframe 0body0rest+=>lengthbody0[]()callee....()objkey...frame 1"m"[]+leftright[]0...1.rest()callee...frame 0[][]010[]...01[]5.objidxframe 00.objidxrest0.objkeyrest"length"[]0[]01.4.objidxrest0.objidxrest0

The same graph, as fjs compile writes it to a .edag.data.js file:

const $0=["rest"];const $1=["rest"];export default ["{}",[[":","default",["=>",0,[["=>",0,[],["=>",0,[["rest"]],["+",[".",["frame",0],0],[".",["rest"],0]]]],["{}",[[":","m",["=>",0,[],["[]",[[".",$0,"length"],["...",$0]]]]]]]],["[]",[["()",["()",["frame",0],[[".",$1,0]]],[10]],[".",["frame",1],"m",["|()",[["...",["[]",[[".",$1,0],4]]],5]]]]]]]]];

Rust that fjs compile writes from it, on the NaNVM runtime:

// @generated by `fjs compile`. Do not edit: recompile the source module instead.

use nanvm_lib::vm::unstable::{
    f64_any, spread_array, spread_item, string_any, string_key, value_item,
};
use nanvm_lib::vm::{Any, IStaticFunction, ToAny, ToArray, ToObject};

#[rustfmt::skip]
#[allow(clippy::if_same_then_else)]
pub fn module<A: IStaticFunction>(runtime: Any<A>) -> Result<Any<A>, Any<A>> {
    let _b7: Any<A> = A::static_function(|_self, _args| {
        let _b440: Any<A> = _args.rest(0).to_any();
        Ok(A::static_function(|_self, _args| {
            let _b383: Any<A> = _args.rest(0).to_any();
            (Any::dot(A::frame(_self)[0].clone(), f64_any(0x0000000000000000)).end())? + (Any::dot(_b383.clone(), f64_any(0x0000000000000000)).end())?
        }, 0, [_b440.clone(), A::frame(_self)[0].clone()].to_array(), Some("(...$1)=>$0[0]+$1[0]")).to_any())
    }, 0, [runtime.clone()].to_array(), Some("(...$0)=>(...$1)=>$0[0]+$1[0]")).to_any();
    Ok([(string_key("default"), {
        let _b12: Any<A> = [(string_key("m"), A::static_function(|_self, _args| {
            let _b668: Any<A> = _args.rest(0).to_any();
            spread_array([value_item((Any::dot(_b668.clone(), string_any("length")).end())?), spread_item(_b668.clone())?])
        }, 0, [runtime.clone()].to_array(), Some("(...$0)=>[$0.length,...$0]")).to_any())].to_object().to_any();
        A::static_function(|_self, _args| {
            let _b1352: Any<A> = _args.rest(0).to_any();
            Ok([{
                let _b16: Any<A> = (Any::call(A::frame(_self)[0].clone(), [(Any::dot(_b1352.clone(), f64_any(0x0000000000000000)).end())?].to_array().to_any()))?;
                (Any::call(_b16.clone(), [f64_any(0x4024000000000000)].to_array().to_any()))?
            }, (Any::dot(A::frame(_self)[1].clone(), string_any("m")).end_call(&A::frame(_self)[2], || spread_array([spread_item([(Any::dot(_b1352.clone(), f64_any(0x0000000000000000)).end())?, f64_any(0x4010000000000000)].to_array().to_any())?, value_item(f64_any(0x4014000000000000))])))?].to_array().to_any())
        }, 0, [_b7.clone(), _b12.clone(), runtime.clone()].to_array(), Some("(...$2)=>[(()=>{const $3=$0($2[0]);return $3(10);})(),$1.m(...[$2[0],4],5)]")).to_any()
    })].to_object().to_any())
}

WebAssembly text (WAT) of the functions compiled from this source, without the NaNVM runtime:

Refused:

File size 154705 exceeds maximum allowed size of 131072 bytes: 'nanvm/demos/wasm/calls.wat.marked'

Emergent Testing

Idle. Press Run to start the suite.

  • ./proof.f.mjs