;=============================================================================
;
;  exprcom.asm   Expression calculator 'exprcom 1 + 2'
;
;  Written:      95/03/13  Pieter Hintjens
;  Revised:      95/05/24
;
;  Usage:        Assemble and link as a .COM file:
;                    masm exprcom;
;                    link exprcom;
;                    exe2bin exprcom.exe exprcom.com
;                    del exprcom.exe
;
;  Model:        Code, data, and stack are in same segment: CS, DS, ES, and SS
;                point to the one segment, and never change.
;
;  Skeleton generated by LIBERO 2.10 on 24 May, 1995, 12:52.
;=============================================================================

include exprcom.d                      ; Include dialog definitions

                 DATA_SEG              ; Data definitions

end_mark_pty     equ     1             ; Relative priority of tokens
left_par_pty     equ     2             ;   which may occur in exression
right_par_pty    equ     3             ;   - higher number means higher
term_op_pty      equ     4             ;   priority, ie. executed first.
factor_op_pty    equ     5
lowest_op_pty    equ     4
end_mark_token   equ     'E'           ; Indicates end of operator stack

;  Each entry in operator stack contains operator and priority in 2 bytes
operator_max     equ     20            ; Max entries in operator stack
operator_ptr     dw      0             ; Current size of operator stack
operator_stack   dw      operator_max dup (0)

;  Each entry in operand stack contains signed nunber in 2 bytes
operand_max      equ     20            ; Max entries in operand stack
operand_ptr      dw      0             ; Current size of operand stack
operand_stack    dw      operand_max dup (0)

arg_ptr          dw      0
result           dw      0             ; Calculated result of expression
the_pty          db      0             ; Current priority
the_token        db      0             ; Current token
the_number       dw      0             ; Current number


msg_helptext     label   byte
                 db      'expr v1.0 (c) 1991-95 by Pieter A. Hintjens.\n'
                 db      'Expression calculator, returns errorlevel result\n\n'
                 db      'expr [/?] expression\n\n'
                 db      '  /?         - show this help\n'
                 db      '  expression - numbers, +/-*, (), spaces\n'
                 db      0             ; End of text

msg_program_id        db 'expr: ',0
msg_num_overflow      db 'number in expression is too large\n',0
msg_operand_over      db 'operand stack overflowed\n',0
msg_operator_over     db 'operator stack overflowed\n',0
msg_add_over          db 'addition overflow\n',0
msg_div_zero          db 'divide-by-zero error\n',0
msg_bad_token         db 'invalid token on stack: ',0
msg_operator_under    db 'operator stack underflowed\n',0
msg_operand_under     db 'operand stack underflowed\n',0
msg_no_left_par       db 'too many )''s in expression\n',0
msg_no_right_par      db 'too few )''s in expression\n',0
msg_inv_token         db 'invalid token in expression: ',0
msg_no_token          db 'unexpected end of expression - use /? for help\n',0



;*************************   INITIALISE THE PROGRAM   ************************

MODULE initialise_the_program
                 cmp     arg_help,1    ; Was /? specified on command line?
                 jne     initialise_okay
                 lea     si,msg_helptext
                 call    echo          ; yes - display help message
                 mov     the_next_event,terminate_event
                 ret                   ;   and terminate dialog

initialise_okay  label   near          ; Normal initialisation
                 mov     arg_ptr,0     ; Move to start of expression
                 mov     result,0      ; Assume result is zero
                 mov     al,end_mark_token
                 mov     ah,end_mark_pty
                 mov     operator_stack[0],ax
                 mov     the_next_event,ok_event
                 ret                   ; Return to state machine
ENDMODULE


;*****************************   GET NEXT TOKEN   *****************************

MODULE get_next_token
                 mov     bp,arg_ptr    ; Get address into argument text
