;                SORTV.ASM ver 1.4
;               by Ward Christensen
;                (revised 6/29/81)
;
;Simple sort program for sorting lists of names,
;or any other variable length file, with CR/LF
;delimited records.
;
;This is a "simple" program: FILE MUST FIT IN MEMORY.
;
;06/29/81 Cleaned up file and re-tabified it.  (KBP)
;
;06/22/81 Changed so MAC is not needed. Changed so alternate or
;         standard CP/M may be selected. By Ted Shapin.
;
;01/03/81 Change stack init.  By Keith Petersen, W8SDZ
;
;11/15/80 Add @ command (WLC)
;
;10/24/80 Originally written by Ward Christensen
;
;FORMAT:
;       SORTV input-name output-name
; OR    SORTV name
;
;If the second format is used, the file is read into
;memory, sorted, erased, created, and written back.
;
;       The sort will be based on the first characters
;       in the file, unless the command is followed by
;       an "@" sign, then a string (1 or more characters)
;       to skip.  If these are present, the line will be
;       sorted starting after one of these characters.
;
;Example: SORTV NAMES.SUB @.
;
;Will sort NAMES.SUB by filetype, since it skips past
;the "." before doing the compare.
;
EOF     EQU     1AH
CR      EQU     0DH
LF      EQU     0AH
;
BIAS    EQU     0       ;0 FOR STANDARD CP/M, 4200H FOR ALTERNATE CP/M
;
;BDOS/CBIOS EQUATES (VERSION 10)
;
RDCON   EQU     1
WRCON   EQU     2
PRINT   EQU     9
RDCONBF EQU     10
CONST   EQU     11
OPEN    EQU     15
CLOSE   EQU     16
SRCHF   EQU     17
SRCHN   EQU     18
ERASE   EQU     19
READ    EQU     20
WRITE   EQU     21
MAKE    EQU     22
REN     EQU     23
STDMA   EQU     26
BDOS    EQU     5+BIAS
FCB     EQU     5CH+BIAS
FCB2    EQU     6CH+BIAS
FCBEXT  EQU     FCB+12
FCBRNO  EQU     FCB+32
;
       ORG     100H+BIAS
;
;INIT LOCAL STACK
;
       LXI     SP,STACK
;
       CALL    START
       DB      'SORTV rev 1.3'
       DB      CR,LF,'$'
;
START   POP     D       ;GET ID
       MVI     C,PRINT
       CALL    BDOS    ;PRINT ID
;
;START OF PROGRAM EXECUTION
;
       CALL    SVSKIP  ;SAVE SKIP INFO
       CALL    CKNAMES ;SEE THAT 2 NAMES ARE THERE
       CALL    OPENIN  ;OPEN INPUT FILE
       CALL    READN   ;READ THE NAMES
       CALL    SORTN   ;SORT THE NAMES
       CALL    WRITEN  ;WRITE THE NAMES
       CALL    ERXIT
       DB      '++DONE++$'
;
;====>  SUBROUTINES
;       ----------------
;
;====>  SAVE "SKIP TO" INFORMATION
;
SVSKIP  LXI     H,81H+BIAS
;
SVSKL   MOV     A,M
       ORA     A
       RZ              ;NO 'SKIP TO'
       CPI     '@'     ;SKIP DELIMITER?
       INX     H
       JNZ     SVSKL
       LXI     D,SKIPC ;CHARS TO SKIP
;
SVSKL2  MOV     A,M
       STAX    D
       INX     H
       INX     D
       ORA     A
       JNZ     SVSKL2
       RET
;
;====>  CHECK THAT 2 NAMES WERE SUPPLIED
;
CKNAMES LDA     FCB+1
       CPI     ' '
       JZ      NONAME
       LDA     FCB2+1
       CPI     ' '
       JZ      SAMENAM
       CPI     '@'     ;SKIP PARM?
       JZ      SAMENAM
       LXI     H,FCB2
       LXI     D,OUTNAME
       LXI     B,12
       CALL    MOVER
       RET
;
;OUTPUT NAME = INPUT NAME
;
SAMENAM LXI     H,FCB
       LXI     D,OUTNAME
       LXI     B,12
       CALL    MOVER
       RET
