********************************************************* * * * wc.a - word count utility. Counts characters, words * * and lines in a file. A 'word' is any collection * * of non-space characters in a row, until a space * * (or tab) or linefeed character is encountered. * * And a linefeed marks the end of a line. * * * * usage: wc * * * * a freely-redistributable program * * * * by: W. Wesley Howe (bix: whowe plink: WHOWE) * * * * 31-Jul-88 * * * ********************************************************* * * Assembled with CAPE 2.0 * EXEOBJ NORMOBJ ;we don't want PC-relative references to data * * First some equates instead of includes.. * BR EQUR A4 ;base-register for this program BUFFSZ EQU 976 ;2-block buffer size for speed * AbsExecBase EQU 4 ;THE fixed location in the Amiga * Disable EQU -120 ;exec library offsets here Enable EQU -126 FindTask EQU -294 OldOpenLibrary EQU -408 CloseLibrary EQU -414 * tc_SigRecvd EQU $1A ;exec equate - task structure offset * Open EQU -30 ;DOS library offsets Close EQU -36 Read EQU -42 Write EQU -48 Output EQU -60 * MODE_OLDFILE EQU 1005 ;DOS equate (in AmigaDOS manual) * CODE ************************************************************************* * * * The following is the entry point when the program is run. The overall * * structure here is to find the filename on the command line, open it * * through AmigaDOS, then read through the file counting the characters, * * words and lines, then close the file and report the numbers before * * exiting. At various points along the way we will check if things are * * normal or if the user typed a control-C, and exit if needed. * * * ************************************************************************* wc: movem.l d2-d7/a2-a6,-(sp) ;save registers per convention lea Bseg,BR move.l a0,a2 ;save command pointer DOS gave us bsr.l OpenDOS ;we put this below as a subroutine tst.l d0 beq.l 998$ ;big problem - no DOS move.l a2,a0 moveq #0,d0 cmp.l a0,d0 beq.s 3$ ;no command - issue usage message lea Fnam1(BR),a1 ;our destination 1$: cmpi.b #' ',(a0) ;strip any leading spaces bne.s 2$ addq.l #1,a0 bra.s 1$ 2$: move.b (a0)+,d0 ;we shouldn't hit a null here. beq.s 3$ cmpi.b #$0a,d0 ;linefeed marks end of input line bne.s 10$ 3$: bra.l 950$ ;issue usage message 10$: move.b d0,(a1)+ ;copy our character move.b (a0)+,d0 ;get the next one cmpi.b #$0a,d0 ;linefeed marks last of line beq.s 13$ cmpi.b #' ',d0 ;spaces separate names - we will ;consider this as end of filename bne.s 10$ ;otherwise jump to save point. 13$: moveq #0,d0 ;we got here at end of line move.b d0,(a1) ;adding a NULL terminator to name 30$: moveq #0,d0 ;we now have the filename move.l d0,File1(BR) ;clear handles for fileclosing move.l d0,Chars(BR) ;set char count to zero move.l d0,Words(BR) ;set word count to zero move.l d0,Lines(BR) ;set line count to zero move.w d0,BrkFlg(BR) ;set break flag off move.w d0,FndWord(BR) ;start with no word found move.w d0,BuffChrs(BR) ;clear buffer count move.w d0,LastRead(BR) ;and DOS read count move.l DOSBase(BR),a6 ;prepare to open the filename move.w #MODE_OLDFILE,d2 ;an existing file is what we want ext.l d2 lea Fnam1(BR),a0 move.l a0,d1 jsr Open(a6) tst.l d0 beq.s 940$ ;a zero return means file not opened move.l d0,File1(BR) ;save filehandle 40$: bsr.s GetData ;loop to count the words and lines tst.w d0 beq.s 900$ ;error or EOF was detected cmpi.b #$09,d1 ;check for tab or space beq.s 50$ cmpi.b #' ',d1 beq.s 50$ cmpi.b #$0a,d1 ;check for linefeed beq.s 60$ tst.w FndWord(BR) ;here if we haven't already found ;the start of a word then we call ;this a word start. bne.s 40$ addq.l #1,Words(BR) addq.w #1,FndWord(BR) bra.s 40$ 50$: clr.w FndWord(BR) ;spaces mark the end of a word bra.s 40$ 60$: addq.l #1,Lines(BR) ;linefeeds mark the end of a line clr.w FndWord(BR) ;as well as a word bra.s 40$ 900$: bsr.l CloseFiles ;close file before we go tst.w BrkFlg(BR) ;if a ^C break we skip the report bne.s 998$ bsr.l Report tst.w d0 bmi.s 998$ ; -1 return means write error moveq #0,d0 bra.s 999$ 940$: lea Nof1(PC),a0 ;error message bsr.l Print ;skip error test - this is an error bra.s 998$ 950$: lea Usage(PC),a0 bsr.s Print ;skip error test - this is an error 998$: move.l #20,d0 999$: bsr.l CloseDOS movem.l (sp)+,d2-d7/a2-a6 ;restore the registers we saved rts ;this takes us back to DOS ************************************************************************* * * * The following routine is a buffered read routine. We read 2 blocks at * * a time (which speeds things up a lot) into part of the BSS section. * * (We use a BSS section so that all this empty space isn't all loaded * * from disk, to keep the program size down.) Each time we call this * * routine, if there are still characters in the buffer we snatch one * * and return. If they are all gone, we try to read more. If we can't * * get any more, we must be at the end of the file. We return either 1 * * or 0 in d0 to indicate success or failure, and place the character we * * got into d1. * * * ************************************************************************* GetData: tst.w BuffChrs(BR) beq.s 10$ ;if none left, get more lea Buff1(BR),a0 moveq #0,d0 move.w LastRead(BR),d0 ;we get the next one by taking the sub.w BuffChrs(BR),d0 ;start of the buffer, adding what subq.w #1,BuffChrs(BR) ;we read, and subtracting the number move.b 0(a0,d0.w),d1 ;left to get address of next char moveq #1,d0 ;then we decrease the number left rts 10$: bsr.l AAbort ;^C or ^D check tst.w d0 beq.s 20$ ;no break detected lea BrkStr(pc),a0 bsr.s Print ;tell about it addq.w #1,BrkFlg(BR) ;shuts off ending report bra.s 30$ ;go to failure exit point 20$: move.l DOSBase(BR),a6 move.l File1(BR),d1 ;filehandle from open lea Buff1(BR),a0 move.l a0,d2 ;address of buffer move.l #BUFFSZ,d3 ;length we want to read jsr Read(a6) ;get DOS to do it for us. tst.l d0 bmi.s 30$ ;error beq.s 30$ ;EOF (End Of File) move.w d0,LastRead(BR) ;d0 has number we actually read in add.l d0,Chars(BR) ;keeps up characters count subq.l #1,d0 ;decrease because we get one now move.w d0,BuffChrs(BR) move.b Buff1(BR),d1 ;this time only next is at front moveq #1,d0 ;success flag rts 30$: moveq #0,d0 ;failure flag rts ************************************************************************* * * * This routine will print a 'BSTR'. That is, a string of characters * * that has the number of characters as the first one, followed by that * * many characters. This style is easy to interface to the Write routine * * since the number of characters is already counted at assembly time. * * Therefore, we don't have to write a routine to count the characters * * before they are printed. * * * ************************************************************************* Print: move.l Screen(BR),d1 ;'file' pointer we got for console moveq #0,d3 ;clear length upper bytes move.b (a0)+,d3 ;get length from first byte move.l a0,d2 ;and pass new value as buffer move.l DOSBase(BR),a6 jsr Write(a6) ;call DOS to do the work rts ;check for error return after call ************************************************************************* * * * This routine will call Close only if we managed to Open a file. * * * ************************************************************************* CloseFiles: move.l DOSBase(BR),a6 move.l File1(BR),d1 beq.s 1$ ;file was never opened jsr Close(a6) 1$: rts ************************************************************************* * * * This function prints out the collected numbers at the end of the * * program, calling several other routines to help it. * * * ************************************************************************* Report: lea D1Str(PC),a0 ;first part of report bsr.s Print tst.w d0 bmi.s 100$ move.l Chars(BR),d0 ;print chars bsr.s PrtDec tst.w d0 bmi.s 100$ lea D2Str(PC),a0 ;print Words bsr.s Print tst.w d0 bmi.s 100$ move.l Words(BR),d0 bsr.s PrtDec tst.w d0 bmi.s 100$ lea D3Str(PC),a0 bsr.s Print ;print Lines tst.w d0 bmi.s 100$ move.l Lines(BR),d0 bsr.s PrtDec tst.w d0 bmi.s 100$ lea D4Str(PC),a0 ;closeout printing bsr.s Print tst.w d0 bmi.s 100$ moveq #0,d0 100$: rts ************************************************************************* * * * This routine, used by the Report function above, takes the long * * value in d0 and converts it into an ascii decimal string. This is * * done by dividing the number repetitively by 10, taking the remainder * * and creating a number string in the TmpBuff that is backwards. We * * then take this string and copy it backwards, then print it to the * * console screen. * * * ************************************************************************* PrtDec: tst.l d0 bne.s 10$ move.w #$3000,NumBuff(BR) ;special case for zero moveq #2,d0 ;force length lea NumBuff(BR),a0 bra.s 900$ 10$: lea TmpBuff(BR),a1 ;prepare pointers to TmpBuff moveq #0,d1 20$: divu #10,d0 ;divide by ten swap d0 ;remainder (modulo) in upper word add.b #'0',d0 ;convert to ascii move.b d0,0(a1,d1.w) ;save digit addq.w #1,d1 ;bump count clr.w d0 ;clears bottom half swap d0 ;restore number/10 tst.l d0 ;if we got to zero we're done bne.s 20$ ;else divide again lea NumBuff(BR),a0 move.w d1,d0 addq.w #1,d0 ;save char count 30$: move.b -1(a1,d1.w),(a0)+ ;flip the string while copying dbra d1,30$ lea NumBuff(BR),a0 900$: move.l Screen(BR),d1 ;file move.l a0,d2 ;buffer moveq #0,d3 move.w d0,d3 ;length move.l DOSBase(BR),a6 ;send to DOS to print it jsr Write(a6) rts ************************************************************************* * * * This is used at the startup to open the DOS library, and to find the * * 'filehandle' for the console screen with the Output() function. * * * ************************************************************************* OpenDOS: move.l AbsExecBase,a6 ;We need Exec to find DOS moveq #0,d0 ;any version of DOS is O.K. lea DOSName(PC),a1 ;name of our library jsr OldOpenLibrary(a6) ;compatible with older DOS versions move.l d0,DOSBase(BR) tst.l d0 ;a zero means we didn't find DOS beq.s 1$ ;so we can't find the Screen move.l d0,a6 ;we need DOS to find console screen jsr Output(a6) move.l d0,Screen(BR) ;save for later beq.s 1$ ;a failure if we didn't get it bsr.s ClrSigs ;a sure way to avoid spurious ^C moveq #1,d0 ;flag success to caller 1$: rts ;retval checked after call ************************************************************************* * * * A simple routine to close the library we opened above before we exit. * * * ************************************************************************* CloseDOS: tst.l DOSBase(BR) beq.s 1$ move.l AbsExecBase,a6 move.l DOSBase(BR),a1 jsr CloseLibrary(a6) 1$: rts ************************************************************************* * * * This is a hack to clear any ^C or ^D signals that may have been set * * for or by some other program prior to when we started up. It is not * * the 'approved' way to do it, but it does work... * * * ************************************************************************* ClrSigs: move.l 4,a6 moveq #0,d0 move.l d0,a1 jsr FindTask(a6) move.l d0,MyTcb(BR) ;the TCB (task control block) address jsr Disable(a6) ;hold the phone (stops task switching) move.l MyTcb(BR),a0 adda.w #tc_SigRecvd,a0 ;offset to signals move.l (a0),d0 andi.l #$ffff0fff,d0 ;clears nasty old break signals move.l d0,(a0) jsr Enable(a6) ;back on the line 1$: rts ************************************************************************* * * * Here we merely look to see if one of the signal bits from ^C or ^D * * has been set by the system, indicating that the user wants us to * * stop and go away (since we don't have our own window, they can't hit * * a close gadget to tell us this). We call this every time we need to * * read from disk. * * * ************************************************************************* AAbort: move.l MyTcb(BR),a0 adda.w #tc_SigRecvd,a0 move.l (a0),d0 andi.l #$00003000,d0 ;CTRL-C and CTRL-D rts ;non-zero return means break sent ************************************************************************* * * * Fixed DATA (for PC-Relative references). We need to read this but not * * to write to these locations. All of our READ/WRITE data is in another * * section, and the program has been written to use it as uninitialized * * data. That is, we specifically clear any locations that are important * * to be in a known state. * * * ************************************************************************* DOSName: CSTRING 'dos.library' DS.W 0 Usage: PSTRING 'Usage: wc \10' DS.W 0 Nof1: PSTRING 'Unable to open file\10' DS.W 0 D1Str: PSTRING 'Characters = ' DS.W 0 D2Str: PSTRING '.\10 Words = ' DS.W 0 D3Str: PSTRING '.\10 Lines = ' DS.W 0 D4Str: PSTRING '.\10' DS.W 0 BrkStr: PSTRING '\10*** BREAK ***\10' DS.W 0 * BSS ************************************************************************* * * * Uninitialized DATA storage space (for Base-register references). This * * section will not actually be part of our disk file, but rather just * * a description of our requirements will be in the executable. The * * loader will then set aside some memory for us to use, without us * * having to specifically ask for it. However, all the locations we get * * will contain random data (or, at least, there is no guarantee that * * the system will clear it for us.) * * * ************************************************************************* Bseg: ;to let us load our base-register DOSBase: DS.L 1 ;to save the DOS library address File1: DS.L 1 ;to save the file-handle in Screen: DS.L 1 ;to save the console file-handle Chars: DS.L 1 ;our three counters Words: DS.L 1 Lines: DS.L 1 MyTcb: DS.L 1 ;the TCB for the break detect BrkFlg: DS.W 1 ;used by program logic FndWord: DS.W 1 ;used by program logic NumBuff: DS.B 16 ;string we place decimal digits in TmpBuff: DS.B 16 ;where the string goes backwards Fnam1: DS.B 108 ;to hold the filename characters Buff1: DS.B BUFFSZ ;to hold 2 blocks BuffChrs: DS.W 1 ;number left in buffer LastRead: DS.W 1 ;number originally in buffer * END *