116 lines
4.4 KiB
Plaintext
116 lines
4.4 KiB
Plaintext
bdos: equ 5 ; CP/M syscalls
|
|
putch: equ 2 ; Print a character
|
|
puts: equ 9 ; Print a string
|
|
fopen: equ 15 ; Open a file
|
|
fread: equ 20 ; Read a file
|
|
fcb: equ 5ch ; FCB for file given on command line
|
|
dma: equ 80h ; Default DMA
|
|
org 100h ; CP/M loads the program starting at page 1
|
|
;; Zero out pages two and three (to keep a 16-bit counter
|
|
;; for each possible byte in the file).
|
|
;; We can do this because this program is small enough to
|
|
;; fit in page 1 in its entirety.
|
|
xra a ; Zero A.
|
|
mov b,a ; Zero B too (make it loop 256 times)
|
|
lxi d,200h ; Start of page two
|
|
zero: stax d ; Zero out a byte (store A, which is zero)
|
|
inx d ; Next byte
|
|
stax d ; Zero out another byte
|
|
inx d ; Next byte
|
|
dcr b ; Decrement the loop counter.
|
|
jnz zero ; Continue until B comes back to zero.
|
|
;; Open the file given on the command line.
|
|
lxi d,fcb ; CP/M always tries to parse the command line,
|
|
mvi c,fopen ; and gives us a file "object" in page zero.
|
|
call bdos ; We can just call fopen on it.
|
|
inr a ; It sets A=FF on error, so if incrementing A
|
|
jz error ; rolls back over to 0, that's an error.
|
|
;; Process the file record by record.
|
|
;; In CP/M, each file consists of a number of 128-byte
|
|
;; records. An exact size is not kept.
|
|
;; If a text file is not an exact multiple of 128 bytes
|
|
;; long, the last record will contain a ^Z (26 decimal),
|
|
;; and anything after that byte should be ignored.
|
|
read: lxi d,fcb ; From the file control block (the "object"),
|
|
mvi c,fread ; read one record. By default it ends up in
|
|
call bdos ; the last half of page zero.
|
|
ana a ; Zero carry flag.
|
|
rar ; Low bit says if end reached
|
|
jc output ; If so, go print the table
|
|
ana a ; If any other bits are set, that's a
|
|
jnz error ; read error.
|
|
;; Count the characters in the current record.
|
|
lxi d,dma-1 ; Set DE to point just before the record
|
|
byte: inr e ; Go to the next byte.
|
|
jz read ; If end of record, go get next record.
|
|
ldax d ; Grab the current byte
|
|
cpi 26 ; If it is EOF, we're done.
|
|
jz output ; Go print the table
|
|
mov l,a ; Otherwise, increment the counter for this
|
|
mvi h,2 ; character: the low byte is kept in page 2.
|
|
inr m ; 'm' means the value in memory at HL.
|
|
jnz byte ; If no rollover, we're done; count next byte
|
|
inr h ; But we're keeping a 16-bit counter, so
|
|
inr m ; if there is rollover, increment high byte.
|
|
jmp byte ; The high byte is in page 3 -unorthodox, but
|
|
; it's easy to access here.
|
|
;; We've done the whole file. For each printable
|
|
;; ASCII character (32..126), print the character and
|
|
;; the count.
|
|
output: mvi a,32 ; Start at 32.
|
|
;; Print a character and its counter
|
|
char: mov l,a ; Load 16-bit counter into DE. Low byte
|
|
mvi h,2 ; is in page 2 at a;
|
|
mov e,m
|
|
inr h ; And the high byte is in page 3.
|
|
mov d,m
|
|
mov a,d ; Test if the counter is zero
|
|
ora e
|
|
mov a,l ; Put the character back in A
|
|
jz next ; If zero, don't print anything.
|
|
push psw ; If not, push the character,
|
|
push d ; and the counter.
|
|
mvi c,putch ; Print the current character
|
|
mov e,a
|
|
call bdos
|
|
lxi d,separator ; Then print ': '
|
|
call outs
|
|
;; Then convert the counter to ASCII
|
|
pop d ; Retrieve the counter
|
|
lxi h,numend ; Get pointer to end of digit string
|
|
push h ; And put it on the stack
|
|
dgtloop: xchg ; Put counter in HL (16-bit accumulator)
|
|
lxi b,-10 ; Dividend is 10
|
|
mov d,b ; Start quotient at -1 (we'll loop once
|
|
mov e,b ; too many, this corrects for it)
|
|
divloop: inx d ; Increment the quotient,
|
|
dad b ; subtract 10 from the dividend,
|
|
jc divloop ; and keep doing it until it goes negative
|
|
lxi b,10+'0' ; Add 10 back to get the remainder,
|
|
dad b ; plus '0' to make it ASCII.
|
|
mov a,l
|
|
pop h ; Retrieve digit pointer
|
|
dcx h ; Decrement it (to point at current digit)
|
|
mov m,a ; Store the digit
|
|
push h ; And store the new pointer
|
|
mov a,d ; Check if the quotient is now zero
|
|
ora e
|
|
jnz dgtloop ; If not, do the next digit.
|
|
pop d ; Set DE to point at the first digit
|
|
call outs ; And output it as a string.
|
|
pop psw ; Restore the character
|
|
next: inr a ; Increment it
|
|
cpi 127 ; Did we just do the last character?
|
|
jnz char ; If not, go do the next character.
|
|
ret ; If so, we're done.
|
|
;; Print the error message
|
|
error: lxi d,errmsg
|
|
;; Print string
|
|
outs: mvi c,puts
|
|
jmp bdos
|
|
;; Strings
|
|
errmsg: db '?$' ; "Error message" (if file error)
|
|
separator: db ': $' ; Goes in between character and number
|
|
number: db '00000' ; Space to keep ASCII representation of
|
|
numend: db 13,10,'$' ; a 16-bit number, plus newline.
|