        TITLE WC - WORD COUNT UTILITY
        PAGE 55,132
;***********************************************************************
;       THIS PROGRAM COUNTS BYTES, WORDS AND LINES IN A TEXT FILE.
;
;       USAGE:  WC file1 file2 ... 
;               defaults to standard input if no file name given.
;		file names can be ambiguous
;
;       REQUIRES MSDOS 2 OR HIGHER
;
;       BY: JON DART
;           DEPARTMENT OF ANTHROPOLOGY
;           UCSD C-001
;	    LA JOLLA, CA  92093
;
;	VERSION 1.3, 10-JAN-87 ADDS WILDCARDS, MULTIPLE FILENAMES
;	VERSION 1.2, 06-JUL-86
;       VERSION 1.1, 16-JAN-86
;       VERSION 1.0, 08-NOV-85
;
;	TO BUILD WC.EXE:
;	    MASM WC,WC,NUL,NUL
;	    LINK WC,WC,NUL,ASM -C0x300

TRUE	EQU	1
FALSE	EQU	0
SILENT	EQU	FALSE			;"TRUE" FOR UNLABELED OUTPUT (LIKE UNIX)
BUFSIZE EQU     8192                    ;SIZE OF FILE BUFFER
MAXARGS	EQU	40			;MAX. # OF COMMAND LINE ARGUMENTS
M       EQU     BYTE PTR 0[BX]

        .XLIST
        INCLUDE ASCII.DEF
        INCLUDE MSDOS2.DEF
	INCLUDE MACROS.DEF
        .LIST

        EXTRN   COUT:NEAR,ERRORMSG:NEAR,CRLF:NEAR
        EXTRN   TYPTX:NEAR,SKIPSP:NEAR,CPYCNT:NEAR,ZERO:NEAR
        EXTRN   PRINTDD:NEAR
	EXTRN	GETARGS:NEAR
	EXTRN	FIXPATH:NEAR
	EXTRN	TYPE_UFN:ABS, TYPE_UNK:ABS

STACK   SEGMENT PARA STACK
        DB 512 DUP (?)
STACK   ENDS
        ASSUME  SS:STACK

;       INITIALIZED DATA AREA:
;
DATA    SEGMENT BYTE PUBLIC
NUMARGS DW	0			;NUMBER OF COMMAND LINE ARGUMENTS
INHANDLE DW	0                       ;INPUT FILE HANDLE
                                        ;(DEFAULTS TO STD. INPUT)
NUMLINE DD      0 			;NUMBER OF LINES
NUMWORD DD      0                       ;NUMBER OF WORDS
NUMBYTE DD      0                       ;NUMBER OF BYTES
ONEFILE	DB	1			;SET 1 IF ONE UNAMBIG. ARGUMENT		
PREV    DB      0                       ;PREVIOUS CHAR., USED IN WORD COUNT
MSG1    DB      CR,LF,"Can't open: ",0
MSG1E   DB      CR,LF,0
MSG2    DB      CR,LF,"wc: read error.",CR,LF,0
MSG3    DB      CR,LF,"wc: not enough memory for buffer.",CR,LF,0
MSG4	DB	CR,LF,"wc: unknown pathname: ",0
MSG5	DB	CR,LF,"wc: too many arguments.",CR,LF,0
DATA    ENDS
        ASSUME  DS:DATA,ES:DATA

;	UNINITIALIZED DATA AREA:

BUFFER  EQU     0                       ;OFFSET TO BUFFER (FROM ES)
ARGPTRS EQU	BUFFER+BUFSIZE		;POINTERS TO COMMAND LINE ARGS
ARGBUF	EQU	ARGPTRS+(MAXARGS*2)	;BUFFER FOR COMMAND LINE ARGS
SPATH	EQU	ARGBUF+300		;SEARCH PATH
PREFIX	EQU	SPATH + 65		;SEARCH PREFIX
FILENAME EQU	PREFIX + 65		;COMPOSITE FILE NAME (PREFIX + NAME)
DTA	EQU	FILENAME + 65		;DISK TRANSFER ADDRESS
BUFEND	EQU	DTA + 128		;END OF BUFFER SPACE

CODE	SEGMENT BYTE PUBLIC
	ASSUME CS:CODE
