27
27
28
28
open class SyntaxRewriter {
29
29
public init( ) { }
30
+
30
31
% for node in SYNTAX_NODES:
31
32
% if is_visitable ( node) :
33
+ /// Visit a `${node.name}`.
34
+ /// - Parameter node: the node that is being visited
35
+ /// - Returns: the rewritten node
32
36
open func visit( _ node: ${ node. name} ) -> ${ node. base_type} {
33
37
% cast = ( 'as! ' + node. base_type) if node. base_type != 'Syntax' else ''
34
38
return visitChildren ( node) ${ cast}
@@ -37,9 +41,19 @@ open class SyntaxRewriter {
37
41
% end
38
42
% end
39
43
44
+ /// Visit a `TokenSyntax`.
45
+ /// - Parameter node: the node that is being visited
46
+ /// - Returns: the rewritten node
40
47
open func visit( _ token: TokenSyntax ) -> Syntax {
41
48
return token
42
49
}
50
+
51
+ /// Visit a `UnknownSyntax`.
52
+ /// - Parameter node: the node that is being visited
53
+ /// - Returns: the rewritten node or `nil`.
54
+ open func visit( _ node: UnknownSyntax ) -> Syntax {
55
+ return visitChildren ( node)
56
+ }
43
57
44
58
/// The function called before visiting the node and its descendents.
45
59
/// - node: the node we are about to visit.
@@ -49,7 +63,9 @@ open class SyntaxRewriter {
49
63
/// specialized `visit(_:)` methods. Use this instead of those methods if
50
64
/// you intend to dynamically dispatch rewriting behavior.
51
65
/// - note: If this method returns a non-nil result, the specialized
52
- /// `visit(_:)` methods will not be called for this node.
66
+ /// `visit(_:)` methods will not be called for this node and the
67
+ /// visited node will be replaced by the returned node in the
68
+ /// rewritten tree.
53
69
open func visitAny( _ node: Syntax ) -> Syntax ? {
54
70
return nil
55
71
}
@@ -58,44 +74,130 @@ open class SyntaxRewriter {
58
74
/// - node: the node we just finished visiting.
59
75
open func visitPost( _ node: Syntax ) { }
60
76
77
+ /// Visit any Syntax node.
78
+ /// - Parameter node: the node that is being visited
79
+ /// - Returns: the rewritten node
61
80
public func visit( _ node: Syntax ) -> Syntax {
62
- visitPre ( node)
63
- defer { visitPost ( node) }
64
-
65
- // If the global visitor returned non-nil, skip specialized dispatch.
66
- if let newNode = visitAny ( node) {
67
- return newNode
68
- }
81
+ return visit ( node. base. data)
82
+ }
69
83
70
- switch node. raw. kind {
71
- case . token: return visit ( node as! TokenSyntax )
72
84
% for node in SYNTAX_NODES:
73
- % if is_visitable ( node) :
74
- case . ${ node. swift_syntax_kind} : return visit ( node as! ${ node. name} )
85
+ /// Implementation detail of visit(_:). Do not call directly.
86
+ private func visitImpl${ node. name} ( _ data: SyntaxData) - > Syntax {
87
+ % if node. is_base ( ) :
88
+ let node = Unknown ${ node. name} ( data)
89
+ visitPre ( node)
90
+ defer { visitPost ( node) }
91
+ if let newNode = visitAny ( node) { return newNode }
92
+ return visit ( node)
93
+ % else :
94
+ let node = ${ node. name} ( data)
95
+ visitPre ( node)
96
+ defer { visitPost ( node) }
97
+ if let newNode = visitAny ( node) { return newNode }
98
+ return visit ( node)
75
99
% end
100
+ }
101
+
76
102
% end
77
- default : return visitChildren ( node)
103
+
104
+ final func visit( _ data: SyntaxData ) -> Syntax {
105
+ // Create the node types directly instead of going through `makeSyntax()`
106
+ // which has additional cost for casting back and forth from `_SyntaxBase`.
107
+ switch data. raw. kind {
108
+ case . token:
109
+ let node = TokenSyntax ( data)
110
+ visitPre ( node)
111
+ defer { visitPost ( node) }
112
+ if let newNode = visitAny ( node) { return newNode }
113
+ return visit ( node)
114
+ case . unknown:
115
+ let node = UnknownSyntax ( data)
116
+ visitPre ( node)
117
+ defer { visitPost ( node) }
118
+ if let newNode = visitAny ( node) { return newNode }
119
+ return visit ( node)
120
+ // The implementation of every generated case goes into its own function. This
121
+ // circumvents an issue where the compiler allocates stack space for every
122
+ // case statement next to each other in debug builds, causing it to allocate
123
+ // ~50KB per call to this function. rdar://55929175
124
+ % for node in SYNTAX_NODES:
125
+ case . ${ node. swift_syntax_kind} :
126
+ return visitImpl ${ node. name} ( data)
127
+ % end
78
128
}
79
129
}
80
130
81
- func visitChildren( _ nodeS: Syntax ) -> Syntax {
82
- // Visit all children of this node, returning `nil` if child is not
83
- // present. This will ensure that there are always the same number
84
- // of children after transforming.
131
+ final func visitChildren( _ nodeS: Syntax ) -> Syntax {
85
132
let node = nodeS. base
86
- let newLayout = RawSyntaxChildren ( node) . map { ( n: ( RawSyntax ? , AbsoluteSyntaxInfo ) ) -> RawSyntax ? in
87
- let ( raw, info) = n
88
- guard let child = raw else { return nil }
133
+
134
+ // Walk over all children of this node and rewrite them. Don't store any
135
+ // rewritten nodes until the first non-`nil` value is encountered. When this
136
+ // happens, retrieve all previous syntax nodes from the parent node to
137
+ // initialize the new layout. Once we know that we have to rewrite the
138
+ // layout, we need to collect all futher children, regardless of whether
139
+ // they are rewritten or not.
140
+
141
+ // newLayout is nil until the first child node is rewritten and rewritten
142
+ // nodes are being collected.
143
+ var newLayout : ContiguousArray < RawSyntax ? > ?
144
+
145
+ for (i, ( raw, info) ) in RawSyntaxChildren ( node) . enumerated ( ) {
146
+ guard let child = raw else {
147
+ // Node does not exist. If we are collecting rewritten nodes, we need to
148
+ // collect this one as well, otherwise we can ignore it.
149
+ if newLayout != nil {
150
+ newLayout!. append ( nil )
151
+ }
152
+ continue
153
+ }
154
+
155
+ // Build the Syntax node to rewrite
89
156
let absoluteRaw = AbsoluteRawSyntax ( raw: child, info: info)
90
157
let data = SyntaxData ( absoluteRaw, parent: node)
91
- return visit ( makeSyntax ( data) ) . raw
158
+
159
+ let rewritten = visit ( data)
160
+ if rewritten. base. data. absoluteRaw. info. nodeId != info. nodeId {
161
+ // The node was rewritten, let's handle it
162
+ if newLayout == nil {
163
+ // We have not yet collected any previous rewritten nodes. Initialize
164
+ // the new layout with the previous nodes of the parent. This is
165
+ // possible, since we know they were not rewritten.
166
+
167
+ // The below implementation is based on Collection.map but directly
168
+ // reserves enough capacity for the entire layout.
169
+ newLayout = ContiguousArray < RawSyntax ? > ( )
170
+ newLayout!. reserveCapacity ( node. raw. numberOfChildren)
171
+ for j in 0 ..< i {
172
+ newLayout!. append ( node. raw. child ( at: j) )
173
+ }
174
+ }
175
+
176
+ // Now that we know we have a new layout in which we collect rewritten
177
+ // nodes, add it.
178
+ newLayout!. append ( rewritten. raw)
179
+ } else {
180
+ // The node was not changed by the rewriter. Only store it if a previous
181
+ // node has been rewritten and we are collecting a rewritten layout.
182
+ if newLayout != nil {
183
+ newLayout!. append ( raw)
184
+ }
185
+ }
92
186
}
93
187
94
- // Sanity check, ensure the new children are the same length.
95
- assert ( newLayout. count == node. raw. numberOfChildren)
188
+ if let newLayout = newLayout {
189
+ // A child node was rewritten. Build the updated node.
190
+
191
+ // Sanity check, ensure the new children are the same length.
192
+ assert ( newLayout. count == node. raw. numberOfChildren)
193
+
194
+ let newRaw = node. raw. replacingLayout ( Array ( newLayout) )
195
+ return makeSyntax ( . forRoot( newRaw) )
196
+ } else {
197
+ // No child node was rewritten. So no need to change this node as well.
198
+ return nodeS
199
+ }
96
200
97
- let newRaw = node. raw. replacingLayout ( newLayout)
98
- return makeSyntax ( . forRoot( newRaw) )
99
201
}
100
202
}
101
203
0 commit comments