File: FilePathWindows.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (465 lines) | stat: -rw-r--r-- 12,712 bytes parent folder | download
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
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
/*
 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
*/

internal struct _ParsedWindowsRoot {
  var rootEnd: SystemString.Index

  // TODO: Remove when I normalize to always (except `C:`)
  // have trailing separator
  var relativeBegin: SystemString.Index

  var drive: SystemChar?
  var fullyQualified: Bool

  var deviceSigil: SystemChar?

  var host: Range<SystemString.Index>?
  var volume: Range<SystemString.Index>?
}

extension _ParsedWindowsRoot {
  static func traditional(
    drive: SystemChar?, fullQualified: Bool, endingAt idx: SystemString.Index
  ) -> _ParsedWindowsRoot {
    _ParsedWindowsRoot(
      rootEnd: idx,
      relativeBegin: idx,
      drive: drive,
      fullyQualified: fullQualified,
      deviceSigil: nil,
      host: nil,
      volume: nil)
  }

  static func unc(
    deviceSigil: SystemChar?,
    server: Range<SystemString.Index>,
    share: Range<SystemString.Index>,
    endingAt end: SystemString.Index,
    relativeBegin relBegin: SystemString.Index
  ) -> _ParsedWindowsRoot {
    _ParsedWindowsRoot(
      rootEnd: end,
      relativeBegin: relBegin,
      drive: nil,
      fullyQualified: true,
      deviceSigil: deviceSigil,
      host: server,
      volume: share)
  }

  static func device(
    deviceSigil: SystemChar,
    volume: Range<SystemString.Index>,
    endingAt end: SystemString.Index,
    relativeBegin relBegin: SystemString.Index
  ) -> _ParsedWindowsRoot {
    _ParsedWindowsRoot(
      rootEnd: end,
      relativeBegin: relBegin,
      drive: nil,
      fullyQualified: true,
      deviceSigil: deviceSigil,
      host: nil,
      volume: volume)
  }
}

struct _Lexer {
  var slice: Slice<SystemString>

  init(_ str: SystemString) {
    self.slice = str[...]
  }

  var backslash: SystemChar { .backslash }

  // Try to eat a backslash, returns false if nothing happened
  mutating func eatBackslash() -> Bool {
    slice._eat(.backslash) != nil
  }

  // Try to consume a drive letter and subsequent `:`.
  mutating func eatDrive() -> SystemChar? {
    let copy = slice
    if let d = slice._eat(if: { $0.isLetter }), slice._eat(.colon) != nil {
      return d
    }
    // Restore slice
    slice = copy
    return nil
  }

  // Try to consume a device sigil (stand-alone . or ?)
  mutating func eatSigil() -> SystemChar? {
    let copy = slice
    guard let sigil = slice._eat(.question) ?? slice._eat(.dot) else {
      return nil
    }

    // Check for something like .hidden or ?question
    guard isEmpty || slice.first == backslash else {
      slice = copy
      return nil
    }

    return sigil
  }

  // Try to consume an explicit "UNC" directory
  mutating func eatUNC() -> Bool {
    slice._eatSequence("UNC".unicodeScalars.lazy.map { SystemChar(ascii: $0) }) != nil
  }

  // Eat everything up to but not including a backslash or null
  mutating func eatComponent() -> Range<SystemString.Index> {
    let backslash = self.backslash
    let component = slice._eatWhile({ $0 != backslash })
      ?? slice[slice.startIndex ..< slice.startIndex]
    return component.indices
  }

  var isEmpty: Bool {
    return slice.isEmpty
  }

  var current: SystemString.Index { slice.startIndex }

  mutating func clear() {
    // TODO: Intern empty system string
    self = _Lexer(SystemString())
  }

  mutating func reset(to: SystemString, at: SystemString.Index) {
    self.slice = to[at...]
  }
}

internal struct WindowsRootInfo {
  // The "volume" of a root. For UNC paths, this is also known as the "share".
  internal enum Volume: Equatable {
    /// No volume specified
    ///
    /// * Traditional root relative to the current drive: `\`,
    /// * Omitted volume from other forms: `\\.\`, `\\.\UNC\server\\`, `\\server\\`
    case empty

    /// A specified drive.
    ///
    /// * Traditional disk: `C:\`, `C:`
    /// * Device disk: `\\.\C:\`, `\\?\C:\`
    /// * UNC: `\\server\e:\`, `\\?\UNC\server\e:\`
    ///
    // TODO: NT paths? Admin paths using `$`?
    case drive(Character)