;
NONAME  CALL    ERXIT
       DB      '++Error - ',CR,LF
       DB      'Command format requires an '
       DB      'input name, and an output name.$'
;
;====>  OPEN THE INPUT FILE
;
OPENIN  PUSH    B
       PUSH    D
       PUSH    H
       MVI     C,OPEN
       LXI     D,FCB
       CALL    BDOS
       POP     H
       POP     D
       POP     B
       INR     A
       RNZ             ;SUCCESSFUL?  RETURN
       CALL    ERXIT
       DB      '++Input file not found$'
;
;====>  READ IN THE NAMES
;
READN   LXI     H,SBUFF ;TO FIRST NAME
;
READNL  CALL    READL   ;READ ONE LINE
       RC              ;GOT EOF, RETURN
       CALL    CHAIN   ;CHAIN THINGS TOGETHER
       JMP     READNL
;
;====>  READ ONE LINE
;
READL   SHLD    CURR    ;SAVE CURR LINE PTR
       XRA     A       ;GET 0
       MOV     M,A     ;INIT FORWARD
       INX     H       ;       POINTER
       MOV     M,A     ;       TO
       INX     H       ;       0
       LXI     D,SKIPC ;TO CK SKIP CHARS PRESENT
;
READLLP LDA     BDOS+2  ;ARE WE
       DCR     A       ;       OVER-
       CMP     H       ;       FLOW-
       JZ      OFLO    ;       ING?
       PUSH    D
       PUSH    H
       LXI     H,EXTFCB
       CALL    RDBYTE  ;READ A BYTE
       POP     H
       POP     D
       CPI     EOF     ;SET CARRY
       STC             ;       AND RETURN
       RZ              ;       IF EOF
       MOV     M,A     ;STORE CHAR
;TEST FOR SKIP CHAR FOUND
       MOV     B,A     ;SAVE FOR COMPARE
       LDAX    D
       ORA     A       ;NO MORE SKIP CHARS?
       JZ      READLNS ;NO MORE
       CMP     B       ;A SKIP CHAR?
       JNZ     READLNS ;NO, KEEP TRYIN.
       INX     D       ;TO NEXT SKIP CHAR
;
READLNS INX     H       ;POINT TO NEXT
       MOV     A,B     ;GET CHAR
       CPI     CR      ;END OF LINE?
       JNZ     READLLP ;       NO, LOOP.
       PUSH    D
       PUSH    H
       LXI     H,EXTFCB
       CALL    RDBYTE  ;GOBBLE UP LF
       POP     H
       POP     D
       LDAX    D       ;GET SKIP CHAR END
       ORA     A       ;TEST IT AND SET "NO EOF"
       RZ
;ERROR - NO SKIP CHAR
       LHLD    CURR
       INX     H       ;SKIP
       INX     H       ;       POINTER
;
ERPLP   MOV     E,M
       PUSH    B
       PUSH    D
       PUSH    H
       MVI     C,WRCON
       CALL    BDOS
       POP     H
       POP     D
       POP     B
       MOV     A,M
       INX     H
       CPI     CR
       JNZ     ERPLP
       CALL    ERXIT
       DB      LF,'++NO SKIP CHAR FOUND++$'
;
OFLO    CALL    ERXIT
       DB      '++File won''t fit in memory$'
;
;====>  CHAIN RECORDS TOGETHER
;
CHAIN   PUSH    H       ;SAVE POINTER
       LHLD    CURR    ;GET CURRENT
       XCHG            ;       TO DE
       LHLD    PREV    ;PREV TO HL
       MOV     M,E     ;MOVE CURR
       INX     H       ;       TO
       MOV     M,D     ;       PREV
       XCHG            ;THEN MOVE
       SHLD    PREV    ;       PREV TO CURR
       POP     H
       RET
;
;====>  SORT THE NAMES
;
SORTN   XRA     A       ;SHOW NO
       STA     SWAPS   ;       SWAPS
       LXI     H,PTR   ;POINT PREV
       SHLD    PREV    ;       TO PTR
       LHLD    PTR     ;POINT TO FIRST
;
;HANDLE WIERD CASE OF ONLY ONE NAME
;
       MOV     A,M     ;GET POINTER
       INX     H       ;POINT TO NEXT
       ORA     M       ;OR TOGETHER
       DCX     H       ;BACK UP
       RZ              ;RETURN IF ONLY ONE
