TITLE	PRINTF ROUITNE FOR MS-DOS
;
; PRINTF(Control String, arg1, arg2,...,argn-1,argn)
;
; Characters are output to PFHandle according to the
; specifications contained in the Control String.
;
; The conversion characters are as follow:
;
;		%c - output the next argument as a character
;		%s - output the next argument as a string
;		%x - output the next argument as a hexidecimal number
;		     using abcedf
;		%X - output the next argument as a hexidecimal number
;		     using ABCDEF
;		%d - output the next argument as a decimal number
;
;
; Other format specifiers that may precede the conversion character are:
;
;		- (minus sign) - causes the field to be left-adjusted
;		+ (plus sign)  - causes the field to be right-adjusted (default)
;		n - digit specifing the minimum field width (default to 1)
;		L - specifing a long integer
;
;   On entry to PRINTF the stack contains the return address and a pointer
;   to an argument list.
;
;   ____________________
;   |	Ret Addr       |      <= SP
;   --------------------
;   |  Ptr to Arg List |
;   --------------------
;
;   And the argument list contains the following:
;
;	String_ptr		    (a pointer to the control string)
;	Arg 1
;	Arg 2
;	  .
;	  .
;	  .
;	Arg n-1
;	Arg n
;
;   If the argument is a %s or %c the arg contains a pointer to the string
;   or character.
;
;   The arguments are used in one-to-one correspondence to % specifiers.

.xlist
.xcref
	INCLUDE dossym.asm
.cref
.list

printf_CODE SEGMENT public byte
ASSUME	CS:PRINTF_CODE,DS:NOTHING,ES:NOTHING,SS:NOTHING

	PUBLIC	PRINTF, PFHandle
	PUBLIC	PRINTF_LAST

PFHandle	DW  1
PRINTF_LEFT	DB  0
PRINTF_LONG	DB  0
PRINTF_HEX	DB  0
TABLE_INDEX	DB  0
S_FLAG		DB  0
PRINTF_WIDTH	DW  0
PRINTF_BASE	DW  0
PAD_CHAR	DB  " "

PRINTF_TABLE DB "0123456789ABCDEFabcdef"

PRINTF_STACK   STRUC
OLDES	DW  ?
OLDDS	DW  ?
OLDSI	DW  ?
OLDDI	DW  ?
OLDAX	DW  ?
OLDBX	DW  ?
OLDCX	DW  ?
OLDDX	DW  ?
OLDBP	DW  ?
OLDCS	DW  ?
OLDIP	DW  ?
STRING	DW  ?
PRINTF_STACK   ENDS

PRINTF_ARGS    STRUC
CONSTR	DW ?
ARG	DW ?
PRINTF_ARGS    ENDS

RET_ADDR1  DW  ?
RET_ADDR2  DW  ?

BUFSIZ	=      20
PRINTF_BUF DB  BUFSIZ DUP (?)
	db	0			;This buffer is always nul terminated
BUFEND DW   $-PRINTF_BUF

PRINTF	proc	far
	PUSH	BP			;Save the callers' registers
	PUSH	DX
	PUSH	CX
	PUSH	BX
	PUSH	AX
	PUSH	DI
	PUSH	SI
	PUSH	ES
	PUSH	DS
	MOV	BP,SP
	PUSH	CS
	POP	ES			;ES points to Printf segment
	MOV	DI,OFFSET PRINTF_BUF	;DI points to the output buffer
	MOV	BP,[BP.STRING]		;BP points to the argument list
	MOV	SI,DS:[BP]		;SI points to the control string
	XOR	BX,BX			;BX is the index into the arg list
	CALL	Clear_flags		; initialize the world
GET_CHAR:
	LODSB				;Get a character
	CMP	AL,"%"                  ;Is it a conversion specifier?
	JZ	CONV_CHAR		;Yes - find out which one
	OR	AL,AL			;Is it the end of the control string?
	JZ	PRINTF_DONE		;Yes - then we're done
	CALL	OUTCHR			;Otherwise store the character
	JMP	SHORT GET_CHAR		;And go get another

PRINTF_DONE:
	CALL	FLUSH
	POP	DS
	POP	ES
	POP	SI
	POP	DI
	POP	AX
	POP	BX
	POP	CX
	POP	DX
	POP	BP
	POP	CS:[RET_ADDR1]		       ;Fix up the stack
	POP	CS:[RET_ADDR2]
	POP	AX
	PUSH	CS:[RET_ADDR2]
	PUSH	CS:[RET_ADDR1]
	RET

