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