;
SORTL   CALL    CMPR    ;COMPARE ENTRIES
       CC      SWAP    ;SWAP IF WRONG ORDER
       CALL    NEXT    ;POINT TO NEXT
       JNC     SORTL   ;LOOP IF MORE
       LDA     SWAPS   ;ANY
       ORA     A       ;       SWAPS?
       JNZ     SORTN   ;YES, LOOP
       RET             ;NO, RETURN
;
;---->  COMPARE TWO NAMES
;
CMPR    PUSH    H       ;SAVE POINTER
       MOV     E,M     ;GET NEXT
       INX     H       ;       POINTER
       MOV     D,M     ;       TO DE
       INX     D       ;ALIGN POINTERS
;
;SKIP IF NECESSARY
;
       LXI     B,SKIPC
;
TSTSKIP LDAX    B
       ORA     A
       JZ      COMPL   ;NO SKIP
       INX     B
;
SKIP1   INX     H
       CMP     M
       JNZ     SKIP1
       XCHG            ;SWAP
;
SKIP2   INX     H
       CMP     M
       JNZ     SKIP2
       XCHG            ;PUT THINGS BACK
       JMP     TSTSKIP
;
COMPL   INX     D       ;TO NEXT
       INX     H       ;TO NEXT
       LDAX    D       ;GET ONE
       CMP     M       ;COMPARE
       JNZ     COMPNE  ;NO COMPARE
       CPI     CR      ;END?
       JNZ     COMPL   ;       NO, LOOP
;
COMPH   POP     H       ;RESTORE POINTER
       RET             ;THEY ARE EQUAL
;
;COMPARE NOT EQUAL - SEE IF END OF ELEMENT,
;AND IF SO, CALL THEM EQUAL
;
COMPNE  MOV     A,M
       CPI     CR
       JZ      COMPH
       LDAX    D
       CMP     M
       JMP     COMPH   ;CARRY SET AS APPROP
;
;---->  SWAP ENTRIES
;
;LOGIC: PTR POINTS TO SOME ENTRY, WHICH POINTS
;TO ANOTHER ENTRY.  THEY ARE NOT IN ORDER.  THUS:
;POINT PTR TO THE SECOND, POINT THE SECOND TO
;THE FIRST, AND POINT THE FIRST TO WHAT THE
;SECOND USED TO POINT TO.
;
SWAP    MVI     A,1
       STA     SWAPS   ;SHOW WE SWAPPED
;BC=NEXT
       MOV     C,M
       INX     H
       MOV     B,M
       DCX     H
;CHAIN CURRENT TO NEXT ONES CHAIN
       LDAX    B
       MOV     M,A
       INX     B
       INX     H
       LDAX    B
       MOV     M,A
       DCX     B
       DCX     H
;SAVE CURRENT POINTER IN DE
       XCHG
;GET POINTER TO PREV
       LHLD    PREV
;POINT PREV TO NEXT
       MOV     M,C
       INX     H
       MOV     M,B
;STORE CURR IN NEXT
       MOV     A,E
       STAX    B
       INX     B
       MOV     A,D
       STAX    B
       DCX     B
;RESTORE CURRENT POINTER
       XCHG
       RET             ;CURRENT POINTER IN DE
;
;---->  GET NEXT ETRY, CARRY IF NOT 2 MORE
;
NEXT    SHLD    PREV    ;SAVE POINTER
       MOV     E,M
       INX     H
       MOV     D,M
       XCHG            ;HL= NEXT
       MOV     A,H     ;CARRY ON
       ORA     L       ;       IF HL
       STC             ;       =
       RZ              ;       0
       MOV     A,M     ;GET
       INX     H       ;SEE IF THERE
       ORA     M       ;       IS
       DCX     H       ;       ANOTHER
       RNZ             ;THERE IS ANOTHER
       STC             ;SHOW NOT 2 TO SWAP
       RET
;
;====>  WRITE THE NAMES
;
WRITEN  LXI     H,0     ;INIT
       SHLD    EXTFCB+2 ;      EFCB
       XRA     A       ;INIT
       STA     FCBEXT  ;       THE
       STA     FCBRNO  ;       FCB
