; CCOUNT.Z80 ; ; Counts characters in text files. ; Vers equ 10 SubVers equ ' ' ; ; HISTORY: ; ; Version 1.0 -- March 7, 1992 -- Gene Pizzetta ; Based on a very mimimal test program I have used for a long time ; (It had the search characters hard coded into it). I decided that ; maybe I should release it after sprucing it up and making some ; additions: entering search bytes on the command line, option ; to count all characters, screen paging, 32-bit long-word counters ; and display so more than 65,535 characters can be counted, help ; screen, etc. etc. The result is an all-new program. ; ; System addresses . . . ; CpmFcb equ 5Ch ; default file control block CpmDma equ 80h ; default DMA buffer ; ; ASCII . . . ; CtrlC equ 03h ; ^C TAB equ 09h ; tab LF equ 0Ah ; line feed CR equ 0Dh ; carriage return CtrlS equ 13h ; ^S CpmEof equ 1Ah ; ^Z (end-of-file) ; ; Program specifications . . . ; NumSiz equ 10 ; minimum length of long-word output BufSiz equ 8 ; buffer size in records (sectors) ; .request zslib,z3lib,syslib ; ext eatspc,eatnspc,zcheck,fxropen,fxrclose,fxget ; ZSLIB ext parcnt,parget,uncaps,hvtinit,hvdinit,hvon,hvoff ext gcomnam,comnam ext z3init,envptr,z3log,getcrt,puter2,inverror ; Z3LIB ext isdigit,retud,initfcb,eval,padc,pa2hc,pafdc ; SYSLIB ext caps,eprint,epstr,crlf,pfn2,cin,condin,cout ext codend ext plwdc ; ZSLIB 3.6 ; jp Start ; db 'Z3ENV',1 Z3EAdr: dw 0 ; ; Configuration . . . ; dw 0 ; filler db 'CCOUNT' ; for ZCNFG db Vers/10+'0',Vers mod 10+'0' AllFlg: db 0 ; 0=requested matches, FFh=all matches HiFlg: db 0 ; 0=ignore high bits, FFh=don't PagFlg: db 0 ; 0=page screen, FFh=don't AbtFlg: db 0 ; 0=abort no error, FFh=abort is error CRTLns: db 24 ; number of lines on screen (CP/M only) ; ; Start of Program . . . ; Start: ld hl,(Z3EAdr) call zcheck ; check for Z-System jr z,IsZ3 ; (it is) ld hl,0 ; it's not, so make ENVPTR zero IsZ3: call z3init ; initialize ENVPTR ld (Stack),sp ; save stack pointer ld sp,Stack ; set up new stack ld hl,DftNam ; point to default program name call gcomnam ; get invocation name, if possible call hvtinit ; initialize terminal ; call GetOpt ; initialize data area, parse command line call IniFil ; set up control block, log into directory call OpnFil ; open file call RdLoop ; process file byte by byte call ClsFil ; close file call Report ; print results ; Finish: xor a ; no error ErExit: ld b,a ; put error code in B call Z3Chk ; Z-System? jr z,Exit ; (no, so skip error flag) ld a,b ; recover error code call puter2 ; load program error flag or a ; error? call nz,inverror ; (if so, invoke error handler) Exit: call hvdinit ; de-initialize terminal ld sp,(Stack) ; restore stack ret ; return to system ; ; Subroutines . . . ; ; GetOpt -- Initialize data area and parse command line parameters. ; GetOpt: xor a ; initialize data segment to nulls ld (BegIni),a ld hl,BegIni ld de,BegIni+1 ld bc,EndIni-BegIni ldir ld hl,(HiFlg) ; set default options ld (OpHFlg),hl ld a,(AllFlg) ld (OpAFlg),a call Z3Chk ; Z-System? ld a,(CRTLns) ; assume default screen lines jr z,GetOp1 ; (not Z3, use default) call getcrt ; get screen lines from environment inc hl ld a,(hl) GetOp1: dec a ; screen lines - 1 ld (ScrLns),a ; ..to permanent storage dec a ; screen lines - 2 for first screen ld (LnsCnt),a ; ..to screen lines counter ld hl,CpmDma+1 ; point to command tail ld a,1 ; check first token push hl ; save CL pointer call parget ; make sure we have a file spec pop hl ; restore pointer jp z,Usage ; (none, show how to use it) cp '/' ; usage request? jp z,Usage ; (yes) ld a,2 ; check second token call parget ; do we have any search characters? ret z ; (none) cp '/' ; is the first character a slash? jr z,GotOp1 ; (yes, must be options) ld hl,CpmDma+1 ; point back to start of command line ld de,SchStr ; point to search byte storage ld c,1 ; start with second token OpLoop: inc c ; point to next token ld a,c ; move token counter to A for PARGET push hl ; save CL pointer call parget ; get token jr z,PrsEnd ; (end of command line) cp '"' ; upper-case letter? jr z,AscChr ; (yes) cp '''' ; lower-case letter? jr z,AscLow ; (yes) cp '^' ; control character? jr z,AscCtl ; (yes) cp '/' ; option string? jr z,GotOpt ; (must be) call isdigit ; decimal or hex? jr nz,PrsErr ; (nope, invalid parameter) push de ; save storage pointer call eval ; evaluate number jr c,PrsErr ; (EVAL error) ld a,d ; high byte zero? or a jr nz,PrsErr ; (no, number too large) dec hl ; prepare for ChkPrs ld a,e ; get low byte from EVAL pop de ; recover storage pointer OpLp1: ld (de),a ; store converted parameter inc de ; point to next open slot call ChkPrs ; check for illegal trailing character ld hl,SchCnt ; point to search byte counter inc (hl) ; ..and increment it pop hl ; recover CL pointer jr OpLoop ; ..and continue with next token ; ; End of command line parse PrsEnd: pop hl ; adjust stack and return ret ; AscLow: inc hl ; point to character after quote ld a,(hl) ; get it call uncaps ; make it lower-case jr OpLp1 ; store it and continue ; AscChr: inc hl ; point to character after quote ld a,(hl) ; get it call caps ; make it upper-case jr OpLp1 ; store it and continue ; AscCtl: inc hl ; point to character after caret ld a,(hl) ; get it sub '@' ; make it a control character jr nc,OpLp1 ; (if okay, store it and continue) ; ; Invalid command line parameter PrsErr: ld a,19 ; error code call eprint db 'Invalid parameter.',0 jp ErExit ; ; Parse command line options after slash GotOpt: pop de ; adjust stack GotOp1: inc hl ; point to next character ld a,(hl) ; get it or a ret z ; (zero is end of command line) cp 'A' ; count all characters? jr z,OptA cp 'H' ; make high bits significant? jr z,OptH cp 'P' ; page screen? jr z,OptP cp ' ' ; space? jr z,GotOp1 ; (skip intervening and trailing spaces) ld a,19 ; error code call eprint db 'Invalid option.',0 jp ErExit ; OptA: ld de,OpAFlg ; point to option A flag jr DoOpt ; ..flip it and continue ; OptH: ld de,OpHFlg ; point to option H flag ; DoOpt: ld a,(de) ; get flag cpl ; flip it ld (de),a ; put it back jr GotOp1 ; get next option ; OptP: ld de,OpPFlg ; point to option P flag jr DoOpt ; ..flip it and continue ; ; ChkPrs -- Checks for illegal trailing character after parameter token ; is evaluated. ; ChkPrs: inc hl ; get next character from command line ld a,(hl) cp ' ' ret z ; (okay) cp TAB ret z ; (okay) or a ret z ; (okay) jr PrsErr ; otherwise, it's wrong ; ; IniFil -- Set up input control block, file control block, and buffers. ; Log into target directory. ; IniFil: ld hl,InFcb ; HL -> control block FCB ld de,CpmFcb ; DE -> default FCB call Z3Chk ; Z-System? jr nz,IniFl1 ; (yep) call retud ; for CP/M, get current user ld a,c ld (CpmFcb+13),a ; ..and stuff it into FCB IniFl1: call z3log ; log into directory ex de,hl inc hl ; HL -> filename ld a,(hl) ; anything there? cp ' ' jr z,MissFn ; (missing filename) push hl ; save filename pointer ld bc,11 push bc ; save length count ld a,'?' ; ambiguous? cpir jr z,AmbgFn ; (yep, can't do it) inc de ; DE -> InFcb filename space pop bc ; recover length count pop hl ; recover filename pointer ldir ; move it ld a,BufSiz ; put buffer size in input control block ld (InCtrl),a call codend ld (BufAdr),hl ; put buffer address in input control block ld a,(CpmFcb+15) ; check for invalid directory spec inc a jr nz,DirOK ; (directory spec okay) ld a,2 ; error code call eprint db 'Invalid directory.',0 jp ErExit ; MissFn: call eprint db 'Missing',0 jr NamErr ; AmbgFn: call eprint db 'Ambiguous',0 NamErr: call eprint db ' filename.',0 ld a,8 ; error code jp ErExit ; DirOK: call eprint db 'Reading ',0 call hvon ; turn on highlighting call retud ; get drive and user ld a,b ; get drive add a,'A' ; ..make it ASCII call cout ; ..and print it ld a,c ; get user call pafdc ; ..and print it ld a,':' call cout ; print colon ld de,InFcb+1 ; point to filename call pfn2 ; ..and print it call hvoff ; turn off highlighting call eprint db ' .. ',0 ret ; ; OpnFil -- Open file for reading. ; OpnFil: ld de,InCtrl ; point to input control block call fxropen ; open file ret nz ; (open okay) ld a,10 ; error code call eprint db 'Not found.',0 jp ErExit ; ; RdLoop -- Reads and processes characters from file according to user's ; selections. Separate loops are provided for the four basic options ; in the interest of speed (since space is no problem). ; RdLoop: ld de,InCtrl ; point to input control block ld a,(OpHFlg) ; check H flag or a jr nz,RLoopH ; (high bits are significant) ld a,(OpAFlg) ; check A flag or a jr nz,ALoop ; (count all characters) ; ; GLoop counts only given characters, ignoring high bits ; GLoop: call fxget ; get character ret z ; (end of file) cp CpmEof jr z,GotEof ; (end of file) and 7Fh ; reset high bit call Match ; check for match jr GLoop ; get next character ; ; ALoop counts all characters, ignoring high bits ; ALoop: call fxget ; get character ret z ; (end of file) cp CpmEof jr z,GotEof ; (end of file) and 7Fh ; reset high bit call CkCon ; check for user abort call IncTot ; increment total characters counter call GotMat ; increment character array jr ALoop ; get next character ; ; RLoopH -- high bits are significant here ; RLoopH: ld a,(OpAFlg) ; check A flag or a jr nz,ALoopH ; (count all characters) ; ; GLoopH counts only given characters, and high bits are significant ; GLoopH: call fxget ; get character ret z ; (end of file) cp CpmEof jr z,GotEof ; (end of file) call Match ; check for match jr GLoopH ; get next character ; ; ALoopH counts all characters, and high bits are significant ; ALoopH: call fxget ; get character ret z ; (end of file) cp CpmEof jr z,GotEof ; (end of file) call CkCon ; check for user abort call IncTot ; increment total characters counter call GotMat ; increment character array jr ALoopH ; get next character ; ; GotEof -- End-of-file character is recorded, but is not included ; in the total number of characters because it is not really part ; of the text. Still, it's sometimes useful to know it's there. ; GotEof: call GotMat ; record ^Z end-of-file character ret ; ..and quit ; ; Match -- Matches and counts characters for "given characters" loops ; (GLoop and GLoopH). Must preserve DE. ; Match: call CkCon ; check for user abort call IncTot ; increment total characters counter ld hl,SchCnt ; get number of search characters ld b,(hl) ; ..into B inc b ; check for zero dec b ret z ; (no search characters) ld hl,SchStr-1 ; point to search characters string MLoop: inc hl ; point to next search character cp (hl) ; does it match our character in A? jr z,GotMat ; (match found, increment character array) djnz MLoop ; loop to end of search string ret ; ; GotMat -- Computes address in 1024-byte array by using the value of ; the character in A. Then the four-byte-long word at the computed ; address is incremented. DE is preserved. ; GotMat: ld bc,CCount ; get address of array ld l,a ; compute offset into array ld h,0 ; ..using ASCII value of character add hl,hl ; ASCII value times 2 add hl,hl ; ASCII value times 4 add hl,bc ; HL -> low byte of corresponding counter ; ; Incr32 -- increments 32-bit binary number (4 bytes, low-byte first) ; pointed to by HL. ; Incr32: ld b,4 ; 4 bytes Loop32: inc (hl) ; increment byte ret nz ; (quit if it hasn't reach zero) inc hl ; point to next byte djnz Loop32 ; ..and do it again ret ; IncTot: ld hl,ChrCnt jr Incr32 ; ; ClsFil -- Closes input file. ; ClsFil: ld de,InCtrl ; point to file control block call fxrclose ; close file ret nz ; (close okay) ld a,4 ; error code call eprint db 'File close error.',0 jp ErExit ; ; Report -- Prints summary report on console. ; Report: call crlf ld hl,ChrCnt ; point to total of characters found call Prt32 ; and print it call eprint db ' -> Total Characters',0 ld a,(OpAFlg) or a jr z,ReptG ; (only given characters) ; ; ReptA -- Report all characters in set. ; ReptA: xor a ; characters from null to FFh ld c,a ; duplicate character is kept in C ld b,a ; 256 of them RALoop: bit 7,c ; 8-bit character? jr z,RALp1 ; (no) ld a,(OpHFlg) ; yes, but are we ignoring them? or a ret z ; (yes, so we're through) ld a,c ; recover character RALp1: call PrtRep ; report on this character inc c ; increment both copies of character inc a djnz RALoop ; loop through them all ret ; ; ReptG -- Report counts only for characters given on command line ; ReptG: ld hl,SchCnt ; point to number of search characters inc (hl) ; check for zero dec (hl) ret z ; (no search characters) ld b,(hl) ; get number into counter ld hl,SchStr-1 ; point to search string ; RGLoop: inc hl ; point to next search character ld a,(hl) ; get it into A ld c,a ; ..and a duplicate into C bit 7,a ; 8-bit character? jr z,RGLp1 ; (nope) ld a,(OpHFlg) ; check H flag or a ld a,c ; get character back into A jr z,RGLp2 ; (ignore 8-bit character) RGLp1: call PrtRep ; report on this character RGLp2: djnz RGLoop ; ..and continue ret ; done ; ; PrtRep -- Prints report of number found of character in A (also duplicated ; in C), followed by character value in ASCII, hexadecimal, and decimal. ; PrtRep: push af ; save character call CkScrn ; increment screen counter, check for pause pop af ; restore character call PrtMat ; print count call eprint db ' -> ',0 push hl ; save SchStr pointer call PrtAsc ; print ASCII value bit 7,a ; was a "+" printed? call z,Space1 ; (no, add extra space instead) call Space3 call PrtHex ; print hexadecimal value call Space3 call PrtDec ; print decimal value pop hl ; recover SchStr pointer ret ; ; PrtAsc -- Prints ASCII representation of value in A (duplicated in C), ; prefixing a "^" if a control character, suffixing a "+" if an 8-bit ; character, printing "SP" for space and "DL" for delete. Preserves ; character in A and C. ; PrtAsc: ld c,a ; save character in C call Space1 ld a,(OpHFlg) ; check H flag or a ld a,c ; recover character jr z,PrtAs1 ; (we're ignoring high bits) bit 7,a ; check high bit jr nz,PrtHi ; (special handling for 8-bit character) PrtAs1: cp 7Fh ; DEL? jr z,PrtDel ; (yes, special handling) cp ' ' ; space? jr z,PrtSpc ; (yes, special handling) jr c,PrtCtl ; (control character, special handling) call Space1 ld a,c ; get new copy in A call cout ; ..and print it ret ; ; PrtCtl -- Special handling for control characters PrtCtl: ld a,'^' call cout ; print caret ld a,c add a,'@' ; make character printable call cout ; ..and print it ld a,c ; restore character ret ; ; PrtSpc -- Special handling for space character PrtSpc: call eprint ; print "SP" db 'SP',0 ld a,c ; restore character ret ; ; PrtDel -- Special handling for DEL character PrtDel: call eprint ; print "DL" db 'DL',0 ld a,c ; restore character ret ; ; PrtHi -- Special handling for 8-bit characters PrtHi: push af ; save character and 7Fh ; reset high bit ld c,a ; put copy in C call PrtAs1 ; print it ld a,'+' call cout ; suffix it with a "+" pop af ; recover character ret ; ; PrtDec -- Prints decimal value of character in A in three-character field. ; PrtDec: call padc ret ; ; PrtHex -- Prints two-character hexadecimal value of character in A, ; suffixing an "h". ; PrtHex: call pa2hc ; print value push af ; save character ld a,'h' call cout ; suffix an "h" pop af ; recover character ret ; ; Prt32 -- Prints 32-bit number pointed to by HL. Preserves AF and BC. ; Prt32: push af ; save character ld a,NumSiz ; field size call plwdc ; convert binary to ASCII string pop af ; restore character ret ; ; Space1, Space2, Space3 -- Prints corresponding number of spaces on ; the console, preserving all registers. ; Space3: call Space1 Space2: call Space1 Space1: push af ld a,' ' call cout pop af ret ; ; PrtMat -- retrieves character count from array of 32-bit counters, ; and prints it. Preserves A, BC, and HL. ; PrtMat: push hl ; save HL ld de,CCount ; point to array ld l,a ; compute offset into array ld h,0 ; ..using ASCII value of character add hl,hl ; ASCII value times 2 add hl,hl ; ASCII value times 4 add hl,de ; HL -> low byte of corresponding counter call Prt32 ; print count pop hl ; recover HL ret ; ; Z3Chk -- confirms Z-System by checking ENVPTR. Returns Z if not ZCPR3. ; Z3Chk: ld a,(envptr+1) ; get high byte of pointer or a ; zero means no Z3 ret ; ; CkScrn -- Increments and checks screen lines counter. Monitors for ; user input via CkAbrt. ; CkScrn: call CkAbrt ; check for user input ld a,(OpPFlg) ; check P flag or a call nz,crlf ; (new line if no screen paging) ret nz ; (no paging) ld a,(LnsCnt) ; get line counter dec a ; ..decrement it ld (LnsCnt),a ; ..and put it back call nz,crlf ; (new line if screen not filled) ret nz ; (screen not filled) call eprint ; screen is filled, print "[more]" db CR,LF db '[more]',0 call cin ; ..and pause for user to press a key call eprint ; key pressed, erase "[more]" db CR,' ',CR,0 cp CtrlC ; was the key ^C? jr z,Abort ; (yes, abort the program) cp ' ' ; was the key a space? ld a,1 ; assume it was ld (LnsCnt),a ret z ; (it was, so advance by one line) ld a,(ScrLns) ; otherwise, fill the screen again ld (LnsCnt),a ret ; ; CkCon -- Checks for user input at the end of each line, while file is ; being read. Aborts program if ^C is received. ; CkCon: cp LF ; line feed? ret nz ; (not end of line) push af ; save character call condin ; check console cp CtrlC ; ^C? jr z,CkCon1 ; (yep, abort) pop af ; recover character ret ; CkCon1: pop af ; adjust stack call ClsFil ; close file jr Abort ; ..and abort ; ; CkAbrt -- Checks for user input. If ^S, wait for another key before ; continuing. If ^C, abort the program. If anything else, ignore it. ; CkAbrt: call condin ; check for key press ret z ; (none, continue) cp CtrlS ; ^S? call z,cin ; (yes, wait for another key) cp CtrlC ; ^C? ret nz ; (no, continue) call crlf ; yes, send new line and abort ; ; Abort -- abort to operating system. Set error flag according to ; configuration. ; Abort: ld a,(AbtFlg) ; error code (if any) call eprint db 'Aborted',0 jp ErExit ; ; Usage -- intelligent usage screen ; Usage: call eprint DftNam: db 'CCOUNT Version ' db Vers/10+'0','.',Vers mod 10+'0',SubVers,CR,LF db 'Counts characters in text files.',CR,LF db 'Usage:',CR,LF,' ',0 ld hl,comnam call epstr ; print invocation or default name call eprint db ' {dir:}infile {byte {...}} {/options}',CR,LF db 'Options:',CR,LF db ' A count ',0 ld a,(AllFlg) ; check configuration flag or a ld hl,MsgAll jr z,Usage1 ld hl,MsgGiv Usage1: call epstr call eprint db ' characters',CR,LF db ' H high bits are ',0 ld a,(HiFlg) ; check configuration flag or a call nz,PrtIn call eprint db 'significant',CR,LF db ' P ',0 ld a,(PagFlg) ; check configuration flag or a call z,PrDont call eprint db 'page screen',CR,LF db 'Bytes:',CR,LF db ' ''c lower-case character',CR,LF db ' "c upper-case character',CR,LF db ' ^c control character',CR,LF db ' nn decimal ASCII value',CR,LF db ' nnH hexadecimal ASCII value',CR,LF db 'Hexadecimal values must begin with a digit.',0 jp Finish ; MsgAll: db 'all',0 MsgGiv: db 'given',0 ; PrtIn: call eprint db 'in',0 ret ; PrDont: call eprint db 'don''t ',0 ret ; DSEG ; ScrLns: ds 1 ; number of screen lines LnsCnt: ds 1 ; screen lines counter OpAFlg: ds 1 ; FFh=display all characters OpHFlg: ds 1 ; 0=ignore high bits OpPFlg: ds 1 ; 0=page screen BegIni equ $ ; beginning of data initialization area SchCnt: ds 1 ; number of characters we're counting SchStr: ds 65 ; string of bytes to match and count ChrCnt: ds 4 ; total characters found CCount: ds 256*4 ; character count table (256 * 4 bytes) InCtrl: ds 1 ; control table for input file ds 7 BufAdr: ds 2 InFcb: ds 36 EndIni equ $ ; end of data initialization area ; ds 100 ; stack Stack: ds 2 ; stack pointer storage ; end