Skip to content

Messages and Handlers

Matt DeFano edited this page Mar 8, 2019 · 4 revisions

Message Passing Order | Parameters | Functions | Messages Sent by WyldCard | Multithreading

A HyperTalk script is a set of message handlers and function handlers that describe how the part reacts when WyldCard sends a message to it. A message handler traps incoming messages. A function handler is a subroutine that can be invoked by a message handler to compute and return a value.

As previously noted, WyldCard automatically sends messages to parts as the user interacts with them. However, messages are not limited to WyldCard's origination: A script can invent its own messages and send them to itself or to other parts.

Simply invoking the name of a message as a statement in a script "sends" the message to the current part. For example, consider this script:

on mouseUp
  answer "Hello World" with "Why, thank you."
  mouseUp    -- uh oh, this creates an infinite loop
end mouseUp

The result of this code is an infinite loop: After displaying the "hello world" greeting, the button sends itself the mouseUp message, which causes the handler to execute again. Press command-period (or control-period) to abort a runaway script.

To send a message to different part, use the send command. For example, send "mouseUp" to background button 1.

Message passing order

Messages automatically traverse a hierarchy of objects: If a part receives a message and does not have a handler to handle it (or, if its handler invokes the pass command), then the message is forwarded to the next part in the sequence.

Messages follow this sequence:

Buttons and fields pass messages to the card or background on which they appear; a card passes messages to its background; and a background passes messages to its stack. If the stack does not trap the message, then the message is passed to WyldCard which handles the message itself.

This message passing architecture empowers parts to override system behavior by trapping event messages. For example, add the following script to a field to disallow entry any of any character other than an even number:

-- Prevent user from typing anything other than an even number in this field.
on keyDown theKey
  if theKey is a number then
    if theKey mod 2 is 0 then pass keyDown
  end if
end keyDown

This works because WyldCard sends the keyDown message to the field when a user types a character into it. Because this script declares an on keyDown handler, the script traps this message and prevents WyldCard from acting on it (WyldCard's action would be to insert the typed character in the field's text).

However, when the typed character is an even number, the script invokes pass keyDown which explicitly forwards the message through the message passing order to WyldCard. Thus, by implementing this handler and only conditionally passing the keyDown message back to WyldCard, the script can "steal" these key press events and prevent their normal behavior.

Note that HyperTalk does not short-circuit logical evaluations as many programming languages do. The script cannot be simplified to if theKey is a number and theKey mod 2 is 0 because in the case of theKey being a non-numeric value, the mod expression will produce an error.

Anytime a command is executed in HyperTalk, a message of the same name as the command is sent to the current card. The enables the same capacity for trapping the behavior of commands. For example, adding the following script to a card, background or stack prevents the create (menu) command from doing its job:

on create
  answer "Are you sure you want to create a menu?" with "Yes" or "No"
  if it is "Yes" then pass create
end create

Message parameters

Every message has a name (like mouseUp) that identifies which handler should handle it. Messages may also contain arguments.

For example, we could define a message called greet whose argument defines the name of the person to greet. We'd invoke this message as greet "Bill" or send "greet Bill".

Then, a handler to handle this message would be written like:

on greet thePerson
  answer "Hi " & thePerson
end greet

HyperTalk does not require that the number of message arguments match the number of parameters defined by the handler. If a handler defines more parameters than actual arguments provided with the message, the unspecified parameters are bound to empty (""). In the previous example, send greet is equivalent to send greet "". Both cause the greet handler to execute with thePerson bound to the empty string.

HyperTalk does not differentiate between handlers that handle the same message but which accept a different number of arguments (known as overloading). When a script defines two handlers for the same message, only the first handler lexically appearing in the script will ever be used handle the message (subsequent handlers are effectively unreachable).

Function handlers

A function handler is a subroutine scripted by the user that performs some action and optionally returns a value. More accurately, all user-defined functions return a value, but those which do not explicitly call return implicitly return empty.

Note that user-defined function handlers may not...

  • Be invoked using the [the] <function> of ... syntax (like you'd use with a built-in HyperTalk function)
  • Have the same name as another function in the same script, or the same name as a HyperTalk keyword. HyperTalk does not support function overloading (two functions with the same name but having different parameters).
  • Be nested inside of other functions or handlers

If a function handler is not defined in the same script in which it is invoked, the message passing order is used to locate the function handler. Thus, if a card button attempts to invoke myFunction() and the button's script does not define function myFunction then the card's script is searched, then the background's, then the stack's. However, unlike message handlers, invoking a function for which no function handler exists results in a syntax error.

The syntax for defining a function handler is:

function <functionName> [<arg1> [, <arg2>] ... [, <argN>]]]
  <statementList>
  [return <expression>]
