*  PROGRAM NAME:  LIST
*  AUTHOR:  RICHARD CONN
*  VERSION:  1.0
*  DATE:  10 FEB 81
*  PREVIOUS VERSIONS:  -None-
VERS    equ     10      ; Version Number

*
*
*  Section 0:  Introduction to the LIST Program
*



*
*       LIST is a CP/M utility which displays a file on the user console in
* paged mode.  It prints 24 lines of text and pauses; typing a ^C at this point
* returns to CP/M, while any other character continues.  If a line is more than
* LINE$LENGTH character long, it is broken and the line count is incremented
* accordingly.
*

*
*       The structure of the LIST Program is as follows --
*
*  Section      Functions/Routines
*  -------      ------------------
*
*     0         Introduction and Documentation
*
*     1         Initialization of the LIST Program
*               Significant Labels are:
*                 OPTION:  Command Line option processing
*                   OPTION$NUMBER:  Set the Line Numbering Flag (/N)
*                 DRIVE:  Extract the disk drive letter from the command line
*
*     2         Mainline of the LIST Program
*               Significant Labels are:
*                 LIST:  Open files, perform tests, etc
*                 LIST1:  Start processing loop
*                 LIST$LOOP:  Main program loop
*
*     3         Support Utilties
*               Significant Routines are:
*                 PRINT$ID:  Print Program ID
*                 TYP$COMP:  Compare the 3-byte file type pted to by HL to
*                       the FCB type; return w/Zero Set if match
*                 PRINT$MESSAGE:  Print string pted to by return address;
*                       string ends in 0
*                 LOADER:  Copy top of buffer down to front of buffer and
*                       load up to 16K into buffer
*                 LOADER1:  Load the buffer pted to by HL for B blocks (max)
*                 CRLF:  Print <CR> <LF> (no regs affected)
*                 CHAR$OUT:  Print char in A; no regs affected
*                 CHAR$IN:  Input char in A; only PSW affected
*                 PRINT$LINE:  Print line pted to by HL on console; process
*                       line numbering, tab expansion, line overflow fcts
*                 PRINT$NUMBER:  Print HL as up to 5 decimal digits with
*                       leading <SP>; follow by <SP>
*
*      4        Buffers
*

NUMBER$LINES    equ     24      ; Number of lines/screen
LINE$LENGTH     equ     77      ; Maximum number of characters/line
NUMBER$LENGTH   equ     7       ; Number of characters in line number

*
*  DEFINE MISCELLANEOUS CONSTANTS USED IN PROGRAM
*
OPT$CHAR        equ     '/'     ; Option character
CR              equ     13      ; <CR>
LF              equ     10      ; <LF>
BS              equ     8       ; <BS>
TAB             equ     9       ; <TAB>
BEL             equ     7       ; <BEL>
CPM             equ     0       ; Warm Boot Address
BUFF            equ     80H     ; CP/M Buffer
FCB             equ     5CH     ; CP/M FCB
WBADR           equ     1       ; CP/M Warm Boot Address
BDOS            equ     5       ; CP/M BDOS Entry Point
CTRLC           equ     'C'-'@' ; ^C
CTRLZ           equ     'Z'-'@' ; ^Z

*
*
*  Section 1:  Initialization of LIST Program
*



       ORG     100H

       LXI     H,BUFF  ; SCAN FOR OPTION
       MOV     A,M     ; GET CHAR COUNT
       ADD     L       ; ADD TO HL
       MOV     L,A
       MOV     A,H
       ACI     0
       MOV     H,A
       INX     H       ; HL PTS TO CHAR AFTER LAST CHAR IN COMMAND LINE
       MVI     M,0     ; STORE ENDING 0
       LXI     H,BUFF+1        ; PT TO 1ST CHAR
       PUSH    H       ; SAVE PTR FOR LATER
       XRA     A       ; A=0
       STA     NFLG    ; TURN OFF LINE NUMBERING FLAG
       STA     OVFL    ; TURN OFF LINE LENGTH OVERFLOW FLAG
