# # RPN Calculator. bsy@play, 10/97. Took about 45 minutes to type in # and debug. Another 20 minutes to add comments. # # The specifications ask for a 4-deep stack RPN calculator which # implements: +, -, *, /, ^ binary operators, b (set-base), p (print # top of stack), a (print all of stack) commands, with functions for # each of these calculator operations. The set-base operation # consumes the top of the stack, setting the current input/output base # for all numbers. If [xyzw] is the stack, with x being the top # element, then the results of the operations are defined as: # # + [(y+x)zww] # - [(y-x)zww] # * [(y*x)zww] # / [(y/x)zww] # ^ [(y^x)zww] # b [yzww] # A valid number entry (call it n) causes the stack to become [nxyz] # # Invalid digit(s) for the current base causes an error message. # Division by zero causes an error message. Raising to a negative # exponent causes an error message. # # The equivalent C code is: # # void main(void) # { # int base = 10, s0 = 0, s1 = 0, s2 = 0, s3 = 0, val, v0; # # for (;;) { # read_str(buf,34); # if (('0' <= buf[0] && buf[0] <= '9') || # 'A' <= buf[0] && buf[0] <= 'Z')) { # if (decode(buf,base,&val)) { # s3 = s2; s2 = s1; s1 = s0; s0 = val; # } else { # if (buf[0] == 'A' && buf[1] == '\n') { # s3 = s2; s2 = s1; s1 = s0; s0 = 10; # } else { # put_str("Illegal character input number\n"); # } # } # } else if (buf[0] == '+') { # v0 = f_add(s0,s1); # s0 = v0; s1 = s2; s2 = s3; # } else if (buf[0] == '-') { # v0 = f_minus(s0,s1); # s0 = v0; s1 = s2; s2 = s3; # } else if (buf[0] == '*') { # if (s0 == 0) { # put_str("Division by zero\n"); # } else { # v0 = f_times(s0,s1); # s0 = v0; s1 = s2; s2 = s3; # } # } else if (buf[0] == '/') { # v0 = f_div(s0,s1); # s0 = v0; s1 = s2; s2 = s3; # } else if (buf[0] == '^') { # if (s0 < 0) { # put_str("Negative exponent!\n"); # } else { # v0 = f_exp(s0,s1); # s0 = v0; s1 = s2; s2 = s3; # } # } else if (buf[0] == 'p') { # putnum_nl(s0,base); # } else if (buf[0] == 'a') { # putnum_nl(s3,base); # putnum_nl(s2,base); # putnum_nl(s1,base); # putnum_nl(s0,base); # } else if (buf[0] == 'b') { # if (s0 >= 2 && s0 < 37) { # base = s0; s0 = s1; s1 = s2; # } else { # put_str("Illegal base\n"); # } # } else if (buf[0] == 'q') { # return; # } else { # put_str("Illegal command\n"); # } # } # } # # The calculator stack is kept in registers s0 through s3, with s0 the top (x). # The current base is in s4. val is on the stack. # .data buf: .space 34 # max length needed, base 2, 32 characters # plus newline and null termination .text main: sub $sp,$sp,32 sw $fp,4($sp) add $fp,$sp,32 sw $ra,0($fp) sw $s0,-4($fp) sw $s1,-8($fp) sw $s2,-12($fp) sw $s3,-16($fp) sw $s4,-20($fp) # val -24($fp) # fp -28($fp) == 4($sp) move $s0,$zero move $s1,$zero move $s2,$zero move $s3,$zero li $s4,10 mloop: # read in a line -- either a number or a command. la $a0,buf li $a1,34 li $v0,8 syscall la $t0,buf lb $t1,($t0) blt $t1,0x30,not_digit_or_cap # < 0 bgt $t1,0x5a,not_digit_or_cap # > Z ble $t1,0x39,okay_digit_or_cap # <= 9 blt $t1,0x41,bad_num # < A okay_digit_or_cap: add $a2,$sp,8 move $a1,$t0 # str move $a0,$s4 # base jal decode beq $v0,1,okay_num # handle special A case. la $t0,buf lb $t1,($t0) bne $t1,0x41,bad_num # A special case lb $t1,1($t0) bne $t1,0xa,bad_num # is "A" "newline" move $s3,$s2 move $s2,$s1 move $s1,$s0 li $s0,10 b mloop .data bad_num_str: .asciiz "Illegal character in input number\n" .text bad_num: la $a0,bad_num_str li $v0,4 syscall b mloop okay_num: move $s3,$s2 move $s2,$s1 move $s1,$s0 lw $s0,8($sp) b mloop not_digit_or_cap: bne $t1,'+',not_plus move $a0,$s0 move $a1,$s1 jal f_plus move $s0,$v0 move $s1,$s2 move $s2,$s3 b mloop not_plus: bne $t1,'-',not_minus move $a0,$s0 move $a1,$s1 jal f_minus move $s0,$v0 move $s1,$s2 move $s2,$s3 b mloop not_minus: bne $t1,'*',not_times move $a0,$s0 move $a1,$s1 jal f_times move $s0,$v0 move $s1,$s2 move $s2,$s3 b mloop not_times: bne $t1,'/',not_div bne $s0,$zero,okay_div .data div_zero: .asciiz "Division by zero\n" .text la $a0,div_zero li $v0,4 syscall b mloop okay_div: move $a0,$s0 move $a1,$s1 jal f_div move $s0,$v0 move $s1,$s2 move $s2,$s3 b mloop not_div: bne $t1,'^',not_exp bge $s0,$zero,do_exp .data neg_exp: .asciiz "Negative exponent!\n" .text la $a0,neg_exp li $v0,4 syscall b mloop do_exp: move $a0,$s0 move $a1,$s1 jal f_exp move $s0,$v0 move $s1,$s2 move $s2,$s3 b mloop not_exp: # unary cmds bne $t1,'p',not_print move $a0,$s0 move $a1,$s4 jal putnum_nl b mloop not_print: bne $t1,'a',not_all_print move $a0,$s3 move $a1,$s4 jal putnum_nl move $a0,$s2 move $a1,$s4 jal putnum_nl move $a0,$s1 move $a1,$s4 jal putnum_nl move $a0,$s0 move $a1,$s4 jal putnum_nl b mloop not_all_print: bne $t1,'b',not_base blt $s0,2,bad_base blt $s0,37,okay_base .data bad_base_msg: .asciiz "Illegal base\n" .text bad_base: la $a0,bad_base_msg li $v0,4 syscall b mloop okay_base: move $s4,$s0 move $s0,$s1 move $s1,$s2 move $s2,$s3 b mloop not_base: beq $t1,'o',turn_off .data bad_cmd: .asciiz "Illegal command\n" .text la $a0,bad_cmd li $v0,4 syscall b mloop turn_off: lw $ra,0($fp) lw $s0,-4($fp) lw $s1,-8($fp) lw $s2,-12($fp) lw $s3,-16($fp) lw $s4,-20($fp) lw $fp,4($sp) add $sp,$sp,32 li $v0,0 # main returns 0 as exit status jr $ra # Let n be the input number in base b, where # n = sum_{i=0}{k} a_i b^i # we read in one digit at a time (a_j), and what we # compute is the sequence of values: # n_0 = a_k # n_1 = n_0 b + a_{k-1} = a_k b + a_{k-1} # n_2 = n_1 b + a_{k-2} = a_k b^2 + a_{k-1} b + a_{k-2} # ... # n_k = n_{k-1} b + a_0 = n. # # int decode(int base,char *str,int *ip) -- returns 1 to mean success # with the decoded number stored in *ip. # # uses scratch registers t0, t1. decode: li $v0,0 # initial value dloop: lb $t1,0($a1) # walk through string add $a1,$a1,1 beq $t1,0xa,decode_done beq $t1,0,decode_done blt $t1,0x30,bad_char bgt $t1,0x39,not_digit # # The conversion from ascii character to digit # values may be made faster by using a table # lookup as well, at the expense of a little # more memory (256 bytes). # sub $t1,$t1,0x30 # 0...9: $t1 has digit value j got_new_digit not_digit: blt $t1,0x41,bad_char # capital A bgt $t1,0x5a,bad_char # through Z sub $t1,$t1,0x37 # -0x41+a = -0x37 got_new_digit: bge $t1,$a0,bad_char mul $v0,$v0,$a0 add $v0,$v0,$t1 j dloop bad_char: li $v0,0 # error indication jr $ra decode_done: sw $v0,0($a2) li $v0,1 jr $ra # # recursive putnum. Input $a0 is the value, $a1 is the base. # output is the digit string in base $a1 of the value $a0. # Uses scratch registers t0 and t1. Treats numbers as unsigned. # # base case: it works for $a0 < $a1. # divu $a0,$a1 gives $lo == 0 when $a0 < $a1, so we branch to no_recurse. # output is by table lookup, so the output is the correct digit string. # inductive step: assume it works for $a0 < $a1^k # we prove that it works for $a1^k <= $a0 < $a1^{k+1} # with $a0 in that range, the divu $a0,$a1 gives d_0 in $hi, where # $a0 = sum_{i=0}^{k} d_i $a1^i, and gives floor($a0/$a1) in $lo. # $lo = sum_{i=0}{k-1} d_{i+1} $a1^i, so it is less than $a1^k, # and we can recursively output the digits d_{k}...d_1 using a recursive # call to putnum, with $lo as the new $a0, and we need only output the # remainder $hi as before to complete the output digit sequence. # putnum: sub $sp,$sp,12 sw $fp,4($sp) add $fp,$sp,12 sw $ra,0($fp) divu $a0,$a1 mflo $t0 # result of division mfhi $t1 # remainder beq $t0,$zero,no_recurse sw $t1,-4($fp) # save remainder on stack frame move $a0,$t0 jal putnum # call putnum(floor($a0/$a1),$a1) lw $t1,-4($fp) .data digit_table: .ascii "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" dbuf: .asciiz "1" .text no_recurse: lb $t1,digit_table($t1) # simple table lookup for digit sb $t1,dbuf la $a0,dbuf # output the digit li $v0,4 syscall lw $ra,0($fp) lw $fp,4($sp) add $sp,$sp,12 jr $ra f_plus: addu $v0,$a1,$a0 jr $ra f_minus: subu $v0,$a1,$a0 jr $ra f_times: mul $v0,$a1,$a0 jr $ra f_div: divu $v0,$a1,$a0 jr $ra # precondition: a0 >= 0 # # v0 = a1^a0 # # f_exp(int a0,int a1) /* a0 >= 0 */ # { # int v0; # for (v0 = 0; a0; a0 >>= 1) { # if (a0 & 1) v0 = v0 * a1; # a1 = a1 * a1; # } # return v0; # } f_exp: li $v0,1 j f_exp_test f_exp_loop: and $t0,$a0,1 # if (a0 & 1) { beq $t0,$zero,f_exp_square # mul $v0,$v0,$a1 # v0 = v0 * a1 # } f_exp_square: mul $a1,$a1,$a1 # a1 = a1 * a1; srl $a0,$a0,1 # a0 >>= 1 f_exp_test: bne $a0,$zero,f_exp_loop jr $ra # # putnum_nl -- instead of unsigned, we print a signed number: we test # to see if it is negative, if so print the negative sign. Then we # take the absolute value of the number as an unsigned value, and call # putnum. We follow with a newline, so numbers are on a line by themselves. # .data nl: .asciiz "\n" minus_sign: .asciiz "-" .text putnum_nl: sub $sp,$sp,8 sw $ra,4($sp) bge $a0,$zero,putnum_positive sw $a0,8($sp) la $a0,minus_sign li $v0,4 syscall lw $a0,8($sp) subu $a0,$zero,$a0 putnum_positive: jal putnum la $a0,nl li $v0,4 syscall putnum_done: lw $ra,4($sp) add $sp,$sp,8 jr $ra