    /// A volume with a GUID in a non-traditional path
    ///
    /// * UNC: `\\host\Volume{0000-...}\`, `\\.\UNC\host\Volume{0000-...}\`
    /// * Device roots: `\\.\Volume{0000-...}\`, `\\?\Volume{000-...}\`
    ///
    // TODO: GUID type?
    case guid(String)

    // TODO: Legacy DOS devices, such as COM1?

    /// Device object or share name
    ///
    /// * Device roots: `\\.\BootPartition\`
    /// * UNC: `\\host\volume\`
    case volume(String)

    // TODO: Should legacy DOS devices be detected and/or converted at construction time?
    // TODO: What about NT paths: `\??\`
  }

  /// Represents the syntactic form of the path
  internal enum Form: Equatable {
    /// Traditional DOS roots: `C:\`, `C:`, and `\`
    case traditional(fullyQualified: Bool) // `C:\`, `C:`, `\`

    /// UNC syntactic form: `\\server\share\`
    case unc

    /// DOS device syntactic form: `\\?\BootPartition`, `\\.\C:\`, `\\?\UNC\server\share`
    case device(sigil: Character)

    // TODO: NT?
  }

  /// The host for UNC paths, else `nil`.
  internal var host: String?

  /// The specified volume (or UNC share) for the root
  internal var volume: Volume

  /// The syntactic form the root is in
  internal var form: Form

  init(host: String?, volume: Volume, form: Form) {
    self.host = host
    self.volume = volume
    self.form = form
    checkInvariants()
  }
}

extension _ParsedWindowsRoot {
  fileprivate func volumeInfo(_ root: SystemString) -> WindowsRootInfo.Volume {
    if let d = self.drive {
      return .drive(Character(d.asciiScalar!))
    }

    guard let vol = self.volume, !vol.isEmpty else { return .empty }

    // TODO: check for GUID
    // TODO: check for drive
    return .volume(root[vol].string)
  }
}

extension WindowsRootInfo {
  internal init(_ root: SystemString, _ parsed: _ParsedWindowsRoot) {
    self.volume = parsed.volumeInfo(root)

    if let host = parsed.host {
      self.host = root[host].string
    } else {
      self.host = nil
    }

    if let sig = parsed.deviceSigil {
      self.form = .device(sigil: Character(sig.asciiScalar!))
    } else if parsed.host != nil {
      assert(parsed.volume != nil)
      self.form = .unc
    } else {
      self.form = .traditional(fullyQualified: parsed.fullyQualified)
    }
  }
}

extension WindowsRootInfo {
  /// NOT `\foo\bar` nor `C:foo\bar`
  internal var isFullyQualified: Bool {
    return form != .traditional(fullyQualified: false)
  }

  ///
  /// `\\server\share\foo\bar.exe`, `\\.\UNC\server\share\foo\bar.exe`
  internal var isUNC: Bool {
    host != nil
  }

  ///
  /// `\foo\bar.exe`
  internal var isTraditionalRooted: Bool {
    form == .traditional(fullyQualified: false) && volume == .empty
  }

  ///
  /// `C:foo\bar.exe`
  internal var isTraditionalDriveRelative: Bool {
    switch (form, volume) {
    case (.traditional(fullyQualified: false), .drive(_)): return true
    default: return false
    }
  }

  // TODO: Should this be component?
  func formPath() -> FilePath {
    fatalError("Unimplemented")
  }

  //    static func traditional(
  //      drive: Character?, fullyQualified: Bool
  //    ) -> WindowsRootInfo {
  //      let vol: Volume
  //      if let d = Character {
  //        vol = .drive(d)
  //      } else {
  //        vol = .relative
  //      }
  //
  //      return WindowsRootInfo(
  //        volume: .relative, form: .traditional(fullyQualified: false))
  //    }

  internal func checkInvariants() {
    switch form {
    case .traditional(let qual):
      precondition(host == nil)
      switch volume {
      case .empty:
        precondition(!qual)
        break
      case .drive(_): break
      default: preconditionFailure()
      }
    case .unc:
      precondition(host != nil)
    case .device(_): break
    }
  }

}

