The Long View

 

AppleScript

 

    Scripting was something most early Macintosh users wanted to avoid.  If you were a MSDOS user, you probably had a lot of configurational problems.  Your problems focused on the management of two scripts, autoexec.bat and config.sys.  These were run by the MSDOS batch command interpreter when the system first started up.  The batch facility built into MSDOS was a relatively simple scripting system inherited from the operating system's predecessor, CP/M, and its predecessor, DEC's RT-11.  In those systems, you could create a text file containing a command or sequence of commands just as you might type them interactively into the command interpreter.  You could just type the name of that text file at the command prompt, and the commands in the file would execute in order, as if you had typed them.  The initial state of your MSDOS system was set by execution of the  contents of autoexec.bat and config.sys.  When you installed a new piece of software, it would probably alter one or both of those two initialization files, thereby breaking the tenuous configurational truce you established to accommodate all the conflicting desires of your previous software installations.  One of the great things about the original Macintosh was that the survival of your startup configuration did not depend on a couple of unintelligible batch scripts.  You did not need to learn a command language to use the Macintosh. This was the premise of a famous early Macintosh advertisement.

   The command language of IBM PCs and practically all other operating systems (including Unix), enabled you to execute programs by typing their names.  Most built-in and acquired programs performed some single function, and hopefully the program's name told you something about its function.  These program names functioned as verbs in the scripting language.  Sentences in the scripting language did not use explicit subjects; the subject was understood to be the computer. But they often accepted a direct object (in the grammatical sense) as a parameter.  For example, "del myfile.txt" in the MSDOS command language would tell the computer to delete the file named "myfile.txt".  Del was a the name of a program that deleted files, and myfile.txt was the name of a file to be deleted. 

    In contrast, simply executing a Macintosh program usually didn't do much of anything, except to create a graphical environment in which you could make things happen by pointing and clicking.  There was not much point making a command language for the Macintosh, in which every program was designed to carry on a prolonged dialog with you, not to automatically do a sequence of things without your input.  The language of that dialog was defined by mouse clicks, and short phrases that you typed using the keyboard.

    Still, there were occasionally situations when you wanted to automate things on the Macintosh.  In fact, the first thing most original Macintosh users ever saw their computers do was a kind of automated batch task, called Journaling, and used in the Guided Tour Of Macintosh disks.  The computer played back a movie of some other user doing things, and you could watch and learn (and listen to the narration on the accompanying cassette tape).  This was done by sending fake events to the Finder and other programs.  Events were representations of user activities, like clicking on an icon or menu, or typing with the keyboard, and sent to programs.  Journaling was originally designed as a debugging tool, and its educational function was discovered later.


The Toolbox Event Manager

    Macintosh programs worked by receiving and processing events, which were data structures created and enqueued by the Event Manager in response to user actions.  Programs removed events from the queue and processed them, usually in chronological sequence of occurrence.  There were 16 kinds of events defined by the Event Manager.  The meanings of 12 of these were pre-defined by the operating system, leaving 4 that could be defined by an application for its own purposes.  Journaling was done by creating sequences of events (or recording them) just as they would look to a program if a user was creating them by doing things with the mouse, or keyboard, insertion of disks, etc. 

    There were scripting programs for the Macintosh that also worked in fundamentally the same way.  Apple distributed one, called Macro Maker, with System 6, and there were some others as well (QuickKeys was popular).  These recorded a user’s actions, and could play back the corresponding set of events.  For example, if you wanted to move a window to the upper left corner of the screen, Macro Maker would record your click in the drag bar of the box, and your drag from there a particular distance.  If you wanted to play that back, it would only work if the window you were dragging was in the same location as the one at the time you recorded the macro, so the mouse click would land on the same part of the window.  It was a stupid kind of scripting, that didn’t try to define of what the user really wanted to do, but just recorded the events.  Still, that could be pretty useful, and these programs were popular.

  The application-defined events were often used by programs in a higher-level way. Some programmers started using the application-defined events as a way of sending messages from one part of the program to another.  In a memory-limited environment, in which a program's code could not all be in memory at one time, it was convenient for one part of the program to send messages to other parts using events.  Each time through the event loop, your program could unload most of its code segments, and then respond to the next event, loading just the code needed to service that kind of event.  A piece of temporarily resident code could enqueue a message about what to do next, include a handle to some data that needed to be processed, and then could allow itself to be removed from memory. The next time through the event loop, the program could read the message and load up a different set of code segments needed to perform that task.  It could then release the memory allocated for the data.  Tasks that needed to be performed in a sequence could be queued up in order, and performed in a series of traversals of the event loop.  Events of that kind were not just a record of where the mouse moved or what key was clicked, but represented a high level application-specific task that needed to be performed, and they could include data that needed to be processed.

    A drawback of this kind of program design was the small number of application-defined events.  Four application events was not very many.  And then Apple discovered that they too needed to add some additional kinds of events.


