MoreOSL/ReadMe.html

<HTML>
<HEAD>
   <TITLE>Read Me</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF">
 
<H1>Read Me About MoreOSL</H1>
 
<P>1.0b1</P>
 
<P>MoreOSL, or MOSL for short, is a source code library for
implementing AppleScript support within your application. It has the
following key features.</P>
 
<UL>
   <LI>C implementation -- Many AppleScript support frameworks (for
   example, PowerPlant, MacApp) are available to C++ applications
   only. MOSL is entirely written in C.
   
   <LI>object focused -- Historically, DTS AppleScript support
   samples have concentrated on supporting text scripting. MOSL
   ignores text scripting and concentrates on the scripting of
   discrete objects, such as windows and items within windows.
   
   <LI>modern -- MOSL incorporates modern AppleScript techniques,
   such as 'deep' object resolution. It is also fully Carbon
   compatible.
   
   <LI>well tested -- MOSL includes a test application, TestMoreOSL,
   that demonstrates its capabilities. It also includes a large suite
   of AppleScript-based tests.
   
   <LI>comprehensive -- MOSL allows you to easily implement the bulk
   of the core event suite. MOSL supports all key forms except
   <CODE>formRelativePosition</CODE>. MOSL also supports data
   comparison for most revelant data types.
</UL>
 
<P>The MoreOSL library is targetted to run on a PowerPC with Mac OS
8.5 and above. It compiles and works best under Carbon, but it will
compile and work with InterfaceLib.</P>
 
<H2>Packing List</H2>
 
<P>The MoreOSL folder of the sample contains the following items:
</P>
 
<UL>
   <LI>"ReadMe.html" -- This documentation file.
   
   <LI>"MoreOSL.h" and "MoreOSL.c" -- The core scripting support
   library. This is the code that I expect you will want to drop
   straight into your application.
   
   <LI>"MoreOSLStringCompare.h", "MoreOSLStringCompare.c",
   "MoreOSLStringCompare.rsrc" and "MoreOSLStringCompare.script" -- A
   string comparison engine that supports all the AppleScript string
   operators in an international friendly fashion. See
   <A HREF="#MoreOSLStringCompare">below</A> for more details on why
   this is hard.
   
   <LI>"MoreOSLTokens.h" and "MoreOSLTokens.c" -- The definition of,
   and operations upon, the <CODE>MOSLToken</CODE> data type. This is
   the simple token structure used by MOSL. You may want to add or
   modify this structure for your application. See
   <A HREF="#TokenFormat">Token Format</A> for more details.
   
   <LI>"MoreOSLHelpers.h" and "MoreOSLHelpers.c" -- Code that
   supports standard properties of standard system objects, such as
   the application itself and windows. The code assumes that you're
   using the Mac OS 8.5 Window Manager interfaces. This code was
   written to get TestMoreOSL running; you may want to use it or,
   then again, you may choose to ignore it.
   
   <LI>"TestMoreOSL" -- A folder containing the MOSL test
   application, TestMoreOSL.
</UL>
 
<P>The remaining folders contain components of the DTS sample code
library MoreIsBetter that are needed for TestMoreOSL.</P>
 
<H2>Using the Sample</H2>
 
<H3>Getting Started</H3>
 
<P>You probably want to start by launching the TestMoreOSL
application. I've included a Carbon version in the sample package.
You should install CarbonLib 1.0.2 or higher before running this
application.</P>
 
<P><TABLE BORDER=0 WIDTH=498>
   <TR>
      <TD bgcolor="#FFFF99">
         <P><B>WARNING:<BR>
         
         </B>The debug version of TestMoreOSL logs extensively to
         BBEdit. When you run it, it will create a new window in
         BBEdit and spew lots of logging information to the window.
         You probably want to close any important documents you might
         have open in BBEdit before launch the application.
      </TD></TR>
</TABLE></P>
 
<P>To run the test application, simply launch it in the Finder. The
application will bring up a new, untitled document window. You can't
manipulate the window using the mouse, so don't even try.
<STRONG>TestMoreOSL only supports an AppleScript interface, it does
not support the user interface.</STRONG></P>
 
<P>Once TestMoreOSL is running, you can start scripting it. You might
want to drop the TestMoreOSL application on Script Editor to look at
its dictionary. Alternatively, you can open the "Test Script" script
(in the TestMoreOSL folder) and run it. This script displays a list
of test choices to run. You can run one of many different tests and
demos by selecting them and clicking Run.</P>
 
<H3>Seeing the Log Output</H3>
 
<P>If BBEdit is running while you execute your tests, you will see a
massive amount of logging information spewing out to an untitled
document in BBEdit. This is a log of how TestMoreOSL is processing
the incoming Apple events. You might want to stop the logging, either
because you want to use BBEdit or because it's too slow, or for some
other reason. There are a number of ways to do this.</P>
 
<OL>
   <LI>If you build the sample with debugging turned off, no log
   output will be generated (in fact, all the logging code is
   compiled out).
   
   <LI>If you launch TestMoreOSL and then close all the windows in
   BBEdit, the logging will be generated but won't be displayed.
   
   <LI>The following AppleScript command will turn off all logging in
   the debug version. <TABLE BORDER=0>
      <TR>
         <TD bgcolor="#EEEEEE">
            <PRE>tell application "TestMoreOSL"
    set debug to 0
end tell</PRE>
         </TD></TR>
   </TABLE>
</OL>
 
<P>In order to save time, "Test Script" uses this last feature to
disable all logging if you are running all tests. To find out what
the various bits in the <CODE>debug</CODE> property mean, look for
their definitions in "MoreOSL.h".</P>
 
<H3>Use MoreOSL in Your Code</H3>
 
<P>To find out more about how to use the MoreOSL library in your own
code, see <A HREF="#UsingMOSL">Adding Scriptability using
MoreOSL</A>.</P>
 
<H2>Building the Sample</H2>
 
<P>The sample was built using the standard MoreIsBetter build
environment (CodeWarrior Pro 2 compiler with Universal Interfaces
3.3.1). You should be able to build the project in CodeWarrior Pro
5.3 without difficulty. To build it, up the project, select either
the "Dbg-Carbon" or the "Dbg-PPC" target, and choose Make from the
Project menu. Either target will build a TestMoreOSL application.
</P>
 
<P>Both targets build to the same application name because that makes
it easier to maintain the "Test Script".</P>
 
<H2><A NAME="UsingMOSL"></A>Adding Scriptability using MoreOSL</H2>
 