end <functionName>

When calling a user-defined function, use the syntax <functionName>(<arg1>, <arg2>, ...). The number of arguments passed to the function must match the number declared in the handler.

Consider this recursive function for generating the Fibonacci sequence:

on mouseUp
  answer fibonacci(empty, 0, 1, 200)
end mouseUp

function fibonacci sequence, lastValue, thisValue, maxValue
  if sequence is empty then put "0" into sequence
  put thisValue + lastValue after the last item of sequence

  if thisValue + lastValue <= maxValue then
    return fibonacci(sequence, thisValue, thisValue + lastValue, maxValue)
  else
    return sequence
  end if

end fibonacci

Messages automatically sent by WyldCard

WyldCard automatically sends the following messages to parts as the user interacts with the stack:

Event Message Description
arrowKey Sent when an arrow key is pressed; sends the arrow key's direction as an argument to the message (one of up, down, left or right).
commandKeyDown Sent when the command key (or the meta key, on non-macOS systems) is pressed in conjunction with another key (for example, cmd-x); passes the character typed as the argument to the message.
controlKey Sent when the control key is pressed in conjunction with another key; passes the character typed as the argument to the handler. This differs from HyperCard's implementation, which passed a numeric keycode as its argument.
choose Sent to the current card when the tool selection changes; passes the tool name and tool number as arguments (for example, choose "Brush", 7).
closeCard Sent to the current card when navigating away from it.
deleteCard Sent to the current card just before it is removed from the stack.
doMenu Sent to the current card when the user chooses a menu from the menu bar; passes the menu name and menu item name as arguments, for example, doMenu "Edit", "Undo"
enterKey Sent when the enter key is pressed.
enterInField Sent when the enter key is pressed while typing in a field.
exitField Sent to editable fields when they lose focus.
functionKey Sent when a function (i.e, F1) key is pressed; sends the number of the function key as its argument (on functionKey whichKey, where whichKey is a number between 1 and 12)
idle Periodically sent to the current card when there are no other scripts executing.
keyDown Sent when a key is typed over a focused part; sends the key as an argument to the message, for example, on keyDown theKey.
mouseDoubleClick Sent when the mouse is double-clicked over a part.
mouseDown Sent when the mouse is pressed over a part.
mouseEnter Sent when the cursor enters the bounds of a part. Note that WyldCard sends this message to cards, too, but HyperCard did not.
mouseLeave Sent when the cursor leaves the bounds of a part. Note that WyldCard sends this message to cards, too, but HyperCard did not.
mouseStillDown Sent when the mouse is long-pressed (held down) over a part.
mouseUp Sent when the mouse is pressed and released (clicked) over a part.
mouseWithin Send repeatedly to buttons and fields while the mouse is within their bounds.
newCard Sent to new cards when they are added to the stack.
newButton Sent to buttons when they are first added to a card or background. New buttons will have no script to handle this message (but cut-and-pasted buttons may), plus card and background scripts in the message passing order may respond to this message, too.
newField Sent to fields when they are first added to the card or background. See also newButton.
returnInField Sent when the return key is pressed while typing in a field.
returnKey Sent when the return key is pressed.
openCard Sent to cards as they are displayed in the stack window (as a user navigates to them).
openField Sent to editable fields when they gain focus.
openStack Sent to a stack when it is opened in WyldCard.
resumeStack Sent to the current card of a stack when it gains focus from another stack window.
suspendStack Sent to the current card of a stack when it loses focus to another stack window.
tabKey Sent when the tab key is pressed.

Parts do not need to implement a handler for every message they might receive. Messages for which no handler exist are simply ignored.

Multithreading in WyldCard

Script execution in HyperCard is essentially single-threaded, but in WyldCard up to eight scripts can execute concurrently. This threading behavior is implicitly managed by WyldCard as there are no constructs in the HyperTalk language to manage threads of execution.

Each time WyldCard sends a message to a part (e.g., mouseUp) it attempts to execute that message's handler on one of the eight available threads. If all threads are currently busy executing other scripts, then execution of the handler will be enqueued for processing when one of the other scripts completes.

Any messages generated in the execution of a handler will execute synchronously and on the same thread as the calling handler. In other words, only messages produced by WyldCard can execute on different threads. Once a script starts executing, all messages and functions in its call stack are single-threaded.

Clone this wiki locally