; Assessment, Excersise 3
;
; Program to count the frequenct of occurance
; of each character in a file
;
; By Andy Bennett (andyjpb@ashurst.eu.org), 2002

                AREA    charFreq, CODE, READONLY                ; declare code area
SWI_Exit        EQU     0x11    ; finish program
SWI_Open        EQU     0x66    ; open a file or device
SWI_Close       EQU     0x68    ; close an open file or device
SWI_Read        EQU     0x6a    ; read from an open file or device
SWI_Flen        EQU     0x6c    ; returns the current length of an open file or device
SWI_Write       EQU     0x69    ; write to an open file or device
SWI_WriteC      EQU     0x0     ; write a character to the console
SWI_Write0      EQU     0x2     ; write a null terminated string to the console
readonly        EQU     0       ; open a file or device for reading
writeonly       EQU     4       ; open a file or device for writing
                ENTRY           ; code entry point

START           ADR     r0, INFILE      ; set r0 to point to INFILE
                MOV     r1, #readonly   ; set access mode to file as readonly
                SWI     SWI_Open        ; open the file pointed at by r0
                CMP     r0, #0          ; check for success on opening file
                SWIEQ   SWI_Exit        ; if file could not be opened then exit now
                MOV     r5, r0          ; save the file handle in r5 for later
                SWI     SWI_Flen        ; store the length of the file in r0
                CMP     r0, #-1         ; check that the call was successful
                SWIEQ   SWI_Exit        ; if the call failed then exit now
                MOV     r6, r0          ; move the length of the file into r6
                MOV     r2, #1          ; read the file byte by byte
                MOV     r8, #4          ; store constant in register to keep MUL happy

                ADR     r0, FREQARRAY   ; point r1 at the beginning of FREQARRAY
                MOV     r1, #0          ; initialise loop counter
                MOV     r3, #0          ; prepare to initialise FREQARRAY to 0
INITARRAY       STR     r3, [r0, r1]
                CMP     r1, #112        ; check to see if we are at the end of FREQARRAY
                BGT     COUNT           ; once memory is initialised, begin counting
                ADD     r1, r1, #4      ; move along FREQARRAY by one word
                B       INITARRAY       ; continue looping


COUNT           CMP     r6, #0          ; are we at the end of the file
                BEQ     ENDCOUNT        ; if so, go to the next bit of code
                ADR     r1, FILECONTENTS        ; if not, point r1 at the temp storage space
                MOV     r0, r5          ; retrive the file handle from r5
                SWI     SWI_Read        ; read the byte in
                CMP     r0, #0          ; check that the call was sucessful
                BNE     ENDCOUNT        ; if anything was left unread, then end of file encountered prematurely so wrap up
                LDRB    r4, [r1]        ; load byte into the register

                CMP     r4, #9          ; is it a tab (white space)
                MOVEQ   r3, #104        ; move along FREQARRAY 26 words (26x4) to the "whitespace" word
                BEQ     UPDATEFREQ      ; "subroutine" to update FREQARRAY

                CMP     r4, #10         ; is it a line feed (white space)
                MOVEQ   r3, #26         ; move along FREQARRAY 26 words to the "whitespace" word
                BEQ     UPDATEFREQ      ; "subroutine" to update FREQARRAY

                CMP     r4, #13         ; is it a carriage return (white space)
                MOVEQ   r3, #26         ; move along FREQARRAY 26 words to the "whitespace" word
                BEQ     UPDATEFREQ      ; "subroutine" to update FREQARRAY

                CMP     r4, #32         ; is it a space (white space)
                MOVEQ   r3, #26         ; move along FREQARRAY 26 words to the "whitespace" word
                BEQ     UPDATEFREQ      ; "subroutine" to update FREQARRAY

                                        ; deal with upper case letters
                CMP     r4, #65         ; is it >= 65
                BLT     LITTLECMP
                CMP     r4, #90         ; or <= 90
                SUBLE   r3, r4, #65     ; move along FREQARRAY (r4 - #65) words to the correct letter
                BLE     UPDATEFREQ      ; "subroutine" to update FREQARRAY

                                        ; deal with lower case letters
LITTLECMP       CMP     r4, #97         ; is it >= 97
                BLT     OTHERS
                CMPGE   r4, #122        ; or <= 122
                SUBLE   r3, r4, #97     ; move along FREQARRAY (r4 - #97) words to the correct letter
                BLE     UPDATEFREQ      ; "subroutine" to update FREQARRAY

                                        ; deal with other characters
