        Page    58,132
        Title   PRINTF.ASM      Generic Printf Module
;******************************************************************************
;
;   Name:       PRINTF.ASM      Generic Printf Module
;
;   Revision:   1.00
;
;   Date:       November 30, 1988
;
;   Author:     Randy Spurlock
;
;******************************************************************************
;
;  Module Functional Description:
;
;       Printf - Prints a formatted string of arguments to
;                the requested handle.
;
;       Calling Sequence:
;
;               mov     al,HANDLE       ; Get the desired handle to print on
;               lea     bx,ds:[args]    ; Get a pointer to the arguments
;               lea     si,ds:[format]  ; Get a pointer to the format string
;               call    printf          ; Call the printf routine
;
;       The conversion characters are as follows:
;
;               %% - Print percent sign to handle
;               %c - Output the next argument as a character
;               %s - Output the next argument as a string
;               %x - Output the next argument as a hex number using abcdef
;               %X - Output the next argument as a hex number using ABCDEF
;               %h - Output the next argument as a hex number using abcdef
;               %H - Output the next argument as a hex number using ABCDEF
;               %d - Output the next argument as a decimal number (Signed)
;               %u - Output the next argument as a decimal number (Unsigned)
;               %o - Output the next argument as a octal number
;               %b - Output the next argument as a binary number
;               %f - Output the next argument as a fractional number (Signed)
;
;       Other format specifiers may precede the conversion character:
;
;               -  - Left justify the field
;               +  - Set signed field
;               n  - Specify the field width/precision
;               t  - Specify short value
;               l  - Specify long value
;               #  - Specify far argument pointer
;               &  - Specify indirect argument pointer
;               $  - Specify immediate argument value
;               *  - Variable precision/width value (From argument list)
;
;       All arguments must be pointers to the actual values.
;
;       The following escape sequences are also handled:
;
;               \\   -  Backslash
;               \n   -  Newline
;               \t   -  Horizontal Tab
;               \v   -  Vertical Tab
;               \b   -  Backspace
;               \r   -  Carriage Return
;               \f   -  Form Feed
;               \ddd -  ASCII Character (Octal Notation)
;               \xdd -  ASCII Character (Hexadecimal Notation)
;
;******************************************************************************
;
;  Changes:
;
;    DATE     REVISION                          DESCRIPTION
;  --------   --------  -------------------------------------------------------
;  11/30/88     1.00    Original                                Randy Spurlock
;
;******************************************************************************
        Page
;
;  Public Declarations
;
        Public  Printf                  ; Generic Printf routine
;
;  Define the Local Equates
;
FORMAT_CHAR     Equ     "%"             ; Format specification character
ESCAPE_CHAR     Equ     "\"             ; Escape sequence character
BASE_HEX        Equ     16              ; Base 16 - Hexadecimal
BASE_DECIMAL    Equ     10              ; Base 10 - Decimal
BASE_OCTAL      Equ     8               ; Base 8  - Octal
BASE_BINARY     Equ     2               ; Base 2  - Binary
FORMAT_LENGTH   Equ     5               ; Maximum format number width
ESCAPE_LENGTH   Equ     3               ; Escape sequence number width (Decimal)
HEX_LENGTH      Equ     2               ; Escape sequence number width (Hex)
MAX_WORD        Equ     0FFFFh          ; Maximum 16-bit count value
MAX_BYTE        Equ     0FFh            ; Maximum 8-bit count value
SPACE_PAD       Equ     " "             ; Space pad character
ZERO_PAD        Equ     "0"             ; Zero pad character
LEFT_JUST       Equ     8000h           ; Left justification flag
SHORT_SPEC      Equ     4000h           ; Short specification flag
LONG_SPEC       Equ     2000h           ; Long specification flag
UPPER_CASE      Equ     1000h           ; Upper case hexadecimal flag
SIGNED_CONV     Equ     0800h           ; Signed conversion flag
SIGNED_TYPE     Equ     0400h           ; Signed type flag
SIGNED_VAL      Equ     0200h           ; Signed value flag
PRE_PAD         Equ     0100h           ; Pre-pad sign character flag
FAR_SPEC        Equ     0080h           ; Far argument specification flag
OVER_FLOW       Equ     0040h           ; Field overflow flag
FRACTIONAL      Equ     0020h           ; Fractional integer flag
VAR_WIDTH       Equ     0010h           ; Variable width flag
VAR_PRE         Equ     0008h           ; Variable precision flag
PAD_CHAR        Equ     0004h           ; Pad character flag (0=Space, 1=Zero)
INDIRECT        Equ     0002h           ; Indirection flag (1 = Indirect)
IMMEDIATE       Equ     0001h           ; Immediate flag (1 = Immediate)
SIGNED          Equ     8000h           ; Sign flag test mask
DECIMAL_ADJUST  Equ     30h             ; ASCII decimal to binary adjust value
HEX_ADJUST      Equ     07h             ; ASCII hex to binary adjust value
UPPER_MASK      Equ     0DFh            ; Lower to upper case mask value
FAR_SIZE        Equ     4h              ; Far argument pointer size   (4 bytes)
NEAR_SIZE       Equ     2h              ; Near argument pointer size  (2 bytes)
LONG_SIZE       Equ     4h              ; Long numeric argument size  (4 bytes)
NORMAL_SIZE     Equ     2h              ; Normal numeric argument size(2 bytes)
SHORT_SIZE      Equ     1h              ; Short numeric argument size (1 bytes)
BUFF_SIZE       Equ     64              ; Numeric build buffer size
DIGIT_MAX       Equ     39h             ; Maximum ASCII digit value
DOS_FUNCTION    Equ     21h             ; MS-DOS function request interrupt
WRITE_FILE      Equ     40h             ; Write file function code
;
;  Define any ASCII characters needed
;
PLUS            Equ     "+"             ; Plus sign character
MINUS           Equ     "-"             ; Minus sign character
EQUAL           Equ     "="             ; Equal sign character
ASTERISK        Equ     "*"             ; Asterisk character
POINT           Equ     "."             ; Decimal point character
NULL            Equ     00h             ; ASCII code for null
BS              Equ     08h             ; ASCII code for backspace
HT              Equ     09h             ; ASCII code for horizontal tab
LF              Equ     0Ah             ; ASCII code for line feed
VT              Equ     0Bh             ; ASCII code for vertical tab
FF              Equ     0Ch             ; ASCII code for form feed
CR              Equ     0Dh             ; ASCII code for carriage return
;
;  Define any Macros needed
;
        Page
;******************************************************************************
;
;       Save(Regs)
;
;               While there are registers in the list (Left to right)
;                       Push this register onto the stack
;               Endwhile
;
;******************************************************************************
Save            Macro   a,b,c,d,e,f,g,h,i,j,k,l,m,n,o
        Irp     x,<a,b,c,d,e,f,g,h,i,j,k,l,m,n,o>
                Ifnb    <x>
                    push    x
                Endif
        Endm
                Endm
        Page
;******************************************************************************
;
;       Restore(Regs)
;
;               While there are registers in the list (Right to left)
;                       Pop this register from the stack
;               Endwhile
;
;******************************************************************************
Restore         Macro   a,b,c,d,e,f,g,h,i,j,k,l,m,n,o
        Irp     x,<o,n,m,l,k,j,i,h,g,f,e,d,c,b,a>
                Ifnb    <x>
                    pop     x
                Endif
        Endm
                Endm
        Page
