1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
|
/*
This source file is part of the Swift System open source project
Copyright (c) 2020 Apple Inc. and the Swift System project authors
Licensed under Apache License v2.0 with Runtime Library Exception
See https://swift.org/LICENSE.txt for license information
*/
// FIXME: Need to rewrite and simplify this code now that SystemString
// manages (and hides) the null terminator
// The separator we use internally
private var genericSeparator: SystemChar { .slash }
// The platform preferred separator
//
// TODO: Make private
internal var platformSeparator: SystemChar {
_windowsPaths ? .backslash : genericSeparator
}
// Whether the character is the canonical separator
// TODO: Make private
internal func isSeparator(_ c: SystemChar) -> Bool {
c == platformSeparator
}
// Whether the character is a pre-normalized separator
internal func isPrenormalSeparator(_ c: SystemChar) -> Bool {
c == genericSeparator || c == platformSeparator
}
// Separator normalization, checking, and root parsing is internally hosted
// on SystemString for ease of unit testing.
extension SystemString {
// For invariant enforcing/checking. Should always return false on
// a fully-formed path
fileprivate func _hasTrailingSeparator() -> Bool {
// Just a root: do nothing
guard _relativePathStart != endIndex else { return false }
assert(!isEmpty)
return isSeparator(self.last!)
}
// Enforce invariants by removing a trailing separator.
//
// Precondition: There is exactly zero or one trailing slashes
//
// Postcondition: Path is root, or has no trailing separator
internal mutating func _removeTrailingSeparator() {
if _hasTrailingSeparator() {
self.removeLast()
assert(!_hasTrailingSeparator())
}
}
// Enforce invariants by normalizing the internal separator representation.
//
// 1) Normalize all separators to platform-preferred separator
// 2) Drop redundant separators
// 3) Drop trailing separators
//
// On Windows, UNC and device paths are allowed to begin with two separators,
// and partial or mal-formed roots are completed.
//
// The POSIX standard does allow two leading separators to
// denote implementation-specific handling, but Darwin and Linux
// do not treat these differently.
//
internal mutating func _normalizeSeparators() {
guard !isEmpty else { return }
var (writeIdx, readIdx) = (startIndex, startIndex)
if _windowsPaths {
// Normalize forwards slashes to backslashes.
//
// NOTE: Ideally this would be done as part of separator coalescing
// below. However, prenormalizing roots such as UNC paths requires
// parsing and (potentially) fixing up semi-formed roots. This
// normalization reduces the complexity of the task by allowing us to
// use a read-only lexer.
self._replaceAll(genericSeparator, with: platformSeparator)
// Windows roots can have meaningful repeated backslashes or may
// need backslashes inserted for partially-formed roots. Delegate that to
// `_prenormalizeWindowsRoots` and resume.
readIdx = _prenormalizeWindowsRoots()
writeIdx = readIdx
} else {
assert(genericSeparator == platformSeparator)
}
while readIdx < endIndex {
assert(writeIdx <= readIdx)
// Swap and advance our indices.
let wasSeparator = isSeparator(self[readIdx])
self.swapAt(writeIdx, readIdx)
self.formIndex(after: &writeIdx)
self.formIndex(after: &readIdx)
while wasSeparator, readIdx < endIndex, isSeparator(self[readIdx]) {
self.formIndex(after: &readIdx)
}
}
self.removeLast(self.distance(from: writeIdx, to: readIdx))
self._removeTrailingSeparator()
}
}
extension FilePath {
internal mutating func _removeTrailingSeparator() {
_storage._removeTrailingSeparator()
}
internal mutating func _normalizeSeparators() {
_storage._normalizeSeparators()
}
// Remove any `.` and `..` components
internal mutating func _normalizeSpecialDirectories() {
guard !isLexicallyNormal else { return }
defer { assert(isLexicallyNormal) }
let relStart = _relativeStart
let hasRoot = relStart != _storage.startIndex
// TODO: all this logic might be nicer if _parseComponent considered
// the null character index to be the next start...
var (writeIdx, readIdx) = (relStart, relStart)
while readIdx < _storage.endIndex {
let (compEnd, nextStart) = _parseComponent(startingAt: readIdx)
assert(readIdx < nextStart && compEnd <= nextStart)
let component = readIdx..<compEnd
// `.` is skipped over
if _isCurrentDirectory(component) {
readIdx = nextStart
continue
}
// `..`s are preserved at the very beginning of a relative path,
// otherwise parse-back a component to remove the parent (but stop at
// root).
if _isParentDirectory(component) {
// Skip over it if we're at the root
if hasRoot && writeIdx == relStart {
readIdx = nextStart
continue
}
// Drop the parent (unless we're preserving `..`s)
if writeIdx != relStart {
let priorComponent = _parseComponent(priorTo: writeIdx)
if !_isParentDirectory(priorComponent) {
writeIdx = priorComponent.lowerBound
readIdx = nextStart
continue
}
assert(self.root == nil && self.components.first!.kind == .parentDirectory)
}
}
if readIdx == writeIdx {
(readIdx, writeIdx) = (nextStart, nextStart)
continue
}
while readIdx != nextStart {
_storage.swapAt(readIdx, writeIdx)
readIdx = _storage.index(after: readIdx)
writeIdx = _storage.index(after: writeIdx)
}
}
assert(readIdx == _storage.endIndex && readIdx >= writeIdx)
if readIdx != writeIdx {
_storage.removeSubrange(writeIdx...)
_removeTrailingSeparator()
}
}
}
extension SystemString {
internal var _relativePathStart: Index {
_parseRoot().relativeBegin
}
}
extension FilePath {
internal var _relativeStart: SystemString.Index {
_storage._relativePathStart
}
internal var _hasRoot: Bool {
_relativeStart != _storage.startIndex
}
}
// Parse separators
extension FilePath {
internal typealias _Index = SystemString.Index
// Parse a component that starts at `i`. Returns the end
// of the component and the start of the next. Parsing terminates
// at the index of the null byte.
internal func _parseComponent(
startingAt i: _Index
) -> (componentEnd: _Index, nextStart: _Index) {
assert(i < _storage.endIndex)
// Parse the root
if i == _storage.startIndex {
let relativeStart = _relativeStart
if i != relativeStart {
return (relativeStart, relativeStart)
}
}
assert(!isSeparator(_storage[i]))
guard let nextSep = _storage[i...].firstIndex(where: isSeparator) else {
return (_storage.endIndex, _storage.endIndex)
}
return (nextSep, _storage.index(after: nextSep))
}
// Parse a component prior to the one that starts at `i`. Returns
// the start of the prior component. If `i` is the index of null,
// returns the last component.
internal func _parseComponent(
priorTo i: _Index
) -> Range<_Index> {
precondition(i > _storage.startIndex)
let relStart = _relativeStart
if i == relStart { return _storage.startIndex..<relStart }
assert(i > relStart)
var slice = _storage[..<i]
if i != _storage.endIndex {
assert(isSeparator(slice.last!))
slice.removeLast()
}
let end = slice.endIndex
while slice.endIndex != relStart, let c = slice.last, !isSeparator(c) {
slice.removeLast()
}
return slice.endIndex ..< end
}
internal func _isCurrentDirectory(_ component: Range<_Index>) -> Bool {
_storage[component].elementsEqual([.dot])
}
internal func _isParentDirectory(_ component: Range<_Index>) -> Bool {
_storage[component].elementsEqual([.dot, .dot])
}
internal func _isSpecialDirectory(_ component: Range<_Index>) -> Bool {
_isCurrentDirectory(component) || _isParentDirectory(component)
}
}
extension FilePath.ComponentView {
// TODO: Store this...
internal var _relativeStart: SystemString.Index {
_path._relativeStart
}
}
extension SystemString {
internal func _parseRoot() -> (
rootEnd: Index, relativeBegin: Index
) {
guard !isEmpty else { return (startIndex, startIndex) }
// Windows roots are more complex
if _windowsPaths { return _parseWindowsRoot() }
// A leading `/` is a root
guard isSeparator(self.first!) else { return (startIndex, startIndex) }
let next = self.index(after: startIndex)
return (next, next)
}
}
extension FilePath.Root {
// Asserting self is a root, returns whether this is an
// absolute root.
//
// On Unix, all roots are absolute. On Windows, `\` and `X:` are
// relative roots
//
// TODO: public
internal var isAbsolute: Bool {
assert(FilePath(SystemString(self._slice)).root == self, "not a root")
guard _windowsPaths else { return true }
// `\` or `C:` are the only form of relative roots, and all
// absolute roots are at least 3 chars long.
let slice = self._slice
guard slice.count < 3 else { return true }
assert(
(slice.count == 1 && slice.first == .backslash) ||
(slice.count == 2 && slice.last == .colon))
return false
}
}
extension FilePath {
internal var _portableDescription: String {
guard _windowsPaths else { return description }
let utf8 = description.utf8.map { $0 == UInt8(ascii: #"\"#) ? UInt8(ascii: "/") : $0 }
return String(decoding: utf8, as: UTF8.self)
}
}
// Whether we are providing Windows paths
@inline(__always)
internal var _windowsPaths: Bool {
#if os(Windows)
return true
#else
return forceWindowsPaths
#endif
}
extension FilePath {
// Whether we should add a separator when doing an append
internal var _needsSeparatorForAppend: Bool {
guard let last = _storage.last, !isSeparator(last) else { return false }
// On Windows, we can have a path of the form `C:` which is a root and
// does not need a separator after it
if _windowsPaths && _relativeStart == _storage.endIndex {
return false
}
return true
}
// Perform an append, inseting a separator if needed.
// Note that this will not check whether `content` is a root
internal mutating func _append(unchecked content: Slice<SystemString>) {
assert(FilePath(SystemString(content)).root == nil)
if content.isEmpty { return }
if _needsSeparatorForAppend {
_storage.append(platformSeparator)
}
_storage.append(contentsOf: content)
}
}
// MARK: - Invariants
extension FilePath {
internal func _invariantCheck() {
#if DEBUG
var normal = self
normal._normalizeSeparators()
precondition(self == normal)
precondition(!self._storage._hasTrailingSeparator())
precondition(_hasRoot == (self.root != nil))
#endif // DEBUG
}
}
|