Kjetil's Information Center: A Blog About My Projects

RPN FPU Calculator

Just for fun, I wrote a RPN (Reverse Polish Notation) calculator in x86 assembler. It uses the FPU (Floating-Point Unit) for all the calculations, and some common C-library calls for input/output.

It only supports the basic arithmetic operations like addition, subtraction, multiplication and division, and only operates on integers. I have tried to emulate the basic behaviour of the common dc Unix command-line RPN calculator, so it will print the result when getting a 'p', and quit on a 'q'. Also be aware that it expects each operator or operand on a single line by itself.

It is written using AT&T syntax and will probably only assemble/link on Linux x86/i386 platforms. Check out the code:

/* 
 * Assembling and linking commands:
 * as -o fpu-rpn.o fpu-rpn.s
 * ld -dynamic-linker /lib/ld-linux.so.2 -lc -o fpu-rpn fpu-rpn.o
*/


.section .data
output_format:
  .asciz "%d\n"
error_input:
  .asciz "Error: Unknown input.\n"
error_stack:
  .asciz "Error: Stack empty.\n"


.section .bss
  .lcomm input, 16
  .lcomm buffer, 4
  .lcomm status, 2


.section .text
.globl _start
_start:
  finit

read_input:
  pushl stdin
  pushl $16
  pushl $input
  call fgets
  addl $12, %esp

  /* End if EOF is reached (fgets returns NULL). */
  cmpl $0, %eax
  je end

  /* Extract first character of returned input string. */
  movl (%eax), %ebx

  /* Clear any errors on the FPU each round. */
  fclex

  /* Switch-case like check on the input. */
  cmpb $0x71, %bl # 'q'
  je end
  cmpb $0x70, %bl # 'p'
  je display_result
  cmpb $0x2B, %bl # '+'
  je addition
  cmpb $0x2D, %bl # '-'
  je subtraction
  cmpb $0x2A, %bl # '*'
  je multiplication
  cmpb $0x2F, %bl # '/'
  je division

  /* Check if between 0-9 to determine if it's a number. */
  cmpb $0x30, %bl # '0'
  jl input_error
  cmpb $0x39, %bl # '9'
  jg input_error

  /* Convert number from string and push onto FPU's stack. */
  pushl %eax
  call atoi
  addl $4, %esp
  movl %eax, buffer
  filds buffer
  jmp read_input

input_error:
  push stdout
  push $error_input
  call fputs
  addl $8, %esp
  jmp read_input


addition:
  faddp
  jmp check_stack

subtraction:
  fsubrp
  jmp check_stack

multiplication:
  fmulp
  jmp check_stack

division:
  fdivrp
  jmp check_stack

check_stack:
  fstsw status
  testw $0b1000000, status /* Test for "Stack Fault" flag. */
  jz read_input

  fdecstp /* Decrement stack to avoid displaying the fake result. */
  push stdout
  push $error_stack
  call fputs
  addl $8, %esp
  jmp read_input


display_result:
  /* Display the value at the top of the FPU's stack. */
  fistpl buffer
  fstsw status
  testw $0b1000000, status
  jnz read_input /* Just do the check again to receive error messsage. */

  pushl buffer
  pushl $output_format
  call printf
  addl $8, %esp
  jmp read_input


end:
  /* Tell the Linux kernel to end this process. */
  movl $1, %eax # exit()
  movl $0, %ebx
  int $0x80
          


Topic: Scripts and Code, by Kjetil @ 13/02-2009, Article Link