; m13_calculator.asm
; CIS-261 final poject: "running total" calculator
; @topic W140560 Final Poject
; @brief <a href="http://www.c-jump.com/bcc/c261c/MLabs/M13highlvl/M13highlvl.html" target="_blank">Using Assembly with High-Level Languages</a>, math handler procedures

.586P
.MODEL FLAT     ; Flat memory model
option casemap:none ; Treat labels as case-sensitive

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Add C run-time libraries
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; see https://docs.microsoft.com/en-us/cpp/c-runtime-library/crt-library-features?view=vs-2019
; printf() needs the following library:
includelib legacy_stdio_definitions.lib

PUBLIC m13_calculate ; make this procedure externally visible to the linker

m13_calculate   PROTO NEAR32 stdcall, arithmetic_command:DWORD, right_operand:DWORD
binary_plus     PROTO NEAR32 stdcall, right_operand:DWORD
binary_minus    PROTO NEAR32 stdcall, right_operand:DWORD
binary_multiply PROTO NEAR32 stdcall, right_operand:DWORD
binary_divide   PROTO NEAR32 stdcall, right_operand:DWORD
binary_modulo   PROTO NEAR32 stdcall, right_operand:DWORD
binary_assign   PROTO NEAR32 stdcall, right_operand:DWORD

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; procedures defined in other modules
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
EXTERN _m13_left_operand:DWORD ; global variable defined in m13_externs.cpp
EXTERN _print_error@4:NEAR     ; procedure is defined in m13_externs.cpp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; procedures of the C standard library
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
EXTERN _printf:NEAR

.const
        ASCII_TAB                   EQU 9
        ERROR_division_by_zero      BYTE ASCII_TAB, "attempt to divide by zero ignored...", 0
        ERROR_unexpected_command    BYTE ASCII_TAB, "unexpected command code [%d]", 0
        ERROR_unimplemented_command BYTE ASCII_TAB, "unimplemented command [%d]", 0

        ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
        ; command codes for arithmetic operators
        ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
        COMMAND_ADD         EQU 0
        COMMAND_SUBTRACT    EQU 1
        COMMAND_MULTIPLY    EQU 2
        COMMAND_DIVIDE      EQU 3
        COMMAND_MODULO      EQU 4
        COMMAND_ASSIGN      EQU 5
        COMMAND_UNKNOWN     EQU 6

.data   ; The data segment
        ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
        ; Table of addresses of procedures that are invoked indirectly
        ; Zero entry indicates that procedure is still unimplemented
        ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
        call_table DWORD OFFSET binary_plus
                   DWORD        0 ; OFFSET binary_minus
                   DWORD        0 ; OFFSET binary_multiply
                   DWORD OFFSET binary_divide
                   DWORD        0 ; OFFSET binary_modulo
                   DWORD        0 ; OFFSET binary_assign

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.code   ; Code segment begins
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; entry point routine to dispatch operations to the appropriate handlers
; inputs:
;   right_operand -- param on the stack
;   arithmetic_command -- code of operation. acceptable codes are:
;   ---------------
;   operation code
;   --------- -----
;       '+'    0
;       '-'    1
;       '*'    2
;       '/'    3
;       '%'    4
;       '='    5
;   ---------------
; returns
;   zero if success, error code otherwise (the returned value is in EAX)
;
m13_calculate   PROC NEAR32 stdcall, arithmetic_command:DWORD, right_operand:DWORD
    push ebx                        ; preserve EBX
    mov   ebx, [arithmetic_command] ; load command code into EBX

    ;;-------------------------------------------------------------------------
    ;; validate range of command code
    ;;-------------------------------------------------------------------------
    cmp ebx, COMMAND_UNKNOWN    ; if ( arithmetic_command < COMMAND_UNKNOWN )
    jb  @F                      ; command is in good range

    ;;-------------------------------------------------------------------------
    ;; command code is out of range
    ;;-------------------------------------------------------------------------
    ; NOTE: printf() follows C convention: arguments passed right to left
    push  ebx                               ; command code to display
    push  OFFSET ERROR_unexpected_command   ; format string to use
    call  _printf                           ; print string
    ; NOTE: printf is a varargs function. It does not
    ; pop its own args off the stack.
    ; The caller must remove the arguments:
    add  esp, 8

    pop ebx                     ; restore EBX
    mov eax, @Line              ; use line number in this file as error code
    ret                         ; RETURN

