        TTL     > <wini>arm.FileSwitch.FSCommon : common routines, heap stuff

; System heap used for: fscb, scb, stream buffers

; RMA used for: transients, copy buffers

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;                   S Y S   h e a p   m a n a g e m e n t
; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; SMustGetArea
; ============
;
; Get a new heap block

; In    r3 = size of area to get (should never be 0 !)
;       fp valid (never 0)

; Out   VC: ok, r2 -> block
;       VS: fail, r2 -> Nowt

SMustGetArea ENTRY

 [ debugheap
 DREG r3,"SMustGetArea "
 ]
        BL      SGetArea
        EXIT    NE                      ; NE -> block allocated (or error VS)

        ADRVC   r14, errorbuffer        ; Ok, it is an error
        STRVC   r14, globalerror        ; fp MUST be valid (will adx if not)
        SETV    VC
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; SGetArea
; ========
;
; Get a new heap block, possibly failing because of lack of space

; In    r3 = size of area to get

; Out   VC, NE: ok, r2 -> block
;       VC, EQ: failed to claim block (no room), r2 -> Nowt; errorbuffer valid
;       VS    : bad fail, r2 -> Nowt

SGetArea ENTRY "r0, r1, r3"

 [ debugheap
 DREG r3,"SGetArea "
 ]
        BL      STrySysHeap
        EXIT    NE                      ; r2 -> block (or VS fail)
        EXIT    VS                      ; VS -> bad fail

heap_magic         * 0
heap_freelist      * 4
heap_highwatermark * 8
heap_end           * 12

        LDR     r1, =SysHeapStart + heap_highwatermark
        LDMIA   r1, {r1, r14}
        SUB     r1, r14, r1             ; Amount left at end of heap

        SUB     r1, r3, r1              ; Amount to grow heap by
        ADD     r1, r1, #8              ; Plus enough for housekeeping
        MOV     r0, #0                  ; System heap id
 [ debugheap
 DREG r1,"Doing ChangeDynamicArea(SysHeap) r1 = "
 ]
        SWI     XOS_ChangeDynamicArea
        BVC     %FT90
        CMP     r0, r0                  ; VC, EQ -> alloc failed
        EXIT


90      BL      STrySysHeap             ; Try again
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Primitive allocator for use ONLY by SGetArea

; Out   r2 -> allocated core if successful

STrySysHeap ENTRY "r0, r1, r3"

 [ debugheap
 DREG r3,"STrySysHeap ",cc
 ]
        MOV     r0, #HeapReason_Get
        LDR     r1, =SysHeapStart
        SWI     XOS_Heap                ; Corrupts r3 !
 [ debugheap
 BVS %FT00
 DREG r2,"; returns "
00
 ]
        CMPVC   pc, #0                  ; VC, NE -> ok
        EXIT    VC

        ADR     r1, fsw_GetArea         ; Always copy into errorbuffer
        BL      CopyErrorAppendingString

        MOV     r2, #Nowt               ; Give 'Address extinction' if used !

        LDR     r1, errorbuffer
        LDR     r14, =ErrorNumber_HeapFail_Alloc
        TEQ     r14, r1                 ; We permit this alone to be wrong
                                        ; VS -> bad fail

        SUBEQS  r14, r14, r14           ; SSwales does pervy things again!
        STREQ   r14, globalerror        ; VC, EQ !!! -> block not allocated
        EXIT                            ; Again fp MUST be valid (adx if not)


fsw_GetArea
        DCB     ": FileSwitch GetArea", 0
        LTORG

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; SFreeArea
; =========
;
; Free an old heap block in either the system heap or RMA. Accumulate V

; In    r2 -> block to free. If r2 = 0 or Nowt then don't try to free anything

; Out   VC: r2 = Nowt, block freed
;       VS: r2 = Nowt, fail or VSet on entry

SFreeArea ENTRY "r0, r1"

 [ debugheap
 DREG r2,"SFreeArea "
 ]
        CMP     r2, #0                  ; Must cope with 0 for non-existent
        CMPNE   r2, #Nowt               ; And Nowt -> nothing there. VC
        EXITS   EQ                      ; Restore caller V

        LDR     r1, =SysHeapStart       ; SysHeap address > RMA address
        CMP     r1, r2
        BHI     %FT50

        MOV     r0, #HeapReason_Free
        SWI     XOS_Heap

30      ADRVS   r1, %FT80               ; No distinction between heaps anymore
        MOV     r2, #Nowt               ; Ensure we don't use it again anyhow
        EXITS   VC                      ; Restore caller V

        BL      CopyErrorAppendingString
        EXIT


50      MOV     r0, #ModHandReason_Free
        SWI     XOS_Module

        B       %BT30

80
        DCB     ": FileSwitch FreeArea", 0
        ALIGN

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; SNewArea
; ========
;
; Free an old heap block and get a new one

; In    r0 -> address of pointer to block to free
;       r3 = size of block to get

; Out   VC: block freed and new one obtained; pointer to block updated
;       VS: fail

SNewArea ENTRY "r0, r2"

 [ debugheap
 DREG r3,"SNewArea "
 ]
        LDR     r2, [r0]                ; Address of block to free
        BL      SFreeArea
        BLVC    SMustGetArea            ; Get new block only if freed
        MOVVS   r2, #Nowt               ; Nowt pointer if either failed
        STR     r2, [r0]                ; Update block address in any case
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; SNewString
; ==========
;
; Free an old string and copy a new one into some new workspace

; In    r0 -> address of pointer to block
;       r1 -> string (CtrlChar terminated) or 0

; Out   r1 -> Heap block, with string copied into it, or 0
;       pointer to block updated accordingly

