File: Recovery.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (193 lines) | stat: -rw-r--r-- 6,810 bytes parent folder | download
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
  }
}