Skip to content

Commit fd9220f

Browse files
authored
Merge pull request #3 from apple/master
Merge upstream
2 parents 2737f31 + c393e30 commit fd9220f

File tree

8 files changed

+364
-51
lines changed

8 files changed

+364
-51
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ The goal of the new Swift driver is to provide a drop-in replacement for the exi
9797
* [x] Teach the `DarwinToolchain` to also handle iOS, tvOS, watchOS
9898
* [x] Fill out the `GenericUnixToolchain` toolchain to get it working
9999
* [ ] Implement a `WindowsToolchain`
100-
* [ ] Implement proper tokenization for response files
100+
* [x] Implement proper tokenization for response files
101101
* Compilation modes
102102
* [x] Batch mode
103103
* [ ] Whole-module-optimization mode

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -297,26 +297,70 @@ public struct Driver {
297297
}
298298
}
299299

300-
// Response files.
300+
// MARK: - Response files.
301301
extension Driver {
302-
/// Tokenize a single line in a response file
303-
private static func tokenizeResponseFileLine(_ line: String) -> String {
304-
// FIXME: This is wrong. We need to do proper shell escaping.
305-
return line.replacingOccurrences(of: "\\ ", with: " ")
302+
/// Tokenize a single line in a response file.
303+
///
304+
/// This method supports response files with:
305+
/// 1. Double slash comments at the beginning of a line.
306+
/// 2. Backslash escaping.
307+
/// 3. Space character (U+0020 SPACE).
308+
///
309+
/// - Returns: One line String ready to be used in the shell, if any.
310+
///
311+
/// - Complexity: O(*n*), where *n* is the length of the line.
312+
private static func tokenizeResponseFileLine<S: StringProtocol>(_ line: S) -> String? {
313+
if line.isEmpty { return nil }
314+
315+
// Support double dash comments only if they start at the beginning of a line.
316+
if line.hasPrefix("//") { return nil }
317+
318+
var result: String = ""
319+
/// Indicates if we just parsed an escaping backslash.
320+
var isEscaping = false
321+
322+
for char in line {
323+
if char.isNewline { return result }
324+
325+
// Backslash escapes to the next character.
326+
if char == #"\"#, !isEscaping {
327+
isEscaping = true
328+
continue
329+
} else if isEscaping {
330+
// Disable escaping and keep parsing.
331+
isEscaping = false
332+
}
333+
334+
// Ignore spacing characters, except by the space character.
335+
if char.isWhitespace && char != " " { continue }
336+
337+
result.append(char)
338+
}
339+
return result.isEmpty ? nil : result
306340
}
307341

342+
/// Tokenize each line of the response file, omitting empty lines.
343+
///
344+
/// - Parameter content: response file's content to be tokenized.
345+
private static func tokenizeResponseFile(_ content: String) -> [String] {
346+
return content
347+
.split(separator: "\n")
348+
.compactMap { tokenizeResponseFileLine($0) }
349+
}
350+
351+
/// Recursively expands the response files.
352+
/// - Parameter visitedResponseFiles: Set containing visited response files to detect recursive parsing.
308353
private static func expandResponseFiles(
309354
_ args: [String],
310355
diagnosticsEngine: DiagnosticsEngine,
311356
visitedResponseFiles: inout Set<AbsolutePath>
312357
) throws -> [String] {
313-
// FIXME: This is very very prelimary. Need to look at how Swift compiler expands response file.
314-
315358
var result: [String] = []
316359

317360
// Go through each arg and add arguments from response files.
318361
for arg in args {
319362
if arg.first == "@", let responseFile = try? AbsolutePath(validating: String(arg.dropFirst())) {
363+
// Guard against infinite parsing loop.
320364
guard visitedResponseFiles.insert(responseFile).inserted else {
321365
diagnosticsEngine.emit(.warn_recursive_response_file(responseFile))
322366
continue
@@ -326,7 +370,7 @@ extension Driver {
326370
}
327371

328372
let contents = try localFileSystem.readFileContents(responseFile).cString
329-
let lines = contents.split(separator: "\n", omittingEmptySubsequences: true).map { tokenizeResponseFileLine(String($0)) }
373+
let lines = tokenizeResponseFile(contents)
330374
result.append(contentsOf: try expandResponseFiles(lines, diagnosticsEngine: diagnosticsEngine, visitedResponseFiles: &visitedResponseFiles))
331375
} else {
332376
result.append(arg)
@@ -707,7 +751,7 @@ extension Driver {
707751
}
708752

709753
extension Diagnostic.Message {
710-
public static func error_i_mode(_ driverKind: DriverKind) -> Diagnostic.Message {
754+
static func error_i_mode(_ driverKind: DriverKind) -> Diagnostic.Message {
711755
.error(
712756
"""
713757
the flag '-i' is no longer required and has been removed; \
@@ -731,17 +775,14 @@ extension Driver {
731775

732776
// Make sure we have a non-negative integer value.
733777
guard let numThreads = Int(numThreadsArg.asSingle), numThreads >= 0 else {
734-
diagnosticsEngine.emit(Diagnostic.Message.error_invalid_arg_value(arg: .numThreads, value: numThreadsArg.asSingle))
778+
diagnosticsEngine.emit(.error_invalid_arg_value(arg: .numThreads, value: numThreadsArg.asSingle))
735779
return 0
736780
}
737781

738-
#if false
739-
// FIXME: Check for batch mode.
740-
if false {
782+
if case .batchCompile = compilerMode {
741783
diagnosticsEngine.emit(.warning_cannot_multithread_batch_mode)
742784
return 0
743785
}
744-
#endif
745786

746787
return numThreads
747788
}
@@ -1032,7 +1073,7 @@ extension Driver {
10321073
}
10331074
}
10341075

1035-
if !moduleName.isSwiftIdentifier {
1076+
if !moduleName.sd_isSwiftIdentifier {
10361077
fallbackOrDiagnose(.error_bad_module_name(moduleName: moduleName, explicitModuleName: parsedOptions.contains(.moduleName)))
10371078
} else if moduleName == "Swift" && !parsedOptions.contains(.parseStdlib) {
10381079
fallbackOrDiagnose(.error_stdlib_module_name(moduleName: moduleName, explicitModuleName: parsedOptions.contains(.moduleName)))

Sources/SwiftDriver/Toolchains/DarwinToolchain.swift

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ fileprivate func envVarName(forExecutable toolName: String) -> String {
1515
return "SWIFT_DRIVER_\(toolName.uppercased())_EXEC"
1616
}
1717

18-
// FIXME: This should be in DarwinToolchain, but GenericUnixToolchain is
19-
// currently using it too for some reason.
20-
extension Toolchain {
21-
/// Utility function to lookup an executable using xcrun.
18+
/// Toolchain for Darwin-based platforms, such as macOS and iOS.
19+
///
20+
/// FIXME: This class is not thread-safe.
21+
public final class DarwinToolchain: Toolchain {
22+
public let env: [String: String]
23+
2224
func xcrunFind(exec: String) throws -> AbsolutePath {
2325
if let overrideString = env[envVarName(forExecutable: exec)] {
2426
return try AbsolutePath(validating: overrideString)
@@ -35,14 +37,7 @@ extension Toolchain {
3537
return AbsolutePath("/usr/bin/" + exec)
3638
#endif
3739
}
38-
}
3940

40-
/// Toolchain for Darwin-based platforms, such as macOS and iOS.
41-
///
42-
/// FIXME: This class is not thread-safe.
43-
public final class DarwinToolchain: Toolchain {
44-
public let env: [String: String]
45-
4641
/// Retrieve the absolute path for a given tool.
4742
public func getToolPath(_ tool: Tool) throws -> AbsolutePath {
4843
switch tool {

Sources/SwiftDriver/Toolchains/GenericUnixToolchain.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public final class GenericUnixToolchain: Toolchain {
3434
// If we happen to be on a macOS host, some tools might not be in our
3535
// PATH, so we'll just use xcrun to find them too.
3636
#if os(macOS)
37-
return try xcrunFind(exec: exec)
37+
return try DarwinToolchain(env: self.env).xcrunFind(exec: exec)
3838
#else
3939
throw Error.unableToFind(tool: exec)
4040
#endif

Sources/SwiftDriver/Utilities/StringAdditions.swift

Lines changed: 108 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@
1111
//===----------------------------------------------------------------------===//
1212
extension String {
1313
/// Whether this string is a Swift identifier.
14-
var isSwiftIdentifier: Bool {
15-
if isEmpty { return false }
14+
public var sd_isSwiftIdentifier: Bool {
15+
guard let start = unicodeScalars.first else {
16+
return false
17+
}
18+
19+
let continuation = unicodeScalars.dropFirst()
1620

17-
// FIXME: This is a hack. Check the actual identifier grammar here.
18-
return spm_mangledToC99ExtendedIdentifier() == self
21+
return start.isValidSwiftIdentifierStart &&
22+
continuation.allSatisfy { $0.isValidSwiftIdentifierContinuation }
1923
}
2024
}
2125

@@ -29,3 +33,103 @@ extension DefaultStringInterpolation {
2933
appendInterpolation(value)
3034
}
3135
}
36+
37+
extension Unicode.Scalar {
38+
39+
var isValidSwiftIdentifierStart: Bool {
40+
guard isValidSwiftIdentifierContinuation else { return false }
41+
42+
if isASCIIDigit || self == "$" {
43+
return false
44+
}
45+
46+
// N1518: Recommendations for extended identifier characters for C and C++
47+
// Proposed Annex X.2: Ranges of characters disallowed initially
48+
if (0x0300...0x036F).contains(value) ||
49+
(0x1DC0...0x1DFF).contains(value) ||
50+
(0x20D0...0x20FF).contains(value) ||
51+
(0xFE20...0xFE2F).contains(value) {
52+
return false
53+
}
54+
55+
return true
56+
}
57+
58+
var isValidSwiftIdentifierContinuation: Bool {
59+
if isASCII {
60+
return isCIdentifierBody(allowDollar: true)
61+
}
62+
63+
// N1518: Recommendations for extended identifier characters for C and C++
64+
// Proposed Annex X.1: Ranges of characters allowed
65+
return value == 0x00A8 ||
66+
value == 0x00AA ||
67+
value == 0x00AD ||
68+
value == 0x00AF ||
69+
70+
(0x00B2...0x00B5).contains(value) ||
71+
(0x00B7...0x00BA).contains(value) ||
72+
(0x00BC...0x00BE).contains(value) ||
73+
(0x00C0...0x00D6).contains(value) ||
74+
(0x00D8...0x00F6).contains(value) ||
75+
(0x00F8...0x00FF).contains(value) ||
76+
77+
(0x0100...0x167F).contains(value) ||
78+
(0x1681...0x180D).contains(value) ||
79+
(0x180F...0x1FFF).contains(value) ||
80+
81+
(0x200B...0x200D).contains(value) ||
82+
(0x202A...0x202E).contains(value) ||
83+
(0x203F...0x2040).contains(value) ||
84+
value == 0x2054 ||
85+
(0x2060...0x206F).contains(value) ||
86+
87+
(0x2070...0x218F).contains(value) ||
88+
(0x2460...0x24FF).contains(value) ||
89+
(0x2776...0x2793).contains(value) ||
90+
(0x2C00...0x2DFF).contains(value) ||
91+
(0x2E80...0x2FFF).contains(value) ||
92+
93+
(0x3004...0x3007).contains(value) ||
94+
(0x3021...0x302F).contains(value) ||
95+
(0x3031...0x303F).contains(value) ||
96+
97+
(0x3040...0xD7FF).contains(value) ||
98+
99+
(0xF900...0xFD3D).contains(value) ||
100+
(0xFD40...0xFDCF).contains(value) ||
101+
(0xFDF0...0xFE44).contains(value) ||
102+
(0xFE47...0xFFF8).contains(value) ||
103+
104+
(0x10000...0x1FFFD).contains(value) ||
105+
(0x20000...0x2FFFD).contains(value) ||
106+
(0x30000...0x3FFFD).contains(value) ||
107+
(0x40000...0x4FFFD).contains(value) ||
108+
(0x50000...0x5FFFD).contains(value) ||
109+
(0x60000...0x6FFFD).contains(value) ||
110+
(0x70000...0x7FFFD).contains(value) ||
111+
(0x80000...0x8FFFD).contains(value) ||
112+
(0x90000...0x9FFFD).contains(value) ||
113+
(0xA0000...0xAFFFD).contains(value) ||
114+
(0xB0000...0xBFFFD).contains(value) ||
115+
(0xC0000...0xCFFFD).contains(value) ||
116+
(0xD0000...0xDFFFD).contains(value) ||
117+
(0xE0000...0xEFFFD).contains(value)
118+
}
119+
120+
/// `true` if this character is an ASCII digit: [0-9]
121+
var isASCIIDigit: Bool { (0x30...0x39).contains(value) }
122+
123+
/// `true` if this is a body character of a C identifier,
124+
/// which is [a-zA-Z0-9_].
125+
func isCIdentifierBody(allowDollar: Bool = false) -> Bool {
126+
if (0x41...0x5A).contains(value) ||
127+
(0x61...0x7A).contains(value) ||
128+
isASCIIDigit ||
129+
self == "_" {
130+
return true
131+
} else {
132+
return allowDollar && self == "$"
133+
}
134+
}
135+
}

Sources/SwiftDriver/Utilities/VirtualPath.swift

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -40,23 +40,7 @@ public enum VirtualPath: Hashable {
4040
}
4141

4242
/// The name of the path for presentation purposes.
43-
///
44-
/// FIXME: Maybe this should be debugDescription or description
45-
public var name: String {
46-
switch self {
47-
case .relative(let path):
48-
return path.pathString
49-
50-
case .absolute(let path):
51-
return path.pathString
52-
53-
case .standardInput, .standardOutput:
54-
return "-"
55-
56-
case .temporary(let path):
57-
return path.pathString
58-
}
59-
}
43+
public var name: String { description }
6044

6145
/// The extension of this path, for relative or absolute paths.
6246
public var `extension`: String? {
@@ -147,6 +131,24 @@ extension VirtualPath: Codable {
147131
}
148132
}
149133

134+
extension VirtualPath: CustomStringConvertible {
135+
public var description: String {
136+
switch self {
137+
case .relative(let path):
138+
return path.pathString
139+
140+
case .absolute(let path):
141+
return path.pathString
142+
143+
case .standardInput, .standardOutput:
144+
return "-"
145+
146+
case .temporary(let path):
147+
return path.pathString
148+
}
149+
}
150+
}
151+
150152
extension VirtualPath {
151153
/// Replace the extension of the given path with a new one based on the
152154
/// specified file type.

0 commit comments

Comments
 (0)