printf	endp

PRINTF_PERCENT:
	CALL	OUTCHR
	JMP	GET_CHAR

CONV_CHAR:
	;Look for any format specifiers preceeding the conversion character
	LODSB
	CMP	AL,"%"                      ;Just print the %
	JZ	PRINTF_PERCENT
	CMP	AL,"-"                      ;Right justify the field
	JZ	LEFT_ADJ
	CMP	AL,"+"                      ;Left justify the field
	JZ	NXT_CONV_CHAR
	CMP	AL,"L"                      ;Is it a long integer
	JZ	LONG_INT
	CMP	AL,"l"
	JZ	LONG_INT
	CMP	AL,"0"                      ;Is it a precision specification
	JB	LOOK_CONV_CHAR
	CMP	AL,"9"
	JA	LOOK_CONV_CHAR
	CMP	AL,"0"
	JNZ	NOT_PAD
	CMP	CS:[PRINTF_WIDTH],0
	JNZ	NOT_PAD
	MOV	CS:BYTE PTR [PAD_CHAR],"0"
NOT_PAD:
	PUSH	AX			    ;Adjust decimal place on precision
	MOV	AX,10
	MUL	CS:[PRINTF_WIDTH]
	MOV	CS:[PRINTF_WIDTH],AX
	POP	AX
	XOR	AH,AH
	SUB	AL,"0"
	ADD	CS:[PRINTF_WIDTH],AX	       ;And save the total
	JMP	SHORT NXT_CONV_CHAR

	;Set the correct flags for the options in a conversion

LEFT_ADJ:
	INC	CS:BYTE PTR[PRINTF_LEFT]
	JMP	SHORT NXT_CONV_CHAR

LONG_INT:
	INC	CS:BYTE PTR[PRINTF_LONG]
NXT_CONV_CHAR:
	JMP	CONV_CHAR

	;Look for a conversion character

LOOK_CONV_CHAR:
	CMP	AL,"X"
	JZ	HEX_UP

	;Make all other conversion characters upper case

	CMP	AL,"a"
	JB	CAPS
	CMP	AL,"z"
	JG	CAPS
	AND	AL,0DFH
CAPS:
	CMP	AL,"X"
	JZ	HEX_LO
	CMP	AL,"D"
	JZ	DECIMAL
	CMP	AL,"C"
	JZ	C_PUT_CHAR
	CMP	AL,"S"
	JZ	S_PUT_STRG

	;Didn't find any legal conversion character - IGNORE it

	call	clear_flags
	jmp	get_char

HEX_LO:
	MOV	CS:[TABLE_INDEX],6		;Will print lower case hex digits
HEX_UP:
	MOV	CS:[PRINTF_BASE],16    ;Hex conversion
	JMP	CONV_TO_NUM

DECIMAL:
	MOV	CS:[PRINTF_BASE],10    ;Decimal conversion
	JMP	CONV_TO_NUM

S_PUT_STRG:
	INC	CS:[S_FLAG]	       ;It's a string specifier
C_PUT_CHAR:
	PUSH	SI		       ;Save pointer to control string
	MOV	SI,BX
	ADD	BX,2
	MOV	SI,ds:[BP+SI.ARG]      ;Point to the % string or character
	CMP	BYTE PTR CS:[S_FLAG],0
	JNZ	S_PUT_1
	LODSB
	cmp	al,0
	jz	short c_s_end
	CALL	OUTCHR		       ;Put it into our buffer
	JMP	SHORT C_S_END

S_PUT_1:
	mov	cx,cs:[printf_width]
	or	cx,cx
	jz	s_put_2
	cmp	cs:byte ptr[printf_left],0
	jnz	s_put_2
	push	si
	call	Pad_string
	pop	si
s_put_2:
	push	si
s_put_3:
	LODSB			       ;Put them all in our buffer
	CMP	AL,0
	jz	s_put_4
	CALL	OUTCHR
	jmp	short S_PUT_3
s_put_4:
	pop	si
	cmp	byte ptr[printf_left],0
	jz	c_s_end
	mov	cx,cs:[printf_width]
	or	cx,cx
	jz	c_s_end
	call	Pad_string
