##############################################(nowrap)#############
 Alpha AppleEvent Packages

                                   created: 4/9/98 {10:32:09 PM} 
                               last update: 4/14/99 {2:10:26 PM} 
							       version: 1.2
 Author: Jonathan Guyer
 E-mail: <jguyer@his.com>
    www: <http://www.his.com/~jguyer/>
 
 Copyright (c) 1998  Jonathan Guyer
 
###################################################################

This package is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License.
 
###################################################################

Alpha's AEBuild command makes it (comparatively) easy to 
interface with other applications, through the traumatic world of 
AppleEvents (and Capture AE makes writing AE descriptors far easier 
than it used to be, particulary for some inarticulate applications 
(I won't mention any names, but a major offender rhymes with ÒBlinderÓ)).  
Using AEGizmos, one can build complicated event structures for 
interaction with other programs. 

Alpha automatically processes AppleEvent replies from those 
programs with AEPrint, which renders them reasonably 
human-readable, but quite unwieldy to deal with, 
programmatically.  Programmers wishing to work with AE from 
within Alpha have been forced to write clumsy regexpen to attempt 
to extract the elements of interest from event replies, but 
because of the potential complexity of AE reply records, these 
regexpen almost invariably fail to account for all contingencies.

To make it easier to work with AppleEvent replies (and, by 
extension, to process events for which Alpha has been declared a 
handler), I have written a suite of packages: aeparse.tcl, 
aecoerce.tcl, and aebuild.tcl. The first two packages work in concert 
to implement a reasonable facsimile of Jens Alfke's 
Òformidable-looking BNF grammar specificationÓ, parsing AEPrint 
strings into Tcl-readable lists of lists. The third package 
essentially replaces the routines in appleEvents.tcl with a more 
consistently named set of routines. Stub routines remain in 
appleEvents.tcl to map to the new format.

Given an Òaevt\ansr{...}Ó, a ÒGURL\GURL{...}Ó, or some subset of an 
AEGizmo string, you can parse to a list of lists; keyword searches 
are then easy.

If you call:

	aeparse::event [AEBuild bladdity blah]

you get back a typed and tagged list of lists. If the reply 
from [AEBuild bladdity blah] is:

	aevt\ansr{BLAH:5, blah:ÒBlah?Ó}

then aeparse::event will return

	reco {{BLAH {long 5}} {blah {TEXT Blah?}}}

All the extra tags may seem cumbersome, but the default behavior of 
the routines is to strip them out. For instance: 

	aeparse::keywordValue blah [aeparse::event [AEBuild bladdity blah]]

will just return:

	Blah?

If you don't know in advance that the 'blah' keyword holds a string, 
then aeparse::keywordValue takes an optional typed flag, so you'll get back

	TEXT Blah?

Since, for many event replies, the direct object is what's wanted, 
there is a short-hand to replace

	aeparse::keywordValue ---- [aeparse::event [AEBuild bladdity blah]]

with 

	aebuild::result bladdity blah


In the event of AppleEvent errors, aeparse::event automatically checks 
for 'errn' and 'errs' tags and throws an appropriate error. The 
array 'aeparse::errors' holds error messages to be used when no 
'errs' tag accompanies 'errn' (the Finder does this); see the 
initialization code for aeparse for examples of the format.

In addition to parsing AE descriptor strings, calls to 
aeparse::event automatically perform any type coercions that have 
been declared with aecoerce::register. For example:

	aeparse::event {aevt\ansr{----:bool(Ç01È)}}
	
will not return

	reco {{---- {bool {hexd 01}}}}
	
but rather

	reco {{---- {bool 1}}}
	
so that

	aeparse::keywordValue "----" \
		[aeparse::event {aevt\ansr{----:bool(Ç01È)}}]
	
just returns Ò1Ó; no further processing is necessary.  

A more powerful example is seen in a rewrite of 
mailboxPathIndex in eudoraMenu.tcl:

	proc mailboxPathIndex {ind} {
		set res [objectProperty 'CSOm' euFS [mailboxByIndex $ind]]
		return [extractPath $res]
	}

'res' will hold something like

	aevt\ansr{'----':fss (ÇFFFD000001CC02496E00002F52FE002F52827FFF7FFF7FFF7F0001C70FC00000F90001D3F9547FFF7FFF01C95F5001D33C5E01D33BFC004120B401D33C4401D3CCCCCCCCCCCCÈ)}
	
extractPath looks for anything between Ç and È and attempts to 
coerce that from an 'fss ' to a path, returning

	documents:Eudora:HIS:In
	
This example happens to work, but some gross assumptions have 
been made about the format of the returned AE descriptor string 
and explicit coercions are needed after every AEBuild call. Using 
the routines of this package, we can instead write:

	proc mailboxPathIndex {ind} {
		set res [aeparse::event [objectProperty 'CSOm' euFS [mailboxByIndex $ind]]]
		return [aeparse::keywordValue "----" $res]
	}

In this case, 'res' holds

	reco {{---- {{fss } documents:Eudora:HIS:In}}}

Although the code is not any shorter, the coercion of 'fss ' has 
been performed automatically (and not arbitrarily).  Although 
this list of lists looks like a mess, it is easily processed by 
aeparse::keywordValue, so you need not worry about its 
internals.  We can simplify 'mailboxPathIndex' even further by 
writing:

	proc mailboxPathIndex {ind} {
		return [aebuild::result 'CSOm' core getd ---- \
					[propertyObject euFS [mailboxByIndex $ind]]]
	}
	
or, better yet,

	proc mailboxPathIndex {ind} {
		return [aebuild::objectProperty 'CSOm' euFS [mailboxByIndex $ind]]
	}

CAVEAT: Because the AE descriptor string is being completely 
parsed, aeparse::event can be rather slower than blind regexping. 
The trade-off is that the results you obtain are much less 
likely to be erroneous, especially for error conditions. I will be 
looking for ways to speed things up, though.
_________________________________________________________________

Cheater's Guide to AppleEvent Descriptors
_________________________________________________________________

Until I found out about Capture AE (thanks Tom!), there was no simple 
way to figure out how to write an AEDesc for some applications (I've 
never learned anything useful from AETracker, but YMMV). Ironically, 
the Finder is one of the hardest, at least in part because its 
scripting model was developed concurrently with, and in some cases 
before, the OSA architecture as a whole was developed. Determining 
something as trivial as a list of the available disks which, in 
AppleScript, is

	tell application "Finder" to get disks

can be an unholy nightmare to translate into AEGizmos. Fortunately, 
if you activate Capture AE, execution of this script dumps

	Process("Finder").SendAE "core,getd,'----':obj {form:indx, want:type(cdis), seld:abso(Ç616C6C20È), from:'null'()}"

The '&inte' and '&timo' keys are not relevant to us, so this is 
easily enough rendered in Tcl as

	AEBuild -r 'MACS' core getd ---- {obj {form:indx, want:type(cdis), seld:abso(Ç616C6C20È), from:'null'()}}

Although it works, "abso(Ç616C6C20È)" isn't very nice to look at.  A 
quick pass with aecoerce::hexd:TEXT (or perusal of an ASCII chart) 
reveals this is, reasonably enough, the identifier 'all ', so we 
substitute that and make use of this suite of routines, yielding

	aebuild::result 'MACS' core getd ---- {obj {form:indx, want:type(cdis), seld:abso(all), from:'null'()}}

which... unfortunately... returns

	{reco {{want {type prop}} {from {null {}}} {form {type prop}} {seld {type sdsk}}}} {reco {{want {type cdis}} {from {null {}}} {form {type name}} {seld {TEXT catbert}}}} {reco {{want {type cdis}} {from {null {}}} {form {type name}} {seld {TEXT ratbert}}}}}

which is a parsing nightmare (note that, on top of everything else, the 
Startup Disk is treated differently from everything else). Fortunately, 
we can make the Finder do our work for us by using the 'rtyp' tag

	aebuild::result 'MACS' core getd rtyp TEXT ---- {obj {form:indx, want:type(cdis), seld:abso('all '), from:'null'()}}

which yields a manageable

	catbert: dogbert: ratbert: 

on my computer.


Even with Capture AE, it's handy to be able to read 'aete' resources 
(if for no other reason, to decipher what you just blindly copied 
into your AEBuild statements). For this, Yuji Hachiya's 'aete' editor 
for ResEdit is indispensible.


If, for some reason, Capture AE is unavailable to you (redistribution 
is prohibited, so WestCodeSoft could conceivably withdraw it), there 
is, fortunately, a trick you can try: 

	1)	Enter the above AppleScript in the Script Editor and save it as 
		a compiled script. 
	2)	Open the script in a resource editor, open the 'scpt' resources, 
		and open what will likely be the only 'scpt' resource there 
		(if you have a 'scpt' resource editor installed, you'll want to 
		be sure to open the resource as hexadecimal). 
	3)	Look for the first occurence of the string 'MACS' (Ç4D414353È) 
		and change this to 'ALFA' (Ç414C4641È). Ignore any explicit 
		references to "Finder"; they don't matter. You've just changed the 
		script to ask Alpha for a list of all disks. Alpha obviously doesn't 
		know how to do this, but that's not the point. 
	4)	Close and save your changes.
	5)	Return to Alpha and write a dummy Tcl routine to intercept this event, 
		such as 

			proc snoop {event} {}

		It needn't do any more than this. aeparse::event is decidedly not 
		your friend for this task (try it if you don't believe me).
	6)	Declare 'snoop' as an AppleEvent handler for the event you wish to 
		examine:

			eventHandler core getd snoop

	7)	Place a trace on 'snoop'.
	8)	Run your modified AppleScript applette from the Finder.
	9)	Return to Alpha and dump traces. 

