8.5. Top-Level Entities
A script object's top-level entities are variables defined at the top level of the script object having the special feature that even though they belong to the script object, and even though the rules of scope protect them from being visible to code outside the script object, they can be accessed (retrieved and assigned to) by code outside the script object. A top-level entity is either a script property, a handler, or a script object.
8.5.1. Accessing Top-Level Entities
The most important fact about a script object's top-level entities is that even though they are not directly visible to code outside the script object, they are accessible on demand to any code that can see the script object. This means that they can be both retrieved and assigned to (fetched and set) by such code.
A special way of talking is required for doing this. Because a script object's top-level entities are not visible outside the script object, it is necessary, in effect, to ask the script object politely to yield access to a top-level entity (and the script object always politely complies). We say that to access a script object's top-level entities, you must target that script object. The syntax for doing this comes in two forms:
In the second form, the keyword its (explained more fully in Chapter 11) is required to specify that we mean myScript's top-level entities and not some other variables. If you omit its from the line where you set x, you actually set a different x (an implicit global, as we'll see in Chapter 10), not the property of myScript. If you omit its from the line where you run innerScript, there is a runtime error, because no defined variable called innerScript is visible here.
(It makes no difference whether the keyword its appears before the call to sayHowdy. This special treatment of handler calls will be discussed in "Handler Calls, Commands, and Script Objects," later in this chapter.)
8.5.2. Persistence of Top-Level Entities
Another remarkable feature of a top-level entity of a script object is that it is persistent, meaning that its value survives the execution of the script. If a top-level entity's value is changed during the execution of a script, then if you execute the script again, the top-level entity will have this new value at the outset, as long as you haven't done something to reinitialize it.
So, for example:
script myScript property x : 5 end script tell myScript set its x to (its x) + 1 end tell display dialog myScript's x
In a script editor application, run that code. A dialog appears saying 6. Now, for dramatic effect, without closing the script window or making any other change, walk away and do something else, such as get a cup of coffee. Now run the code again. The dialog says 7!
A script as a whole is a script object, so we can demonstrate the very same thing much more simply with a property of the script as a whole:
property x : 5 set x to x + 1 display dialog x
The first time you run this, the dialog says 6. Then when you run it again, the dialog says 7.
This amazing behavior is possible because AppleScript has a memory (see "Maintenance of State" in Chapter 3). Your compiled script is in AppleScript's memory. After the script is executed, AppleScript retains the compiled script, and along with it, the values of all its top-level entities. A script property is a top-level entity. So after the first execution of the script, AppleScript is remembering that the script has a property x and that its value is 6. Thus when you run the script a second time and x is incremented again, it becomes 7.
Now, at this point, you are saying: "But wait! I can see x being initialized to 5 right there in the script. So what about that line? Are you saying that, the second time the script is executed, AppleScript is ignoring that line?" Well, it's really just a matter of what initialize means. It means to give a value to something that has no value. The second time the script is executed, x has a value already, remembered from the previous execution. So the initialization has no effect, because x doesn't need initializing.
Now let's go back to the previous code, with the script object myScript. What AppleScript is remembering between executions is the script as a whole. So what persists is the top-level entities of the script as a whole. But myScript is a script object defined at the top level, so it is a top-level entity of the script as a whole. Therefore it persists, and therefore its top-level entities persist. That's why that example works.
Generalizing, this means we can nest a script object in a script object in a script object to any depth we like, and as long as each script object is defined at the top level of its containing script object and the top script object is defined at the top level of the script as a whole, the properties of even the deepest script object in the nesting will persist between executions.
Here's an example two levels deep:
script outerScript script innerScript property x : 5 on increment( ) set x to x + 1 end increment end script tell innerScript to increment( ) display dialog innerScript's x end script run outerScript -- 6, then 7, and so forth
Here's an even more dramatic example, where one script object value is replaced wholesale with another:
script myScript script myInnerScript display dialog "Hello" end script run myInnerScript end script script myNastyScript display dialog "Get lost" end script run myScript set myScript's myInnerScript to myNastyScript
That code displays Hello the first time, but then it displays Get lost every time after that. The reason is that after the first time, myInnerScript has been replaced by myNastyScript, and this new version of myInnerScript persists.
22.214.171.124. What reinitializes top-level entities
Nothing lives forever, so just how long does all this persistence persist? Well, for one thing, it all comes to an end if you edit the script. That's because altering the script means that the entire script must be recompiled, and at that point the contents of the old compiled script, along with the values of the top-level entities from the previous execution, are thrown away from AppleScript's memory. So, compiling reinitializes top-level entities. (That's why throughout this section I've been telling you to execute the script multiple times without doing anything else. If you so much as type a space in the script window, the script's persistence goes away.)
Another thing that reinitializes top-level entities is generating the script object anew in code. To see what I mean, consider this code:
repeat 3 times script s property x : 1 display dialog x set x to x + 1 end script run s end repeat
There is a loop that repeats three times, and each time we see x being incremented.But what about afterwards? Do you think the value of x will persist after it increments three times? (Think about it before reading on.)
Ha ha, it was a trick question! The value of x will never even increment in the first place. Well, it increments, but then it is reinitialized. That code does not display 1 and then 2 and then 3; it displays 1 and then 1 and then 1. Why? Because each time through the loop, the entire script object definition happens again. So the variable s is defined anew, with a new script object. And each time, this new script object's top-level entities are initialized afresh. The script object s is not defined at the top level of a script object; it is not itself a top-level entity. Thus, no persistence.
Similarly, when a script object is defined within a handler, it has no persistence. The script object is created anew each time the handler runs. This is true even in a script object's explicit run handler. This code illustrates that a script object in an explicit run handler does not have persistent top-level entities:
script myScript on run script myInnerScript property x : 10 end script tell myInnerScript set its x to (its x) + 1 display dialog its x end tell end run end script run myScript -- 11
That code yields 11 every time it runs. As I said in "Code and the Run Handler" in Chapter 6, only stuff in the implicit run handler is at the top level. An explicit run handler is an ordinary handler, and stuff inside it follows the rules for being inside a handler. So myInnerScript is not defined at the top level of a script object; it is not a top-level entity, and has no persistence.
126.96.36.199. File-level persistence
So far we've talked about persistence only while a script window remains open for editing in a script editor application. But what about when a script is saved as a compiled script file and its window is closed? Does persistence work when the script file is opened for editing again later? And does persistence work when a saved compiled script file is repeatedly executed by a script runner?
The answer, unfortunately, is: it depends. Persistence doesn't work if you save, close, and open a script file using the current Script Editor. When you run the newly opened script, you find that everything is reinitialized. On the other hand, such persistence does work with older versions of the Script Editor (1.9 or before), or with Script Debugger. Create and run this script several times in Script Debugger:
property x : 5 set x to x + 1 display dialog x
Now save the script as a compiled script file, and quit, just to prove to yourself that AppleScript's own memory of the value of x is well and truly erased. Now open the compiled script file again (in Script Debugger) and execute it. The incrementing of x picks up right where it left off previously.
It would be really nice if persistence always worked in a compiled script file, but unfortunately this mechanism is not automatic, and in fact has nothing to do with AppleScript itself. AppleScript has no way to enforce file-level persistence, because AppleScript doesn't deal with files. It is up to the environment that's talking to the AppleScript scripting component, after it asks AppleScript to run a compiled script file, to save AppleScript's copy of the compiled script back into the compiled script file after execution. If it doesn't do this, then the compiled script file won't contain the new values, and the values won't persist. Script Editor and Script Debugger behave differently in this regard.
Similarly, script runners and other environments that execute compiled script files can behave differently. Some environments do save the compiled script file back to disk after each execution along with any changed top-level entities. For example, applets do this. So does Apple's Script Menu, and so does BBEdit with its Script menu. But Entourage's Script menu, for example, does not.
Actually, persistence in Apple's Script Menu (and perhaps some other environments) is itself inconsistently implemented. It turns out that the Script Menu implements persistence only if execution of the script returns a value (even though this value is otherwise ignored). Some actions, such as the user canceling out of a dialog, can cause the script to return with no value, and in that case, persistence fails. You can guarantee persistence in this situation by trickery, making sure that the script returns a value no matter what. In the case of display dialog you can catch the error produced by the user canceling and return a value anyway:
property x : 5 set x to x + 1 try display dialog x on error return 0 end try
All this inconsistency can be troublesome. You can't depend on file-level persistence; you must test each intended environment to see whether (and how) it works there. In the next section, I'll describe a mechanism for implementing file-level persistence from within a script, in such a way that it is guaranteed to work.