get_next_loop:   mov     al,arg_text[bp]
                 inc     bp            ;
                 cmp     al,' '        ; Skip leading spaces
                 je      get_next_loop ;
                 mov     the_token,al  ; Store al in token
                 mov     arg_ptr,bp    ;  and update argument pointer
                 cmp     al,'+'        ; '+' -> term operator
                 je      have_term     ;
                 cmp     al,'-'        ; '-' -> term operator
                 je      have_term     ;
                 cmp     al,'*'        ; '*' -> factor operator
                 je      have_factor   ;
                 cmp     al,'/'        ; '/' -> factor operator
                 je      have_factor   ;
                 cmp     al,'('        ; '(' -> left parenthesis
                 je      have_left_par ;
                 cmp     al,')'        ; ')' -> right parenthesis
                 je      have_right_par;
                 cmp     al,'0'        ; '0'..'9' -> number
                 jb      get_next_end  ;
                 cmp     al,'9'        ;
                 jbe     have_number   ;
get_next_end:    cmp     al,0          ; 0 -> end of expression
                 je      have_end_mark ;
                                       ; Anything else is an error
                 mov     the_next_event,other_event
                 ret                   ; Return to state machine

have_term:       mov     the_next_event,term_op_event
                 mov     the_pty,term_op_pty
                 ret                   ; Return to state machine

have_factor:     mov     the_next_event,factor_op_event
                 mov     the_pty,factor_op_pty
                 ret                   ; Return to state machine

have_left_par:   mov     the_next_event,left_par_event
                 mov     the_pty,left_par_pty
                 ret                   ; Return to state machine

have_right_par:  mov     the_next_event,right_par_event
                 mov     the_pty,right_par_pty
                 ret                   ; Return to state machine

have_end_mark:   mov     the_next_event,end_mark_event
                 mov     the_pty,end_mark_pty
                 ret                   ; Return to state machine

have_number:     mov     the_next_event,number_event
                 mov     bx,10         ; BX = 10 for multiply
                 xor     cx,cx         ; CX = next digit in low byte
                 sub     al,'0'        ; AX = value of first digit
                 xor     ah,ah         ;
next_digit:      mov     cl,arg_text[bp]
                 sub     cl,'0'        ;
                 cmp     cl,9          ; Is result in range 0 to 9?
                 ja      have_number_x ;  no - we're done
                 inc     bp            ;
                 mul     bx            ; Multiply value by 10
                 add     ax,cx         ; Add-in to result
                 jo      num_overflow  ; If overflow, signal error
                 jmps    next_digit    ; Go do next digit until done
have_number_x:   mov     the_number,ax ; Store number value
                 mov     arg_ptr,bp    ;  and update argument pointer
                 ret                   ; Return to state machine
num_overflow:    lea     si,msg_num_overflow
                 jmps    echo_exception
ENDMODULE

echo_exception   proc    near          ; Echo message at SI and raise exception
                 push    si            ; Start message with name of program
                 lea     si,msg_program_id
                 call    echo
                 pop     si
                 mov     the_exception_event,exception_event
                 mov     exception_raised,1
                 jmp     echo
echo_exception   endp


;**************************   ALLOW SIGNED NUMBER   ***************************

MODULE allow_signed_number
                 mov     bp,arg_ptr    ; Get address into argument text
                 mov     al,arg_text[bp]
                 cmp     al,'0'        ; '0'..'9' -> number
                 jb      allow_exit    ;   else quit
                 cmp     al,'9'        ;
                 ja      allow_exit    ;
                 inc     bp            ; Bump past first digit
                 call    have_number   ;   and collect number
                 cmp     the_token,'-' ;
                 jne     allow_number  ; If negative sign,
                 xor     ax,ax         ;   get inverse of number
                 sub     ax,the_number ;   and put back in the_number
                 mov     the_number,ax ;
allow_number:    mov     the_exception_event,number_event
                 mov     exception_raised,1
allow_exit       label   near
                 ret                   ; Return to state machine
ENDMODULE


;****************************   STACK THE NUMBER   ****************************

MODULE stack_the_number
                 mov     ax,the_number ; AX = current number value
                 call    push_operand  ; Push onto operand stack
                 ret                   ; Return to state machine
