supermario/bin/MPW-3.2.3/Examples/AExamples/Count.a
2019-06-29 22:17:03 +08:00

568 lines
15 KiB
Plaintext

*-------------------------------------------------------------------------------------------
*
* NAME
* Count.a -- count lines and characters
*
* SYNOPSIS
* Count [-l] [-c] [file…]
*
* DESCRIPTION
* "Count" counts the lines and characters in its input, and writes the
* counts to standard output. If no files are specified standard input is
* read. If more than one file is specified, separate counts are written
* for each file, one per line, preceeded by the file name. A total is also
* written following the list of files.
*
* COPYRIGHT
* Copyright Apple Computer, Inc. 1985-1987
* All rights reserved.
*
* MODIFICATION HISTORY
* 10/15/90 JAK No options and both options produce
* char and line output.
*-------------------------------------------------------------------------------------------
CASE OBJ
INCLUDE 'traps.a' ; for Pack7
INCLUDE 'packmacs.a' ; for NumToStr, which calls Pack7
INCLUDE 'intenv.a' ; so we can get our args, open files, etc.
INCLUDE 'signal.a' ; so we can handle 'Command-.'
IMPORT INITCURSORCTL ; to init the spinning beach ball
IMPORT ROTATECURSOR ; for the spinning beach ball
RC_Normal EQU 0
RC_ParmErrs EQU 1
RC_Abort EQU 2 ; Return codes
;SIGINT EQU 2
EOLChar EQU $0D ; the Return character marks the end of line
STRING Pascal ; length byte strings
BufSize EQU 1024 ; size of input buffer
* global data--these declarations outside of any module are allocated and accessed
* relative to register A5
Globals RECORD
ArgV DS.L 1 ; the address of our arguments
ArgC DS.L 1 ; the number of our arguments
RetCode DC.B RC_Normal ; set to RC_ …
CRStr DC.W $010D ; a 'string' that is a return character
Interrupted DC.B 0 ; not interrupted yet
progname DS.L 1 ; the address of our name
NumFiles DC.W 0 ; the number of files to process
WriteChars DC.B 0 ; TRUE if the user wants line count
WriteLines DC.B 0 ; TRUE if the user wants char count
Opts DC.B 0 ; TRUE if user has selected either line or char
LineCount DC.L 0
CharCount DC.L 0
TotalLines DC.L 0
TotalChars DC.L 0
Max DC.B 5 ; length of 'Total' string, or the longest filename
myBuf DS.B BufSize ; for reading from the file
curByte DC.W -1 ; the current offset in myBuf
lastByte DS.W 1 ; last valid byte in myBuf
ENDR
*******************************************************************
* ROUTINE WriteStrings
* FUNCTION calls write for an arbitrary number of strings
* INPUT a NIL pointer on stack, followed by arbitrary number of string pointers,
* and the file descriptor
* OUTPUT none
* NOTES PROCEDURE WriteStrings (NIL, Str^ …,FD);
*******************************************************************
WriteStrings PROC
Link A6,#0 ; set up a stack frame
Move.L A2,-(SP) ; and save one permanent register
LEA 8(A6),A2 ; point A2 at first (last) parameter
* next, create a call block for the write routine on the stack
Clr.L -(SP) ; set the length to zero
SubQ #8,SP ; make room for the buffer and fd
Move.L (A2)+,(SP) ; put the file descriptor in its place
* now pull the arguments off the stack and write them out
@1 Move.L (A2)+,D0 ; get the string pointer
BEQ.S @0 ; the list of strings is NIL terminated
Move.L D0,A0 ; move the pointer so we can use it
Move.B (A0)+,11(SP) ; to move the length byte into the length arg
Move.L A0,4(SP) ; move the pointer into the buffer arg
JSR write ; write it--CASE is significant
BRA.S @1 ; and try again
* done writing. Clean up the stack and return
@0 Move.L A2,A1 ; we still need this
Move.L -4(A6),A2 ; restore A2
UNLK A6 ; throw away the scratch stack stuff
Move.L (SP),A0 ; get the return address
Move.L A1,SP ; throw away the parameters
JMP (A0) ; and bail out
ENDPROC
*******************************************************************
* ROUTINE Stop
* FUNCTION terminates execution
* INPUT Message(A6)--error message to display on exit
* OUTPUT Tool execution is terminated--return to MPW shell
* NOTES call with a JMP, not a JSR--it doesn't return to caller anyway
*******************************************************************
Stop PROC
* don't bother to save permanent registers--we're never going back to the caller
WITH Globals
MoveQ #0,D0
Move.B RetCode,D0 ; we'll return this status
TST.B Interrupted
BEQ.S @1
Move.B #RC_Abort,D0 ; unless we were interrupted
@1 Move.L D0,-(SP)
JSR exit ; (does not return)
ENDWITH
ENDPROC
*******************************************************************
* ROUTINE Intr
* FUNCTION sets the global Interrupted to TRUE--passed to the Runtime routine
* INPUT
* OUTPUT Interrupted is set TRUE
* NOTES
*******************************************************************
Intr PROC
ST Globals.Interrupted
RTS
ENDPROC
*******************************************************************
* ROUTINE SyntaxError
* FUNCTION Report a syntax error for the command line
* INPUT above(A7)--pointers to strings to append to the error message
* OUTPUT displays error message and calls Stop to terminate program execution
* NOTES call with a JMP, not a JSR--it doesn't return anyway
*******************************************************************
SyntaxError PROC
WITH Globals
PEA #' - '
Move.L progName,-(SP)
PEA #'### '
PEA DiagnosticFD
JSR WriteStrings ; finish writing the error line
CLR.L -(SP)
PEA CrStr
PEA #' [-l] [-c] [files…].'
Move.L progName,-(SP)
PEA #'# Usage - '
PEA DiagnosticFD
JSR WriteStrings ; and write the 'usage' line
JMP Stop
ENDWITH
ENDPROC
*******************************************************************
* ROUTINE LetterOpt
* FUNCTION Set a letter option
* INPUT D0--char
* D4--ArgVIndex
* A1--address of current option
* OUTPUT if char = valid option, set option flag, else syntaxerror
* NOTES PROCEDURE LetterOpt(Opt: Char; VAR ArgVIndex: Integer);
* ArgVIndex can be updated by this routine to skip arguments to options
*******************************************************************
LetterOpt PROC
Cmp.B #'l',D0
BEQ.S @0
Cmp.B #'L',D0 ; -l?
BNE.S @1
@0 ST Globals.WriteLines ; means only lines
ADDQ #1,Globals.Opts ; yes, an option has been selected
RTS
@1 Cmp.B #'c',D0 ; -c?
BEQ.S @2
Cmp.B #'C',D0
BNE.S @3
@2 ST Globals.WriteChars ; means only characters
ADDQ #1,Globals.Opts ; yes, an option has been selected
RTS
@3 Clr.L -(SP) ; otherwise its a bad option
PEA Globals.CRStr
PEA #'" is not an option.'
Move.L A1,-(SP) ; pointer to current option
PEA #'"' ; the leading quote around the option
JMP SyntaxError
* SyntaxError never returns
ENDPROC
*******************************************************************
* ROUTINE Init
* FUNCTION Tool initalization
* INPUT
* OUTPUT
* NOTES PROCEDURE Init;
*******************************************************************
Init PROC
ForPascal EQU 1
InitSF RECORD {OldA6},DECREMENT
ShellRet DS.L 1
RetAddress DS.L 1
OldA6 DS.L 1
EnvP DS.L 1
Size EQU *
ENDR
WITH Globals
Link A6,#InitSF.Size
PEA ForPascal ; optimized Move.L #1,-(SP)
PEA InitSF.EnvP(A6)
PEA ArgV
PEA ArgC
Move.L InitSF.ShellRet(A6),-(SP)
JSR _RTInit ; get things set up
LEA InitSF.Size(A6),SP ; throw away the arguments
PEA Intr ; our interrupt handler
Move.L #SIGINT,-(SP)
JSR signal ; so we can handle user interrupts
* D0 has handle to prevSig, which we will ignore
LEA InitSF.Size(A6),SP ; throw away the arguments
MoveM.L A2/D3-D4,-(SP) ; let's do some ArgV processing
Move.L ArgV,A2
Move.L ArgC,D3
Move.L (A2)+,progName ; we now have a global that points to our name
Move.B #RC_ParmErrs,RetCode
MoveQ #0,D4 ; ArgVIndex := 0;
@0 AddQ #1,D4
Cmp.L D4,D3
BLE.S DoneArgOptions
Move.L (A2)+,A0 ; get the next arg
Move.L A0,A1 ; keep a pointer to the start of the string
Move.B (A0)+,D1 ; get the len
BEQ.S @0 ; arg := ''; get the next one
Move.B (A0)+,D0
Cmp.B #'-',D0 ; is it an option?
BNE.S @1
Move.B (A0)+,D0
JSR LetterOpt
* caller to LetterOpt can check if ArgIndex changed--if so, skip the increment of ArgIndex
BRA.S @0 ; go again
@1 AddQ #1,NumFiles ; bump the file count
Cmp.B Max,D1 ; a new longest name?
BLE.S @0
Move.B D1,Max ; a new max
BRA.S @0
DoneArgOptions
Move.B #RC_Normal,RetCode ; parameters ok so far
Clr.L -(SP)
JSR InitCursorCtl ; initialize the spinning cursor
Tst.B Interrupted ; user break yet?
BEQ.S @3
JMP Stop
@3 MoveM.L (SP)+,A2/D3-D4
UNLK A6
RTS
ENDWITH
ENDPROC
*******************************************************************
* ROUTINE PrintCount
* FUNCTION writes the filename (if needed), linecount and/or charcount to standard output
* INPUT pointer to the filename in A2 (if counting multiple files)
* OUTPUT
* NOTES
*******************************************************************
PrintCount PROC
PrintSF RECORD 0,DECREMENT
LineBuf DS.B 256
tempStr DS.B 10
MaxBlanks DS.B 1
ALIGN
Size EQU *
ENDR
LINK A6,#PrintSF.Size
MoveM.L D6/A3,-(SP)
Move.W #(256/4)-1,D0 ; fill LineBuf with blanks
Move.L #' ',D1
LEA PrintSF.LineBuf(A6),A0
@0 Move.L D1,(A0)+
DBRA D0,@0
WITH Globals
MoveQ #3,D6 ; skip first three blanks
LEA 4+PrintSF.LineBuf(A6),A3 ; A3 is the current offset into lineBuf
Cmp.W #1,NumFiles ; >1 if more than one file
BLE.S noName
Move.L D7,A0 ; D7 points to the current filename
MoveQ #0,D1
Move.B (A0)+,D1 ; get the length byte
Add.B D1,D6 ; update the new length
MoveQ #0,D0
Move.B Max,D0 ; Max is the longest name
Sub.B D1,D0 ; D0 is how much shorter current is than max
AddQ #3,D0
Add.B D0,D6 ; update the counter
BRA.S @2 ; zero base the length
@1 Move.B (A0)+,(A3)+
@2 DBRA D1,@1 ; move in the filename
Add.W D0,A3 ; and update our roving pointer
noName
ENTRY DoLines,DoChars,WriteBuf
; if no options selected, print both lines and chars.
TST.B Opts
BNE.S @0
JSR DoLines ; insert lines into buffer
JSR DoChars ; insert chars into buffer
BRA.S @Exit
@0 TST.B WriteLines ; do we want to print the line count?
BEQ.S @1
JSR DoLines ; insert lines into buffer
@1 TST.B WriteChars
BEQ.S @Exit
JSR DoChars ; insert chars into buffer
@Exit
Move.B D6,PrintSF.lineBuf(A6) ; set the length byte
CLR.L -(SP) ; set up the stack for WriteStrings
PEA CRStr
PEA PrintSF.linebuf(A6)
PEA OutputFD
JSR WriteStrings
MoveM.L (SP)+,D6/A3
UNLK A6
RTS
DoLines
Move.L lineCount,D0
add.w #10,D6 ; update counter
JMP WriteBuf
DoChars
MOVE.L charcount,D0
add.w #13,D6 ; add field length and 3 blanks to counter
JMP WriteBuf
WriteBuf
LEA PrintSF.tempStr(A6),A0
_NumToString
MoveQ #0,D1
Move.B (A0)+,D1
MoveQ #10,D0 ; we'll say this field is 10 long
Sub.B D1,D0 ; D0 := field length-length of numstring
Add.W D0,A3 ; skip the extra padding
BRA.S @2 ; zero base by doing the DBCC first
@1 Move.B (A0)+,(A3)+
@2 DBRA D1,@1 ; move in the number
RTS
ENDWITH
ENDPROC
*******************************************************************
* ROUTINE PrintTotals
* FUNCTION writes the summary line to standard output
* INPUT
* OUTPUT
* NOTES calls PrintCount to print the totals if appropriate
*******************************************************************
PrintTotals PROC
Cmp.W #1,Globals.numFiles
BGT.S @0
RTS ; do nothing if only one file
@0 LEA #'Total',A0
Move.L A0,D7 ; our new 'filename'
Move.L Globals.totallines,Globals.linecount
Move.L Globals.totalchars,Globals.charcount
JSR PrintCount ; recycled code
RTS
ENDPROC
*******************************************************************
* ROUTINE GetChar
* FUNCTION reads from the file in hunks, and hands out a character at a time
* INPUT fd: long in D4--the file descriptor for the file to read
* OUTPUT the next character in D0--zero = TRUE means end of file
* NOTES
*******************************************************************
GetChar PROC
WITH Globals
Move.W curByte,D1 ; get the current offset
BPL.S @0 ; we have a valid block currently
@1 PEA BufSize ; Move.L #BufSize,-(SP)--count
PEA mybuf ; where
Move.L D4,-(SP) ; the file descriptor
JSR read ; read the next block
LEA 12(SP),SP ; clean up the stack
MoveQ #0,D1 ; start at the beginning again
Move.W D0,lastByte
BNE.S @2 ; end of file?
RTS ; pass the zero flag back to the caller
@0 Move.W lastByte,D0 ; get the last valid byte
@2 Cmp.W D0,D1
BGE.S @1
LEA mybuf,A0
Move.B 0(A0,D1),D0 ; read the next character
AddQ.W #1,D1
Move.W D1,curByte ; update curByte
RTS
ENDWITH
ENDPROC
*******************************************************************
* ROUTINE CountFile (fd:filedescriptor)
* FUNCTION counts the lines and characters in fd
* INPUT fd: long--the file descriptor for the file to count
* OUTPUT charcount, linecount, totalchars, totallines updated
* NOTES
*******************************************************************
CountFile PROC
WITH globals
CLR.L linecount
Move.L (SP)+,A1 ; save the return address
Move.L (SP),D0 ; and the file descriptor
Move.L A1,(SP) ; return the return address
MoveM.L D4-D7,-(SP)
Move.L D0,D4 ; save the fd for the getchar routine
MoveQ #0,D7 ; initialize our counter registers
MoveQ #0,D6
ReadLoop JSR getchar
BEQ.S fileEnd ; zero means no more bytes to read
AddQ.L #1,D7 ; otherwise bump the char counter
Move.B D0,D5 ; save the char in a permanent register
CMP.B #EOLChar,D5 ; bump linecount?
BNE.S ReadLoop
AddQ.L #1,D6 ; yes
Move.L D6,-(SP)
JSR RotateCursor ; spin the ball
Tst.B Interrupted ; user break yet?
BEQ.S ReadLoop ; no--continue
JMP Stop ; abort mission
fileEnd CMP.B #EOLChar,D5 ; was the last character read a line end?
BEQ.S @0
TST.L D7 ; have we counted any characters
BEQ.S @0 ; no--don't increment line count
AddQ.L #1,D6
@0 Move.L D6,lineCount ; update globals and leave
Move.L D7,charCount
Add.L D6,totallines
Add.L D7,totalchars
MoveM.L (SP)+,D4-D7
RTS
ENDPROC
*******************************************************************
* ROUTINE Count
* FUNCTION the MAIN proc--calls Init, then processes the files
* INPUT
* OUTPUT
* NOTES
*******************************************************************
Count MAIN
IMPORT c2pstr,p2cstr
WITH Globals
JSR Init
Move.L ArgV,A2
ADDQ #4,A2 ; skip the program name
Move.L (A2)+,D7 ; set the cc's
BNE.S @0 ; otherwise count stdin
* CountStdIn
Clr.L -(SP) ; we don't need to open standard input
JSR CountFile
JSR PrintCount
JMP Stop
@1 Move.L (A2)+,D7 ; set the cc's
BEQ.S ShowTotals ; ArgV is NIL terminated
@0 Move.L D7,A0
Move.B (A0)+,D0 ; pick up the length byte
BEQ.S @1 ; zero length--next, please
Move.B (A0)+,D1 ; now the first charcter
Cmp.B #'-',D1 ; an option--already handled by Init
BEQ.S @1
* otherwise we have a file to process
Move.L D7,-(SP) ; convert the filename to a C string
JSR p2cstr
PEA O_RDONLY
Move.L D7,-(SP)
JSR open ; open the file
Move.L D0,D6 ; save the result--fd or error
JSR c2pstr ; love those length bytes
LEA 12(SP),SP ; throw away the arguments
Move.L D6,-(SP) ; push the fd
BMI.S BailOut ; an error if negative
JSR CountFile
JSR PrintCount
BRA.S @1
ShowTotals
JSR PrintTotals
JMP Stop
BailOut CLR.L (SP) ; space came from move D6 above
PEA CRStr
Move.L D7,-(SP)
PEA #' - could not open file '
Move.L progName,-(SP)
PEA #'### '
PEA DiagnosticFD ; optimized Move.L #DiagnosticFD,-(SP)
JSR WriteStrings
CLR.L -(SP)
PEA CRStr
PEA #' [-l] [-c] [files…].'
Move.L progName,-(SP)
PEA #'# Usage - '
PEA DiagnosticFD ; optimized Move.L #DiagnosticFD,-(SP)
JSR WriteStrings
Move.B #RC_ParmErrs,RetCode
JMP Stop
ENDWITH
ENDPROC
END