Switcher/Multifinder

    It started with Switcher.  Context switches in Switcher were synchronized with the event loop.  Programs were switched out when they went to the Event Manager to get a new event.  Andy Hertzfeld wanted to give programs a warning before they were switched out, so they could do some preparation. Switcher would send a program a suspend warning event just before switching it out, to give it a chance to do those things.  Andy used app4Evt.  Yes, it was necessary to add more events, but why did Andy have to take one of ours?  Why not just add an event number 17? Because of event masks.  Event masks made it possible to tell the Event Manager that you wanted to take events out of order.  By setting an event mask, you could tell the Event Manager what kinds of events you were interested in accepting and it would give you those only, leaving others on the queue to get later.  The event mask was sparsely coded using a 16 bit number, each bit of which represented a specific kind of event.  This let programmers specify an arbitrary set of event types in the mask, by setting the bit corresponding to each one. 16 bits meant 16 masks for 16 event types.  Changing the way event masks worked would have broken all the old code.  So Switcher usurped the last application-specific event, app4Event, and it became Resume/Suspend.  Programs that used app4Event for something else could not be 'Switcher Aware'.  They could still run, if they told  Switcher (in the SIZE resource) that they could not accept Suspend/Resume events.  On the next revision, programmers quit using app4Event and made their application work and play well with Switcher.  And so there were three.

    Weirdly, there was a mask, 512 decimal, corresponding to event 9 and located between the activate Mask and the network Mask, that was never used.  I'd like to hear the story behind that.  But apparently it was not available for some reason, because it would have been perfect for use by Switcher/Multifinder, Andy must have known that, and it was never used.

    With Multifinder, the landscape for events exploded.  Just as it was good for one part of a program to leave a message for the others using events, it was a natural for one program to use that same mechanism to leave a message for another program.  In the Multifinder, as in Switcher, programs were switched in and out when they checked the Event Manager for an event.  They really needed to be warned.  For example, before being switched out they should change the appearance of their windows to indicate their inactive state.  Programs did not only run when they were in the foreground.  A program could ask to get events sent to it when it was in the background.  It would briefly wake up from suspended execution with an event to process, and then go back to sleep when it had completed it.  These minor context switches, which did not change the user's perception of which program was active and which was in the background, also were synchronized by the Event Manager.  So orderly communication between programs should become the job of the Event Manager, and in System 7 that's what happened.  This required not just one new event type, but an entire world of new events.  Apple called them High Level Events, because they would be application-defined events that addressed the function of the program, rather than representing user actions at the atomic level.


