mirror of
https://github.com/elliotnunn/supermario.git
synced 2025-02-21 01:29:30 +00:00
568 lines
15 KiB
Plaintext
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
|
|
|