Skip to content

Commit 95e0c66

Browse files
authored
[SourceKit] Add SwiftLang API to parse into ByteTree format
Redesigns the `SwiftLang.parse` API to accommodate a format parameter which can be used to request a ByteTree-serialized syntax tree instead of JSON-serialized. This gives us a convenient way to use the fastest currently available combination of tools for parsing a file into a syntax tree. The new API only accepts paths, not source buffers, and returns JSON as `Data` instead of `String`, which are both better directions in the long run. The format type is extensible so that it can easily be extended to support "direct" parsing into a SyntaxSourceFile. Deprecated wrapper methods with the old names and signatures are still available for any existing clients.
1 parent 783e8ec commit 95e0c66

File tree

1 file changed

+131
-25
lines changed

1 file changed

+131
-25
lines changed

tools/SourceKit/tools/swift-lang/SwiftLang.swift

Lines changed: 131 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
// This file provides Swift language support by invoking SourceKit internally.
1313
//===----------------------------------------------------------------------===//
1414

15+
import Foundation
16+
1517
enum SourceKitdError: Error, CustomStringConvertible {
1618
case EditorOpenError(message: String)
1719
case EditorCloseError(message: String)
@@ -26,19 +28,132 @@ enum SourceKitdError: Error, CustomStringConvertible {
2628
}
2729
}
2830