AppleEvents

   One kind of High Level Event was supported in depth by Apple, and all programmers were required to use them, starting in System 7.  AppleEvents are High Level Events that are specifically designed for application-level inter-process communication.  Apple replaced the mechanisms used by the Finder for launching and terminating programs, and for opening and printing documents, originally part of the Segment Manager, and replaced them with AppleEvents.  If a program was launched along with a document (because for example a user double-clicked its document), it would be started by the Finder, and then sent an AppleEvent specifying that it open the document.  The Finder’s print document menu worked the same way, launching the program, sending it an AppleEvent telling it to print the document.  Programs could also receive AppleEvents telling them to quit.  Support for this basic set of 4 AppleEvents (open application, open document, print document, and quit application) was required for all programs starting with System 7.  This motivated programmers to become familiar with AppleEvents right away.  Apple did a good job of making it easy to create application-specific AppleEvents that could be fully customized for the specific needs of your application.  There was a way to package your application-specific data into the event, and the AppleEvent mechanism would pass it to another part of your code or to another program that understood its structure and meaning.  The AppleEvent mechanism was agnostic about the meaning of the events or the content of the data.  And there was no practical limit to the number of application-specific events you could make up.

    AppleEvents were designed to carry a lot of information.  They were complicated, because they were expected to specify a relatively large chunk of work that needed doing, and to provide all the data needed to do it.  This was necessary because the granularity of Macintosh multitasking was very coarse.  The Macintosh couldn’t do a context switch very often, and any program could delay context switches for an arbitrary period, just by not checking for a new event.  This had the advantage that a program could claim the processor for long enough to finish a time-critical function, but it meant that a conversation between two programs should consist of a small number of intensely meaningful transactions.  So AppleEvents were rich with content, but packaged in a very general way so as not to place too stringent constraints on what could be put in there.  They consisted of two sets of information, one generic set called attributes that carried information all AppleEvents had to have, and one set called parameters that were completely application-specific.  Among the attributes was the target of the AppleEvent, that is, the program that is supposed to receive the event, it’s ID, which was the function to be performed, like open application or print document, and a direct object, which was an application-specific designation of the thing to be operated on (for example for printing, it would specify the file to be printed).  Thus, an AppleEvent was a complete sentence, specifying (or rather requesting) some complete action from some targeted program. 

    In pre-System 7 Macintosh applications, everything revolved around handling low level events.  It was possible, using AppleEvents, to build nearly everything around high level events instead.  You could imagine a program that basically processed AppleEvents.  Low level events, like mouse clicks and key presses, would engage another separate piece of the program, that would do nothing with them except to translate the low level events (what the user was doing) into a set of AppleEvents that represented the user’s real intention.  If there were two user interface mechanisms for doing the same thing, they would create identical AppleEvents.  The heart of the program, the part that actually performed the program’s function, would not know or care how the user interface worked.  The user interface could be separated from the program’s function.  You could, if you wanted to, completely rebuild the user interface without touching the heart of the program, or visa versa.  And in fact, AppleEvents created by an entirely different program would be equivalent to ones created by the program’s own user interface.  This approach, called a “fully-factored” coding approach, was ideally suited for client-server style programming, and for cooperative programming among peer programs.  You could make a new program that drove any existing program around by sending it AppleEvents.  But the driving program would have to understand the meaning of application-specific events, and of the data they contain.  


Enter AppleScript

    The structure of AppleEvents seems to invite the creation of a new scripting technology.  AppleEvents are designed to represent the most meaningful atoms of function in a Macintosh program.  Programs can and must define those functional elements for themselves, to represent the functions they perform.   The script language is up for grabs.  Define any language you want to.  All you have to do is make sure that the language can be interpreted into a sequence of AppleEvents that will perform the required functions.  It was clear that application developers who wanted to make scripting languages for their programs should implement them as just an alternative front end for a fully-factored program. 

   But imagine that all programmers somehow could specify all the AppleEvents that their programs used and what they did, and make this information public.  If they could do that, it would be possible to make a single scripting language processor that could create and send AppleEvents to any program.  A single script might orchestrate the work of a number of interactive Macintosh programs sequentially to automate a multi-program workflow.  Not just a one-shot unidirectional workflow like the kind Unix shell script writers were used to, but an interaction among programs like the kind normally overseen by human operators.  The hard part was the direct object.  In Unix shell scripts, the direct object of commands are almost always files, or pipes (which are like files).  For Macintosh programs, the objects to be operated on by programs would have to be almost crazily specific.  Say I want the script to move one of five windows a program has open.  Or I want to change the typeface of the first sentence of every paragraph in one of four documents open in a word processor.  To do this, we would need the applications to specify not only the functions they can perform, but also the structure of the content of their documents, parsed as they exist in the mind of the program’s user.  Sounds almost impossible.  But a way for programmers to do this was already built in to AppleEvents.  You just had to give the programmers a reason to do it.

    The AppleScript project was started in the waning days of the 1980’s, after the structure of AppleEvents was already decided, and System 7 was in final testing.  The story of AppleScript’s development has been beautifully described by William Cook, who was a principal in both the design and implementation of the language.  The pre-existence of the AppleEvent specification meant that AppleScript designers didn’t have to (and didn’t have the opportunity to) design the form of the ultimate output of their script interpreter.  But they had to both design their language, and come up with a way for programmers to design their AppleEvents for optimum scriptability.  They also had to come up with a way for programmers to publish the structure (both verbs and direct objects) of the sentences that their programs would understand.  AppleScript could not possibly be released with the introduction of AppleEvents in System 7 (in May 1991), but it was released soon enough after that (with system 7.1.1, in 1993) so that most programmers had not yet designed a complex set of AppleEvents.  In the end, application-specific AppleEvents were created primarily to provide AppleScript capabilities, and the parts of the AppleEvent specification that were used in AppleScript were the only ones that saw widespread use.  Although its continuation was briefly threatened at the time of the NeXT reconquista in 1997, AppleScript is still alive and well as of this writing, and support for it in Cocoa continues to increase.  Clever Cocoa programmers design their program’s custom objects with an eye for supporting AppleScript. 

   

