;---------------
;   HEXOUT
;---------------

; (C)1986 Eric Isaacson.  Permission to modify or use this file is granted
;    to registered A86 and D86 users only.  I grant public domain to the
;    .COM file that results from assembling this source file.

; HEXOUT is a program that accepts a sequence of hexadecimal numbers in its
;   invocation line, and outputs the associated binary values to standard
;   output.  This is useful, for example, to send a sequence of control
;   codes to your printer.  You could type HEXOUT >PRN followed by the hex
;   values you want to be sent.

; HEXOUT assumes correct input; it does not support error detection.


TAIL_BUFF EQU 081   ; we'll read command tail right where MSDOS gives it to us

HEXOUT:
  MOV SI,TAIL_BUFF  ; point to the command tail
L1:		    ; loop here to fetch every hex byte
  CALL GET_HEX	    ; fetch the next value
  JC >L2	    ; jump if there were no more values
  CALL OUT_VALUE    ; output the resulting byte
  JMP L1	    ; loop for another byte

L2:		    ; scanning is complete
  MOV AX,04C00	    ; MS-DOS function-number for successful exit
  INT 33	    ; go back to the operating system


; GET_HEX advances SI to the next hex number, and reads that number.  If we
;   reach the terminating carriage-return before the next hex digit, we
;   return Carry.  Otherwise, we return NoCarry, with AL set to the value
;   of the hex number, and SI advanced beyond the number.

GET_HEX:
  LODSB 	    ; fetch the next byte
  CMP AL,0D	    ; is it the terminator?
  STC		    ; set Carry in case it is
  JE RET	    ; return Carry if it is
  CALL HEX_DIGIT?   ; is the byte a hex digit?
  JC GET_HEX	    ; loop if not, to find the first hex digit
  MOV AH,AL	    ; it is a digit: save the value in AH
  LODSB 	    ; fetch the next byte
  CALL HEX_DIGIT?   ; is it also a hex digit?
  JC >L1	    ; jump if not: 1-digit value
  SHL AH,1	    ; two hex digits: pack the values into AL
  SHL AH,1	    ; AH * 4
  SHL AH,1	    ; AH * 8
  SHL AH,1	    ; AH * 16
  OR AL,AH	    ; AL = AH * 16 + AL -- values are now packed into AL
  RET		    ; NoCarry set by OR signals success

L1:		    ; the number has a single digit
  DEC SI	    ; retreat input pointer back to the following non-digit
  MOV AL,AH         ; fetch the single digit's value
  CLC		    ; NoCarry signals success
  RET


; HEX_DIGIT? returns NoCarry if AL is an ASCII hex digit; it also transforms
;   AL into the associated binary value.  Return Carry if AL was not a hex
;   digit.

HEX_DIGIT?:
  SUB AL,'0'        ; reduce decimal digits to their binary values
  JC RET	    ; return Carry if AL was below decimal-digit range
  CMP AL,10	    ; was the value a decimal digit?
  JB >L1	    ; return NoCarry if it was
  ADD AL,'0'        ; restore input AL
  AND AL,0DF	    ; coerce letters to upper case
  SUB AL,'A'-10     ; reduce A--F range to 10--15
  CMP AL,10	    ; was input below the A--F range?
  JB RET	    ; return Carry if it was
  CMP AL,16	    ; was input above the A--F range?  NoCarry now set if yes
L1:		    ; Carry flag is now the opposite of its return value
  CMC		    ; flip it to the correct value
  RET


; OUT_VALUE outputs AL to standard output.

OUT_VALUE:
  PUSH AX	    ; push AL value onto the stack
  MOV DX,SP	    ; MS-DOS memory-pointer now points to the AL-value
  MOV CX,1	    ; we will output 1 byte
  MOV BX,1	    ; open-file handle for standard output is 1
  MOV AH,040	    ; function number for MS-DOS write is 040
  INT 33	    ; write the AL-value to standard output
  POP AX	    ; pop AL back off the stack
  RET
