22C:18, Lecture 25, Summer 1997

Douglas W. Jones
University of Iowa Department of Computer Science

Continuing to Work the Example

Having illustrated how to declare, initialize and print the calculator's stack, we can now go about writing the body of the calculate routine for our calculator example. This was shown in Pascal in the previous lecture as:

        procedure calculate(var line: string);
        var i: 0..stringsize;
        begin
            i := 0;
            while s[i] <> endofstring do begin
                case s[i] of
                'E':begin
                        sp := sp + 1;
                        stack[sp] := 0;
                    end;
                '0'..'9':
                    stack[sp] := stack[sp] * 10
                                 + (ord(s[i]) - ord('0'));
                '+':begin
                        stack[sp-1] := stack[sp-1] + stack[sp];
                        sp := sp - 1;
                    end;
                '-':begin
                        stack[sp-1] := stack[sp-1] - stack[sp];
                        sp := sp - 1;
                    end;
                end;
                i := i + 1;
            end;
        end;
We can translate the outer loop of this procedure to assembly language trivially:
CALCULATE:
	; on entry R3 = pointer to line

	STORES	R1,R2		; receiving sequence

	LOAD	R4,PRPN		; global setup of R4,5
	LOAD	R5,R4,RPNSP	; get the stack pointer

CALLP:
	LOADS	R6,R3		; get one character
	EXTB	R6,R6,R3
	BZS	CALQT		;   quit if character is null

	; body of loop not shown
	;   R3 = pointer into line
	;   R4 = pointer to RPN record
	;   R5 = copy of RPNSP
	;   R6 = character from input line
	
	ADDSI	R3,1		; bump line pointer
	BR	CALLP		; process next character
CALQT:
	STORE	R5,R4,RPNSP	; put back the stack pointer
	LOADS	R1,R2		; return sequence
	JUMPS	R1
With this framework in place, we can descend into the loop body, and borrow the cookbook code for a case/select statement from a previous lecture; this cookbook code includes bounds checks on the selector before using the jump table:
        ; body of loop
        ;   R3 = pointer into line
        ;   R4 = pointer to RPN record
        ;   R5 = copy of RPNSP
        ;   R6 = character from input line

	CMPI	R6," "		; check bounds on character
	BLT	CALER		;   error if under blank
	CMPI	R6,"z"
	BGT	CALER		;   error if over "z"

	LEA	R1,CALJT-(" "<<2); get address of jumptab[0]
	ADDS	R1,R6,2		; compute address jumptab[R6]
	LOADS	R1,R1		; get the contents of jumptab[R6]
	JUMPS	R1		; use it!

	ALIGN	4
CALJT:
	W       CALSP,CALER,CALER,CALER,CALER,CALER,CALER,CALER ;  !"#$%&'
        W       CALER,CALER,CALER,CALPL,CALER,CALMI,CALER,CALER ; ()*+,-./
        W       CALDG,CALDG,CALDG,CALDG,CALDG,CALDG,CALDG,CALDG ; 01234567
        W       CALDG,CALDG,CALER,CALER,CALER,CALER,CALER,CALER ; 89:;<=>?
        W       CALER,CALER,CALER,CALER,CALER,CALEN,CALER,CALER ; @ABCDEFG
        W       CALER,CALER,CALER,CALER,CALER,CALER,CALER,CALER ; HIJKLMNO
        W       CALER,CALER,CALER,CALER,CALER,CALER,CALER,CALER ; PQRSTUVW
        W       CALER,CALER,CALER,CALER,CALER,CALER,CALER,CALER ; XYZ[\]^_
        W       CALER,CALER,CALER,CALER,CALER,CALEN,CALER,CALER ; `abcdefg
        W       CALER,CALER,CALER,CALER,CALER,CALER,CALER,CALER ; hijklmno
        W       CALER,CALER,CALER,CALER,CALER,CALER,CALER,CALER ; pqrstuvw
        W       CALER,CALER,CALER                               ; xyz

	; the easy case: blank
CALSP:
	BR	CALEC

	; other case: not shown here
CALEC:
In developing the calculator, it would be sensible to build dummy code for each case and then flesh things out gradually, testing the calculator after each new command added. Initially, all of the cases could be made equivalent to CALSP -- the label on the code for handling blank. Then, cases could be added. For example, consider the following code for enter and digit:
	; case enter
CALEN:
	ADDSI	R5,4		; push ...
	STORES	R0,R5		; ... zero
	BR	CALEC

	; case digit
CALDG:
	ADDI	R6,-"0"		; convert digit to integer
	LOADS	R7,R5		; get old top of stack
	SL	R7,1		; times 10
	ADDSL	R7,R6,1		; plus the new digit
	STORES	R7,R5		; store new top of stack
	BR	CALEC
This code will work correctly for correct input to the calculator, but will produce corrupt results for incorrect input to the calculator. The reason is that it does nothing about the possibility of stack overflow or underflow. To deal with these, it should be rewritten to test for these conditions:
        ; case enter
CALEN:
	LEA	R1,R4,RPNSTK+STACKSIZE-4  ; get end of stack
	CMP	R5,R1		; check for stack full
	BGE	CALER		;   error if so
        ADDSI   R5,4            ; push ...
        STORES  R0,R5           ; ... zero
        BR      CALEC

        ; case digit
CALDG:
	LEA	R1,R4,RPNSTK	; get start of stack
	CMP	R5,R1		; check for stack empty
	BLT	CALER		;   error if so
        ADDI    R6,-"0"         ; convert digit to integer
        LOADS   R7,R5           ; get old top of stack
        SL      R7,1            ; times 10
        ADDSL   R7,R6,1         ; plus the new digit
        STORES  R7,R5           ; store new top of stack
        BR      CALEC
This leaves an unanswered question: What should happen if an error is detected. All of the error checks in the above have been written as branches or jumps to CALER, and nothing has been said about what happens when control reaches CALER. This is a high-level design problem, not an assembly language issue!