Many Nouns, Few Verbs

    Command line scripts like the ones used in MSDOS and in traditional Unix emphasize verbs.  Every new program is a new verb.  Objects are almost always directories, files or groups of files.  That is fitting, because in those operating systems, users collect many programs, each of which does a relatively specific thing.  The scheme envisioned by William Cook and Kurt Piersol that became AppleScript reflected the fact that Macintosh users had relatively few programs, each of which could do a lot of different things.  Of course, you could have represented all of those different things as a lot of different AppleEvent types.  But they decided to try to do a lot with very few AppleEvent types, and let programmers put their effort into the problem of describing and parsing descriptions of objects.  Most things a program can do can be boiled down to a small number of generic verbs, like get, set, make, save, delete, which can be applied to nearly any kind of data.  They envisioned a suite of commands like that, to which a program might subscribe, and implement in the way that makes sense for its application-specific data.  This would serve the needs of the AppleScript programmer, who would otherwise be facing an entirely new language for every application.  If the verbs were entirely application specified, one program would use “set” to mean the same thing that would be called “put” or “change” in another application.  The point was to integrate all Macintosh programs into one linguistic framework.  Of course, it was impossible to cover all the bases, and there would have to be application-specific commands.  But the stuff not covered by the generic commands would mostly be truly application-specific, and so would have to be learned by the AppleScript programmer to script that program anyway.

  But the data handled by each program had to be application-specific.  There was no way to guess categories of data that could be used collectively by all programs.  So the effort went into specifying the structure, rather than the specific nature of a program’s data.  AppleScript asked programmers to see their data as a hierarchy of containment of objects, with attributes defined at each level.  An application may contain documents, each of which has attributes like its name, format, and order on the screen.  Documents may contains columns and rows of text, each of which may have attributes such as their locations, meanings (e.g. numeric, date), typefaces, etc.  Columns and rows may contain cells which contain characters.  If a programmer would specify the containment hierarchy and attributes, then an AppleScript could specify a particular component in the hierarchy as the target of a command, such as “the second character in the cell in the 5th column of the 3rd  row of the document entitled mydoc”.  Once specified, this object could be modified in any way that made sense.  The programmer would just have to say which commands should apply to which objects.  AppleEvents came with a mechanism for specifying a list of objects and making them them collectively the direct object of an event.  If the AppleScript designers could come up with a way of describing a range or array of objects, they could all fit into one AppleEvent.  They did that and more. AppleScript also included several clever ways for creating such lists, such as the “whose clause”.  This was a method for requesting that the program add data objects to a list using a query, as in: all the paragraphs whose first word begins with “The”.  When properly supported by the programmer, this was a powerful construction that allowed a single AppleEvent to specify a large and complicated piece of computation. Remember it was important to boost the power of each AppleEvent, because of the granularity of Macintosh multitasking.


The ‘AETE’ Resource and AppleScript Dictionary

    How was an AppleScript programmer to discover the object containment hierarchy and command set of a program?  And how was the AppleScript interpreter to learn the various ID numbers associated with all the different object types and commands? 
