-
Notifications
You must be signed in to change notification settings - Fork 441
/
Copy pathTokenConsumer.swift
393 lines (350 loc) · 12.5 KB
/
TokenConsumer.swift
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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
//===----------------------------------------------------------------------===//
//
// 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 compiler(>=6)
@_spi(RawSyntax) internal import SwiftSyntax
#else
@_spi(RawSyntax) import SwiftSyntax
#endif
/// A type that consumes instances of ``TokenSyntax``.
protocol TokenConsumer {
associatedtype Token
/// The current token syntax being examined by the consumer
var currentToken: Lexer.Lexeme { get }
var swiftVersion: Parser.SwiftVersion { get }
/// The experimental features that have been enabled.
var experimentalFeatures: Parser.ExperimentalFeatures { get }
/// Whether the current token matches the given kind.
mutating func consumeAnyToken() -> Token
/// Consume the current token and change its token kind to `remappedTokenKind`.
mutating func consumeAnyToken(remapping remappedTokenKind: RawTokenKind) -> Token
/// Consumes a given token, or splits the current token into a leading token
/// matching the given token and a trailing token and consumes the leading
/// token.
///
/// <TOKEN> ... -> consumePrefix(<TOK>) -> [ <TOK> ] <EN> ...
mutating func consumePrefix(_ prefix: SyntaxText, as tokenKind: RawTokenKind) -> Token
/// Synthesize a missing token with `kind`.
/// If `text` is not `nil`, use it for the token's text, otherwise use the token's default text.
mutating func missingToken(_ kind: RawTokenKind, text: SyntaxText?) -> Token
/// Return the lexeme that will be parsed next.
func peek() -> Lexer.Lexeme
func lookahead() -> Parser.Lookahead
#if SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION
var shouldRecordAlternativeTokenChoices: Bool { get }
/// When compiled with `SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION`,
/// record alternative tokens that the parser was looking for at the offset of
/// `lexeme`.
///
/// E.g. if at offset 33, we issue an `at(.leftParen)` call, this will record
/// that `.leftParen` is an interesting token at offset 33. This allows the
/// test case mutators to prefer replacing the current token at offset 33 by a
/// left paren, because apparently this would be a code path that the parser
/// is interested in.
mutating func recordAlternativeTokenChoice(for lexeme: Lexer.Lexeme, choices: [TokenSpec])
#endif
}
// MARK: Checking if we are at one specific token (`at`)
/// 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 TokenConsumptionHandle {
/// The kind that is expected to be consumed if the handle is eaten.
var spec: TokenSpec
/// If `true`, the token we should consume should be synthesized as a missing token
/// and no tokens should be consumed.
var tokenIsMissing: Bool = false
}
extension TokenConsumer {
/// Returns whether the current token matches `spec`
@inline(__always)
mutating func at(_ spec: TokenSpec) -> Bool {
#if SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION
if shouldRecordAlternativeTokenChoices {
recordAlternativeTokenChoice(for: self.currentToken, choices: [spec])
}
#endif
return spec ~= self.currentToken
}
/// Returns whether the current token matches one of the two specs.
@inline(__always)
mutating func at(
_ spec1: TokenSpec,
_ spec2: TokenSpec
) -> Bool {
#if SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION
if shouldRecordAlternativeTokenChoices {
recordAlternativeTokenChoice(for: self.currentToken, choices: [spec1, spec2])
}
#endif
switch self.currentToken {
case spec1: return true
case spec2: return true
default: return false
}
}
/// Returns whether the current token matches one of the three specs.
@inline(__always)
mutating func at(
_ spec1: TokenSpec,
_ spec2: TokenSpec,
_ spec3: TokenSpec
) -> Bool {
#if SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION
if shouldRecordAlternativeTokenChoices {
recordAlternativeTokenChoice(for: self.currentToken, choices: [spec1, spec2, spec3])
}
#endif
switch self.currentToken {
case spec1: return true
case spec2: return true
case spec3: return true
default: return false
}
}
/// Returns whether the current token is an operator with the given `name`.
@inline(__always)
mutating func atContextualPunctuator(_ name: SyntaxText) -> Bool {
return self.currentToken.isContextualPunctuator(name)
}
/// Checks whether the parser is currently positioned at any token in `Subset`.
/// If this is the case, return the `Subset` case that the parser is positioned in
/// as well as a handle to consume that token.
@inline(__always)
mutating func at<SpecSet: TokenSpecSet>(
anyIn specSet: SpecSet.Type
) -> (spec: SpecSet, handle: TokenConsumptionHandle)? {
#if SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION
if shouldRecordAlternativeTokenChoices {
recordAlternativeTokenChoice(for: self.currentToken, choices: specSet.allCases.map(\.spec))
}
#endif
if let matchedKind = SpecSet(lexeme: self.currentToken, experimentalFeatures: self.experimentalFeatures) {
precondition(matchedKind.spec ~= self.currentToken)
return (
matchedKind,
TokenConsumptionHandle(spec: matchedKind.spec)
)
}
return nil
}
/// Checks whether the parser's next token is any token in `SpecSet`.
/// If this is the case, return the corresponding `SpecSet` case.
@inline(__always)
func peek<SpecSet: TokenSpecSet>(isAtAnyIn specSet: SpecSet.Type) -> SpecSet? {
guard let matchedKind = SpecSet(lexeme: self.peek(), experimentalFeatures: self.experimentalFeatures) else {
return nil
}
precondition(matchedKind.spec ~= self.peek())
return matchedKind
}
/// Whether the current token’s text starts with the given prefix.
@inline(__always)
mutating func at(prefix: SyntaxText) -> Bool {
return self.currentToken.tokenText.hasPrefix(prefix)
}
/// Whether the current token is at the start of a line.
@inline(__always)
var atStartOfLine: Bool {
return self.currentToken.isAtStartOfLine
}
/// Eat a token that we know we are currently positioned at, based on `at(anyIn:)`.
@inline(__always)
mutating func eat(_ handle: TokenConsumptionHandle) -> Token {
if handle.tokenIsMissing {
return missingToken(handle.spec)
} else {
return eat(handle.spec)
}
}
func withLookahead<T>(_ body: (_: inout Parser.Lookahead) -> T) -> T {
var lookahead = lookahead()
return body(&lookahead)
}
}
extension TokenConsumer {
/// Returns whether the next (peeked) token matches `spec`
@inline(__always)
mutating func peek(isAt spec: TokenSpec) -> Bool {
#if SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION
if shouldRecordAlternativeTokenChoices {
recordAlternativeTokenChoice(for: self.peek(), choices: [spec])
}
#endif
return spec ~= self.peek()
}
/// Returns whether the next (peeked) token matches one of the two specs.
@inline(__always)
mutating func peek(
isAt spec1: TokenSpec,
_ spec2: TokenSpec
) -> Bool {
#if SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION
if shouldRecordAlternativeTokenChoices {
recordAlternativeTokenChoice(for: self.peek(), choices: [spec1, spec2])
}
#endif
switch self.peek() {
case spec1: return true
case spec2: return true
default: return false
}
}
/// Returns whether the next (peeked) token matches one of the three specs.
@inline(__always)
mutating func peek(
isAt spec1: TokenSpec,
_ spec2: TokenSpec,
_ spec3: TokenSpec
) -> Bool {
#if SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION
if shouldRecordAlternativeTokenChoices {
recordAlternativeTokenChoice(for: self.peek(), choices: [spec1, spec2, spec3])
}
#endif
switch self.peek() {
case spec1: return true
case spec2: return true
case spec3: return true
default: return false
}
}
}
// MARK: Consuming tokens (`consume`)
extension TokenConsumer {
/// If the current token matches `spec`, consume the token and return it.
/// Otherwise return `nil`.
@inline(__always)
mutating func consume(if spec: TokenSpec) -> Token? {
if self.at(spec) {
return self.eat(spec)
}
return nil
}
/// If the current token matches one of the specs, consume the token and return it.
/// Otherwise return `nil`.
@inline(__always)
mutating func consume(
if spec1: TokenSpec,
_ spec2: TokenSpec
) -> Token? {
if let token = consume(if: spec1) {
return token
} else if let token = consume(if: spec2) {
return token
} else {
return nil
}
}
/// If the current token matches one of the specs, consume the token and return it.
/// Otherwise return `nil`.
@inline(__always)
mutating func consume(
if spec1: TokenSpec,
_ spec2: TokenSpec,
_ spec3: TokenSpec
) -> Token? {
if let token = consume(if: spec1) {
return token
} else if let token = consume(if: spec2) {
return token
} else if let token = consume(if: spec3) {
return token
} else {
return nil
}
}
/// Consumes and returns the current token is an operator with the given `name`.
@inline(__always)
mutating func consumeIfContextualPunctuator(_ name: SyntaxText, remapping: RawTokenKind? = nil) -> Token? {
if self.atContextualPunctuator(name) {
if let remapping = remapping {
return self.consumeAnyToken(remapping: remapping)
} else {
return self.consumeAnyToken()
}
}
return nil
}
/// If the current token matches `spec1` and is followed by a token that
/// matches `spec2` consume both tokens and return them. Otherwise, return `nil`.
@inline(__always)
mutating func consume(if spec1: TokenSpec, followedBy spec2: TokenSpec) -> (Token, Token)? {
if self.at(spec1) && spec2 ~= self.peek() {
return (consumeAnyToken(), consumeAnyToken())
} else {
return nil
}
}
/// If the current token satisfies `condition1` and the next token satisfies
/// `condition2` consume both tokens and return them.
/// Otherwise, return `nil`.
@inline(__always)
mutating func consume(
if condition1: (Lexer.Lexeme) -> Bool,
followedBy condition2: (Lexer.Lexeme) -> Bool
) -> (Token, Token)? {
if condition1(self.currentToken) && condition2(self.peek()) {
return (consumeAnyToken(), consumeAnyToken())
} else {
return nil
}
}
@inline(__always)
mutating func consume<SpecSet: TokenSpecSet>(ifAnyIn specSet: SpecSet.Type) -> Self.Token? {
if let (_, handle) = self.at(anyIn: specSet) {
return self.eat(handle)
} else {
return nil
}
}
/// If the current token starts with the given prefix, consume the prefis as the given token kind.
///
/// Otherwise, return `nil`.
mutating func consume(ifPrefix prefix: SyntaxText, as tokenKind: RawTokenKind) -> Token? {
if self.at(prefix: prefix) {
return consumePrefix(prefix, as: tokenKind)
} else {
return nil
}
}
}
// MARK: Convenience functions
extension TokenConsumer {
var canHaveParameterSpecifier: Bool {
// The parameter specifiers like `isolated`, `consuming`, `borrowing` are
// also valid identifiers and could be the name of a type. Check whether
// the following token is something that can introduce a type (which,
// thankfully, doesn't overlap with the set of tokens that can continue
// a type production), in which case the current token is interpretable
// as a parameter specifier.
let lexeme = peek()
switch lexeme.rawTokenKind {
case .atSign, .leftParen, .identifier, .leftSquare, .wildcard:
return true
case .keyword:
switch PrepareForKeywordMatch(lexeme) {
case TokenSpec(.inout), TokenSpec(.Any), TokenSpec(.Self), TokenSpec(.var), TokenSpec(.let):
return true
default:
return false
}
case .prefixOperator where lexeme.isContextualPunctuator("~"):
return true
default:
return false
}
}
/// Whether the current token can be a function argument label.
func atArgumentLabel(allowDollarIdentifier: Bool = false) -> Bool {
return self.currentToken.isArgumentLabel(allowDollarIdentifier: allowDollarIdentifier)
}
}