[ Team LiB ] |
19.2 Resolution DifficultiesHaving sketched a basic picture of how terminology is resolved, we can proceed to cover various complications that occasionally arise. 19.2.1 Conflict ResolutionTerms are sought in the dictionaries of the innermost application, of AppleScript itself, and of all scripting additions, as well as in the script. Given such a large namespace involving multiple independent entities, it is possible for conflicts to arise. Such a conflict is called a terminology clash. Either the programmer generates the clash by an unwise choice of variable names, or different dictionaries generate it by defining the same term in different ways. 19.2.1.1 Clash caused by the programmerWhen the programmer causes a terminology clash, various things can happen. Sometimes the code won't compile; sometimes it won't run; sometimes it runs but gets an unexpected result; sometimes the clash is resolved sensibly and there's no problem. When the compiler stops you from using a term, it is generally because the term is defined elsewhere as a certain "part of speech" and you're trying to use it in a different way. For example, this won't compile: local container tell application "Finder" to set container to 7 -- compile-time error Within the context of a tell directed at the Finder, container is resolved as the Finder's term container, which is a class name; that's not something that can be assigned to, so the compiler balks. You may be wondering what happened to rule 1 under Section 19.1.2.2. Rule 1 didn't apply in that example, because container was resolved as a class. Rule 1 specifically applies only if a term is resolved as a property. So, for example: local bounds
tell application "Finder"
set bounds to (get bounds of item 1)
end tell
bounds -- {-33, -33, 31, 31}
The term bounds in the third line is found to be the name of a property defined by the Finder. But the first bounds in the third line is not followed by of and is matched by the name of a variable that's in scope at this point, so it is taken to be that variable, by rule 1. Thus there's no terminology clash. The explicit declaration of bounds in the first line is crucial here; take it away, and the code won't run. With no existing variable bounds in scope during the third line, both occurrences of bounds in the third line are taken as a property in the Finder; so now the phrase set bounds in the third line is incorporated into an Apple event sent to the Finder, which replies with an error message because it doesn't know what it's supposed to set the bounds of. This won't compile: set sel to {length:2, offset:4} -- compile-time error The error message from the compiler is so mysterious that it might take you a while to realize that the problem is a terminology clash. The trouble is that offset is defined as a command in a scripting addition. Because it's a command, you're trying to use a verb where a noun is expected. Observe that length doesn't cause a clash here, even though it's defined in AppleScript's own dictionary; that's because it's defined as a property, and you're using it as a property. (Remember, names of items of a record are properties; see Section 13.13.1.) This won't compile: local desktop -- compile-time error Again, the problem is a scripting addition; desktop is part of an enumeration used in the path to command. (See Section 19.3.1, later in this chapter.) Now let's look at a terminology clash where the compiler doesn't complain. This is potentially worse for the programmer than when the compiler does complain, because the code runs but it doesn't behave as expected: local container, x
set container to "howdy"
tell application "Finder" to set x to container
x -- container, not "howdy"
In that example, container in the third line is the name of a class in the Finder; a class is a legitimate value for a variable, so the variable x ends up with that class as its value. In a case like that, the compiler can give you one subtle hint that something might be wrong—in the way it formats the "pretty-printed" decompiled code. You can set AppleScript's pretty-printing preferences to distinguish dictionary terms from script-based terms such as variable and handler names, and this will let you see that container in the third line is being interpreted as a dictionary term. If you are aware of a conflict caused by your choice of variable names, and you insist upon using those variable names, you can usually resolve the conflict, as explained earlier in Section 10.5. You can use pipes to suppress AppleScript's interpretation of something as a dictionary term. This works: local container
tell application "Finder" to set |container| to 10
container -- 10
So does this: set sel to {length:9, |offset|:4} In the case of a handler call, you will have to retarget the message as well: on container( )
display dialog "Howdy"
end container
tell application "Finder" to my |container|( ) -- Howdy
In that example, if you omit my, the message is sent to the Finder instead of the script. If you omit the pipes as well, the compiler reinterprets the last line: on container( ) display dialog "Howdy" end container tell application "Finder" to get container {} -- error The parentheses are (surprisingly) transformed at compile time into an empty list to be used as an index in an element specifier; an empty list is not an integer, so this fails at runtime. Sometimes pipes aren't quite enough. This doesn't work: local folder
set folder to 5
tell application "Finder" to set |folder| to 10
folder -- 5, not 10
The trouble is that folder is also defined in a scripting addition, as the name of a property. It's a property of the file information pseudo-class returned by the info for command. That in itself is a terminology clash, because the scripting addition defines folder as 'asdr', but the Finder defines it as 'cfol'. To use folder as a variable name successfully, if we are going to put pipes around it anywhere, we must put pipes around it everywhere, so that it isn't identified with this scripting addition property: local |folder|
set |folder| to 5
tell application "Finder" to set |folder| to 10
|folder| -- 10
19.2.1.2 Clash between dictionariesThere's nothing wrong with an application's dictionary redefining an AppleScript-defined term, so long as the same English-like term and the same underlying four-letter code are both used (and, I should probably add, so long as they use the term as the same part of speech). For example, AppleScript defines the term name ('pnam') as a property of a script object; the Finder defines it as a property of an item. Since they both use the same English-like term and the same four-letter code, and they both use it as a property, this is not a conflict. If the Finder's name were defined as a different four-letter code, that would count as a conflict. Even if an application's dictionary generates a terminology clash, at least the problem arises only if you're targeting that application; but when a scripting addition makes the same kind of mistake, it conflicts everywhere, and there's nothing you can do about it. In general, a clash between dictionaries is completely out of your control; even if you know about it, you can't resolve it. The only dictionary you can refer to explicitly is the innermost application dictionary. You have no way to help AppleScript when there's a clash; you can't specify that a term should be interpreted in accordance with AppleScript's own dictionary or a particular scripting addition's dictionary. We have already seen some examples of trouble caused by poor choice of terminology in an application's dictionary. The use of the term end transaction by FileMaker Pro as the English-like equivalent of the 'misc/endt' Apple event (Section 12.5.2) conflicts with the AppleScript's own use of end transaction as the closing phrase of a transaction block. BBEdit's use of the term contents as a property of a text-object (Section 11.4) conflicts with AppleScript's own contents of operator. With scripting additions, there have historically been many instances of clashes. The real trouble lies in the nature of the scripting addition mechanism itself, which invites such clashes; this is one of the reasons why Apple discourages developers from writing scripting additions (see Chapter 20). Thoughtful scripting addition developers frequently give their terms unique, improbable names to reduce the likelihood of clashes. For example, the GTQ Script Library scripting addition sorts lists with the sort command, which is probably not a very wise choice of English-like term; the ACME Script Widgets scripting addition sorts lists with the ACME sort command, a term unlikely to recur elsewhere (see http://www.osaxen.com/gtq_scripting_library.html and http://www.acmetech.com). Even this doesn't prevent a four-letter code in the scripting addition from matching a four-letter code in some application; that sort of problem is hard for even a wary developer to guard against. With luck, some user notices the clash and notifies the scripting addition's developer, who responds by creating a new version of the scripting addition with an altered dictionary. But there are some scripting additions where terminology clash is simply part of the price of using them. 19.2.2 Invalid Apple EventsAside from confirming that your terminology is defined, does the compiler use the dictionary to check that you're employing that terminology in a valid manner? It does to some extent, but not as much as one might wish; it is all too easy to form a nonsensical expression and get it past the compiler (which will then form a nonsensical Apple event, which will be sent to the application at runtime). In general, you should not expect that compilation can be used as "sanity check." It's up to you to know the language and to use it sensibly. This is one reason why I describe the contents of dictionaries in such detail later in this chapter (Section 19.3). The compiler does largely enforce the difference between verbs and nouns, though it has various ways of expressing this, and it might not be obvious what it's doing. For example, this will compile: tell application "Finder" to folder the item It probably looks to you as if AppleScript has treated folder as a verb; but in fact AppleScript is supplying get, as it typically does if the verb is missing. AppleScript is parsing your words like this: tell application "Finder" to get folder index item It thinks you're asking for a folder by index (e.g., folder 1), except that you've put the class name item as your index instead of an integer value. The fact that a class name is not a valid index value doesn't seem to faze the compiler one bit, but of course the resulting Apple event is nonsense and causes a runtime error when sent to the Finder. The compiler also checks that the terms following a verb match the labeled parameters of that verb as defined by the dictionary. For example, this won't compile: tell application "Finder" to duplicate x by y -- compile-time error because a command must be followed only by valid labeled parameters, and the duplicate command doesn't have a by parameter. The compiler also won't let you say get name 1 or get left 1. Evidently, it knows that name is a property and that left is an enumeration (a constant), and that these are not the sorts of terms that can be used in an element specifier. The compiler also displays some intelligence about singular and plural forms of a class name. The plural form of a class name is taken to be a synonym for the every element specifier; otherwise, if you use a plural where a singular is expected or vice versa, the compiler will usually change it for you, silently: tell application "Finder" folder -- folder (the class name) folders -- {...}, a list of references to every folder folders 1 -- compiles as folder 1 folder 1 thru 2 -- compiles as folders 1 thru 2 end tell Now for the bad news: the dictionary describes certain definite relationships—this property is a property of this class, this element is an element of this class, this name is a class, this name is a property—but AppleScript largely ignores this information. As far as the compiler is concerned, property and element names are not encapsulated with respect to their class, and property names and class names are not distinguished. The result is a namespace mess; indeed, this is one reason why terminology clashes can so easily occur. Here we use a class name where a property name is expected: tell application "Finder" to eject the item of file 1 That's rank nonsense. The Finder's dictionary makes it clear that item isn't a property of file, and that it isn't a property name but a class name. But AppleScript's compiler ignores such matters; it will simply translate the line into an Apple event and (at runtime) send it, at which point it's up to the Finder to return an error message. Here we use a property name where a class name is expected: tell application "Finder" to make new extension hidden But extension hidden isn't a class; it's a property. And it isn't a property of the application class. But the code compiles anyway. Again, demonstrating the complete lack of encapsulation: tell application "Finder" to get column 1 of desktop That's also total nonsense; column is an element of the list view options class, not of the desktop, and of course there's an error at runtime. 19.2.3 Raw Four-Letter CodesIt is sometimes possible to resolve a term yourself, in code, in advance of compilation. Instead of an English-like term, you type the corresponding four-letter code. You are thus essentially doing what the compiler would do: you've looked up the English-like term in a dictionary and substituted the corresponding four-letter code yourself. This is a useful device in situations where, because of a terminology clash, the English-like term would be ambiguous, but the four-letter code is not.
The notation is straightforward. Typically the term will be either a noun (a class) or a verb (an event), so you use the word class or event followed by a space, followed by the four or eight letters, respectively. The entire thing is wrapped in guillemets («»). On the U.S. keyboard layout, these are typed using Option-\ and Shift-Option-\ (backslash). Let's look at an example. This won't compile: tell application "BBEdit" tell window 1 offset of "i" in contents of word 1 -- compile-time error end tell end tell The reason is that we're trying to use offset as defined in a scripting addition, but BBEdit's own implementation of offset is getting in our way. There is no way to target the scripting addition, so in this context, where BBEdit is specified as the innermost application dictionary, there is no way to inform AppleScript that we mean the scripting addition command offset and not BBEdit's offset. One workaround is not to use the scripting addition command in the context of an innermost application dictionary that conflicts with it. All we have to do is use the scripting addition's offset outside the tell block: tell application "BBEdit"
tell window 1
set w to contents of word 1
end tell
end tell
offset of "i" in w -- 3 (word 1 is "this")
But we could use four-letter codes instead. BBEdit's offset is a noun, a property 'Ofse', while the scripting addition's offset is a verb 'syso/offs'. This means that with regard to the underlying Apple event representation the two are completely distinguishable. So, we type this: tell application "BBEdit"
tell window 1
«event sysooffs» of "i" in (get contents of word 1) -- 3
end tell
end tell
That's what we type, but it isn't what appears after compilation. The decompilation process involves translating terms from their raw four-letter codes back to their English-like equivalents. Thus, the compiled script ends up looking like this: tell application "BBEdit"
tell window 1
offset of "i" in (get contents of word 1) -- 3
end tell
end tell
Nevertheless, unlike the earlier attempt, this is the correct offset, the one defined by the scripting addition; the script compiles—and it runs, because when «event sysooffs» is sent to BBEdit, BBEdit can't deal with it and it is passed along to the scripting addition. Unfortunately, because decompilation has removed our raw four-letter code, if we edit this script and compile it again, we are right back where we started. We must be prepared to enter «event sysooffs» into the code manually before every compilation if we want to use this solution. Occasionally you'll see a four-letter code show up in a compiled script. Typically this is because the compiled script has lost track of an application; thus it can't find the application's dictionary, so it can't decompile the four-letter codes to the corresponding English-like terms. Therefore it just shows you the four-letter codes directly. For example, here's a script targeting Eudora: tell application "Eudora" get subject of message 1 of mailbox "Trash" end tell Now I'll quit the Script Editor and throw Eudora away. When I try to open the script again, the Script Editor naturally can't find Eudora, so it asks me where it is; it's gone, so to get the script to open at all, I just pick an application at random (the Finder). This satisfies AppleScript's desire for a dictionary, but of course the terms subject, message, and mailbox aren't defined in that dictionary, so the raw four-letter codes are displayed (and the Finder is substituted for Eudora as the target of the tell block): tell application "Finder" get «class euSu» of «class euMS» 1 of «class euMB» "Trash" end tell The same sort of thing can happen if you open an old compiled script whose application's dictionary has changed. It can happen with a dictionary, too; here's a line from BBEdit's dictionary: cover page «class lwec» [r/o] -- should a cover page be generated
for the job and where should it be placed
Evidently, either the dictionary itself is defective because someone forgot to define the class 'lwec', or else at the time this dictionary was written, something (AppleScript itself, or a scripting addition) defined the class 'lwec' and no longer does. A further use of raw four-letter codes in a script is described under Section 19.3.6, later in this chapter. 19.2.4 Multiple-Word CommandsMany terms, especially in scripting additions, consist of multiple words. An example frequently used in this book is display dialog. Such a term would seem to present extra challenges; but AppleScript seems to rise to them quite well. For example: local clipboard, tester set clipboard to "Mannie" -- sets the variable clipboard set the tester to "Moe" -- sets the variable tester (ignoring "the") set the clipboard to "Jack" -- sets the system scrap That example illustrates AppleScript's ability to recognize the scripting addition command set the clipboard to, which is used in the last line. This works even though set is an AppleScript command, clipboard could be the name of a variable, and the is usually ignored. In this particular code, clipboard is the name of a variable, and we are able to set it, as the second line shows. The third line illustrates that AppleScript usually ignores the. Though I don't know the details, a natural explanation of AppleScript's success here would be that it tries the longest possible combinations of words first. A multiple-word property name such as text item delimiters presents a special case in the context of an innermost application dictionary. To see why, contrast it with a single-word property such as space. Both are defined by AppleScript itself as global script properties (see Chapter 16), but they are treated differently. When you say this, no Apple event is sent to the Finder: tell application "Finder" to get space That's because, in accordance with rule 1 under Section 19.1.2.2, AppleScript has resolved space as a name that's in scope in the script. But with text item delimiters, AppleScript can't do that, because a multiple-word name isn't a legal variable name. Thus, when you say this, an Apple event is formed and sent: tell application "Finder" to get text item delimiters -- error The Apple event tells the Finder to get its 'txdl' property (because 'txdl' is the four-letter code for text item delimiters). But the Finder has no 'txdl' property, so it returns an error. To prevent this, you have to specify that the text item delimiters belongs to me, or AppleScript: tell application "Finder" to get my text item delimiters |
[ Team LiB ] |