OPTION:
       MOV     A,M     ; SCAN FOR OPTION
       ORA     A       ; DONE?
       JZ      OPTION$DONE
       INX     H       ; PT TO NEXT
       CPI     OPT$CHAR        ; OPTION?
       JNZ     OPTION
       MOV     A,M     ; GET OPTION LETTER
       CPI     'N'     ; NUMBER LINES?
       JZ      OPTION$NUMBER
       CALL    PRINT$ID        ; PRINT PROGRAM ID
       CALL    PRINT$MESSAGE
       DB      CR,LF,' The LIST command takes the following format --'
       DB      CR,LF,'         LIST d:filename.typ [/N]'
       DB      CR,LF,' Only the "/N" option is available; this option prints'
       DB      CR,LF,'line numbers in front of each line.'
       DB      CR,LF,' The drive specification "d:" is optional.',0
       JMP     CPM
*  THIS OPTION TURNS ON THE LINE NUMBERING FLAG
OPTION$NUMBER:
       MVI     A,0FFH  ; TURN ON FLAG
       STA     NFLG
       JMP     OPTION
*  DONE PROCESSING OPTIONS
OPTION$DONE:
       POP     H       ; GET PTR TO COMMAND LINE
DRIVE:
       MOV     A,M     ; SCAN FOR DRIVE NAME
       ORA     A       ; DONE?
       JZ      LIST
       INX     H       ; PT TO NEXT CHAR
       CPI     ' '     ; NON-SPACE?
       JZ      DRIVE   ; CONTINUE SCAN IF SO
       MOV     A,M     ; CHECK FOR COLON
       CPI     ':'     ; COLON MEANS DRIVE NAME PRECEEDS
       JNZ     LIST
       DCX     H       ; PT TO DRIVE NAME
       MOV     A,M     ; GET IT
       SUI     'A'     ; ADJUST FROM LETTER TO NUMBER (A=0,B=1,ETC)
       JC      DRIVE$ERROR
       CPI     16      ; IN RANGE?
       JNC     DRIVE$ERROR
       MOV     E,A     ; PLACE NUMBER IN E
       MVI     C,14    ; SELECT DISK
       CALL    BDOS
       JMP     LIST
DRIVE$ERROR:
       CALL    PRINT$MESSAGE
       DB      CR,LF,'ERROR:  Invalid Drive Specification',0
       JMP     CPM

*
*
*  Section 2:  Mainline of LIST Program
*



LIST:
       CALL    PRINT$ID        ; PRINT PROGRAM ID
       LXI     H,COM$TYPE      ; DON'T PRINT *.COM OR *.OBJ FILES
       CALL    TYP$COMP        ; COMPARE TYPES
       JZ      TYPE$ERROR
       LXI     H,OBJ$TYPE
       CALL    TYP$COMP
       JNZ     LIST0
TYPE$ERROR:
       CALL    PRINT$MESSAGE
       DB      CR,LF,'ERROR:  Attempt to List COM or OBJ File',0
       JMP     CPM
LIST0:
       LXI     D,FCB           ; TRY TO OPEN FILE
       MVI     C,15            ; OPEN FILE
       CALL    BDOS
       CPI     0FFH    ; ERROR?
       JNZ     LIST1
       CALL    PRINT$MESSAGE
       DB      CR,LF,'ERROR:  File Not Found',0
       JMP     CPM

LIST1:
       CALL    CRLF            ; NEW LINE
       LXI     H,BUFFER        ; FILE BUFFER (16K)
       MVI     B,8*16          ; LOAD 16K OF FILE (MAX)
       CALL    LOADER1         ; LOAD BUFFER PTED TO BY HL FOR 16K
       LXI     H,BUFFER        ; PT TO FIRST CHARACTER
       LDA     NFLG            ; NUMBER LINES?
       ORA     A               ; SET FLAGS
       JZ      LIST$LOOP
       PUSH    H               ; SAVE HL
       LXI     H,0             ; SET FOR 1ST LINE NUMBER
       SHLD    LINE$NUMBER
       POP     H               ; RESTORE HL

