Skip to content

[Recovery 6/7] Support recovery in front of declarations #747

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 34 commits into from
Sep 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
25d193d
Implement recovery in `eat`
ahoppen Sep 8, 2022
172f326
Eliminate `eat` from the parser
ahoppen Sep 8, 2022
70eff85
Sink a few functions from Parser down to TokenConsumer
ahoppen Sep 3, 2022
0cc2195
Remove `parser` as argument to the `where` condition of `consume(if:)`
ahoppen Sep 8, 2022
38422e0
Sink some more parser primitives to TokenConsumer
ahoppen Sep 8, 2022
2ef1b84
Fix issue that rejected parses with `any` as first paramter label
ahoppen Sep 7, 2022
cbcc3a1
Some trivial similifications of `self.at` to `consume(if:)` or `at(an…
ahoppen Sep 8, 2022
f8c24ca
Rename expectWithoutLookahead to expectWithoutRecovery
ahoppen Sep 8, 2022
41dd9d2
Change `consume(ifAny:)` to take an array instead of variadig arguments
ahoppen Sep 8, 2022
6e9d9b7
Rename consumeIdentifer to expectIdentifier
ahoppen Sep 8, 2022
b536300
Remove consumeInteger
ahoppen Sep 8, 2022
eea6c41
Rename `consume(remapping:)` -> `consumeAnyToken(remapping:)`
ahoppen Sep 5, 2022
a7fa8ff
Implement a common paradigm for `at`, `consume` and `expectWithoutRec…
ahoppen Sep 8, 2022
e8ee5d1
Transformations from `self.at` to `self.at(anyIn:)`
ahoppen Sep 3, 2022
b61672a
Eliminate isIdentifier from Lexeme
ahoppen Sep 8, 2022
e596756
Replace access to currentToken.tokenText by higher level parser primi…
ahoppen Sep 8, 2022
5727bd0
Replace currentToken.tokenKind by higher level parser primitives wher…
ahoppen Sep 8, 2022
abda5f7
Migrate some more accesses to `currentToken` regarding punctuators to…
ahoppen Sep 8, 2022
d2b464f
Replace `isContextualKeyword` by `atContextualKeyword` where possible
ahoppen Sep 8, 2022
8b1bfe8
Migate a few places of currentToken.isAtStartOfLine to `at(:where:)`
ahoppen Sep 5, 2022
7cfd366
Migrate one case to expectContextualKeywordWithoutRecovery
ahoppen Sep 8, 2022
5682bce
Simplify a call of `consume(ifAny:where:)` to `consume(ifAny:contextu…
ahoppen Sep 8, 2022
5ad4739
Allow checking whether a lexeme matches the specification in `RawToke…
ahoppen Sep 8, 2022
aa86c8c
Migrate `isBinaryOperator` and `isAnyOperator` to `RawTokenKindSubset`
ahoppen Sep 8, 2022
2973ece
Migrate `isEffectsSpecifier` to RawTokenKindSubset
ahoppen Sep 8, 2022
60b8322
Migrate `isPossibleDeclStart` to `RawTokenKindSubset`
ahoppen Sep 8, 2022
5238797
Allow specifying how tokens should be remapped in `RawTokenKindSubset`
ahoppen Sep 5, 2022
f27de16
Migrate a few uses of `consumeAnyToken` to higher level parser constr…
ahoppen Sep 8, 2022
6a07ac5
Migrate `isStartOfStatement` to `RawTokenKindSubset`
ahoppen Sep 8, 2022
56e377c
Rewrite isStartOfDeclaration to more closesly match the parser implem…
ahoppen Sep 8, 2022
f6943b6
Add a check whether a code block item appears to be an expression
ahoppen Sep 8, 2022
1ab6472
Change precedence of `import` keyword to `declKeyword`
ahoppen Sep 1, 2022
2ccb251
Remove check that a token’s SyntaxArena must contain the token’s text
ahoppen Sep 6, 2022
f3dfa0f
Support recovery in front of declarations
ahoppen Sep 8, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 116 additions & 87 deletions Sources/SwiftParser/Attributes.swift

