Implementing a stack machine
TL;DR
This is a tutorial on programming classes and methods with a stack machine example
Implementing a stack machine
A stack machine is a simple model of a computer in which operations are performed on a stack.
We would like to implement a simple stack machine in Smalltalk.
Creating a class
Let's create a class called StackMachine
.
We'll create it from the Playground as a subclass of CollectionValueHolder
.
This is just a wrapper around a collection that allows us to view live updates (it turns it into a model with a view as in MVC).
StackMachine new
NB: For each of the code changes we will make, we can either implement them directly, or we can load the change snippets, such as the following one, by clicking on the checkmark.
CollectionValueHolder subclass: #StackMachine instanceVariableNames: '' classVariableNames: '' package: 'StackMachine'. StackMachine class instanceVariableNames: ''
Let's try creating an instance again:
StackMachine new
The object exists now, but the value
slot has not been initialized. (See the Raw
tab.)
Implementing the initialize method
Let's browse the class and initialize the slot to an OrderedCollection
. (Click on the Meta
tab.)
"protocol: #accessing" StackMachine >> initialize value := OrderedCollection new
Now we can view the machine, but its state is empty. (Go back to the Boxes
tab.)
StackMachine new
Testing with example methods
To test our code as we write it, we also write
example methods
. These are just like test methods, except they also return an example
, an object that we can explore, or reuse in further example method.
Let's create an example for an empty stack machine, including some tests.
StackMachine new empty
"protocol: #accessing" StackMachine >> empty <gtExample> | result | result := StackMachine new. self assert: result value isEmpty. ^ result
Push, top and pop
Now let's implement a push:
method. First let's define an example with a value pushed.
StackMachine new pushed1
"protocol: #accessing" StackMachine >> push: anObject self addLast: anObject "protocol: #accessing" StackMachine >> pushed1 <gtExample> | result | result := self empty. result push: 1. self assert: result size equals: 1. self assert: result top equals: 1. ^ result "protocol: #accessing" StackMachine >> top self assert: value isNotEmpty description: 'stack is empty'. ^ value last
NB: push:
must send addLast:
to self
and not the value
slot, so the MVC announcements will work.
We'll need top
to test the result of push:
. We'll also check the precondition that the stack is not empty when we perform top
. (Same for pop
.)
We have push and top; now we need pop.
StackMachine new push1andPop
"protocol: #accessing" StackMachine >> pop self assert: value isNotEmpty description: 'stack is empty'. ^ self removeLast "protocol: #accessing" StackMachine >> push1andPop <gtExample> | result popped | result := self pushed1. popped := result pop. self assert: popped equals: 1. self assert: result isEmpty. ^ result
Interacting with live objects in the Inspector
Now we can inspect a StackMachine and interact with the live object. Inspect the following snippet, and directly send it push:
and pop
messages. (Pull up the playground view at the bottom of the Inspector.)
StackMachine new
Implementing stack machine operations
Let's push another value on the stack.
StackMachine new pushed1and2
"protocol: #accessing" StackMachine >> pushed1and2 <gtExample> | result | result := self pushed1. result push: 2. self assert: result value size equals: 2. self assert: result top equals: 2. ^ result
Now we're ready to implement an add
operation.
StackMachine new add1and2
"protocol: #accessing" StackMachine >> add1and2 <gtExample> | result | result := self pushed1and2. result add. self assert: result value size equals: 1. self assert: result top equals: 3. ^ result "protocol: #accessing" StackMachine >> add self assert: value size >= 2 description: 'operation needs two arguments on the stack'. self push: self pop + self pop
More operations
We can add other arithmetic operations, as well as dedicated stack operations, like dup
(push a copy of the top value) and swap
(swap the top two values).
NB: swap
makes it easier to implement operators like sub
(subtract) and div
(integer divide).