SNewString ENTRY "r2, r3"

 [ debugheap
 DSTRING r1,"SNewString ",cc
 DREG r0," var "
 ]
        CMP     r1, #0
        BEQ     %FT50

        BL      strlen                  ; How big is the source name ?
        ADD     r3, r3, #1              ; +1 for terminating 0
        BL      SNewArea
        MOVVC   r2, r1                  ; src^
        LDRVC   r1, [r0]                ; New dest block
        BLVC    strcpy
 [ debugheap
 DREG r1,"SNewString returns "
 ]
        EXIT

50      LDR     r2, [r0]                ; Just free the existing string
        BL      SFreeArea
 [ debugheap
 DREG r1,"SNewString returns "
 ]
        STRVC   r1, [r0]                ; Make this 0 in case we look it up
        EXIT                            ; r1 still 0

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;                L i n k e d   a r e a   m a n a g e m e n t
; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

          ^     0
la_link   #     4
la_domain #     4       ; DomainId when resource allocated
la_hsize  #     0

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; SGetLinkedArea
; ==============
;
; Claim a new heap block and add it to the global list

; In    r0 -> address of pointer to block
;       r3 = size of block to get

; Out   VC: r2 = !r0 -> new block obtained (past the link)
;       VS: fail

SGetLinkedArea ENTRY "r0, r3"

 [ debugheap
 DREG r0,"SGetLinkedArea: var ",cc
 DREG r3,", size "
 ]
        ADD     r3, r3, #la_hsize       ; extra info
        BL      SMustGetArea            ; Get new block

