JS string to function without evaluating

Hey guys,
I recently wanted to dynamically convert a string to a function and store it without evaluating (JS, not production code).
After a few problems , I came across this Github gist:

It didn’t do generators so did my own version with async generators/generators.

If you have need for it its pretty easy to use, just make sure you use " not ' and you cant use => (prototype problem).
You also cant "use strict" as it generates a new Function.

Summing up parseFunction can generate any function like:
let parsed = parseFunction("function(){return 123}") will equate to
// let parsed = anonymous function(){return 123};
parsed() // returns 123

My version with async and generators:

function parseFunction (str) {
  const   
      index = str.indexOf('{'),
      tindex = str.indexOf('(') 
      body = str.substring(index+1, str.lastIndexOf('}')),
      dec = str.substring(0, index),
      type = str.substring(0, tindex),
      params = dec.substring(dec.indexOf('(')+1, dec.lastIndexOf(')')),
      args = params.split(',');
      isAsync = str.trim().startsWith('async'),
      isGenerator = type.trim().endsWith('*'),
        
  args.push(body);
  
  if(isAsync && isGenerator){
    const AsyncGeneratorFunction = Object.getPrototypeOf(async function*(){}).constructor;
    function Fn () { return AsyncGeneratorFunction.apply(this, args);}
    Fn.prototype = AsyncGeneratorFunction.prototype;
  }
  else if(isAsync){
    const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
    function Fn () { return AsyncFunction.apply(this, args);}
    Fn.prototype = AsyncFunction.prototype;
  }
  else if(isGenerator){
    const GeneratorFunction = Object.getPrototypeOf(function*(){}).constructor;
    function Fn () { return GeneratorFunction.apply(this, args);}
    Fn.prototype = GeneratorFunction.prototype;
  }
  else{
    function Fn () { return Function.apply(this, args);}
    Fn.prototype = Function.prototype;
  }
  return new Fn();
}

Wow! That is really cool. This is something that @senocular and @krilnon would find fascinating.

Where it gets super useful is dynamic template literals.e.g.

var x = 1;
myObj = {  lit1: () => `the value of x is ${x}`};
myObj.lit1() //returns 'the value of x is 1'
 x = 5;
myObj.lit1() //returns 'the value of x is 5;

Well with something like this (based on the gist)

const dynLit  = function(x) {
  let y = `return ${x}`;
  let dynLit = function () {return Function.call(this,y)};
  dynLit.prototype = Function.prototype;
  return new dynLit();
}

You could do this:

myObj.lit2 = dynLit(" ${x} is not x");

and update myObj:

myObj = {  
    lit1: () => `the value of x is ${x}`,
    lit2: anonymous function() {return `${x} is not x`}
};

Now you can actually dynamically build objects and templates (with JS) that can use template literals and update when required…

This can be done more simply and with fewer limitations by just running the string through the function constructor and returning the result. So basically:

function parseFunction (str) {
  return Function(`return ${str}`)()
}

In fact this works for any source code expression, not just functions

let parsed = parseFunction("() => {return 123}")
parsed() // returns 123
let parsedNum = parseFunction("456")
parsedNum // 456

The original gist is going to run into problems trying to parse the function into its individual parts depending on how that function is defined. For example what happens when you have a default parameter assigned to an object literal?

let parsed = parseFunction("function(x = {}){return 123}")
// parse failure in finding first "{"

JavaScript already knows how to parse the function fully, so you can let it handle all of that and simply return the value in full from a host function who’s only job is to evaluate and return the result of the code (not be the actual function itself). This is also forward compatible since no parser needs to be updated for future JS features. Its all dependent on the runtime itself which already knows what it supports and how to parse it. So out of the box it works for arrow functions, generators, async, and everything else, as well as everything to come (like maybe generator arrow functions).

3 Likes

cool… I didn’t even realize you could call the function constructor without new Function. I’m definitely going to use this. Thanks

1 Like

That’s what the original code was doing with return Function.apply(this, args) and the async/generator variants :wink:

1 Like