XCMD Etiquette
Volume Number: 9
Issue Number: 1
Column Tag: Hypercard/Pascal
Standardizing the interaction of externals with HyperTalk in a
user-oriented way
By Jeremy John Ahouse and Eric Carlson, Berkeley, California
Note: Source code files accompanying this article are located on MacTech CD-ROMor source code disks.
About the authors
Jeremy John Ahouse and Eric Carlson are biologists who have found themselves
doing ever more computer work and lots of HyperCard scripting. Jeremy wrote a
chapter in the new Howard Sams intermediate scripting book Tricks of the HyperTalk
Masters and Eric is employed by Apple Computer Inc. as a multimedia software
engineer.
This note suggests several approaches to standardizing the interaction of
externals with HyperTalk in a user-oriented way.
We have noticed that as XFCN's and XCMD's are promulgated, many have become
difficult to use, and are often difficult to interpret in the context of reading a script.
There is no reason that externals should not retain the spirit of HyperTalk. We will
make several recommendations to this end and then offer example code that illustrates
our thoughts. (Note: We will refer to both XCMD's and XFCN's as XCMD's.)
HyperCard has given many people a chance to use their computers in ways that
were until recently restricted to “programmers”. The distinction between users and
programmers has been eroded by a new class of user/programmer called scripters.
We'll give away the moral of this story now; when writing XCMDs you should treat
scripters the way you would treat users if you were writing traditional Macintosh
applications.
There are really two issues that we need to address. The first is making an
external easy to use, the second is making scripts that use externals easy to read and
understand. These two are not mutually exclusive.
We begin by listing four problems and then follow with discussions and possible
solutions for them. We will end by illustrating our points with source for a
StringLength XFCN.
The Problems
1) Many externals obscure the flow of HyperTalk scripts. This makes them harder
to understand (and debug).
2) When an error occurs during the execution of an external, how should this be
reported to the user/scripter?
3) A user forgets the parameters of your external and there isn't a standard way to
find out what they are.
4) A user doesn't want to include a long list of parameters if only one feature of an
external is used.
Solutions
Solution No. 1:
Making scripts read well requires XCMD's that are well named and parameters
that are easy to understand. We illustrate this point with a counter example:
put xseven(2, no, 4, h, 1) into msg
Try to use words for input parameters whenever you can. Obviously in some
cases it will be much clearer to pass numbers. A rule of thumb is: use numbers only if
you are actually working with a number in the external, like the number of items in a
list, or lines in a container. Finally, numbers are appropriate if the parameters in
the XCMD can get their values from HyperTalk functions (like max) or properties
(like textHeight) that return numbers.
A way to avoid naming your XCMD obscurely is to not ask too much of it. Allow
your external to do a reasonable number of things well. If you have lots of great ideas
write more than one XCMD. Remember that XCMD's extend HyperTalk.
Another aspect of naming externals is to try to make them read well. This is
particularly important for XFCNs, which may become part of HyperTalk statements.
Try this test. How does your function sound/read in the following contexts:
get myFunction()
put myFunction() into msg
put item 4 of myFunction() into temp
Names that start with verbs don't work well. XCMDs, on the other hand, often
read well if they start with verbs.
Solution No. 2:
Reporting errors is always a problem. There are many levels of users and while
some will want to handle error codes themselves, others will benefit from a less subtle
solution. Make the last (optional) parameter either "Dialog" or "noDialog" with the
former as default. Here is an example:
functionThatDanLeftOut(param1, param2, "Dialog")
or, equivalently:
functionThatDanLeftOut(param1, param2)
In these cases the external will return error codes in a dialog and in the result,
whereas
functionThatDanLeftOut(param1, param2, "noDialog")
will report return error conditions in the result only. This convention will
allow users to suppress error messages that stop the flow of a script and to handle the
error conditions on their own if they so choose.
It should also be apparent from the tone of this note that we don't encourage the
idea of returning errors like this:
-202
rather do this:
"The Mac seems to have chewing gum in the speaker.
It seems that this recommendation may be difficult for people who implement
whole systems that reside outside of HyperCard and who use a set of externals to
communicate with their extra-HC system. We are thinking here of search engines,
databases, etc For those who feel strongly about the need to return error conditions
numerically, we suggest offering your users a function which returns a description of
an error condition when passed the error number:
put Error("-202") into msg box
would put
"The Mac seems to have chewing gum in the speaker.
into the msg box. The point here is to make interactions with externals as easy to use
as possible.
Solution No. 3:
XCMDs are often documented only within the simple stacks written to distribute
and demonstrate them. It is inconvenient for a scripter to have to find and open that
stack if they forget the syntax for an external during stack development. Additionally,
as newer (debugged!) versions of externals come out, it is often difficult to know
which version of an XCMD is in a stack. Support the following forms for your
external:
functionThatDanLeftOut("?")
should reports back the syntax for the external without performing its function, i.e.:
functionThatDanLeftOut("param1", "param2", "param3")
would be returned by the XFCN (or XCMD). If some of the parameters are optional
surround them with the <> symbols. For example,
commandThatDanLeftOut("param1", <"param2">, <"param3">).
Version and copyright information can be made available to scripters in the same
way:
functionThatDanLeftOut("??")
or
commandThatDanLeftOut "??
should return the copyright information and the version for the external. As first
written, this article recommended using the copyright symbol (“©”) for version and
copyright information. Upon review, Fred Stauder noted that this symbol is not
available on all international keyboards, and so recommended a change. Thanks Fred!
A pair of simple Pascal functions to check for and respond to these requests might
look like this:
{1}
procedure reportToUser (paramPtr: XCmdPtr;
msgStr: str255);
{}
{ report something back to the user. we always fill }
{ in the result field of the paramBlock, and optionally }
{ use HC's "answer" dialog unless requested not to }
{}
var
tempName: str255;
begin
paramPtr^.returnValue := PasToZero(paramPtr, msgStr);
{check the last param to see if the user requested that }
{ we suppress the error dialog }
ZeroToPas(paramPtr, paramPtr^.params[paramPtr^.paramCount]^,
tempName);
UprString(tempName, true);
if tempName <> 'NODIALOG' then
SendCardMessage(paramPtr,
concat('answer "', msgStr, '"'));
end; { procedure }
function askedForHelp (paramPtr: XCmdPtr;
syntaxMsg: Str255;
copyRightMsg: Str255): boolean;
{}
{ check to see if the user sent a '?' or a '??' as }
{ the only parameter. if so we will respond with }
{ the calling syntax or the copyright/version info }
{ for this external }
{}
var
firstStr: str255;
begin
askedForHelp := false;
if paramPtr^.paramCount = 1 then
begin
ZeroToPas(paramPtr, paramPtr^.params[1]^, firstStr);
{ what is the first param? }
if firstStr = '?' then
begin
reportToUser(paramPtr, syntaxMsg);
askedForHelp := true
end { asked for help }
else if firstStr = '??' then
begin
reportToUser(paramPtr, copyRightMsg);
askedForHelp := true
end; { asked for copyright info }
end; { one parameter passed }
end; { function }
Many externals (wise externals?) check the parameter count and return some of
this information if the number of passed parameters is wrong. Most of these will
continue to function properly if a user presents the external with a "?" or a "??", but
the point is to make this method standard so that users know to use it. Adopting this
approach will give scripters a standard way to query XCMDS and will give us a way to
internally document externals.
Solution No. 4:
Support default values for your externals. This means that a user is required to
pass only those parameters that are necessary. In the StringWidth function that we
discuss below, if only a string is passed, the function defaults to the HyperCard default
text size, font, and style - 12 point, Geneva, plain. This approach seems to offer a good
combination of flexibility and clean HyperTalk. If taken to the extreme, this approach
can also make it very difficult to elucidate the purpose of an XCMD when reading
through a script, so keep point 1 in mind as you decide on optional parameters and
default values.
Problems?
Not all of these recommendations will be universally applicable. Doubtless
someone has written an external which must be passed "?" or "??", but try to
remember the spirit of these approaches. Make the external easy to use, flexible, easy
to read (for debugging if not aesthetics), and finally treat external users like
Macintosh users. HyperCard has made “programming” (whoops “scripting”)
available to many people who never thought they would ever have so much control over
their computer. It is vital that we do what we can to suppress the tendency for the
techno-macho/techno-less macho dichotomy to take hold (or should we say widen).
What follows is the source for an XFCN written in Think Pascal which tries to
follow some of our own advice. This XFCN returns the width in pixels of a string
passed to it. It is similar in function to Fred Stauder's XCMD from the March, 1991
issue of MacTutor, but we have given it some additional functionality as well as writing
it as an XFCN (it is a function after all). Fred's implementation contained no