HyperCard, Forth
Volume Number: 3
Issue Number: 12
Column Tag: Forth Forum
Extending HyperCard from Forth
By Jörg Langowski, MacTutor Editorial Board, Grenoble, France
HyperCard external functions and commands
One of the major events in the last couple of months on the Macintosh scene has
been the introduction of Hypercard (You’ve read an article on it in the last MacTutor).
This month’s column will deal with interfacing Forth code to Hypercard; we shall see
how an external command can be implemented in Mach2.
The syntax of HyperTalk commands
A ‘command’ in the Hypertalk language is a word followed by a number of
parameters, separated by spaces. Example:
dial it with modem
will execute the dial command and pass the parameter string: “it with modem”.
Dial will then know how to deal with that string; in this case, get the ASCII string
stored in it, interpret it as a phone number to be dialed and issue the appropriate
Hayes-compatible modem command.
Similarly, if we define our own command, munch, we may send parameters to it,
“three bars of milk chocolate”, and the corresponding Hypertalk line will read:
munch three bars of milk chocolate
The concept, as you see, is very simple. All we now need to know is how such an
external command is implemented, i.e., how the Hypertalk system finds the command
and how the parameters are passed.
How an external command is found
An external command is stored as a resource of type XCMD. The resource name is
the name of the external command as called through Hypertalk. I have not peeked inside
Hypertalk’s guts yet, but it seems to me very probable that a GetNamedResource is
used to get an XCMD that is to be executed.
The resource is a piece of executable 68000 code, just like a WDEF or MDEF
would be. When the XCMD is executed, the system jumps to the beginning of the
resource. There, on top of the stack a pointer to a parameter block is passed, which
looks like the following (in Pascal notation):
XCmdPtr = ^XCmdBlock;
XCmdBlock = RECORD
paramCount: INTEGER; { the number of arguments }
params: ARRAY[1..16] OF Handle; { the arguments }
returnValue: Handle; { the result of this XCMD }
passFlag: BOOLEAN; { pass the message on? }
entryPoint: ProcPtr; { call back to HyperCard }
request: INTEGER; { what you want to do }
result: INTEGER; { the answer it gives }
inArgs: ARRAY[1..8] OF LongInt;
{ args XCMD sends HyperCard }
outArgs: ARRAY[1..4] OF LongInt;
{ answer HyperCard sends back }
END;
A Hypertalk function, in contrast to a command, will always return a value and
its syntax is different from that of a command. For example:
the exp of number
will return the exponential of number. A Hypertalk line like that has no meaning, so
we’ll write, for instance:
put the exp of number into it
and it will contain the result. An alternative syntax would be
put exp(number) into it.
An external function is stored as a resource of type XFCN, with the resource name
being the name of the function, like for the XCMDs; again, it is called by jumping to the
start of the resource.
The parsing of the line following the command or function is automatically done
by Hypercard; the arguments (i.e., the words following the command) are passed to the
XCMD in form of handles to zero-terminated strings. The XCMD that we write can do
whatever necessary with them. If you want, you may return a result by storing a
handle to it in returnValue. (All data values going to and from HyperTalk are
zero-terminated ASCII strings). A result that has been stored in this field can be
accessed by the user in the variable ‘the result’ after executing a command. In a
function, returnValue is always expected to contain something.
passFlag is used to determine whether the command has been executed
successfully. If this flag is false, the XCMD or XFCN has handled the message and the
script resumes execution. If passFlag is true, HyperCard searches the remaining
inheritance chain for another handler or XCMD with the same name. This is just like
the ‘pass’ control structure in a script.
You may also call HyperCard from within your code. This is what the second part
of the parameter block is used for. To call HyperCard commands, you put your
arguments into inArgs, the command code into request, and call the routine pointed to
by entryPoint. HyperCard returns the values you requested in outArgs and a result
code in result.
The command and result codes are part of a Pascal definition in the Hypercard
developer package materials. They are given in Listing 1, together with a description
of what the commands do.
Mach2 implementation
The example program (Listing 1) will create an XCMD that inverts the screen a
given number of times, like the built-in flash command. The difference is that the
result will return Done when the command was executed successfully and Error when a
negative number has been given as a parameter (incorrect). Its syntax will be:
flashy
where is a string that is interpreted as a decimal number.
Now look at the example. You’ll recognize the glue code that is used in the XCMD
definition to setup the Forth stack and transfer the parameters from the Pascal to the
Forth stack; I defined in the form of a MACH macro so the definition of the actual glue
routine becomes very simple. The XCMD is embedded between the labels flashy.start
and flashy.end. callJSR is a redefinition of the kernel word execute, which does a JSR
to an address given on the stack. This word is needed in the two following words,
ZeroToPas and StrToNum, which call Hypercard routines through the entryPoint
parameter in the Hypercard parameter block. ZeroToPas converts a C string
(zero-terminated) to a Pascal string (count byte followed by characters). StrToNum
takes a Pascal string and interprets it as a number. Both routines also need to be given
the address of the Hypercard parameter block containing Hypercard’s entry point.
For the other Hypercard functions that may be called from outside, see their
request codes and Pascal definitions given at the beginning of the listing. With the two
routines given here as an example, you can easily figure out how to call the remaining
ones. I also strongly recommend reading the documentation and Pascal or C example
files that come with the Hypercard developer’s package, which you may obtain through
APDA (DDA in France, see below).
The actual XCMD, flashy, first gets the first parameter, which is a handle to a
string, then converts this string to a number. If the number is negative, it will return
“Error” as a return string, if it is positive, it flashes the screen for this number of
times and returns “Done”.
The last part of the listing will simply create a file containing the new resource.
Calling the XCMD from Hypercard
When you’ve created the file “xcmd.res”, enter ResEdit and install the XCMD
resource in your home stack (or any other stack, for that matter). Then you can call
Hypercard and, for example, create a button which has the following script:
on mouseup
flashy 10
put the result
end mouseup
Pushing this button, then, should flash the screen ten times and display “Done”
in the message window. If you change the number to -10, no flashing should happen and
“Error” should be displayed in the message box.
You may try to change the resource type to XFCN; this will change the syntax to
(for example)
on mouseup
put flashy(10)
end mouseup
but should not otherwise affect the behavior of the command.
Feedback Dept.
Mac Applications in Forth?
Paul Thomas
Danville, CA
Q. Can Forth be used on the Mac to do software stuff: i.e. games, engineering
applications, etc.? I just picked up “Mach2” last month and I’ve been reading Leo
Brodie’s “Starting Forth”. I was studying the Mac Toolbox using Turbo Pascal, but I
went over to Palo Alto Shipping and visited with Rick Miley. I’m an engineering student
(mechanical engineering) and he got me excited about Forth. Most of your articles tend
to deal with hardware, data transfer, low level stuff. I realize that Forth is great for
that type of work, but I’d like to program the Mac in Forth. I haven’t been able to find
very many example programs in Forth. I guess I could try my hand at translating from
Pascal and C to Forth. It’s going to be tough though since I’m new at Forth - and it
really IS different from Pascal or C. Are most of your readers interested in low level
Forth work? Do you know of anyone who is programming applications in Forth? Most
people that I know that program the Mac work in Pascal or C.
I do enjoy your articles [Thanks! JL]. I’d love to send you a message over
CalvaCom, but it’s a bit too expensive for me. I can’t even afford Compuserve. Do you
have an address on GEnie?
A. Can Forth be used to do software stuff: why, yes! don’t we do software stuff? In
fact, I see what you mean and can only say that one full-fledged application in each
column would probably be too much. If you’ve seen the space taken up by long articles
in MacTutor, you’ll understand that we cannot print a regular column that long each