ENDMODULE

push_operand     proc    near          ; Push operand in AX:
                 pushr   <bx>          ;   save work registers
                 mov     bx,operand_ptr;
                 inc     bx            ;   bump stack pointer
                 inc     bx            ;
                 cmp     bx,operand_max * 2
                 ja      operand_over
                 mov     operand_stack[bx],ax
                 mov     operand_ptr,bx
push_operand_1:  popr    <bx>          ;   restore work registers
                 ret                   ; Return to caller
operand_over     label   near
                 lea     si,msg_operand_over
                 call    echo_exception
                 jmps    push_operand_1
push_operand     endp


;***************************   STACK THE OPERATOR   ***************************

MODULE stack_the_operator
                 mov     al,the_token  ; AL = operator
                 mov     ah,the_pty    ; AH = priority
                 call    push_operator ; Push AX onto operator stack
                 ret                   ; Return to state machine
ENDMODULE

push_operator    proc    near          ; Push operator in AX:
                 pushr   <bx>          ;   save work registers
                 mov     bx,operator_ptr;
                 inc     bx            ;   bump stack pointer
                 inc     bx            ;
                 cmp     bx,operator_max * 2
                 ja      operator_over
                 mov     operator_stack[bx],ax
                 mov     operator_ptr,bx
push_operator_1: popr    <bx>          ;   restore work registers
                 ret                   ; Return to caller
operator_over    label   near
                 lea     si,msg_operator_over
                 call    echo_exception
                 jmps    push_operator_1
push_operator    endp


;**************************   UNSTACK GE OPERATORS   **************************

MODULE unstack_ge_operators
unstack_ge_next  label   near          ; while operator [ptr] >= the_pty
                 mov     bx,operator_ptr
                 mov     ax,operator_stack[bx]
                 cmp     ah,the_pty    ;
                 jl      unstack_ge_exit
                 call    unstack_operator
                 jmps    unstack_ge_next
unstack_ge_exit  label   near
                 ret                   ; Return to state machine
ENDMODULE

unstack_operator proc    near          ; Unstack and evaluate operator
                 call    pop_operator  ; Get next operator from stack
                 mov     dx,ax         ; DX = operator and priority
                 call    pop_operand   ; BX = operand 2
                 mov     bx,ax         ;
                 cmp     dl,'+'        ;
                 jne     unstack_1

                 call    pop_operand   ; AX = operand 1
                 add     ax,bx         ;    + operand 2
                 jno     unstack_okay  ; and re-stack result
                 lea     si,msg_add_over
                 call    echo_exception;
                 jmps    unstack_okay  ;

unstack_1:       cmp     dl,'-'
                 jne     unstack_2
                 call    pop_operand   ; AX = operand 1
                 sub     ax,bx         ;    - operand 2
                 jmps    unstack_okay  ; and re-stack result

unstack_2:       cmp     dl,'*'
                 jne     unstack_3
                 xor     dx,dx         ; High word is zero
                 call    pop_operand   ; AX = operand 1
                 mul     bx            ;    * operand 2
                 jmps    unstack_okay  ; and re-stack result

unstack_3:       cmp     dl,'/'
                 jne     unstack_4
                 call    pop_operand   ; AX = operand 1
                 cmp     bx,0          ; If operand 2 = zero,
                 je      divide_zero   ;   signal divide-by-zero error
                 div     bx            ; AX = operand 1 / operand 2
                 jmps    unstack_okay  ; and re-stack result
divide_zero:     lea     si,msg_div_zero
                 call    echo_exception
                 xor     ax,ax         ; Assumed result is zero
                 jmps    unstack_okay  ;   and re-stack result

unstack_4:       cmp     dl,end_mark_token
                 jne     unstack_error ; At end:
                 mov     feedback,al   ;   result of expression = AX
                 call    echonum       ;
                 lea     si,newline    ;
                 call    echo          ;
                 jmps    unstack_okay  ; Return to caller