50      LDRVC   r3, LinkedAreas         ; Push old block^ in new block
        STRVC   r3, [r2, #la_link]
        STRVC   r2, LinkedAreas         ; Add new block to head of chain

        MOVVC   r3, #0
        LDRVC   r3, [r3, #DomainId]     ; Remember domain id where allocated
        STRVC   r3, [r2, #la_domain]

        ADDVC   r2, r2, #la_hsize       ; Caller gets this r2
        STRVC   r2, [r0]                ; Update block^ in local frame
 [ debugheap
 EXIT VS
 DREG r2,"SGetLinkedArea returns ",cc
 DREG r0," to var "
 ]
        EXIT

; .............................................................................

SGetLinkedTransientArea ALTENTRY

 [ debugheap
 DREG r3,"SGetLinkedTransientArea: size "
 ]
        ADD     r3, r3, #la_hsize       ; extra info
        BL      SGetRMA
        EXIT    EQ                      ; EQ -> failed to get block (or VS err)

        ADRVC   r0, TransientBlock
        B       %BT50

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; SGetLinkedString
; ================
;
; Claim a new heap block for string, add it to the global list
; and stuff the string in too

; In    r0 -> address of pointer to block
;       r1 -> string to copy; CtrlChar terminator

; Out   VC: r1 -> new block obtained
;       VS: fail

SGetLinkedString ENTRY "r0, r2-r4"

        MOV     r4, #space-1

05      BL      strlenTS                ; ep for below
        ADD     r3, r3, #1              ; +1 for terminator

 [ debugheap
 DREG r0,"SGetLinkedString: var ",cc
 DREG r3,", size ",cc
 DSTRING r1,", string "
 ]
        BL      SGetLinkedArea          ; r2 := new block^, r0 = caller's var^
        EXIT    VS

        Swap    r1, r2                  ; Copy string into new block,
        BL      strcpyTS                ; exchanging caller's^ to the copy
 [ debugheap
 DREG r1,"SGetLinkedString returns "
 ]
        EXIT

; .............................................................................

SGetLinkedString_excludingspaces ALTENTRY

        MOV     r4, #space
        B       %BT05

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; SCopySpecialField
; =================
;
; Claim a new heap block for string, add it to the global list
; and stuff the string in too

; In    specialptr -> special to set
;       speciallen = length of the above (may be 0)

; Out   VC: new block obtained
;       VS: fail

SCopySpecialField ENTRY "r0-r3"

        LDR     r3, speciallength
 [ debugheap
 DREG r3,"SCopySpecialField: length "
 ]
        ADD     r3, r3, #1              ; +1 for terminator
        MOV     r1, r3                  ; Remembering length to copy for below
        ADR     r0, SpecialField        ; Update block^ in local frame
        BL      SGetLinkedArea          ; r2 := new block^
        EXIT    VS

 [ debugheap
 DREG r2,"SCopySpecialField got "
 ]
        LDR     r3, specialptr          ; Copy string into new block

50      SUBS    r1, r1, #1              ; Remember, we did +1 for terminator...
        LDRNEB  r14, [r3], #1           ; r3 would have been 1 for null string
        MOVEQ   r14, #0                 ; Terminate dest string
        STRB    r14, [r2], #1
        BNE     %BT50

 [ debugheap
 LDR r1, SpecialField
 DSTRING r1,"Copied special field "
 ]
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; SFreeLinkedArea
; ===============
;
; Free the given heap block; remove from global list. Accumulates V

; In the normal course of alloc/dealloc we add to and free from the head of
; the chain, so this new method doesn't incur any nasty overhead really, guv.

; As system heap block addresses are unique throughout the machine, there is
; no need to check domain id for explicit single deallocate.

; In    r0 -> address of pointer to block

; Out   VC: block freed
;       VS: failed to free block, or V set initially

SFreeLinkedArea ENTRY "r0, r2"

        LDR     r2, [r0]                ; Get this block^
        SUB     r2, r2, #la_hsize       ; Get real memory^
 [ debugheap
 DREG r0,"SFreeLinkedArea: var "
 DREG r2,"Block to free is "
 ]
        ADR     r0, LinkedAreas - la_link

10      LDR     r14, [r0, #la_link]     ; Is this a pointer to the block we
                                        ; want to free ?
 [ debugheap
 DREG r14, "Trying against block at "
 ]
 [ paranoid
 CMP r14, #Nowt
 BNE %FT01
 ADR r0, %FT90
 BL CopyError
 EXIT
90
 DCD 0
 DCB "Linked area underflow !", 0
 ALIGN
01
 ]
        CMP     r14, #Nowt              ; End of list without finding it ?
        BEQ     %FT50                   ; [really bad stuff]

        CMP     r14, r2
        MOVNE   r0, r14
        BNE     %BT10                   ; Loop till we find it

        LDR     r14, [r2, #la_link]     ; Store block pointer in previous block
        STR     r14, [r0, #la_link]

50      BL      SFreeArea               ; Free this block

        EXITS   VC                      ; Restore caller V
        EXIT                            ; VSet

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; DelinkLinkedArea
; ================
;
; Unlink the given heap block from global list. Preserves V

; In    r2 -> block

DelinkLinkedArea ENTRY "r0, r2"

        SUB     r2, r2, #la_hsize       ; Get real memory^
 [ debugheap
 DREG r2,"DelinkLinkedArea: "
 ]
        ADR     r0, LinkedAreas - la_link

10      LDR     r14, [r0, #la_link]     ; Is this a pointer to the block we
                                        ; want to delink ?
 [ debugheap
 DREG r14, "Trying against block at "
 ]
 [ paranoid
 CMP r14, #Nowt
 BNE %FT01
 ADR r0, %FT90
 BL CopyError
 EXIT
90
 DCD 0
 DCB "Linked area underflow !", 0
 ALIGN
01
 ]
        CMP     r14, #Nowt              ; End of list without finding it ?
        EXITS   EQ                      ; [really bad stuff]

        CMP     r14, r2
        MOVNE   r0, r14
        BNE     %BT10                   ; Loop till we find it

        LDR     r14, [r2, #la_link]     ; Store block pointer in previous block
        STR     r14, [r0, #la_link]
        EXITS

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; SFreeAllLinkedAreas
; ===================
;
; Free all the linked heap blocks in this domain

; Out   VC: blocks freed
;       VS: failed to free all blocks

SFreeAllLinkedAreas ENTRY "r0, r2, r3"

        ADR     r0, LinkedAreas - la_link
        MOV     r3, #0                  ; Free blocks allocated in this domain
        LDR     r3, [r3, #DomainId]

10      LDR     r2, [r0, #la_link]
        CMP     r2, #Nowt               ; VClear
        BEQ     %FA90

        LDR     r14, [r2, #la_domain]   ; Is this in the right domain?
        TEQ     r14, r3
        MOVNE   r0, r2
        BNE     %BT10                   ; [nope, try next block]

        LDR     r14, [r2, #la_link]     ; Store block pointer in previous block
        STR     r14, [r0, #la_link]

50      BL      SFreeArea               ; Free this block
        BVC     %BT10                   ; Note that r0 is still the same and
                                        ; has been made good with new link etc.

90      EXITS   VC                      ; Restore caller V
        EXIT                            ; VSet

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; SFreeAllLinkedAreasEverywhere
; =============================
;
; Free all the linked heap blocks in all domains. Only done at reset

; Out   VC: blocks freed
;       VS: failed to free all blocks

SFreeAllLinkedAreasEverywhere ENTRY "r0, r2"

 [ debugheap
 DLINE "SFreeAllLinkedAreasEverywhere"
 ]

10      LDR     r2, LinkedAreas         ; Get this block^
        CMP     r2, #Nowt               ; End of list reached ?
        EXIT    EQ                      ; VClear

        LDR     r14, [r2, #la_link]     ; Get next block^
        STR     r14, LinkedAreas        ; Remove this block from list

        BL      SFreeArea               ; Free this block
        B       %BT10

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; SFreePassedFilename
; ===================

; In    PassedFilename to be thrown away

; Out   V = V(in) OR V from freeing string

SFreePassedFilename ENTRY "r0"

 [ debugheap
 DLINE "Discarding PassedFilename"
 ]
        ADR     r0, PassedFilename

10      BL      SFreeLinkedArea
        EXITS   VC                      ; Restore caller V
        EXIT                            ; VSet

; .............................................................................
; In    CommandLine to be thrown away

SFreeCommandLine ALTENTRY

 [ debugheap
 DLINE "Discarding CommandLine"
 ]
        ADR     r0, CommandLine
        B       %BT10

; .............................................................................
; In    FullFilename to be thrown away

SFreeFullFilename ALTENTRY

 [ debugheap
 DLINE "Discarding FullFilename"
 ]
        ADR     r0, FullFilename
        B       %BT10

; .............................................................................
; In    PathString to be thrown away

SFreePathString ALTENTRY

 [ debugheap
 DLINE "Discarding PathString"
 ]
        LDR     r14, PathStringOffset   ; If path part of PassedFilename
        CMP     r14, #-1                ; then we can't free it
        EXITS   NE                      ; >>>a186<<< preserve caller's V

        ADR     r0, PathString
        B       %BT10

; .............................................................................
; In    SpecialField to be thrown away

SFreeSpecialField ALTENTRY

 [ debugheap
 DLINE "Discarding SpecialField"
 ]
        ADR     r0, SpecialField
        B       %BT10

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; SFreePassedFilenameAndSpecial
; =============================

; Use this only for operations that have done TransNameSetFSAndPolice

; In    PassedFilename and SpecialField to be thrown away

; Out   V = V(in) OR V from freeing string(s)

SFreePassedFilenameAndSpecial ENTRY

        BL      SFreeSpecialField       ; Get rid of this first as we allocate

        BL      SFreePassedFilename     ; after PassedFilename mostly
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; SFreePassedFilename2AndSpecial2
; ===============================

; Used when we've parsed two input filenames

; In    PassedFilename2 and SpecialField2 to be thrown away

; Out   V = V(in) OR V from freeing string(s)

SFreePassedFilename2AndSpecial2 ENTRY

 [ debugheap
 DLINE "Discarding SpecialField2 and PassedFilename2"
 ]
        ADR     r0, SpecialField2
        BL      SFreeLinkedArea

        ADR     r0, PassedFilename2
        BL      SFreeLinkedArea
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

SFreePassedFilenameAndSpecial_delinked ENTRY "r2"

 [ debugheap
 DLINE "Discarding delinked SpecialField and PassedFilename"
 ]
        LDR     r2, PassedFilename
        SUB     r2, r2, #la_hsize       ; Get real memory^
        BL      SFreeArea

        LDR     r2, SpecialField

50      SUB     r2, r2, #la_hsize       ; Get real memory^
        BL      SFreeArea
        EXITS   VC
        EXIT

; .............................................................................

SFreePassedFilename2AndSpecial2_delinked ALTENTRY

 [ debugheap
 DLINE "Discarding delinked SpecialField2 and PassedFilename2"
 ]
        LDR     r2, PassedFilename2
        SUB     r2, r2, #la_hsize       ; Get real memory^
        BL      SFreeArea

        LDR     r2, SpecialField2
        B       %BT50

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;                   R M A   h e a p   m a n a g e m e n t
; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; SGetRMA
; =======
;
; Get a new RMA block, possibly failing because of lack of space

; In    r3 = size of area to get

; Out   VC, NE: ok, r2 -> block
;       VC, EQ: failed to claim block (no room), r2 -> Nowt
;       VS    : bad fail, r2 -> Nowt

SGetRMA ENTRY "r0-r1, r3"

 [ debugheap
 DREG r3,"SGetRMA ",cc
 ]
        MOV     r0, #ModHandReason_Claim
        SWI     XOS_Module
 [ debugheap
 BVS %FT01
 DREG r2,": returns "
01
 ]
        CMPVC   pc, #0                  ; VC, NE -> ok
        EXIT    VC

        MOV     r2, #Nowt               ; Give 'Address extinction' if used !
        LDR     r1, [r0]
        LDR     r14, =ErrorNumber_MHNoRoom ; We permit this alone to be wrong
        CMP     r14, r1                 ; VC, EQ if so
        ADRNE   r1, %FT90
        BLNE    CopyErrorAppendingString ; VSet
 [ debugheap
 BVS %FT01
 DLINE ": claim failed"
01
 ]
        EXIT
90
        DCB     ": FileSwitch GetRMA", 0
        ALIGN

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;                      C o m m o n   r o u t i n e s
; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; strlen, strlenTS
; ================
;
; Find the length of a string (exclusive of terminator, so can't HeapGet (0))

; In    r1 -> CtrlChar(/r4) terminated string

; Out   r3 = number of chars (can be used as size for Heap)

strlen ENTRY "r0, r4"

        MOV     r4, #space-1

05      MOV     r3, #0                  ; ep for below

10      LDRB    r0, [r1, r3]
        CMP     r0, #delete             ; Order, you git! EQ -> ~HI
        CMPNE   r0, r4                  ; Any char <= r4 is a terminator
        ADDHI   r3, r3, #1
        BHI     %BT10
        EXITS

; .............................................................................

strlenTS ALTENTRY

        B       %BT05

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; strcat, strcatTS
; ================
;
; Concatenate two strings

; In    r1, r2 -> CtrlChar(/r4) terminated strings

; Out   new string in r1 = "r1" :CC: "r2" :CC: 0

strcat ENTRY "r1, r2, r4"

        MOV     r4, #space-1

05      LDRB    r14, [r1], #1           ; Find where to stick the appendage
        CMP     r14, #delete            ; Order, you git! EQ -> ~HI
        CMPNE   r14, r4                 ; Any char <= r4 is a terminator
        BHI     %BT05
        SUB     r1, r1, #1              ; Point back to the term char

10      LDRB    r14, [r2], #1           ; Copy from *r2++
        CMP     r14, #delete            ; Order, you git! EQ -> ~HI
        CMPNE   r14, r4                 ; Any char <= r4 is a terminator
        MOVLS   r14, #0                 ; Terminate dst with 0
        STRB    r14, [r1], #1           ; Copy to *r1++
        BHI     %BT10

        EXITS

; ............................................................................

strcatTS ALTENTRY

        B       %BT05

; .............................................................................
;
; strcpy, strcpyTS
; ================
;
; Copy a string and terminate with 0

; In    r1 -> dest area
;       r2 -> CtrlChar(/r4) terminated src string

strcpy ALTENTRY

        MOV     r4, #space-1
        B       %BT10

; .............................................................................

strcpyTS ALTENTRY ; Match with strcatTS !!!

        B       %BT10

 [ False ; No longer used
; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; strchr
; ======
;
; Finds the first occurence of a character in a string (excl. terminator)

; In    r0  = character
;       r1 -> string, CtrlChar

; Out   EQ: r1 -> character (found)
;       NE: r1 = 0 (not found)

strchr ENTRY

10      LDRB    r14, [r1], #1
        TEQ     r0, r14
        SUBEQ   r1, r1, #1
        EXIT    EQ
        CMP     r14, #delete            ; Order, you git !
        CMPNE   r14, #space-1
        BHI     %BT10

        MOV     r1, #0
        PullEnv
        BICS    pc, lr, #Z_bit          ; NE
 ]

 [ False
; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; strcmp
; ======
;
; Compares two strings (case insensitive)

; In    r1 -> string, CtrlChar
;       r2 -> string, CtrlChar

; Out   EQ/NE as appropriate

strcmp ENTRY "r1-r4"

10      LDRB    r3, [r1], #1
        LDRB    r4, [r2], #1
        LowerCase r3, r14
        CMP     r3, #delete             ; Order, you git !
        CMPNE   r3, #space-1            ; Finished ?
        MOVLS   r3, #0
        LowerCase r4, r14
        CMP     r4, #delete             ; Order, you git !
        CMPNE   r4, #space-1            ; Finished ?
        MOVLS   r4, #0
        CMP     r3, r4                  ; Differ ?
        EXIT    NE

        CMP     r3, #0
        BNE     %BT10
        MOVS    r3, #0                  ; EQ
        EXIT
 ]

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; strncmp
; =======
;
; Compares two strings (case insensitive) with length limit

; In    r1 -> string, CtrlChar
;       r2 -> string, CtrlChar
;       r3 = length

; Out   EQ/NE as appropriate

strncmp ENTRY "r1-r5"

10      CMP     r3, #0
        EXIT    EQ                      ; same up to required length ?
        SUB     r3, r3, #1

        LDRB    r4, [r1], #1
        LowerCase r4, r14
        CMP     r4, #delete             ; Order, you git !
        CMPNE   r4, #space-1            ; Finished ?
        MOVLS   r4, #0

        LDRB    r5, [r2], #1
        LowerCase r5, r14
        CMP     r5, #delete             ; Order, you git !
        CMPNE   r5, #space-1            ; Finished ?
        MOVLS   r5, #0

        CMP     r4, r5                  ; Differ ?
        EXIT    NE

        CMP     r4, #0                  ; Both ended together ?
        BNE     %BT10                   ; [no, more chars to come]
        EXIT                            ; EQ

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r1 -> string

; Out   flags from CMP r0, #space for eol detection

FS_SkipSpaces ROUT

10      LDRB    r0, [r1], #1
        CMP     r0, #space      ; Leave r1 -> ~space
        BEQ     %BT10
        SUB     r1, r1, #1
        MOV     pc, lr          ; r0 = first ~space

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; SReadTime
; =========
;
; Read the RealTime into r2, r3 in a form useful for DateStamping

; In    r2 bottom 12 bits contains file type to put into date. Will be masked

; Out   r2,r3 updated, flags preserved

SReadTime ENTRY "r0-r1, r4", 8 ; Uses local stack for block

        MOV     r4, #&FF000000          ; Create &FFFFFttt
        ORR     r2, r2, r4, ASR #12     ; Fill bits 31-12 with 1's
                                        ; Put type bits in bottom 12 bits
                                        ; Effectively masked by ORRing with 1's
        MOV     r1, sp
        MOV     r0, #3                  ; New OSWord RC - bought from Tim !
        STRB    r0, [r1]
        MOV     r0, #14
        SWI     XOS_Word                ; ReadTime shouldn't give error

        LDRB    r0, [r1, #4]            ; Top byte of date
        ORR     r2, r0, r2, LSL #8      ; &FFFFFttt -> &FFFtttdd
        LDR     r3, [r1]                ; Low word of date
 [ anyfiledebug
 DREG r0,"Time is ",cc
 DREG r3
 ]
        EXITS

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; ExPathString
; ============

; Try looking for an object using a given path string

; In    r1 -> filename
;       r2 -> path string to use

; Out   VS: fail, r0,r1 dead
;       VC: r0 = 0: end of path, object not found
;              = 1: r1 -> new (stripped) filename to use, object is ok
;                     fsforthisop set up, name is good

; Filename used in search ALWAYS copied into FullFilename

ExPathString ENTRY "r2, r3"

        LDR     r14, PathStringOffset   ; Override path set ?
        CMP     r14, #-1
        ADDNE   r14, r1, r14
        STRNE   r14, PathString
        BNE     %FT60

        MOV     r3, r1                  ; Save filename^
        ADR     r0, PathString          ; Copy into local string
        MOV     r1, r2
        BL      SGetLinkedString        ; May contain spaces
        EXIT    VS

60      MOV     r14, #0                 ; Stop when dir found
        STRB    r14, lookforplingrun

        MOV     r1, r3                  ; Restore filename^
        BL      ExPathCommon

        BL      SFreePathString         ; Deallocate after use
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; ExPathVariable
; ==============
;
; Try looking for an object using a given path variable

; In    r1 -> filename
;       r2 -> path variable name
       
; Out   VS: fail
;       VC: r0  = 0: end of path, object not found
;              >= 1: r1 -> new (stripped) filename to use, object is ok
;                      fsforthisop set up, name is good

; Filename used in search ALWAYS copied into FullFilename

ExPathVariable ENTRY "r2, r3"

        LDR     r14, PathStringOffset   ; Override path set ?
        CMP     r14, #-1
        ADDNE   r14, r1, r14
        STRNE   r14, PathString
        BNE     %FT60

        ADR     r3, anull               ; Default null path to use if unset
        MOV     r0, r2                  ; r0 -> path variable name
        BL      SReadPathString
        EXIT    VS

60      MOV     r14, #0                 ; Stop when dir found
        STRB    r14, lookforplingrun

        BL      ExPathCommon

        BL      SFreePathString         ; Deallocate after use
        EXIT


anull   DCB     0
        ALIGN

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; ExPathWithDefault
; =================
;
; Try looking for a file/object using a path variable or a given default if the
; path variable is unset

; In    r0 -> path variable name
;       r1 -> filename
;       r2 -> default path to use

; Out   VS: fail
;       VC: r0  = 0: end of supplied / default path
;                      object not found, or was (directory & skip)
;              >= 1: r1 -> new (stripped) filename to use, object is ok
;                      fsforthisop set up, name is good

; Filename used in search ALWAYS copied into FullFilename

ExPathWithDefault ENTRY "r2, r3"

        MOV     r14, #0                 ; Stop when dir found

50      STRB    r14, lookforplingrun

        LDR     r14, PathStringOffset   ; Override path set ?
        CMP     r14, #-1
        ADDNE   r14, r1, r14
        STRNE   r14, PathString
        BNE     %FT60

        MOV     r3, r2                  ; Default path to use
        BL      SReadPathString
        EXIT    VS

60      BL      ExPathCommon

        BL      SFreePathString         ; Deallocate after use
        EXIT

; .............................................................................

ExPathWithDefaultNoDir ALTENTRY

        MOV     r14, #1                 ; Look for !Run in directories
        B       %BT50

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; ExPathCommon
; ============
;
; Try looking for an object along the given path
; Give up at first error or at end of path

; If filename already has a fs prefix then don't apply path

; If we hit a directory, then action depends on lookforplingrun

; In    r1 -> filename (space or CtrlChar terminator)
;       PathString set up

; Out   VS: fail, no FullFilename allocated
;       VC: r0  = 0: end of path, file not found, or was (directory & skip)
;              >= 1: r1 -> new (stripped) filename to use, object is ok
;                      fsforthisop set up, name is good
;       FullFilename and SpecialField set up if object exists and no error

ExPathCommon ENTRY "r1-r5"

 [ debugpath
 DSTRING r1,"ExPath for file ",cc
 Push r1
 LDR r1,PathString
 DSTRING r1," on path "
 Pull r1
 ]
        BL      SetFSForOp      ; Is there a fs prefix ?
        BLVC    PoliceNameWithError ; Must be ok BEFORE we try applying path
        EXIT    VS              ; Don't allocate special yet !

        LDRB    r5, hadfsprefix         ; Already had filing system ?
        TEQ     r5, #0
        BNE     %FT50                   ; Just use what we already have

        MOV     r4, r1          ; Keep track of stripped part of PassedFilename
        BL      InitPathItem    ; Copy and initialise. EQ if null
        BEQ     %FT50           ; If path is null, use what we've got already

        ADD     r0, r3, #(:LEN:".!Run")+1 ; r3=maxprefixlen; +possible postfix
        BL      strlen          ; How big is the filename ?
        ADD     r3, r3, r0      ; Get space in FullFilename for max path + name
        ADR     r0, FullFilename
        BL      SGetLinkedArea
        EXIT    VS

        LDRB    r5, pathnailed          ; Already absolute ?

10      MOV     r1, r4                  ; Restore filename^
        BL      ExistPathItem           ; Try looking with current path prefix
        BVS     %FT99                   ; Deallocate FullFilename
        CMP     r0, #-1                 ; Silly prefix to apply ? FujjVal = -2
        BMI     %BT10                   ; Loop, no special was allocated

        MOVEQ   r0, #object_nothing     ; EOP that existed (map -1 -> 0). NSA
        BEQ     %FT99                   ; Deallocate FullFilename

        CMP     r0, #object_directory   ; If a dir, treat differently
        BLEQ    LookForPlingRun
        BVS     %FT95                   ; Deallocate FullFilename+SpecialField

15      CMP     r0, #object_nothing
        STRNE   r1, [sp]                ; Return name to use (ptr into FullFN)
        EXIT    NE

        BL      SFreeSpecialField       ; Loop after freeing special
        BVC     %BT10

        EXIT



50 ; Nailed object, so copy filename with fs prefix (and special)
   ; into FullFilename (keep our promise to do so)

        LDR     r1, [sp]
        BL      strlen
        ADD     r3, r3, #(:LEN:".!Run")+1
        ADR     r0, FullFilename
        BL      SGetLinkedArea
        EXIT    VS

        LDR     r2, [sp]
        LDR     r1, FullFilename
        BL      strcpy

        BL      SetFSForOp              ; Set fs and police wrt. new string
        BLVC    PoliceName              ; No error poss, just set up stripped
        BLVC    SCopySpecialField       ; Setup SpecialField
        BVS     %FT99                   ; Deallocate FullFilename

        MOVVC   r0, #fsfile_ReadInfo
        BLVC    CallFSFile              ; fsforthisop set up, name ok
        BVS     %FT95                   ; Deallocate FullFilename+SpecialField

        CMP     r0, #object_directory   ; If a dir, treat differently 
        BLEQ    LookForPlingRun
        BVS     %FT95                   ; Deallocate FullFilename+SpecialField

80      CMP     r0, #object_nothing
        STRNE   r1, [sp]                ; Return stripped^ to the caller
        EXIT    NE                      ; r0 = modified object type

                                        ; If r0 = 0 must have deallocated
                                        ; SpecialField and FullFilename

95      BL      SFreeSpecialField

99      BL      SFreeFullFilename
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r0 = object type
;       r1 -> stripped part of FullFilename to stick appendage on

; Out   r0 = object type (modified)

LookForPlingRun ENTRY "r1, r2"

        LDRB    r14, lookforplingrun    ; If not looking, just return IsADir
        TEQ     r14, #0
        EXIT    EQ

        ADR     r2, DotPlingRun         ; Does !Run exist ? Append to stripped
        BL      strcat                  ; name
        MOV     r0, #fsfile_ReadInfo
        BL      CallFSFile
        EXIT    VS

        TEQ     r0, #object_file        ; If not a file, return NotFound
        MOVNE   r0, #object_nothing
        EXIT

DotPlingRun
        DCB     ".!Run", 0
        ALIGN

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; InitPathItem
; ============
;
; Initialise path to reading elements from

; In    PathString set up

; Out   NE: r3 = maxprefixlen; usable path
;       EQ: path is null

InitPathItem ENTRY "r0-r2, r4"

        LDR     r1, PathString  ; Setup first^
        STR     r1, pathelementptr
 [ debugpath
 DSTRING r1,"InitPathItem: path "
 ]

        MOV     r14, #1         ; Path is good (EQ -> done)
        STRB    r14, pathnotended

; Count the maximum prefix size

        MOV     r3, #0          ; maxprefixlen
20      MOV     r2, #0          ; currprefixlen

50      LDRB    r0, [r1], #1    ; Look at char in pathname
        TEQ     r0, #","        ; Delimiter reached yet ?
        TEQNE   r0, #0          ; Or end of path variable ?
        ADDNE   r2, r2, #1      ; currprefixlen++
        BNE     %BT50
        CMP     r2, r3          ; new maxprefixlen if greater
        MOVHI   r3, r2          ; r3 := length of this prefix
        TEQ     r0, #0          ; Finished yet ?
        BNE     %BT20           ; reset currprefixlen

; r3 := n chars from prefix + 1 for eventual '.' + 1 for terminating 0

        CMP     r3, #0          ; Path full of identity op ? VClear
        ADDEQ   r3, r3, #1      ; +1 for terminating 0
        ADDNE   r3, r3, #2      ; + 1 for eventual '.' +1 for terminating 0 
        EXIT                    ; CMP result passed to caller

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; ExistPathItem
; =============
;
; Get the next path element, append filename and see it it exists
; Never glues on inappropriate prefixes, 'cos these would go bang !
; eg. net:$. with %.fred -> not found
;     -bum-  with fred   -> error
;     $.%    with fred   -> error

; In    r1 -> filename to be appended to path item, space or CtrlChar term
;       r5 = pathnailed flag from original filename

; Out   VS: Had an error from FSFile
;       VC: r0 = -1: end of path
;              = -2: filename absolute and prefix has file components
;              =  0: new file not found
;             >=  1: object (of type r0 - may be dir) found
;                      r1 -> new file name
;                      fsforthisop set up, name is good

; Leaves new filename in FullFilename, SpecialField set up if r0 >= 0

ExistPathItem ENTRY "r2-r5"

 [ debugpath
 DSTRING r1,"ExistPathItem "
 ]
        BL      PathItem        ; Get a path element into FullFilename
        EXIT    VS              ; r0 -> path after fsforthisop set ok
        MOVCS   r0, #-1         ; Path exhausted ?
 [ debugpath
 BCS %FT95
 ]
        EXIT    CS              ; VClear always

        CMP     r5, #0          ; If original name absolute, then prefix must
        BEQ     %FT10           ; have been null, or just a fs prefix
        LDRB    r14, [r0]
 [ debugpath
 DREG r14,"Name was absolute, prefix termch "
 ]
        CMP     r14, #0         ; A non-null prefix now would be silly
        MOVNE   r0, #-2         ; so just say 'Not found' to caller. Total fujj
 [ debugpath
 BNE %FT95
 ]
        EXIT    NE              ; VClear

10      MOV     r2, r1          ; Filename to try on this path
        MOV     r1, r0          ; Path so far, after stripping fsprefix
        MOV     r4, #space      ; CtrlChar + spaces terminate name >>>a186<<<
        BL      strcatTS

        BL      PoliceNameWithError     ; Check final name. fsforthisop is set
        BLVC    SCopySpecialField       ; up already, so we can't change it
        EXIT    VS

        LDR     r0, fsforthisop         ; No selected filing system ?
        CMP     r0, #Nowt
        MOVEQ   r0, #object_nothing     ; Pretend it doesn't exist then. VClear

        MOVNE   r0, #fsfile_ReadInfo    ; Get file info
        BLNE    CallFSFile
        BLVS    SFreeSpecialField

 [ debugpath
95
 DREG r0,"ExistPathItem returns type: "
 ]
        CMPVC   r0, #0          ; Does the object exist ? VClear
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; PathItem
; ========
;
; Fill FullFilename with the next item from the path
;   a null path element yields a null prefix
;   a filing system prefix yields a null prefix after setting fsforthisop
;   a normal path element yields a textual prefix, which may have a dot

; In    pathelementptr -> next path element to read, -> 0 at end

; Out   VC:
;         CC: path element in FullFilename, pathelem^ ready for next read
;              fsforthisop set, r0 -> prefix after fsprefix stripping
;         CS: path exhausted, FullFilename not updated, pathelem^ -> 0 of path
;       VS: fail
;       Rest of universe ok

PathItem ENTRY "r0-r3, r6-r7"

        LDRB    r14, pathnotended       ; Any more to get ?
        CMP     r14, #0                 ; 0 -> no, eop
 [ debugpath
 BNE %FT01
 WRLN "Path exhausted"
 CMP r0,r0 ; EQ,CS,VC again
01
 ]
        EXIT    EQ                      ; No - CSet, VClear exit

        LDR     r2, pathelementptr
 [ debugpath
 DSTRING r2,"Getting a PathItem from "
 ]
10      LDRB    r0, [r2], #1    ; Strip spaces to point to this path element
        TEQ     r0, #space
        BEQ     %BT10
        SUB     r2, r2, #1

        LDR     r1, FullFilename
        MOV     r14, r1         ; Remember start point
20      LDRB    r0, [r2]        ; Loop copying until 0, ' ' or ',' in path
        MOV     r3, r0          ; Remember char
        CMP     r0, #0
        STREQB  r0, pathnotended ; Path very definitely finished !
        ADDNE   r2, r2, #1      ; Advance ptr if not 0
        CMPNE   r0, #space
        CMPNE   r0, #","
        MOVEQ   r0, #0          ; Terminate prefix
        STRB    r0, [r1], #1
        BNE     %BT20

        MOV     r1, r14         ; Set fs from prefix. Name unpoliced
        BL      SetFSForOp
        EXIT    VS
        STR     r1, [sp]        ; Tell caller where we got to in the name

30      LDRB    r0, [r2], #1    ; Skip trailing spaces on end of element
        TEQ     r0, #space      ; Terminator of element just read has been
        BEQ     %BT30           ; skipped already
        CMP     r0, #","        ; Is it a comma ?
        CMPEQ   r3, #space      ; Was it a space that stopped us copying ?
        SUBNE   r2, r2, #1      ; Again, leaves ptr -> 0 if it was there
                                ; or ptr -> ',' if already had ',' term

; Allows us to do 'xxx , yyy' & 'xxx ,, yyy' properly

        STR     r2, pathelementptr
 [ debugpath
 LDR r1, FullFilename
 DSTRING r1,"Gives file prefix ",cc
 DSTRING r2, ", leaves path "
 ]
        CLC
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; SReadVariable
; =============
;
; Read a variable into a buffer with a default string if unset

; In    r0 -> variable name
;       r1 -> buffer
;       r2 =  buffer length
;       r3 -> default string to set in buffer if variable doesn't exist

; Out   VS: failed because buffer overflowed, but variable exists !
;       VC, NE: expanded variable in pathname
;       VC, EQ: variable is not set, null string set in buffer

SReadVariable ENTRY "r0-r4"

 [ debugpath
 DSTRING r0,"Reading variable ",cc
 DSTRING r3,", default value ",cc
 DREG r1," to buffer ",cc
 DREG r2,", length "
 ]
        MOV     r3, #0          ; We know what the name is
        MOV     r4, #VarType_Expanded ; Expand macros in variable
        SWI     XOS_ReadVarVal
        MOV     r3, #0          ; Terminate string in buffer
        MOVVS   r2, #0          ; Expansion forced to null on all errors
        STRB    r3, [r1, r2]
 [ debugpath
 BVS %FT42
 DSTRING r1,"Variable expands to "
42
 ]
        CMPVC   pc, #0          ; NE, VClear
        EXIT    VC


; See if variable was unset

50      LDR     r14, [r0]
        CMP     r14, #ErrorNumber_VarCantFind
        BLNE    CopyError       ; Otherwise tell us about it
        EXIT    VS

        LDR     r2, [sp, #4*3]  ; Copy default string into buffer
        BL      strcpy
 [ debugpath
 WRLN "Variable not found, default used"
 ]
        EXIT                    ; VClear from CMP

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r0 -> path variable name
;       r3 -> default string

; Out   VC: r1 -> path string in stack
;           r2 = sp adjust needed after use
;       VS: fail, r2 = sp adjust needed

SReadVariableToBuffer ROUT

        MOV     r2, sp, LSR #20         ; Find stack base
        SUB     r2, sp, r2, LSL #20     ; Amount of stack left
 [ debugpath
 DREG r2, "amount of stack = "
 ]
        SUB     r2, r2, #2*1024
        CMP     r2, #StaticName_length  ; Never less than old compatible
        MOVMI   r2, #StaticName_length  ; length object

 [ debugpath
 DREG r2, "amount of stack used = "
 ]
        SUB     sp, sp, r2
        MOV     r1, sp                  ; Point to buffer before saving lr

        ENTRY
        BL      SReadVariable
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r0 -> path variable name
;       r3 -> default string

SReadPathString ENTRY "r0-r2"

        BL      SReadVariableToBuffer

        ADRVC   r0, PathString
        BLVC    SGetLinkedString        ; May contain spaces

        ADD     sp, sp, r2              ; Restore sp after SReadVariable
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; ValidateR2R5_ReadFromCore
; =========================
;
; Check whether we can read from memory

; In    r2 -> start of block
;       r5 -> end of block

ValidateR2R5_ReadFromCore ENTRY "r0, r1"

 [ debugosfile
 DREG r2, "Validate for read from core: ",cc
 DREG r5
 ]
        CMP     r5, r2                  ; Prevent 5678 .. 1234 saves etc.
        BMI     %FA40                   ; as Sam doesn't check ordering

        MOV     r0, r2
        MOV     r1, r5
        SWI     XOS_ValidateAddress
        EXIT    CC

        CMP     r2, #&03800000          ; Is block within ROM area ?
        RSBCSS  r14, r5, #&04000000     ; Rare occurence, so don't put
        EXIT    CS                      ; before ValidateAddress call

40      addr    r0, ErrorBlock_CoreNotReadable
        B       %FT95

; .............................................................................
;
; ValidateR2R5_WriteToCore
; ========================
;
; Check whether we can write to memory

; In    r2 -> start of block
;       r5 -> end of block

ValidateR2R5_WriteToCore ALTENTRY

 [ debugosfile
 DREG r2, "Validate for write to core: ",cc
 DREG r5
 ]
        CMP     r5, r2                  ; Prevent 5678 .. 1234 loads etc.
        BMI     %FA90                   ; as Sam doesn't check ordering

        MOV     r0, r2
        MOV     r1, r5
        SWI     XOS_ValidateAddress
        EXIT    CC

90      addr    r0, ErrorBlock_CoreNotWriteable

95      BL      CopyError
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        LTORG

        LNK     $fileprefix.FSUtils
