Why does VBScript have Execute, ExecuteGlobal and Eval?


/>

JScript
has an extremely powerful (and almost always misused, but that’s another story) feature:
eval takes
a string at runtime and treats that string as though it were part of the compile-time
text of the program.  I added that feature
to VBScript version 5, but I did it with three methods:
Execute, ExecuteGlobal and Eval.  Why
three, when JScript makes do with one? Let’s start by examining in detail what the
JScript “eval” function does.

 

 The
JScript “eval” function takes a string, treats the string as JScript code, compiles
the string, executes the resulting code, and returns the value of the last
expression evaluated
while running the code.

 

Now,
in JScript (and many other C-like languages) there is a fairly weak distinction
between a statement and an expression.  For
example, this is perfectly legal JScript:

 

function
foo()

{

    1;

    2;

    3
+ 4;

}

 

This
doesn’t do much; it doesn’t even return a value! But that’s not the compiler’s
problem.  The behaviour of the line
3
+ 4;
for
instance is to add three to four and discard the result. 

 

Also
note that semicolons are optional in JScript (sort of — I’ll post more about
that later.) 

 

So
when you say
eval(“3
+ 4”)
in
JScript you get seven — the compiler adds the semicolon on, executes the statement
3
+ 4;
and
returns the result of the last expression computed.

 

Now
consider how JScript evaluates expressions that reference variables. The implementation
of eval is
smart enough to follow JScript’s rules for inner scopes shadowing outer scopes:

 

var
x = 20;

function
foo()

{

  var
x = 10;

  print(eval(“x”));
// 10 — eval uses local

}

 

This
seems reasonable, right?  But what if
you evaluate a declaration?

 

var
x = 20;

function
foo()

{

  eval(“var
x = 10”);

}

foo();

print(x);
// 20 — declaration was local to foo.

 

Well,
maybe it’s silly to want to add a declaration using eval.  But
hold on, as we already discussed, named functions are basically just variables.  Suppose
you wanted to add a function dynamically:

 

function
foo()

{

  eval(“function
bar(){ return 123; }”);

  print(bar());  //
123

}

foo();

print(bar());
// fails

 

Why
does the latter fail?  Because the
eval is
done in the scope of the function
foo,
so function
bar is
local to
foo.  When foo goes
away, so does
bar.

 

The
long and short of it is that if you want to affect the global name space, you have
to do it explicitly.  For example:

  

var
bar;

function
foo()

{

  eval(“function
barlocal(){ return 123; } bar = barlocal;”);

}

foo();

print(bar());
// succeeds, bar is a global function.

 

(And
of course,
bar now
refers to a closure. If
foo took
any arguments, they would be captured by
barlocal.)

 

Now,
suppose you were tasked with implementing
eval in
VBScript.  A few salient facts might spring
to mind:

 

1)
VBScript doesn’t have first class functions, so this trick with assigning a local
function into global scope won’t work. (Well, VBScript has a very weak form of first
class functions that I’ll discuss later.)

 

2)
But contrariwise, it would be a real pain in the rear if the VBScript’s “eval” couldn’t
access local variables, and worked ONLY at global scope.

 

3)
VBScript does not have this weird property that expressions are statements.  In
fact, you can’t determine whether you’re looking at an expression or a statement lexically
thanks to the assignment and equality operators being the same.  Suppose
you see this string:
X
= Y
  Does
that mean “set variable
X to
the value of
Y
or does that mean “compare
X to Y,
leave both the same, and return
True or False“?
Obviously we want to be able to do both, but how do we tell the difference?

 

There
are three things that we need to do: evaluate expressions,
execute statements using local scope
and execute statements using global scope.  My
philosophy is when you have three things to
do, implement three methods
.  Hence,
we have
Eval,
which takes an expression and returns its value,
Execute,
which takes a group of statements and executes them in local scope, and
ExecuteGlobal which
executes them in global scope.

 

I
understand why you need to distinguish between
Eval and Execute,”
I hear you say, “but why have both
Execute and ExecuteGlobal?  Why
not just add an optional -IsGlobal- flag to
Execute?

 

Good
question.  First of all, in my opinion
it is bad coding style to implement public methods which have very different behaviour
based on the value of a flag.  You do two
things, have two methods.

 

Second,
Boolean flags are a bad idea because sometimes you want to extend the method even
more.  VBScript has this problem in the
runtime — a lot of the methods take an argument which is either 0 for “case
insensitive” or 1 for “case sensitive” or
a valid LCID
, for “case sensitive in this locale”. What a mess! (Worse,
1 is a valid locale identifier: Arabic-with-neutral-sublanguage.)

 

In
the case at hand, I suppose an enumerated type would be superior to a Boolean and
extensible to boot, but still, it makes my skin crawl to see one public method that
does two things based on a flag where two methods will do.

 

Comments (5)

  1. Dan Shappir says:

    You do have a ExecuteGlobal of sorts in JavaScript using the new Function(…) notation. When you define a function in this way, it uses in the global scope rather than the containing scope, so:

    var x = 20;
    function foo()
    {
    var x = 30;
    var bar = new Function("x = 10");
    bar();
    }
    print(x);

    outputs 10 rather than 20. Add to that the JavaScript "feature" where variables declared without ‘var’ are added to the global scope, and you get the effect of a ExecuteGlobal.

  2. idle says:

    Dear Eric, is this the place for fan mail?

    I really enjoy reading your blog, especially as I’m up to my eyeballs in Javascript (optimising) at the moment. And your OLE date post, that’s a definite keeper. 🙂

  3. estee says:

    btw, the following doesn’t work:

    value=eval(‘{1:2,3:4}’)

    Seems that "eval" cannot distinguish blocks and associative arrays!!

    my workaround was:

    eval(‘value={1:2,3:4}’)

    am i right?

  4. Eric Lippert says:

    Indeed, you are correct.

    eval evaluates statements. JScript differs from VBScript in that in JScript, an expression is almost always a legal statement. In JScript, it is perfectly legal to say

    1;

    2;

    3+4;

    pointless, but legal. eval will therefore evaluate "2" as the statement "2;".

    But there is one kind of expression which is not a legal statement, and that’s an object literal. Therefore eval will not evaluate it, as you’ve seen.

    This fact also leads to some quirks in the automatic semicolon insertion algorithm. See the comments to my article "Quibbling over semicolons" for an example of this issue in that context.

  5. lava.sangeetham@gmail.com says:

    Hi i have to execute "sub procedure" and "calling sub" string statements as blow.

    Call abc

    sub abc

    msgbox("i am in sub")

    End sub

    i am reading this codes as string form other file .

    can i execute this Sub using Eval or Execute methods?