“The big idea is ‘messaging’” – Alan Kay

I have found that very elegant and simple languages are usually based on a strong principle that is uniformly applied and in Smalltalk that principle is message passing, and it provides the base of everything that makes Smalltalk great. It has even been said that message passing is more important than objects in OOP. Hopefully before the time we finish Redline the elegance of Smalltalk and the importance of message passing will become evident.
The goal of this ‘small talk’ (part 1 and part 2) is to be able to send messages to Smalltalk objects and like Smalltalk itself this ability will lay the foundation for all the other features we will add. Part 1 of ‘Message in a bottle’ will make the changes necessary to parse Smalltalk source code containing Unary Messages into a tree of nodes that represent that input. In Part 2 of ‘Message in a bottle’ we will make the changes necessary to generate Java bytecode from input nodes and execute that bytecode which will result in messages being passed to objects.
Like the previous talk you can download the sources from github then checkout the tag with ‘git checkout MESSAGING1’. I’m assuming that those following along are looking at the ‘CHANGES’ file and comparing the ‘diffs’ to see what has changed.
Message Passing
Sending messages to objects is what makes things happen in Smalltalk and I literally mean everything. Message passing is the founding principle and it is uniformly applied. If you are familiar with other programming languages then message passing’s effect on the execution of the program is most like calling a method or function.
Elegance
Everything in Smalltalk is expressed as message passing: the sending of a message to a receiver with optional arguments, including all syntactic constructs. This uniformity and consistency makes Smalltalk incredibly easy to learn and enables new syntax to be added at any time. For example, the method “on:do” which provides exception handling wont be found in the Smalltalk-80 Kernel but it will be found in the Pharo Smalltalk, and yet it looks like it was always part of the language.
The following is the Smalltalk syntax rule and a few examples, however for a full explanation it is recommended you read the relevant section of the ‘Blue Book’:
| receiver message [: argument ] |
Account new
account debit: 100
customer name uppercase
100 * 1.5
widget moveTo: position * offset
account balance negative ifTrue: [ account overdrawn ] |
The receiver is the object to which the message is sent. The result of a message expression can itself be the receiver of another expression with evaluation from left to right. The first example shows the message ‘new’ being sent to the Class ‘Account’. The last example above shows three messages, ‘balance’, ‘negative’ and ‘ifTrue:’ with ‘negative’ and ‘ifTrue:’ each sent to the result of the previous send. The last message (ifTrue:) takes an optional argument which is another message send in a [] which denotes a block of code that can be passed around and evaluated later. It is convention in Smalltalk for messages that take arguments to be followed by a colon ‘:’. It is possible for the argument to a message to be the result of another message send as shown in the second last example ‘widget moveTo:’.
The example ‘100 * 1.5’ is evaluated just like the other message sends, with ‘100’ being a number object and the receiver of the message ‘*’. A message like ‘*’ is called a binary message and they take one argument which in this case is the number object ‘1.5’. Again, it is possible for the argument to a message to be result of another message send, so we could have ‘100 * 1.5 + 2’, where ‘*’ and ‘+’ are both binary messages.
You now know all the Smalltalk syntax you need to move forward. Of course there are some other things to know in order to define the methods that are invoked in response to messages but these too are created by sending messages to objects. The compiler and environment add a few short-cuts to aid in the expressing of message sends but under the covers they send messages to objects as well. That’s the beauty of the Smalltalk principle of message passing when uniformly applied.
Sending Messages
For Redline to send messages to objects we need a way of breaking up input into chunks we can understand and determining which of those chunks represent a receiver, a message and any possible optional arguments, which themselves may be a message send. To accomplish this in a small step we will implement just enough of the Smalltalk language to recognise a message send without any arguments which is called a “Unary Message”. An example of a Unary Message is ‘Object new’ where the receiver (‘Object class’) is sent the message ‘new’. What we will do is:
- Modifying the Smalltalk grammar file (smalltalk.g)
- Invoking the SmalltalkLexer and SmalltalkParser created by the ANTLR Tools
- Interpret the tree of nodes that represents the input (without execution)
- Creating some additional Java classes to support the above.
Grammar
The following is the initial grammar (in ANTLR format) that we have in place to break up the Smalltalk source into understandable chunks, keeping in mind that we have kept just enough of the full grammar to understand Unary Messages and we will be adding to this grammar with each ‘small talks’ installment. The grammar itself was deduced from the last few pages of the ‘Blue Book’ which provides a ‘railroad‘ diagram of the Smalltalk Grammar and from my previous work on implementing Smalltalk. Using the grammar and the ANTLR plug-in you can see a ‘railroad’ view.
program returns [Program n]
: sequence EOF {$n = new Program($sequence.n);}
;
sequence returns [Sequence n]
: statements {$n = new Sequence($statements.n);}
;
statements returns [Statements n]
: statementList ‘.’? {$n = new Statements($statementList.n);}
;
statementList returns [StatementList n]
: { $n = new StatementList(); }
expression {$n.add($expression.n);}
;
expression returns [Expression n]
: cascade {$n = new Expression($cascade.n);}
;
cascade returns [Cascade n]
: messageSend {$n = new Cascade($messageSend.n);}
;
messageSend returns [MessageSend n]
: unaryMessageSend {$n = new MessageSend($unaryMessageSend.n);}
;
unaryMessageSend returns [UnaryMessageSend n]
: primary {$n = new UnaryMessageSend($primary.n);} (unaryMessage {$n.add($unaryMessage.n);})+
;
unaryMessage returns [UnaryMessage n]
: NAME {$n = new UnaryMessage($NAME.text);}
;
primary returns [Primary n]
: variable {$n = $variable.n;}
;
variable returns [Variable n]
: NAME {$n = new Variable($NAME.text);}
; |
The top level node that is returned by the parser is Program. The ‘program’ rule represents the top level rule and it returns the Program node which is the root of all the other nodes that make up the tree structure of the parsed Smalltalk program. Right at the bottom of the grammar, when a unary messages is recognized a UnaryMessage node is created to hold information about the unary message and that node is returned up a level to the rule above it. The ‘unaryMessageSend’ rule says that a unary message send can be made up of a primary and one or more unary messages. As each unary message is recognised it is added to the UnaryMessageSend node and when no more unary messages are recognised the UnaryMessageSend node is returned up a level. This process continues until the top level node is reached because we have no more input to parse. The ‘primary’ rule in our initial grammar is used to recognize the receiver of the message sends, and only ‘primary variables’ for now. A graph of this process is shown below for the input “Object new name” (see src/test/smalltalk/Test.st):

