1

I'm trying to make a calculator in Assembly where the equation is read in as a string (ie 9+3-2/5*4) as opposed to reading in one digit at a time and asking the user which operation they want to perform. I figured out how to parse the string so I can convert the ASCII digits to decimal and store the operators for comparison.

However, I don't know how to tackle the problem of following the order of operations correctly.

Full source code here:

;calculator.asm

%macro  convert 2
    mov al, dl
    mov dl, 0
    mov bl, %1
    div bl
    add byte[result+%2], al
%endmacro

section .data
    msg db  "Enter an equation: "
    input   db  "000000000", 10
    decOne  db  0
    decTwo  db  0
    result  db  0
    endLoop dq  9
section .text
    global _start

_start:
    ;cout << msg
    mov     rax, 1
    mov     rdi, 1
    mov     rsi, msg
    mov     rdx, 19
    syscall
    
    ;cin >> input
    mov     rax, 0
    mov     rdi, 0
    mov     rsi, input
    mov     rdx, 10
    syscall

    xor r10d, r10d
    ;convert first digit and store in decOne
    sub byte[input+r10], '0'
    mov al, byte[input+r10]
    mov byte[decOne], al
    inc r10
    
    ;operator comparison
operatorComp:
    mov al, byte[input+r10]
    cmp al, '+'
    je  addition
    cmp al, '-'
    je  subtraction
    cmp al, '*'
    je  multiplication
    cmp al, '/'
    je  division
subtraction:
    inc r10

    sub byte[input+r10], '0'
    mov al, byte[input+r10]
    mov byte[decTwo], al
    
    mov al, byte[decOne]
    sub al, byte[decTwo]
    mov byte[result], al
    mov byte[decOne], al
    
    inc r10
    cmp r10, qword[endLoop]
    je  done
    jmp operatorComp
addition:
    inc r10
    
    sub byte[input+r10], '0'
    mov al, byte[input+r10]
    mov byte[decTwo], al
    
    mov al, byte[decOne]
    add al, byte[decTwo]
    mov byte[result], al
    mov byte[decOne], al
    
    inc r10 
    cmp r10, qword[endLoop]
    je  done
    jmp operatorComp
multiplication:
    inc r10

    sub byte[input+r10], '0'
    mov al, byte[input+r10]
    mov byte[decTwo], al
    
    mov al, byte[decOne]
    mul byte[decTwo]
    mov byte[result], al
    mov byte[decOne], al
    
    inc r10
    cmp r10, qword[endLoop]
    je  done
    jmp operatorComp
division:
    inc r10
    
    sub byte[input+r10], '0'
    mov al, byte[input+r10]
    mov byte[decTwo], al
    
    mov al, byte[decOne]
    div byte[decTwo]
    mov byte[result], al
    mov byte[decOne], al
    
    inc r10
    cmp r10, qword[endLoop]
    je  done
    jmp operatorComp
done:
    ;convert result to ASCII
    mov dl, byte[result]
    convert 100, 0
    convert 10, 1
    convert 1, 2
    add byte[result+2], dl

    ;output result
    mov     rax, 1
    mov     rdi, 1
    mov     rsi, result
    mov     rdx, 3
    syscall
    
    ;exit program
    mov     rax, 60
    mov     rdi, 0
    syscall

Currently this program only parses the string in the order it was provided and makes the jumps accordingly.

rageatm
  • 13
  • 3
  • 2
    How do you 'know' that the program is not jumping to the *addition* label? The code `mov bl, byte[addOp]` `cmp al, bl` `je addition` is correct although `cmp al, '+'` `je addition` would be much better. Also, why does your output uses a count of RDX=3 when *result* is just 1 byte **that you don't even convert from number to string**. – Sep Roland Dec 07 '22 at 00:42
  • The code from the link is not too long. Better include its text in the question. People don't like having to look elsewhere to be of assistance! – Sep Roland Dec 07 '22 at 00:43
  • `does the je instruction only work for numerical values` - in asm characters are numbers as well. – 500 - Internal Server Error Dec 07 '22 at 00:45
  • 1
    I hope you realize that as a calculator this sucks. `9+3-2/5` would produce 2 which is not according to what everybody learnt at school `(9+3)-(2/5)`. – Sep Roland Dec 07 '22 at 00:49

1 Answers1

2

R10 is not callee-preserved. Move its setup to after the prompt and input syscalls:

xor r10d, r10d      ; Better than `mov r10, 0`
;convert first digit and store in decOne
sub byte[input+r10], '0'

mov al, byte[decOne]
div byte[decTwo]
mov byte[result], al

The byte-sized division divides AX by the specified source operand. You forgot to zero AH.

movzx ax, byte[decOne]
div   byte[decTwo]
mov   byte[result], al

Because endLoop is hard-coded for an expression of 9 characters (endLoop dq 9), make sure the expression is that long. The example is too short: turn "9+3-2/5" into "9+3-2/5*4" which will produce "8".


Because the result will be equal to 8, turn it into the character "8" with the instruction add byte[result], 48 prior to outputting:

done:
    ;output result
    mov     rax, 1
    mov     rdi, 1
    mov     rsi, result
    add     byte [rsi], '0'
    mov     rdx, 1
    syscall

The convert macro has multiple issues:

  • mov dl, 0 destroys the source and you need it 3 times, but each time a bit reduced
  • For this byte-sized division you need to zero AH
  • The quotient is added to memory that you did not reserve for that purpose
  • The quotient is still not a character, requires adding 48
%macro  convert 2
    movzx ax, dl                             <<<
                                             <<<  
    mov   bl, %1              ; {100,10,1}
    div   bl
    add   al, '0'                            <<<
    mov   byte[result+%2], al ; {0,1,2}      <<<
    mov   dl, ah                             <<<
%endmacro

section .data
    msg db  "Enter an equation: "
    input   db  "000000000", 10
    decOne  db  0
    decTwo  db  0
    result  db  0, 0, 0                      <<<
    endLoop dq  9

Use the macro this way:

done:
    ;convert result to ASCII
    mov dl, byte[result]
    convert 100, 0
    convert 10, 1
    convert 1, 2
                                              <<<
    ;output result
Sep Roland
  • 33,889
  • 7
  • 43
  • 76
  • OK, I've implemented the suggestions you make and at least now it's performing the jumps correctly and giving me the result of 8. Adding '0' only works for single digits so I'm trying to implement a conversion macro I just need to tweak it cause it's not working at the moment. Regardless you've been very helpful so far. Thank you! – rageatm Dec 07 '22 at 02:15
  • @rageatm I have amended your macro! – Sep Roland Dec 07 '22 at 02:20
  • 1
    You're awesome! I'm gonna take a break but I'll have to re-visit later to figure out how to implement it with PEMDAS in mind. Hope I don't have to completely restructure the program to do that. – rageatm Dec 07 '22 at 02:43
  • @rageatm Once you get this version working correctly, consider accepting it as is (clicking on the accept check mark on the left side) and feel free to ask a new question regarding your PEMDAS approach. Otherwise it risks becoming unmanageable. Good luck. – Sep Roland Dec 07 '22 at 02:50
  • In the `convert` macro, isn't `dl - dl / %1 * %1` just getting the remainder? It's in AH after 8-bit division, no need to manually multiply and subtract like on ARM. – Peter Cordes Dec 07 '22 at 04:28
  • @PeterCordes Nice catch! I have only to blame myself for staying up too long. – Sep Roland Dec 08 '22 at 21:12