;RESTORE NAME
       LXI     H,OUTNAME
       LXI     D,FCB
       LXI     B,12
       CALL    MOVER
       PUSH    B
       PUSH    D
       PUSH    H
       MVI     C,ERASE
       LXI     D,FCB
       CALL    BDOS
       POP     H
       POP     D
       POP     B
       PUSH    B
       PUSH    D
       PUSH    H
       MVI     C,MAKE
       LXI     D,FCB
       CALL    BDOS
       POP     H
       POP     D
       POP     B
       INR     A       ;MAKE OK?
       JZ      BADOUT  ;       NO, ERROR
       LHLD    PTR     ;GET FIRST
;
WNLP    CALL    WRITEL  ;WRITE ONE LINE
       JNC     WNLP    ;LOOP IF MORE
       MVI     A,EOF   ;WRITE EOF CHAR
       PUSH    H
       LXI     H,EXTFCB
       CALL    WRBYTE
       POP     H
       LXI     H,EXTFCB ;FLUSH
       CALL    FLUSH    ;       BUFFERS
       PUSH    B
       PUSH    D
       PUSH    H
       MVI     C,STDMA ;RESET DMA
       LXI     D,80H+BIAS
       CALL    BDOS
       POP     H
       POP     D
       POP     B
       PUSH    B
       PUSH    D
       PUSH    H
       MVI     C,CLOSE
       LXI     D,FCB
       CALL    BDOS
       POP     H
       POP     D
       POP     B
       CALL    ERXIT   ;       AND EXIT
       DB      '++DONE++$'
;
WRITEL  PUSH    H       ;SAVE POINTER
       INX     H
;
WRLP    INX     H       ;TO NEXT CHAR
       MOV     A,M     ;GET CHAR
       PUSH    H
       LXI     H,EXTFCB
       CALL    WRBYTE  ;WRITE IT
       POP     H
       MOV     A,M     ;SEE IF END
       CPI     CR      ;       OF LINE
       JNZ     WRLP    ;NO, LOOP
       MVI     A,LF    ;OTHERWISE
       PUSH    H
       LXI     H,EXTFCB
       CALL    WRBYTE  ;WRITE LF
       POP     H
       POP     H       ;GET POINTER
       MOV     E,M     ;GET
       INX     H       ;       FORWARD
       MOV     D,M     ;       POINTER
       XCHG            ;PUT IT IN HL
       MOV     A,H     ;IS POINTER
       ORA     L       ;       ZERO?
       RNZ             ;NO, RETURN
       STC             ;CARRY SHOWS END
       RET
;
BADOUT  CALL    ERXIT
       DB      '++Can''t make output file$'
;
;FOLLOWING FROM 'EQU10.LIB'---->
;
;MOVE, COMPARE SUBROUTINES
;
MOVER   MOV     A,M
       STAX    D
       INX     H
       INX     D
       DCX     B
       MOV     A,B
       ORA     C
       JNZ     MOVER
       RET
;
;       FROM EQU10.LIB: AS OF 07/19/80
;
;RDBYTE, HL POINTS TO EXTENDED FCB:
;
;       2 BYTE BUFFER ADDR
;       2 BYTE "BYTES LEFT" (INIT TO 0)
;       1 BYTE BUFFER SIZE (IN PAGES)
;       2 BYTE FCB ADDRESS
;
RDBYTE  MOV     E,M
       INX     H
       MOV     D,M     ;GET BUFFER ADDR
       INX     H
       MOV     C,M
       INX     H
       MOV     B,M     ;BC = BYTES LEFT
       MOV     A,B     ;GET COUNT
       ORA     C
       JNZ     RDBNORD ;NO READ
;
       INX     H       ;TO BUFFER SIZE
       MOV     A,M     ;GET COUNT
       ADD     A       ;MULTIPLY BY 2
       MOV     B,A     ;SECTOR COUNT IN B
       INX     H       ;TO FCB
       PUSH    H       ;SAVE FCB POINTER
       MOV     A,M     ;GET..
       INX     H
       MOV     H,M     ;..ADDR..
       MOV     L,A     ;..TO HL
