; Copyright 1996 Acorn Computers Ltd
;
; Licensed under the Apache License, Version 2.0 (the "License");
; you may not use this file except in compliance with the License.
; You may obtain a copy of the License at
;
;     http://www.apache.org/licenses/LICENSE-2.0
;
; Unless required by applicable law or agreed to in writing, software
; distributed under the License is distributed on an "AS IS" BASIS,
; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
; See the License for the specific language governing permissions and
; limitations under the License.
;
        SUBT    > Sources.Canonical

; This file contains the routines to canonicalise a path:

; CanonicalisePath
;  ObtainAbsoluteParts
;   GetDiscFromPath
;   GetAbsolute
;  StitchAbsolutePartsTogether
;   CanonicaliseSpecialAndDisc
;   ReduceParents
;  UnWildcardPath
;   SkipToLeafEnd
;   ResolveWildcard
;    ResolveWildcardBySteam


; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; ReduceParents
; =============
;
; Given a complete file path from .$, .& or .%, reduce any ^s in it
; to their true value.
;
; In    r2 = pointer to filepath to reduce, nul terminated
;               The path is assumed kosha - this routine
;               will break if it isn't.
;       fscb
;
; Out   filepath reduced.

ReduceParents Entry "r0-r3"
 [ chopoffdollarfrompaths
        MOV     r1, r2                  ; Start of $ or .$ sequence
        LDRB    r0, [r2], #1
        TEQ     r0, #0
        EXIT    EQ

        ; Check for .$ and chop it
        TEQ     r0, #"."
        LDREQB  r0, [r2], #1
        LDRB    lr, [r2], #1
        TEQ     lr, #0
        BNE     %FT05

        ; Goes $ or .$
        TEQ     r0, #"$"
        LDREQ   lr, [fscb, #fscb_info]
        TSTEQ   lr, #fsinfo_handlesurdetc
        MOVEQ   r0, #0
        STREQB  r0, [r1]
        EXIT

05
        ; Reduce the $ if present and not on a handlesurdetc FS
        ; During the main loop:
        ; r2 points to the start of the path - no reduction
        ; to before this!
        ; r1 points to the destination position
        ; r3 points to the source position
        MOV     r3, r2
        TEQ     r0, #"$"
        LDREQ   lr, [fscb, #fscb_info]
        TSTEQ   lr, #fsinfo_handlesurdetc
        SUBEQ   r2, r2, #2              ; 1 for $ and 1 for .
        MOV     r1, r2
 |
        ; Skip over the .<absolute>., or <absolute>., or <absolute>
        ; and exit if reached end of string
        LDRB    r0, [r2], #1
        TEQ     r0, #0
        EXIT    EQ

        TEQ     r0, #"."
        LDREQB  r0, [r2], #1

        TEQ     r0, #0
        LDRNEB  r0, [r2], #1
        TEQNE   r0, #0
        EXIT    EQ

        MOV     r1, r2
        MOV     r3, r2
 ]

; At this point:
; r3 points at source position
; r1 points at destination position
; r2 points at 'do not reduce parents to before here' position

10
        ; At start of a path element - check it for being a ^
        LDRB    r0, [r3], #1
        TEQ     r0, #"^"
        BNE     %FT40

        ; Identified a ^ - skip back one element and skip forward over the ^.

        ; Don't go backwards to before the start position
        CMP     r1, r2
        BLS     %FT30

        ; Skip to the . before the start of this element
        SUB     r1, r1, #1

        ; Go back until we hit a .
20
        LDRB    r0, [r1, #-1]!
        TEQ     r0, #"."
        BNE     %BT20

        ; Move forwards to start of element
        ADD     r1, r1, #1

30
        ; Now skip the . after the ^
        LDRB    r0, [r3], #1
        TEQ     r0, #0
        BNE     %BT10           ; Not end of path - go for another element
        STRB    r0, [r1, #-1]   ; End of path - terminate new path back one character to
                                ; obliterate the . which is there
        EXIT

40
        ; Path element which isn't a ^ - copy it down
        STRB    r0, [r1], #1    ; Store next byte regardless
        TEQ     r0, #0
        EXIT    EQ              ; End of path - stop
        TEQ     r0, #"."
        BEQ     %BT10           ; End of path element - check next element
        LDRB    r0, [r3], #1
        B       %BT40           ; Next byte of element - loop

; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; CanonicaliseSpecialAndDisc
;
; In    R1 = special field^ to be canonicalised
;       R2 = disc name^ to be canonicalised
;       fscb
;
; Out   R1 = unchanged, RMAlloced special field^ or NULL
;       R2 = unchanged, RMAlloced disc^ or NULL
;

CanonicaliseSpecialAndDisc EntryS "r0,r3,r4,r5,r6,r7,r8", 4
 [ debugcanonical
        DSTRING r1, "Canonicalise #",cc
        DSTRING r2, "::"
 ]

        ; Check whether this call is supported
        LDR     r0, [fscb, #fscb_info]
        TST     r0, #fsinfo_multifsextensions

 [ debugdontworryresourcefs
        EXIT    EQ
 |
        BEQ     %FT50
 ]

        ; Preserve the source values
        MOV     r7, r1
        MOV     r8, r2

        ; Get the string lengths
        MOV     r0, #fsfunc_CanonicaliseSpecialAndDisc
        MOV     r3, #NULL
        MOV     r4, #NULL
 [ debugcanonical
        DSTRING r1, "CallCanonical(",cc
        DSTRING r2, ",",cc
        DREG    r3, ",",cc
        DREG    r4, ",",cc
        DREG    r5, ",",cc
        DREG    r6, ",",cc
        DLINE   ")->",cc
 ]
        BL      CallFSFunc_Given
 [ debugcanonical
        DSTRING r1, "(",cc
        DSTRING r2, ",",cc
        DREG    r3, ",",cc
        DREG    r4, ",",cc
        DREG    r5, ",",cc
        DREG    r6, ",",cc
        DLINE   ")"
 ]
        EXIT    VS

        ; Get some memory for this
        ADD     r3, r3, #1
        BL      SMustGetArea
        EXIT    VS
        MOV     r5, r3
        MOV     r1, r2
        ADD     r3, r4, #1
        BL      SMustGetArea
        BVS     %FT99

        ; Get the canonical values
        MOV     r4, r2          ; Area for new disc -> r4
        MOV     r6, r3          ; size of disc name area -> r6
        MOV     r3, r1          ; Area for special field -> r3

        Push    "r3,r4"
        MOV     r1, r7
        MOV     r2, r8
        MOV     r0, #fsfunc_CanonicaliseSpecialAndDisc
 [ debugcanonical
        DSTRING r1, "CallCanonical(",cc
        DSTRING r2, ",",cc
        DREG    r3, ",",cc
        DREG    r4, ",",cc
        DREG    r5, ",",cc
        DREG    r6, ",",cc
        DLINE   ")->",cc
 ]
        BL      CallFSFunc_Given
 [ debugcanonical
        DSTRING r1, "(",cc
        DSTRING r2, ",",cc
        DREG    r3, ",",cc
        DREG    r4, ",",cc
        DREG    r5, ",",cc
        DREG    r6, ",",cc
        DLINE   ")"
 ]
        Pull    "r3,r4"
        MOVVS   r1, r3
        BVS     %FT98
 [ debugcanonical
        DSTRING r1, "....after canonicalisation #",cc
        DSTRING r2, "::"
 ]

        ; Some filing systems don't bother with the bit of the
        ; FS spec which says they have to copy r3 to r1 and
        ; r4 to r2 on exit from that FS entry point.  NFS, for
        ; example.  This is what causes the System Heap to fill
        ; up with literally thousands of blocks saying "Printer"
        ; whenever you are using an NFS printer (although it
        ; does remember to update R2 so you don't get the disc
        ; name leaking too)  (sbrodie 17/01/1999)
        TEQ     r2, #NULL              ; was disc name returned as zero?
        MOVNE   r2, r4                 ; no - so update R2 based on what it *should* be
        TEQ     r1, #NULL              ; was special field returned as zero?
        MOVNE   r1, r3                 ; no - so update R1 based on what it *should* be

        ; Free the unwanted areas
        Push    "r2"
        TEQ     r2, #NULL
        MOVEQ   r2, r4
        BLEQ    SFreeArea
        TEQ     r1, #NULL
        MOVEQ   r2, r3
        BLEQ    SFreeArea
        Pull    "r2"

        EXIT

50
        ; Stupid filing system can't canonicalise a special and disc!

        ; Nowt we can do with a special field, but we can try to do something
        ; with a disc.
 [ debugcanonical
        DLINE   "Stupid filing system to canonicalise..."
 ]

; In order to placate ArcFS only *Dir to @ if special field is null

        TEQ     r1,#NULL
        EXIT    NE                      ; Exit if special field supplied

        ; We've got a disc name, so let's *Dir to that
        MOV     r5, r1
        MOV     r4, r2

        TEQ     r2, #0
        MOVEQ   r1, #'@'                ; store "@" onto stack as it's going to get mangled
        STREQ   r1, [sp]
        MOVEQ   r1, sp
        MOVEQ   r3, #0
        BEQ     %FT60

        ; Generate ":Discname" on the stack
        MOV     r1, r2
        BL      strlen
        ADD     r3, r3, #1+1+3
        BIC     r3, r3, #3
        SUB     sp, sp, r3
        MOV     r1, sp
        MOV     r0, #":"
        STRB    r0, [r1], #1
        BL      strcpy
        MOV     r1, sp
60
        Push    "r0-r5"
        MOV     r0, #fsfunc_Dir
 [ debugcanonical
        DSTRING r1, "*Dir to "
 ]
        BL      CallFSFunc_Given
 [ debugcanonical
        BVC     %FT00
        ADD     r0, r0, #4
        DSTRING r0, "*Dir failed with error "
00
 ]
        Pull    "r0-r5"
        ADD     sp, sp, r3

        MOV     r1, r5                  ; Restore special/discname pointers
        MOV     r2, r4

        MOVVS   r3, #0
        STRVS   r3, globalerror
        EXITS   VS                      ; Exit if error, don't canonicalise disc name
                                        ; This fixes problem with ArcFS

; Find name of selected disc

        Push    "r1-r2"
        SUB     sp, sp, #64             ; Somewhere to read the discname
        MOV     r0, #&ff
        STRB    r0, [sp]                ; Stack the length just in case the FS is stupid
        MOV     r0, #fsfunc_ReadDiscName
        MOV     r2, sp
        BL      CallFSFunc_Given
        LDRVCB  r3, [sp]
        MOVVS   r3, #0                  ; On error length=0 and cancel globalerror
        STRVS   r3, globalerror
        MOVVS   r3, #&ff
        CLRV

        ; Check for non-filled in block
        ; Check for 0 length name
        TEQ     r3, #&ff
        TEQNE   r3, #0
        BEQ     %FT80

 [ debugcanonical
        ADD     r0, sp, #1
        DSTRING r0, "Disc name is "
 ]
        ; Check for "Unset"
        TEQ     r3, #7
        LDREQB  r0, [sp, #1]
        TEQEQ   r0, #""""
        LDREQB  r0, [sp, #2]
        TEQEQ   r0, #"U"
        LDREQB  r0, [sp, #3]
        TEQEQ   r0, #"n"
        LDREQB  r0, [sp, #4]
        TEQEQ   r0, #"s"
        LDREQB  r0, [sp, #5]
        TEQEQ   r0, #"e"
        LDREQB  r0, [sp, #6]
        TEQEQ   r0, #"t"
        LDREQB  r0, [sp, #7]
        TEQEQ   r0, #""""
        BEQ     %FT80

        ADD     r3, r3, #1
        BL      SMustGetArea
        BVS     %FT80
        TEQ     r2, #Nowt
        MOVEQ   r1, #NULL
        BEQ     %FT75

        MOV     r1, r2
        ADD     r2, sp, #1
        MOV     r0, #0
        LDRB    r3, [sp]
        STRB    r0, [r2, r3]            ; Ensure disc name null terminated
        BL      strcpy                  ; dest = r1, src = r2

75
 [ debugcanonical
        DSTRING r1, "Stored disc name is "
 ]
        STR     r1, [sp, #64+1*4]      ; Update disc name ptr

; Restore stack

80      ADD     sp, sp, #64
        Pull    "r1-r2"

 [ debugcanonical
        DSTRING r2, "Returned disc name is "
        DSTRING r1, "Returned special field is "
 ]
        EXIT

98
        ; Error exit with both strings allocated:
        ; r1 - special field
        ; r4 - disc name

        STR     r0, [sp]
        MOV     r2, r4
        BL      SFreeArea
        LDR     r0, [sp]

99
        ; Error exit with only the new special field allocated:
        ; r1 - special field

        STR     r0, [sp]
        MOV     r2, r1
        BL      SFreeArea

        EXIT

; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; GetDiscFromPath
;
; In    r1 = path^
;
; Out   r1 = path^ after disc and .
;       r2 = disc^ or NULL

GetDiscFromPath Entry

        ; Check for the presence of a disc name
        LDRB    r14, [r1]
        TEQ     r14, #":"
        MOVNE   r2, #NULL
        EXIT    NE

        ; The start of the disc name
        ADD     r2, r1, #1

        ; Advance to the disc name's end
10
        LDRB    r14, [r1], #1
        TEQ     r14, #"."
        TEQNE   r14, #0
        BNE     %BT10

        ; If there's more, terminate the disc name, else move the
        ; rover (r1) back to the terminating nul
        TEQ     r14, #"."
        SUBNE   r1, r1, #1
        MOVEQ   r14, #0
        STREQB  r14, [r1, #-1]

        EXIT

; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; GetAbsolute
;
; In    r1 = path^ (path without disc or special field)
;
; Out   r0 = Absolute char in path or 0 if none there
;       r1 = path^ (path without disc, special field or absolute)
;
GetAbsolute Entry "r2"
        LDRB    r0, [r1]
        BL      IsAbsolute
        MOVNE   r0, #0
        EXIT    NE
        LDRB    r2, [r1, #1]!
        TEQ     r2, #"."
        ADDEQ   r1, r1, #1
        EXIT

; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; ObtainAbsoluteParts
;
; In    r1 = Path^
;       r3 = @/% relativity indicator (0 for @, not 0 for %)
;       r6 = special field^
;       fscb
;
; Out   r0 = special field^ (SNewStringed string)
;       r1 = user's path^ (part of original user's path)
;       r2 = disc^ (SNewStringed string)
;       r3 = Abs's path^ (SNewStringed string)
;
; The path gets analysed, the disc split off, the absolute chosen
; and the parts for reconstruction into a path returned.
;

ObtainAbsoluteParts Entry "r0-r5,r7", 4
 [ debugcanonical
 DSTRING r1,"Obtain absolute parts of "
 DREG r3, "Relativity is "
 DSTRING r6, "Special is "
 ]

        ; No strings allocated yet
        MOV     r0, #NULL
        STR     r0, [sp, #Proc_LocalStack + 0*4]
        STR     r0, [sp, #Proc_LocalStack + 2*4]
        STR     r0, [sp, #Proc_LocalStack + 3*4]
        STR     r0, [sp]

        ; Get the disc and the absolute
        BL      GetDiscFromPath
        BL      GetAbsolute
        STR     r1, [sp, #Proc_LocalStack + 1*4]

        ; Is there a disc name?
        TEQ     r2, #NULL
        BNE     %FT70

        ; There is no disc name
        ; Is there a special field?
        TEQ     r6, #NULL
        BNE     %FT40

        ; There is no disc and no special field - substitute an absolute reference
        TEQ     r0, #"$"
        BNE     %FT05
        MOV     r4, #Dir_Current
        B       %FT10

05
        BL      %FT90

10
        ; Post-processing to obtain strings for special, disc and path
        ; r4 -> part of SysVar indicating directory

        MOV     r7, r0          ; For later

        ; Read the directory string
        MOV     r0, sp
        MOV     r2, r4
        BL      ReadDir
        EXIT    VS

11
        LDR     r1, [sp]
        LDRB    r14, [r1]
        TEQ     r14, #"#"
        MOVNE   r0, #NULL
        BNE     %FT15

        ; Police the special field
        ADD     r1, r1, #1
        BL      PoliceSpecial
        BVS     %FT20

 [ debugcanonical
 DSTRING r1,"Special accepted"
 ]

        ; Skip past special field
        MOV     r0, #":"
        BL      strchr
        BNE     %FT50           ; Not found
 [ debugcanonical
 DSTRING r1, "strchr gives "
 ]
        ADD     r1, r1, #1

        ; Reconstruct pointer to special
        LDR     r0, [sp]
        ADD     r0, r0, #1

15
 [ debugcanonical
 DSTRING r1,"name being checked is "
 ]
        ; Check the name
        BL      PoliceName
        BVC     %FT30

        ; Bad name means use default disc and path
20
 [ debugcanonical
 DLINE "Name from dirstore due to being bad"
 ]
        CLRV
        B       %FT50

30
 [ debugcanonical
 DLINE "Dirstore accepted"
 ]

        ; Split into disc and path
        BL      GetDiscFromPath

        TEQ     r7, #"$"
        MOVNE   r3, r1                  ; The path as part of the dirpath
        addr    r3, Default_RootD_Path, EQ

        ; Exit for 'no special field and no disc'
        B       %FT80

40
        ;
        ; Special field and no disc specified
        ;
        ; r0 = Absolute char
        ; r1 = user's path^
        ; r2 = disc^ = NULL
        ; r3 = relativity indicator
        ; r4 unused
        ; r5 unused
        ; r6 = user's special field^
        ;
        ; Possible actions:
        ; a) Construct path from path's <sp>, NULL and Default_RootD_Path ("$")
        ; b) Construct path from <abs>'s <sp>, <abs>'s disc and <abs>'s path
        ; c) Construct path from path's <sp>, NULL and default <abs>'s path for FS type
        ;
        ; if $ in path:
        ;   (a)
        ; else
        ;   if no abs specified
        ;     set <abs> to default start dir
        ;   if <special>=<abs>'s special
        ;     (b)
        ;   else
        ;     (c)


        ; Is it #<special>:$
        TEQ     r0, #"$"
        MOVEQ   r0, r6
        addr    r3, Default_RootD_Path, EQ
        BEQ     %FT80

        ; Convert absolute to fsw_dirspart string
        BL      %FT90

        MOV     r7, r0          ; For later (absolute)

        ; Get the <abs>'s special field
        MOV     r0, sp
        MOV     r2, r4
        BL      ReadDir
        EXIT    VS

        LDR     r1, [sp]
 [ debugcanonical
 DSTRING r1, "Name from dirstore is "
 ]
        LDRB    r14, [r1]
        TEQ     r14, #"#"
        MOVNE   r1, #NULL
        BNE     %FT45

        MOV     r0, #":"
        BL      strchr
        MOVNE   r1, #NULL
        BNE     %FT45

        MOV     r0, #0
        STRB    r0, [r1]
        MOV     r5, r1

45
        ; Compare with the user's special field
        MOV     r2, r6
        BL      strcmp
        BNE     %FT50

        ; Re-terminate the special field
        TEQ     r1, #NULL
        MOVNE   r0, #":"
        STRNEB  r0, [r5]

        B       %BT11

50
        ; ReadDir dir string now rejected
 [ debugcanonical
 DLINE "Name from dirstore rejected"
 ]

        ; Use the user's special
        MOV     r0, r6

        ; and copy default disc (NULL), and default path
        MOV     r2, #NULL
        LDR     r1, [fscb, #fscb_info]
        TST     r1, #fsinfo_handlesurdetc
        BNE     %FT60

        TEQ     r7, #"@"
        addr    r3, Default_CSD_Path, EQ
        TEQ     r7, #"%"
        addr    r3, Default_LibD_Path, EQ
        TEQ     r7, #"\\"
        addr    r3, Default_PSD_Path, EQ
        TEQ     r7, #"&"
        addr    r3, Default_URD_Path, EQ
        B       %FT80

60
        TEQ     r7, #"@"
        addr    r3, Default_CSD_Whan_Path, EQ
        TEQ     r7, #"%"
        addr    r3, Default_LibD_Whan_Path, EQ
        TEQ     r7, #"\\"
        addr    r3, Default_PSD_Whan_Path, EQ
        TEQ     r7, #"&"
        addr    r3, Default_URD_Whan_Path, EQ
        B       %FT80

70
        ; Disc name present - must be relative to $
        ; In    r0 = Absolute char from path, or 0 if none
        ;       r1 = Path without disc or absolute char
        ;       r2 = disc
        ;       r3 = @/% relativity indicator (0 for @, not 0 for %)
        ;       r6 = special field^
        ;       r7 = original path^
        ;
        ; Two cases:
        ; non-handlesurdetc flavour filing system:
        ;       Check :<disc> isn't followed by <abs> where <abs> != $
        ;       If ok construct #<user's sp>::<user's disc>.$
        ; handlesurdetc flavour filing system:
        ;       :<disc>.& construct #<user's sp>::<user's disc>.&
        ;       :<disc> or :<disc>.$ construct #<user's sp>::<user's disc>.$
        ;       :<disc>.% construct #<user's sp>::<user's disc>.%
        ;       :<disc>.<abs other than $, & or %> give error
        ;

        LDR     lr, [fscb, #fscb_info]
        TST     lr, #fsinfo_handlesurdetc
        BNE     %FT75

        TEQ     r0, #"$"
        TEQNE   r0, #0
        ADRNEL  r0, ErrorBlock_WasntDollarAfterDisc
        BLNE    CopyError
        EXIT    VS
        MOV     r0, r6                  ; Use user's special field
        addr    r3, Default_RootD_Path
        MOV     r5, #0
        B       %FT80

75
        TEQ     r0, #"$"
        TEQNE   r0, #"&"
        TEQNE   r0, #"%"
        TEQNE   r0, #0
        ADRNEL  r0, ErrorBlock_WasntDollarAfterDisc
        BLNE    CopyError
        EXIT    VS
        TEQ     r0, #"%"
        addr    r3, Default_LibD_Whan_Path, EQ
        TEQ     r0, #"&"
        addr    r3, Default_URD_Whan_Path, EQ
        TEQ     r0, #"$"
        TEQNE   r0, #0
        addr    r3, Default_RootD_Path, EQ
        MOV     r0, r6                  ; Use user's special field
        MOV     r5, #0

80
        ; Claim strings exit
        ;
        ; r0 = special to copy
        ; r2 = disc to copy
        ; r3 = path to copy
        ; r5 = stack adjust once finished
 [ debugcanonical
 DSTRING r0, "Claiming SP=",cc
 DSTRING r2, " dsc=",cc
 DSTRING r3, " path="
 ]

        MOV     r1, r0

        ; Map NULL, Nowt or '' special fields to NULL
        TEQ     r1, #NULL
        TEQNE   r1, #Nowt
        LDRNEB  r0, [r1]
        TEQNE   r0, #0
        MOVEQ   r1, #NULL

        ; Ensure special field terminated
        TEQ     r1, #NULL
        BEQ     %FT82
        Push    "r1"
        MOV     r0, #":"
        BL      strchr
        MOVEQ   r0, #0
        STREQB  r0, [r1]
        Pull    "r1"

82
        ADD     r0, sp, #Proc_LocalStack + 0*4
        BL      SNewString
        ADDVC   r0, sp, #Proc_LocalStack + 2*4
        MOVVC   r1, r2
        BLVC    SNewString
        ADDVC   r0, sp, #Proc_LocalStack + 3*4
        MOVVC   r1, r3
        BLVC    SNewString

        ; Free the dir string
        MOV     r0, sp
        BL      SFreeLinkedString

        EXIT    VC

        ; Error exit - free the strings
        LDR     r2, [sp, #Proc_LocalStack + 0*4]
        TEQ     r2, #NULL
        BLNE    SFreeArea
        LDR     r2, [sp, #Proc_LocalStack + 2*4]
        TEQ     r2, #NULL
        BLNE    SFreeArea
        EXIT

90
        ; Subroutine to convert (r0,r3)=(absolute, default absolute)
        ; into r4=Dir_Blah number
        TEQ     r0, #0
        BNE     %FT95

        TEQ     r3, #0
        MOVEQ   r0, #"@"
        MOVNE   r0, #"%"

95
        TEQ     r0, #"@"
        MOVEQ   r4, #Dir_Current
        TEQ     r0, #"\\"
        MOVEQ   r4, #Dir_Previous
        TEQ     r0, #"&"
        MOVEQ   r4, #Dir_UserRoot
        TEQ     r0, #"%"
        MOVEQ   r4, #Dir_Library

        MOV     pc, lr


; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; StitchAbsolutePartsTogether
;
; In    r0 = special field^
;       r1 = user's path^
;       r2 = disc^
;       r3 = Abs's path
;       r6 = Special field^^ to replace
;       r7 = path^^ to replace
;       fscb
;
; Out   parts joined and replace old values
;
; The disc and special field get canonicalised before being used.
; Path gets parents reduced.
;

StitchAbsolutePartsTogether Entry "r0,r1,r2,r3,r4,r5,r6,r7,r8,r9"
 [ debugcanonical
 DSTRING r0,"Constructing new path from ",cc
 DSTRING r2," ",cc
 DSTRING r3," ",cc
 DSTRING r1," "
 ]
        ; Preserve user's path^ in r8
        MOV     r8, r1

        ; Preserve the original special field and disc
        ; to test whether the new values should be freed afterwards or not
        MOV     r4, r0
        MOV     r5, r2

        MOV     r1, r0
        BL      CanonicaliseSpecialAndDisc
        EXIT    VS

        ; Preserve caned disc in r9
        MOV     r9, r2

        ;
        ; Replace the special field
        ;
        SUB     sp, sp, #4

        MOV     r2, r1
        TEQ     r1, #NULL
        STREQ   r1, [sp]
        MOVNE   r0, sp
        BLNE    SGetLinkedString
        ADDVS   sp, sp, #4
        BVS     %FT98

        MOV     r0, r6
        BL      SFreeLinkedString
        Pull    "r0"
        STR     r0, [r6]
        BVS     %FT98

        ; Free the RMA special field
        TEQ     r2, r4
        BLNE    SFreeArea
        BVS     %FT99
10

        ; Construct a new path
        ;
        ; r0 = unused
        ; r1 = unused
        ; r2 = unused
        ; r3 = Abs's path^
        ; r4 = unused
        ; r5 = original disc^
        ; r6 = unused
        ; r7 = orig. path^^
        ; r8 = user's path^
        ; r9 = use this disc^

        MOV     r4, r3

        ; Get the length into r3

        MOV     r3, #0

        ; The disc
        MOVS    r1, r9
        BLNE    strlen_accumulate
        ADDNE   r3, r3, #2              ; for the : and the .

        ; The Abs's path
        MOV     r1, r4                  ; Abs's path never NULL
        BL      strlen_accumulate

        ; The user's path
        LDRB    r1, [r8]
        TEQ     r1, #0
        ADDNE   r3, r3, #1              ; For the . if the user's path is non-empty
        MOV     r1, r8
        BL      strlen_accumulate
        ADD     r3, r3, #1+:LEN:".!Run" ; Add one for the terminator and ".!Run"

        ; Allocate a new area for the path and replace the old
        ; r1 holds the user's path during this
        SUB     sp, sp, #4
        MOV     r0, sp
 [ debugheap
        DLINE   "StitchAbsolutePartsTogether:",cc
 ]
        BL      SGetLinkedArea
        ADD     sp, sp, #4
        BVS     %FT99
        MOV     r6, r2                  ; Hold original new path start in r6

        ; Copy the various parts to make up the name

        ; Copy the disc name, if present
        MOV     r1, r2
        TEQ     r9, #NULL
        MOVNE   r2, #":"
        STRNEB  r2, [r1], #1
        MOVNE   r2, r9
        BLNE    strcpy_advance
        ; Hold position in new path after disc for ReduceParents later
        MOV     r3, r1
        LDRNEB  r2, [r4]
        TEQNE   r2, #0
        MOVNE   r2, #"."
        STRNEB  r2, [r1], #1


        ; Copy the Abs's path
        MOV     r2, r4
        BL      strcpy_advance

        ; Copy the user's path
        LDRB    r2, [r8]
        TEQ     r2, #0
        MOVNE   r2, #"."
        STRNEB  r2, [r1], #1
        MOV     r2, r8
        BL      strcpy_advance

        ; Replace the old path
        MOV     r0, r7
        BL      SFreeLinkedString
        STR     r6, [r7]
        BVS     %FT99

        ; Reduce the parents now using position after disc saved earlier
        MOV     r2, r3
        BL      ReduceParents

        ; Free the disc name if necessary
        TEQ     r5, r9
        EXIT    EQ
        MOVS    r2, r9
        BLNE    SFreeArea

        EXIT

98
        ; Error exit - free both special field and disc if necessary
        ; r1 = new special field^
        ; r4 = original special field^
        ; r5 = original disc^
        ; r9 = new disc^

        TEQ     r1, r4
        BEQ     %FT99
        MOVS    r2, r1
        BLNE    SFreeArea

99
        ; Error exit - free disc if necessary
        ; r5 = original disc^
        ; r9 = new disc^

        TEQ     r5, r9
        EXIT    EQ
        MOVS    r2, r9
        BLNE    SFreeArea

        EXIT

; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; CanonicalisePath
;
; In    r1 = path^^
;       r3 = Relativity indicator
;       r6 = special field^^
;       fscb
;
; Out   Path canonicalised
;

CanonicalisePath Entry "r0,r1,r2,r3,r6,r7"
        MOV     r7, r1
        LDR     r1, [r1]
        LDR     r6, [r6]
        BL      ObtainAbsoluteParts
        EXIT    VS
        LDR     r6, [sp, #4*4]
 [ debugheap
        DREG    r0, "Special into stitch absolute parts is "
 ]
        BL      StitchAbsolutePartsTogether

        ; Free the strings generated by ObtainAbsoluteParts
        BL      SFreeArea               ; free disc
        MOV     r2, r0
        BL      SFreeArea               ; free special
        MOV     r2, r3
        BL      SFreeArea               ; free abs's path

        MOVVC   r1, r7
        LDRVC   r6, [r6]
        BLVC    UnWildcardPath
        EXIT

; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; ResolveWildcardBySteam
;
; In    r0 = objecttype of directory
;       r1 = path^ of directory in which wildcard resides
;       r2 = wildcard^ to resolve
;       r6 = special field^ or scb^
;       fscb
;
; Out   r0 corrupt
;       r2 = RMAlloced resolved wildcard, or NULL if not found
;
MinWildCardSpace * &10
MaxWildCardSpace * &400
ResolveWildcardBySteam Entry "r1,r3,r4,r5,r6,r7"

        ; Hold wildcard for later
        MOV     r7, r2

        GetLumpOfStack  r5, #MinWildCardSpace, #MaxWildCardSpace, #2048, %FA70

        ; Start at start of directory
        MOV     r4, #0

10
        ; Read some more dir entries
        MOV     r0, #fsfunc_ReadDirEntries
        MOV     r2, sp
        MOV     r3, #MaxWildCardSpace           ; A big number
        Push    "r1,r5,r6"
        BL      CallFSFunc_Given
        Pull    "r1,r5,r6"
        ADDVS   sp, sp, r5
        EXIT    VS

        ; No entries read at all?
        TEQ     r3, #0
        BEQ     %FT60

        ; Got some entries, lets wildmatch through them

        MOV     r2, r7
        MOV     r1, sp
20
        ; Have we found a match?
        BL      WildMatch
        BEQ     %FT50

        ; Skip over unmatched name
30
        LDRB    lr, [r1], #1
        TEQ     lr, #0
        BNE     %BT30

        ; Decrement names count and loop if non-zero
        SUBS    r3, r3, #1
        BNE     %BT20

        ; More entries?
        CMP     r4, #-1
        ADDNE   lr, sp, r5
        LDRNE   r1, [lr, #0*4]
        BNE     %BT10

40
        ; No more entries, hence no match
        ADD     sp, sp, r5
        MOV     r2, #NULL
        EXIT

50
        ; Found a match pointed to by r1
        BL      strlen
        ADD     r3, r3, #1
        BL      SMustGetArea

        ; Bad fail
        ADDVS   sp, sp, r5
        EXIT    VS

        ; Copy the string into the new piece of memory
        Swap    r1, r2
        BL      strcpy

        ; Drop the stack frame
        ADD     sp, sp, r5

        ; Result in r2
        MOV     r2, r1

        EXIT

60
        ; No entries read this time
        CMP     r4, #-1
        BEQ     %BT40                           ; No more entries because we've reached the end
        ADD     sp, sp, r5                      ; Drop the stack frame

70
        ; Not enough stack error exit - and no stack frame claimed
        ADRL    r0, ErrorBlock_NotEnoughStackForWildcardResolution
        BL      CopyError
        EXIT

; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; ResolveWildcard
;
; In    r1 = path^ of directory in which wildcard resides.
;               This must be canonical.
;       r2 = wildcard^ to resolve
;       r6 = special field^/scb^
;       fscb    (not a MultiFS)
;
; Out   r2 = RMAlloced resolved wildcard, or NULL if not found
;

ResolveWildcard Entry "r0,r1,r2,r3,r4,r5,fscb"

        ; Ensure the directory exists, but don't care about it's attributes
        BL      EnsureCanonicalObject
        BLVC    AssessDestinationForPathTailForDirRead
        EXIT    VS

        ; Get the wildcard back
        LDR     r2, [sp, #2*4]

        ; Is the fscb a MultiFS fscb?
        LDR     r14, [fscb, #fscb_info]         ; Wish to get full fsinfo word
        TST     r14, #&ff
        BEQ     %FT20

        ; Does the non-MultiFS fscb support the MultiFS extensions?
        TST     r14, #fsinfo_multifsextensions
        BEQ     %FT20

        ; We've got a non-MultiFS filing system supporting MultiFS extensions
        ; including fsfunc_ResolveWildcard
        ;
        ; In:
        ; r0 = fsfunc_ResolveWildcard
        ; r1 = directory in which Wildcard sits
        ; r2 = destination to place result, or 0 if just want length of result given
        ; r3 = Wildcard to resolve
        ; r5 = size of destination
        ; r6 = Special field
        ;
        ; Out:
        ; r2 = unchanged or -1 if not found
        ; r4 = overhang of result, 0 if fitted, or < 0 if FS wants resolution done by steam
        ;
        MOV     r0, #fsfunc_ResolveWildcard
        MOV     r3, r2
        GetLumpOfStack  r5, #MinWildCardSpace, #MaxWildCardSpace, #2048, %FA40
        MOV     r2, sp

        Push    "r1,r5"
        BL      CallFSFunc_Given
        Pull    "r1,r5"

        ; Error?
        BVS     %FT50

        ; Is the filing system asking me to do it by steam?
        CMP     r4, #0
        MOVLT   r2, r3
        BLT     %FT10           ; Signed <

        ; Not found?
        CMP     r2, #-1
        ADDEQ   sp, sp, r5
        MOVEQ   r2, #NULL
        STREQ   r2, [sp, #2*4]
        EXIT    EQ

        ; Was there enough room for the name (did we overflow)?
        CMP     r4, #0
        BGT     %FT30

        ; The name's on the stack - give it some RMA
        MOV     r1, sp
        BL      strlen
        ADD     r3, r3, #1
        BL      SMustGetArea
        BVS     %FT50

        ; Got the memory - copy the string
        Swap    r1, r2
        BL      strcpy

        ; Drop the stack chunk and set return value
        ADD     sp, sp, r5
        STR     r1, [sp, #2*4]
        EXIT

10
        ; Do it by steam because we were asked to - drop the stack frame
        ADD     sp, sp, r5

20
        ; Do it by steam because the filing system can't cope
        BL      ResolveWildcardBySteam
        STRVC   r2, [sp, #2*4]
        STRVS   r0, [sp]
        EXIT

30
        ; Not enough stack... and drop the stack frame
        ADD     sp, sp, r5

40
        ADRL    r0, ErrorBlock_NotEnoughStackForWildcardResolution
        BL      CopyError
        STR     r0, [sp]
        EXIT

50
        ; VS exit - drop the stack frame
        ADD     sp, sp, r5
        STR     r0, [sp]
        EXIT

; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; SkipToLeafEnd
;
; In    r2 = path^
;
; Out   r2 advanced to next . or terminator
;
SkipToLeafEnd Entry
10
        LDRB    r14, [r2], #1
        TEQ     r14, #"."
        TEQNE   r14, #0
        BNE     %BT10
        SUB     r2, r2, #1
        EXIT

; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; UnWildcardPath
;
; In    r1 = linked semi-canonical (:<disc>.<abs><rest of path>) path^^
;       r6 = Special field^
;       fscb (not image filing system)
;
; Out   Path has resolvable wildcards resolved
;
UnWildcardPath Entry "r0,r1,r2,r3,r4,r5,r7"

 [ debugcanonical
 Push r1
 LDR r1,[r1]
 DSTRING r1,"UnWildcarding "
 Pull r1
 ]
        ; r7 = path^^
        ; r1 = start of element being checked
        ; r2 = end of element being checked
        MOV     r7, r1
        LDR     r1, [r7]
        MOV     r2, r1

        ; Skip the disc name (if present)
        LDRB    r14, [r2]
        TEQ     r14, #":"
        BNE     %FT10
        BL      SkipToLeafEnd
        LDRB    r0, [r2]
        TEQ     r0, #"."
 [ debugcanonical
 BEQ %FT01
 DLINE "Path has no leaves - no unwildcarding to do"
01
 ]
        EXIT    NE

        ; Start is after separator
        ADD     r2, r2, #1

10
        MOV     r1, r2
        BL      SkipToLeafEnd

        ; Check for this leaf being wildcarded
        ; Patch in a terminator whilst we do the check
        LDRB    r0, [r2]
        MOV     r14, #0
        STRB    r14, [r2]
        BL      Util_CheckWildName
        STRB    r0, [r2]
        BEQ     %FT20

        ; Is that all?
        TEQ     r0, #0
 [ debugcanonical
 BNE %FT01
 LDR r1,[r7]
 DSTRING r1,"Result is "
01
 ]
        EXIT    EQ

        ; There's more - move to next leaf
        ADD     r2, r2, #1
        B       %BT10

20
        ; It is wildcarded - resolve it

        ; Temporarily terminate the dirpath and the wildleaf
        LDR     r14, [r7]
        CMP     r1, r14
        MOV     r14, #0
        LDRB    r0, [r2]
        STRB    r14, [r2]
        STRHIB  r14, [r1, #-1]

        ; Save the ends of the leaf
        MOV     r4, r1
        MOV     r5, r2

        ; Set up the ResolveWildcard parameters
        MOV     r2, r1
        LDRHI   r1, [r7]
        MOVLS   r1, r2                          ; A nul string for dir if first leaf is wildcarded

        BL      ResolveWildcard
 [ debugcanonical
 BVC %FT01
 DLINE "Wildcard resolution error"
01
 ]
        EXIT    VS

        ; Put the . or nul back
        STRB    r0, [r5]

        ; Was a match found - if not tidy up and give up!
        TEQ     r2, #NULL
        BNE     %FT30

        LDR     r14, [r7]
        CMP     r4, r14
        MOV     r14, #"."
        STRHIB  r14, [r4, #-1]
 [ debugcanonical
 BNE %FT01
 Push r1
 LDR r1,[sp, #1*4]
 LDR r1,[r1]
 DSTRING r1,"Element not found - result is "
 Pull r1
01
 ]
        EXIT

30
        ; Found a match - reconstruct the path

        ; Hold start of path (which may be a nul string, and not [r7])
        MOV     r4, r1

        ; accumulate the path length
        MOV     r3, #0
        BL      strlen_accumulate       ; 1st half of path
        MOV     r1, r2
        BL      strlen_accumulate       ; Resolved wildcard
        LDRB    r1, [r5]
        TEQ     r1, #0
        ADDNE   r1, r5, #1
        BLNE    strlen_accumulate       ; 2nd half of path
        ADD     r3, r3, #1+1+:LEN:".!Run"+1 ; 2 .s, ".!Run" and the terminator

        ; Get some linked memory
        MOV     r1, r2                  ; Save Wilcard over SGetLinkedArea
        SUB     sp, sp, #4
        MOV     r0, sp
 [ debugheap
        DLINE   "UnWildcardPath:",cc
 ]
        BL      SGetLinkedArea
        ADD     sp, sp, #4
        BVS     %FT99

        ; Copy the various parts to join them together
        MOV     r0, r1
        MOV     r1, r2
        MOV     r3, r2

 [ debugcanonical
        DSTRING r4,"Reconstructing path from: ",cc
        DSTRING r0, " ",cc
        DSTRING r5, " "
 ]
        MOV     r2, r4
        BL      strcpy_advance          ; First half of path
        MOV     r2, #"."
        STRB    r2, [r1], #1            ; .
        MOV     r2, r0
        BL      strcpy_advance          ; Resolved wildcard
        LDRB    r2, [r5]
        TEQ     r2, #0
        MOVNE   r2, #"."
        STRNEB  r2, [r1], #1            ; .
        ADDNE   r2, r5, #1
        BLNE    strcpy                  ; Second half of path
        ; (not strcpy_advance as we want r1 to point at the start
        ; of the remainder of the path - the unchecked bit)

        ; Junk the wildcard, which we've now copied
        MOV     r2, r0
        BL      SFreeArea

        ; Junk the original path
        MOV     r0, r7
        BL      SFreeLinkedString

        ; Replace with the new string
        STR     r3, [r7]

 [ debugcanonical
 BVC %FT01
 DLINE "Error return releasing strings"
01
 ]
        EXIT    VS

        MOV     r2, r1
        B       %BT10

99
        ; Error allocating area for new path - free wildcard in r1
        MOV     r2, r1
        BL      SFreeArea

 [ debugcanonical
 DLINE "Error exit"
 ]
        EXIT

        END