For the case above we obtain

	snoop 'core\getd{'----':obj {form:indx, want:type(cdis), seld:abso(Ç616C6C20È), from:'null'()}, &inte:cans, &timo:3600}' 
	snoop OK: 

Although this probably seems like a lot of work, I assure you that 
it's far preferable to the hunt-and-peck alternative. There's no 
reason that this methodology won't work with other scriptable 
applications, either, but I take no responsibility for the implosion 
of your monitor.

_________________________________________________________________

Lexicon
_________________________________________________________________

There are a plethora of routines in these packages, and the 
majority of the parsing routines, in particular, will probably 
never be called directly.  The ones most programmers will ever 
have need to call are listed below.

Note: If you do have reason to parse some sub-string of an 
AEDesc, keep in mind that, with the exception of aeparse::event, 
all of the token and grammar parsers operate in place, i.e., they 
are not passed a string, but the name of a string and, when 
finished, the parsed material is removed from the start of the 
string.
_________________________________________________________________

AEParse
_________________________________________________________________

NAME
	aeparse::event - Parse a textual event record, as returned by AEPrint.
SYNOPSIS
	aeparse::event chars ?-all? ?-coerce coercions? 
		?-noCoerce noCoercions? 
_________________________________________________________________

DESCRIPTION

	Optional parameters:
    
	-all: Return the entire parsed event structure, not just the AE 
		descriptor record. The default behavior omits the class and the 
		event. The reply from a call to AEBuild is always(?) an 
		Òaevt\ansrÓ, but Alpha event handlers could potentially receive 
		anything and may have use for this information.
        
	-coerce: Temporarily override (or supply) the specified type 
		coercions. Takes a list of lists, each element of which is 
		{'fromType' 'toType' 'coercionProc'}.
		
	-noCoerce: Do not perform the specified type coercions. This is 
		useful, for instance, to prevent the automatic coercion of 
		'alis's or 'fss 's to paths; this coercion is time consuming and 
		may not always be of interest. Takes a list of lists, each 
		element of which is {'fromType' 'toType'}. glob style 
		wildcards can be used to block a whole family of coercions, 
		e.g., Ò-noCoerce {{hexd *}}Ó will prevent all hexd coercions.
		
