Previous Page
Next Page

3.2. The Open Scripting Architecture

When System 7 was being created, along with Apple events and many other new technologies, it was already Apple's plan to create a language, AppleScript, that would give end users access to the power of Apple events. But, much to the disappointment of users and developers, there wasn't time to create AppleScript before the release of System 7 in mid-1991, and the bulk of the work was postponed until 1992-1993.

One of the conundrums facing the founders of AppleScript at this time was the architectural question of where the language should live. They could have made AppleScript the internal scripting language of a single application, like HyperCard's HyperTalk , but this would mean that the user would run AppleScript code entirely from within this one application, which was unacceptable. AppleScript needed to be available everywhere, and thus would somehow have to be part of the system. But what part? There was no good place, so a new one was created: the resulting structure is the Open Scripting Architecture (OSA).

3.2.1. Components

Under the OSA, a scripting language is implemented by a something called a component . (Components were not invented specially for the OSA; they existed already in connection with QuickTime.) Think of a component as a piece of self-contained functionality made available at system level so that any program can hook up to it and use it. One thing that's special about components is that they can be installed and uninstalled dynamically. So an OSA-savvy program doesn't necessarily think in terms of any particular scripting language; it asks the systemin particular, the Component Manager what scripting languages are presently installed, and if it wants to use one, the Component Manager provides access to it.

Because components are installed dynamically, this installation must actually take place while the computer is running. AppleScript is installed as the computer starts up and simply left in place, so that it's always available. You may recall that under Mac OS 9 there was an extension called AppleScript (in the Extensions folder of the system Folder). Its job was to install AppleScript as a component under the OSA as the computer started up. On Mac OS X, the same function is performed by a file called AppleScript.component, which is in /System/Library/Components; this type of file is called a component file.

One nice consequence of this architecture is that Apple can easily release upgrades to AppleScript, and the user can easily install them, with no effect on any other part of the system. AppleScript itself has a version number, which refers to the version number of the installed component that implements it; you can find out what this is by running the following one-word script in the Script Editor:

version

At the time of this writing, the result is "1.10.3".

3.2.2. Other Scripting Languages

One of Apple's purposes in designing the Open Scripting Architecture was to provide a place for other scripting languages that already existed, and for yet others that might exist in the futurehence the "Open" in its name. AppleScript is the only OSA language supplied by Apple, and in fact is designated the default scripting component, the one that is used when no particular scripting component is specified. Still, in theory there can be others.

