NeilFraser / JS-Interpreter

A sandboxed JavaScript interpreter in JavaScript.
Apache License 2.0
1.96k stars 352 forks source link

how to pass multiple parameters and/or arrays/objects to functions? #271

Closed tsdexter closed 2 months ago

tsdexter commented 2 months ago

I'm sure I'm just doing something wrong but I have the following code in which code input like return scope.sq(2) works as expected, but return scope.sq2({x:2}) does not

reproduction: https://stackblitz.com/edit/vitejs-vite-jmttsc?file=src%2FApp.svelte,src%2Fapp.css&terminal=dev

        let result = {};
    const outputWrapper = function setOutput(value) {
        console.log(`setOutput`, { value });
        result = JSON.parse(value);
    };
    const funcs = {
        sq: (x) => x * x,
        sq2: (args) => {
            console.log(`sq2`, { args: args });
            return args.x * args.x;
        },
        times: (a1, a2) => {
            console.log(`times`, { a1, a2 });
            return a1 * a2;
        }
    };
    const init = function (interpreter, globalObject) {
        const scope = interpreter.nativeToPseudo({});
        interpreter.setProperty(globalObject, 'scope', scope);
        for (const key in funcs) {
            if (typeof funcs[key] === 'function') {
                interpreter.setProperty(
                    scope,
                    key,
                    interpreter.createNativeFunction((args) => funcs[key](args))
                );
            }
        }
        interpreter.setProperty(
            globalObject,
            'setOutput',
            interpreter.createNativeFunction(outputWrapper)
        );
    };
    const theCode = `
        function main() {
            ${code}
        };
        setOutput(main());
    `;
    const interpreter = new Interpreter(theCode, init);
    interpreter.run();
    console.log(`result setOutput`, result);

When I try to pass multiple arguments or arrays/objects, they don't work, only single primitive values.

Trying to use sq2 results in:

image

and using times(2,2) results in:

image

Is there a generic way to pass in functions that take multiple parameters and/or arrays/objects? The end goal is to be able to pass in an entire library in place of funcs that has functions accepting various types and amounts of parameters in the form of myLibrary.module1.func({ parm1, parm2 }: {parm1: number, parm2: number}) or myLibrary.module2.otherfunc(parm1: Array<number>, parm2: number)

NeilFraser commented 2 months ago

Good questions.

First, in order to convert native JavaScript objects into interpreter objects, we're going to need access to the interpreter. One way to do this is to move the API function definitions inside the init function, so they have access to the interpreter variable.

    const init = function (interpreter, globalObject) {
        const funcs = {
            sq: (x) => x * x,
            sq2: (args) => {
                console.log(`sq2`, { args: args });
                return args.x * args.x;
            },
            times: (a1, a2) => {
                console.log(`times`, { a1, a2 });
                return a1 * a2;
            }
        };
        const scope = interpreter.nativeToPseudo({});
        interpreter.setProperty(globalObject, 'scope', scope);
        for (const key in funcs) {
            if (typeof funcs[key] === 'function') {
                interpreter.setProperty(
                    scope,
                    key,
                    interpreter.createNativeFunction((args) => funcs[key](args))
                );
            }
        }
        interpreter.setProperty(
            globalObject,
            'setOutput',
            interpreter.createNativeFunction(outputWrapper)
        );
    };

With that done, now we can convert the JS-Interpreter object args into a native JavaScript object:

            sq2: (args) => {
                args = interpreter.pseudoToNative(args);
                console.log(`sq2`, { args: args });
                return args.x * args.x;
            },

And finally, we need to simplify your loop for defining these functions, so that it doesn't force a definition with a single argument:

                    interpreter.createNativeFunction(funcs[key])

Hope this helps!

tsdexter commented 2 months ago

Thanks a lot for the quick response. I will try it out