The solution to both of these was a published dictionary located in the resource fork of the program.  Programs advertised their AppleScript capabilities in a dictionary that was located in an AETE resource.  The interpreter could look there and associate the names of objects and commands with their corresponding ID numbers that had to be packed into AppleEvents.  And when Apple made their Script Editor tool for writing and executing AppleScripts, they included an AETE parser that could show you the structure of a program’s scriptability.  The similarity in the structure of all programs’ command structures made the dictionary all you really needed to see to know what to do.  And  the AETE resource included plenty of spaces for including explanatory information for AppleScript programmers.  Apple’s Script Editor was (is) pretty cool, but it was eclipsed by third party tools, most notably Late Night Software’s Script Debugger.  This tool, which is still available for OS X, went way beyond the Script Editor. Its most important capability is, of course, debugging, including breakpoints, single stepping and variable display.  Recent versions even have an Explorer, which interacts with your program using AppleEvents, and can display the entire object hierarchy of the program at runtime, including the attributes of all objects.  Changes made in anything by execution of a script (or through the program’s user interface), become apparent in the Explorer by touching the reload button.  For a well-factored program, this is not only a way to debug AppleScripts, but a way to debug the program itself.  AppleScripts, like the original Journaling mechanism of the original Macintosh, can be a very effective debugging tool.


The AppleScript Language

    The most audacious, and the most controversial design choice make by the AppleScript team was the choice to fashion the syntax of the language after English.  A lot has been said about this.  It is worth mentioning that this was in many ways an homage to HyperCard and to Dan Winklers HyperTalk language that it used.  I once heard an interview of Winkler, in which he was asked what language most influenced HyperTalk.  He replied “English”.  This tells it all.  Winkler added some clever things to HyperTalk.  For example, there was a pre-defined variable called “it”.  If you got some information, but didn’t specify where to put it, it went into variable named “it”.  This allowed you to say something like “Get the first word in the first field, and put it in the second field”.  AppleScript copied as many of these amazing tricks as possible, and expanded on them.  People at that time were still dreaming of computer languages that could be used by anybody (not just trained programmers), and AppleScript, like HyperCard, was supposed to be a programming environment for non-programmers.  Did it works out that way?  I guess reluctantly I have to say that it did not.  AppleScript is an unusual language in that it is easier to read than it is to write.  This is because there are a number of different correct ways of saying something in English.  Usually, only one of them is a correct AppleScript sentence.  Reading the sentence, it makes great sense.  But because AppleScript resembles English so much, you start to expect that any correct English sentence ought to work.  When, after some frustrating experimentation, you find the correct form of the sentence for AppleScript, it makes you crazy that it isn’t really any more correct English than the four other constructions that you tried and were rejected.  Still, it is a highly readable language, and it is great to be able to read some other scripter’s code, and actually be able to immediately understand it. It was also just plain bad luck that AppleScript was introduced right about the time that Apple started their big push for software localization on the Macintosh.  Should there be versions of AppleScript for every script and every language in the world?  Yes, there probably should, and no, there will never be.

   Because so much gets done with each AppleEvent, and so with every line of AppleScript, the language is very efficient and executes rapidly.  People often forget to mention this when talking about AppleScript..  It is highly threaded, meaning that most of the work is pushed into the application program rather than the interpreter.  For the same reason, however, implementing AppleScript properly is a lot of work for the application programmer.  This is getting easier because of improvements in Cocoa.

  Despite all of its problems and the loud criticisms by pundits, AppleScript is a huge success.  It is supported by nearly all Macintosh OS X programs.  The programs that need it most, like the Adobe Creative Suite programs and Microsoft Office, support it best, and support for it by programmers in general continues to increase.  AppleScript Studio and Automator are brilliant products.   Apple’s leadership, who apparently once contemplated killing it, has apparently had a change of heart, and support for the language in Cocoa improves with each release.   I don’t know what the future will hold, and I am a little nervous about iOS, which has no support for any kind of scripting.  But iOS does not yet support much of anything in the way of inter-process communication.  It is new and barely getting started.  At some point, interprocess communication will have to be addressed, and when it is AppleScript will be the dominant influence on whatever happens.  At this point I can’t imagine the Macintosh without AppleEvents and AppleScript.  How many other System 7 features can say the same?

  


-- BG (basalgangster@macGUI.com)


 

Sunday, November 21, 2010

 
 
Made on a Mac

next >

< previous