29-
public class SwiftLang {
31+
// MARK: Public APIs
32+
33+
public enum SwiftLang {
34+
/// Synchronously parses Swift source code into a syntax tree.
35+
///
36+
/// - Parameter url: A file URL pointing to a Swift source file.
37+
/// - Parameter format: The format to use for the returned syntax tree.
38+
/// - Returns: The syntax tree in the indicated format.
39+
/// - Throws: If SourceKit responds to the request with an error. This is
40+
/// usually a communication or configuration error, not a
41+
/// syntax error in the code being parsed.
42+
/// - Precondition: `url` must be a file URL.
43+
public static func parse<Tree>(
44+
contentsOf url: URL, into format: SyntaxTreeFormat<Tree>
45+
) throws -> Tree {
46+
precondition(url.isFileURL, "Can only parse files at file URLs")
47+
return try parse(SourceFile(path: url.path), into: format)
48+
}
49+
}
50+
51+
extension SwiftLang.SyntaxTreeFormat {
52+
/// Return the syntax tree serialized in JSON format. JSON is easy to inspect
53+
/// and test with but very inefficient to deserialize. Use it for testing and
54+
/// debugging.
55+
public static var json: SwiftLang.SyntaxTreeFormat<Data> {
56+
return jsonString.withTreeMapped { $0.data(using: .utf8)! }
57+
}
58+
59+
/// Return the syntax tree serialized as ByteTree. ByteTree is fast and
60+
/// compact but difficult to inspect or manipulate with textual tools.
61+
/// It's recommended in production.
62+
public static var byteTree: SwiftLang.SyntaxTreeFormat<Data> {
63+
return .init(kind: .kind_SyntaxTreeSerializationByteTree) { dict in
64+
dict.getData(syntaxTreeKey)
65+
}
66+
}
67+
68+
/// Creates a new `SyntaxTreeFormat` instance which converts the tree of an
69+
/// existing format.
70+
///
71+
/// You can use this method to add new `SyntaxTreeFormat`s; simply declare a
72+
/// new static constant in an extension of `SyntaxTreeFormat` which maps one
73+
/// of the existing formats.
74+
///
75+
/// - Parameter transform: A function which converts `self`'s Tree type to
76+
/// the result type's Tree type.
77+
/// - Returns: A new format which creates a tree in `self`'s format, then
78+
/// applies `transform` to the tree to convert it.
79+
public func withTreeMapped<NewTree>(
80+
by transform: @escaping (Tree) throws -> NewTree
81+
) -> SwiftLang.SyntaxTreeFormat<NewTree> {
82+
return .init(kind: kind) { [makeTree] dict in
83+
try transform(makeTree(dict))
84+
}
85+
}
86+
}
87+
88+
// MARK: Deprecated APIs
3089

31-
fileprivate static func parse(content: String, name: String, isURL: Bool) throws -> String {
90+
extension SwiftLang {
91+
/// Parses the Swift file at the provided URL into a `Syntax` tree in Json
92+
/// serialization format by querying SourceKitd service. This function isn't
93+
/// thread safe.
94+
/// - Parameter path: The URL you wish to parse.
95+
/// - Returns: The syntax tree in Json format string.
96+
@available(swift, deprecated: 5.0, renamed: "parse(_:into:)")
97+
public static func parse(path: String) throws -> String {
98+
return try parse(SourceFile(path: path), into: .jsonString)
99+
}
100+
101+
/// Parses a given source buffer into a `Syntax` tree in Json serialization
102+
/// format by querying SourceKitd service. This function isn't thread safe.
103+
/// - Parameter source: The source buffer you wish to parse.
104+
/// - Returns: The syntax tree in Json format string.
105+
@available(swift, deprecated: 5.0, message: "use parse(_:into:) with a file instead")
106+
public static func parse(source: String) throws -> String {
107+
let content = SourceFile(
108+
name: "foo", contentKey: .key_SourceText, contentValue: source
109+
)
110+
return try parse(content, into: .jsonString)
111+
}
112+
}
113+
114+
// MARK: Internals
115+
116+
extension SwiftLang {
117+
/// The format to serialize the syntax tree in.
118+
public struct SyntaxTreeFormat<Tree> {
119+
/// Value for EditorOpen's key_SyntaxTreeSerializationFormat key.
120+
fileprivate let kind: SourceKitdUID
121+
122+
/// Extracts the syntax tree from the response dictionary and converts it
123+
/// into a value of type Tree.
124+
fileprivate let makeTree: (SourceKitdResponse.Dictionary) throws -> Tree
125+
126+
/// Serialize the syntax tree into a JSON string. For backwards
127+
/// compatibility only.
128+
fileprivate static var jsonString: SyntaxTreeFormat<String> {
129+
return .init(kind: .kind_SyntaxTreeSerializationJSON) { dict in
130+
dict.getString(syntaxTreeKey)
131+
}
132+
}
133+
}
134+
}
135+
136+
extension SwiftLang {
137+
fileprivate struct SourceFile {
138+
let name: String
139+
let contentKey: SourceKitdUID
140+
let contentValue: String
141+
}
142+
143+
/// Parses the SourceFile in question using the provided serialization format.
144+
fileprivate static func parse<Tree>(
145+
_ content: SourceFile, into format: SyntaxTreeFormat<Tree>
146+
) throws -> Tree {
32147
let Service = SourceKitdService()
33148
let Request = SourceKitdRequest(uid: .request_EditorOpen)
34-
if isURL {
35-
Request.addParameter(.key_SourceFile, value: content)
36-
} else {
37-
Request.addParameter(.key_SourceText, value: content)
38-
}
39-
Request.addParameter(.key_Name, value: name)
149+
150+
Request.addParameter(content.contentKey, value: content.contentValue)
151+
Request.addParameter(.key_Name, value: content.name)
152+
40153
Request.addParameter(.key_SyntaxTreeTransferMode,
41154
value: .kind_SyntaxTreeFull)
155+
Request.addParameter(.key_SyntaxTreeSerializationFormat,
156+
value: format.kind)
42157
Request.addParameter(.key_EnableSyntaxMap, value: 0)
43158
Request.addParameter(.key_EnableStructure, value: 0)
44159
Request.addParameter(.key_SyntacticOnly, value: 1)
@@ -50,28 +165,19 @@ public class SwiftLang {
50165
}
51166

52167
let CloseReq = SourceKitdRequest(uid: .request_EditorClose)
53-
CloseReq.addParameter(.key_Name, value: name)
168+
CloseReq.addParameter(.key_Name, value: content.name)
54169
let CloseResp = Service.sendSyn(request: CloseReq)
55170
if CloseResp.isError {
56171
throw SourceKitdError.EditorCloseError(message: CloseResp.description)
57172
}
58-
return Resp.value.getString(.key_SerializedSyntaxTree)
59-
}
60-
61-
/// Parses the Swift file at the provided URL into a `Syntax` tree in Json
62-
/// serialization format by querying SourceKitd service. This function isn't
63-
/// thread safe.
64-
/// - Parameter url: The URL you wish to parse.
65-
/// - Returns: The syntax tree in Json format string.
66-
public static func parse(path: String) throws -> String {
67-
return try parse(content: path, name: path, isURL: true)
173+
return try format.makeTree(Resp.value)
68174
}
175+
}
69176

70-
/// Parses a given source buffer into a `Syntax` tree in Json serialization
71-
/// format by querying SourceKitd service. This function isn't thread safe.
72-
/// - Parameter source: The source buffer you wish to parse.
73-
/// - Returns: The syntax tree in Json format string.
74-
public static func parse(source: String) throws -> String {
75-
return try parse(content: source, name: "foo", isURL: false)
177+
extension SwiftLang.SourceFile {
178+
init(path: String) {
179+
self.init(name: path, contentKey: .key_SourceFile, contentValue: path)
76180
}
77181
}
182+
183+
private let syntaxTreeKey = SourceKitdUID.key_SerializedSyntaxTree

0 commit comments

Comments
 (0)