ENTRY:
        TEST_DOS2                       ;TEST FOR DOS 2.0, EXIT IF DOS 1
        MOV     BX,(BUFEND+800H) SHR 4
	MOV	AH,SET_BLOCK
	INT	DOS			;SET PROGRAM+BUFFER SIZE
	JNB	MEMOK			;IF OK
        MOV     AX,DATA
        MOV     DS,AX
        MOV     DX,OFFSET MSG3
        CALL    ERRORMSG                ;NOT ENOUGH MEMORY
        JMP     EXIT2
MEMOK:  MOV     AX,DS			;GET DATA SEG
	ADD	AX,800H			;ADD OFFSET TO BUFFER
	MOV	ES,AX			;SET EXTRA SEG TO POINT TO BUFFER
        MOV     BX,OFFSET (80H)         ;GET BYTE COUNT FOR COMMAND LINE
        CMP     BYTE PTR [BX],0
        JNE     L_1                     ;IF NOT 0
        MOV     AX,DATA
        MOV     DS,AX
        CALL    WC                      ;NO COMMAND LINE INPUT, USE STD. INPUT
	JMP	EXIT2
L_1:
        PUSH    BX
        MOV     DL,M
        MOV     DH,0
        ADD     BX,DX
        INC     BX
        MOV     M,0                     ;PUT 0 BYTE AT END OF COMMAND LINE
        POP     BX
        INC     BX
	MOV	CX,MAXARGS
	MOV	SI,OFFSET ARGPTRS
	MOV	DI,OFFSET ARGBUF
	CALL	GETARGS			;GET COMMAND LINE ARGS
	JB	TOOMANY			;IF TOO MANY ARGS
	CMP	CX,0			;NO ARGUMENTS?
	JNE	L_2			;YES, HAVE SOME
	CALL	WC			;NO ARGS, JUST READ STDIN
	JMP	EXIT2
TOOMANY:
	MOV	AX,DATA			;TOO MANY ARGUMENTS
	MOV	DS,AX
	MOV	DX,OFFSET MSG5
	CALL	ERRORMSG		;SHOW MSG
	JMP	EXIT2			;QUIT
L_2:
	MOV	AX,DATA
	MOV	DS,AX
	CMP	CX,1
	JE	GETFILES		;IF JUST ONE ARGUMENT
	MOV	BYTE PTR ONEFILE,0	;CLEAR "ONE ARG" FLAG
GETFILES:
	MOV	SI,OFFSET ARGPTRS
ARGLOOP:
	MOV	BX,WORD PTR ES:[SI]	;ES:BX POINTS TO NEXT ARG
	PUSH	CX
	PUSH	SI
	CALL	DOARG			;DO WC FOR ONE ARGUMENT
	POP	SI
	POP	CX
SKIPSW:
	ADD	SI,2			;ADVANCE TO NEXT ARG
	LOOP	ARGLOOP
	JMP	EXIT2			;ALL DONE

DOARG   PROC	NEAR
	PUSH	DS			;SAVE DATA SEG.
	MOV	AX,ES
	MOV	DS,AX			;SET DATA SEG TO BUFFER SEG
	MOV	AH,SET_DTA
	MOV	DX,OFFSET DTA
	INT	DOS			;SET DTA
        MOV	CX,OFFSET SPATH
        MOV	DX,OFFSET PREFIX
        PUSH    BX
        CALL    FIXPATH                 ;PARSE PATHNAME
	POP	BX
	POP	DS
	CMP	AL,TYPE_UNK
	JE	BADNAME
	CMP	AL,TYPE_UFN
	JE	GOTNAME			;IF UNAMBIG. FILE NAME
	MOV	BYTE PTR ONEFILE,0	;AMBIG. FILE NAME OR DIR, FLAG IT
	JMP	SHORT GOTNAME
BADNAME:
	MOV	DX,OFFSET MSG4
	CALL	ERRORMSG		;BAD FILE NAME, SHOW MSG.
	PUSH	DS
	MOV	AX,ES
	MOV	DS,AX
	MOV	DX,BX
	CALL	ERRORMSG		;SHOW FILE THAT CAUSED IT
	POP	DS
	RET
GOTNAME:
	PUSH	DS			;SAVE DATA SEG.
	MOV	AX,ES
	MOV	DS,AX			;SET DATA SEG TO BUFFER SEG
        MOV     DX,OFFSET SPATH         ;POINT TO SEARCH PATH
        MOV     CX,31H                  ;SET SEARCH ATTRIBUTES
        MOV     AH,FIND_FIRST
        INT     DOS                     ;SEARCH FOR 1ST MATCH
	POP	DS
	JB	BADNAME			;IF NOTHING FOUND