<P>Even with MOSL, adding scriptability to your application is still
not easy. I suggest you take the following steps.</P>
 
<H3>Design Your Dictionary</H3>
 
<P>It's important that you design your AppleScript dictionary before
you start coding. Think in terms of what your target user needs to
do, not in terms of how your application works internally. A well
designed dictionary actually makes the job of implementing scripting
support easier, because you know a priori the list of classes, the
elements within each class, and the properties of each class.</P>
 
<P>I'm not an AppleScript dictionary expert, so you can't take
TestMoreOSL as a paragon of good dictionary design. Instead, you
should probably start by looking at other, well designed and easily
scripted applications. Also, you might want to look at some of the
resources on the
<A HREF="http://developer.apple.com/techpubs/macos8/InterproCom/AppleScriptDev/applescriptdev.html">AppleScript
for Developers</A> web site. I find Cal Simone's classic
<A HREF="http://developer.apple.com/dev/techsupport/develop/bysubject/iac.html">develop
Articles</A> to be especially helpful.</P>
 
<H3><A NAME="TokenFormat"></A>Token Format</H3>
 
<P>Once you know what classes you have to support, the next step is
to think about tokens. When I started MOSL, understanding what would
be a good token format was the hardest initial design obstacle.
Fortunately, MOSL makes decision much easier for you.</P>
 
<P>MOSL uses a very simple design for its tokens, as described in
"MOSLTokens.h". The basic format is a fix-sized record. The fields
are defined and used by MOSL, except for the <CODE>tokData</CODE>
field, which is specificially intended to store a pointer to the
native object that the token represents. If you're using C++ (why
aren't you using the PowerPlant or MacApp scripting support!?!), you
can use this to store a C++ object pointer. If you're using C, you
typically store a pointer to your 'object' equivalent, such as a
<CODE>DialogRef</CODE> or <CODE>WindowRef</CODE>, or a pointer to the
data structure that holds the object's state.</P>
 
<P>The meaning of <CODE>tokData</CODE> is class-specific, so you
don't have to store the same data in the field for each class. For
example, TestMoreOSL stores a <CODE>DialogRef</CODE> in
<CODE>tokData</CODE> for all window-like classes
(<CODE>cDocument</CODE>, <CODE>cWindow</CODE>,
<CODE>cNodeWindow</CODE>, <CODE>cAboutWindow</CODE>) and stores a
pointer to the underlying <CODE>Node</CODE> structure for nodes
(<CODE>cNode</CODE>).</P>
 
<P>Remember, your token should never be the actual data itself, it
should represent the data (that is, be a pointer to the data). This
is critical to understand because the token is passed to both your
getter and setter object primitives (see below).</P>
 
<P>Finally, it is possible for you to safely extend the
<CODE>MOSLToken</CODE> structure to add extract fields. All of the
routines that MOSL uses to operate on tokens are in the
"MOSLTokens.c" file, so your changes should be isolated to that file.
</P>
 
<H3>First Code</H3>
 
<P>The first code you should write is to call the
<CODE>InitMoreOSL</CODE> routine. You must pass it the
<A HREF="#EventTable">event table</A>, the
<A HREF="#ClassTable">class table</A>, the
<A HREF="#DefaultEventHandler">default event handler</A>, and a
error-to-string callback. The event table and class table are later
sections. The other parameters are discussed here.</P>
 