;
RDBLP   MVI     A,1AH   ;GET EOF CHAR
       STAX    D       ;SAVE IN CASE EOF
       PUSH    D       ;SAVE DMA ADDR
       PUSH    H       ;SAVE FCB ADDR
       PUSH    B
       PUSH    D
       PUSH    H
       MVI     C,STDMA
       CALL    BDOS    ;SET DMA ADDR
       POP     H
       POP     D
       POP     B
       POP     D       ;GET FCB
       PUSH    B
       PUSH    D
       PUSH    H
       MVI     C,READ
       CALL    BDOS
       POP     H
       POP     D
       POP     B
       ORA     A
       POP     H       ;HL=DMA, DE=FCB
       JNZ     RDBRET  ;GOT EOF
       MOV     A,L
       ADI     80H     ;TO NEXT BUFF
       MOV     L,A
       MOV     A,H
       ACI     0
       MOV     H,A
       XCHG            ;DMA TO DE, FCB TO HL
       DCR     B       ;MORE SECTORS?
       JNZ     RDBLP   ;YES, MORE
;
RDBRET  POP     H       ;GET FCB POINTER
       DCX     H       ;TO LENGTH
       MOV     A,M     ;GET LENGTH
       DCX     H       ;TO COUNT
       MOV     M,A     ;SET PAGE COUNT
       DCX     H       ;TO LO COUNT
       DCX     H       ;TO HI FCB
       DCX     H       ;TO EFCB START
       JMP     RDBYTE  ;LOOP THRU AGAIN
;
RDBNORD INX     H       ;TO LENGTH
       MOV     A,M     ;GET LENGTH (PAGES)
       XCHG            ;BUFF TO HL
       ADD     H
       MOV     H,A     ;HL = END OF BUFF
       MOV     A,L
       SUB     C
       MOV     L,A
       MOV     A,H
       SBB     B
       MOV     H,A     ;HL = DATA POINTER
       MOV     A,M     ;GET BYTE
       XCHG            ;EFCB BACK TO HL
       CPI     1AH     ;EOF?
       RZ              ;YES, LEAVE POINTERS
       DCX     B       ;DECR COUNT
       DCX     H       ;"BYTES LEFT"
       MOV     M,B
       DCX     H
       MOV     M,C     ;STORE BACK COUNT
       RET
;
;SAMPLE EFCB:
;
;EFCB   DW      BUFF    ;BUFFER ADDR
;       DW      0       ;BYTES LEFT (OR TITE)
;       DB      20      ;BUFFER SIZE (IN PAGES)
;       DW      FCB     ;FCB ADDRESS
;
;
;WRBYTE, HL POINTS TO EXTENDED FCB:
;
;       2 BYTE BUFFER ADDR
;       2 BYTE "BYTES LEFT" (INIT TO 0)
;       1 BYTE BUFFER SIZE (IN PAGES)
;       2 BYTE FCB ADDRESS
;
WRBYTE  MOV     E,M
       INX     H
       MOV     D,M     ;DE=BUF ADDR
       INX     H
       MOV     C,M
       INX     H
       MOV     B,M     ;BC=BYTES IN BUFF
       PUSH    D       ;SAVE FCB
       XCHG
       DAD     B       ;TO NEXT BYTE
       MOV     M,A     ;STORE IT
       INX     B       ;ONE MORE
       XCHG
       POP     D
;
;SEE IF BUFFER IS FULL
;
       INX     H       ;GET
       MOV     A,M     ;       SIZE
       CMP     B       ;FULL?
       JNZ     WRBNOWR ;NO WRITE
;
       ADD     A       ;MULTIPLY BY 2
       MOV     B,A     ;SECTOR COUNT IN B
       INX     H       ;TO FCB
       PUSH    H       ;SAVE FCB POINTER
       MOV     A,M     ;GET..
       INX     H       ;..FCB..
       MOV     H,M     ;..ADDR..
       MOV     L,A     ;..TO HL
