13
13
@_spi ( RawSyntax) import SwiftSyntax
14
14
15
15
extension Parser {
16
- private enum IfConfigContinuationClauseStartKeyword : TokenSpecSet {
16
+ private enum PoundIfDirectiveKeywords : TokenSpecSet {
17
+ case poundIfKeyword
17
18
case poundElseifKeyword
18
19
case poundElseKeyword
20
+ case pound
19
21
20
22
var spec : TokenSpec {
21
23
switch self {
24
+ case . poundIfKeyword: return . poundIfKeyword
22
25
case . poundElseifKeyword: return . poundElseifKeyword
23
26
case . poundElseKeyword: return . poundElseKeyword
27
+ case . pound: return TokenSpec ( . pound, recoveryPrecedence: . openingPoundIf)
24
28
}
25
29
}
26
30
27
31
init ? ( lexeme: Lexer . Lexeme ) {
28
32
switch PrepareForKeywordMatch ( lexeme) {
33
+ case TokenSpec ( . poundIfKeyword) : self = . poundIfKeyword
29
34
case TokenSpec ( . poundElseifKeyword) : self = . poundElseifKeyword
30
35
case TokenSpec ( . poundElseKeyword) : self = . poundElseKeyword
36
+ case TokenSpec ( . pound) : self = . pound
31
37
default : return nil
32
38
}
33
39
}
@@ -103,32 +109,72 @@ extension Parser {
103
109
do {
104
110
var firstIteration = true
105
111
var loopProgress = LoopProgressCondition ( )
106
- while let poundIfHandle = firstIteration ? self . canRecoverTo ( . poundIfKeyword) : self . canRecoverTo ( anyIn: IfConfigContinuationClauseStartKeyword . self) ? . handle,
107
- loopProgress. evaluate ( self . currentToken)
108
- {
109
- var ( unexpectedBeforePoundIf, poundIf) = self . eat ( poundIfHandle)
110
- firstIteration = false
111
- // Parse the condition.
112
+ LOOP: while let ( match, handle) = self . canRecoverTo ( anyIn: PoundIfDirectiveKeywords . self) , loopProgress. evaluate ( self . currentToken) {
113
+ var unexpectedBeforePound : RawUnexpectedNodesSyntax ?
114
+ var poundKeyword : RawTokenSyntax
112
115
let condition : RawExprSyntax ?
113
- switch poundIf. tokenKind {
114
- case . poundIfKeyword, . poundElseifKeyword:
116
+ var atElifTypo : Bool {
117
+ guard self . at ( TokenSpec ( . pound) ) , self . currentToken. trailingTriviaText. isEmpty else {
118
+ return false
119
+ }
120
+ var lookahead = self . lookahead ( ) // consume `#`
121
+ lookahead. consumeAnyToken ( )
122
+ guard lookahead. at ( TokenSpec ( . identifier, allowAtStartOfLine: false ) ) , lookahead. currentToken. tokenText == " elif " , lookahead. currentToken. leadingTriviaText. isEmpty else {
123
+ return false // `#` and `elif` must not be separated by trivia
124
+ }
125
+ lookahead. consumeAnyToken ( ) // consume `elif`
126
+ // We are only at a `elif` typo if it’s followed by an identifier for the condition.
127
+ // `#elif` or `#elif(…)` could be macro invocations.
128
+ return lookahead. at ( TokenSpec ( . identifier, allowAtStartOfLine: false ) )
129
+ }
130
+
131
+ switch match {
132
+ case . poundIfKeyword:
133
+ if !firstIteration {
134
+ break LOOP
135
+ }
136
+ firstIteration = false
137
+ ( unexpectedBeforePound, poundKeyword) = self . eat ( handle)
138
+ condition = RawExprSyntax ( self . parseSequenceExpression ( . basic, forDirective: true ) )
139
+ case . poundElseifKeyword:
140
+ if firstIteration {
141
+ break LOOP
142
+ }
143
+ ( unexpectedBeforePound, poundKeyword) = self . eat ( handle)
115
144
condition = RawExprSyntax ( self . parseSequenceExpression ( . basic, forDirective: true ) )
116
145
case . poundElseKeyword:
146
+ if firstIteration {
147
+ break LOOP
148
+ }
149
+ ( unexpectedBeforePound, poundKeyword) = self . eat ( handle)
117
150
if let ifToken = self . consume ( if: . init( . if, allowAtStartOfLine: false ) ) {
118
- unexpectedBeforePoundIf = RawUnexpectedNodesSyntax ( combining: unexpectedBeforePoundIf , poundIf , ifToken, arena: self . arena)
119
- poundIf = self . missingToken ( . poundElseifKeyword)
151
+ unexpectedBeforePound = RawUnexpectedNodesSyntax ( combining: unexpectedBeforePound , poundKeyword , ifToken, arena: self . arena)
152
+ poundKeyword = self . missingToken ( . poundElseifKeyword)
120
153
condition = RawExprSyntax ( self . parseSequenceExpression ( . basic, forDirective: true ) )
121
154
} else {
122
155
condition = nil
123
156
}
124
- default :
125
- preconditionFailure ( " The loop condition should guarantee that we are at one of these tokens " )
157
+ case . pound:
158
+ if firstIteration {
159
+ break LOOP
160
+ }
161
+ if atElifTypo {
162
+ ( unexpectedBeforePound, poundKeyword) = self . eat ( handle)
163
+ guard let elif = self . consume ( if: TokenSpec ( . identifier, allowAtStartOfLine: false ) ) else {
164
+ preconditionFailure ( " The current token should be an identifier, guaranteed by the `atElifTypo` check. " )
165
+ }
166
+ unexpectedBeforePound = RawUnexpectedNodesSyntax ( combining: unexpectedBeforePound, poundKeyword, elif, arena: self . arena)
167
+ poundKeyword = self . missingToken ( . poundElseifKeyword)
168
+ condition = RawExprSyntax ( self . parseSequenceExpression ( . basic, forDirective: true ) )
169
+ } else {
170
+ break LOOP
171
+ }
126
172
}
127
173
128
174
var elements = [ Element] ( )
129
175
do {
130
176
var elementsProgress = LoopProgressCondition ( )
131
- while !self . at ( . eof) && !self . at ( . poundElseKeyword, . poundElseifKeyword, . poundEndifKeyword) && elementsProgress. evaluate ( currentToken) {
177
+ while !self . at ( . eof) && !self . at ( . poundElseKeyword, . poundElseifKeyword, . poundEndifKeyword) && !atElifTypo && elementsProgress. evaluate ( currentToken) {
132
178
let newItemAtStartOfLine = self . currentToken. isAtStartOfLine
133
179
guard let element = parseElement ( & self , elements. isEmpty) , !element. isEmpty else {
134
180
break
@@ -142,8 +188,8 @@ extension Parser {
142
188
143
189
clauses. append (
144
190
RawIfConfigClauseSyntax (
145
- unexpectedBeforePoundIf ,
146
- poundKeyword: poundIf ,
191
+ unexpectedBeforePound ,
192
+ poundKeyword: poundKeyword ,
147
193
condition: condition,
148
194
elements: syntax ( & self , elements) ,
149
195
arena: self . arena
0 commit comments