#
# 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
