@@ -89,83 +89,34 @@ extension Parser.Lookahead {
8989 _ spec3: TokenSpec ,
9090 recursionDepth: Int = 1
9191 ) -> RecoveryConsumptionHandle ? {
92- if recursionDepth > 10 {
93- // `canRecoverTo` calls itself recursively if it finds a nested opening token, eg. when calling `canRecoverTo` on
94- // `{{{`. To avoid stack overflowing, limit the number of nested `canRecoverTo` calls we make. Since returning a
95- // recovery handle from this function only improves error recovery but is not necessary for correctness, bailing
96- // from recovery is safe.
97- // The value 10 was chosen fairly arbitrarily. It seems unlikely that we get useful recovery if we find more than
98- // 10 nested open and closing delimiters.
99- return nil
100- }
10192 #if SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION
10293 if shouldRecordAlternativeTokenChoices {
10394 recordAlternativeTokenChoice ( for: self . currentToken, choices: [ spec1, spec2, spec3] )
10495 }
10596 #endif
106- let initialTokensConsumed = self . tokensConsumed
10797
108- let recoveryPrecedence = min ( spec1. recoveryPrecedence, spec2. recoveryPrecedence, spec3. recoveryPrecedence)
109- let shouldSkipOverNewlines =
110- recoveryPrecedence. shouldSkipOverNewlines && spec1. allowAtStartOfLine && spec2. allowAtStartOfLine
111- && spec3. allowAtStartOfLine
112-
113- while !self . at ( . endOfFile) {
114- if !shouldSkipOverNewlines, self . atStartOfLine {
115- break
116- }
117- let matchedSpec : TokenSpec ?
118- switch self . currentToken {
119- case spec1:
120- matchedSpec = spec1
121- case spec2:
122- matchedSpec = spec2
123- case spec3:
124- matchedSpec = spec3
125- default :
126- matchedSpec = nil
127- }
128- if let matchedSpec {
129- return RecoveryConsumptionHandle (
130- unexpectedTokens: self . tokensConsumed - initialTokensConsumed,
131- tokenConsumptionHandle: TokenConsumptionHandle ( spec: matchedSpec)
132- )
133- }
134- let currentTokenPrecedence = TokenPrecedence ( self . currentToken)
135- if currentTokenPrecedence >= recoveryPrecedence {
136- break
137- }
138- if let closingDelimiter = currentTokenPrecedence. closingTokenKind {
139- let closingDelimiterSpec = TokenSpec ( closingDelimiter)
140- let canCloseAtSameLine : Int ? = self . withLookahead { lookahead in
141- var tokensToSkip = 0
142- while !lookahead. at ( . endOfFile) , !lookahead. currentToken. isAtStartOfLine {
143- tokensToSkip += 1
144- if lookahead. at ( closingDelimiterSpec) {
145- return tokensToSkip
146- } else {
147- lookahead. consumeAnyToken ( )
148- }
98+ let result = canRecoverToImpl (
99+ recoveryPrecedence: min ( spec1. recoveryPrecedence, spec2. recoveryPrecedence, spec3. recoveryPrecedence) ,
100+ recursionDepth: recursionDepth,
101+ matchesSpec: { lookahead -> ( TokenSpec , _) ? in
102+ let match : TokenSpec ? =
103+ switch lookahead. currentToken {
104+ case spec1:
105+ spec1
106+ case spec2:
107+ spec2
108+ case spec3:
109+ spec3
110+ default :
111+ nil
149112 }
113+ guard let match, match. allowAtStartOfLine || !lookahead. atStartOfLine else {
150114 return nil
151115 }
152- if let tokensToSkip = canCloseAtSameLine {
153- for _ in 0 ..< tokensToSkip {
154- self . consumeAnyToken ( )
155- }
156- continue
157- }
158- self . consumeAnyToken ( )
159- guard self . canRecoverTo ( closingDelimiterSpec, recursionDepth: recursionDepth + 1 ) != nil else {
160- continue
161- }
162- self . eat ( closingDelimiterSpec)
163- } else {
164- self . consumeAnyToken ( )
116+ return ( match, TokenConsumptionHandle ( spec: match) )
165117 }
166- }
167-
168- return nil
118+ )
119+ return result? . handle
169120 }
170121
171122 /// Checks if we can reach a token in `subset` by skipping tokens that have
@@ -182,7 +133,6 @@ extension Parser.Lookahead {
182133 recordAlternativeTokenChoice ( for: self . currentToken, choices: specSet. allCases. map ( \. spec) )
183134 }
184135 #endif
185- let initialTokensConsumed = self . tokensConsumed
186136
187137 if specSet. allCases. isEmpty {
188138 return nil
@@ -192,31 +142,80 @@ extension Parser.Lookahead {
192142 overrideRecoveryPrecedence ?? specSet. allCases. map ( {
193143 return $0. spec. recoveryPrecedence
194144 } ) . min ( ) !
195- var loopProgress = LoopProgressCondition ( )
196- while !self . at ( . endOfFile) && self . hasProgressed ( & loopProgress) {
145+
146+ return self . canRecoverToImpl (
147+ recoveryPrecedence: recoveryPrecedence,
148+ recursionDepth: 1 ,
149+ matchesSpec: { lookahead in
150+ guard let ( specSet, handle) = lookahead. at ( anyIn: specSet) ,
151+ specSet. spec. allowAtStartOfLine || !lookahead. atStartOfLine
152+ else {
153+ return nil
154+ }
155+ return ( specSet, handle)
156+ }
157+ )
158+ }
159+
160+ @inline ( __always)
161+ private mutating func canRecoverToImpl< Match> (
162+ recoveryPrecedence: TokenPrecedence ,
163+ recursionDepth: Int ,
164+ matchesSpec: ( Parser . Lookahead ) -> ( Match , TokenConsumptionHandle ) ?
165+ ) -> ( match: Match , handle: RecoveryConsumptionHandle ) ? {
166+ if recursionDepth > 10 {
167+ // `canRecoverToImpl` calls itself recursively if it finds a nested opening token, eg. when calling `canRecoverTo` on
168+ // `{{{`. To avoid stack overflowing, limit the number of nested `canRecoverTo` calls we make. Since returning a
169+ // recovery handle from this function only improves error recovery but is not necessary for correctness, bailing
170+ // from recovery is safe.
171+ // The value 10 was chosen fairly arbitrarily. It seems unlikely that we get useful recovery if we find more than
172+ // 10 nested open and closing delimiters.
173+ return nil
174+ }
175+ let initialTokensConsumed = self . tokensConsumed
176+
177+ while !self . at ( . endOfFile) {
197178 if !recoveryPrecedence. shouldSkipOverNewlines, self . atStartOfLine {
198179 break
199180 }
200- if let ( kind, handle) = self . at ( anyIn: specSet) {
201- return (
202- kind,
203- RecoveryConsumptionHandle (
204- unexpectedTokens: self . tokensConsumed - initialTokensConsumed,
205- tokenConsumptionHandle: handle
206- )
181+ if let ( matchedSpec, tokenHandle) = matchesSpec ( self ) {
182+ let handle = RecoveryConsumptionHandle (
183+ unexpectedTokens: self . tokensConsumed - initialTokensConsumed,
184+ tokenConsumptionHandle: tokenHandle
207185 )
186+ return ( matchedSpec, handle)
208187 }
209188 let currentTokenPrecedence = TokenPrecedence ( self . currentToken)
210189 if currentTokenPrecedence >= recoveryPrecedence {
211190 break
212191 }
213- self . consumeAnyToken ( )
214192 if let closingDelimiter = currentTokenPrecedence. closingTokenKind {
215193 let closingDelimiterSpec = TokenSpec ( closingDelimiter)
216- guard self . canRecoverTo ( closingDelimiterSpec) != nil else {
217- break
194+ let canCloseAtSameLine : Int ? = self . withLookahead { lookahead in
195+ var tokensToSkip = 0
196+ while !lookahead. at ( . endOfFile) , !lookahead. currentToken. isAtStartOfLine {
197+ tokensToSkip += 1
198+ if lookahead. at ( closingDelimiterSpec) {
199+ return tokensToSkip
200+ } else {
201+ lookahead. consumeAnyToken ( )
202+ }
203+ }
204+ return nil
205+ }
206+ if let tokensToSkip = canCloseAtSameLine {
207+ for _ in 0 ..< tokensToSkip {
208+ self . consumeAnyToken ( )
209+ }
210+ continue
211+ }
212+ self . consumeAnyToken ( )
213+ guard self . canRecoverTo ( closingDelimiterSpec, recursionDepth: recursionDepth + 1 ) != nil else {
214+ continue
218215 }
219216 self . eat ( closingDelimiterSpec)
217+ } else {
218+ self . consumeAnyToken ( )
220219 }
221220 }
222221
0 commit comments