Hacker’s Guide to Visual FoxPro
An irreverent look at how Visual FoxPro really works. Tells you the inside scoop on every command, function, property, event and method of Visual FoxPro.
EVALUATE()
, &, EXECSCRIPT()
Sometimes you need to refer to a file without knowing its name or you need to build an expression or even a series of lines of code and then execute it. Think of () (called “indirect reference” or “name expression”), EVALUATE()
(better known as EVAL()), & (macro expansion), and EXECSCRIPT()
as the guillotine that performs the execution. Hmmm, maybe that’s not such a good analogy.
In any case, all four items let you write code when you don’t know exactly what should happen when the code runs. There are four levels, which correspond to the four choices.
(cName)
uResult = EVALUATE(cExpression)
&cString
uResult = EXECSCRIPT(cCode)
First, perhaps you want to let a user select a report to run, or you’d like to use the same piece of code to open several different tables. Indirect reference lets you put a name in a variable and refer to the variable instead of giving the specific name. For example, if you write:
USE (m.cFileName)
FoxPro evaluates the variable cFileName and substitutes the name found there in the USE command.
Indirect reference works only when FoxPro expects a name. So you can’t write something like:
BROWSE FOR (m.cExpression)
because FoxPro expects an expression there, not a name.
Which brings us neatly to the next level. When FoxPro expects an expression, you can use EVALUATE()
, which tells FoxPro to evaluate the named variable, and then use the result as part of the command. (Incidentally, TYPE()
and EVALUATE()
use exactly the same level of indirection. If you have trouble figuring out what parameter to pass to TYPE()
, imagine that you’re passing it to EVALUATE()
to be evaluated.)
For example, in the BROWSE above, you could write:
BROWSE FOR EVAL(m.cExpression)
It turns out, though, that it’s not a great idea to use EVALUATE()
in a FOR clause. We’ll explain why below. But here’s a better example of EVALUATE()
:
STORE EVAL(cFieldName) TO SomeVar
EVALUATE()
is essential in reports, where macro expansion doesn’t work. You can use expressions like:
EVAL(FIELD(1))
to handle reporting on query results where the field names are unknown (such as in a crosstab). You can also use:
EVAL(cGroupExpr)
as a grouping expression to avoid designing separate reports where grouping is the only difference.
The third level is for the times when you want to build up whole pieces of code and then execute them. In this case, the string contains more than just an expression—perhaps even an entire command. Here, you need macro expansion. (These are “real” macros, not to be confused with the wimpy keyboard macros covered in SAVE MACROS
, PLAY MACRO
, and RESTORE MACROS
. Keyboard macros are just a simplistic recording and playback of keystrokes. This macro expansion—arguably one of Fox’s most powerful commands—tells Fox to interpret, compile and execute the code on the fly.) Macros are also used to substitute for reserved words. For example, you’ve probably written something like this:
cOldSafety=SET("SAFETY")
SET SAFETY OFF
* some code
SET SAFETY &cOldSafety
Here, the “ON” or “OFF” that was stored in cOldSafety is a character string, but the command SET SAFETY
expects a keyword of ON or OFF. The & macro operator converts the string to a keyword.
Another common place for a macro is in queries. You might build up any of the field list, the table list or the WHERE clause in a string and then macro-expand it like this:
SELECT &cFieldList ;
FROM &cTableList ;
WHERE &cWhere
Macros are powerful and work almost everywhere (though not, as noted above, in reports). There are a couple of gotchas, though.
First, only variables can be macro expanded. You can’t apply a macro to a field or property. So, you need two steps when the string to be expanded is in a field or property. Say the field cCmd of MyTable contains an entire command to be executed by macro expansion. You have to do it like this:
cVar=MyTable.cCmd
&cVar
The second gotcha is sort of a consequence of the first. When you macro-expand a variable, you can’t use the “m.” variable notation. This is okay because you can’t macro-expand a field, so there’s no ambiguity. But why does it work this way?
The “.” is a terminator for macro expansion. (Boy, this is a gory topic. We’ve got “executed” commands and “terminators.” Where’s Arnold?) This means the string to be expanded ends as soon as you reach a period. This can be very convenient if you want to add something to the expanded string or expand several strings in sequence. Before the addition of indirect reference to the Fox language in 2.0, it was essential because you could write something like:
USE &cFileName..EXT
but now, with named expressions, it’s better to write:
USE (cFileName+".EXT")
We still find uses for the double period in something like:
lcAlias = alias()
* more code in here
xx = &lcAlias..Field1
or:
ThisForm.pgfPageFrame.&PageName..Control.Property = .T.
though you could use EVAL(lcAlias+”.Field1”) in the first case.
The consequence of the period as terminator is what happens when you write something like:
&m.cVar
FoxPro sees this as “macro expand m, then tack the results onto ‘cVar’.” Since you don’t usually have a character variable called “m”, generally you get an error message when you do this. Even if there is a variable m, you certainly don’t get what you wanted.
What about performance? We often see people going to great lengths to eliminate macros from their code. But the truth is that, in certain cases, macros are actually faster than EVALUATE()
. Here are a couple of guidelines.
Always use indirect reference rather than EVALUATE()
or a macro. If FoxPro expects a name, indirect reference is the right choice.
In a command that will be executed repeatedly, it’s generally better to use a macro than EVALUATE()
. The macro is expanded once and substituted into the command while an EVALUATE()
expression is evaluated each time the command executes. This is also true for scoped commands that apply to multiple records, like the BROWSE above. The macro is expanded once and the result used on each record; EVALUATE()
is re-evaluated for each record. So, for the BROWSE above, you’re better off issuing:
BROWSE FOR &cExpression
Macros that execute only once aren’t really all that slow. The place to avoid macros is inside loops.
Indirect referencing, EVALUATE()
, and macros fill in parts of a line of code, or can even stand in for a whole line of code. But what if you want to compose and run multiple lines of code? In versions prior to VFP 7, you had to use a utility like Randy Pearson’s CodeBlock or RunCode.PRG from the VFP FFC subdirectory. But VFP 7 gives us EXECSCRIPT()
, which runs many lines of code, much like highlighting several lines in the Command Window and executing them (they’re probably running the same code internally). EXECSCRIPT()
takes a character string as its first parameter, which can be a variable, character field, or memo field. Subsequent parameters are passed to the code contained in the first parameter.
Perhaps you have a table of user-defined formulas. They all take two numeric parameters, nFirst and nSecond. A really cool module of your application allows them to define the formula, then stores it in a memo field in the UDCode table. The memo field, cleverly called cCode, might contain:
LPARAMETERS nFirst, nSecond
RETURN nFirst + nSecond
To execute the code in the memo field, issue:
nNewValue = EXECSCRIPT(UDCode.cCode, 2, 4)
Since EXECSCRIPT()
returns a value, you can embed it in other statements, such as within SELECT commands. The possibilities are mind-boggling. You can store code in memo fields, perhaps providing custom functionality for individual clients. You can build and execute multiple lines on the fly, built from information entered by the user.