@@ -89,47 +89,96 @@ extension Parser.Lookahead {
8989 _ spec3: TokenSpec ,
9090 recursionDepth: Int = 1
9191 ) -> RecoveryConsumptionHandle ? {
92+ #if SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION
93+ if shouldRecordAlternativeTokenChoices {
94+ recordAlternativeTokenChoice ( for: self . currentToken, choices: [ spec1, spec2, spec3] )
95+ }
96+ #endif
97+
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
112+ }
113+ guard let match else { return nil }
114+ return ( match, match)
115+ }
116+ )
117+ return result? . handle
118+ }
119+
120+ /// Checks if we can reach a token in `subset` by skipping tokens that have
121+ /// a precedence that have a lower ``TokenPrecedence`` than the minimum
122+ /// precedence of a token in that subset.
123+ /// If so, return the token that we can recover to and a handle that can be
124+ /// used to consume the unexpected tokens and the token we recovered to.
125+ mutating func canRecoverTo< SpecSet: TokenSpecSet > (
126+ anyIn specSet: SpecSet . Type ,
127+ overrideRecoveryPrecedence: TokenPrecedence ? = nil
128+ ) -> ( match: SpecSet , handle: RecoveryConsumptionHandle ) ? {
129+ #if SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION
130+ if shouldRecordAlternativeTokenChoices {
131+ recordAlternativeTokenChoice ( for: self . currentToken, choices: specSet. allCases. map ( \. spec) )
132+ }
133+ #endif
134+
135+ if specSet. allCases. isEmpty {
136+ return nil
137+ }
138+
139+ let recoveryPrecedence =
140+ overrideRecoveryPrecedence ?? specSet. allCases. map ( {
141+ return $0. spec. recoveryPrecedence
142+ } ) . min ( ) !
143+
144+ return self . canRecoverToImpl (
145+ recoveryPrecedence: recoveryPrecedence,
146+ recursionDepth: 1 ,
147+ matchesSpec: { lookahead in
148+ guard let ( specSet, _) = lookahead. at ( anyIn: specSet) else { return nil }
149+ return ( specSet, specSet. spec)
150+ }
151+ )
152+ }
153+
154+ @inline ( __always)
155+ private mutating func canRecoverToImpl< Match> (
156+ recoveryPrecedence: TokenPrecedence ,
157+ recursionDepth: Int ,
158+ matchesSpec: ( inout Parser . Lookahead ) -> ( Match , TokenSpec ) ?
159+ ) -> ( match: Match , handle: RecoveryConsumptionHandle ) ? {
92160 if recursionDepth > 10 {
93- // `canRecoverTo ` calls itself recursively if it finds a nested opening token, eg. when calling `canRecoverTo` on
161+ // `canRecoverToImpl ` calls itself recursively if it finds a nested opening token, eg. when calling `canRecoverTo` on
94162 // `{{{`. To avoid stack overflowing, limit the number of nested `canRecoverTo` calls we make. Since returning a
95163 // recovery handle from this function only improves error recovery but is not necessary for correctness, bailing
96164 // from recovery is safe.
97165 // The value 10 was chosen fairly arbitrarily. It seems unlikely that we get useful recovery if we find more than
98166 // 10 nested open and closing delimiters.
99167 return nil
100168 }
101- #if SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION
102- if shouldRecordAlternativeTokenChoices {
103- recordAlternativeTokenChoice ( for: self . currentToken, choices: [ spec1, spec2, spec3] )
104- }
105- #endif
106169 let initialTokensConsumed = self . tokensConsumed
107170
108- let recoveryPrecedence = min ( spec1. recoveryPrecedence, spec2. recoveryPrecedence, spec3. recoveryPrecedence)
109- let shouldSkipOverNewlines =
110- recoveryPrecedence. shouldSkipOverNewlines && spec1. allowAtStartOfLine && spec2. allowAtStartOfLine
111- && spec3. allowAtStartOfLine
112-
113171 while !self . at ( . endOfFile) {
114- if !shouldSkipOverNewlines, self . atStartOfLine {
172+ if !recoveryPrecedence . shouldSkipOverNewlines, self . atStartOfLine {
115173 break
116174 }
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 (
175+ if let ( matchedSpec, tokenSpec) = matchesSpec ( & self ) {
176+ if !tokenSpec. allowAtStartOfLine && self . atStartOfLine { break }
177+ let handle = RecoveryConsumptionHandle (
130178 unexpectedTokens: self . tokensConsumed - initialTokensConsumed,
131- tokenConsumptionHandle: TokenConsumptionHandle ( spec: matchedSpec )
179+ tokenConsumptionHandle: TokenConsumptionHandle ( spec: tokenSpec )
132180 )
181+ return ( matchedSpec, handle)
133182 }
134183 let currentTokenPrecedence = TokenPrecedence ( self . currentToken)
135184 if currentTokenPrecedence >= recoveryPrecedence {
@@ -167,59 +216,4 @@ extension Parser.Lookahead {
167216
168217 return nil
169218 }
170-
171- /// Checks if we can reach a token in `subset` by skipping tokens that have
172- /// a precedence that have a lower ``TokenPrecedence`` than the minimum
173- /// precedence of a token in that subset.
174- /// If so, return the token that we can recover to and a handle that can be
175- /// used to consume the unexpected tokens and the token we recovered to.
176- mutating func canRecoverTo< SpecSet: TokenSpecSet > (
177- anyIn specSet: SpecSet . Type ,
178- overrideRecoveryPrecedence: TokenPrecedence ? = nil
179- ) -> ( match: SpecSet , handle: RecoveryConsumptionHandle ) ? {
180- #if SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION
181- if shouldRecordAlternativeTokenChoices {
182- recordAlternativeTokenChoice ( for: self . currentToken, choices: specSet. allCases. map ( \. spec) )
183- }
184- #endif
185- let initialTokensConsumed = self . tokensConsumed
186-
187- if specSet. allCases. isEmpty {
188- return nil
189- }
190-
191- let recoveryPrecedence =
192- overrideRecoveryPrecedence ?? specSet. allCases. map ( {
193- return $0. spec. recoveryPrecedence
194- } ) . min ( ) !
195- var loopProgress = LoopProgressCondition ( )
196- while !self . at ( . endOfFile) && self . hasProgressed ( & loopProgress) {
197- if !recoveryPrecedence. shouldSkipOverNewlines, self . atStartOfLine {
198- break
199- }
200- if let ( kind, handle) = self . at ( anyIn: specSet) {
201- return (
202- kind,
203- RecoveryConsumptionHandle (
204- unexpectedTokens: self . tokensConsumed - initialTokensConsumed,
205- tokenConsumptionHandle: handle
206- )
207- )
208- }
209- let currentTokenPrecedence = TokenPrecedence ( self . currentToken)
210- if currentTokenPrecedence >= recoveryPrecedence {
211- break
212- }
213- self . consumeAnyToken ( )
214- if let closingDelimiter = currentTokenPrecedence. closingTokenKind {
215- let closingDelimiterSpec = TokenSpec ( closingDelimiter)
216- guard self . canRecoverTo ( closingDelimiterSpec) != nil else {
217- break
218- }
219- self . eat ( closingDelimiterSpec)
220- }
221- }
222-
223- return nil
224- }
225219}
0 commit comments