	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