*
*  MAIN LOOP FOR PRINTING LINES
*
LIST$LOOP:
       MVI     C,NUMBER$LINES  ; NUMBER OF LINES/SCREEN
LIST$LOOP1:
       CALL    PRINT$LINE      ; PRINT ONE LINE
       CPI     CTRLZ
       JZ      CPM
       CPI     LF              ; LINE FEED?
       JNZ     LIST$LOOP2
       INX     H               ; PT TO CHAR AFTER <LF>
       MOV     A,M             ; GET POSSIBLE ^Z
       CPI     CTRLZ           ; ^Z IF SO
       JZ      CPM
LIST$LOOP2:
       DCR     C               ; COUNT DOWN
       JNZ     LIST$LOOP3
       CALL    CHAR$IN         ; WAIT FOR CHAR
       CALL    CRLF            ; NEW LINE
       CPI     CTRLC           ; ^C?
       JZ      CPM             ; ABORT IF SO
       JMP     LIST$LOOP       ; CONTINUE
LIST$LOOP3:
       CALL    CRLF            ; NEW LINE
       JMP     LIST$LOOP1

*
*
*  Section 3:  Support Utilities
*



*
*  THIS ROUTINE PRINTS THE PROGRAM ID
*
PRINT$ID:
       CALL    PRINT$MESSAGE
       DB      'LIST  Version ',VERS/10+'0','.',(VERS MOD 10)+'0',0
       RET

*
*  THIS ROUTINE COMPARES THE THREE BYTES PTED TO BY HL AGAINST THE TYPE
*    IN THE FCB; RETURNS W/ZERO SET IF MATCH
*
TYP$COMP:
       LXI     D,FCB+9 ; PT TO TYPE IN FCB
       MVI     B,3             ; 3 BYTES
TYP$COMP$LOOP:
       LDAX    D               ; GET BYTE
       CMP     M               ; COMPARE
       RNZ
       INX     H               ; PT TO NEXT
       INX     D
       DCR     B
       JNZ     TYP$COMP$LOOP
       RET

*
*  PRINT MESSAGE PTED TO BY RETURN ADDRESS ENDING IN 0
*
PRINT$MESSAGE:
       XTHL                    ; SAVE HL AND GET PTR
PRINT$MESSAGE$LOOP:
       MOV     A,M             ; GET CHAR
       INX     H               ; PT TO NEXT
       ORA     A               ; DONE?
       JZ      PRINT$MESSAGE$DONE
       CALL    CHAR$OUT        ; PRINT IT
       JMP     PRINT$MESSAGE$LOOP
PRINT$MESSAGE$DONE:
       XTHL                    ; RESTORE HL AND RETURN ADDRESS
       RET

*
*  THIS ROUTINE LOADS THE FILE BUFFER
*       ENTRY POINT 'LOADER' COPIES THE END OF THE FILE BUFFER DOWN TO THE
* BEGINNING AND LOADS THE REST OF THE BUFFER (UP TO 16K).
*       ENTRY POINT 'LOADER1' LOADS THE BUFFER POINTED TO BY HL FOR B-BLOCKS
* (1 BLOCK = 128 BYTES).
*
LOADER:
       PUSH    B               ; SAVE BC
       LXI     H,BUFFER$LAST   ; PT TO LAST BLOCK
       LXI     D,BUFFER        ; PT TO 1ST BLOCK
       CALL    MOVE$BLOCK      ; MOVE BLOCK FROM HL TO DE
       CALL    MOVE$BLOCK      ; MOVE PAGE (2 BLOCKS)
       XCHG                    ; PT TO 2ND BLOCK
       MVI     B,8*16-2        ; 2 BLOCKS LESS THAN 16K
       CALL    LOADER1         ; LOAD BLOCKS
       POP     B               ; RESTORE BC
       RET
