The ability to evaluate code at runtime is a new feature in VBScript 5.0. Because this feature is new, many people aren't familiar with what runtime code evaluation is and how to use it.
What Is Runtime Code Evaluation?
To understand runtime code evaluation, you need to know the difference between how compiled-language scripts (e.g., C or C++ scripts) and interpreted-language scripts (e.g., VBScript or Perl scripts) execute. In a compiled-language script, a compiler reads the script's source code and translates it into low-level instructions that the hardware can understand, which is typically binary code. You then execute the binary code separately. If you use a compiled language, you must write a complete script before you can compile and execute it.
In interpreted-language scripts, a runtime module reads in the source code, translates the source code into binary code, and executes the instructions directly. This module reads, translates, and executes the source code line by line. Thus, you don't need to write a complete script before you execute it.
Because interpreted-language scripts act on each line individually, you can provide source code in two ways: You can include (i.e., hard-code) all the code in a source file or dynamically generate the code. Before VBScript 5.0, you had to hard-code all instructions in the source file. However, with VBScript 5.0, you can use special statements and methods to dynamically generate source code—a process called runtime code evaluation. With runtime code evaluation, you can read the code from a database according to certain runtime conditions or perform certain actions based on the privileges of the connected user. (Some interpreted languages have long offered runtime code evaluation. One such language is JScript. For more information about JScript, see the sidebar "Where to Find VBScript 5.0 and How It Compares with VBA and JScript," page 2.)
To use runtime code evaluation, you first need to generate a string that provides instructions about the tasks you want to accomplish during runtime. You then execute that string with the Execute statement, the ExecuteGlobal statement, or the Eval method.
The Execute Statement
The Execute statement executes the instructions that you specify as the argument during runtime. You must enclose the instructions in double quotes because the argument must be a string. For example, the code
Execute "MsgBox Date()"
executes the instruction MsgBox Date(), which displays a message box with the current date. To specify multiple sets of instructions, you separate each instruction with a colon. For example, the code
Execute "buf = Date(): MsgBox buf"
first assigns the current date to the buf variable, then displays the buf variable's contents in a message box.
Each variable has a certain visibility, or scope. In software, the scope specifies the portion of the script in which a certain statement or variable is visible and can be safely called into action. The scope can be global (i.e., you can invoke the statement or variable from any place in the script) or local (i.e., you can invoke the statement or variable only in the limited block of code where you placed the statement or declared the variable). A statement or variable that executes within the script's main body has a global scope.
When you use the Execute statement, the scope of the Execute statement and the scope of the objects it manipulates (i.e., any statements and variables) must be the same. For example, the code
buf = "Hello" Execute "MsgBox buf"
correctly displays Hello in a message box because the buf variable has the same scope as the Execute statement. However, in the code in Listing 1, the AnotherProc subroutine internally defines and invokes another subroutine called Foo. Foo can access the buf variable in the global scope, so the call to Foo at callout A in Listing 1 successfully displays the contents of the buf variable. However, the call to Foo at callout B in Listing 1 produces an error message because Foo isn't available outside the AnotherProc subroutine. In other words, this call has a global scope, but you can access Foo only from within the scope in which it was defined (i.e., within the AnotherProc subroutine).
As this example illustrates, if you call Execute from within a subroutine, the subroutine's variables and functions aren't available to any other module in your script. This limitation occurs because Execute modifies only the current scope. This behavior is by design. If you want a global scope, you need to use the ExecuteGlobal statement.
The ExecuteGlobal Statement
Microsoft introduced the ExecuteGlobal statement with only minimal documentation. (Some VBScript 5.0 documentation doesn't even mention the ExecuteGlobal statement.) ExecuteGlobal has the same syntax as Execute. However, unlike Execute's processing, every piece of code that ExecuteGlobal processes has a global scope. For example, as Listing 2 shows, if you use ExecuteGlobal instead of Execute, you can make the Foo subroutine available to calls outside the scope of the AnotherProc subroutine. In other words, you won't get an error message with the code in Listing 2. You can think of ExecuteGlobal as a preprocessor that moves all the code you pass to it to the beginning of the VBScript file.
Using ExecuteGlobal instead of Execute can affect the way in which the script treats variables. Consider the code in Listing 3. Because the declaration of buf (i.e., Dim buf) occurs within the body of the Foo subroutine, buf is local to Foo and therefore maintains its value of 1 only within Foo. You obtain the result of 1 no matter whether you use ExecuteGlobal or Execute.
If the variable declaration is outside the body of Foo, as Listing 4 shows, the results of using ExecuteGlobal or Execute differ. Using ExecuteGlobal makes buf a global variable that is accessible and modifiable from any point in the script. For example, if you use ExecuteGlobal and put the code
AnotherProc MsgBox buf
at the end of Listing 4, you receive a message box that displays a value of 1. If you use Execute instead ExecuteGlobal in the same code, you receive a message box that displays an empty string. An empty box appears because you defined buf within the body of AnotherProc. Thus, buf takes the initialization value. In this case, you didn't set an initialization value, so an empty string results. If you add an Option Explicit statement to this code, you receive an error message.
If you define buf in the script's main body before you call AnotherProc, as Listing 5 shows, buf is a true global variable. The resulting message box displays a value of 1, whether you use ExecuteGlobal or Execute.
The Eval Method
Both Execute and ExecuteGlobal are statements and therefore can't return values. Sometimes, you need a value back from the runtime environment. In other words, rather than mere execution, sometimes you need an evaluation of the code expression. A typical example of this situation is
buf = "a = b" Execute buf ExecuteGlobal buf Eval buf
The expression is ambiguous—it might mean the assignment of the value of a to b or the result of the comparison between a and b. When passed such an expression, both Execute and ExecuteGlobal understand it as an assignment. Eval, instead, understands it as an expression to evaluate.
Eval is a method that takes in a code expression and returns the value that the evaluation produced, if any. You can't use multiple statements, function declarations, or subroutine calls with Eval. The method accepts only expressions that involve variables, constants, and function calls. Eval considers variables in the context in which you call this method.
An Example: The Include Function
Here's a practical example of how you can use runtime code evaluation to help solve task automation problems. Suppose you want to write a script in which you need to import functions and classes from an external .vbs file. Unlike the XML-based format of Windows Script Host (WSH) 2.0, VBScript doesn't have a mechanism that lets you import the contents of files. If you don't have the time to learn WSH 2.0, you can use ExecuteGlobal in a handy workaround called the Include function. The Include function reads the content of a .vbs file so that you can execute any of the imported file's functions or classes in the main context of the current script. In this case, the use of ExecuteGlobal has the same effect as putting all the content of the imported file at the beginning of the script.
Listing 6 shows the Include function. The code begins by declaring the user-defined function and its variables. The code then creates an instance of the FileSystemObject object to open the .vbs file. (For information about this object, see my January 2000 through May 2000 columns.) After the code reads the file's contents into the buf variable, the code uses the ExecuteGlobal statement to make the file's contents available throughout the script.
To use the Include function, the code in Listing 6 must be present in your script. In this function, you can't replace ExecuteGlobal with Execute. If you make this replacement, you won't be able to call any of the imported functions or classes outside the Include function.
As this example illustrates, the ExecuteGlobal statement, Execute statement, and Eval method are useful tools that Microsoft has added to VBScript 5.0. Another useful addition to VBScript 5.0 is the support for regular expressions, which I'll cover next month.