In actual fact there have never been many other OSA languages. This may be because developers have not felt much need to supply them. (It also may be because the OSA itself hasn't quite lived up to its original promise.) Here are a few that I know of:

  • UserTalk , the internal scripting language of UserLand Frontier

  • QuicKeys Script , the scripting language of CE Software's QuicKeys

  • JavaScript, by way of Late Night Software's JavaScript OSA

  • AppleScript Debugger , the debuggable version of AppleScript used by Late Night Software's Script Debugger

  • The OSABridge components, created by Philip Aker

JavaScript OSA and AppleScript Debugger come in both a Mac OS 9 form (extensions called JavaScript and Script Debugger Nub) and a Mac OS X form (component files called JavaScript and ScriptDebugger). UserTalk and QuicKeys Script were available only on earlier systems. (UserTalk was truly dynamic, being loaded and available to other applications only when Frontier itself was running. On Mac OS X, UserTalk is still Frontier's internal scripting language, but it is not available as an OSA language . Similarly, QuicKeys Script is not present as an OSA language in the Mac OS X version of QuicKeys.)

Let's take JavaScript OSA as an example. (It's free, so you can easily try it out.) You put the JavaScript component file into /Library/Components; you then log out and log in. The effect is that JavaScript is now available as an OSA scripting language on your machine. This means that any OSA-savvy environment can see it. For example, in Apple's Script Editor, there's an OSA language popup menu at the left side of the top of the window, below the toolbar (in Figure 2-1 this says "AppleScript"); this popup menu now displays "JavaScript" as an alternative language, meaning that you can switch to JavaScript and compile and run a JavaScript program, right within Script Editor. This behavior illustrates the dynamic and generalized nature of the Open Scripting Architecture.

(A cool feature of JavaScript OSA is that it adds to the JavaScript language some classes allowing Apple events to be expressed. Thus it enables JavaScript to be used as an alternative to AppleScript for driving scriptable applications. See Appendix B.)

A slightly different approach is taken by the OSABridge components. They do not, of themselves, implement a language; rather, they act as a bridge (hence the name) from the OSA to the text-based shell scripting languages already present in Mac OS X (Perl, Ruby, Python, PHP, sh, and Tcl). Among other things, this bridge makes it easy to package a script in one of these languages as an applet or droplet (see "Applet and Droplet," later in this chapter), and the Tcl component lets your script implement a graphical user interface.

3.2.3. Talking to a Scripting Component

Knowledge of an OSA scripting language resides in the component, not in the OSA-savvy application that uses it. For example, earlier we said, in the Terminal:

% osascript -e 'tell app "Finder" to get disks'

The phrase 'tell app "Finder" to get disks' is an AppleScript expression; when we gave this command in the Terminal, it was obeyedreferences to all mounted volumes were displayed in the Terminal. But the Terminal doesn't know AppleScript. The shell, to which we're talking in the Terminal, doesn't know AppleScript. And the osascript program, which we call from the shell, doesn't know AppleScript either. So who does know it? The AppleScript scripting component , of course.

Similarly, the Script Editor, even though it is the place where users mostly work with the AppleScript language, does not in fact know any AppleScript. The Script Editor is merely a conduit, a front end to the AppleScript scripting component, where all the work of compiling and running scripts actually takes place. That is why, in the previous section, the Script Editor was willing to stop compiling and running AppleScript and start compiling and running JavaScript instead.

There are two approaches that an application can take when it wants to gain access to a scripting component. An OSA-savvy application like the Script Editor wants to be able to access any scripting component at all, indiscriminately. For this purpose, the OSA supplies a special component called the Generic Scripting Component (GSC). The program asks the Component Manager to let it talk to the GSC, and after that the GSC routes communications between the program and the appropriate scripting component such as AppleScript. Alternatively, an application might ask the Component Manager for direct access to one particular scripting component. Either way, once it's in communication with the appropriate scripting component, the application can do scripting in that scripting language.

Figure 3-2 diagrams a typical chain of events by which a program turns text into a runnable script, runs it, and is able to display the result, under the OSA:

Figure 3-2. The OSA in action


  1. The program asks the Component Manager to put it in touch with the scripting component.

  2. The program obtains some text and hands it to the scripting component with instructions to compile and run it. If any of the expressions in this script are equivalents of Apple events, those Apple events will be generated and sent, and we will then be in the world of Figure 3-1.

  3. The program asks the scripting component for the result as text; the scripting component complies.

The process diagrammed in Figure 3-2 is how a script editor application such as the Script Editor works. In fact, you can even build a simple working version of the Script Editor yourself, using the developer tool Interface Builder. With the example application shown in Figure 3-3, I can enter text, compile it, display it pretty-printed, and run it; yet, in creating this application, I didn't write a single line of code. This isn't because Interface Builder knows any AppleScript, but because the project uses an OSAScriptController to mediate between the Open Scripting Architecture and the text field in the window.

Figure 3-3. Rolling your own Script Editor


3.2.4. Maintenance of State

One of the structural problems that the Open Scripting Architecture was intended to solve was that of simultaneity. In those early days, remember, System 7 had only just introduced a way of letting more than one application run at the same time (cooperative multitasking ), without which Apple events and AppleScript would clearly be impossible. Now imagine the following scenario. Application A starts to run an AppleScript program, and will yield time to the AppleScript engine until the program is finished. The AppleScript engine encounters a line involving an Apple event, and will itself yield time, while the system delivers the Apple event to application B. But now suppose that, in the course of responding to this Apple event, application B were to start running some AppleScript code. The system can't say: "No, don't do that; my AppleScript engine is busy!" Rather, the system will have somehow to accommodate the possibility that two or more AppleScript programs may run simultaneously; every running AppleScript program thus needs somehow to be accorded the same status as a real application, with its own context and state that can be preserved while it is paused to give time to other processes.

Components are implemented in a special way that helps to solve this problem, which is one reason why components lie at the core of the Open Scripting Architecture. I'll describe metaphorically how it works. Imagine the AppleScript scripting component as a kind of little universe, a universe where the knowledge of AppleScript resides. And imagine that this universe can make copies of itself. When an application asks the Component Manager for access to the AppleScript scripting component, as at the top of Figure 3-2, it isn't simply given a telephone line to the one master copy of the AppleScript universe sitting in the system; instead, it's as if the Component Manager makes a copy of the AppleScript universe and gives the application a telephone line to that copy. The application now has its own private AppleScript universe. This private copy of the AppleScript universe is technically an instance of the AppleScript scripting component.

You can readily see how this architecture solves the simultaneity problem. Suppose we have two different applications, each of which gets a connection to the AppleScript scripting component and asks it to compile and execute a script. The AppleScript component does not get all flustered and say, "Oh, gosh, two different programs are trying to get me to do two different things at once!" Rather, there are in effect at that moment two different AppleScript component instancesthe one that the first application is talking to and the one that the second application is talking to. Each application asks its own instance of the AppleScript component to compile and execute its script, and there is no conflict or confusion at all.

One important and characteristic consequence of this architecture is that each instance of a component has a memory of its own. When paused, it remembers what it was doing. We say that a component can maintain state. When it is handed a script to compile, it remembers the script. After compilation, it remembers the compiled version of the script. After the script is executed, it remembers the result. And this is true of each individual instance of the component, separately; state is maintained individually for each instance. The component to which a program gets a connection is like an instance in the world of object-oriented programming (that's why it's called an instance). In terms of our analogy, each little AppleScript universe remembers what goes on in it. Thus an application is able to return again and again to its same AppleScript universe and refer back to things that happened earlier. Multiple applications can do this at the same time, and yet the AppleScript scripting component maintains state for each of them without getting confused at some global level, because it isn't operating at a global level. It's operating at a local levellocal to the application that summoned it.

You can see this happening in Figure 3-2. Recall that from the calling application's point of view, there are two separate steps: first the application hands the AppleScript scripting component some text to be compiled and executed; then, after execution is completed, the application asks for the result. The fact that the application can come back a second time and ask for the result of an execution that took place earlier is an example of the AppleScript scripting component's ability to maintain state. Similarly, although this does not appear in Figure 3-2, it would be possible to introduce a third step: we could have our application initially hand the text to the AppleScript component and ask it to compile it, but not (at that time) to execute it. Our application could then later return to the AppleScript component and say: "Hey, remember that script I had you compile a little while ago? Now I'd like you to execute it." And this will work, because the AppleScript component remembers the compiled version of the script.

Back in the world of System 7, on a computer where memory was tight and under a system architecture where context switching was slow, this approach was downright elegant. Virtually no information passes between the calling application and the AppleScript component instance unless the calling application explicitly demands it. Thus, for example, when the application asks for some text to be compiled, it does not receive a copy of the compiled code unless it asks for it. Then later, when the application comes back and asks that the compiled script be run, there is no overhead of handing across a lot of compiled code; the AppleScript component already has the compiled code, and is ready to rock and roll (as a computer scientist would say) with no further ado.

An application can ask the AppleScript component for a copy of the compiled code in various forms, though; and there are certain occasions when it will wish to do so. A script editor application, for example, immediately after compilation, will ask for a pretty-printed version of the code, for display in the script window. And then there's the problem of extended persistence . The internal memory of an AppleScript scripting component instance, after all, will not persist forever. The lifetime of one of these instances can be no longer than that of the application that summoned it. When that application quits, any of these little component universes that it may have created must also fade away, and all the stuff that a component instance has been remembering escapes like the air from a popped balloon. So if an application asks the AppleScript scripting component to compile a script and then wants the compiled version to persist somehow after the application itself quits, it must take special steps to obtain the compiled version from the AppleScript component and save it to disk. This in fact is just what a script editor application does when you save a compiled script file.


Previous Page
Next Page