;
WRBLP   PUSH    D       ;SAVE DMA ADDR
       PUSH    H       ;SAVE FCB ADDR
       PUSH    B
       PUSH    D
       PUSH    H
       MVI     C,STDMA
       CALL    BDOS    ;SET DMA ADDR
       POP     H
       POP     D
       POP     B
       POP     D       ;GET FCB
       PUSH    B
       PUSH    D
       PUSH    H
       MVI     C,WRITE
       CALL    BDOS
       POP     H
       POP     D
       POP     B
       ORA     A
       POP     H       ;HL=DMA, DE=FCB
       JNZ     WRBERR  ;GOT ERR
       MOV     A,L
       ADI     80H     ;TO NEXT BUFF
       MOV     L,A
       MOV     A,H
       ACI     0
       MOV     H,A
       XCHG            ;DMA TO DE, FCB TO HL
       DCR     B       ;MORE SECTORS?
       JNZ     WRBLP   ;YES, MORE
;
WRBRET  POP     H       ;GET FCB POINTER
       DCX     H       ;TO LENGTH
       DCX     H       ;TO COUNT
       MVI     M,0     ;SET 0 TO WRITE
       DCX     H       ;TO LO COUNT
       MVI     M,0
       PUSH    B
       PUSH    D
       PUSH    H
       MVI     C,STDMA
       LXI     D,80H+BIAS
       CALL    BDOS
       POP     H
       POP     D
       POP     B
       RET
;
WRBNOWR DCX     H       ;TO LENGTH
       MOV     M,B     ;SET NEW LENGTH
       DCX     H
       MOV     M,C
       RET
;
;FLUSH THE EFCB BUFFERS
;
FLUSH   MOV     E,M
       INX     H
       MOV     D,M     ;DE=BUF ADDR
       INX     H
       MOV     C,M
       INX     H
       MOV     B,M     ;BC=BYTES IN BUFF
       INX     H       ;TO COUNT
       MOV     A,B
       ORA     C
       RZ              ;NOTHING TO WRITE
       MOV     A,C     ;GET LOW COUNT
       ADD     A       ;SHIFT HIGH TO CARRY
       MOV     A,B     ;GET LOW COUNTAL
       RAL             ;MULT BY 2, + CARRY
       INR     A       ;FUDGE FOR PARTIAL SECT
       MOV     B,A     ;SAVE SECTOR COUNT
       INX     H       ;TO FCB
       MOV     A,M
       INX     H
       MOV     H,M
       MOV     L,A     ;HL=FCB
;
FLUSHL  PUSH    B
       PUSH    D
       PUSH    H
       MVI     C,STDMA
       CALL    BDOS
       POP     H
       POP     D
       POP     B
       XCHG
       PUSH    B
       PUSH    D
       PUSH    H
       MVI     C,WRITE
       CALL    BDOS
       POP     H
       POP     D
       POP     B
       XCHG
       ORA     A
       JNZ     WRBERR
       PUSH    H
       LXI     H,80H
       DAD     D
       XCHG
       POP     H
       DCR     B
       JNZ     FLUSHL
       XCHG
       PUSH    B
       PUSH    D
       PUSH    H
       MVI     C,CLOSE
       CALL    BDOS
       POP     H
       POP     D
       POP     B
       INR     A
       RNZ
       CALL    ERXIT
       DB      '++OUTPUT FILE CLOSE ERROR ++$'
;
WRBERR  CALL    ERXIT
       DB      '++OUTPUT FILE WRITE ERROR++$'
;
;EXIT WITH ERROR MESSAGE
;
MSGEXIT EQU     $       ;EXIT W/"INFORMATIONAL" MSG
;
ERXIT   POP     D       ;GET MSG
       MVI     C,PRINT
       CALL    BDOS
;
;EXIT, RESTORING STACK AND RETURN
;
EXIT    JMP     0+BIAS
;
;====>  START OF WORK AREA
;
EXTFCB  DW      DKBUF
       DW      0
       DB      4
       DW      FCB
PREV    DW      PTR     ;POINTER TO PREV POINTER
SKIPC   DB      0       ;SKIP CHARS END
       DS      8       ;VARIABLE SKIP CHARS
;
       DS      100     ;STACK AREA
STACK   EQU     $
;
OUTNAME DS      12      ;OUTPUT FILENAME
SWAPS   DS      1
CURR    DS      2
PTR     DS      2       ;POINTER TO FIRST NAME
;
       ORG     ($+255) AND 0FF00H ;TO PAGE
;
DKBUF   DS      256*4   ;4 PAGES OF BUFFER
SBUFF   DS      0       ;NAMES READ IN HERE
;
       END