extension SystemString {
  // TODO: Or, should I always inline this to remove some of the bookeeping?
  private func _parseWindowsRootInternal() -> _ParsedWindowsRoot? {
    assert(_windowsPaths)

    /*
      Windows root: device or UNC or DOS
        device: (`\\.` or `\\?`) `\` (drive or guid or UNC-link)
          drive: letter `:`
          guid: `Volume{` (hex-digit or `-`)* `}`
          UNC-link: `UNC\` UNC-volume
        UNC: `\\` UNC-volume
          UNC-volume: server `\` share
        DOS: fully-qualified or legacy-device or drive or `\`
          full-qualified: drive `\`

     TODO: What is \\?\server1\e:\utilities\\filecomparer\ from the docs?
     TODO: What about admin use of `$` instead of `:`? E.g. \\system07\C$\

     NOTE: Legacy devices are not handled by System at a library level, but
     are deferred to the relevant syscalls.
    */

    var lexer = _Lexer(self)

    // Helper to parse a UNC root
    func parseUNC(deviceSigil: SystemChar?) -> _ParsedWindowsRoot {
      let serverRange = lexer.eatComponent()
      guard lexer.eatBackslash() else {
        fatalError("expected normalized root to contain backslash")
      }
      let shareRange = lexer.eatComponent()
      let rootEnd = lexer.current
      _ = lexer.eatBackslash()
      return .unc(
        deviceSigil: deviceSigil,
        server: serverRange, share: shareRange,
        endingAt: rootEnd, relativeBegin: lexer.current)
    }


    // `C:` or `C:\`
    if let d = lexer.eatDrive() {
      // `C:\` - fully qualified
      let fullyQualified = lexer.eatBackslash()
      return .traditional(
        drive: d, fullQualified: fullyQualified, endingAt: lexer.current)
    }

    // `\` or else it's just a rootless relative path
    guard lexer.eatBackslash() else { return nil }

    // `\\` or else it's just a current-drive rooted traditional path
    guard lexer.eatBackslash() else {
      return .traditional(
        drive: nil, fullQualified: false, endingAt: lexer.current)
    }

    // `\\.` or `\\?` (device paths) or else it's just UNC
    guard let sigil = lexer.eatSigil() else {
      return parseUNC(deviceSigil: nil)
    }
    _ = sigil // suppress warnings

    guard lexer.eatBackslash() else {
      fatalError("expected normalized root to contain backslash")
    }

    if lexer.eatUNC() {
      guard lexer.eatBackslash() else {
        fatalError("expected normalized root to contain backslash")
      }
      return parseUNC(deviceSigil: sigil)
    }

    let device = lexer.eatComponent()
    let rootEnd = lexer.current
    _ = lexer.eatBackslash()

    return .device(
      deviceSigil: sigil, volume: device,
      endingAt: rootEnd, relativeBegin: lexer.current)
  }

  @inline(never)
  internal func _parseWindowsRoot() -> (
    rootEnd: SystemString.Index, relativeBegin: SystemString.Index
  ) {
    guard let parsed = _parseWindowsRootInternal() else {
      return (startIndex, startIndex)
    }
    return (parsed.rootEnd, parsed.relativeBegin)
  }
}

extension SystemString {
  // UNC and device roots can have multiple repeated roots that are meaningful,
  // and extra backslashes may need to be inserted for partial roots (e.g. empty
  // volume).
  //
  // Returns the point where `_normalizeSeparators` should resume.
  internal mutating func _prenormalizeWindowsRoots() -> Index {
    assert(_windowsPaths)
    assert(!self.contains(.slash), "only valid after separator conversion")

    var lexer = _Lexer(self)

    // Only relevant for UNC or device paths
    guard lexer.eatBackslash(), lexer.eatBackslash() else {
      return lexer.current
    }

    // Parse a backslash, inserting one if needed
    func expectBackslash() {
      if lexer.eatBackslash() { return }

      // A little gross, but we reset the lexer because the lexer
      // holds a strong reference to `self`.
      //
      // TODO: Intern the empty SystemString. Right now, this is
      // along an uncommon/pathological case, but we want to in
      // general make empty strings without allocation
      let idx = lexer.current
      lexer.clear()
      self.insert(.backslash, at: idx)
      lexer.reset(to: self, at: idx)
      let p = lexer.eatBackslash()
      assert(p)
    }
    // Parse a component and subsequent backslash, insering one if needed
    func expectComponent() {
      _ = lexer.eatComponent()
      expectBackslash()
    }

    // Check for `\\.` style paths
    if lexer.eatSigil() != nil {
      expectBackslash()
      if lexer.eatUNC() {
        expectBackslash()
        expectComponent()
        expectComponent()
        return lexer.current
      }
      expectComponent()
      return lexer.current
    }

    expectComponent()
    expectComponent()
    return lexer.current
  }
}