The tree of nodes that are returned by the parser contains a representation of the Smalltalk program that was parsed. This tree is then processed using the Interpreter and Analyser classes. The Analyser class along with the node classes implement a Visitor pattern so each node can be processed by the Interpreter. The main classes involved in this processing are described next in ‘The Parts’ section.
When you have checked out and compiled the source for this talk you can run ‘stic’ against the test input ‘src/test/smalltalk/Test.st’ and you should see the following output:
* Currently for demonstration purposes the nodes ‘variable’ and ‘unaryMessage’ print their values when visited by the analyser and this will be changed in later talks.
The Parts
This section details the main classes used to interpret the source and the role that the class plays.
Interpreter: st.redline.smalltalk.interpreter.Interpreter
The Interpreter class provides control over the parsing, analysing, generating and execution process. The Interpreter class is responsible for invoking the SmalltalkLexer and SmalltalkParser classes that are generated by the ANTLR tools from the ‘Smalltalk.g’ grammar file. The Interpreter class takes the Smalltalk source to interpret as a Java String.
Analyser: st.redline.smalltalk.interpreter.Analyser
The Analyser class analyses each node in the tree of nodes and gathers information and decides what message sends should be generated in response to the input node. Part 2 of ‘Message in a bottle’ will cover this aspect in more detail.
BasicNode: st.redline.smalltalk.interpreter.BasicNode
The BasicNode class provides a base for parsed nodes with each of its subclasses representing a specific element of Smalltalk. A BasicNode represents a singular element like a String or a Number, while BasicListNode represents an element with one or more associated nodes, like a literal Array. There is a subclass of BasicNode or BasicListNode for each Smalltalk language construct represented in the grammar file.
Smalltalk: st.redline.smalltalk.Smalltalk
To enable Smalltalk to be embedded in your Application or to be used stand alone there is a single entry point to Redline which is the Java Class named Smalltalk (st.redline.smalltalk.Smalltalk), and to use Redline Smalltalk you need to create an instance of this Java class.
Environment: st.redline.smalltalk.Environment
Redline runs in a self contained environment and this environment is passed to the Smalltalk instance when it is created. This environment is referenced whenever the Smalltalk instance requires information like command line arguments or operating system properties. This approach is loosely modeled on Ruby Rack and will allow Redline Smalltalk to be more easily integrated and to be used in situations where user sessions need to be kept separate like in a Web Container.
The Next Step
In the next installment of Redline: Smalltalk in “small talks”’ we will analyse the tree of nodes that represent a Smalltalk program to generate Java Virtual Machine bytecode for each node and execute it, resulting in running Smalltalk programs!