LOADER1:
       CALL    LOADER2         ; LOAD NEXT BLOCK
       RNZ                     ; DONE IF PAST EOF
       LXI     D,BUFF          ; PT TO BUFFER
       XCHG                    ; EXCHANGE PTRS
       CALL    MOVE$BLOCK      ; MOVE BLOCK LOADED INTO FILE BUFFER
       XCHG                    ; RESTORE PTRS
       DCR     B               ; COUNT DOWN
       JNZ     LOADER1
       RET
*
*  LOAD BUFFER FROM DISK
*
LOADER2:
       PUSH H ! PUSH D ! PUSH B
       LXI     D,FCB           ; PT TO FILE NAME
       MVI     C,20            ; READ BLOCK
       CALL    BDOS
       ORA     A               ; SET FLAG
       POP B ! POP D ! POP H
       RET

*
*  MOVE BLOCK (128 BYTES) FROM HL TO DE
*
MOVE$BLOCK:
       PUSH    B               ; SAVE BC
       MVI     B,128           ; 128 BYTES
MOVE$BLOCK$LOOP:
       MOV     A,M             ; GET BYTE
       STAX    D               ; PUT BYTE
       INX     H               ; PT TO NEXT
       INX     D
       DCR     B               ; COUNT DOWN
       JNZ     MOVE$BLOCK$LOOP
       POP     B               ; RESTORE BC
       RET

*
*  OUTPUT <CR> <LF>; DON'T CHANGE A
*
CRLF:
       PUSH    PSW     ; SAVE A
       MVI     A,CR    ; <CR>
       CALL    CHAR$OUT
       MVI     A,LF    ; <LF>
       CALL    CHAR$OUT
       POP     PSW     ; GET A
       RET

*
*  CHARACTER OUTPUT ROUTINE
*    OUTPUT CHARACTER IN REG A TO CONSOLE
*
CHAR$OUT:
       PUSH H ! PUSH D ! PUSH B ! PUSH PSW
       MOV     E,A             ; CHAR IN E
       MVI     C,2             ; OUTPUT TO CON:
       CALL    BDOS
       POP PSW ! POP B ! POP D ! POP H
       RET

*
*  CHARACTER INPUT ROUTINE
*    CHARACTER IS RETURNED IN REG A
*
CHAR$IN:
       PUSH H ! PUSH D ! PUSH B
       LXI     H,CHAR$IN$RET   ; PLACE RETURN ADDRESS ON STACK
       PUSH    H
       LHLD    WBADR           ; INDEX INTO BIOS FOR NO ECHO
       MOV     A,L             ; ADD 6 FOR CONSOLE INPUT ROUTINE
       ADI     6
       MOV     L,A
       MOV     A,H
       ACI     0
       MOV     H,A             ; HL PTS TO ROUTINE
       PCHL                    ; "CALL" CONSOLE INPUT ROUTINE
CHAR$IN$RET:
       POP B ! POP D ! POP H
       RET

*
*  PRINT LINE PTED TO BY HL ON CONSOLE
*
PRINT$LINE:
       PUSH    B               ; SAVE LINE COUNT
       MVI     C,0             ; SET CHAR COUNT
       LDA     NFLG            ; NUMBER LINE?
       ORA     A               ; 0=NO
       JZ      PRINT$LINE1
       LDA     OVFL            ; OVERFLOW FROM PREVIOUS LINE?
       ORA     A               ; 0=NO
       JNZ     PRINT$LINE1
       PUSH    H               ; SAVE PTR TO LINE
       LHLD    LINE$NUMBER     ; FETCH AND INCREMENT LINE NUMBER
       INX     H
       SHLD    LINE$NUMBER
       CALL    PRINT$NUMBER    ; PRINT LINE NUMBER
       POP     H               ; RESTORE PTR TO LINE
PRINT$LINE1:
       XRA     A               ; TURN OFF OVERFLOW FLAG
       STA     OVFL
       LXI     D,BUFFER$LAST   ; IN LAST BLOCK?
       MOV     A,H             ; CHECK AGAINST H
       CMP     D
       JNZ     PRINT$LINE$LOOP
       PUSH    H               ; SAVE PTR TO LINE (RELATIVE OFFSET IN L)
       CALL    LOADER          ; LOAD NEXT 16K
       LXI     H,BUFFER        ; PT TO 1ST BYTE OF BLOCK
       POP     D               ; GET RELATIVE OFFSET IN E
       MOV     L,E             ; RELATIVE OFFSET IN L -- CONTINUE