GOTFILE:
	PUSH	DS			;SAVE DATA SEG.
	MOV	AX,ES
	MOV	DS,AX			;SET DATA SEG TO BUFFER SEG
	MOV	SI,OFFSET PREFIX
	MOV	DI,OFFSET FILENAME
	MOV	CX,65
	CALL	CPYCNT			;COPY PREFIX TO FILE NAME AREA
        DEC     DI                      ;BACK UP OVER NULL
        MOV     SI,OFFSET (DTA+30)      ;POINT TO FILE NAME WE FOUND
        CMP     [SI],BYTE PTR '.'       ;DOES IT START WITH .? -
        JNE     NOTDOT                  ;- NO
	POP	DS
	JMP     SHORT SKIPFILE 		;YES, JUST SKIP IT
NOTDOT:
	CALL	CPYCNT			;COPY FILE WE FOUND AFTER PREFIX
	POP	DS
	CALL	DOFILE			;DO 1 FILE
SKIPFILE:
        MOV     AH,FIND_NEXT
        INT     DOS                     ;FIND NEXT MATCH, IF ANY
        JC      NOMORE                  ;IF NONE
        JMP     GOTFILE                 ;GOT ONE, BACK TO TOP OF LOOP
NOMORE:
	RET
DOARG	ENDP

;	DOFILE = PROCESS 1 FILE (NAME IN ES:FILENAME)

DOFILE  PROC	NEAR
	PUSH	DS
	MOV	AX,ES
	MOV	DS,AX
        MOV     DX,OFFSET FILENAME      ;DX POINTS TO FILE NAME
        MOV     AL,READ_ACCESS
        MOV     AH,DOS2_OPEN
        INT     DOS                     ;TRY TO OPEN FILE
	POP	DS
        JNC     NAMEOK                  ;IF OK
        JMP     SHORT BADOPN
NAMEOK:
        MOV     WORD PTR INHANDLE,AX    ;SAVE FILE HANDLE
	CMP	BYTE PTR ONEFILE,1
	JE	NOSHOW
	CALL	SHOWFILE		;SHOW FILE NAME (ONLY IF >1 SPECIFIED)
NOSHOW:
	CALL	WC			;SHOW BYTE, WORD, LINE COUNTS FOR FILE
	RET
BADOPN:
        MOV     DX,OFFSET MSG1
        CALL    ERRORMSG                ;BAD OPEN, SHOW MSG.
	PUSH	DS
	MOV	AX,ES
	MOV	DS,AX
        MOV     DX,OFFSET FILENAME      ;DX POINTS TO FILE NAME
        CALL    ERRORMSG                ;SHOW IT
	POP	DS
	RET
DOFILE	ENDP

;	SHOW FILE NAME

SHOWFILE PROC	NEAR
	MOV	AX,"="
	MOV	CX,5
BARS:
	CALL	COUT
	LOOP	BARS
	MOV	AX,SPACE
	CALL	COUT
	MOV	BX,OFFSET FILENAME
SHOWF:	MOV	AL,BYTE PTR ES:[BX]
	CMP	AL,0
	JE	ENDF
	CALL	COUT
	INC	BX
	JMP	SHOWF	
ENDF:	MOV	AL,SPACE
	CALL	COUT
	MOV	AL,'='
	MOV	CX,5
BARS2:
	CALL	COUT
	LOOP	BARS2
	CALL	CRLF
	RET
SHOWFILE ENDP

WC 	PROC NEAR
	MOV	BYTE PTR PREV,0		;CLEAR "PREVIOUS CHAR"
	PUSH	ES
	MOV	AX,DS
	MOV	ES,AX
	MOV	DI,OFFSET NUMLINE
	MOV	CX,12
	CALL	ZERO			;CLEAR COUNTS
	POP	ES
FILLBUF:
        MOV     CX,BUFSIZE              ;CX = # BYTES TO READ
        MOV     BX,WORD PTR INHANDLE    ;BX = FILE HANDLE
        PUSH    DS
        MOV     AX,ES
        MOV     DS,AX                   ;SET DATA SEG TO WHERE BUFFER IS
        MOV     DX,OFFSET BUFFER
        MOV     AH,READ
        INT     DOS                     ;FILL BUFFER FROM FILE
        POP     DS                      ;RESTORE DATA SEG
        JC      BADREAD                 ;IF ERROR
        MOV     BX,OFFSET BUFFER
        CMP     AX,0
        JNE     GOTTEXT                 ;IF SOMETHING READ
        JMP     DONE

