1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#if swift(>=6)
@_spi(RawSyntax) internal import SwiftSyntax
#else
@_spi(RawSyntax) import SwiftSyntax
#endif
// MARK: Lookahead
/// After calling `consume(ifAnyFrom:)` we know which token we are positioned
/// at based on that function's return type. This handle allows consuming that
/// token.
struct RecoveryConsumptionHandle {
var unexpectedTokens: Int
var tokenConsumptionHandle: TokenConsumptionHandle
fileprivate init(unexpectedTokens: Int, tokenConsumptionHandle: TokenConsumptionHandle) {
self.unexpectedTokens = unexpectedTokens
self.tokenConsumptionHandle = tokenConsumptionHandle
}
/// A `RecoveryConsumptionHandle` that doesn't skip over any unexpected tokens
/// and consumes a token matching `spec`.
static func constant(_ spec: TokenSpec) -> RecoveryConsumptionHandle {
return RecoveryConsumptionHandle(
unexpectedTokens: 0,
tokenConsumptionHandle: TokenConsumptionHandle(spec: spec)
)
}
/// A `RecoveryConsumptionHandle` that doesn't skip over any unexpected tokens
/// and consumes `handle`.
static func noRecovery(_ handle: TokenConsumptionHandle) -> RecoveryConsumptionHandle {
return RecoveryConsumptionHandle(
unexpectedTokens: 0,
tokenConsumptionHandle: handle
)
}
/// A `RecoveryConsumptionHandle` that will not eat any tokens but instead
/// synthesize a missing token of kind `token`.
static func missing(_ spec: TokenSpec) -> RecoveryConsumptionHandle {
return RecoveryConsumptionHandle(
unexpectedTokens: 0,
tokenConsumptionHandle: TokenConsumptionHandle(spec: spec, tokenIsMissing: true)
)
}
}
extension Parser.Lookahead {
/// See `canRecoverTo` that takes 3 specs.
mutating func canRecoverTo(
_ spec: TokenSpec
) -> RecoveryConsumptionHandle? {
return canRecoverTo(spec, spec, spec)
}
/// See `canRecoverTo` that takes 3 specs.
mutating func canRecoverTo(
_ spec1: TokenSpec,
_ spec2: TokenSpec
) -> RecoveryConsumptionHandle? {
return canRecoverTo(spec1, spec2, spec1)
}
/// Tries eating tokens until it finds a token that matches `spec1`, `spec2` or `spec3`
/// without skipping tokens that have a precedence that's higher than the
/// lowest precedence in the expected kinds. If it found a token in this way,
/// returns `true`, otherwise `false`.
/// If this method returns `true`, the parser probably wants to consume the
/// tokens this lookahead skipped over to find `kind` by consuming
/// `lookahead.tokensConsumed` as unexpected.
mutating func canRecoverTo(
_ spec1: TokenSpec,
_ spec2: TokenSpec,
_ spec3: TokenSpec
) -> RecoveryConsumptionHandle? {
#if SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION
if shouldRecordAlternativeTokenChoices {
recordAlternativeTokenChoice(for: self.currentToken, choices: [spec1, spec2, spec3])
}
#endif
let initialTokensConsumed = self.tokensConsumed
let recoveryPrecedence = min(spec1.recoveryPrecedence, spec2.recoveryPrecedence, spec3.recoveryPrecedence)
let shouldSkipOverNewlines =
recoveryPrecedence.shouldSkipOverNewlines && spec1.allowAtStartOfLine && spec2.allowAtStartOfLine
&& spec3.allowAtStartOfLine
while !self.at(.endOfFile) {
if !shouldSkipOverNewlines, self.atStartOfLine {
break
}
let matchedSpec: TokenSpec?
switch self.currentToken {
case spec1:
matchedSpec = spec1
case spec2:
matchedSpec = spec2
case spec3:
matchedSpec = spec3
default:
matchedSpec = nil
}
if let matchedSpec {
return RecoveryConsumptionHandle(
unexpectedTokens: self.tokensConsumed - initialTokensConsumed,
tokenConsumptionHandle: TokenConsumptionHandle(spec: matchedSpec)
)
}
let currentTokenPrecedence = TokenPrecedence(self.currentToken)
if currentTokenPrecedence >= recoveryPrecedence {
break
}
self.consumeAnyToken()
if let closingDelimiter = currentTokenPrecedence.closingTokenKind {
let closingDelimiterSpec = TokenSpec(closingDelimiter)
guard self.canRecoverTo(closingDelimiterSpec) != nil else {
break
}
self.eat(closingDelimiterSpec)
}
}
return nil
}
/// Checks if we can reach a token in `subset` by skipping tokens that have
/// a precedence that have a lower ``TokenPrecedence`` than the minimum
/// precedence of a token in that subset.
/// If so, return the token that we can recover to and a handle that can be
/// used to consume the unexpected tokens and the token we recovered to.
mutating func canRecoverTo<SpecSet: TokenSpecSet>(
anyIn specSet: SpecSet.Type,
overrideRecoveryPrecedence: TokenPrecedence? = nil
) -> (match: SpecSet, handle: RecoveryConsumptionHandle)? {
#if SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION
if shouldRecordAlternativeTokenChoices {
recordAlternativeTokenChoice(for: self.currentToken, choices: specSet.allCases.map(\.spec))
}
#endif
let initialTokensConsumed = self.tokensConsumed
if specSet.allCases.isEmpty {
return nil
}
let recoveryPrecedence =
overrideRecoveryPrecedence ?? specSet.allCases.map({
return $0.spec.recoveryPrecedence
}).min()!
var loopProgress = LoopProgressCondition()
while !self.at(.endOfFile) && self.hasProgressed(&loopProgress) {
if !recoveryPrecedence.shouldSkipOverNewlines, self.atStartOfLine {
break
}
if let (kind, handle) = self.at(anyIn: specSet) {
return (
kind,
RecoveryConsumptionHandle(
unexpectedTokens: self.tokensConsumed - initialTokensConsumed,
tokenConsumptionHandle: handle
)
)
}
let currentTokenPrecedence = TokenPrecedence(self.currentToken)
if currentTokenPrecedence >= recoveryPrecedence {
break
}
self.consumeAnyToken()
if let closingDelimiter = currentTokenPrecedence.closingTokenKind {
let closingDelimiterSpec = TokenSpec(closingDelimiter)
guard self.canRecoverTo(closingDelimiterSpec) != nil else {
break
}
self.eat(closingDelimiterSpec)
}
}
return nil
}
}
|