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 R1With 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 CALECThis 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!