PRINT$LINE$LOOP:
       MOV     A,M             ; GET CHAR FROM FILE
       INX     H               ; PT TO NEXT CHAR
       ANI     7FH             ; MASK OUT MSB
       CPI     CTRLZ           ; PROCESS EOF
       JZ      PRINT$LINE$CR
       CPI     BS              ; PROCESS <BS>
       JZ      PRINT$LINE$BS
       CPI     TAB             ; PRINT <TAB>
       JZ      PRINT$LINE$TAB
       CPI     CR              ; PROCESS EOL
       JZ      PRINT$LINE$CR
       CPI     ' '             ; DON'T OUTPUT LESS THAN <SP>
       JC      PRINT$LINE$LOOP
       CPI     7EH             ; DON'T OUTPUT IF GREATER THAN OR EQUAL TO 7EH
       JNC     PRINT$LINE$LOOP
       CALL    CHAR$OUT        ; PRINT CHAR
       INR     C               ; INCREMENT CHAR COUNT
       CALL    OVFL$TEST       ; CHECK FOR LINE OVERFLOW
       JMP     PRINT$LINE$LOOP
PRINT$LINE$BS:
       MOV     A,C             ; POSSIBLE TO <BS>?
       ORA     A               ; 0=NO
       JZ      PRINT$LINE$LOOP
       MVI     A,BS            ; PRINT <BS>
       CALL    CHAR$OUT
       DCR     C               ; COUNT DOWN
       JMP     PRINT$LINE$LOOP
PRINT$LINE$TAB:
       MVI     A,' '           ; PRINT <SP>
       CALL    CHAR$OUT
       INR     C               ; INCREMENT COUNT
       CALL    OVFL$TEST       ; CHECK FOR LINE OVERFLOW
       MOV     A,C             ; MULTIPLE OF 8?
       ANI     7               ; MASK FOR 3 LSB
       JNZ     PRINT$LINE$TAB
       JMP     PRINT$LINE$LOOP
PRINT$LINE$CR:
       MOV     A,M             ; GET POSSIBLE <LF>
       POP     B               ; RESTORE LINE COUNT
       RET

*
*  TEST FOR LINE OVERFLOW AND PRINT OVERFLOW CHARS IF SO
*
OVFL$TEST:
       LDA     NFLG            ; NUMBERING LINES?
       ORA     A               ; 0=NO
       JZ      OVFL$TEST1
       LDA     OVFL            ; IN OVERFLOW?
       ORA     A               ; 0=NO
       JNZ     OVFL$TEST1
*  WE ARE ON A NUMBERED LINE
       MVI     A,LINE$LENGTH   ; GET LINE LENGTH
       SUI     NUMBER$LENGTH   ; SUBTRACT LENGTH OF LEADING NUMBER
       JMP     OVFL$TEST2
*  WE ARE NOT ON A NUMBERED LINE
OVFL$TEST1:
       MVI     A,LINE$LENGTH   ; CHECK CHAR COUNT
OVFL$TEST2:
       CMP     C               ; OK?
       RNZ
*  CHECK TO SEE IF ONE OF NEXT 3 CHARS IS A <CR>
       MOV     A,M             ; NEXT CHAR A <CR>?
       ANI     7FH             ; MASK OUT MSB
       CPI     CR
       RZ
       INX     H               ; NEXT CHAR A <CR>?
       MOV     A,M
       ANI     7FH
       DCX     H
       CPI     CR
       RZ
       INX     H               ; 3RD CHAR A <CR>?
       INX     H
       MOV     A,M
       ANI     7FH
       DCX     H
       DCX     H
       CPI     CR
       RZ