Large diffs are not rendered by default.

86 changes: 42 additions & 44 deletions Sources/SwiftParser/Availability.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ extension Parser {
repeat {
let entry: RawSyntax
switch source {
case .available where self.currentToken.isIdentifier,
.unavailable where self.currentToken.isIdentifier:
case .available where self.at(.identifier),
.unavailable where self.at(.identifier):
entry = RawSyntax(self.parseAvailabilityMacro())
default:
entry = self.parseAvailabilitySpec()
Expand All @@ -48,15 +48,12 @@ extension Parser {

// Before continuing to parse the next specification, we check that it's
// also in the shorthand syntax and recover from it.
if
keepGoing != nil,
self.currentToken.isIdentifier,
AvailabilityArgumentKind(rawValue: self.currentToken.tokenText) != nil
{
if keepGoing != nil,
let (_, handle) = self.at(anyIn: AvailabilityArgumentKind.self) {
var tokens = [RawTokenSyntax]()
tokens.append(self.consumeAnyToken())
tokens.append(self.eat(handle))
var recoveryProgress = LoopProgressCondition()
while !self.at(.eof) && !self.at(.comma) && !self.at(.rightParen) && recoveryProgress.evaluate(currentToken) {
while !self.at(any: [.eof, .comma, .rightParen]) && recoveryProgress.evaluate(currentToken) {
tokens.append(self.consumeAnyToken())
}
let syntax = RawTokenListSyntax(elements: tokens, arena: self.arena)
Expand All @@ -70,7 +67,7 @@ extension Parser {
return RawAvailabilitySpecListSyntax(elements: elements, arena: self.arena)
}

enum AvailabilityArgumentKind: SyntaxText {
enum AvailabilityArgumentKind: SyntaxText, ContextualKeywords {
case message
case renamed
case introduced
Expand All @@ -92,21 +89,11 @@ extension Parser {
do {
var loopProgressCondition = LoopProgressCondition()
while keepGoing != nil && loopProgressCondition.evaluate(currentToken) {
guard self.currentToken.tokenKind == .identifier,
let argKind = AvailabilityArgumentKind(rawValue: self.currentToken.tokenText) else {
// Not sure what this label is but, let's just eat it and
// keep going.
let arg = self.consumeAnyToken()
keepGoing = self.consume(if: .comma)
elements.append(RawAvailabilityArgumentSyntax(
entry: RawSyntax(arg), trailingComma: keepGoing, arena: self.arena))
continue
}

let entry: RawSyntax
switch argKind {
case .message, .renamed:
let argumentLabel = self.consumeAnyToken()
switch self.at(anyIn: AvailabilityArgumentKind.self) {
case (.message, let handle)?,
(.renamed, let handle)?:
let argumentLabel = self.eat(handle)
let (unexpectedBeforeColon, colon) = self.expect(.colon)
// FIXME: Make sure this is a string literal with no interpolation.
let stringValue = self.consumeAnyToken()
Expand All @@ -118,8 +105,9 @@ extension Parser {
value: RawSyntax(stringValue),
arena: self.arena
))
case .introduced, .obsoleted:
let argumentLabel = self.consumeAnyToken()
case (.introduced, let handle)?,
(.obsoleted, let handle)?:
let argumentLabel = self.eat(handle)
let (unexpectedBeforeColon, colon) = self.expect(.colon)
let version = self.parseVersionTuple()
entry = RawSyntax(RawAvailabilityLabeledArgumentSyntax(
Expand All @@ -129,21 +117,29 @@ extension Parser {
value: RawSyntax(version),
arena: self.arena
))
case .deprecated:
let argumentLabel = self.consumeAnyToken()
if self.at(.colon) {
let colon = self.eat(.colon)
case (.deprecated, let handle)?:
let argumentLabel = self.eat(handle)
if let colon = self.consume(if: .colon) {
let version = self.parseVersionTuple()
entry = RawSyntax(RawAvailabilityLabeledArgumentSyntax(
label: argumentLabel, colon: colon, value: RawSyntax(version), arena: self.arena))
label: argumentLabel,
colon: colon,
value: RawSyntax(version),
arena: self.arena
))
} else {
entry = RawSyntax(argumentLabel)
}
case .unavailable, .noasync:
let argument = self.consumeAnyToken()
case (.unavailable, let handle)?,
(.noasync, let handle)?:
let argument = self.eat(handle)
// FIXME: Can we model this in SwiftSyntax by making the
// 'labeled' argument part optional?
entry = RawSyntax(argument)
case nil:
// Not sure what this label is but, let's just eat it and
// keep going.
entry = RawSyntax(self.consumeAnyToken())
}

keepGoing = self.consume(if: .comma)
Expand All @@ -162,15 +158,14 @@ extension Parser {
/// availability-argument → platform-name platform-version
/// availability-argument → *
mutating func parseAvailabilitySpec() -> RawSyntax {
if self.currentToken.isBinaryOperator && self.currentToken.tokenText == "*" {
let star = self.consumeAnyToken()
if let star = self.consumeIfContextualPunctuator("*") {
// FIXME: Use makeAvailabilityVersionRestriction here - but swift-format
// doesn't expect it.
return RawSyntax(star)
}

if self.currentToken.isIdentifier || self.at(.wildcardKeyword) {
if self.currentToken.tokenText == "swift" || self.currentToken.tokenText == "_PackageDescription" {
if self.at(any: [.identifier, .wildcardKeyword]) {
if self.atContextualKeyword("swift") || self.atContextualKeyword("_PackageDescription") {
return RawSyntax(self.parsePlatformAgnosticVersionConstraintSpec())
}
}
Expand All @@ -179,11 +174,14 @@ extension Parser {
}

mutating func parsePlatformAgnosticVersionConstraintSpec() -> RawAvailabilityVersionRestrictionSyntax {
assert(self.currentToken.isIdentifier || self.at(.wildcardKeyword))
let platform = self.consumeAnyToken()
let (unexpectedBeforePlatform, platform) = self.expectAny([.identifier, .wildcardKeyword], default: .identifier)
let version = self.parseVersionTuple()
return RawAvailabilityVersionRestrictionSyntax(
platform: platform, version: version, arena: self.arena)
unexpectedBeforePlatform,
platform: platform,
version: version,
arena: self.arena
)
}

/// Parse a platform-specific version constraint.
Expand All @@ -202,7 +200,7 @@ extension Parser {
/// platform-name → tvOS
mutating func parsePlatformVersionConstraintSpec() -> RawAvailabilityVersionRestrictionSyntax {
// Register the platform name as a keyword token.
let plaform = self.consume(remapping: .contextualKeyword)
let plaform = self.consumeAnyToken(remapping: .contextualKeyword)
let version = self.parseVersionTuple()
return RawAvailabilityVersionRestrictionSyntax(
platform: plaform, version: version, arena: self.arena)
Expand All @@ -220,9 +218,9 @@ extension Parser {
let platform = self.consumeAnyToken()

let version: RawVersionTupleSyntax?
if case .integerLiteral = self.currentToken.tokenKind {
if self.at(.integerLiteral) {
version = self.parseVersionTuple()
} else if case .floatingLiteral = self.currentToken.tokenKind {
} else if self.at(.floatingLiteral) {
version = self.parseVersionTuple()
} else {
version = nil
Expand All @@ -241,7 +239,7 @@ extension Parser {
/// platform-version → decimal-digits '.' decimal-digits
/// platform-version → decimal-digits '.' decimal-digits '.' decimal-digits
mutating func parseVersionTuple() -> RawVersionTupleSyntax {
if self.currentToken.tokenKind == .integerLiteral {
if self.at(.integerLiteral) {
let majorMinor = self.consumeAnyToken()
return RawVersionTupleSyntax(
majorMinor: RawSyntax(majorMinor), patchPeriod: nil, patchVersion: nil,
Expand Down
Loading