Friday 24 October 2014

The Power of Eval

In this interview Eric Lippert does an interesting statement about the power of JavaScript's eval:

As for if there will be an eval-like facility for spitting fresh code at runtime, like there is in JavaScript, the answer is sort of. I worked on JavaScript in the late 1990s, including the JScript.NET langauge that never really went anywhere, so I have no small experience in building implementations of JS "eval". It is very hard. JavaScript is a very dynamic language; you can do things like introduce new local variables in "eval" code.

Yes, I had already noticed and profited of the astonishing behaviour of JavaScript's eval time ago. You're invoking a function, but in a way it's as if its code were running right inside the calling function (from a "classic" perspective you get access to the Stack of that calling function). So you can write code where the code inside eval gets access to variables in the current function scope or where eval declares new variables in such scope that can be used later on, like this:

var name = "xuan";
 console.log("name: " + name);
 eval("name += 'lluis';");
 console.log("name: " + name);

 eval ("var city='Toulouse';");
 //access to a variable defined in an eval works fine
 //so it's as if eval were adding the variable to the executio context of the enclosing function
 console.log("city: " + city);

and even like this (where a closure is accessing a variable that was created later than the closure itself:

var var1 = "hi"; 
 var closure = function(){
  console.log("closure start");
  //normal access to trapped variable  
  console.log("var1: " + var1);
  //access to trapped variable defined in an eval run after defining the closure
  console.log("var2: " + var2);
  var2 += " world";
  console.log("var2: " + var2);
  console.log("closure end");

 };
 eval("var2 = 'hello';");
 console.log("var2: " + var2);
 closure();
 console.log("var2: " + var2);

You can find the code here

So powerful and given how JavaScript Execution Contexts and [[scope]] work, the implementation does seem quite natural (well, for the case of defining a var in an eval and accessing to it later on it seems like the interpreter is adding that variable to the execution context of the enclosing function).

This is clearly much more powerful that the sort of eval that you can implement yourself in C# via Reflection (no need for Roslyn, Mono and linqPad has had this for many years).

All this reminded me about a post JavaScript needs blocks that I had come across some time ago. It's written by a Ruby guy and from my non Ruby perspective (that probably means that I'm not getting the full idea of the article) I don't see any major need for blocks in JavaScript. Anyway, I was thinking you could implement something similar to blocks by passing "code strings" and running them with eval. But there's a problem that I've found with such approach, trying to return from inside an eval. It would be a bit adventurous to think that such return would exit the enclosing function (this has nothing to do with the [[scope]] chain, so for such behaviour the interpreter would have to be designed with this case in mind). So to my surprise, when I did a test I found that JavaScript does not allow returning from inside an eval!

//trying to run this:
eval("return 1;");

//in node.js you'll get:
Illegal return statement
//and in FireFox you'll get:
SyntaxError: return not in function

Perl is the other language that I use on a regular basis that features an eval function. From the documentation:

In the first form, often referred to as a "string eval", the return value of EXPR is parsed and executed as if it were a little Perl program. The value of the expression (which is itself determined within scalar context) is first parsed, and if there were no errors, executed as a block within the lexical context of the current Perl program. This means, that in particular, any outer lexical variables are visible to it, and any package variable settings or subroutine and format definitions remain afterwards.

So yes, if you run some tests you'll see that the code inside eval will have access to the local variables of the enclosing function and to package ones, but local variable declared inside eval will exist only there, won't be accessible outside. Regarding a return statement inside an eval, it'll return from the eval itself, not from the enclosing function.

I've always complained about Perl not coming with a REPL, but well, thanks to eval writing a basic one on your own is that simple as this:

#!/usr/bin/perl
# repl.pl
# A simple REPL for Perl
#
# Copyright (c) 2014 by Jim Lawless (jimbo@radiks.net)
# MIT / X11 license
# See: http://www.mailsend-online.com/license2014.php
   
$t="\"Jimbo's Perl REPL\"";
while($t) {
   chomp $t;
   print eval($t),"\n";
   $t=;
}

No comments:

Post a Comment