BADREAD:
        MOV     DX,OFFSET MSG2
        CALL    ERRORMSG
        JMP     CLOSEFILE

GOTTEXT:
        ADD     AX,BX                   ;COMPUTE ADDRESS OF END OF BUFFER
        MOV     DX,AX                   ;SAVE IN DX
	MOV	SI,OFFSET NUMBYTE
        ADD     WORD PTR [SI],AX        ;UPDATE BYTE COUNT
        JNC     NOCARRY
        INC     WORD PTR [SI+2]         ;IF CARRY
NOCARRY:
        MOV     DI,OFFSET NUMLINE       ;NUMBER OF LINES
        MOV     SI,OFFSET NUMWORD	;NUMBER OF WORDS
BEGIN:
COUNT:  CMP     DX,BX                   ;COUNT BYTES AND LINES
        JNE     L_12                    ;UNTIL END OF BUFFER
        JMP     FILLBUF                 ;GO FILL BUFFER AGAIN
L_12:
        MOV     AL,BYTE PTR ES:[BX]     ;GET BYTE FROM BUFFER
        AND     AL,7FH                  ;STRIP HI BIT
        INC     BX
        CMP     AL,SPACE                ;IS SPACE?
        JE      WCOUNT                  ;YES, POSSIBLE END OF WORD
        CMP     AL,CR                   ;IS CR?
        JNE     COUNT2                  ;NO, IGNORE CHAR.
COUNTLINE:
        INC     WORD PTR DS:[DI]        ;COUNT A LINE
        JNC     WCOUNT
        INC     WORD PTR DS:[DI+2]	;IF CARRY, INCREMENT HI WORD
WCOUNT: CMP     BYTE PTR PREV,0         ;SAW A SPACE OR CR, WAS PREV. CHAR. NULL?
        JE      COUNT2
        CMP     BYTE PTR PREV,CR        ;OR CR?
        JE      COUNT2
        CMP     BYTE PTR PREV,LF        ;OR LF?
        JE      COUNT2
        CMP     BYTE PTR PREV,SPACE     ;OR SPACE?
        JE      COUNT2
        CMP     BYTE PTR PREV,TAB       ;OR TAB?
        JE      COUNT2
        INC     WORD PTR [SI]           ;NO, ASSUME END OF WORD
        JNC     COUNT2
        INC     WORD PTR [SI+2]
COUNT2:
        MOV     BYTE PTR PREV,AL
        JMP     COUNT
DONE:
        MOV     AX,WORD PTR NUMBYTE
        MOV     SI,AX
        MOV     AX,WORD PTR NUMBYTE+2
        MOV     DI,AX
        MOV     AL,0
        CALL    PRINTDD
	IF	SILENT
	MOV	AL,SPACE
	CALL	COUT
	ELSE
        CALL    TYPTX
        DB      ' bytes,',SPACE+200Q
	ENDIF
        MOV     AX,WORD PTR NUMWORD
        MOV     SI,AX
        MOV     AX,WORD PTR NUMWORD+2
        MOV     DI,AX
        MOV     AL,0
        CALL    PRINTDD
	IF 	SILENT
	MOV	AL,SPACE
	CALL	COUT
	ELSE
        CALL    TYPTX
        DB      ' words,',SPACE+200Q
	ENDIF
        MOV     AX,WORD PTR NUMLINE
        MOV     SI,AX
        MOV     AX,WORD PTR NUMLINE+2
        MOV     DI,AX
        MOV     AL,0
        CALL    PRINTDD
	IF	NOT SILENT
        CALL    TYPTX
        DB      ' lines','.'+200Q
	ENDIF
	CALL	TYPTX
	DB	CR,LF+200Q
CLOSEFILE:
        MOV     BX,WORD PTR INHANDLE
        CMP     BX,0                    ;WERE WE READING STD. INPUT?
        JE      NOCLOSE                 ;YES, NO NEED TO CLOSE
        MOV     AH,DOS2_CLOSE
        INT     DOS                     ;CLOSE FILE
NOCLOSE:
	RET
WC	ENDP

EXIT2:
        MOV     AH,EXIT
        INT     DOS                     ;EXIT TO DOS

CODE    ENDS
        END     ENTRY