;
;  Define the standard code segment
;
Code    Segment Word Public 'CODE'      ; Define the standard code segment
        Assume  cs:Code, ds:Nothing, es:Nothing
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Printf(Format_String, Arguments, Handle)
;
;               Save the required registers
;               While next character from Format_String <> 0
;                       If next character is a format character (percent sign)
;                               Get the next character from Format_String
;                               If a format specifier (+,-,n,l,#,&,$,*)
;                                       Set the appropriate specifier flag
;                               Else not a format specifier
;                                       If a conversion character (c,s,x,d,o,b)
;                                               Print argument using conversion
;                                               Increment argument pointer
;                                       Else not a conversion character
;                                               Ignore this format
;                                       Endif
;                               Endif
;                       Else the character is not a format character
;                               If character is a escape character (backslash)
;                                       Get next character from Format_String
;                                       If a valid escape sequence
;                                               Handle the escape sequence
;                                       Else an invalid escape sequence
;                                               Print the character to Handle
;                                       Endif
;                               Else the character is not an escape character
;                                       Print the character to Handle
;                               Endif
;                       Endif
;               Endwhile
;               Restore the required registers
;               Return to the caller
;
;       Registers on Entry:
;
;               AL    - File Handle to Print on
;               DS:BX - Pointer to Argument List
;               DS:SI - Pointer to Format String
;
;       Registers on Exit:
;
;               FL    - DR flag is cleared (Move forward)
;
;******************************************************************************
Printf          Proc    Near            ; Generic printf procedure
        Save    ax,bx,cx,dx,si,di,bp,ds,es
        mov     ah,al                   ; Save print handle in AH
        cld                             ; Clear the direction flag (Forward)
;
;  Get a character from format string and decide what to do with it
;
Get_Char:
        call    Clear_Flags             ; Clear all of the specifier flags
        lodsb                           ; Get a character from format string
        cmp     al,FORMAT_CHAR          ; Check for a format character (%)
        je      Do_Format               ; Jump if a format character
        cmp     al,ESCAPE_CHAR          ; Check for an escape character (\)
        je      Do_Escape               ; Jump if a escape character
        or      al,al                   ; Check for end of the format string
        jnz     Normal_Output           ; Jump if a normal character to output
        jmp     Printf_Exit             ; Jump if end of the format string
;
;  It is a escape character, get the next character and check it
;
Do_Escape:
        lodsb                           ; Get next character from format string
        push    cs                      ; Put copy of CS onto the stack
        pop     es                      ; Put copy of current CS into ES
        lea     di,cs:[Escape_Table]    ; Setup the escape character table
        mov     cx,ESCAPE_SIZE          ; Get the escape character table size
        repne   scasb                   ; Scan the escape table for a match
        je      Go_Escape               ; Jump if an there was a table match
        mov     ch,BASE_OCTAL           ; Set the current base value to OCTAL
        mov     cl,ESCAPE_LENGTH        ; Set the escape maximum number count
        call    Check_Digit             ; Check for numeric digit (Character)
        jc      Normal_Output           ; Jump if an unknown escape character
Get_Code:
        dec     si                      ; Backup to the start of the number
        push    dx                      ; Save the field width/precision
        call    Get_Number              ; Call routine to get the number
        mov     al,dl                   ; Put the character number into AL
        pop     dx                      ; Restore the field width/precision
        jmp     Short Normal_Output     ; Go get next format string character
Go_Escape:
        mov     di,ESCAPE_SIZE          ; Get the escape table size
        sub     di,cx                   ; Compute the matching entry number
        dec     di                      ; Convert number to zero based
        shl     di,1                    ; Make number into jump table index
        call    cs:[di+Escape_Jump]     ; Call the correct routine
        jmp     Short Get_Char          ; Go get the next character
Normal_Output:
        call    Output                  ; Not a special character, output it
        jmp     Short Get_Char          ; Go get next format string character
;
;  Not a valid format specifier, check for a conversion character
;
Do_Convert:
        lea     di,cs:[Convert_Table]   ; Setup the convert character table
        mov     cx,CONVERT_SIZE         ; Get the convert character table size
        repne   scasb                   ; Scan the convert table for a match
        jne     Get_Char                ; Jump if an unknown convert character
        mov     di,CONVERT_SIZE         ; Get the convert table size
        sub     di,cx                   ; Compute the matching entry number
        dec     di                      ; Convert number to zero based
        shl     di,1                    ; Make number into jump table index
        call    cs:[di+Convert_Jump]    ; Call the correct routine
        jmp     Short Get_Char          ; Go get the next character
;
;  It is a format character, get the next character and check it
;
Do_Format:
        lodsb                           ; Get next character from format string
        push    cs                      ; Put copy of CS onto the stack
        pop     es                      ; Put copy of current CS into ES
        lea     di,cs:[Format_Table]    ; Setup the format character table
        mov     cx,FORMAT_SIZE          ; Get the format character table size
        repne   scasb                   ; Scan the format table for a match
        je      Go_Format               ; Jump if an there was a table match
        mov     ch,BASE_DECIMAL         ; Set the current base value to DECIMAL
        mov     cl,FORMAT_LENGTH        ; Set the format maximum number count
        cmp     al,POINT                ; Check for a decimal point
        jne     Chk_Var                 ; Jump if no decimal point found
        dec     si                      ; Correct pointer for no width value
        xor     dh,dh                   ; Setup a width value of zero
        jmp     Short Chk_Pre           ; Go check for a precision value
Chk_Var:
        cmp     al,ASTERISK             ; Check for variable width field
        jne     Do_Width                ; Jump if not a variable width field
        or      bp,VAR_WIDTH            ; Set variable width flag bit
        mov     dh,MAX_BYTE             ; Set width value as already set
        jmp     Short Chk_Pre           ; Go check for a precision value
Do_Width:
        call    Check_Digit             ; Check for numeric digit (Field width)
        jc      Do_Convert              ; Jump if an unknown format character
        dec     si                      ; Backup to the start of the number
        or      al,al                   ; Check for a leading zero
        jnz     Get_Width               ; Jump if first digit not a zero
        or      bp,PAD_CHAR             ; First digit zero, use zero pad
        or      bp,PRE_PAD              ; Set pre-pad sign character flag
Get_Width:
        call    Get_Number              ; Call routine to get the field width
        mov     dh,dl                   ; Save the field width in DH
        cmp     Byte Ptr ds:[si],ASTERISK
        jne     Chk_Pre                 ; Jump if not a variable width field
        inc     si                      ; Increment past variable character
        or      bp,VAR_WIDTH            ; Set variable width flag bit
        mov     dh,MAX_BYTE             ; Set width value as already set
Chk_Pre:
        xor     dl,dl                   ; Setup a precision of zero
        cmp     Byte Ptr ds:[si],POINT  ; Check for a decimal point
        jne     Do_Format               ; Jump if no precision given
        or      bp,FRACTIONAL           ; Set the fractional conversion flag
        dec     dl                      ; Set precision as already set
        inc     si                      ; Increment past the decimal point
        cmp     Byte Ptr ds:[si],ASTERISK
        jne     Get_Pre                 ; Jump if not a variable precision
        inc     si                      ; Increment past variable character
        or      bp,VAR_PRE              ; Set variable precision flag bit
        jmp     Short Do_Format         ; Go check for more format characters
Get_Pre:
        call    Get_Number              ; Call routine to get the precision
        adc     dl,0                    ; Setup correct precision value
        jmp     Short Do_Format         ; Go check for more format characters
Go_Format:
        mov     di,FORMAT_SIZE          ; Get the format table size
        sub     di,cx                   ; Compute the matching entry number
        dec     di                      ; Convert number to zero based
        shl     di,1                    ; Make number into jump table index
        call    cs:[di+Format_Jump]     ; Call the correct routine
        jmp     Short Do_Format         ; Go check for more format characters
;
;  Restore the registers and return to the caller
;
Printf_Exit:
        Restore ax,bx,cx,dx,si,di,bp,ds,es
        ret                             ; Return to the caller
        Page
;
;  Define the format specifier character and jump tables
;
Format_Table    Label   Byte
                Db      '-'             ; Left justify format specifier
                Db      '+'             ; Set signed specifier
                Db      't'             ; Short format specifier
                Db      'T'             ; Short format specifier
                Db      'l'             ; Long format specifier
                Db      'L'             ; Long format specifier
                Db      '#'             ; Far format specifier
                Db      '&'             ; Indirect format specifier
                Db      '$'             ; Immediate format specifier
FORMAT_SIZE     Equ     This Byte - Format_Table
Format_Jump     Label   Word
                Dw      Left_Justify
                Dw      Set_Signed
                Dw      Short_Specify
                Dw      Short_Specify
                Dw      Long_Specify
                Dw      Long_Specify
                Dw      Far_Specify
                Dw      Set_Indirect
                Dw      Set_Immediate
;
;  Define the escape character and jump tables
;
Escape_Table    Label   Byte
                Db      'n'             ; Newline escape character
                Db      't'             ; Horizontal tab escape character
                Db      'v'             ; Vertical tab escape character
                Db      'b'             ; Backspace escape character
                Db      'r'             ; Carriage return escape character
                Db      'f'             ; Form feed escape character
                Db      'x'             ; Output character (Hex representation)
ESCAPE_SIZE     Equ     This Byte - Escape_Table
Escape_Jump     Label   Word
                Dw      New_Line
                Dw      Horz_Tab
                Dw      Vert_Tab
                Dw      Back_Space
                Dw      Carr_Ret
                Dw      Form_Feed
                Dw      Out_Hex
;
;  Define the convert character and jump tables
;
Convert_Table   Label   Byte
                Db      '%'             ; Print the percent sign
                Db      'c'             ; Print next argument as a character
                Db      'C'             ; Print next argument as a character
                Db      's'             ; Print next argument as a string
                Db      'S'             ; Print next argument as a string
                Db      'x'             ; Print next argument as HEX (abcdef)
                Db      'X'             ; Print next argument as HEX (ABCDEF)
                Db      'h'             ; Print next argument as HEX (abcdef)
                Db      'H'             ; Print next argument as HEX (ABCDEF)
                Db      'd'             ; Print next argument as DECIMAL (+/-)
                Db      'D'             ; Print next argument as DECIMAL (+/-)
                Db      'u'             ; Print next argument as UNSIGNED
                Db      'U'             ; Print next argument as UNSIGNED
                Db      'o'             ; Print next argument as OCTAL
                Db      'O'             ; Print next argument as OCTAL
                Db      'b'             ; Print next argument as BINARY
                Db      'B'             ; Print next argument as BINARY
                Db      'f'             ; Print next argument as FRACTIONAL
                Db      'F'             ; Print next argument as FRACTIONAL
CONVERT_SIZE    Equ     This Byte - Convert_Table
Convert_Jump    Label   Word
                Dw      Print_Format    ; Print format routine
                Dw      Do_Char         ; Print character routine
                Dw      Do_Char         ; Print character routine
                Dw      Do_String       ; Print string routine
                Dw      Do_String       ; Print string routine
                Dw      Do_Hex_Lower    ; Print lowercase hexadecimal routine
                Dw      Do_Hex_Upper    ; Print uppercase hexadecimal routine
                Dw      Do_Hex_Lower    ; Print lowercase hexadecimal routine
                Dw      Do_Hex_Upper    ; Print uppercase hexadecimal routine
                Dw      Do_Decimal      ; Print signed decimal routine
                Dw      Do_Decimal      ; Print signed decimal routine
                Dw      Do_Unsigned     ; Print unsigned decimal routine
                Dw      Do_Unsigned     ; Print unsigned decimal routine
                Dw      Do_Octal        ; Print octal routine
                Dw      Do_Octal        ; Print octal routine
                Dw      Do_Binary       ; Print binary routine
                Dw      Do_Binary       ; Print binary routine
                Dw      Do_Fractional   ; Print decimal fractional routine
                Dw      Do_Fractional   ; Print decimal fractional routine
;
;  Define the end of the printf procedure
;
Printf          Endp                     ; End of the Printf procedure
        Page
;******************************************************************************
;
;       Format Specificer Routines
;
;
;               These routines handle the following format specifiers:
;
;
;       Specifier               Action Taken
;       ---------               ------------
;
;           -           The following field will be left justified.
;
;           +           The following field will be have a sign (+/-)
;                       if it is a signed type field (d).
;
;           t           The following field is a short value.
;
;           T           The following field is a short value.
;
;           l           The following field is a long value.
;
;           L           The following field is a long value.
;
;           #           The following argument has a far address.
;
;           &           The following argument has an indirect address.
;
;           $           The following argument is an immediate value.
;
;               These routines simply set or reset flags which are
;       used later during actual conversion to perform the special
;       formatting options.
;
;******************************************************************************
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Left_Justify()
;
;               Set the left justification flag
;               Return to the caller
;
;       Registers on Entry:
;
;               None
;
;       Registers on Exit:
;
;               BP    - Left justification flag set
;
;******************************************************************************
Left_Justify    Proc    Near            ; Left justify procedure
        or      bp,LEFT_JUST            ; Set the left justification flag
        ret                             ; Return to the caller
Left_Justify    Endp                    ; End of the Left_Justify procedure
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Set_Signed()
;
;               Set the signed flag
;               If the field width is non-zero
;                       Set the pre-pad sign flag
;               Endif
;               Return to the caller
;
;       Registers on Entry:
;
;               DH    - Field width (Not given if zero)
;
;       Registers on Exit:
;
;               BP    - Signed flag set
;
;******************************************************************************
Set_Signed      Proc    Near            ; Set signed procedure
        or      bp,SIGNED_CONV          ; Set the signed conversion flag
        or      dh,dh                   ; Check the field width
        jz      Sign_Ret                ; Jump if field width not set
        or      bp,PRE_PAD              ; Set the pre-pad sign flag
Sign_Ret:
        ret                             ; Return to the caller
Set_Signed      Endp                    ; End of the Set_Signed procedure
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Short_Specify()
;
;               Set the short specification flag
;               Return to the caller
;
;       Registers on Entry:
;
;               None
;
;       Registers on Exit:
;
;               BP    - Short specification flag set
;
;******************************************************************************
Short_Specify   Proc    Near            ; Short specify procedure
        or      bp,SHORT_SPEC           ; Set the short specification flag
        ret                             ; Return to the caller
Short_Specify   Endp                    ; End of the Short_Specify procedure
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Long_Specify()
;
;               Set the long specification flag
;               Return to the caller
;
;       Registers on Entry:
;
;               None
;
;       Registers on Exit:
;
;               BP    - Long specification flag set
;
;******************************************************************************
Long_Specify    Proc    Near            ; Long specify procedure
        or      bp,LONG_SPEC            ; Set the long specification flag
        ret                             ; Return to the caller
Long_Specify    Endp                    ; End of the Long_Specify procedure
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Far_Specify()
;
;               Set the far specification flag
;               Return to the caller
;
;       Registers on Entry:
;
;               None
;
;       Registers on Exit:
;
;               BP    - Far specification flag set
;
;******************************************************************************
Far_Specify     Proc    Near            ; Far specify procedure
        or      bp,FAR_SPEC             ; Set the far specification flag
        ret                             ; Return to the caller
Far_Specify     Endp                    ; End of the Far_Specify procedure
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Set_Indirect()
;
;               Set the indirect flag
;               Return to the caller
;
;       Registers on Entry:
;
;               None
;
;       Registers on Exit:
;
;               BP    - Indirection flag set
;
;******************************************************************************
Set_Indirect    Proc    Near            ; Set indirect procedure
        or      bp,INDIRECT             ; Set the indirection flag
        ret                             ; Return to the caller
Set_Indirect    Endp                    ; End of the Set_Indirect procedure
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Set_Immediate()
;
;               Set the immediate flag
;               Return to the caller
;
;       Registers on Entry:
;
;               None
;
;       Registers on Exit:
;
;               BP    - Immediate flag set
;
;******************************************************************************
Set_Immediate   Proc    Near            ; Set immediate procedure
        or      bp,IMMEDIATE            ; Set the immediate flag
        ret                             ; Return to the caller
Set_Immediate   Endp                    ; End of the Set_Immediate procedure
        Page
;******************************************************************************
;
;       Escape Sequence Routines
;
;
;               These routines handle the following escape sequences:
;
;
;       Character               Escape Sequence
;       ---------               ---------------
;
;           n           Newline is output to requested handle
;
;           t           Horizontal tab is output to requested handle
;
;           v           Vertical tab is output to requested handle
;
;           b           Backspace is output to requested handle
;
;           r           Carriage return is output to requested handle
;
;           f           Form feed is output to requested handle
;
;           x           Character is output to requested handle (Hex code)
;
;
;               All of these routines except for the "x" escape
;       sequence simply send the desired character(s) out to the
;       requested handle. The "x" routine get the hexadecimal
;       number given and outputs the corresponding character to
;       the requested handle.
;
;******************************************************************************
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       New_Line(Handle)
;
;               Output carriage return to handle
;               Output line feed to handle
;               Return to the caller
;
;       Registers on Entry:
;
;               AH    - Handle
;
;       Registers on Exit:
;
;               AL    - Destroyed
;
;******************************************************************************
New_Line        Proc    Near            ; Output new line procedure
        mov     al,CR                   ; Get carriage return ASCII character
        call    Output                  ; Output the carriage return to handle
        mov     al,LF                   ; Get a line feed ASCII character
        call    Output                  ; Output the line feed to handle
        ret                             ; Return to the caller
New_Line        Endp                    ; End of the New_Line procedure
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Horz_Tab(Handle)
;
;               Output horizontal tab to handle
;               Return to the caller
;
;       Registers on Entry:
;
;               AH    - Handle
;
;       Registers on Exit:
;
;               AL    - Destroyed
;
;******************************************************************************
Horz_Tab        Proc    Near            ; Output horizontal tab procedure
        mov     al,HT                   ; Get horizontal tab ASCII character
        call    Output                  ; Output the horizontal tab to handle
        ret                             ; Return to the caller
Horz_Tab        Endp                    ; End of the Horz_Tab procedure
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Vert_Tab(Handle)
;
;               Output vertical tab to handle
;               Return to the caller
;
;       Registers on Entry:
;
;               AH    - Handle
;
;       Registers on Exit:
;
;               AL    - Destroyed
;
;******************************************************************************
Vert_Tab        Proc    Near            ; Output vertical tab procedure
        mov     al,VT                   ; Get vertical tab ASCII character
        call    Output                  ; Output the vertical tab to handle
        ret                             ; Return to the caller
Vert_Tab        Endp                    ; End of the Vert_Tab procedure
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Back_Space(Handle)
;
;               Output backspace to handle
;               Return to the caller
;
;       Registers on Entry:
;
;               AH    - Handle
;
;       Registers on Exit:
;
;               AL    - Destroyed
;
;******************************************************************************
Back_Space      Proc    Near            ; Output backspace procedure
        mov     al,BS                   ; Get backspace ASCII character
        call    Output                  ; Output the backspace to handle
        ret                             ; Return to the caller
Back_Space      Endp                    ; End of the Back_Space procedure
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Carr_Ret(Handle)
;
;               Output carriage return to handle
;               Return to the caller
;
;       Registers on Entry:
;
;               AH    - Handle
;
;       Registers on Exit:
;
;               AL    - Destroyed
;
;******************************************************************************
Carr_Ret        Proc    Near            ; Output carriage return procedure
        mov     al,CR                   ; Get carriage return ASCII character
        call    Output                  ; Output the carriage return to handle
        ret                             ; Return to the caller
Carr_Ret        Endp                    ; End of the Carr_Ret procedure
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Form_Feed(Handle)
;
;               Output form feed to handle
;               Return to the caller
;
;       Registers on Entry:
;
;               AH    - Handle
;
;       Registers on Exit:
;
;               AL    - Destroyed
;
;******************************************************************************
Form_Feed       Proc    Near            ; Output form feed procedure
        mov     al,FF                   ; Get form feed ASCII character
        call    Output                  ; Output the form feed to handle
        ret                             ; Return to the caller
Form_Feed       Endp                    ; End of the Form_Feed procedure
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Out_Hex(String, Handle)
;
;               Set number base to hexadecimal
;               Set maximum number length
;               Call routine to get the number
;               Output the number (Character) to handle
;               Return to the caller
;
;       Registers on Entry:
;
;               AH    - Handle
;               DS:SI - Pointer to number
;
;       Registers on Exit:
;
;               AL    - Destroyed
;               CX    - Destroyed
;               DL    - Destroyed
;               DS:SI - Pointer set to first character past number
;
;******************************************************************************
Out_Hex         Proc    Near            ; Output hex character procedure
        mov     ch,BASE_HEX             ; Set number base to hexadecimal
        mov     cl,HEX_LENGTH           ; Set maximum hex digit length
        call    Get_Number              ; Call routine to get the number
        jc      Out_Exit                ; Jump if no number present
        mov     al,dl                   ; Move the number value into AL
        call    Output                  ; Output the corresponding character
Out_Exit:
        ret                             ; Return to the caller
Out_Hex         Endp                    ; End of the Out_Hex procedure
        Page
;******************************************************************************
;
;       Conversion Formatting Routines
;
;
;               These routines handle the following conversion types:
;
;
;       Character               Conversion Done
;       ---------               ---------------
;
;           c           Convert next argument as a character
;
;           s           Convert next argument as a string
;
;           x           Convert next argument as a hex number using abcdef
;
;           X           Convert next argument as a hex number using ABCDEF
;
;           d           Convert next argument as a decimal number (Signed)
;
;           u           Convert next argument as a decimal number (Unsigned)
;
;           o           Convert next argument as a octal number
;
;           b           Convert next argument as a binary number
;
;
;               These routines format the arguments passed to them.
;       Numeric arguments can be either word or double word (long)
;       values and for the decimal option it can be formatted as
;       signed or unsigned. All arguments are passed as pointers,
;       either near or far, to the actual argument value.
;
;******************************************************************************
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Do_Char(Argument, Handle, Flags)
;
;               Save the required registers
;               Call routine to check for variable width/precision
;               Call routine to get the character address
;               If field width is not set (Zero)
;                       Set field width to character value (1)
;               Endif
;               If character length (1) > field width
;                       Set the field overflow flag
;                       Set character length to field width
;               Endif
;               Set pad character to a space
;               Call routine to calculate pad counts
;               Call routine to output pre-string pad characters
;               Get character to output
;               Call routine to output character to handle
;               Call routine to output post-string pad characters
;               Restore the required registers
;               Return to the caller
;
;       Registers on Entry:
;
;               AH    - Handle
;               DS:BX - Pointer to argument
;               DH    - Current field width
;               BP    - Formatting flags
;
;       Registers on Exit:
;
;               AL    - Destroyed
;               BX    - Points to next argument
;               CX    - Destroyed
;               DL    - Destroyed
;               DI    - Destroyed
;               ES    - Destroyed
;
;******************************************************************************
Do_Char         Proc    Near            ; Character formatting procedure
        Save    si,ds                   ; Save the required registers
        call    Variable                ; Call routine to check width/precision
        call    Get_Address             ; Call routine to get argument address
        mov     cl,1                    ; Set actual width to character value
        or      dh,dh                   ; Check the current field width
        jnz     Width_Chk               ; Jump if field width specified
        mov     dh,1                    ; Set field width to character length
Width_Chk:
        cmp     cl,dh                   ; Compare actual width to given width
        jbe     Send_Pre                ; Jump if string fits in field width
        or      bp,OVER_FLOW            ; Set the field overflow flag
        mov     cl,dh                   ; Clip string to the field width
Send_Pre:
        mov     al,cl                   ; Save the actual string length
        and     bp,Not PAD_CHAR         ; Set current pad character to a space
        call    Calculate               ; Call routine to calculate pad values
        call    Pad                     ; Call routine to send pad characters
Send_Char:
        lodsb                           ; Get the character to output
        call    Output                  ; Call routine to output the character
Send_Post:
        mov     cl,ch                   ; Get the calculated pad counts
        call    Pad                     ; Call routine to send pad characters
        Restore si,ds                   ; Restore the required registers
        ret                             ; Return to the caller
Do_Char         Endp                    ; End of the Do_Char routine
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Do_String(Argument, Handle, Width, Flags)
;
;               Save the required registers
;               Call routine to check for variable width/precision
;               Call routine to get the string address
;               Call routine to compute string length
;               If field width is not set (Zero)
;                       Set field width to string length
;               Endif
;               If string length > field width
;                       Set the field overflow flag
;                       Set string length to field width
;               Endif
;               Set pad character to a space
;               Call routine to calculate pad counts
;               Call routine to output pre-string pad characters
;               Output the string characters
;               Call routine to output post-string pad characters
;               Restore the required registers
;               Return to the caller
;
;       Registers on Entry:
;
;               AH    - Handle
;               DS:BX - Pointer to argument
;               DH    - Current field width
;               BP    - Formatting flags
;
;       Registers on Exit:
;
;               AL    - Destroyed
;               BX    - Points to next argument
;               CX    - Destroyed
;               DL    - Destroyed
;               DI    - Destroyed
;               ES    - Destroyed
;
;******************************************************************************
Do_String       Proc    Near            ; String formatting procedure
        Save    si,ds                   ; Save the required registers
        call    Variable                ; Call routine to check width/precision
        call    Get_Address             ; Call routine to get argument address
        call    Get_Length              ; Call routine to get string length
        jz      String_Exit             ; Jump if nothing to output
        or      dh,dh                   ; Check the current field width
        jnz     Chk_Width               ; Jump if field width specified
        mov     dh,cl                   ; Set field width to string length
Chk_Width:
        cmp     cl,dh                   ; Compare actual width to given width
        jbe     Do_Pre                  ; Jump if string fits in field width
        or      bp,OVER_FLOW            ; Set the field overflow flag
        mov     cl,dh                   ; Clip string to the field width
Do_Pre:
        mov     al,cl                   ; Save the actual string length
        and     bp,Not PAD_CHAR         ; Set current pad character to a space
        call    Calculate               ; Call routine to calculate pad values
        mov     dl,al                   ; Setup the string output length
        call    Pad                     ; Call routine to send pad characters
Send_String:
        Save    ax,bx,cx,dx             ; Save the required registers
        mov     cl,dl                   ; Get the computed string length
        xor     ch,ch                   ; Convert string length to a full word
        mov     dx,si                   ; Get pointer to the character string
        mov     bl,ah                   ; Get the file handle value
        xor     bh,bh                   ; Convert file handle to a full word
        mov     ah,WRITE_FILE           ; Get write file function code
        int     DOS_FUNCTION            ; Try to write the string to the file
        Restore ax,bx,cx,dx             ; Restore the required registers
Do_Post:
        mov     cl,ch                   ; Get the calculated pad counts
        call    Pad                     ; Call routine to send pad characters
String_Exit:
        Restore si,ds                   ; Restore the required registers
        ret                             ; Return to the caller
Do_String       Endp                    ; End of the Do_String routine
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Do_Hex(Argument, Handle, Width, Precision, Flags, Type)
;
;               Save the required registers
;               If type is uppercase
;                       Set the uppercase format flag
;               Endif
;               Call routine to check for variable width/precision
;               Set current number base to hexadecimal
;               Call routine to get the argument address
;               Call routine to output the numeric string
;               Restore the required registers
;               Return to the caller
;
;       Registers on Entry:
;
;               AH    - Handle
;               DS:BX - Pointer to argument
;               DH    - Current field width
;               DL    - Current field precision
;               BP    - Formatting flags
;
;       Registers on Exit:
;
;               BX    - Points to next argument
;
;******************************************************************************
Do_Hex          Proc    Near            ; Hexadecimal formatting procedure
Do_Hex_Upper    Label   Near            ; Do_Hex_Upper entry point (ABCDEF)
        or      bp,UPPER_CASE           ; Set uppercase formatting flag
Do_Hex_Lower    Label   Near            ; Do_Hex_Lower entry point (abcdef)
        Save    si,ds                   ; Save the required registers
        call    Variable                ; Call routine to check width/precision
        mov     ch,BASE_HEX             ; Set the current number base to hex
        call    Get_Address             ; Call routine to get argument address
        call    Compute                 ; Call routine to output number
        Restore si,ds                   ; Restore the required registers
        ret                             ; Return to the caller
Do_Hex          Endp                    ; End of the Do_Hex routine
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Do_Decimal(Argument, Handle, Width, Precision, Flags)
;
;               Save the required registers
;               Call routine to check for variable width/precision
;               Set the signed type formatting flag
;               Set current number base to decimal
;               Call routine to get the argument address
;               Call routine to output the numeric string
;               Restore the required registers
;               Return to the caller
;
;       Registers on Entry:
;
;               AH    - Handle
;               DS:BX - Pointer to argument
;               DH    - Current field width
;               DL    - Current field precision
;               BP    - Formatting flags
;
;       Registers on Exit:
;
;               BX    - Points to next argument
;
;******************************************************************************
Do_Decimal      Proc    Near            ; Decimal formatting procedure
        Save    si,ds                   ; Save the required registers
        call    Variable                ; Call routine to check width/precision
        or      bp,SIGNED_TYPE          ; Set signed type formatting flag
        mov     ch,BASE_DECIMAL         ; Set current number base to decimal
        call    Get_Address             ; Call routine to get argument address
        call    Compute                 ; Call routine to output number
        Restore si,ds                   ; Restore the required registers
        ret                             ; Return to the caller
Do_Decimal      Endp                    ; End of the Do_Decimal routine
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Do_Unsigned(Argument, Handle, Width, Precision, Flags)
;
;               Save the required registers
;               Call routine to check for variable width/precision
;               Set current number base to decimal
;               Call routine to get the argument address
;               Call routine to output the numeric string
;               Restore the required registers
;               Return to the caller
;
;       Registers on Entry:
;
;               AH    - Handle
;               DS:BX - Pointer to argument
;               DH    - Current field width
;               DL    - Current field precision
;               BP    - Formatting flags
;
;       Registers on Exit:
;
;               BX    - Points to next argument
;
;******************************************************************************
Do_Unsigned     Proc    Near            ; Unsigned decimal formatting procedure
        Save    si,ds                   ; Save the required registers
        call    Variable                ; Call routine to check width/precision
        mov     ch,BASE_DECIMAL         ; Set current number base to decimal
        call    Get_Address             ; Call routine to get argument address
        call    Compute                 ; Call routine to output number
        Restore si,ds                   ; Restore the required registers
        ret                             ; Return to the caller
Do_Unsigned     Endp                    ; End of the Do_Unsigned routine
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Do_Octal(Argument, Handle, Width, Precision, Flags)
;
;               Save the required registers
;               Call routine to check for variable width/precision
;               Set current number base to octal
;               Call routine to get the argument address
;               Call routine to output the numeric string
;               Restore the required registers
;               Return to the caller
;
;       Registers on Entry:
;
;               AH    - Handle
;               DS:BX - Pointer to argument
;               DH    - Current field width
;               DL    - Current field precision
;               BP    - Formatting flags
;
;       Registers on Exit:
;
;               BX    - Points to next argument
;
;******************************************************************************
Do_Octal        Proc    Near            ; Octal formatting procedure
        Save    si,ds                   ; Save the required registers
        call    Variable                ; Call routine to check width/precision
        mov     ch,BASE_OCTAL           ; Set current number base to octal
        call    Get_Address             ; Call routine to get argument address
        call    Compute                 ; Call routine to output number
        Restore si,ds                   ; Restore the required registers
        ret                             ; Return to the caller
Do_Octal        Endp                    ; End of the Do_Octal routine
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Do_Binary(Argument, Handle, Width, Precision, Flags)
;
;               Save the required registers
;               Call routine to check for variable width/precision
;               Set current number base to binary
;               Call routine to get the argument address
;               Call routine to output the numeric string
;               Retore the required registers
;               Return to the caller
;
;       Registers on Entry:
;
;               AH    - Handle
;               DS:BX - Pointer to argument
;               DH    - Current field width
;               DL    - Current field precision
;               BP    - Formatting flags
;
;       Registers on Exit:
;
;               BX    - Points to next argument
;
;******************************************************************************
Do_Binary       Proc    Near            ; Binary formatting procedure
        Save    si,ds                   ; Save the required registers
        call    Variable                ; Call routine to check width/precision
        mov     ch,BASE_BINARY          ; Set current number base to binary
        call    Get_Address             ; Call routine to get argument address
        call    Compute                 ; Call routine to output number
        Restore si,ds                   ; Restore the required registers
        ret                             ; Return to the caller
Do_Binary       Endp                    ; End of the Do_Binary routine
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Do_Fractional(Argument, Handle, Width, Precision, Flags)
;
;               Save the required registers
;               Call routine to check for variable width/precision
;               Set the signed type formatting flag
;               Set current number base to decimal
;               Call routine to get the argument address
;               Call routine to output the numeric string
;               Restore the required registers
;               Return to the caller
;
;       Registers on Entry:
;
;               AH    - Handle
;               DS:BX - Pointer to argument
;               DH    - Current field width
;               DL    - Current field precision
;               BP    - Formatting flags
;
;       Registers on Exit:
;
;               BX    - Points to next argument
;
;******************************************************************************
Do_Fractional   Proc    Near            ; Fractional formatting procedure
        Save    si,ds                   ; Save the required registers
        call    Variable                ; Call routine to check width/precision
        or      bp,SIGNED_TYPE          ; Set signed type formatting flag
        or      bp,FRACTIONAL           ; Set the fractional formatting flag
        mov     ch,BASE_DECIMAL         ; Set current number base to decimal
        call    Get_Address             ; Call routine to get argument address
        call    Compute                 ; Call routine to output number
        Restore si,ds                   ; Restore the required registers
        ret                             ; Return to the caller
Do_Fractional   Endp                    ; End of the Do_Fractional routine
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Get_Address(Argument, Flags)
;
;               Save the required registers
;               If immediate specifier has been set
;                       Set DS:SI to current value of DS:BX
;                       Increment BX to next argument (1,2,4)
;               Else no immediate specifier
;                       If far format specifier has been set
;                               Set DS:SI to far pointer at DS:BX
;                               If indirect format specifier has been set
;                                       Set DS:SI to far pointer at DS:SI
;                               Endif
;                               Increment BX to next argument (4)
;                       Else no far specifier
;                               Set SI to near pointer at DS:BX
;                               If indirect format specifier has been set
;                                       Set SI to near pointer at DS:SI
;                               Endif
;                               Increment BX to next argument (2)
;                       Endif
;               Endif
;               Restore the required registers
;               Return to the caller
;
;       Registers on Entry:
;
;               DS:BX - Pointer to argument list
;               BP    - Formatting flags
;
;       Registers on Exit:
;
;               DS:SI - New address pointer (Character or string)
;               BX    - Points to the next argument
;
;******************************************************************************
Get_Address     Proc    Near            ; Get address procedure
        Save    ax                      ; Save the required registers
        test    bp,IMMEDIATE            ; Check for immediate specifier set
        jz      Check_Specifier         ; Jump if no immediate specifier
        mov     si,bx                   ; Setup the address into DS:SI
        mov     ax,NORMAL_SIZE          ; Default to normal size argument
Check_Short:
        test    bp,SHORT_SPEC           ; Check for a short immediate argument
        jz      Check_Long              ; Jump if not a short argument
        mov     ax,SHORT_SIZE           ; Setup to short size argument
        jmp     Short Immediate_Fixup   ; Go perform the address fixup
Check_Long:
        test    bp,LONG_SPEC            ; Check for a long immeidate argument
        jz      Immediate_Fixup         ; Jump if not a long argument
        mov     ax,LONG_SIZE            ; Setup to long size argument
Immediate_Fixup:
        add     bx,ax                   ; Update the argument pointer value
        jmp     Short Get_Exit          ; Go return control to the caller
Check_Specifier:
        test    bp,FAR_SPEC             ; Check for far specifier set
        jz      Near_Addr               ; Jump if a normal near address
Far_Addr:
        lds     si,Dword Ptr ds:[bx]    ; Load the far address into DS:SI
        test    bp,INDIRECT             ; Check for indirect address
        jz      Far_Fixup               ; Jump if no indirection specified
        lds     si,Dword Ptr ds:[si]    ; Get the indirect far address
Far_Fixup:
        add     bx,FAR_SIZE             ; Update the argument pointer value
        jmp     Short Get_Exit          ; Go return to the caller
Near_Addr:
        mov     si,Word Ptr ds:[bx]     ; Load the near address into SI
        test    bp,INDIRECT             ; Check for indirect address
        jz      Near_Fixup              ; Jump if no indirection specified
        mov     si,Word Ptr ds:[si]     ; Get the indirect near address
Near_Fixup:
        add     bx,NEAR_SIZE            ; Update the argument pointer value
Get_Exit:
        Restore ax                      ; Restore the required registers
        ret                             ; Return to the caller
Get_Address     Endp                    ; End of the Get_Address procedure
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Variable(Width, Precision, Flags)
;
;               Save the required registers
;               If variable width specified
;                       Get the actual width value (Byte)
;               Endif
;               If variable precision specified
;                       Get the actual precision value (Byte)
;               Endif
;               Restore the required registers
;               Return to the caller
;
;       Registers on Entry:
;
;               DS:BX - Pointer to argument list
;               BP    - Formatting flags
;
;       Registers on Exit:
;
;               DH    - Actual width value
;               DL    - Actual precision value
;               BX    - Points to the next argument
;
;******************************************************************************
Variable        Proc    Near            ; Get variable width/precision procedure
        Save    si                      ; Save the required registers
        test    bp,VAR_WIDTH            ; Check for a variable width value
        jz      Pre_Chk                 ; Jump if no variable width
        mov     si,Word Ptr ds:[bx]     ; Load the near address into SI
        add     bx,NEAR_SIZE            ; Update the argument pointer value
        mov     dh,Byte Ptr ds:[si]     ; Get the actual field width value
Pre_Chk:
        test    bp,VAR_PRE              ; Check for a variable precision value
        jz      Var_Exit                ; Jump if no variable precision
        mov     si,Word Ptr ds:[bx]     ; Load the near address into SI
        add     bx,NEAR_SIZE            ; Update the argument pointer value
        mov     dl,Byte Ptr ds:[si]     ; Get the actual precision value
Var_Exit:
        Restore si                      ; Restore the required registers
        ret                             ; Return to the caller
Variable        Endp                    ; End of the Variable procedure
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Get_Length(String)
;
;               Calculate the length of the string (Null terminator)
;               Return to the caller
;
;       Registers on Entry:
;
;               DS:SI - Pointer to string
;
;       Registers on Exit:
;
;               AL    - Destroyed
;               CX    - String length
;               DI    - Destroyed
;               ES    - Destroyed
;               ZR    - Zero set if zero length
;
;******************************************************************************
Get_Length      Proc    Near            ; Get string length procedure
        push    ds                      ; Put a copy of DS onto the stack
        pop     es                      ; Set ES to the current DS value
        mov     di,si                   ; Set DI to the current SI value
        mov     al,NULL                 ; Setup to scan for null terminator
        mov     cx,MAX_WORD             ; Setup to scan for maximum length
        repne   scasb                   ; Scan for the string terminator
        not     cx                      ; Correct the count value
        dec     cx                      ; Adjust to get actual string length
        ret                             ; Return to the caller
Get_Length      Endp                    ; End of the Get_Length routine
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Calculate(Width, Length)
;
;               Save the required registers
;               Calculate total pad length
;               If total pad length > 0
;                       If left justification is not requested
;                               Set pre pad count to total
;                               Zero post pad count
;                       Else left justification requested
;                               Set post pad count to total
;                               Zero pre pad count
;                       Endif
;               Else total pad length < 0
;                       Set pre pad count to zero
;                       Set post pad count to zero
;               Endif
;               Restore the required registers
;               Return to the caller
;
;       Registers on Entry:
;
;               CL    - Length of the string
;               DH    - Field width
;               BP    - Formatting flags
;
;       Registers on Exit:
;
;               CH    - Post string pad count
;               CL    - Pre string pad count
;
;******************************************************************************
Calculate       Proc    Near            ; Calculate pad length procedure
        Save    ax                      ; Save the required registers
        mov     al,dh                   ; Get the current field width
        mov     ah,cl                   ; Get the length of the output string
        xor     cx,cx                   ; Default pre/post pad counts to zero
        sub     al,ah                   ; Compute the total pad count
        jbe     Calc_Exit               ; Jump if no pad necessary
        mov     cl,al                   ; Default to right justification
        test    bp,LEFT_JUST            ; Check if left justify was specified
        jz      Calc_Exit               ; Jump if no left justification
        mov     ch,cl                   ; Make post pad count the total count
        xor     cl,cl                   ; Zero the pre pad count value
Calc_Exit:
        Restore ax                      ; Restore the required registers
        ret                             ; Return to the caller
Calculate       Endp                    ; End of the Calculate procedure
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Pad(Handle, Pad, Count)
;
;               Save the required registers
;               While count > 0
;                       Output the current pad character
;                       Decrement the count
;               Endwhile
;               Restore the required registers
;               Return to the caller
;
;       Registers on Entry:
;
;               AH    - Handle
;               CL    - Pad count
;
;       Registers on Exit:
;
;               CL    - Destroyed
;
;******************************************************************************
Pad             Proc    Near            ; Pad character procedure
        Save    ax                      ; Save the required registers
        or      cl,cl                   ; Check for no padding required
        jz      Pad_Exit                ; Jump if no padding is required
        mov     al,SPACE_PAD            ; Default to a space pad character
        test    bp,PAD_CHAR             ; Check for a zero pad character
        jz      Pad_Loop                ; Jump if using a space pad character
        mov     al,ZERO_PAD             ; Setup to use a zero pad character
Pad_Loop:
        call    Output                  ; Call routine to output pad character
        dec     cl                      ; Decrement the pad count
        jnz     Pad_Loop                ; Jump if more pad characters to send
Pad_Exit:
        Restore ax                      ; Restore the required registers
        ret                             ; Return to the caller
Pad             Endp                    ; End of the Pad procedure
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Clear_Flags(Flags)
;
;               Clear the formatting flags
;               Default to space padding
;               Zero the current field width
;               Clear direction flag (All string operations forward)
;               Return to the caller
;
;       Registers on Entry:
;
;               None
;
;       Registers on Exit:
;
;               BP    - Destroyed (BP contains the flags and is zeroed)
;               DH    - Set to zero (Current field width)
;               DL    - Set to zero (Current field precision)
;
;******************************************************************************
Clear_Flags     Proc    Near            ; Clear formatting flags procedure
        xor     bp,bp                   ; Clear all of the formatting flags
        xor     dh,dh                   ; Zero the current field width
        xor     dl,dl                   ; Zero the current field precision
        cld                             ; Clear the direction flag
        ret                             ; Return to the caller
Clear_Flags     Endp                    ; End of the Clear_Flags procedure
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Print_Format(Handle)
;
;               Save the required registers
;               Output format specification character to handle
;               Restore the required registers
;               Return to the caller
;
;       Registers on Entry:
;
;               AH    - Handle
;
;       Registers on Exit:
;
;               AL    - Destroyed
;
;******************************************************************************
Print_Format    Proc    Near            ; Output format specifier procedure
        mov     al,FORMAT_CHAR          ; Get format specifier character
        call    Output                  ; Output the format specifier to handle
        ret                             ; Return to the caller
Print_Format    Endp                    ; End of the Print_Format procedure
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Check_Digit(Base, Character)
;
;               Save the required registers
;               Call routine to convert character to binary
;               If the character can be converted
;                       If value is greater than base
;                               Set carry flag (Character not a digit)
;                       Endif
;               Else character cannot be converted
;                       Set carry flag (Character not a digit)
;               Endif
;               Restore the required registers
;               Return to the caller
;
;       Registers on Entry:
;
;               AL    - Digit to check (ASCII)
;               CH    - Number system base
;
;       Registers on Exit:
;
;               AL    - Binary value of digit (If it is a digit)
;               FL    - CY set if character is not a digit in current base
;
;******************************************************************************
Check_Digit     Proc    Near            ; Check digit procedure
        Save    bx                      ; Save the required registers
        Save    ax                      ; Save the original digit value
        call    Convert                 ; Call routine to convert character
        mov     bl,al                   ; Save converted value in BL
        Restore ax                      ; Restore the original digit value
        jc      Check_Exit              ; Jump if could not be converted
        cmp     bl,ch                   ; Check against current number base
        cmc                             ; Set correct carry flag state
        jc      Check_Exit              ; Jump if not valid for this base
        mov     al,bl                   ; Digit is valid, save binary value
Check_Exit:
        Restore bx                      ; Restore the required registers
        ret                             ; Return to the caller
Check_Digit     Endp                    ; End of the Check_Digit procedure
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Convert(Character)
;
;               If character is a decimal digit (0 to 9)
;                       Convert ASCII digit to binary (Subtract 30h)
;                       Clear the carry flag (Character could be converted)
;               Else character is not a decimal digit
;                       Convert character to uppercase
;                       If character is a hex digit (A to F)
;                               Convert ASCII digit to binary (Subtract 37h)
;                               Clear carry flag (Character could be converted)
;                       Else character is not a hex digit
;                               Set carry flag (Could not be converted)
;                       Endif
;               Endif
;               Return to the caller
;
;       Registers on Entry:
;
;               AL    - Digit to check (ASCII)
;               CH    - Number system base
;
;       Registers on Exit:
;
;               AL    - Binary value of digit (If it is a digit)
;               FL    - CY set if character is not a digit in current base
;
;******************************************************************************
Convert         Proc    Near            ; Convert character procedure
        sub     al,DECIMAL_ADJUST       ; Adjust character for ASCII decimal
        jc      Convert_Exit            ; Jump if below decimal limit
        cmp     al,BASE_DECIMAL         ; Check for a valid decimal character
        cmc                             ; Set carry flag to correct state
        jnc     Convert_Exit            ; Jump if there is a valid digit
        and     al,UPPER_MASK           ; Convert anything else to uppercase
        sub     al,HEX_ADJUST           ; Adjust character for ASCII hexadecimal
        cmp     al,BASE_DECIMAL         ; Check for a valid hex character
        jc      Convert_Exit            ; Jump if below hexadecimal limit
        cmp     al,BASE_HEX             ; Check against upper hex limit
        cmc                             ; Set carry flag to correct state
Convert_Exit:
        ret                             ; Return to caller with value and flag
Convert         Endp                    ; End of the Convert routine
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Output(Character, Handle)
;
;               Save the required registers
;               Output the character to requested handle
;               Restore the required registers
;               Return to the caller
;
;       Registers on Entry:
;
;               AL    - Character to output
;               AH    - Handle (For output)
;
;       Registers on Exit:
;
;               None
;
;******************************************************************************
Output          Proc    Near            ; Output character procedure
        Save    bx,cx,dx,ds             ; Save the required registers
        push    ss                      ; Put a copy of SS onto the stack
        pop     ds                      ; Setup DS to the current SS value
        Save    ax                      ; Save the character and handle values
        mov     dx,sp                   ; Setup buffer pointer into stack
        xchg    al,ah                   ; Put requested handle into AL
        cbw                             ; Convert handle to full word
        mov     bx,ax                   ; Move handle number to BX
        mov     cx,1                    ; Setup to write one character
        mov     ah,WRITE_FILE           ; Get the write file function code
        int     DOS_FUNCTION            ; Attempt to write the character
        Restore ax                      ; Restore the character/handle values
        Restore bx,cx,dx,ds             ; Restore required registers
        ret                             ; Return to the caller
Output          Endp                    ; End of the Output procedure
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Get_Number(String, Base, Length)
;
;               Save the required registers
;               While length > 0
;                       Decrement the length
;                       Get the next character from string
;                       Call routine to check for a valid digit
;                       If character is a valid digit
;                               Add new value into total
;                       Endif
;               Endwhile
;               Decrement pointer back to last character
;               Restore the required registers
;               Return to the caller
;
;       Registers on Entry:
;
;               CH    - Number Base
;               CL    - Maximum number length
;               DS:SI - Current string pointer
;
;       Registers on Exit:
;
;               CL    - Destroyed
;               DL    - Number retrieved (Zero if none)
;               DS:SI - Pointer to character following number
;               FL    - CY set if no number was found
;                       ZR set if zero number found
;
;******************************************************************************
Get_Number      Proc    Near            ; Get number procedure
        Save    ax,bx                   ; Save the required registers
        mov     dl,MAX_BYTE             ; Get maximum value for a byte
        xor     ax,ax                   ; Initialize the current total
Get_Loop:
        mov     bx,ax                   ; Save current total in BX register
        lodsb                           ; Get the next string character
        call    Check_Digit             ; Call routine to check for digit
        jc      Number_Done             ; Jump if this is not a valid digit
        inc     dl                      ; Increment no number present flag
        cbw                             ; Convert binary value into full word
        xchg    ax,bx                   ; Move total to AX, digit value in BX
        mul     ch                      ; Multiply current total by number base
        add     ax,bx                   ; Add in the new digit to current total
        dec     cl                      ; Decrement the number length count
        jnz     Get_Loop                ; Jump if more digits are allowed
        mov     bx,ax                   ; Move the current total into BX
        inc     si                      ; Increment to next character
Number_Done:
        dec     si                      ; Decrement back to non-digit character
        add     dl,1                    ; Set carry to indicate presence
        jc      Number_Exit             ; Jump if no number was present
        mov     dl,bl                   ; Save the computed number in DL
        or      dl,dl                   ; Set zero flag for zero result
Number_Exit:
        Restore ax,bx                   ; Restore the required registers
        ret                             ; Return to the caller
Get_Number      Endp                    ; End of the Get_Number procedure
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Compute(Argument, Handle, Width, Precision, Base, Flags)
;
;               Save the required registers
;               Allocate buffer space on the stack
;               If argument value is long
;                       Get the long numeric value (4 bytes)
;               Else if argument value is short
;                       Get the short numeric value (1 byte)
;                       Convert short to long value
;               Else argument value is normal
;                       Get the normal numeric value (2 bytes)
;                       Convert normal to long value
;               Endif
;               If signed type is specified
;                       If the value is signed
;                               Set the signed value flag
;                               Take the twos complement of the value
;                       Endif
;               Endif
;               Zero the fraction value (Integer default)
;               If fractional type conversion
;                       Separate number into integer and fraction
;               Endif
;               Convert integer to ASCII string in buffer
;               If fractional type conversion
;                       Convert fraction to ASCII string in buffer
;               Endif
;               Calculate the numeric string length
;               Default to no sign character
;               If signed type conversion
;                       If numeric value was signed (Negative)
;                               Setup minus sign as sign character
;                               Increment the string length (For sign character)
;                       Else numeric value was not signed
;                               If signed conversion was specified
;                                       Setup plus sign as sign character
;                                       Increment the string length
;                               Endif
;                       Endif
;               Endif
;               If field width is not set (Zero)
;                       Set field width to string length
;               Endif
;               If string length > field width
;                       Set the field overflow flag
;                       Set string length to field width - 1
;               Endif
;               Call routine to calculate pad counts
;               If sign character is present
;                       If pre-pad sign character
;                               Output sign character to handle
;                               Call routine to output pre-string pad characters
;                       Else post-pad sign character
;                               Call routine to output pre-string pad characters
;                               Output sign character to handle
;                       Endif
;               Else sign character is not present
;                       Call routine to output pre-string pad characters
;               Endif
;               While length > 0
;                       Get next character of string
;                       Call routine to output character to handle
;                       Decrement the length
;               Endwhile
;               Set current pad character to a space
;               Call routine to output post-string pad characters
;               If the field overflow flag is set
;                       Output the overflow character
;               Endif
;               Restore the required registers
;               Return to the caller
;
;       Registers on Entry:
;
;               AH    - Handle
;               DS:SI - Pointer to argument
;               CH    - Current number base
;               DH    - Current field width
;               DL    - Current field precision
;               BP    - Formatting flags
;
;       Registers on Exit:
;
;               FL    - DR flag is cleared (Move Forward)
;
;******************************************************************************
Compute         Proc    Near            ; Output numeric string procedure
        Save    bx,si,ds                ; Save the required registers
;
;  Allocate the buffer area on the current stack
;
        sub     sp,BUFF_SIZE            ; Allocate buffer space on the stack
        mov     di,sp                   ; Setup the buffer pointer
        add     di,BUFF_SIZE/2          ; Set the starting buffer position
        push    ax                      ; Save the output handle value
        push    dx                      ; Save the field width/precision
        xor     dh,dh                   ; Convert precision to a full word
        push    dx                      ; Save the field precision
        mov     cl,ch                   ; Move current base value into CL
        xor     ch,ch                   ; Make current base value into a word
        xor     ax,ax                   ; Default to an unsigned
        xor     dx,dx                   ;                        non-long value
        test    bp,SHORT_SPEC           ; Check for short value specified
        jnz     Get_Short               ; Jump if short value specified
        test    bp,LONG_SPEC            ; Check for long value specified
        jnz     Get_Long                ; Jump if long value specified
        mov     ax,ds:[si]              ; Get the normal value (2 Bytes)
        test    bp,SIGNED_TYPE          ; Check for a signed conversion
        jz      Chk_Frac                ; Jump if not a signed conversion
        cwd                             ; Signed conversion, do sign extension
        jmp     Short Chk_Sign          ; Go check for signed argument
Get_Short:
        mov     al,ds:[si]              ; Get the short value (1 Byte)
        test    bp,SIGNED_TYPE          ; Check for a signed conversion
        jz      Chk_Frac                ; Jump if not a signed conversion
        cbw                             ; Signed conversion,
        cwd                             ;                    do sign extension
        jmp     Short Chk_Sign          ; Go check for signed argument
Get_Long:
        mov     ax,ds:[si]              ; Get the
        mov     dx,ds:[si + 2]          ;         long value (4 Bytes)
;
;  If a signed type and a signed number, take the twos complement
;
Chk_Sign:
        test    bp,SIGNED_TYPE          ; Check for a signed type
        jz      Chk_Frac                ; Jump if not signed
        test    dx,SIGNED               ; Check the number for signed
        jz      Chk_Frac                ; Jump if number is not signed
        or      bp,SIGNED_VAL           ; Set the signed value flag
        not     ax                      ; Ones complement of the LSW
        not     dx                      ; Ones complement of the MSW
        add     ax,1                    ; Twos complement of the LSW
        adc     dx,0                    ; Twos complement of the MSW
;
;  If a fractional type, separate the integer and the fraction
;
Chk_Frac:
        mov     si,ss                   ; Get the current stack segment
        mov     ds,si                   ; Setup DS to current stack segment
        mov     es,si                   ; Setup ES to current stack segment
        xor     bx,bx                   ; Zero fraction value (Integer default)
        test    bp,FRACTIONAL           ; Check for fractional conversion
        jz      Do_Integer              ; Jump if standard integer conversion
        test    bp,SHORT_SPEC           ; Check for short value specified
        jnz     Do_Short                ; Jump if a short fractional value
        test    bp,LONG_SPEC            ; Check for long value specified
        jnz     Do_Long                 ; Jump if a long fractional value
        mov     bh,al                   ; Move fraction value to MSB (BH)
        xor     bl,bl                   ; Zero the lower LSB of fraction (BL)
        mov     al,ah                   ; Move integer value to LSB (AL)
        xor     ah,ah                   ; Zero the upper MSB of integer (AH)
        xor     dx,dx                   ; Zero the upper MSW of integer (DX)
        jmp     Short Do_Integer        ; Go convert the integer portion
Do_Long:
        mov     bx,ax                   ; Move fraction value to BX
        mov     ax,dx                   ; Move integer value to LSW (AX)
        xor     dx,dx                   ; Zero the upper MSW of integer (DX)
        jmp     Short Do_Integer        ; Go convert the integer portion
Do_Short:
        mov     bh,al                   ; Move fraction value to MSB (BH)
        xor     bl,bl                   ; Zero the lower LSB of fraction (BL)
        xor     ax,ax                   ; Zero the lower LSW of integer (AX)
        xor     dx,dx                   ; Zero the upper MSW of integer (DX)
;
;  Convert the integer to ASCII and store in the buffer
;
Do_Integer:
        push    di                      ; Save the starting position
        cld                             ; Clear direction flag (Move forward)
Integer_Loop:
        mov     si,ax                   ; Save LSW of value in SI register
        mov     ax,dx                   ; Move MSW of value into AX
        xor     dx,dx                   ; Setup to do the first divide
        div     cx                      ; Divide the MSW by the current base
        xchg    ax,si                   ; Setup for the second division
        div     cx                      ; Divide the result by the current base
        xchg    ax,dx                   ; Put the remainder into AX
        call    Digit                   ; Call routine to convert it to a digit
        stosb                           ; Store the ASCII digit into buffer
        xchg    ax,dx                   ; Restore quotient of second division
        mov     dx,si                   ; Restore quotient of first division
        or      si,ax                   ; Check for a zero result
        jnz     Integer_Loop            ; Jump if more digits to get
;
;  Convert the fraction (If any) to ASCII and store in the buffer
;
Do_Fraction:
        pop     dx                      ; Restore the starting position
        pop     si                      ; Restore the field precision
        mov     ax,bx                   ; Get the fraction value into AX
        mov     bx,di                   ; Save the final position in BX
        mov     di,dx                   ; Get the starting position
        test    bp,FRACTIONAL           ; Check for fractional conversion
        jz      Calc_Length             ; Jump if standard integer conversion
        pop     dx                      ; Restore the field width/precision
        push    dx                      ; Save the field width/precision
        or      dh,dh                   ; Check for a zero field width value
        jnz     Set_Position            ; Jump if field width was given
        mov     dx,bx                   ; Get the final position value
        sub     dx,di                   ; Compute the actual string length
        dec     dx                      ; Check for a single digit
        jnz     Set_Position            ; Jump if more than a single digit
        cmp     Byte Ptr es:[di],ZERO_PAD
        jne     Set_Position            ; Jump if single digit is NOT a zero
        test    bp,PAD_CHAR             ; Check for zero character pad
        jz      Set_Direction           ; Jump if a blank character pad
Set_Position:
        dec     di                      ; Decrement to get new start position
Set_Direction:
        std                             ; Set direction flag (Move Backward)
        mov     Byte Ptr es:[di],POINT  ; Put a decimal point into buffer
        or      si,si                   ; Check for zero precision
        jz      Calc_Length             ; Jump if no digits to compute
        dec     di                      ; Update pointer for decimal point
Fraction_Loop:
        mul     cx                      ; Multiply by the current base
        xchg    ax,dx                   ; Put the MSW of result into AX
        call    Digit                   ; Call routine to convert it to a digit
        stosb                           ; Store the ASCII digit into buffer
        xchg    ax,dx                   ; Restore fraction from multiply
        dec     si                      ; Decrement the precision count
        jnz     Fraction_Loop           ; Jump if more digits left to do
        inc     di                      ; Correct for length calculation
Do_Round:
        mul     cx                      ; Compute the next actual digit
        shl     dx,1                    ; Multiply digit value by two
        cmp     dx,cx                   ; Compare value to current base
        jb      Calc_Length             ; Jump if below current base value
        push    di                      ; Save the current position
        mov     ch,cl                   ; Move current base to CH
Round_Loop:
        mov     al,es:[di]              ; Get the digit to round
        call    Convert                 ; Convert the ASCII to binary
        inc     al                      ; Round the digit up
        mov     ah,al                   ; Save the rounded value in AH
        call    Digit                   ; Convert the binary digit to ASCII
        cmp     ah,ch                   ; Check the value against current base
        jb      Round_Done              ; Jump if rounding is complete
        xor     al,al                   ; Zero the AL register value
        call    Digit                   ; Convert to an ASCII zero value
        mov     es:[di],al              ; Zero the current digit value
        inc     di                      ; Increment to the next digit position
        cmp     di,bx                   ; Check against final position
        jbe     Round_Loop              ; Jump if more digits to round with
        mov     bx,di                   ; Update the new final position
        xor     al,al                   ; Zero the AL register value
        inc     al                      ; Increment AL to a one value
        call    Digit                   ; Convert the one value to ASCII
Round_Done:
        mov     es:[di],al              ; Save the last rounded digit
        pop     di                      ; Restore the current position
;
;  Calculate the length of the numeric ASCII string
;
Calc_Length:
        pop     dx                      ; Restore field width/precision
        pop     ax                      ; Restore the file handle
        mov     cx,bx                   ; Get the final buffer pointer
        sub     cx,di                   ; Compute the numeric string length
;
;  Determine whether or not a sign character is needed for the value
;
        xor     al,al                   ; Default to no sign character
        test    bp,SIGNED_TYPE          ; Check for signed type
        jz      Do_Check                ; Jump if not a signed type
        test    bp,SIGNED_VAL           ; Check for a signed value
        jz      Chk_Conv                ; Jump if not a signed value
        mov     al,MINUS                ; Setup minus as sign character
        inc     cx                      ; Increment the string length
        jmp     Short Do_Check          ; Go check the field width
Chk_Conv:
        test    bp,SIGNED_CONV          ; Check for a signed conversion
        jz      Do_Check                ; Jump if not a signed type
        mov     al,PLUS                 ; Setup plus as sign character
        inc     cx                      ; Increment the string length
;
;  Setup the correct field width based on string length
;
Do_Check:
        or      dh,dh                   ; Check the current field width
        jnz     Width_Check             ; Jump if field width specified
        mov     dh,cl                   ; Set field width to string length
Width_Check:
        cmp     cl,dh                   ; Check actual width to field width
        jbe     Do_Calc                 ; Jump if string fits in the field
        or      bp,OVER_FLOW            ; Set the field overflow flag
        mov     cl,dh                   ; Set string width to field width
        dec     cl                      ; Adjust for the overflow character
        jz      Compute_Exit            ; Jump if no more room in the field
;
;  Calculate the pad counts and handle outputting a sign if necessary
;
Do_Calc:
        push    ax                      ; Save the sign character (If any)
        mov     al,cl                   ; Save the actual string length
        call    Calculate               ; Call routine to calculate pad values
        mov     dl,al                   ; Setup the string output length
        pop     ax                      ; Restore the sign character (If any)
        test    bp,PRE_PAD              ; Check for pre pad sign character
        jz      Do_Pad                  ; Jump if not pre pad sign character
        or      al,al                   ; Check for a sign needed
        jz      Do_Pad                  ; Jump if no sign is needed
        call    Output                  ; Call routine to output sign character
        dec     dl                      ; Decrement the output count
        jz      Compute_Exit            ; Jump if no more room in field
Do_Pad:
        call    Pad                     ; Call routine to output pad characters
        test    bp,PRE_PAD              ; Check for post pad sign character
        jnz     Do_Setup                ; Jump if not post pad sign character
        or      al,al                   ; Check for a sign needed
        jz      Do_Setup                ; Jump if no sign character needed
        call    Output                  ; Call routine to output the sign
        dec     dl                      ; Decrement the output count
        jz      Compute_Exit            ; Jump if no more room in field
Do_Setup:
        mov     si,bx                   ; Setup the source pointer to buffer
        dec     si                      ; Point back to first character
        std                             ; Set direction flag (Reverse order)
;
;  Send the string characters to the output handle
;
Send_Loop:
        lodsb                           ; Get the next character to output
        call    Output                  ; Call routine to output character
        dec     dl                      ; Decrement the output count
        jnz     Send_Loop               ; Jump if more characters to output
        mov     cl,ch                   ; Get the calculated pad counts
;
;  Change pad character, output pad characters and deallocate buffer
;
        and     bp,Not PAD_CHAR         ; Set pad character to a space
        call    Pad                     ; Call routine to send pad characters
;
;  Restore the registers and return to the caller
;
Compute_Exit:
        test    bp,OVER_FLOW            ; Check for field overflow
        jz      Compute_Done            ; Jump if no field overflow
        mov     al,ASTERISK             ; Get the field overflow character (*)
        call    Output                  ; Output the field overflow character
Compute_Done:
        add     sp,BUFF_SIZE            ; Deallocate the buffer area
        Restore bx,si,ds                ; Restore the required registers
        cld                             ; Clear the direction flag
        ret                             ; Return to the caller
Compute         Endp                    ; End of the Compute procedure
        Page
;******************************************************************************
;
;  Routine Functional Description
;
;       Digit(Value, Flags)
;
;               Save the required registers
;               Translate character to ASCII value
;               If uppercase flag is set
;                       If ASCII value is not a digit (abcdef)
;                               Convert to uppercase  (ABCDEF)
;                       Endif
;               Endif
;               Restore the required registers
;               Return to the caller
;
;       Registers on Entry:
;
;               AL    - Binary value (0 - 15)
;
;       Registers on Exit:
;
;               AL    - ASCII character
;
;******************************************************************************
Digit           Proc    Near            ; Convert to ASCII digit procedure
        Save    bx                      ; Save the required registers
        lea     bx,cs:[Digit_Table]     ; Get pointer to digit translate table
        xlat    byte ptr cs:[bx]        ; Translate to ASCII digit
        test    bp,UPPER_CASE           ; Check for uppercase flag
        jz      Digit_Exit              ; Jump if no uppercase flag set
        cmp     al,DIGIT_MAX            ; Check for uppercase adjust needed
        jbe     Digit_Exit              ; Jump if adjustment not needed
        and     al,UPPER_MASK           ; Convert character to uppercase
Digit_Exit:
        Restore bx                      ; Restore the required registers
        ret                             ; Return to the caller
Digit           Endp                    ; End of the Digit procedure
;
;  Define the ASCII digit translation table
;
Digit_Table     Label   Byte            ; Digit translation table
        Db      "0123456789abcdef"
;
;  Define the end of the standard code segment
;
Code    Ends                            ; End of the standard code segment
        End                             ; End of the Printf module