_________________________________________________________________

NAME
	aeparse::keywordValue - Return value of keyword from AE record.
SYNOPSIS
	aeparse::keywordValue keyword record ?typed?
_________________________________________________________________

DESCRIPTION

	Return the value of the specified keyword from a parsed 
	AppleEvent record.

	Optional parameter:
    
	typed: Boolean flag determines whether the value is returned as a 
		list {type value} or simply as value. Default is false.

_________________________________________________________________

AECoerce
_________________________________________________________________

NAME
	aecoerce::apply - coerce an AE value.
SYNOPSIS
	aecoerce::apply value type
_________________________________________________________________

DESCRIPTION

	'value' is coerced to 'type'. 'value' must be a list of 
	{'oldType' 'value'}. If a coercion from 'oldType' to 'type' has 
	not been declared with aecoerce::register, or with the 
	optional -coerce parameter to aeparse::event, then an error is 
	thrown.

_________________________________________________________________

NAME
	aecoerce::register - register a coercion procedure.
SYNOPSIS
	aecoerce::register from to proc
_________________________________________________________________

DESCRIPTION

	Registers 'proc' to handle coercions from AE type 'from' to AE 
	type 'to'. Proc must take a single argument of type 'from' and 
	return a value of type 'to'. E.g., 
	
		aecoerce::register "hexd" "fss " specToPathName
		
	will handle the coercion of Òfss (ÇFFFD000001CÉCCCCCÈ)Ó to 
	{{fss } {documents:Eudora:HIS:In}}.
	
_________________________________________________________________

AEBuild
_________________________________________________________________

NAME
	aebuild::result - Build an AppleEvent and return the parsed 
		direct object of its result.
SYNOPSIS
	aebuild::result args
_________________________________________________________________

DESCRIPTION

	The arguments to this command are those of AEBuild. 
	AEBuild's Ô-rÕ option to is not required, but is not harmful, 
	either. The Ô-qÕ option is not meaningful in this context, 
	and an ÒUnexpected end of format stringÓ error will be thrown.
	
	There is no mechanism for overriding coercions with this call. If 
	you wish to do that, you will need to use the long-hand
	
		aeparse::keywordValue ---- [aeparse::event [AEBuild -r ...] -coerce ...]

_________________________________________________________________

NAME
	aebuild::fromParsed - Turn list of lists (generated by aeparse 
		routines) into AEGizmo strings.
SYNOPSIS
	aebuild::fromParsed args
_________________________________________________________________

DESCRIPTION

	The arguments to this command are those returned by aeparse::event 
	(or a subset thereof). 

		aebuild::fromParsed [aeparse::event blah] 
	
	should be commutative (but there are surely many cases where this 
	fails for now).

_________________________________________________________________

NAME
	aebuild::objectProperty - Return specified property.
SYNOPSIS
	aebuild::objectProperty process property object
_________________________________________________________________

DESCRIPTION

	Ask 'process' for 'property' of 'object'. There is no mechanism for 
	overriding coercions with this call.