<P>The error-to-string callback is necessary because, if an Apple
event fails, MOSL automatically handles putting an error message into
the reply. However, MOSL does not have access to localised resources,
so there's no way it can put the correct localised error message into
the reply. To get the localised error message, it calls the
error-to-string callback. Your initial prototype need not include an
error to string callback (TestMOSL doesn't) but a final product must.
</P>
 
<H3><A NAME="EventTable"></A>Event Table</H3>
 
<P>The next step is to construct your event table. This is an array
of <CODE>MOSLEventEntry</CODE> structures which describe the Apple
events to which your application responds. The exact structure is
given in "MoreOSL.h", but there are some important things you need to
know.</P>
 
<UL>
   <LI>The order of the events in the array is significant to MOSL
   only in that it must match the order in the
   <A HREF="#ClassEventHandlers">class event handler table</A> for
   each class.
   
   <LI>You can work out the list of events in the event table by
   simply looking through your dictionary. See, I told you that
   designing your dictionary first was a good idea.
   
   <LI>For each event, you must specify whether the direct object is
   required, illegal, or optional. You must also specify how the
   result should be treated. For the core event suite, you can just
   copy this information from the <CODE>kEventTable</CODE> in
   "TestMoreOSL.c".
   
   <LI>The event table doesn't actually contain pointers to event
   handlers. The actual event handlers are supplied as part of the
   class table.
</UL>
 
<H3><A NAME="ClassTable"></A>Class Table</H3>
 
<P>The class table is an array of <CODE>MOSLClassEntry</CODE>
structures that describes the classes that your application supports.
Each class table entry contains the following information.</P>
 
<UL>
   <LI>The ID of the class itself. You can get this from your
   dictionary.
   
   <LI>A pointer to a <A HREF="#PropertyTable">property table</A>.
   
   <LI>A pointer to a <A HREF="#ClassEventHandlers">class event
   handlers table</A>.
   
   <LI>A set of <A HREF="#ObjectPrimitives">object primitives</A>.
</UL>
 
<P><A NAME="PropertyTable"></A>The property table is an array of
<CODE>MOSLPropEntry</CODE> structures that describes the properties
for the class. Each entry indicates the property name (actually, a
four character code which the dictionary translate to a user-visible
name) and whether the property is read-only. Both pieces of
information are readily available from your dictionary. Your class's
property table should be terminated by either a property of name
<CODE>kMOSLPropNameLast</CODE>, or a property of name
<CODE>pInherits</CODE> if this class inherits properties from another
class. See "MoreOSL.h" for exact details on how this is set up.</P>
 
<P><A NAME="ClassEventHandlers"></A>The class event handlers table is
an array of function pointers that MOSL calls when it receives an
Apple event whose direct object is in your class. The class event
handlers table is indexed by the same indices as used in the
<A HREF="#EventTable">event table</A>. Every class must have an entry
for every event, although if the event makes no sense for a class you
can set the entry to nil. Finally, MOSL provides a number of general
class event handlers for handling the "count", "exists", "get data",
and "set data" events in terms of your class's object primitives. In
most cases, you can use these general event handlers for these events
and just implement the object primitives.</P>
 
<P><A NAME="ObjectPrimitives"></A>The object primitives for a class
are individual callbacks that MOSL calls under specific
circumstances. Unlike the class event handlers, each object
primitives has a different prototype. Like the class event handlers,
MOSL provides a number of general object primitive routines that you
can use to implement one primitive in terms of the others.</P>
 
<UL>
   <LI>"getter" is the only primitive that is absolutely required for
   each class. MOSL uses this primitive to access properties of
   objects of the class, and to get the canonical object specifier
   for objects of the class.
   
   <LI>"setter" is required if the class has any properties that can
   be modified.
   
   <LI>"counter" and "accessByIndex" are required if objects of the
   class have elements.
   
   <LI>"accessByUniqueID" is required if objects of the class have
   elements and you want to support accessing those elements by
   unique ID. If your class supports the "counter", "accessByIndex",
   and a unique ID property that can be obtained by calling "getter",
   you can use the <CODE>MOSLGeneralAccessByUniqueID</CODE> in place
   of your own primitive.
   
   <LI>"accessByName" is required if objects of the class have
   elements and you want to support accessing those elements by name.
   If your class supports the "counter", "accessByIndex", and a name
   property that can be obtained by calling "getter", you can use the
   <CODE>MOSLGeneralAccessByName</CODE> in place of your own
   primitive.
   
   <LI>"coerceToken" is required for any class that is derived from a
   base class. It is not necessary for the base class (or any other
   standalone class). The primitive allows MOSL to coerce tokens from
   a derived class to the base class, which is necessary for object
   comparisons and <CODE>formRange</CODE> support.
</UL>
 
<H3>Implementing MOSL Callbacks</H3>
 
<P>As you implement the various MOSL callbacks for your classes, you
should keep the following in mind.</P>
 
<UL>
   <LI>Look carefully at the comments in "MoreOSL.h" for an exact
   specification of what you must do in your class event handlers and
   object primitives.
   
   <LI>For easy, if possibly inefficient, code, use MOSL's general
   class event handlers (<CODE>MOSLGeneralCount</CODE>,
   <CODE>MOSLGeneralExists</CODE>, <CODE>MOSLGeneralGetData</CODE>,
   <CODE>MOSLGeneralSetData</CODE>) and object primitives
   (<CODE>MOSLGeneralAccessByUniqueID</CODE>,
   <CODE>MOSLGeneralAccessByName</CODE>) where possible.
   
   <LI>When accessing Apple event parameters in your class event
   handlers, get the parameter using <CODE>AEGetParamDesc</CODE> with
   <CODE>typeWildCard</CODE> and then call
   <CODE>MOSLCoerceObjDesc</CODE> or
   <CODE>MOSLCoerceObjDescToPtr</CODE> to coerce the data to the
   appropriate type. This ensures that commands whose arguments are
   properties or objects (like the one shown below) work
   correctly.<TABLE BORDER=0>
      <TR>
         <TD bgcolor="#EEEEEE">
            <PRE>tell application "TestMoreOSL"
    set name of window 1 to name of window 2
end tell</PRE>
         </TD></TR>
   </TABLE>
   
   <LI>Your "getter" object primitive must implement two distinct
   pieces of functionality.
   
   <OL>
      <LI>When passed a property token (<CODE>tokType</CODE> is
      <CODE>typeProperty</CODE>), your getter must return the value
      of the property itself. If the property is a reference to
      another object, the value must be the token for that object.
      
      <LI>When passed an object token (<CODE>tokType</CODE> matches
      the class for the "getter" object primitive), your getter must
      return an object specifier for that token. To create the
      container object specifier, your getter should construct a
      token for the container and call the container class's getter.
      See <CODE>CNodeGetter</CODE> in "TestMoreOSL.c" for an example.
   
   </OL>
   
   <LI>Your "getter" object primitive will not be called for
   properties other than those listed in your property table.
   Similarly, your "setter" object primitive will not be called for
   properties marked as read-only. You will be called for properties
   from classes from which you inherit properties. Typically you can
   call your super class's "getter" or "setter" object primitives to
   handle such calls (assuming your token format is compatible).
   
   <LI>If your callback needs to get or set standard properties of
   standard system objects (the application, or windows), you may
   find the routines in "MoreOSLHelpers.h" helpful.
   
   <LI>If your callback need to compare strings the same was MOSL
   does (harder than it sounds!), it can call the routines exported
   by "MoreOSLStringCompare.h".
   
   <LI>When implementing your "make" class event handler, you may
   want to take advantage of the <CODE>MOSLSetObjectProperties</CODE>
   routine. For an example of how to do this, look at the
   <CODE>CDocumentMake</CODE> routine in "TestMoreOSL.c".
</UL>
 
<H3>Default Event Handler</H3>
 
<P>When you initialise MOSL, you pass it a class table that includes
all of the classes of AppleScript objects in your application. When
an Apple event arrives, MOSL first resolves the direct object and
then calls the corresponding class event handler. This raises the
question, what happens if the direct object isn't in the class table.
Naively, you might expect that the event just fails. However, it is
important to process certain events whose direct objects are never in
the class table. For example, it is necessary for your application to
see the "open documents" event, even though the direct object is a
list of aliases and you don't have an alias class in your class
table.</P>
 
<P>The default event handler is the solution to this problem. If MOSL
processes an event and can't find its direct object in the class
table, it passes the event to the default event handler. For an
example of how that handler should respond, see the
<CODE>DefaultAppleEventHandler</CODE> routine in "TestMoreOSL.c".
</P>
 
<H2>Caveats</H2>
 
<P>MOSL does not implement <CODE>formRelativePosition</CODE>.</P>
 
<P>MOSL does not support properties, operators, and elements for
built-in types. For example, if you ask for <CODE>length of name of
window 1</CODE>, MOSL will fail. Notably,this also fails in the
Finder. I consider the requirement that all applications support all
properties, operators, and elements of built-in types to be a bug in
AppleScript [2444537]. The accepted workaround is that the script
developer must get the relevant property and then apply the operation
inside AppleScript. MOSL should generate a better error number when
it fails, however.</P>
 
<P>In a similar vein, MOSL does not provide any support for
properties or elements whose values are records or lists. In the MOSL
philosophy, if an object has a property whose value is a record, you
should make it a reference to another object, and if an object has a
property whose value is a list, you should make it an element of the
object. I've received conflicting advice as to whether this is the
right approach. Let me know what you think.</P>
 
<P>On the non-Carbon built, many of the window property accessors
implemented in "MoreOSLHelpers.h" fail on Mac OS 8.5 through 8.6.
This is because, on those systems, the Window Manager routine
<CODE>GetWindowAttributes</CODE> only works for windows that were
created using <CODE>CreateNewWindow</CODE>. The problem is manifest
in TestMoreOSL in that the script below fails with a
<CODE>paramErr</CODE>. I haven't had time to implement a substitute
routine to call on those system versions. It is not a high priority
because this routine works properly on those systems if you call it
from Carbon, and I expect the majority of MOSL clients to be
Carbonated.</P>
 
<P><TABLE BORDER=0>
   <TR>
      <TD bgcolor="#EEEEEE">
         <PRE>tell application "TestMoreOSL"
    get zoomed of window 1
end tell</PRE>
      </TD></TR>
</TABLE></P>
 
<P>The string comparison implementation for Carbon relies on
AppleScript's ability to coerce internation text
(<CODE>typeIntlText</CODE>) to Unicode
(<CODE>typeUnicodeText</CODE>). This feature was added in AppleScript
1.3.2 (Mac OS 8.5). If your application runs under Carbon on Mac OS
8.1, you will have to either install your own coercions handlers or
revert to using the non-Carbon string comparison implementation.</P>
 
<P>TestMoreOSL does not handle the entire <CODE>'core'</CODE> suite.
The missing constructs, listed below, are unimplemented mostly
because they make no sense for the test application. However, it's
hard to guarantee that MOSL is correct and complete without
implementing them all.</P>
 
<UL>
   <LI>"saving in" parameter to "close"
   
   <LI>"at" parameter to "make"
   
   <LI>"print" event
   
   <LI>"delete" event
   
   <LI>"duplicate" event
   
   <LI>"move" event
   
   <LI>"data size" event
   
   <LI>"select" event
   
   <LI>"suite info", "event info", "class info" events -- According
   to AppleScript engineering, these events have been deprecated.
   
   <LI>"selection" property of "application"
   
   <LI>"clipboard" property of "application"
   
   <LI>"selection-object" class
   
   <LI>"alias" class
   
   <LI>"insertion point" class
</UL>
 
<P>MOSL chooses to return the best type for indexed elements. For
example, if you ask TestMoreOSL for every window and there is one
<CODE>about window</CODE> and one <CODE>node window</CODE>, you will
get back a list <CODE>{about window id 128 of application
"TestMoreOSL", node window id 150 of application
"TestMoreOSL"}</CODE> as opposed to <CODE>{window id 128 of
application "TestMoreOSL", window id 150 of application
"TestMoreOSL"}</CODE>. This seems like the right thing to do and it
is in line with other scriptable applications that I tested with.
This design decision effects how I handle the <CODE>index</CODE>
property for windows. Because I required that <CODE>index of every
window</CODE> return a continuous range of numbers, I require that
the <CODE>index</CODE> property be the index of the window within the
entire window list, not the index of the window within its class of
windows. This means that, given the above example, <CODE>index of
node window 1</CODE> is not equal to 1. Most folks who I talk to
think that this is a reasonable compromise.</P>
 
<P>The node tree within a node window is read-only and is not
actually stored in the document file. I may change this eventually,
but only if it's necessary to illustrate some AppleScript concept.
</P>
 
<P>I have no yet run a leak check on MOSL. My coding style acts to
prevent leaks, but I won't claim that MOSL is leak free until I've
actually checked.</P>
 
<P>Nested whose clauses (for example, <CODE>((nodes whose name
contains "2") of nodes whose name contains "1") of node window
1</CODE>) yield weird errors. I haven't had time to investigate this
yet.</P>
 
<P>MOSL does not yet implement the extra error parameters in an error
reply event (such as the "partial results" parameter).</P>
 
<H2>How it Works</H2>
 
<H3>Scripting Strategies</H3>
 
<P>There are two basic characteristics of any scripting
implementation:</P>
 
<UL>
   <LI>class-first versus event-first dispatching
   
   <LI>deep versus shallow resolution
</UL>
 
<P>MOSL implements <STRONG>object-first dispatching</STRONG>. For
each event in the event table, MOSL registers the same Apple event
handler (<CODE>MOSLAppleEventHandler</CODE> in file "MoreOSL.c").
This Apple event handler gets the direct object of the event and
resolves the object. This results in either a single token or a list
of tokens. For a single token, <CODE>MOSLAppleEventHandler</CODE>
calls <CODE>DispatchEvent</CODE> to look up the token's class in the
class table and call the corresponding class event handler. For a
list of tokens, <CODE>MOSLAppleEventHandler</CODE> iterates over the
list, extracting each token, calling <CODE>DispatchEvent</CODE> for
each, and assembling the results into a list.</P>
 
<P>This approach stands in contrast to <STRONG>event-first
dispatching</STRONG>, where each Apple event is processed by a
separate handler. In general, event-first dispatching requires more
code, whereas object-first dispatching is more table driver.
Object-first dispatching also maps well to the native object format
for applications that are programmed in an object-oriented style.
</P>
 
<P>MOSL implements <STRONG>deep object resolution</STRONG>. There are
two ways to interpret a script like <CODE>file 1 of every
item</CODE>. In a deep resolution implementation, this produces a
list that contains the first file of item 1, followed by the first
file of item 2, and so on. If item X is a file, and thus doesn't
include child files, the corresponding list item is the special value
<CODE>missing value</CODE>. In a <STRONG>shallow object
resolution</STRONG> implementation, this same object specifier yields
a single object which is the first file in the list of items.</P>
 
<P>Both approaches are internally consistent and various scriptable
applications use each approach. For example, AppleScript and the
Finder both implement shallow resolution, whereas the AppleScript
engineering team recommends deep resolution. MOSL uses deep
resolution not because it was easier (in fact, at various stages of
the development process, I managed to implement both forms!) but
because I consider it more powerful. If you look at the above
example, you'll notice that the shallow resolution implementation
yields the same results as <CODE>file 1</CODE>, whereas the deep
resolution implementation yields different, and possibly more useful,
results.</P>
 
<H3>Resolution Implementation</H3>
 
<P><A NAME="RecursiveResolve"></A>MOSL's object resolution is done by
the routine <CODE>RecursiveResolve</CODE> (in "MoreOSL.c"). This
routine's core algorithm is shown below.</P>
 
<P><TABLE BORDER=0>
   <TR>
      <TD bgcolor="#EEEEEE">
         <PRE>on RecursiveResolve obj
    if obj is a list then
        set result to empty list
        for each element in obj
            set resolvedObj to (RecursiveResolve item)
            if resolvedObj is a list then
                append each element of resolvedObj to result
            else
                append resolvedObj to result
            end-if
        end-for
        return result
    else
        return AEResolve obj
    end-if
end RecursiveResolve</PRE>
      </TD></TR>
</TABLE></P>
 
<P>This algorithm has a number of consequences:</P>
 
<UL>
   <LI>If the incoming object specifier is a list, all elements of
   the list are resolved independently.
   
   <LI>The result of <CODE>RecursiveResolve</CODE> is always either a
   single object or a flat list of objects. This ensures that
   <CODE>every node of every node window</CODE> produces a flat list,
   rather than a nested list, which is in line with scripter
   expectations.
</UL>
 
<P>Whenever <CODE>AEResolve</CODE> is called, the Object Support
Libraries invokes MOSL's various callbacks.</P>
 
<UL>
   <LI><CODE>MOSLCompareProc</CODE> -- This is a standard callback
   that OSL calls to compare descriptors, both during whose clause
   resolution and in response to the <CODE>kAEEquals</CODE> Apple
   event. It is described in detail in
   <A HREF="#DescriptorComparison">Descriptor Comparison</A>.
   
   <LI><CODE>MOSLCountProc</CODE> -- This is a standard callback that
   OSL calls to count objects within a container. MOSL implements
   this callback in terms of the "counter" object primitive, which it
   finds in the <A HREF="#ClassTable">class table</A>. The actual
   implementation is quite straightforward.
   
   <LI><CODE>ClassOSLAccessorProc</CODE> -- MOSL registers this
   callback once for every class in the class table, with the refCon
   set to the class' index in the class table. The callback is
   responsible for accessing objects and properties within the
   classes in the class table. See <A HREF="#ClassOSLAccessors">Class
   OSL Accessors</A> for more details.
   
   <LI><CODE>PseudoClassOSLAccessorProc</CODE> -- MOSL registers this
   callback once for each pseudo-class, with a refCon that uniquely
   identifies the pseudo-class. A <STRONG>pseudo-class</STRONG> is a
   class that OSL needs to access objects within, but which isn't
   actually a class in the class table (the pseudo-class is not one
   of the application's classes). Specifically, the pseudo-classes
   accessors currently registered by MOSL are
   <CODE>typeWildCard</CODE> from <CODE>typeAEList</CODE>,
   <CODE>typeWildCard</CODE> from <CODE>typeProperty</CODE>, and
   <CODE>cFile</CODE>/<CODE>typeAlias</CODE> from
   <CODE>typeNull</CODE>. See
   <A HREF="#PseudoClassOSLAccessors">Pseudo-Class OSL Accessors</A>
   for details.
</UL>
 
<P><TABLE BORDER=0 WIDTH=498>
   <TR>
      <TD bgcolor="#EEEEEE">
         <P><B>Note:<BR>
         
         </B>Registering the <CODE>ClassOSLAccessorProc</CODE> with a
         distinct refCon for each class allows OSL to do the mapping
         from class ID to class table index using its fast built-in
         hash tables. Doing this lookup inside
         <CODE>ClassOSLAccessorProc</CODE> would either be slower or
         require me to reimplement my own fast lookup mechanism.
      </TD></TR>
</TABLE></P>
 
<H3><A NAME="ClassOSLAccessorCallback"></A>Class OSL Accessors</H3>
 
<P>MOSL registers the <CODE>ClassOSLAccessorProc</CODE> object
accessor once for each class in the class table, with the refCon set
to the class's index in the class table.
<CODE>ClassOSLAccessorProc</CODE> is a simple dispatcher routine. It
establishes the class of interest (by casting its refCon to a
<CODE>MOSLClassTableIndex</CODE>) and then examines the incoming
<CODE>form</CODE> parameter and calls one of the following routines.
</P>
 
<P><TABLE BORDER=1 WIDTH="100%">
   <TR>
      <TD WIDTH=150>
         <P><CODE>formUniqueID</CODE>
      </TD><TD WIDTH=200>
         <P><CODE>ClassAccessorByUniqueID</CODE>
      </TD></TR>
   <TR>
      <TD WIDTH=150>
         <P><CODE>formName</CODE>
      </TD><TD WIDTH=200>
         <P><CODE>ClassAccessorByName</CODE>
      </TD></TR>
   <TR>
      <TD WIDTH=150>
         <P><CODE>formAbsolutePosition</CODE>
      </TD><TD WIDTH=200>
         <P><CODE>ClassAccessorByAbsolutePos</CODE>
      </TD></TR>
   <TR>
      <TD WIDTH=150>
         <P><CODE>formPropertyID</CODE>
      </TD><TD WIDTH=200>
         <P><CODE>ClassAccessorByProperty</CODE>
      </TD></TR>
   <TR>
      <TD WIDTH=150>
         <P><CODE>formRange</CODE>
      </TD><TD WIDTH=200>
         <P><CODE>ClassAccessorByRange</CODE>
      </TD></TR>
</TABLE></P>
 
<P>Of these, <CODE>ClassAccessorByUniqueID</CODE> and
<CODE>ClassAccessorByName</CODE> are simple wrappers around the
class's "accessByUniqueID" and "accessByName" object primitives.
<CODE>ClassAccessorByAbsolutePos</CODE> is somewhat more complex. It
calls the class's "counter" object primitive to determine the number
of elements within the object, then processes the supplied index (for
example, a positive index refers to elements counting from the
beginning, whereas a negative index refers to elements counting from
the end) and calls the "accessByIndex" object primitive with a
positive index number.</P>
 
<P>The most complex case for <CODE>ClassAccessorByAbsolutePos</CODE>
is where the selection data is of <CODE>typeAbsoluteOrdinal</CODE>
and value <CODE>kAEAll</CODE>. In this case,
<CODE>ClassAccessorByAbsolutePos</CODE> is responsible for returning
a token that includes all elements of the object. It does this by
creating an <CODE>AEDescList</CODE>, repeatedly calling the
"accessByIndex" object primitive to get the tokens for each element
of the object, placing those tokens in the list and returning the
list as the token.</P>
 
<P><CODE>ClassAccessorByProperty</CODE> is surprisingly simple. The
incoming parameter is a token that represents an object. [Attempts to
get properties from properties are handled by the
<A HREF="#PseudoClassOSLAccessors">pseudo-class accessor</A> for
<CODE>typeProperty</CODE>.] The result must be a token that
represents a property. All the routine needs to do is change the
<CODE>tokType</CODE> field of the
<CODE><A HREF="#TokenFormat">MOSLToken</A></CODE> structure to
<CODE>typeProperty</CODE> and the <CODE>tokPropName</CODE> field to
be the property name (four character code). It also checks that the
class actually includes the property name in its property table. This
ensures that object resolution for bogus property names comes to a
quick halt with a meaningful error code.</P>
 
<P><CODE>ClassAccessorByRange</CODE> is surprisingly complex. The
basic algorithm is shown below.</P>
 
<P><TABLE BORDER=0>
   <TR>
      <TD bgcolor="#EEEEEE">
         <PRE>on ClassAccessorByRange containerObject, selectionData, desiredType
    get bounds1 from selectionData using keyAERangeStart
    get bounds2 from selectionData using keyAERangeStop
    AEResolve bounds1
    AEResolve bounds2
    coerce bounds1 to its base type
    coerce bounds2 to its base type
    set state to kLookingForFirst
    for each element in containerObject
         coerce theElement to base type
         if state is kLookingForFirst then
             if theElement is bounds1 then
                 set state to kLookingForSecond
             else if theElement is bounds2 then
                 set state to kLookingForSecond
                 set bounds2 to bounds1
             end-if
         end-if
         if state is kLookingForFirst then
             if theElement is compatible with desiredType
                 add theElement to result
             end-if
             if theElement is bounds2 then
                 set state to kDone
             end-if
         end-if
         if state = kDone then
             break
         end-if
    end-for
end ClassAccessorByRange</PRE>
      </TD></TR>
</TABLE></P>
 
<P>In words, this algorithm is:</P>
 
<BLOCKQUOTE><P>Resolve the two boundary objects and coerce them to
their base class. Iterate over each element of the container object.
When you find the first boundary object, start adding compatible
elements to the result. When you find the secondary boundary object,
leave.</P></BLOCKQUOTE>
 
<P>This whole process is implemented in terms of the class's
"counter", "accessByIndex", and "coerceToken" object primitives.</P>
 
<H3><A NAME="PseudoClassOSLAccessors"></A>Pseudo-Class OSL Accessors
</H3>
 
<P>The MOSL routine <CODE>PseudoClassOSLAccessorProc</CODE> is
registered as an OSL object accessor three times, which it
distinguishes between by consulting its refCon.</P>
 
<UL>
   <LI><CODE>cFile</CODE>/<CODE>typeAlias</CODE> from
   <CODE>typeNull</CODE> (implemented by
   <CODE>PseudoCFileAccessor</CODE>)
   
   <LI><CODE>typeWildCard</CODE> from <CODE>typeProperty</CODE>
   (implemented by <CODE>PseudoCPropertyAccessor</CODE>)
   
   <LI><CODE>typeWildCard</CODE> from <CODE>typeList</CODE>
   (implemented by <CODE>PseudoCListAccessor</CODE>)
</UL>
 
<P>The first case is covered in the <A HREF="#cFileIssues">cFile
Issues</A> section. This section describes the other two cases.</P>
 
<H4>Property Pseudo-Class Accessor</H4>
 
<P>When OSL asks for a property from an object, the
<CODE>ClassOSLAccessorProc</CODE> returns a token of
<CODE>typeProperty</CODE>. When the script wants to get the value of
a property, this token becomes the direct object of the "get data"
event. However, if the value of the property is itself an object
specifier (for example, in TestMoreOSL, every document has a
<CODE>node display</CODE> property that is a reference to the node
window for that document), it is possible for an object specifier to
ask for properties or elements of the property.</P>
 
<P>The object specifier <CODE>node 1 of node display of document
1</CODE> (in the TestMoreOSL application) is an example of this
situation. As OSL tries to resolve this object specifier, the first
thing it does is request document 1 of the application. The
<CODE>ClassOSLAccessorProc</CODE> handles this, returning a document
token (<CODE>tokType</CODE> is <CODE>cDocument</CODE>). OSL then
requests the "node display" property of this token. Again, the
<CODE>ClassOSLAccessorProc</CODE> handles this, returning a property
token (<CODE>tokType</CODE> is <CODE>typeProperty</CODE>,
<CODE>tokObjType</CODE> is <CODE>cDocument</CODE>). Finally, OSL
attempts to resolve node 1 of this property token. The token type is
<CODE>typeProperty</CODE>, so it is at this point that the
<CODE>PseudoCPropertyAccessor</CODE> is called.</P>
 
<P><CODE>PseudoCPropertyAccessor</CODE> handles requests for elements
and properties of properties. The first thing it does is use the
token's <CODE>tokObjType</CODE> field to work out what class of
object the token is a property for. It then calls the class's
"getter" object primitive on the token. The specification for this
primitive is that, if the token is a property that is a reference to
another object, it should return a token for that object.
<CODE>PseudoCPropertyAccessor</CODE> now has a token for the object
referenced by the property. It then calls
<CODE>AECallObjectAccessor</CODE> on that token to recommence
resolution based on the object referenced by the property.</P>
 
<P>The end result is that MOSL resolves objects and properties in
object reference properties with a minimum of fuss for the client
application.</P>
 
<H4>List Pseudo-Class Accessor</H4>
 
<P>MOSL regularly uses <CODE>typeAEList</CODE> as a token type. For
example, if you ask for every document, MOSL will return the list as
of documents as a <CODE>typeAEList</CODE>, where each element is a
document token (<CODE>tokType</CODE> is <CODE>cDocument</CODE>). This
approach is fairly standard (it is recommended in <CITE>Inside
Macintosh: Interapplication Communication</CITE>) but it does have
some consequences on for MOSL's object accessors. Specifically,
because MOSL uses <CODE>typeAEList</CODE> as a token type, MOSL must
provide an object accessor for <CODE>typeAEList</CODE>. The
<CODE>PseudoClassOSLAccessorProc</CODE> routine (which is simply a
wrapper for <CODE>PseudoCListAccessor</CODE>) is that accessor.</P>
 
<P>As an example of why this is necessary, consider the object
specifier <CODE>name of every document</CODE>. The
<CODE>ClassOSLAccessorProc</CODE> resolves the first part of the
object specifier (<CODE>every document</CODE>) to a list
(<CODE>typeAEList</CODE>) of document tokens
(<CODE>cDocument</CODE>). OSL then attempts to access the "name"
property of this list. It is not smart enough to do this itself, so
MOSL must register an object accessor for <CODE>typeAEList</CODE>.
</P>
 
<P>The basic implementation of the list pseudo-class object accessor
is shown below.</P>
 
<P><TABLE BORDER=0>
   <TR>
      <TD bgcolor="#EEEEEE">
         <PRE>on PseudoCListAccessor containerObject, selectionData, desiredType
    set result to empty list
    for each element in containerObject
        set resultItem to (AECallObjectAccessor item,selectionData,desiredType)
        if resultItem is a list then
            append each element of resultItem to result
        else
            append resultItem to result
        end-if
    end-for
    return result
end PseudoCListAccessor</PRE>
      </TD></TR>
</TABLE></P>
 
<P>The idea is that, if the container object is a list,
<CODE>PseudoCListAccessor</CODE> breaks the list down into elements,
calls the appropriate object accessor for each element, and puts the
results into a list which it then returns. It uses the same list
merging technique as
<CODE><A HREF="#RecursiveResolve">RecursiveResolve</A></CODE> to
ensure that the resulting list is always flat.</P>
 
<P>This implementation works just fine
<CODE>formAbsolutePosition</CODE> and <CODE>formPropertyID</CODE>. In
fact, this is the core of the deep resolution implementation. For
example, an object specifier of <CODE>node 1 of every node
window</CODE> would cause <CODE>PseudoCListAccessor</CODE> to be
called, and it would redispatch the request for node 1 to each
element of the list and reassemble the results into a list. However,
this basic implementation does not work well for
<CODE>formRange</CODE>. The exact problem and solution are too
convoluted to explain here, but are covered in the comments entitled
"formRange for typeAEList" in the file "MoreOSL.c".</P>
 
<P>There are two other saliant points in the implementation of
<CODE>PseudoCListAccessor</CODE>.</P>
 
<UL>
   <LI>If the call to <CODE>AECallObjectAccessor</CODE> fails, the
   special value <CODE>missing value</CODE> is placed in the result.
   For example, the result of the following script is a list whose
   second element is missing value, because node 2 of node window 1
   does not have any children. <TABLE BORDER=0>
      <TR>
         <TD bgcolor="#EEEEEE">
            <PRE>tell application "TestMoreOSL"
    node 1 of every node of node window 1
end tell</PRE>
         </TD></TR>
   </TABLE>
   
   <LI>If the resolution request passed to
   <CODE>PseudoClassOSLAccessorProc</CODE> is for
   <CODE>formRange</CODE> and the <CODE>desiredClass</CODE> is
   <CODE>cObject</CODE>, <CODE>PseudoClassOSLAccessorProc</CODE> does
   not call <CODE>PseudoCListAccessor</CODE>; instead it just returns
   the N'th descriptor in the list. This weird special case is
   required to make AppleScript's <CODE>repeat with</CODE> construct
   work properly [2445795].
</UL>
 
<H3><A NAME="cFileIssues"></A>cFile Issues</H3>
 
<P>I encountered a number of weird problems related to
<CODE>cFile</CODE> while working on MOSL. Most of them have
workarounds, but the problems are themselves worth noting.</P>
 
<UL>
   <LI>Once you add the "file" property to your
   <CODE>cDocument</CODE> class, you must also add a
   <CODE>cFile</CODE> class to your suite. The <CODE>cFile</CODE>
   class can be empty, but it has to be there to make AppleScript
   compile properly. See the comments in "TestMoreOSLTerminology.r"
   for more details. [2444517]
   
   <LI>Even after you add the <CODE>cFile</CODE> class, the construct
   <CODE>every document whose file is alias "Macintosh
   HD:Test"</CODE> still doesn't work properly. See the comments in
   "TestMoreOSLTerminology.r" for more details. [2444528]
   
   <LI>The only way for a scripter to save a document to a file that
   doesn't yet exist is to use the construct <CODE>save document 1 in
   file "Macintosh HD:New File Name"</CODE>. This actually generates
   a <CODE>cFile</CODE> object specifier that confused MOSL because
   <CODE>cFile</CODE> is not in the class table. I resolved this
   problem by adding <CODE>cFile</CODE> to the set of pseudo-classes
   handled by <CODE>PseudoClassOSLAccessorProc</CODE> (which passes
   the actual work to <CODE>PseudoCFileAccessor</CODE>). When
   <CODE>PseudoCFileAccessor</CODE> is called to return a
   <CODE>cFile</CODE> object object within a container, it simply
   reassembles the form, selection data, and container information
   into an object specificer and then calls <CODE>AECoerceDesc</CODE>
   to coerce that object specifier to an <CODE>FSSpec</CODE>; the
   coercion is handled by a built-in coercion handler.
</UL>
 
<H3><A NAME="DescriptorComparison"></A>Descriptor Comparison</H3>
 
<P>MOSL registers the routine <CODE>MOSLCompareProc</CODE> as its OSL
comparison callback. When I started MOSL, I was under the impression
that this routine would only be called when OSL needed to compare two
of my tokens; because OSL has no idea of my token format, it has to
call me to do the job. This is not true. OSL calls the comparison
callback whenever it needs to compare anything, be they object
tokens, properties, or just data. OSL should, in my opinion, be
smarter about this [2444551], but for the moment the MOSL comparison
callback has to handle all of these comparisons explicitly.</P>
 
<P><CODE>MOSLCompareProc</CODE> has two basic comparison methods.
</P>
 
<UL>
   <LI>compare data descriptors (implemented by
   <CODE>CompareDataDescriptors</CODE>)
   
   <LI>compare tokens (implemented as part of
   <CODE>MOSLCompareProc</CODE>)
</UL>
 
<P>The implementation of each method is fairly straightforward
(comparing data descriptors is longwinded, but simple, except for
<A HREF="#MoreOSLStringCompare">strings</A>). The tricky part of
<CODE>MOSLCompareProc</CODE> is deciding which method to use. The
basic algorithm is:</P>
 
<OL>
   <LI>If both sides are objects, compare the tokens
   
   <LI>If either side is a property, get the value of the property,
   coerce the other side to the same type, and compare the data.
   
   <LI>If either side is data, get the data, coerce the other side to
   the same type, and compare as data.
</OL>
 
<P>The comments inside <CODE>MOSLCompareProc</CODE> ("MoreOSL.c")
explain this in greater detail.</P>
 
<H3>Debugging Infrastructure</H3>
 
<P>Debugging MOSL was hard. This was partly because of my
inexperience with AppleScript and OSL, but also because the
combination of OSL and MOSL is a big object-oriented framework, and
debugging big object-orient frameworks is inherently hard. For
example, when you step over <CODE>AEResolve</CODE>, you have no real
idea which of your callbacks will be called and when.</P>
 
<P>My primary solution was logging. The MoreBBLog module allows easy
logging to BBEdit, and MOSL makes extensive use of that facility.
MOSL also registers coercion handles from all common types to
<CODE>typeText</CODE>. This allows MoreBBLog to display more
meaningful information in the log. Most of those coercion handlers
are in "MoreBBLog.c" itself, but MoreOSL also registers a coercion
handler (<CODE>DebugTokenCoerceProc</CODE>) to coerce tokens to text.
</P>
 
<P>MOSL's logging is controlled by four bits in the
<CODE>gDebugFlags</CODE> global variable.</P>
 
<UL>
   <LI><CODE>kMOSLLogOSLMask</CODE> -- When set, this bit causes MOSL
   to log all of its interactions with OSL. Specifically, all of
   MOSL's OSL callback routines record that they have been called,
   their parameters, and their results.
   
   <LI><CODE>kMOSLLogCallbacksMask</CODE> -- When set, this bit
   causes MOSL to log whenever it calls a client callback (class
   event handler or object primitive). This output is primarily aimed
   at folks who use MOSL and want to figure out what their callbacks
   are doing wrong.
   
   <LI><CODE>kMOSLLogGeneralMask</CODE> -- When set, this bit causes
   MOSL's general class event handlers to log their actions.
   
   <LI><CODE>kMOSLLogDispatchMask</CODE> -- When set, this bit causes
   MOSL's core Apple event dispatcher to log its actions.
</UL>
 
<P>By default the debug version of TestMoreOSL has
<CODE>kMOSLLogOSLMask</CODE> and <CODE>kMOSLLogDispatchMask</CODE>
set, but it also exposes these flags through its <CODE>debug</CODE>
property so that a script can control the level of logging. The
standard test script uses this technique to enable logging when you
run an individual test but disable it when you run all of the tests,
on the assumption that individual tests are run while debugging and
all tests are run during regression testing.</P>
 
<P>The standard test script ("Test Script") proved to be an
invaluable debugging tool in itself. The best feature of the test
script is that it allowed me to make significant changes to the
implementation (such as a conversion from Pascal to C -- a long
story) with some assurance that I hadn't broken some obsure part of
the implementation.</P>
 
<P>Another very useful debugging feature is the
<CODE>DebugOSLAccessorProc</CODE>. In the debug version of MOSL, I
register this object accessor as being capable of accessing anything
from anything. If OSL attempts to access some object from a class I
wasn't expecting, <CODE>DebugOSLAccessorProc</CODE> runs, logs the
parameters, and returns an error. This way I can quickly spot failed
object accesses in the log.</P>
 
<P>MOSL use asserts everywhere. This has helped me find numerous
bugs.</P>
 
<H3><A NAME="MoreOSLStringCompare"></A>MoreOSL String Comparison
</H3>
 
<P>Implementing support for AppleScript's string comparison operators
is easy to do badly, but very hard to do right. The challenges
include:</P>
 
<UL>
   <LI>The system provides no international friendly way of
   implementing the "contains" operator that works 100% correctly.
   The CFString mechanism works pretty well, but it does have its
   problems (see below).
   
   <LI>Any mechanism provided by the system still wouldn't be the
   same as AppleScript's built-in string operators because
   AppleScript carries around its own string canonicalisation tables.
   
   <LI>There is no documented way of getting at the script's current
   "considering" and "ignoring" state. This is a known limitation,
   documented in the AppleScript Language Guide, but it's still lame.
 
</UL>
 
<P>Some of these difficulties are evident even in the Roman script
system. For example, assuming document 1 is called "&THORN;nd" (the
first character is the fi ligature (option-shift-5)), the following
script snippets might produce different results because the
application implements string comparison in terms of
<CODE>IdenticalText</CODE> or <CODE>CFStringCompare</CODE>, which
break down the ligature, but AppleScript's built-in string tables
doesn't (even if you turn on expansion).</P>
 
<P><TABLE BORDER=0>
   <TR>
      <TD bgcolor="#EEEEEE">
         <PRE>tell application "X"
    name of document 1 = "find"
end tell
&nbsp;
tell application "X"
    get name of document 1
    result = "find"
end tell</PRE>
      </TD></TR>
</TABLE></P>
 
<P>Moreover, while testing this against CFString I discovered a bug
[2442526] that causes CFString to break the identity:</P>
 
<BLOCKQUOTE><P>(a equals b) implies ((a contains b) and (b contains
a))</P></BLOCKQUOTE>
 
<P>While this probably won't be a huge problem in practice, it is
annoying.</P>
 
<P>While casting around for a solution (especially for the "contains"
operator) I looked at how some other applications did this. That was
a depressing exercise. The Finder's implementation of "contains", for
example, does not even attempt to be two-byte friendly, nor does it
address the possibility that <CODE>IdenticalText</CODE> may return
true for text of different length (for example, "&THORN;nd" vs
"find").</P>
 
<P>Despite these obstacles, an application must implement string
comparison in order to support the simplest AppleScript constructs
(such as <CODE>formName</CODE>). So I had to come up with a solution.
I chose two different paths depending on the target environment.</P>
 
<UL>
   <LI>Carbon -- For Carbon applications, CFString was the obvious
   solution, despite the niggling bug described above.
   
   <LI>Non-Carbon -- For non-Carbon applications, there is no
   obvious, clean solution. Eventually I resorted to loading an
   AppleScript (using OSA) and forcing it to do the comparison (using
   <CODE>OSADoEvent</CODE>). Sleasy, but functionaly.
</UL>
 
<P>After this much pain, I thought it was important to file an
enhancement request [2444555] asking for a better solution.</P>
 
<H2>Credits and Version History</H2>
 
<P>If you find any problems with this sample, mail
&lt;DTS@apple.com&gt; as the first line of your mail and I'll try to
fix them up.</P>
 
<P>1.0b1 (Mar 2000) was the first version released for internal
review.</P>
 
<P>1.0b2 (Apr 2000) was the first release distributed to the general
public. Contains a small number of minor fixes. The biggest
functional change is that Apple events defined to have no reply (such
as <CODE>'odoc'</CODE>) no longer show "current application" as their
reply in Script Editor's log.</P>
 
<P>Share and Enjoy.</P>
 
<P>Apple Developer Technical Support<BR>
 
Networking, Communications, Hardware (slumming in scriptland!)</P>
 
<P>25 Apr 2000</P>
 
<P>&nbsp;</P>
</BODY>
</HTML>