@@:
    cmp call_table[ ebx * 4 ], 0 ; if ( arithmetic_command != 0 )
    jne @F                       ; command is implemented

    ;;-------------------------------------------------------------------------
    ;; command code is not implemented
    ;;-------------------------------------------------------------------------
    push  ebx                                   ; command code to display
    push  OFFSET ERROR_unimplemented_command    ; format string to use
    call  _printf                               ; print string

    ; NOTE: printf is a varargs function. It does not
    ; pop its own args off the stack.
    ; The caller must remove the arguments:
    add   esp, 8

    pop ebx             ; restore EBX
    mov eax, @Line      ; use line number in this file as error code
    ret                 ; RETURN

@@:
    ;;-------------------------------------------------------------------------
    ;; all validations passed, make calculations
    ;;-------------------------------------------------------------------------
    mov   eax, [right_operand]
    push  eax                   ; prepare right operand on the stack
    call  call_table[ ebx * 4 ] ; invoke command handler
    pop   ebx                   ; restore ebx
    ret
m13_calculate   ENDP


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; inputs:
;   m14_left_operand -- global variable
;   right_operand -- param on the stack
; returns:
;   EAX: zero if success, error code otherwise
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
binary_plus     PROC NEAR32 stdcall, right_operand:DWORD
    mov eax, [right_operand]        ;
    add [_m13_left_operand], eax    ; [_m13_left_operand] += [right_operand]
    xor eax, eax                    ; EAX = 0 indicating success
    ret
binary_plus     ENDP

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; inputs:
;   m14_left_operand -- global variable
;   right_operand -- param on the stack
; returns:
;   EAX: zero if success, error code otherwise
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
binary_minus    PROC NEAR32 stdcall, right_operand:DWORD
    ; TODO
    ret
binary_minus    ENDP

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; inputs:
;   m14_left_operand -- global variable
;   right_operand -- param on the stack
; returns:
;   EAX: zero if success, error code otherwise
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
binary_multiply PROC NEAR32 stdcall, right_operand:DWORD
    ; TODO
    ret
binary_multiply ENDP

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; inputs:
;   m14_left_operand -- global variable
;   right_operand -- param on the stack
; returns:
;   EAX: zero if success, error code otherwise
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
binary_divide   PROC NEAR32 stdcall, right_operand:DWORD
    push ecx                        ; preserve ECX
    mov eax, [_m13_left_operand]    ; EAX = dividend
    cdq                             ; must extend the sign bit of EAX into the EDX register

    mov ecx, [right_operand]        ; ECX = divisor
    test ecx, ecx                   ; if ( ECX == 0 )
    jz @F                           ; then error

    idiv ecx                        ; ( quotient EAX : remainder EDX ) = EAX / ECX
    mov [_m13_left_operand], eax    ; store quotient
    xor eax, eax                    ; EAX = 0 indicating success
    pop ecx                         ; restore
    ret
@@:
    push OFFSET ERROR_division_by_zero

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; Example using printf() for output
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    call _printf    ; print string
    ; NOTE: printf is a varargs function. It does not
    ; pop its own args off the stack.
    ; The caller must remove the arguments:
    add  esp, 4

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; Example calling our own C function for output
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;call _print_error@4
    pop ecx                         ; restore ECX
    mov eax, @Line                  ; use line number in this file as error code
    ret

binary_divide   ENDP


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; inputs:
;   m14_left_operand -- global variable
;   right_operand -- param on the stack
; returns:
;   EAX: zero if success, error code otherwise
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
binary_modulo   PROC NEAR32 stdcall, right_operand:DWORD
    ; TODO
    ret

binary_modulo   ENDP


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; inputs:
;   m14_left_operand -- global variable
;   right_operand -- param on the stack
; returns:
;   EAX: zero if success, error code otherwise
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
binary_assign   PROC NEAR32 stdcall, right_operand:DWORD
    ; TODO
    ret
binary_assign   ENDP

END
