SABLE adopts Smalltalk's message-sending syntax to call virtual and non-virtual methods, invoke primitive operations, inline macros, specify code units (in pragmas), and create metadata attributes. This elegant construct is really the heart of the language.
| Expression | Message Type | Message Name |
|---|---|---|
| ~Receiver | Prefix Message | ~ |
| Receiver isNil | Unary Message | isNil |
| Receiver + Arg | Infix Message | +: |
| Receiver key: Arg1 word: Arg2 | Keyword Message | key:word: |
Prefix and Infix Messages are often called operators because their names are made of symbol characters. However, they are always defined as methods within library classes rather than by the SABLE language. They consist of one or more of the following symbol characters. (Smalltalkers, note that comma is not an operator.)
% & * + - / < = > ? @ \ | ~
Prefix Messages are allowed only on identifiers and literals, for readability. Therefore, they must alias a unary message for use with arbitrarily complex expressions. E.g., defined on integer types:
~ ^{MyType}. {~ alias: #complemented} "My bitwise complement."
Infix Messages always left-associate. That is, [a + b * c - d] is [((a + b) * c) - d]. This strategy keeps the language simple since it doesn't have to be aware of operator precedence rules, and it allows the user to define entirely new operators. Infix Message names end with a colon, but it's not written when used.
In Keyword Messages, a keyword (a word plus a colon) precedes each argument to help the reader understand what each argument means. The name of a keyword message is the concatenation of all its keywords.
It is natural in SABLE to create message chains, where the result of one message becomes the receiver for the next. This elegant syntax is one of the greatest benefits that SABLE inherited from Smalltalk. Look for the many examples throughout this document.
When writing message/method names in comments and in documentation, we prepend '>#' to make it clear what it represents.
All SABLE messages require a receiver, which provides context so the reader knows what kind of method each message will invoke and where the method comes from. These are the message receiver contexts.
| Message Receiver | Matching Method | Example |
|---|---|---|
| Value Expression | Instance Method | lastIndex := start + length - 1 |
| Type Identifier | Static Method | EQUALITY_COMPARER_IMPL[STRING] default |
| TypeRef | Constructor | |list| := {LIST[INT32]} newCapacity: 15 |
| {Myself} | Same-class Constructor | {Myself} new |
| {Base} | Base-class Constructor | {Base} newNamed: name |
The final two forms above are only valid within constructor methods.
A series of messages can be sent to the same receiver very succinctly without repeating the receiver by including a semicolon between each message.
|tomatoCount| := 15. CONSOLE write: 'There are exactly '; write: tomatoCount; writeLine: ' tomatoes in the box'.
(Note, SABLE does not support method "overloading," but it provides a way to get a similar effect.)
This often eliminates needing to assign an expression to a variable just so that multiple operations can be conducted on it.
window formPanel acceptButton text: 'Accept'; backColor: #Beige; isEnabled: True.
One usage of this is to construct and initialize an object in one elegant expression.
|coll| := {LIST[STRING]} new add: 'alpha'; add: 'beta'; add: 'gamma'; yourself.
After the first message which establishes the cascade, the following items actually can be a message chain, defined above. This further allows constructing an object in a single expression, even when sub-objects need to be initialized. The following expression uses this twice.
{FORM} new text: 'My Dialog Box'; controls add: ({BUTTON} new text: 'OK'; clickEvent add: Me >~> #dialogOk_clicked:args:; yourself); showDialog.