*  NONE OF THEM ARE, SO OVERFLOW
       MVI     A,0FFH          ; SET OVERFLOW FLAG
       STA     OVFL
       MVI     A,' '           ; PRINT OVERFLOW CHARS
       CALL    CHAR$OUT
       MVI     A,'<'
       CALL    CHAR$OUT
       POP     D               ; CLEAR STACK
       MVI     A,LF            ; FAKE A <LF>
       DCX     H               ; BACK UP IN PREPARATION FOR <LF> ADVANCE
       POP     B               ; RESTORE LINE COUNT
       RET                     ; RETURN TO MAIN LOOP

*
*  PRINT NUMBER IN HL AS UP TO 5 DECIMAL DIGITS FOLLOWED BY A <SP>
*    AFFECT NO REGISTERS
*
PRINT$NUMBER:
       PUSH H ! PUSH D ! PUSH B ! PUSH PSW
       MVI     A,0FFH
       STA     LDSP    ; TURN OFF LEADING <SP> FLAG
       LXI     D,10000 ; DETERMINE 10,000'S COUNT
       CALL    PNUM
       LXI     D,1000  ; DETERMINE 1,000'S COUNT
       CALL    PNUM
       LXI     D,100   ; 100'S
       CALL    PNUM
       LXI     D,10    ; 10'S
       CALL    PNUM
       MOV     A,L     ; 1'S
       ADI     '0'     ; CONVERT TO ASCII
       CALL    CHAR$OUT        ; PRINT IT
       MVI     A,':'   ; PRINT COLON
       CALL    CHAR$OUT
       MVI     A,' '   ; PRINT <SP>
       CALL    CHAR$OUT
       POP PSW ! POP B ! POP D ! POP H
       RET
*
*  PNUM IS A UTILITY TO SUBTRACT DE FROM HL UNTIL HL<0; PRINT NUMBER OF TIMES
*    SUBTRACTION WAS DONE; IF ZERO AND LEADING SPACE FLAG SET, PRINT <SP>;
*    ELSE, PRINT DIGIT AND CLEAR LEADING SPACE FLAG
*    ON EXIT, HL=HL-DE
*
PNUM:
       MVI     C,'0'           ; SET DIGIT
PNUM1:
       MOV     A,L             ; GET NUMBER
       SUB     E
       MOV     L,A
       MOV     A,H
       SBB     D
       MOV     H,A             ; HL=HL-DE
       JC      PNUM2
       INR     C               ; INCREMENT COUNT
       JMP     PNUM1
PNUM2:
       MOV     A,L             ; ADD BACK IN SO HL IS AGAIN 0 OR POS
       ADD     E
       MOV     L,A
       MOV     A,H
       ADC     D
       MOV     H,A             ; HL RESTORED
       MOV     A,C             ; GET DIGIT
       CPI     '0'             ; ZERO?
       JNZ     PNUM3
       LDA     LDSP            ; LEADING <SP>?
       ORA     A               ; 0=NO
       JZ      PNUM3
       MVI     A,' '           ; PRINT <SP>
       JMP     PNUM4
PNUM3:
       XRA     A               ; NO LEADING <SP>
       STA     LDSP            ; TURN OFF FLAG
       MOV     A,C             ; GET CHAR
PNUM4:
       CALL    CHAR$OUT
       RET

*
*
*  Section 4:  Buffers
*

COM$TYPE:
       DB      'COM'           ; FOR COMPARISON AGAINST COM FILE TYPE
OBJ$TYPE:
       DB      'OBJ'           ; FOR COMPARISON AGAINST OBJ FILE TYPE
LDSP:
       DS      1               ; LEADING <SP> FLAG (0=NO)
OVFL:
       DS      1               ; LINE LENGTH OVERFLOW FLAG (0=NO)
NFLG:
       DS      1               ; LINE NUMBER FLAG (0=NO)
LINE$NUMBER:
       DS      2               ; LINE NUMBER STORAGE BUFFER


       DS      60              ; STACK SPACE
STACK   EQU     $

       ORG     $/256*256+256
BUFFER:
       DS      128*(16*8-2)    ; FILE BUFFER SPACE
BUFFER$LAST:
       DS      128*2           ; LAST BLOCK

       END