-
Notifications
You must be signed in to change notification settings - Fork 10.5k
/
Copy pathSyntax.swift
422 lines (360 loc) · 13.7 KB
/
Syntax.swift
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
//===-------------------- Syntax.swift - Syntax Protocol ------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import Foundation
/// A Syntax node represents a tree of nodes with tokens at the leaves.
/// Each node has accessors for its known children, and allows efficient
/// iteration over the children through its `children` property.
public protocol Syntax:
CustomStringConvertible, TextOutputStreamable {}
internal protocol _SyntaxBase: Syntax {
/// The type of sequence containing the indices of present children.
typealias PresentChildIndicesSequence =
LazyFilterSequence<Range<Int>>
/// The root of the tree this node is currently in.
var _root: SyntaxData { get } // Must be of type SyntaxData
/// The data backing this node.
/// - note: This is unowned, because the reference to the root data keeps it
/// alive. This means there is an implicit relationship -- the data
/// property must be a descendent of the root. This relationship must
/// be preserved in all circumstances where Syntax nodes are created.
var _data: SyntaxData { get }
#if DEBUG
func validate()
#endif
}
extension _SyntaxBase {
public func validate() {
// This is for implementers to override to perform structural validation.
}
}
extension Syntax {
var data: SyntaxData {
guard let base = self as? _SyntaxBase else {
fatalError("only first-class syntax nodes can conform to Syntax")
}
return base._data
}
var _root: SyntaxData {
guard let base = self as? _SyntaxBase else {
fatalError("only first-class syntax nodes can conform to Syntax")
}
return base._root
}
/// Access the raw syntax assuming the node is a Syntax.
var raw: RawSyntax {
return data.raw
}
/// An iterator over children of this node.
public var children: SyntaxChildren {
return SyntaxChildren(node: self)
}
/// The number of children, `present` or `missing`, in this node.
/// This value can be used safely with `child(at:)`.
public var numberOfChildren: Int {
return data.childCaches.count
}
/// Whether or not this node is marked as `present`.
public var isPresent: Bool {
return raw.isPresent
}
/// Whether or not this node is marked as `missing`.
public var isMissing: Bool {
return raw.isMissing
}
/// Whether or not this node represents an Expression.
public var isExpr: Bool {
return raw.kind.isExpr
}
/// Whether or not this node represents a Declaration.
public var isDecl: Bool {
return raw.kind.isDecl
}
/// Whether or not this node represents a Statement.
public var isStmt: Bool {
return raw.kind.isStmt
}
/// Whether or not this node represents a Type.
public var isType: Bool {
return raw.kind.isType
}
/// Whether or not this node represents a Pattern.
public var isPattern: Bool {
return raw.kind.isPattern
}
/// The parent of this syntax node, or `nil` if this node is the root.
public var parent: Syntax? {
guard let parentData = data.parent else { return nil }
return makeSyntax(root: _root, data: parentData)
}
/// The index of this node in the parent's children.
public var indexInParent: Int {
return data.indexInParent
}
/// The absolute position of the starting point of this node. If the first token
/// is with leading trivia, the position points to the start of the leading
/// trivia.
public var position: AbsolutePosition {
return data.position
}
/// The absolute position of the starting point of this node, skipping any
/// leading trivia attached to the first token syntax.
public var positionAfterSkippingLeadingTrivia: AbsolutePosition {
return data.positionAfterSkippingLeadingTrivia
}
/// The absolute position where this node (excluding its trailing trivia)
/// ends.
public var endPosition: AbsolutePosition {
return data.endPosition
}
/// The absolute position where this node's trailing trivia ends
public var endPositionAfterTrailingTrivia: AbsolutePosition {
return data.endPositionAfterTrailingTrivia
}
/// The textual byte length of this node including leading and trailing trivia.
public var byteSize: Int {
return totalLength.utf8Length
}
/// The length this node takes up spelled out in the source, excluding its
/// leading or trailing trivia.
public var contentLength: SourceLength {
return raw.contentLength
}
/// The leading trivia of this syntax node. Leading trivia is attached to
/// the first token syntax contained by this node. Without such token, this
/// property will return nil.
public var leadingTrivia: Trivia? {
return raw.leadingTrivia
}
/// The trailing trivia of this syntax node. Trailing trivia is attached to
/// the last token syntax contained by this node. Without such token, this
/// property will return nil.
public var trailingTrivia: Trivia? {
return raw.trailingTrivia
}
/// The length this node's leading trivia takes up spelled out in source.
public var leadingTriviaLength: SourceLength {
return raw.leadingTriviaLength
}
/// The length this node's trailing trivia takes up spelled out in source.
public var trailingTriviaLength: SourceLength {
return raw.trailingTriviaLength
}
/// The length of this node including all of its trivia.
public var totalLength: SourceLength {
return raw.totalLength
}
/// When isImplicit is true, the syntax node doesn't include any
/// underlying tokens, e.g. an empty CodeBlockItemList.
public var isImplicit: Bool {
return leadingTrivia == nil
}
/// The textual byte length of this node exluding leading and trailing trivia.
public var byteSizeAfterTrimmingTrivia: Int {
return contentLength.utf8Length
}
/// The root of the tree in which this node resides.
public var root: Syntax {
return makeSyntax(root: _root, data: _root)
}
/// The sequence of indices that correspond to child nodes that are not
/// missing.
///
/// This property is an implementation detail of `SyntaxChildren`.
internal var presentChildIndices: _SyntaxBase.PresentChildIndicesSequence {
return raw.layout.indices.lazy.filter {
self.raw.layout[$0]?.isPresent == true
}
}
/// Gets the child at the provided index in this node's children.
/// - Parameter index: The index of the child node you're looking for.
/// - Returns: A Syntax node for the provided child, or `nil` if there
/// is not a child at that index in the node.
public func child(at index: Int) -> Syntax? {
guard raw.layout.indices.contains(index) else { return nil }
guard let childData = data.cachedChild(at: index) else { return nil }
return makeSyntax(root: _root, data: childData)
}
/// A source-accurate description of this node.
public var description: String {
var s = ""
self.write(to: &s)
return s
}
/// Prints the raw value of this node to the provided stream.
/// - Parameter stream: The stream to which to print the raw tree.
public func write<Target>(to target: inout Target)
where Target: TextOutputStream {
data.raw.write(to: &target)
}
/// The starting location, in the provided file, of this Syntax node.
/// - Parameters:
/// - file: The file URL this node resides in.
/// - afterLeadingTrivia: Whether to skip leading trivia when getting
/// the node's location. Defaults to `true`.
public func startLocation(
in file: URL,
afterLeadingTrivia: Bool = true
) -> SourceLocation {
let pos = afterLeadingTrivia ?
data.position :
data.positionAfterSkippingLeadingTrivia
return SourceLocation(file: file.path, position: pos)
}
/// The ending location, in the provided file, of this Syntax node.
/// - Parameters:
/// - file: The file URL this node resides in.
/// - afterTrailingTrivia: Whether to skip trailing trivia when getting
/// the node's location. Defaults to `false`.
public func endLocation(
in file: URL,
afterTrailingTrivia: Bool = false
) -> SourceLocation {
var pos = data.position
pos += raw.leadingTriviaLength
pos += raw.contentLength
if afterTrailingTrivia {
pos += raw.trailingTriviaLength
}
return SourceLocation(file: file.path, position: pos)
}
/// The source range, in the provided file, of this Syntax node.
/// - Parameters:
/// - file: The file URL this node resides in.
/// - afterLeadingTrivia: Whether to skip leading trivia when getting
/// the node's start location. Defaults to `true`.
/// - afterTrailingTrivia: Whether to skip trailing trivia when getting
/// the node's end location. Defaults to `false`.
public func sourceRange(
in file: URL,
afterLeadingTrivia: Bool = true,
afterTrailingTrivia: Bool = false
) -> SourceRange {
let start = startLocation(in: file, afterLeadingTrivia: afterLeadingTrivia)
let end = endLocation(in: file, afterTrailingTrivia: afterTrailingTrivia)
return SourceRange(start: start, end: end)
}
}
/// Determines if two nodes are equal to each other.
public func ==(lhs: Syntax, rhs: Syntax) -> Bool {
return lhs.data === rhs.data
}
/// MARK: - Nodes
/// A Syntax node representing a single token.
public struct TokenSyntax: _SyntaxBase, Hashable {
let _root: SyntaxData
unowned let _data: SyntaxData
/// Creates a Syntax node from the provided root and data.
internal init(root: SyntaxData, data: SyntaxData) {
self._root = root
self._data = data
#if DEBUG
validate()
#endif
}
/// The text of the token as written in the source code.
public var text: String {
return tokenKind.text
}
/// Returns a new TokenSyntax with its kind replaced
/// by the provided token kind.
public func withKind(_ tokenKind: TokenKind) -> TokenSyntax {
guard raw.kind == .token else {
fatalError("TokenSyntax must have token as its raw")
}
let newRaw = RawSyntax(kind: tokenKind, leadingTrivia: raw.leadingTrivia!,
trailingTrivia: raw.trailingTrivia!,
presence: raw.presence)
let (root, newData) = data.replacingSelf(newRaw)
return TokenSyntax(root: root, data: newData)
}
/// Returns a new TokenSyntax with its leading trivia replaced
/// by the provided trivia.
public func withLeadingTrivia(_ leadingTrivia: Trivia) -> TokenSyntax {
guard raw.kind == .token else {
fatalError("TokenSyntax must have token as its raw")
}
let newRaw = RawSyntax(kind: raw.tokenKind!, leadingTrivia: leadingTrivia,
trailingTrivia: raw.trailingTrivia!,
presence: raw.presence)
let (root, newData) = data.replacingSelf(newRaw)
return TokenSyntax(root: root, data: newData)
}
/// Returns a new TokenSyntax with its trailing trivia replaced
/// by the provided trivia.
public func withTrailingTrivia(_ trailingTrivia: Trivia) -> TokenSyntax {
guard raw.kind == .token else {
fatalError("TokenSyntax must have token as its raw")
}
let newRaw = RawSyntax(kind: raw.tokenKind!,
leadingTrivia: raw.leadingTrivia!,
trailingTrivia: trailingTrivia,
presence: raw.presence)
let (root, newData) = data.replacingSelf(newRaw)
return TokenSyntax(root: root, data: newData)
}
/// Returns a new TokenSyntax with its leading trivia removed.
public func withoutLeadingTrivia() -> TokenSyntax {
return withLeadingTrivia([])
}
/// Returns a new TokenSyntax with its trailing trivia removed.
public func withoutTrailingTrivia() -> TokenSyntax {
return withTrailingTrivia([])
}
/// Returns a new TokenSyntax with all trivia removed.
public func withoutTrivia() -> TokenSyntax {
return withoutLeadingTrivia().withoutTrailingTrivia()
}
/// The leading trivia (spaces, newlines, etc.) associated with this token.
public var leadingTrivia: Trivia {
guard raw.kind == .token else {
fatalError("TokenSyntax must have token as its raw")
}
return raw.leadingTrivia!
}
/// The trailing trivia (spaces, newlines, etc.) associated with this token.
public var trailingTrivia: Trivia {
guard raw.kind == .token else {
fatalError("TokenSyntax must have token as its raw")
}
return raw.trailingTrivia!
}
/// The kind of token this node represents.
public var tokenKind: TokenKind {
guard raw.kind == .token else {
fatalError("TokenSyntax must have token as its raw")
}
return raw.tokenKind!
}
/// The length this node takes up spelled out in the source, excluding its
/// leading or trailing trivia.
public var contentLength: SourceLength {
return raw.contentLength
}
/// The length this node's leading trivia takes up spelled out in source.
public var leadingTriviaLength: SourceLength {
return raw.leadingTriviaLength
}
/// The length this node's trailing trivia takes up spelled out in source.
public var trailingTriviaLength: SourceLength {
return raw.trailingTriviaLength
}
/// The length of this node including all of its trivia.
public var totalLength: SourceLength {
return raw.totalLength
}
public static func ==(lhs: TokenSyntax, rhs: TokenSyntax) -> Bool {
return lhs._data === rhs._data
}
public var hashValue: Int {
return ObjectIdentifier(_data).hashValue
}
}