unstack_error:   lea     si,msg_bad_token
                 call    echo_exception
                 mov     ax,dx         ;
                 call    echonum       ; echo DX, as decimal number
                 lea     si,newline    ;
                 call    echo          ; echo '\n'

unstack_okay:    call    push_operand  ; Push result
                 ret                   ; Return to caller
unstack_operator endp


pop_operator     proc    near          ; Pop operator off stack into AX:
                 pushr   <bx>          ;   save work registers
                 mov     bx,operator_ptr
                 cmp     bx,0
                 jl      operator_under
                 mov     ax,operator_stack[bx]
                 dec     bx            ;   bump stack pointer
                 dec     bx            ;
                 mov     operator_ptr,bx
pop_operator_1:  popr    <bx>          ;   restore work registers
                 ret                   ; Return to caller
operator_under   label   near
                 lea     si,msg_operator_under
                 call    echo_exception
                 jmps    pop_operator_1
pop_operator     endp


pop_operand      proc    near          ; Pop operand off stack into AX:
                 pushr   <bx>          ;   save work registers
                 mov     bx,operand_ptr
                 cmp     bx,0
                 jl      operand_under
                 mov     ax,operand_stack[bx]
                 dec     bx            ;   bump stack pointer
                 dec     bx            ;
                 mov     operand_ptr,bx
pop_operand_1:   popr    <bx>          ;   restore work registers
                 ret                   ; Return to caller
operand_under    label   near
                 lea     si,msg_operand_under
                 call    echo_exception
                 jmps    pop_operand_1
pop_operand      endp


;*************************   UNSTACK ALL OPERATORS   **************************

MODULE unstack_all_operators
unstack_all_next label   near          ; while operator [ptr] >= lowest_op_pty
                 mov     bx,operator_ptr
                 mov     ax,operator_stack[bx]
                 cmp     ah,lowest_op_pty
                 jl      unstack_all_exit
                 call    unstack_operator
                 jmps    unstack_all_next
unstack_all_exit label   near
                 ret                   ; Return to state machine
ENDMODULE


;**************************   UNSTACK IF LEFT PAR   ***************************

MODULE unstack_if_left_par
                 mov     bx,operator_ptr
                 mov     ax,operator_stack[bx]
                 cmp     al,'('        ; Expect ( on stack, else error
                 je      unstack_left
                 lea     si,msg_no_left_par
                 call    echo_exception
                 ret                   ; Return to state machine
unstack_left:    call    pop_operator  ; if (, pop off stack
                 ret                   ; Return to state machine
ENDMODULE


;**************************   UNSTACK IF END MARK   ***************************

MODULE unstack_if_end_mark
                 mov     bx,operator_ptr
                 mov     ax,operator_stack[bx]
                 cmp     al,end_mark_token
                 je      unstack_end   ;
                 lea     si,msg_no_right_par
                 call    echo_exception
                 ret                   ; Return to state machine
unstack_end:     call    unstack_operator
                 ret                   ; Return to state machine
ENDMODULE


;**************************   SIGNAL INVALID TOKEN   **************************

MODULE signal_invalid_token
                 lea     si,msg_inv_token
                 call    echo_exception
                 mov     al,the_token  ;
                 call    echoch        ; echo the_token
                 lea     si,newline    ;
                 call    echo          ; echo '\n'
                 ret                   ; Return to state machine
ENDMODULE


;**************************   SIGNAL TOKEN MISSING   **************************

MODULE signal_token_missing
                 lea     si,msg_no_token
                 call    echo_exception
                 ret                   ; Return to state machine
ENDMODULE


;***************************   GET EXTERNAL EVENT   ***************************

MODULE get_external_event
                 ret                   ; Return to state machine
ENDMODULE


;*************************   TERMINATE THE PROGRAM   *************************

MODULE terminate_the_program
                 mov     the_next_event,terminate_event
                 ret                   ; Return to state machine
ENDMODULE


;%END MODULES
;--------------- End of source file ------------------------------------------

                 end     program       ; This block is generated code