C_S_END:
	call	clear_flags
	POP	SI		       ;Restore control string pointer
	JMP	GET_CHAR	       ;Go get another character

pad_string:
	xor	dx,dx
count_loop:
	lodsb
	or	al,al
	jz	count_done
	inc	dx
	jmp	short count_loop
count_done:
	sub	cx,dx
	jbe	count_ret
	call	pad
count_ret:
	ret

CONV_TO_NUM:

	PUSH	SI		    ;Save pointer to control string
	MOV	SI,BX		    ;Get index into argument list
	ADD	BX,2		    ;Increment the index
	MOV	AX,ds:[BP+SI.ARG]   ;Lo word of number in SI
	CMP	BYTE PTR CS:[PRINTF_LONG],0	;Is this is a short or long integer?
	JZ	NOT_LONG_INT
	MOV	SI,BX			     ;Copy index
	ADD	BX,2			     ;Increment the index
	MOV	DX,ds:[BP+SI.ARG]	     ;Hi word of number in BP
	JMP	SHORT DO_CONV
NOT_LONG_INT:
	XOR	DX,DX			     ;Hi word is zero
DO_CONV:
	PUSH	BX			     ;Save index into arguemnt list
	MOV	si,CS:[PRINTF_BASE]
	MOV	cx,CS:[PRINTF_WIDTH]
	CALL	PNUM
	CALL	PAD
CONV_DONE:
	call	clear_flags
	POP	BX
	POP	SI
	jmp	get_char

PNUM:
	DEC	CX
	PUSH	AX
	MOV	AX,DX
	XOR	DX,DX
	DIV	SI
	MOV	BX,AX
	POP	AX
	DIV	SI
	XCHG	BX,DX
	PUSH	AX
	OR	AX,DX
	POP	AX
	JZ	DO_PAD
	PUSH	BX
	CALL	PNUM
	POP	BX
	JMP	SHORT REM
DO_PAD:
	CMP	CS:BYTE PTR[PRINTF_LEFT],0
	JNZ	REM
	CALL	PAD
REM:
	MOV	AX,BX
	CMP	AL,10
	JB	NOT_HEX
	CMP	CS:BYTE PTR [PRINTF_HEX],0
	JNZ	NOT_HEX
	ADD	AL,CS:BYTE PTR [TABLE_INDEX]
NOT_HEX:
	MOV	BX,OFFSET PRINTF_TABLE
	PUSH	DS
	PUSH	CS
	POP	DS
	XLAT	0
	POP	DS
	push	cx
	CALL	OUTCHR
	pop	cx
	RET

PAD:
	OR	CX,CX
	JLE	PAD_DONE
	MOV	AL,CS:BYTE PTR [PAD_CHAR]
PAD_LOOP:
	push	cx
	CALL	OUTCHR
	pop	cx
	LOOP	PAD_LOOP
PAD_DONE:
	RET

OUTCHR:
	STOSB
	CMP	DI,offset bufend-1	;Don't count the nul
	RETNZ
	MOV	CX,BUFSIZ
WRITE_CHARS:
	push	bx
	MOV	BX,PFHandle
	push	ds
	PUSH	CS
	POP	DS
	MOV	DX,OFFSET PRINTF_BUF
	MOV	AH,WRITE
	INT	21H
	pop	ds
	pop	bx
	MOV	DI,OFFSET PRINTF_BUF
	RET

FLUSH:
	CMP	DI,OFFSET PRINTF_BUF
	RETZ
	SUB	DI,OFFSET PRINTF_BUF
	MOV	CX,DI
	call	write_chars
	ret

CLEAR_FLAGS:
	XOR	ax,ax
	MOV	BYTE PTR CS:[PRINTF_LEFT],al	   ;Reset justifing flag
	MOV	BYTE PTR CS:[PRINTF_LONG],al	   ;Reset long flag
	MOV	BYTE PTR CS:[TABLE_INDEX],al	   ;Reset hex table index
	MOV	CS:[PRINTF_WIDTH],ax		   ;Reinitialize width to 0
	MOV	BYTE PTR CS:[PAD_CHAR]," "         ;Reset padding character
	MOV	BYTE PTR CS:[S_FLAG],al 	   ;Clear the string flag
	ret

PRINTF_LAST LABEL   WORD
printf_CODE    ENDS
	END
