# # RPN calculator, bsy@play, 11/97. Took about 1.75 hours to type in and # debug. # # This version uses function pointers for every operation. The stack is # an explicit global data structure, accessed using push / pop functions. # All normal calculator functions are necessarily non-leaf functions (except # for "off", which does not use the stack at all). # # The equivalent C code is: # # #define NELT 4 # int calc_stack[NELT]; # int cur_base; # char buf[34]; # # int pop(void) # { # int val, *sp; # # val = calc_stack[0]; # for (sp = calc_stack; sp < calc_stack + NELT - 1; sp++) { # sp[0] = sp[1]; # } # return val; # } # # void push(int val) # { # int *sp; # # for (sp = calc_stack + NELT - 2; sp >= calc_stack; --sp) { # sp[1] = sp[0]; # } # calc_stack[0] = val; # } # # void f_print_all(char *buf) # { # int *sp; # # for (sp = calc_stack + NELT - 1; sp >= calc_stack; --sp) { # putnum_nl(*sp); # } # } # # void f_print_top(char *buf) # { # int val = pop(); # putnum_nl(val); # push(val); # } # # void f_times(char *buf) # { # push(pop() * pop()); # } # # [ ... ] # # void (*ftbl[])(char *) = { # f_times, [ ... ] , f_print_top, # }; # # void main(void) # { # void (*fptr)(char *); # # for (;;) { # read_str(buf,34); # if ('*' <= buf[0] && buf[0] <= 'p') # fptr = ftbl[buf[0]-'*']; # else # fptr = f_bad; # (*fptr)(buf); # } # } # .data calc_stack: .word 0,0,0,0 calc_stack_end: .word 0 # sentinel cur_base: .word 10 .text # # leaf: int pop(void) returns topmost element of the # calculator stack, shifting everything down. # pop: lw $v0,calc_stack la $t0,calc_stack la $t1,calc_stack_end - 4 b pop_loop_test pop_loop: lw $t2,4($t0) sw $t2,0($t0) add $t0,$t0,4 pop_loop_test: bne $t0,$t1,pop_loop jr $ra # # leaf: void push(int) pushes value into the calculator # stack # push: la $t0,calc_stack la $t1,calc_stack_end - 8 b push_loop_test push_loop: lw $t2,0($t1) sw $t2,4($t1) sub $t1,$t1,4 push_loop_test: ble $t0,$t1,push_loop sw $a0,0($t0) jr $ra # # this function must know about the stack # # frame: s0,s1,a0,a1,ra,fp # f_print_all: sub $sp,$sp,24 sw $fp,4($sp) add $fp,$sp,24 sw $ra,-16($fp) sw $s0,0($fp) sw $s1,-4($fp) sw $a0,-8($fp) sw $a1,-12($fp) la $s0,calc_stack la $s1,calc_stack_end - 4 lw $a1,cur_base b print_loop_test print_loop: lw $a0,0($s1) jal putnum_nl sub $s1,$s1,4 print_loop_test: ble $s0,$s1,print_loop lw $s0,0($fp) lw $s1,-4($fp) lw $a0,-8($fp) lw $a1,-12($fp) lw $ra,-16($fp) lw $fp,-20($fp) add $sp,$sp,24 jr $ra # # We omit knowledge of the stack implementation here by # incurring the expense of doing push(pop()) # # frame: a0, a1, ra, fp # f_print_top: sub $sp,$sp,16 sw $fp,4($sp) add $fp,$sp,16 sw $ra,-8($fp) sw $a0,0($fp) sw $a1,-4($fp) jal pop move $a0,$v0 lw $a1,cur_base jal putnum_nl jal push lw $a0,0($fp) lw $a1,-4($fp) lw $ra,-8($fp) lw $fp,-12($fp) add $sp,$sp,16 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. # # frame: a0,tmp,ra,fp # putnum: sub $sp,$sp,16 sw $fp,4($sp) add $fp,$sp,16 sw $ra,-8($fp) sw $a0,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 $a0,0($fp) lw $ra,-8($fp) lw $fp,-12($fp) add $sp,$sp,16 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) sw $a0,8($sp) bge $a0,$zero,putnum_positive 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 $a0,8($sp) lw $ra,4($sp) add $sp,$sp,8 jr $ra # # f_plus, f_minus, f_times, f_div, f_exp are all leaf # functions; they ignore their sole argument. # f_plus: sub $sp,$sp,16 sw $fp,4($sp) add $fp,$sp,16 sw $ra,-8($fp) sw $a0,-4($fp) jal pop sw $v0,0($fp) jal pop lw $t1,0($fp) add $a0,$v0,$t1 jal push lw $a0,-4($fp) lw $ra,-8($fp) move $t0,$fp lw $fp,-12($fp) move $sp,$t0 jr $ra f_minus: sub $sp,$sp,16 sw $fp,4($sp) add $fp,$sp,16 sw $ra,-8($fp) sw $a0,-4($fp) jal pop sw $v0,0($fp) jal pop lw $t1,0($fp) sub $a0,$v0,$t1 jal push lw $a0,-4($fp) lw $ra,-8($fp) move $t0,$fp lw $fp,-12($fp) move $sp,$t0 jr $ra f_times: sub $sp,$sp,16 sw $fp,4($sp) add $fp,$sp,16 sw $ra,-8($fp) sw $a0,-4($fp) jal pop sw $v0,0($fp) jal pop lw $t1,0($fp) mul $a0,$v0,$t1 jal push lw $a0,-4($fp) lw $ra,-8($fp) move $t0,$fp lw $fp,-12($fp) move $sp,$t0 jr $ra f_div: sub $sp,$sp,16 sw $fp,4($sp) add $fp,$sp,16 sw $ra,-8($fp) sw $a0,-4($fp) jal pop bne $v0,$zero,f_div_okay move $a0,$v0 jal push .data f_div_err_msg: .asciiz "Division by zero\n" .text la $a0,f_div_err_msg li $v0,4 syscall b f_div_done f_div_okay: sw $v0,0($fp) jal pop lw $t1,0($fp) div $a0,$v0,$t1 jal push f_div_done: lw $a0,-4($fp) lw $ra,-8($fp) move $t0,$fp lw $fp,-12($fp) move $sp,$t0 jr $ra f_exp: sub $sp,$sp,16 sw $fp,4($sp) add $fp,$sp,16 sw $ra,-8($fp) sw $a0,-4($fp) jal pop bge $v0,$zero,f_exp_okay move $a0,$v0 jal push .data f_exp_err_msg: .asciiz "Negative exponent\n" .text la $a0,f_exp_err_msg li $v0,4 syscall b f_exp_done f_exp_okay: sw $v0,0($fp) jal pop lw $t0,0($fp) move $t1,$v0 li $v0,1 b f_exp_loop_test f_exp_loop: andi $t2,$t0,1 beq $t2,$zero,f_exp_bit_not_set mul $v0,$v0,$t1 f_exp_bit_not_set: mul $t1,$t1,$t1 srl $t0,$t0,1 f_exp_loop_test: bne $t0,$zero,f_exp_loop move $a0,$v0 jal push f_exp_done: lw $a0,-4($fp) lw $ra,-8($fp) move $t0,$fp lw $fp,-12($fp) move $sp,$t0 jr $ra # # leaf: int decode(int base,char *str,int *num) # returns success if no illegal digit # # uses $t0,$t1. # decode: li $v0,0 # initial value move $t0,$a1 dloop: lb $t1,0($t0) # walk through string add $t0,$t0,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 # # f_num(char *buf) pushes converted number onto the # stack if conversion succeeds. # # frame contains: a0,a1,a2,num,$ra,$fp # f_num: sub $sp,$sp,24 sw $fp,4($sp) add $fp,$sp,24 sw $ra,-16($fp) sw $a0,0($fp) sw $a1,-4($fp) sw $a2,-8($fp) move $a1,$a0 # str lw $a0,cur_base sub $a2,$fp,12 jal decode beq $v0,1,okay_num # handle special A case. lw $t0,0($fp) lb $t1,($t0) bne $t1,0x41,bad_num # A special case lb $t1,1($t0) bne $t1,0xa,bad_num # is "A" "newline" li $a0,10 jal push b f_num_done .data bad_num_str: .asciiz "Illegal character in input number\n" .text bad_num: la $a0,bad_num_str li $v0,4 syscall b f_num_done okay_num: lw $a0,-12($fp) jal push f_num_done: lw $a0,0($fp) lw $a1,-4($fp) lw $a2,-8($fp) lw $ra,-16($fp) lw $fp,-20($fp) add $sp,$sp,24 jr $ra f_set_base: sub $sp,$sp,4 sw $ra,4($sp) jal pop blt $v0,2,f_set_base_bad blt $v0,37,f_set_base_okay .data f_set_base_errmsg: .asciiz "Illegal base\n" .text f_set_base_bad: move $a0,$v0 jal push li $v0,4 la $a0,f_set_base_errmsg syscall b f_set_base_done f_set_base_okay: sw $v0,cur_base f_set_base_done: lw $ra,4($sp) add $sp,$sp,4 jr $ra .data bad_cmd_str: .asciiz "Illegal command\n" .text f_bad: la $a0,bad_cmd_str li $v0,4 syscall jr $ra f_off: li $v0,10 syscall # # Function table. All functions take string buf as argument. # .data ftbl: .word f_times # * 0x2a .word f_plus # + .word f_bad # , .word f_minus # - .word f_bad # . .word f_div # / .word f_num # 0 0x30 .word f_num # 1 .word f_num # 2 .word f_num # 3 .word f_num # 4 .word f_num # 5 .word f_num # 6 .word f_num # 7 .word f_num # 8 0x38 .word f_num # 9 .word f_bad # : .word f_bad # ; .word f_bad # < .word f_bad # = .word f_bad # > .word f_bad # ? .word f_bad # @ 0x40 .word f_num # A .word f_num # B .word f_num # C .word f_num # D .word f_num # E .word f_num # F .word f_num # G .word f_num # H 0x48 .word f_num # I .word f_num # J .word f_num # K .word f_num # L .word f_num # M .word f_num # N .word f_num # O .word f_num # P 0x50 .word f_num # Q .word f_num # R .word f_num # S .word f_num # T .word f_num # U .word f_num # V .word f_num # W .word f_num # X 0x58 .word f_num # Y .word f_num # Z .word f_bad # [ .word f_bad # \ .word f_bad # ] .word f_exp # ^ .word f_bad # _ .word f_bad # ' 0x60 .word f_print_all # a .word f_set_base # b .word f_bad # c .word f_bad # d .word f_bad # e .word f_bad # f .word f_bad # g .word f_bad # h 0x68 .word f_bad # i .word f_bad # j .word f_bad # k .word f_bad # l .word f_bad # m .word f_bad # n .word f_off # o .word f_print_top # p 0x70 buf: .space 34 .text main: sub $sp,$sp,16 sw $fp,4($sp) add $fp,$sp,16 sw $ra,-8($fp) sw $a0,0($fp) sw $a1,-4($fp) mloop: la $a0,buf li $a1,34 li $v0,8 syscall la $a0,buf lb $a1,0($a0) subu $a1,$a1,0x2a bltu $a1,0x47,okay_tbl la $a1,f_bad b do_cmd okay_tbl: sll $a1,$a1,2 lw $a1,ftbl($a1) do_cmd: jalr $a1 b mloop lw $a1,-4($fp) lw $a0,0($fp) lw $ra,-8($fp) lw $fp,-12($fp) add $sp,$sp,16 jr $ra