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