Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Sources/SKCore/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ add_library(SKCore STATIC
FileBuildSettings.swift
MainFilesProvider.swift
PathPrefixMapping.swift
SplitShellCommand.swift
Toolchain.swift
ToolchainRegistry.swift
XCToolchainPlist.swift)
Expand Down
124 changes: 4 additions & 120 deletions Sources/SKCore/CompilationDatabase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,11 @@ extension CompilationDatabase.Command: Codable {
if let arguments = try container.decodeIfPresent([String].self, forKey: .arguments) {
self.commandLine = arguments
} else if let command = try container.decodeIfPresent(String.self, forKey: .command) {
#if os(Windows)
self.commandLine = splitWindowsCommandLine(command, initialCommandName: true)
#else
self.commandLine = splitShellEscapedCommand(command)
#endif
} else {
throw CompilationDatabaseDecodingError.missingCommandOrArguments
}
Expand All @@ -265,123 +269,3 @@ extension CompilationDatabase.Command: Codable {
try container.encodeIfPresent(output, forKey: .output)
}
}

/// Split and unescape a shell-escaped command line invocation.
///
/// Examples:
///
/// ```
/// abc def -> ["abc", "def"]
/// abc\ def -> ["abc def"]
/// abc"\""def -> ["abc\"def"]
/// abc'\"'def -> ["abc\\"def"]
/// ```
///
/// See clang's `unescapeCommandLine()`.
public func splitShellEscapedCommand(_ cmd: String) -> [String] {
struct Parser {
var content: Substring
var i: Substring.UTF8View.Index
var result: [String] = []

var ch: UInt8 { self.content.utf8[i] }
var done: Bool { self.content.endIndex == i }

init(_ string: Substring) {
self.content = string
self.i = self.content.utf8.startIndex
}

mutating func next() {
i = content.utf8.index(after: i)
}

mutating func next(expect c: UInt8) {
assert(c == ch)
next()
}

mutating func parse() -> [String] {
while !done {
switch ch {
case UInt8(ascii: " "): next()
default: parseString()
}
}
return result
}

mutating func parseString() {
var str = ""
STRING: while !done {
switch ch {
case UInt8(ascii: " "): break STRING
case UInt8(ascii: "\""): parseDoubleQuotedString(into: &str)
case UInt8(ascii: "\'"): parseSingleQuotedString(into: &str)
default: parsePlainString(into: &str)
}
}
result.append(str)
}

mutating func parseDoubleQuotedString(into str: inout String) {
next(expect: UInt8(ascii: "\""))
var start = i
while !done {
switch ch {
case UInt8(ascii: "\""):
str += content[start..<i]
next()
return
case UInt8(ascii: "\\"):
str += content[start..<i]
next()
start = i
if !done { fallthrough }
default:
next()
}
}
str += content[start..<i]
}

mutating func parseSingleQuotedString(into str: inout String) {
next(expect: UInt8(ascii: "\'"))
let start = i
while !done {
switch ch {
case UInt8(ascii: "\'"):
str += content[start..<i]
next()
return
default:
next()
}
}
str += content[start..<i]
}

mutating func parsePlainString(into str: inout String) {
var start = i
while !done {
let _ch = ch
switch _ch {
case UInt8(ascii: "\""), UInt8(ascii: "\'"), UInt8(ascii: " "):
str += content[start..<i]
return
case UInt8(ascii: "\\"):
str += content[start..<i]
next()
start = i
if !done { fallthrough }
default:
next()
}
}
str += content[start..<i]
}
}

var parser = Parser(cmd[...])
return parser.parse()
}
Loading