Skip to main content
Engineering LibreTexts

14.7: A Complete Tutorial Example

  • Page ID
    43217
    \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)\(\newcommand{\AA}{\unicode[.8,0]{x212B}}\)

    Let’s see how we can build a complete Seaside application from scratch. (The exercise should take at most a couple of hours. If you prefer to just look at the completed source code, you can grab it from the SqueakSource project http://www.squeaksource.com/PharoByExample. The package to load is PBE-SeasideRPN. The tutorial that follows uses slightly different class names so that you can compare your implementation with ours.) We will build a RPN (Reverse Polish Notation) calculator as a Seaside application that uses a simple stack machine as its underlying model. Furthermore, the Seaside interface will let us toggle between two displays — one which just shows us the current value on top of the stack, and the other which shows us the complete state of the stack. The calculator with the two display options is shown in Figure \(\PageIndex{1}\).

    RPN calculator and stack machine.
    Figure \(\PageIndex{1}\): RPN calculator and its stack machine.

    We begin by implementing the stack machine and its tests.

    First, Define a new class called MyStackMachine with an instance variable contents initialized to a new OrderedCollection.

    MyStackMachine >> initialize
        super initialize.
        contents := OrderedCollection new.
    

    The stack machine should provide operations to push: and pop values, view the top of the stack, and perform various arithmetic operations to add, subtract, multiply and divide the top values on the stack.

    Write some tests for the stack operations and then implement these operations. Here is a sample test:

    MyStackMachineTest >> testDiv
        stack
            push: 3;
            push: 4;
            div.
        self assert: stack size = 1.
        self assert: stack top = (4/3).
    

    You might consider using some helper methods for the arithmetic operations to check that there are two numbers on the stack before doing anything, and raising an error if this precondition is not fulfilled. (It’s a good idea to use Object>>assert: to specify the preconditions for an operation. This method will raise an AssertionFailure if the user tries to use the stack machine in an invalid state.) If you do this, most of your methods will just be one or two lines long.

    You might also consider implementing MyStackMachine>>printOn: to make it easier to debug your stack machine implementation with the help of an object inspector. (Hint: just delegate printing to the contents variable.)

    Complete the MyStackMachine by writing operations dup (push a duplicate of the top value onto the stack), exch (exchange the top two values), and rotUp (rotate the entire stack contents up — the top value will move to the bottom).

    Now we have a simple stack machine implementation. We can start to implement the Seaside RPN Calculator.

    We will make use of 5 classes:

    1. MyRPNWidget — this should be an abstract class that defines the common CSS style sheet for the application, and other common behavior for the com- ponents of the RPN calculator. It is a subclass of WAComponent and the direct superclass of the following four classes.
    2. MyCalculator — this is the root component. It should register the application (on the class side), it should instantiate and render its subcomponents, and it should register any state for backtracking.
    3. MyKeypad - this displays the keys that we use to interact with the calculator.
    4. MyDisplay — this component displays the top of the stack and provides a button to call another component to display the detailed view.
    5. MyDisplayStack — this component shows the detailed view of the stack and provides a button to answer back. It is a subclass of MyDisplay.

    Create a package MyCalculator, and define MyRPNWidget.

    Define the common style for the application.

    Here is a minimal CSS for the application. You can make it more fancy if you like.

    MyRPNWidget >> style
        ^ 'table.keypad { float: left; }
    td.key {
        border: 1px solid grey;
        background: lightgrey;
        padding: 4px;
        text-align: center;
    }
    table.stack { float: left; }
    td.stackcell {
        border: 2px solid white;
        border-left-color: grey;
        border-right-color: grey;
        border-bottom-color: grey;
        padding: 4px;
        text-align: right;
    }
    td.small { font-size: 8pt; }'
    

    Define MyCalculator to be a root component and register itself as an application (i.e., implement canBeRoot and initialize on the class side).

    Implement MyCalculator>>renderContentOn: to render something trivial (such as its name), and verify that the application runs in a browser.

    MyCalculator is responsible for instantiating MyStackMachine, MyKeypad and MyDisplay.

    Define MyKeypad and MyDisplay as subclasses of MyRPNWidget.

    All three components will need access to a common instance of the stack machine, so define the instance variable stackMachine and an initialization method setMyStackMachine: in the common parent, MyRPNWidget. Add instance variables keypad and display to MyCalculator and initialize them in MyCalculator>>initialize. (Don’t forget to send super initialize!)

    Pass the shared instance of the stack machine to the keypad and the display in the same initialize method.

    Implement MyCalculator>>renderContentOn: to simply render in turn the keypad and the display. To correctly display the subcomponents, you must implement MyCalculator>>children to return an array with the keypad and the display. Implement placeholder rendering methods for the keypad and the display and verify that the calculator now displays its two subcomponents.

    Now we will change the implementation of the display to show the top value of the stack.

    Use a table with class keypad containing a row with a single table data cell with class stackcell.

    Change the rendering method of the keypad to ensure that the number 0 is pushed on the stack in case it is empty. (Define and use MyKeypad>>ensureStackMachineNotEmpty.) Also make it display an empty table with class keypad. Now the calculator should display a single cell containing the value 0. If you toggle the halos, you should see something like this: (Figure \(\PageIndex{2}\))

    Displaying the top of the stack.
    Figure \(\PageIndex{2}\): Displaying the top of the stack.

    Now let’s implement an interface to interact with the stack.

    First, define the following helper methods, which will make it easier to script the interface:

    MyKeypad >> renderStackButton: text callback: aBlock colSpan: anInteger
            on: html
        html tableData
            class: 'key';
            colSpan: anInteger;
            with:
                [ html anchor
                    callback: aBlock;
                    with: [ html html: text ]]
    
    MyKeypad >> renderStackButton: text callback: aBlock on: html
        self
            renderStackButton: text
            callback: aBlock
            colSpan: 1
            on: html
    

    We will use these two methods to define the buttons on the keypad with appropriate callbacks. Certain buttons may span multiple columns, but the default is to occupy just one column.

    Use the two helper methods to script the keypad as follows.

    (Hint: start by getting the digit and Enter keys working, then the arithmetic operators.)

    MyKeypad >> renderContentOn: html
        self ensureStackMachineNotEmpty.
        html table
            class: 'keypad';
            with: [
                html tableRow: [
                    self renderStackButton: '+' callback: [self stackOp: #add] on:
                        html.
                    self renderStackButton: '–' callback: [self stackOp:
                        #min] on: html.
                    self renderStackButton: '×' callback: [self stackOp:
                        #mul] on: html.
                    self renderStackButton: '÷' callback: [self stackOp:
                        #div] on: html.
                    self renderStackButton: '±' callback: [self stackOp:
                        #neg] on: html ].
                html tableRow: [
                    self renderStackButton: '1' callback: [self type: '1'] on:
                        html.
                    self renderStackButton: '2' callback: [self type: '2'] on:
                        html.
                    self renderStackButton: '3' callback: [self type: '3'] on:
                        html.
                    self renderStackButton: 'Drop' callback: [self
                        stackPopIfNotEmpty]
                    colSpan: 2 on: html ].
    " and so on ... "
                html tableRow: [
                    self renderStackButton: '0' callback: [self type: '0']
                        colSpan: 2 on: html.
                    self renderStackButton: 'C' callback: [self stackClearTop] on:
                        html.
                    self renderStackButton: 'Enter'
                        callback: [self stackOp: #dup. self setClearMode]
                    colSpan: 2 on: html ]]
    

    Check that the keypad displays properly. If you try to click on the keys, however, you will find that the calculator does not work yet.

    Implement MyKeypad>>type: to update the top of the stack by appending the typed digit. You will need to convert the top value to a string, update it, and convert it back to an integer, something like this:

    MyKeypad >> type: aString
        stackMachine push: (self stackPopTopOrZero asString, aString)
            asNumber.
    

    The two methods stackPopTopOrZero and stackPopIfNotEmpty are used to guard against operating on an empty stack.

    MyKeypad >> stackPopTopOrZero
        ^ stackMachine isEmpty
            ifTrue: [ 0 ]
            ifFalse: [ stackMachine pop ]
    
    MyKeypad >> stackPopIfNotEmpty
        stackMachine isEmpty
            ifFalse: [ stackMachine pop ]
    

    Now when you click on the digit keys the display should be updated. (Be sure that MyStackMachine>>pop returns the value popped, or this will not work!)

    Next, we must implement MyKeypad>>stackOp:. Something like this will do the trick:

    MyKeypad >> stackOp: op
        [ stackMachine perform: op ] on: AssertionFailure do: [ ].
    

    The point is that we are not sure that all operations will succeed. For example, addition will fail if we do not have two numbers on the stack. For the moment we can just ignore such errors. If we are feeling more ambitious later on, we can provide some user feedback in the error handler block.

    The first version of the calculator should be working now. Try to enter some numbers by pressing the digit keys, hitting Enter to push a copy of the current value, and entering + to sum the top two values.

    You will notice that typing digits does not behave the way you might expect. Actually the calculator should be aware of whether you are typing a new number, or appending to an existing number.

    Adapt MyKeypad>>type: to behave differently depending on the current typing mode.

    Introduce an instance variable mode which takes on one of the three values: the symbol #typing (when you are typing), #push (after you have performed a calculator operation and typing should force the top value to be pushed), or #clear (after you have performed Enter and the top value should be cleared before typing). The new type: method might look like this:

    MyKeypad >> type: aString
        self inPushMode ifTrue: [
            stackMachine push: stackMachine top.
            self stackClearTop ].
        self inClearMode ifTrue: [ self stackClearTop ].
        stackMachine push: (self stackPopTopOrZero asString, aString)
            asNumber.
    

    Typing might work better now, but it is still frustrating not to be able to see what is on the stack.

    Define MyDisplayStack as a subclass of MyDisplay.

    Add a button to the rendering method of MyDisplay which will call a new instance of MyDisplayStack. You will need an HTML anchor that looks something like this:

    html anchor
        callback: [ self call: (MyDisplayStack new setMyStackMachine:
            stackMachine)];
        with: 'open'
    

    The callback will cause the current instance of MyDisplay to be temporarily replaced by a new instance of MyDisplayStack whose job it is to display the complete stack. When this component signals that it is done (i.e., by sending self answer), then control will return to the original instance of MyDisplay.

    Define the rendering method of MyDisplayStack to display all of the values on the stack.

    (You will either need to define an accessor for the stack machine’s contents or you can define MyStackMachine>>do: to iterate over the stack values.) The stack display should also have a button labelled close whose callback will simply perform self answer.

    html anchor
        callback: [ self answer];
        with: 'close'
    

    Now you should be able to open and close the stack while you are using the calculator.

    There is, however, one thing we have forgotten. Try to perform some operations on the stack. Now use the Back button of your browser and try to perform some more stack operations. For example, open the stack, type 1, Enter twice and +. The stack should display 2 and 1. Now hit the Back button. The stack now shows three times 1 again. Now if you type +, the stack shows 3. Backtracking is not yet working.

    Implement MyCalculator>>states to return an array with the contents of the stack machine.

    Check that backtracking now works correctly.


    This page titled 14.7: A Complete Tutorial Example is shared under a CC BY-SA 3.0 license and was authored, remixed, and/or curated by via source content that was edited to the style and standards of the LibreTexts platform; a detailed edit history is available upon request.