OTHERS          MOV     r3, #27         ; move along FREQARRAY 27 words to the "others" word

UPDATEFREQ      ADR     r4, FREQARRAY   ; point r4 at the start of FREQARRAY
                MUL     r7, r3, r8      ; multiply offset in words by 4 so that value is in bytes
                ADD     r4, r4, r7      ; move along FREQARRAY to the correct word
                LDR     r3, [r4]        ; load the correct word into r3
                ADD     r3, r3, #1      ; add one to the count
                STR     r3, [r4]        ; write r3 back to memory again

                SUB     r6, r6, #1      ; move along to the next byte in the file
                B       COUNT           ; resume processing with the next character in the file

ENDCOUNT        MOV     r0, r5          ; move the file handle into r0
                SWI     SWI_Close       ; close the file

                ADR     r1, FREQARRAY   ; place the start of FREQARRAY into r1
                ADR     r2, FILECONTENTS
                MOV     r6, #0          ; initialise counter, r6, to 0

REPORT          CMP     r6, #27         ; check loop counter
                BGT     ENDREPORT       ; if >28 go to ENDREPORT

                MUL     r7, r6, r8      ; multiply offset in words by 4 so that value is in bytes
                LDR     r3, [r1, r7]    ; load the current value into r3

                CMP     r6, #25         ; see if we are still on the alpha characters
                ADDLE   r0, r6, #65     ; if we are,  put the current ascii value into r0
                SWILE   SWI_WriteC      ; if we are, write out the character we are on

                CMP     r6, #26         ; see if we are on "White Spaces"
                ADREQ   r0, TXTWHITE    ; if we are, point r0 at white spaces string
                SWIEQ   SWI_Write0      ; if we are, write out the white spaces string

                CMP     r6, #27         ; see if we are on "Others"
                ADREQ   r0, TXTOTHERS   ; if we are, point r0 at others string
                SWIEQ   SWI_Write0      ; if we are, write out the others string

                MOV     r0, #58         ; then we want a colon, so set it up
                SWI     SWI_WriteC      ; and write it out

                MOV     r0, #32         ; then we want a space, so set it up
                SWI     SWI_WriteC      ; and write it out

                BL      HEXOUT          ; write value to console in hexadecimal

                MOV     r0, #10         ; then we want a carriage return, so set it up
                SWI     SWI_WriteC      ; and write it out

                ADD     r6, r6, #1      ; increment loop counter
                B       REPORT          ; continue round loop with next item


ENDREPORT       SWI     SWI_Exit        ; end program


                                        ; subroutines


                                        ; subroutine to output values in hex to the console
HEXOUT          STMED   r13!, {r0-r8, r14}      ; save working registers on stack
                MOV     r2, #8                  ; r2 has nibble (4-bit digit) count = 8
                MOV     r4, #0                  ; initialise flag
HEXLOOP         MOV     r0, r3, LSR #28         ; get top nibble
                CMP     r0, #9                  ; if nibble <= 9, then
                ADDLE   r0, r0, #"0"            ; convert to ASCII numeric char
                ADDGT   r0, r0, #"A"-10         ; else convert to ASCII alphabet char
                CMP     r0, #"0"                ; do we have a zero...
                MOVNE   r4, #1                  ; if not, set the flag
                BNE     HEXNOTZERO              ; if not, get the digit written out immediately
                CMP     r4, #0                  ; ... and the flag is not set
                BEQ     HEXLEADZERO             ; do not write out leading zeros
HEXNOTZERO      SWI     SWI_WriteC              ; print character
HEXLEADZERO     MOV     r3, r3, LSL #4          ; shift left 4 bits to get to next nibble
                SUBS    r2, r2, #1              ; decrement nibble count
                BNE     HEXLOOP                 ; if more, do next nibble
                LDMED   r13!, {r0-r8, pc}       ; retrieve working registers from stack
                                                ; ... and return to calling program


INFILE = "turing.txt", 0        ; input filename + null
                ALIGN           ; align variables on 32bit boundries
TXTWHITE = "White Spaces", 0    ; Text for report + null
TXTOTHERS = "Others", 0         ; Text for report + null
FILECONTENTS = "a"              ; one byte to store one character from the file in whilst we process it
                ALIGN           ; align variables on 32bit boundries
FREQARRAY = "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"  ; 28 words for storing the frequency of each character. Init to "0"

                END