From 2036cab0af3394b32387a6ec0d3d595bb083bc10 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Mon, 6 Feb 2023 11:58:54 +0100 Subject: [PATCH] =?UTF-8?q?Don=E2=80=99t=20use=20arrays=20in=20`consume`?= =?UTF-8?q?=20functions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creating arrays for `consume(ifAny:)` and friends incurs memory allocations and reference counting costs. Stamp out copies of `consume(if:)` for up to three different kinds and start using `RawTokenKindSubset` if more kinds need to be checked. This improves performance of parsing MovieSwiftUI by 5%. rdar://103774645 --- Sources/SwiftParser/Attributes.swift | 49 ++++++-- Sources/SwiftParser/Availability.swift | 4 +- Sources/SwiftParser/Declarations.swift | 72 +++++++++--- Sources/SwiftParser/Directives.swift | 27 ++++- Sources/SwiftParser/Expressions.swift | 29 ++--- Sources/SwiftParser/Lookahead.swift | 18 +-- Sources/SwiftParser/Modifiers.swift | 32 ++++- Sources/SwiftParser/Names.swift | 6 +- Sources/SwiftParser/Parser.swift | 96 +++++++++------ Sources/SwiftParser/Patterns.swift | 2 +- Sources/SwiftParser/Recovery.swift | 50 ++++++-- Sources/SwiftParser/Statements.swift | 14 +-- Sources/SwiftParser/StringLiterals.swift | 4 +- Sources/SwiftParser/SyntaxUtils.swift | 8 +- Sources/SwiftParser/TokenConsumer.swift | 141 +++++++++++++++-------- Sources/SwiftParser/TopLevel.swift | 2 +- Sources/SwiftParser/Types.swift | 41 ++++++- 17 files changed, 418 insertions(+), 177 deletions(-) diff --git a/Sources/SwiftParser/Attributes.swift b/Sources/SwiftParser/Attributes.swift index 089baa9d79f..269c30fef61 100644 --- a/Sources/SwiftParser/Attributes.swift +++ b/Sources/SwiftParser/Attributes.swift @@ -14,7 +14,7 @@ extension Parser { mutating func parseAttributeList() -> RawAttributeListSyntax? { - guard self.at(any: [.atSign, .poundIfKeyword]) else { + guard self.at(.atSign, .poundIfKeyword) else { return nil } @@ -23,7 +23,7 @@ extension Parser { repeat { let attribute = self.parseAttribute() elements.append(attribute) - } while self.at(any: [.atSign, .poundIfKeyword]) && loopProgress.evaluate(currentToken) + } while self.at(.atSign, .poundIfKeyword) && loopProgress.evaluate(currentToken) return RawAttributeListSyntax(elements: elements, arena: self.arena) } } @@ -282,7 +282,7 @@ extension Parser { // The contents of the @_effects attribute are parsed in SIL, we just // represent the contents as a list of tokens in SwiftSyntax. var tokens: [RawTokenSyntax] = [] - while !parser.at(any: [.rightParen, .eof]) { + while !parser.at(.rightParen, .eof) { tokens.append(parser.consumeAnyToken()) } return .effectsArguments(RawEffectsArgumentsSyntax(elements: tokens, arena: parser.arena)) @@ -443,7 +443,7 @@ extension Parser { var elements = [RawDifferentiabilityParamSyntax]() var loopProgress = LoopProgressCondition() - while !self.at(any: [.eof, .rightParen]) && loopProgress.evaluate(currentToken) { + while !self.at(.eof, .rightParen) && loopProgress.evaluate(currentToken) { guard let param = self.parseDifferentiabilityParameter() else { break } @@ -605,7 +605,7 @@ extension Parser { mutating func parseObjectiveCSelector() -> RawObjCSelectorSyntax { var elements = [RawObjCSelectorPieceSyntax]() var loopProgress = LoopProgressCondition() - while !self.at(any: [.eof, .rightParen]) && loopProgress.evaluate(currentToken) { + while !self.at(.eof, .rightParen) && loopProgress.evaluate(currentToken) { // Empty selector piece. if let colon = self.consume(if: .colon) { elements.append( @@ -687,7 +687,7 @@ extension Parser { var elements = [RawSpecializeAttributeSpecListSyntax.Element]() // Parse optional "exported" and "kind" labeled parameters. var loopProgress = LoopProgressCondition() - while !self.at(any: [.eof, .rightParen, .keyword(.where)]) && loopProgress.evaluate(currentToken) { + while !self.at(.eof, .rightParen, .keyword(.where)) && loopProgress.evaluate(currentToken) { switch self.at(anyIn: SpecializeParameter.self) { case (.target, let handle)?: let ident = self.eat(handle) @@ -751,7 +751,7 @@ extension Parser { case (.exported, let handle)?: let ident = self.eat(handle) let (unexpectedBeforeColon, colon) = self.expect(.colon) - let (unexpectedBeforeValue, value) = self.expectAny([.keyword(.true), .keyword(.false)], default: .keyword(.false)) + let (unexpectedBeforeValue, value) = self.expect(.keyword(.true), .keyword(.false), default: .keyword(.false)) let comma = self.consume(if: .comma) elements.append( .labeledSpecializeEntry( @@ -951,7 +951,7 @@ extension Parser { extension Parser { mutating func parseExposeArguments() -> RawExposeAttributeArgumentsSyntax { let language: RawTokenSyntax - if !self.at(any: [.rightParen, .comma]) { + if !self.at(.rightParen, .comma) { language = self.consumeAnyToken() } else { language = missingToken(.identifier) @@ -1076,13 +1076,42 @@ extension Parser { var keepGoing: RawTokenSyntax? = nil repeat { - let (unexpectedBeforeLabel, label) = self.expectAny([.keyword(.visibility), .keyword(.metadata)], default: .keyword(.visibility)) + let (unexpectedBeforeLabel, label) = self.expect(.keyword(.visibility), .keyword(.metadata), default: .keyword(.visibility)) let (unexpectedBeforeColon, colon) = self.expect(.colon) let unexpectedBeforeValue: RawUnexpectedNodesSyntax? let value: RawDocumentationAttributeArgumentSyntax.Value switch label.tokenText { case "visibility": - let (unexpected, token) = self.expectAny([.keyword(.open), .keyword(.public), .keyword(.internal), .keyword(.fileprivate), .keyword(.private)], default: .keyword(.internal)) + enum AccessLevelModifier: RawTokenKindSubset { + case `private` + case `fileprivate` + case `internal` + case `public` + case `open` + + var rawTokenKind: RawTokenKind { + switch self { + case .private: return .keyword(.private) + case .fileprivate: return .keyword(.fileprivate) + case .internal: return .keyword(.internal) + case .public: return .keyword(.public) + case .open: return .keyword(.open) + } + } + + init?(lexeme: Lexer.Lexeme) { + switch lexeme { + case RawTokenKindMatch(.private): self = .private + case RawTokenKindMatch(.fileprivate): self = .fileprivate + case RawTokenKindMatch(.internal): self = .internal + case RawTokenKindMatch(.public): self = .public + case RawTokenKindMatch(.open): self = .open + default: return nil + } + } + } + + let (unexpected, token) = self.expect(anyIn: AccessLevelModifier.self, default: .internal) unexpectedBeforeValue = unexpected value = .token(token) case "metadata": diff --git a/Sources/SwiftParser/Availability.swift b/Sources/SwiftParser/Availability.swift index e3aa54888eb..b2fc6c0e07b 100644 --- a/Sources/SwiftParser/Availability.swift +++ b/Sources/SwiftParser/Availability.swift @@ -219,7 +219,7 @@ extension Parser { } let version: RawVersionTupleSyntax? - if self.at(any: [.integerLiteral, .floatingLiteral]) { + if self.at(.integerLiteral, .floatingLiteral) { version = self.parseVersionTuple() } else { version = nil @@ -242,7 +242,7 @@ extension Parser { /// platform-version → decimal-digits '.' decimal-digits /// platform-version → decimal-digits '.' decimal-digits '.' decimal-digits mutating func parseVersionTuple() -> RawVersionTupleSyntax { - let (unexpectedBeforeMajorMinor, majorMinor) = self.expectAny([.integerLiteral, .floatingLiteral], default: .integerLiteral) + let (unexpectedBeforeMajorMinor, majorMinor) = self.expect(.integerLiteral, .floatingLiteral, default: .integerLiteral) let patchPeriod: RawTokenSyntax? let unexpectedBeforePatch: RawUnexpectedNodesSyntax? let patch: RawTokenSyntax? diff --git a/Sources/SwiftParser/Declarations.swift b/Sources/SwiftParser/Declarations.swift index 5b6d3a646d2..bd722a91f0f 100644 --- a/Sources/SwiftParser/Declarations.swift +++ b/Sources/SwiftParser/Declarations.swift @@ -257,8 +257,8 @@ extension Parser { return RawDeclSyntax(self.parseMacroDeclaration(attrs: attrs, introducerHandle: handle)) case nil: if inMemberDeclList { - let isProbablyVarDecl = self.at(any: [.identifier, .wildcard]) && self.peek().rawTokenKind.is(any: [.colon, .equal, .comma]) - let isProbablyTupleDecl = self.at(.leftParen) && self.peek().rawTokenKind.is(any: [.identifier, .wildcard]) + let isProbablyVarDecl = self.at(.identifier, .wildcard) && self.peek().rawTokenKind.is(.colon, .equal, .comma) + let isProbablyTupleDecl = self.at(.leftParen) && self.peek().rawTokenKind.is(.identifier, .wildcard) if isProbablyVarDecl || isProbablyTupleDecl { return RawDeclSyntax(self.parseLetOrVarDeclaration(attrs, .missing(.keyword(.var)))) @@ -274,7 +274,7 @@ extension Parser { ) } - let isProbablyFuncDecl = self.at(any: [.identifier, .wildcard]) || self.at(anyIn: Operator.self) != nil + let isProbablyFuncDecl = self.at(.identifier, .wildcard) || self.at(anyIn: Operator.self) != nil if isProbablyFuncDecl { return RawDeclSyntax(self.parseFuncDeclaration(attrs, .missing(.keyword(.func)))) @@ -321,7 +321,45 @@ extension Parser { @_spi(RawSyntax) public mutating func parseImportKind() -> RawTokenSyntax? { - return self.consume(ifAny: [.keyword(.typealias), .keyword(.struct), .keyword(.class), .keyword(.enum), .keyword(.protocol), .keyword(.var), .keyword(.let), .keyword(.func)]) + enum ImportKind: RawTokenKindSubset { + case `typealias` + case `struct` + case `class` + case `enum` + case `protocol` + case `var` + case `let` + case `func` + + var rawTokenKind: RawTokenKind { + switch self { + case .typealias: return .keyword(.typealias) + case .struct: return .keyword(.struct) + case .class: return .keyword(.class) + case .enum: return .keyword(.enum) + case .protocol: return .keyword(.protocol) + case .var: return .keyword(.var) + case .let: return .keyword(.let) + case .func: return .keyword(.func) + } + } + + init?(lexeme: Lexer.Lexeme) { + switch lexeme { + case RawTokenKindMatch(.typealias): self = .typealias + case RawTokenKindMatch(.struct): self = .struct + case RawTokenKindMatch(.class): self = .class + case RawTokenKindMatch(.enum): self = .enum + case RawTokenKindMatch(.protocol): self = .protocol + case RawTokenKindMatch(.var): self = .var + case RawTokenKindMatch(.let): self = .let + case RawTokenKindMatch(.func): self = .func + default: return nil + } + } + } + + return self.consume(ifAnyIn: ImportKind.self) } @_spi(RawSyntax) @@ -442,7 +480,7 @@ extension Parser { let unexpectedBeforeInherited: RawUnexpectedNodesSyntax? let inherited: RawTypeSyntax? if colon != nil { - if self.at(any: [.identifier, .keyword(.protocol), .keyword(.Any)]) { + if self.at(.identifier, .keyword(.protocol), .keyword(.Any)) { unexpectedBeforeInherited = nil inherited = self.parseType() } else if let classKeyword = self.consume(if: .keyword(.class)) { @@ -769,7 +807,7 @@ extension Parser { let (unexpectedBeforeLBrace, lbrace) = self.expect(.leftBrace) do { var loopProgress = LoopProgressCondition() - while !self.at(any: [.eof, .rightBrace]) && loopProgress.evaluate(currentToken) { + while !self.at(.eof, .rightBrace) && loopProgress.evaluate(currentToken) { let newItemAtStartOfLine = self.currentToken.isAtStartOfLine guard let newElement = self.parseMemberDeclListItem() else { break @@ -979,7 +1017,7 @@ extension Parser { // Parse the '!' or '?' for a failable initializer. let failable: RawTokenSyntax? - if let parsedFailable = self.consume(ifAny: [.exclamationMark, .postfixQuestionMark, .infixQuestionMark]) { + if let parsedFailable = self.consume(if: .exclamationMark, .postfixQuestionMark, .infixQuestionMark) { failable = parsedFailable } else if let parsedFailable = self.consumeIfContextualPunctuator("!", remapping: .exclamationMark) { failable = parsedFailable @@ -1195,7 +1233,7 @@ extension Parser { if !shouldSkipParameterParsing { var keepGoing = true var loopProgress = LoopProgressCondition() - while !self.at(any: [.eof, .rightParen]) + while !self.at(.eof, .rightParen) && keepGoing && loopProgress.evaluate(currentToken) { @@ -1277,7 +1315,7 @@ extension Parser { let (unexpectedBeforeFuncKeyword, funcKeyword) = self.eat(handle) let unexpectedBeforeIdentifier: RawUnexpectedNodesSyntax? let identifier: RawTokenSyntax - if self.at(anyIn: Operator.self) != nil || self.at(any: [.exclamationMark, .prefixAmpersand]) || self.atRegexLiteralThatCouldBeAnOperator() { + if self.at(anyIn: Operator.self) != nil || self.at(.exclamationMark, .prefixAmpersand) || self.atRegexLiteralThatCouldBeAnOperator() { var name = self.currentToken.tokenText if name.count > 1 && name.hasSuffix("<") && self.peek().rawTokenKind == .identifier { name = SyntaxText(rebasing: name.dropLast()) @@ -1552,7 +1590,7 @@ extension Parser { // Check there is an identifier before consuming var look = self.lookahead() let _ = look.consumeAttributeList() - let hasModifier = look.consume(ifAny: [.keyword(.mutating), .keyword(.nonmutating), .keyword(.__consuming)]) != nil + let hasModifier = look.consume(if: .keyword(.mutating), .keyword(.nonmutating), .keyword(.__consuming)) != nil guard let (kind, handle) = look.at(anyIn: AccessorKind.self) ?? forcedKind else { return nil } @@ -1563,7 +1601,7 @@ extension Parser { // get and set. let modifier: RawDeclModifierSyntax? if hasModifier { - let (unexpectedBeforeName, name) = self.expectAny([.keyword(.mutating), .keyword(.nonmutating), .keyword(.__consuming)], default: .keyword(.mutating)) + let (unexpectedBeforeName, name) = self.expect(.keyword(.mutating), .keyword(.nonmutating), .keyword(.__consuming), default: .keyword(.mutating)) modifier = RawDeclModifierSyntax( unexpectedBeforeName, name: name, @@ -1667,7 +1705,7 @@ extension Parser { var elements = [RawAccessorDeclSyntax]() do { var loopProgress = LoopProgressCondition() - while !self.at(any: [.eof, .rightBrace]) && loopProgress.evaluate(currentToken) { + while !self.at(.eof, .rightBrace) && loopProgress.evaluate(currentToken) { guard let introducer = self.parseAccessorIntroducer() else { // There can only be an implicit getter if no other accessors were // seen before this one. @@ -1812,7 +1850,7 @@ extension Parser { case (_, let handle)?: (unexpectedBeforeName, name) = self.eat(handle) default: - if let identifier = self.consume(ifAny: [.identifier, .dollarIdentifier], allowTokenAtStartOfLine: false) { + if let identifier = self.consume(if: .identifier, .dollarIdentifier, allowTokenAtStartOfLine: false) { // Recover if the developer tried to use an identifier as the operator name unexpectedBeforeName = RawUnexpectedNodesSyntax([identifier], arena: self.arena) } else { @@ -1827,7 +1865,7 @@ extension Parser { var loopProgress = LoopProgressCondition() while (identifiersAfterOperatorName.last ?? name).trailingTriviaByteLength == 0, self.currentToken.leadingTriviaByteLength == 0, - !self.at(any: [.colon, .leftBrace, .eof]), + !self.at(.colon, .leftBrace, .eof), loopProgress.evaluate(self.currentToken) { identifiersAfterOperatorName.append(consumeAnyToken()) @@ -1977,12 +2015,12 @@ extension Parser { var elements = [RawPrecedenceGroupAttributeListSyntax.Element]() do { var attributesProgress = LoopProgressCondition() - LOOP: while !self.at(any: [.eof, .rightBrace]) && attributesProgress.evaluate(currentToken) { + LOOP: while !self.at(.eof, .rightBrace) && attributesProgress.evaluate(currentToken) { switch self.at(anyIn: LabelText.self) { case (.associativity, let handle)?: let associativity = self.eat(handle) let (unexpectedBeforeColon, colon) = self.expect(.colon) - var (unexpectedBeforeValue, value) = self.expectAny([.keyword(.left), .keyword(.right), .keyword(.none)], default: .keyword(.none)) + var (unexpectedBeforeValue, value) = self.expect(.keyword(.left), .keyword(.right), .keyword(.none), default: .keyword(.none)) if value.isMissing, let identifier = self.consume(if: .identifier) { unexpectedBeforeValue = RawUnexpectedNodesSyntax(combining: unexpectedBeforeValue, identifier, arena: self.arena) } @@ -2001,7 +2039,7 @@ extension Parser { case (.assignment, let handle)?: let assignmentKeyword = self.eat(handle) let (unexpectedBeforeColon, colon) = self.expect(.colon) - let (unexpectedBeforeFlag, flag) = self.expectAny([.keyword(.true), .keyword(.false)], default: .keyword(.true)) + let (unexpectedBeforeFlag, flag) = self.expect(.keyword(.true), .keyword(.false), default: .keyword(.true)) let unexpectedAfterFlag: RawUnexpectedNodesSyntax? if flag.isMissing, let unexpectedIdentifier = self.consume(if: .identifier, allowTokenAtStartOfLine: false) { unexpectedAfterFlag = RawUnexpectedNodesSyntax([unexpectedIdentifier], arena: self.arena) diff --git a/Sources/SwiftParser/Directives.swift b/Sources/SwiftParser/Directives.swift index d31fb6eea55..9e24a295d92 100644 --- a/Sources/SwiftParser/Directives.swift +++ b/Sources/SwiftParser/Directives.swift @@ -13,6 +13,29 @@ @_spi(RawSyntax) import SwiftSyntax extension Parser { + private enum IfConfigClauseStartKeyword: RawTokenKindSubset { + case poundIfKeyword + case poundElseifKeyword + case poundElseKeyword + + var rawTokenKind: RawTokenKind { + switch self { + case .poundIfKeyword: return .poundIfKeyword + case .poundElseifKeyword: return .poundElseifKeyword + case .poundElseKeyword: return .poundElseKeyword + } + } + + init?(lexeme: Lexer.Lexeme) { + switch lexeme { + case RawTokenKindMatch(.poundIfKeyword): self = .poundIfKeyword + case RawTokenKindMatch(.poundElseifKeyword): self = .poundElseifKeyword + case RawTokenKindMatch(.poundElseKeyword): self = .poundElseKeyword + default: return nil + } + } + } + /// Parse a conditional compilation block. /// /// This function should be used to parse conditional compilation statements, @@ -83,7 +106,7 @@ extension Parser { do { var firstIteration = true var loopProgress = LoopProgressCondition() - while let poundIfHandle = self.canRecoverTo(any: firstIteration ? [.poundIfKeyword] : [.poundIfKeyword, .poundElseifKeyword, .poundElseKeyword]), + while let poundIfHandle = firstIteration ? self.canRecoverTo(.poundIfKeyword) : self.canRecoverTo(anyIn: IfConfigClauseStartKeyword.self)?.handle, loopProgress.evaluate(self.currentToken) { let (unexpectedBeforePoundIf, poundIf) = self.eat(poundIfHandle) @@ -102,7 +125,7 @@ extension Parser { var elements = [Element]() do { var elementsProgress = LoopProgressCondition() - while !self.at(any: [.eof, .poundElseKeyword, .poundElseifKeyword, .poundEndifKeyword]) && elementsProgress.evaluate(currentToken) { + while !self.at(.eof) && !self.at(.poundElseKeyword, .poundElseifKeyword, .poundEndifKeyword) && elementsProgress.evaluate(currentToken) { let newItemAtStartOfLine = self.currentToken.isAtStartOfLine guard let element = parseElement(&self), !element.isEmpty else { break diff --git a/Sources/SwiftParser/Expressions.swift b/Sources/SwiftParser/Expressions.swift index aedabcc5374..bea91e2c21a 100644 --- a/Sources/SwiftParser/Expressions.swift +++ b/Sources/SwiftParser/Expressions.swift @@ -217,7 +217,7 @@ extension Parser { handle: TokenConsumptionHandle ) -> (operator: RawExprSyntax, rhs: RawExprSyntax) { let asKeyword = self.eat(handle) - let failable = self.consume(ifAny: [.postfixQuestionMark, .exclamationMark]) + let failable = self.consume(if: .postfixQuestionMark, .exclamationMark) let op = RawUnresolvedAsExprSyntax( asTok: asKeyword, questionOrExclamationMark: failable, @@ -398,7 +398,7 @@ extension Parser { pattern: PatternContext = .none ) -> RawExprSyntax { // Try to parse '@' sign or 'inout' as a attributed typerepr. - if self.at(any: [.atSign, .keyword(.inout)]) { + if self.at(.atSign, .keyword(.inout)) { var backtrack = self.lookahead() if backtrack.canParseType() { let type = self.parseType() @@ -428,7 +428,7 @@ extension Parser { ) case (.tryKeyword, let handle)?: let tryKeyword = self.eat(handle) - let mark = self.consume(ifAny: [.exclamationMark, .postfixQuestionMark]) + let mark = self.consume(if: .exclamationMark, .postfixQuestionMark) let expression = self.parseSequenceExpressionElement( flavor, @@ -1046,11 +1046,7 @@ extension Parser { // Check for an operator starting with '.' that contains only // periods, '?'s, and '!'s. Expand that into key path components. - if self.at(any: [ - .postfixOperator, .postfixQuestionMark, - .exclamationMark, .prefixOperator, - .binaryOperator, - ]), + if self.at(.prefixOperator, .binaryOperator, .postfixOperator) || self.at(.postfixQuestionMark, .exclamationMark), let numComponents = getNumOptionalKeyPathPostfixComponents( self.currentToken.tokenText ) @@ -1584,19 +1580,19 @@ extension Parser { // If we saw a comma, that's a strong indicator we have more elements // to process. If that's not the case, we have to do some legwork to // determine if we should bail out. - guard comma == nil || self.at(any: [.rightSquareBracket, .eof]) else { + guard comma == nil || self.at(.rightSquareBracket, .eof) else { continue } // If we found EOF or the closing square bracket, bailout. - if self.at(any: [.rightSquareBracket, .eof]) { + if self.at(.rightSquareBracket, .eof) { break } // If The next token is at the beginning of a new line and can never start // an element, break. if self.currentToken.isAtStartOfLine - && (self.at(any: [.rightBrace, .poundEndifKeyword]) || self.atStartOfDeclaration() || self.atStartOfStatement()) + && (self.at(.rightBrace, .poundEndifKeyword) || self.atStartOfDeclaration() || self.atStartOfStatement()) { break } @@ -1732,10 +1728,7 @@ extension Parser { public mutating func parseClosureSignatureIfPresent() -> RawClosureSignatureSyntax? { // If we have a leading token that may be part of the closure signature, do a // speculative parse to validate it and look for 'in'. - guard - self.at(any: [.atSign, .leftParen, .leftSquareBracket, .wildcard]) - || self.at(.identifier) - else { + guard self.at(.atSign, .leftParen, .leftSquareBracket) || self.at(.wildcard, .identifier) else { // No closure signature. return nil } @@ -1886,7 +1879,7 @@ extension Parser { ) } else if let unownedContextualKeyword = self.consume(if: .keyword(.unowned)) { if let lparen = self.consume(if: .leftParen) { - let (unexpectedBeforeDetail, detail) = self.expectAny([.keyword(.safe), .keyword(.unsafe)], default: .keyword(.safe)) + let (unexpectedBeforeDetail, detail) = self.expect(.keyword(.safe), .keyword(.unsafe), default: .keyword(.safe)) let (unexpectedBeforeRParen, rparen) = self.expect(.rightParen) return RawClosureCaptureItemSpecifierSyntax( specifier: unownedContextualKeyword, @@ -2103,7 +2096,7 @@ extension Parser.Lookahead { var backtrack = self.lookahead() backtrack.eat(.leftBrace) var loopProgress = LoopProgressCondition() - while !backtrack.at(any: [.eof, .rightBrace, .poundEndifKeyword, .poundElseKeyword, .poundElseifKeyword]) && loopProgress.evaluate(backtrack.currentToken) { + while !backtrack.at(.eof, .rightBrace) && !backtrack.at(.poundEndifKeyword, .poundElseKeyword, .poundElseifKeyword) && loopProgress.evaluate(backtrack.currentToken) { backtrack.skipSingle() } @@ -2501,7 +2494,7 @@ extension Parser.Lookahead { // While we don't have '->' or ')', eat balanced tokens. var skipProgress = LoopProgressCondition() - while !lookahead.at(any: [.eof, .rightParen]) && skipProgress.evaluate(lookahead.currentToken) { + while !lookahead.at(.eof, .rightParen) && skipProgress.evaluate(lookahead.currentToken) { lookahead.skipSingle() } diff --git a/Sources/SwiftParser/Lookahead.swift b/Sources/SwiftParser/Lookahead.swift index b01880a5e66..4dbffa45263 100644 --- a/Sources/SwiftParser/Lookahead.swift +++ b/Sources/SwiftParser/Lookahead.swift @@ -103,7 +103,7 @@ extension Parser.Lookahead { return } var lookahead = self.lookahead() - if lookahead.canRecoverTo([kind]) != nil { + if lookahead.canRecoverTo(kind) != nil { for _ in 0..', or 'throws' after paren, it's likely a parameter // of function type. - guard backtrack.at(any: [.arrow, .keyword(.throws), .keyword(.rethrows), .keyword(.throw)]) else { + guard backtrack.at(.arrow) || backtrack.at(.keyword(.throws), .keyword(.rethrows), .keyword(.throw)) else { self.skipSingle() return } @@ -198,7 +198,7 @@ extension Parser.Lookahead { } while self.consume(if: .period) != nil if self.consume(if: .leftParen) != nil { - while !self.at(any: [.eof, .rightParen, .poundEndifKeyword]) { + while !self.at(.eof, .rightParen, .poundEndifKeyword) { self.skipSingle() } self.consume(if: .rightParen) @@ -217,7 +217,7 @@ extension Parser.Lookahead { var didSeeAnyAttributes = false var poundIfLoopProgress = LoopProgressCondition() repeat { - assert(self.at(any: [.poundIfKeyword, .poundElseKeyword, .poundElseifKeyword])) + assert(self.at(.poundIfKeyword, .poundElseKeyword, .poundElseifKeyword)) self.consumeAnyToken() // after `#if` or `#elseif` @@ -235,7 +235,7 @@ extension Parser.Lookahead { break ATTRIBUTE_LOOP } } - } while self.at(any: [.poundElseifKeyword, .poundElseKeyword]) && poundIfLoopProgress.evaluate(self.currentToken) + } while self.at(.poundElseifKeyword, .poundElseKeyword) && poundIfLoopProgress.evaluate(self.currentToken) return didSeeAnyAttributes && self.currentToken.isAtStartOfLine && self.consume(if: .poundEndifKeyword) != nil } @@ -281,7 +281,7 @@ extension Parser.Lookahead { } // Check if we have 'didSet'/'willSet' after attributes. - return lookahead.at(any: [.keyword(.didSet), .keyword(.willSet)]) + return lookahead.at(.keyword(.didSet), .keyword(.willSet)) } } @@ -387,7 +387,7 @@ extension Parser.Lookahead { case .leftSquareBracket: self.consume(if: .rightSquareBracket) case .poundIfKeyword, .poundElseKeyword, .poundElseifKeyword: - if self.at(any: [.poundElseKeyword, .poundElseifKeyword]) { + if self.at(.poundElseKeyword, .poundElseifKeyword) { stack += [.skipSingle] } else { self.consume(if: .poundElseifKeyword) @@ -395,7 +395,7 @@ extension Parser.Lookahead { return } case .skipUntil(let t1, let t2): - if !self.at(any: [.eof, t1, t2, .poundEndifKeyword, .poundElseKeyword, .poundElseifKeyword]) { + if !self.at(.eof, t1, t2) && !self.at(.poundEndifKeyword, .poundElseKeyword, .poundElseifKeyword) { stack += [.skipUntil(t1, t2), .skipSingle] } } diff --git a/Sources/SwiftParser/Modifiers.swift b/Sources/SwiftParser/Modifiers.swift index a17ffee4339..26b88fd9d33 100644 --- a/Sources/SwiftParser/Modifiers.swift +++ b/Sources/SwiftParser/Modifiers.swift @@ -150,9 +150,35 @@ extension Parser { } mutating func parseAccessLevelModifier() -> RawDeclModifierSyntax { - let (unexpectedBeforeName, name) = expectAny( - [.keyword(.private), .keyword(.fileprivate), .keyword(.internal), .keyword(.public)], - default: .keyword(.internal) + enum AccessLevelModifier: RawTokenKindSubset { + case `private` + case `fileprivate` + case `internal` + case `public` + + var rawTokenKind: RawTokenKind { + switch self { + case .private: return .keyword(.private) + case .fileprivate: return .keyword(.fileprivate) + case .internal: return .keyword(.internal) + case .public: return .keyword(.public) + } + } + + init?(lexeme: Lexer.Lexeme) { + switch lexeme { + case RawTokenKindMatch(.private): self = .private + case RawTokenKindMatch(.fileprivate): self = .fileprivate + case RawTokenKindMatch(.internal): self = .internal + case RawTokenKindMatch(.public): self = .public + default: return nil + } + } + } + + let (unexpectedBeforeName, name) = expect( + anyIn: AccessLevelModifier.self, + default: .internal ) let details = self.parseAccessModifierDetails() return RawDeclModifierSyntax( diff --git a/Sources/SwiftParser/Names.swift b/Sources/SwiftParser/Names.swift index 7e6d3cb1733..49979bfa5ef 100644 --- a/Sources/SwiftParser/Names.swift +++ b/Sources/SwiftParser/Names.swift @@ -64,7 +64,7 @@ extension Parser { mutating func parseDeclNameRef(_ flags: DeclNameOptions = []) -> (RawTokenSyntax, RawDeclNameArgumentsSyntax?) { // Consume the base name. let ident: RawTokenSyntax - if self.at(.identifier) || self.at(any: [.keyword(.self), .keyword(.Self), .keyword(.`init`)]) { + if self.at(.identifier) || self.at(.keyword(.self), .keyword(.Self), .keyword(.`init`)) { ident = self.expectIdentifierWithoutRecovery() } else if flags.contains(.operators), let (_, _) = self.at(anyIn: Operator.self) { ident = self.consumeAnyToken(remapping: .binaryOperator) @@ -113,7 +113,7 @@ extension Parser { var elements = [RawDeclNameArgumentSyntax]() do { var loopProgress = LoopProgressCondition() - while !self.at(any: [.eof, .rightParen]) && loopProgress.evaluate(currentToken) { + while !self.at(.eof, .rightParen) && loopProgress.evaluate(currentToken) { // Check to see if there is an argument label. assert(self.currentToken.canBeArgumentLabel() && self.peek().rawTokenKind == .colon) let name = self.consumeAnyToken() @@ -237,7 +237,7 @@ extension Parser.Lookahead { } var loopProgress = LoopProgressCondition() - while !lookahead.at(any: [.eof, .rightParen]) && loopProgress.evaluate(lookahead.currentToken) { + while !lookahead.at(.eof, .rightParen) && loopProgress.evaluate(lookahead.currentToken) { // Check to see if there is an argument label. guard lookahead.currentToken.canBeArgumentLabel() && lookahead.peek().rawTokenKind == .colon else { return false diff --git a/Sources/SwiftParser/Parser.swift b/Sources/SwiftParser/Parser.swift index 55f40957ef7..afd06d78d30 100644 --- a/Sources/SwiftParser/Parser.swift +++ b/Sources/SwiftParser/Parser.swift @@ -251,32 +251,7 @@ extension Parser { ) } var lookahead = self.lookahead() - return lookahead.canRecoverTo([kind], recoveryPrecedence: recoveryPrecedence) - } - - /// Checks if it can reach a token whose kind is in `kinds` by skipping - /// unexpected tokens that have lower ``TokenPrecedence`` than `precedence`. - @_spi(RawSyntax) - public mutating func canRecoverTo( - any kinds: [RawTokenKind] - ) -> RecoveryConsumptionHandle? { - if let matchedKind = kinds.filter({ RawTokenKindMatch($0) ~= self.currentToken }).first { - let remapKind: RawTokenKind? - if matchedKind.base == .keyword { - remapKind = matchedKind - } else { - remapKind = nil - } - return RecoveryConsumptionHandle( - unexpectedTokens: 0, - tokenConsumptionHandle: TokenConsumptionHandle( - tokenKind: self.currentToken.rawTokenKind, - remappedKind: remapKind - ) - ) - } - var lookahead = self.lookahead() - return lookahead.canRecoverTo(kinds) + return lookahead.canRecoverTo(kind, recoveryPrecedence: recoveryPrecedence) } /// Checks if we can reach a token in `subset` by skipping tokens that have @@ -287,7 +262,7 @@ extension Parser { mutating func canRecoverTo( anyIn subset: Subset.Type, recoveryPrecedence: TokenPrecedence? = nil - ) -> (Subset, RecoveryConsumptionHandle)? { + ) -> (matchedKind: Subset, handle: RecoveryConsumptionHandle)? { if let (kind, handle) = self.at(anyIn: subset) { return (kind, RecoveryConsumptionHandle(unexpectedTokens: 0, tokenConsumptionHandle: handle)) } @@ -345,7 +320,7 @@ extension Parser { ) -> (unexpected: RawUnexpectedNodesSyntax?, token: RawTokenSyntax) { return expectImpl( consume: { $0.consume(if: kind, remapping: remapping) }, - canRecoverTo: { $0.canRecoverTo([kind]) }, + canRecoverTo: { $0.canRecoverTo(kind) }, makeMissing: { if let remapping = remapping { return $0.missingToken(remapping, text: kind.defaultText) @@ -356,7 +331,7 @@ extension Parser { ) } - /// Attempts to consume a token whose kind is in `kinds`. + /// Attempts to consume a token whose kind is `kind1` or `kind2`. /// If it cannot be found, the parser tries /// 1. To eat unexpected tokens that have lower ``TokenPrecedence`` than the /// lowest precedence of the expected token kinds and see if a token of @@ -364,14 +339,61 @@ extension Parser { /// 2. If the token couldn't be found after skipping unexpected, it synthesizes /// a missing token of `defaultKind`. @_spi(RawSyntax) - public mutating func expectAny( - _ kinds: [RawTokenKind], - default defaultKind: RawTokenKind + public mutating func expect( + _ kind1: RawTokenKind, + _ kind2: RawTokenKind, + default defaultKind: RawTokenKind, + remapping: RawTokenKind? = nil + ) -> (unexpected: RawUnexpectedNodesSyntax?, token: RawTokenSyntax) { + return expectImpl( + consume: { $0.consume(if: kind1, kind2) }, + canRecoverTo: { $0.canRecoverTo(kind1, kind2) }, + makeMissing: { + if let remapping = remapping { + return $0.missingToken(remapping, text: defaultKind.defaultText) + } else { + return $0.missingToken(defaultKind) + } + } + ) + } + + /// Attempts to consume a token whose kind is `kind1`, `kind2` or `kind3`. + /// If it cannot be found, the parser tries + /// 1. To eat unexpected tokens that have lower ``TokenPrecedence`` than the + /// lowest precedence of the expected token kinds and see if a token of + /// the requested kinds occurs after the unexpected. + /// 2. If the token couldn't be found after skipping unexpected, it synthesizes + /// a missing token of `defaultKind`. + @_spi(RawSyntax) + public mutating func expect( + _ kind1: RawTokenKind, + _ kind2: RawTokenKind, + _ kind3: RawTokenKind, + default defaultKind: RawTokenKind, + remapping: RawTokenKind? = nil + ) -> (unexpected: RawUnexpectedNodesSyntax?, token: RawTokenSyntax) { + return expectImpl( + consume: { $0.consume(if: kind1, kind2, kind3) }, + canRecoverTo: { $0.canRecoverTo(kind1, kind2, kind3) }, + makeMissing: { + if let remapping = remapping { + return $0.missingToken(remapping, text: defaultKind.defaultText) + } else { + return $0.missingToken(defaultKind) + } + } + ) + } + + mutating func expect( + anyIn subset: Subset.Type, + default defaultKind: Subset ) -> (unexpected: RawUnexpectedNodesSyntax?, token: RawTokenSyntax) { return expectImpl( - consume: { $0.consume(ifAny: kinds) }, - canRecoverTo: { $0.canRecoverTo(kinds) }, - makeMissing: { $0.missingToken(defaultKind) } + consume: { $0.consume(ifAnyIn: subset) }, + canRecoverTo: { $0.canRecoverTo(anyIn: subset)?.1 }, + makeMissing: { $0.missingToken(defaultKind.rawTokenKind) } ) } @@ -398,7 +420,7 @@ extension Parser { self.missingToken(.identifier) ) } - if let number = self.consume(ifAny: [.integerLiteral, .floatingLiteral, .dollarIdentifier]) { + if let number = self.consume(if: .integerLiteral, .floatingLiteral, .dollarIdentifier) { return ( RawUnexpectedNodesSyntax(elements: [RawSyntax(number)], arena: self.arena), self.missingToken(.identifier, text: nil) @@ -466,7 +488,7 @@ extension Parser { } var lookahead = self.lookahead() - guard let recoveryHandle = lookahead.canRecoverTo([.rightBrace]) else { + guard let recoveryHandle = lookahead.canRecoverTo(.rightBrace) else { // We can't recover to '}'. Synthesize it. return (nil, self.missingToken(.rightBrace, text: nil)) } diff --git a/Sources/SwiftParser/Patterns.swift b/Sources/SwiftParser/Patterns.swift index e7a58f04f57..850e64a7869 100644 --- a/Sources/SwiftParser/Patterns.swift +++ b/Sources/SwiftParser/Patterns.swift @@ -210,7 +210,7 @@ extension Parser { do { var keepGoing = true var loopProgress = LoopProgressCondition() - while !self.at(any: [.eof, .rightParen]) && keepGoing && loopProgress.evaluate(currentToken) { + while !self.at(.eof, .rightParen) && keepGoing && loopProgress.evaluate(currentToken) { // If the tuple element has a label, parse it. let labelAndColon = self.consume(if: .identifier, followedBy: .colon) let (label, colon) = (labelAndColon?.0, labelAndColon?.1) diff --git a/Sources/SwiftParser/Recovery.swift b/Sources/SwiftParser/Recovery.swift index 4987d39f9d9..669da9ed91e 100644 --- a/Sources/SwiftParser/Recovery.swift +++ b/Sources/SwiftParser/Recovery.swift @@ -42,28 +42,56 @@ public struct RecoveryConsumptionHandle { } extension Parser.Lookahead { - /// Tries eating tokens until it finds a token whose kind is in `kinds` + /// See `canRecoverTo` that takes 3 kinds. + mutating func canRecoverTo( + _ kind1: RawTokenKind, + recoveryPrecedence: TokenPrecedence? = nil + ) -> RecoveryConsumptionHandle? { + return canRecoverTo(kind1, kind1, kind1, recoveryPrecedence: recoveryPrecedence) + } + + /// See `canRecoverTo` that takes 3 kinds. + mutating func canRecoverTo( + _ kind1: RawTokenKind, + _ kind2: RawTokenKind, + recoveryPrecedence: TokenPrecedence? = nil + ) -> RecoveryConsumptionHandle? { + return canRecoverTo(kind1, kind2, kind1, recoveryPrecedence: recoveryPrecedence) + } + + /// Tries eating tokens until it finds a token whose kind is `kind1`, `kind2` or `kind3` /// without skipping tokens that have a precedence that's higher than the - /// lowest precedence in `kinds`. If it found a token of `kind` in this way, + /// lowest precedence in the expected kinds. If it found a token of `kind` 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( - _ kinds: [RawTokenKind], + _ kind1: RawTokenKind, + _ kind2: RawTokenKind, + _ kind3: RawTokenKind, recoveryPrecedence: TokenPrecedence? = nil ) -> RecoveryConsumptionHandle? { let initialTokensConsumed = self.tokensConsumed - let recoveryPrecedence = recoveryPrecedence ?? kinds.map(TokenPrecedence.init).min()! + let recoveryPrecedence = recoveryPrecedence ?? min(TokenPrecedence(kind1), TokenPrecedence(kind2), TokenPrecedence(kind3)) while !self.at(.eof) { - if !recoveryPrecedence.shouldSkipOverNewlines, - self.currentToken.isAtStartOfLine - { + if !recoveryPrecedence.shouldSkipOverNewlines, self.currentToken.isAtStartOfLine { break } - if let matchedKind = kinds.filter({ RawTokenKindMatch($0) ~= self.currentToken }).first { + let matchedKind: RawTokenKind? + switch self.currentToken { + case RawTokenKindMatch(kind1): + matchedKind = kind1 + case RawTokenKindMatch(kind2): + matchedKind = kind2 + case RawTokenKindMatch(kind3): + matchedKind = kind3 + default: + matchedKind = nil + } + if let matchedKind = matchedKind { let remapKind: RawTokenKind? if matchedKind.base == .keyword { remapKind = matchedKind @@ -84,7 +112,7 @@ extension Parser.Lookahead { } self.consumeAnyToken() if let closingDelimiter = currentTokenPrecedence.closingTokenKind { - guard self.canRecoverTo([closingDelimiter]) != nil else { + guard self.canRecoverTo(closingDelimiter) != nil else { break } self.eat(closingDelimiter) @@ -102,7 +130,7 @@ extension Parser.Lookahead { mutating func canRecoverTo( anyIn subset: Subset.Type, recoveryPrecedence: TokenPrecedence? = nil - ) -> (Subset, RecoveryConsumptionHandle)? { + ) -> (matchedKind: Subset, handle: RecoveryConsumptionHandle)? { let initialTokensConsumed = self.tokensConsumed assert(!subset.allCases.isEmpty, "Subset must have at least one case") @@ -136,7 +164,7 @@ extension Parser.Lookahead { } self.consumeAnyToken() if let closingDelimiter = currentTokenPrecedence.closingTokenKind { - guard self.canRecoverTo([closingDelimiter]) != nil else { + guard self.canRecoverTo(closingDelimiter) != nil else { break } self.eat(closingDelimiter) diff --git a/Sources/SwiftParser/Statements.swift b/Sources/SwiftParser/Statements.swift index 8c6b4fb7a0c..94ca37802b3 100644 --- a/Sources/SwiftParser/Statements.swift +++ b/Sources/SwiftParser/Statements.swift @@ -217,13 +217,13 @@ extension Parser { @_spi(RawSyntax) public mutating func parseConditionElement() -> RawConditionElementSyntax.Condition { // Parse a leading #available/#unavailable condition if present. - if self.at(any: [.poundAvailableKeyword, .poundUnavailableKeyword]) { + if self.at(.poundAvailableKeyword, .poundUnavailableKeyword) { return self.parsePoundAvailableConditionElement() } // Parse the basic expression case. If we have a leading let/var/case // keyword or an assignment, then we know this is a binding. - guard self.at(any: [.keyword(.let), .keyword(.var), .keyword(.case)]) else { + guard self.at(.keyword(.let), .keyword(.var), .keyword(.case)) else { // If we lack it, then this is theoretically a boolean condition. // However, we also need to handle migrating from Swift 2 syntax, in // which a comma followed by an expression could actually be a pattern @@ -240,7 +240,7 @@ extension Parser { } // We're parsing a conditional binding. - assert(self.at(any: [.keyword(.let), .keyword(.var), .keyword(.case)])) + assert(self.at(.keyword(.let), .keyword(.var), .keyword(.case))) enum BindingKind { case pattern(RawTokenSyntax, RawPatternSyntax) case optional(RawTokenSyntax, RawPatternSyntax) @@ -322,7 +322,7 @@ extension Parser { /// availability-condition → '#unavailable' '(' availability-arguments ')' @_spi(RawSyntax) public mutating func parsePoundAvailableConditionElement() -> RawConditionElementSyntax.Condition { - assert(self.at(any: [.poundAvailableKeyword, .poundUnavailableKeyword])) + assert(self.at(.poundAvailableKeyword, .poundUnavailableKeyword)) let keyword = self.consumeAnyToken() let (unexpectedBeforeLParen, lparen) = self.expect(.leftParen) let spec = self.parseAvailabilitySpecList() @@ -479,7 +479,7 @@ extension Parser { // If this is a 'catch' clause and we have "catch {" or "catch where...", // then we get an implicit "let error" pattern. let pattern: RawPatternSyntax? - if self.at(any: [.leftBrace, .keyword(.where)]) { + if self.at(.leftBrace, .keyword(.where)) { pattern = nil } else { pattern = self.parseMatchingPattern(context: .matching) @@ -707,7 +707,7 @@ extension Parser { var keepGoing = true var elementList = [RawYieldExprListElementSyntax]() var loopProgress = LoopProgressCondition() - while !self.at(any: [.eof, .rightParen]) && keepGoing && loopProgress.evaluate(currentToken) { + while !self.at(.eof, .rightParen) && keepGoing && loopProgress.evaluate(currentToken) { let expr = self.parseExpression() let comma = self.consume(if: .comma) elementList.append( @@ -959,7 +959,7 @@ extension Parser.Lookahead { lookahead.consumeAnyToken() // just find the end of the line lookahead.skipUntilEndOfLine() - } while lookahead.at(any: [.poundIfKeyword, .poundElseifKeyword, .poundElseKeyword]) && loopProgress.evaluate(lookahead.currentToken) + } while lookahead.at(.poundIfKeyword, .poundElseifKeyword, .poundElseKeyword) && loopProgress.evaluate(lookahead.currentToken) return lookahead.isAtStartOfSwitchCase() } } diff --git a/Sources/SwiftParser/StringLiterals.swift b/Sources/SwiftParser/StringLiterals.swift index 8e9cb3271bb..8c43d70df98 100644 --- a/Sources/SwiftParser/StringLiterals.swift +++ b/Sources/SwiftParser/StringLiterals.swift @@ -465,7 +465,7 @@ extension Parser { let openDelimiter = self.consume(if: .rawStringDelimiter) /// Parse open quote. - var (unexpectedBeforeOpenQuote, openQuote) = self.expectAny([.stringQuote, .multilineStringQuote], default: .stringQuote) + var (unexpectedBeforeOpenQuote, openQuote) = self.expect(.stringQuote, .multilineStringQuote, default: .stringQuote) var openQuoteKind: RawTokenKind = openQuote.tokenKind if openQuote.isMissing, let singleQuote = self.consume(if: .singleQuote) { unexpectedBeforeOpenQuote = RawUnexpectedNodesSyntax(combining: unexpectedBeforeOpenQuote, singleQuote, arena: self.arena) @@ -487,7 +487,7 @@ extension Parser { // This allows us to skip over extraneous identifiers etc. in an unterminated string interpolation. var unexpectedBeforeRightParen: [RawTokenSyntax] = [] var unexpectedProgress = LoopProgressCondition() - while !self.at(any: [.rightParen, .stringSegment, .backslash, openQuoteKind, .eof]) && unexpectedProgress.evaluate(self.currentToken) { + while !self.at(.rightParen, .stringSegment, .backslash) && !self.at(openQuoteKind, .eof) && unexpectedProgress.evaluate(self.currentToken) { unexpectedBeforeRightParen.append(self.consumeAnyToken()) } let rightParen = self.expectWithoutRecovery(.rightParen) diff --git a/Sources/SwiftParser/SyntaxUtils.swift b/Sources/SwiftParser/SyntaxUtils.swift index 041034f8540..5ff0685b819 100644 --- a/Sources/SwiftParser/SyntaxUtils.swift +++ b/Sources/SwiftParser/SyntaxUtils.swift @@ -91,8 +91,12 @@ extension SyntaxText { } extension RawTokenKind { - func `is`(any kinds: [RawTokenKind]) -> Bool { - return kinds.contains(self) + func `is`(_ kind1: RawTokenKind, _ kind2: RawTokenKind) -> Bool { + return self == kind1 || self == kind2 + } + + func `is`(_ kind1: RawTokenKind, _ kind2: RawTokenKind, _ kind3: RawTokenKind) -> Bool { + return self == kind1 || self == kind2 || self == kind3 } } diff --git a/Sources/SwiftParser/TokenConsumer.swift b/Sources/SwiftParser/TokenConsumer.swift index d5fe888b024..25c8ed69613 100644 --- a/Sources/SwiftParser/TokenConsumer.swift +++ b/Sources/SwiftParser/TokenConsumer.swift @@ -50,13 +50,9 @@ struct TokenConsumptionHandle { } extension TokenConsumer { - /// Returns whether the the current token is of kind `kind` and satisfies - /// `condition`. - /// - /// - Parameter kind: The kind to test for. - /// - Parameter condition: An additional condition that must be satisfied for - /// this function to return `true`. - /// - Returns: `true` if the given `kind` matches the current token's kind. + /// Returns whether the the current token is one of the specified kinds, and, + /// in case `allowTokenAtStartOfLine` is false, whether the current token is + /// not on a newline. public mutating func at( _ kind: RawTokenKind, allowTokenAtStartOfLine: Bool = true @@ -67,6 +63,44 @@ extension TokenConsumer { return RawTokenKindMatch(kind) ~= self.currentToken } + /// Returns whether the the current token is one of the specified kinds, and, + /// in case `allowTokenAtStartOfLine` is false, whether the current token is + /// not on a newline. + public mutating func at( + _ kind1: RawTokenKind, + _ kind2: RawTokenKind, + allowTokenAtStartOfLine: Bool = true + ) -> Bool { + if !allowTokenAtStartOfLine && self.currentToken.isAtStartOfLine { + return false + } + switch self.currentToken { + case RawTokenKindMatch(kind1): return true + case RawTokenKindMatch(kind2): return true + default: return false + } + } + + /// Returns whether the the current token is one of the specified kinds, and, + /// in case `allowTokenAtStartOfLine` is false, whether the current token is + /// not on a newline. + public mutating func at( + _ kind1: RawTokenKind, + _ kind2: RawTokenKind, + _ kind3: RawTokenKind, + allowTokenAtStartOfLine: Bool = true + ) -> Bool { + if !allowTokenAtStartOfLine && self.currentToken.isAtStartOfLine { + return false + } + switch self.currentToken { + case RawTokenKindMatch(kind1): return true + case RawTokenKindMatch(kind2): return true + case RawTokenKindMatch(kind3): return true + default: return false + } + } + /// Returns whether the current token is an operator with the given `name`. @_spi(RawSyntax) public mutating func atContextualPunctuator(_ name: SyntaxText) -> Bool { @@ -133,14 +167,10 @@ extension TokenConsumer { // MARK: Consuming tokens (`consume`) extension TokenConsumer { - /// Examines the current token and consumes it if its kind matches the - /// given `TokenKind` and additionally satisfies `condition`. If a token was - /// consumed, the result is that token, else the result is `nil`. - /// - /// - Parameter kind: The kind of token to consume. - /// - Parameter condition: An additional condition that must be satisfied for - /// the token to be consumed. - /// - Returns: A token of the given kind if one was consumed, else `nil`. + /// If the current token is of the given kind and, if `allowTokenAtStartOfLine` + /// is `false`, if the token is not at the start of a line, consume the token + /// and return it. Otherwise return `nil`. If `remapping` is not `nil`, the + /// returned token's kind will be changed to `remapping`. @_spi(RawSyntax) public mutating func consume( if kind: RawTokenKind, @@ -148,53 +178,70 @@ extension TokenConsumer { allowTokenAtStartOfLine: Bool = true ) -> Token? { if self.at(kind, allowTokenAtStartOfLine: allowTokenAtStartOfLine) { - if let remapping = remapping { - return self.consumeAnyToken(remapping: remapping) - } else if kind.base == .keyword { - // We support remapping identifiers to contextual keywords - return self.consumeAnyToken(remapping: kind) - } else { - return self.consumeAnyToken() + if case RawTokenKindMatch(kind) = self.currentToken { + if let remapping = remapping { + return self.consumeAnyToken(remapping: remapping) + } else if kind.base == .keyword { + return self.consumeAnyToken(remapping: kind) + } else { + return self.consumeAnyToken() + } } } return nil } - /// Consumes and returns the current token is an operator with the given `name`. + /// If the current token is of the given kind and, if `allowTokenAtStartOfLine` + /// is `false`, if the token is not at the start of a line, consume the token + /// and return it. Otherwise return `nil`. If `remapping` is not `nil`, the + /// returned token's kind will be changed to `remapping`. @_spi(RawSyntax) - public 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() - } + public mutating func consume( + if kind1: RawTokenKind, + _ kind2: RawTokenKind, + remapping: RawTokenKind? = nil, + allowTokenAtStartOfLine: Bool = true + ) -> Token? { + if let token = consume(if: kind1, remapping: remapping, allowTokenAtStartOfLine: allowTokenAtStartOfLine) { + return token + } else if let token = consume(if: kind2, remapping: remapping, allowTokenAtStartOfLine: allowTokenAtStartOfLine) { + return token + } else { + return nil } - return nil } - /// Examines the current token and consumes it if is any of the given - /// kinds and additionally satisfies `condition`. - /// - /// - Parameter kind: The kinds of token to consume. - /// - Parameter condition: An additional condition that must be satisfied for - /// the token to be consumed. - /// - Returns: A token of the given kind if one was consumed, else `nil`. + /// If the current token is of the given kind and, if `allowTokenAtStartOfLine` + /// is `false`, if the token is not at the start of a line, consume the token + /// and return it. Otherwise return `nil`. If `remapping` is not `nil`, the + /// returned token's kind will be changed to `remapping`. @_spi(RawSyntax) public mutating func consume( - ifAny kinds: [RawTokenKind], + if kind1: RawTokenKind, + _ kind2: RawTokenKind, + _ kind3: RawTokenKind, + remapping: RawTokenKind? = nil, allowTokenAtStartOfLine: Bool = true ) -> Token? { - if !allowTokenAtStartOfLine && self.currentToken.isAtStartOfLine { + if let token = consume(if: kind1, remapping: remapping, allowTokenAtStartOfLine: allowTokenAtStartOfLine) { + return token + } else if let token = consume(if: kind2, remapping: remapping, allowTokenAtStartOfLine: allowTokenAtStartOfLine) { + return token + } else if let token = consume(if: kind3, remapping: remapping, allowTokenAtStartOfLine: allowTokenAtStartOfLine) { + return token + } else { return nil } - for kind in kinds { - if case RawTokenKindMatch(kind) = self.currentToken { - if kind.base == .keyword { - return self.consumeAnyToken(remapping: kind) - } else { - return self.consumeAnyToken() - } + } + + /// Consumes and returns the current token is an operator with the given `name`. + @_spi(RawSyntax) + public 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 diff --git a/Sources/SwiftParser/TopLevel.swift b/Sources/SwiftParser/TopLevel.swift index 833cc066161..d89ad18facc 100644 --- a/Sources/SwiftParser/TopLevel.swift +++ b/Sources/SwiftParser/TopLevel.swift @@ -157,7 +157,7 @@ extension Parser { arena: self.arena ) } - if self.at(any: [.keyword(.case), .keyword(.default)]) { + if self.at(.keyword(.case), .keyword(.default)) { // 'case' and 'default' are invalid in code block items. // Parse them and put them in their own CodeBlockItem but as an unexpected node. let switchCase = self.parseSwitchCase() diff --git a/Sources/SwiftParser/Types.swift b/Sources/SwiftParser/Types.swift index 67a48b1e1c1..e55ec62fab5 100644 --- a/Sources/SwiftParser/Types.swift +++ b/Sources/SwiftParser/Types.swift @@ -140,7 +140,7 @@ extension Parser { ) } - let someOrAny = self.consume(ifAny: [.keyword(.some), .keyword(.any)]) + let someOrAny = self.consume(if: .keyword(.some), .keyword(.any)) var base = self.parseSimpleType() guard self.atContextualPunctuator("&") else { @@ -486,7 +486,7 @@ extension Parser { do { var keepGoing = true var loopProgress = LoopProgressCondition() - while !self.at(any: [.eof, .rightParen]) && keepGoing && loopProgress.evaluate(currentToken) { + while !self.at(.eof, .rightParen) && keepGoing && loopProgress.evaluate(currentToken) { let unexpectedBeforeFirst: RawUnexpectedNodesSyntax? let first: RawTokenSyntax? let unexpectedBeforeSecond: RawUnexpectedNodesSyntax? @@ -783,7 +783,7 @@ extension Parser.Lookahead { mutating func canParseTupleBodyType() -> Bool { guard - !self.at(any: [.rightParen, .rightBrace]) && !self.atContextualPunctuator("...") && !self.atStartOfDeclaration() + !self.at(.rightParen, .rightBrace) && !self.atContextualPunctuator("...") && !self.atStartOfDeclaration() else { return self.consume(if: .rightParen) != nil } @@ -814,7 +814,8 @@ extension Parser.Lookahead { // better if we skip over them. if self.consume(if: .equal) != nil { var skipProgress = LoopProgressCondition() - while !self.at(any: [.eof, .rightParen, .rightBrace, .comma]) + while !self.at(.eof) + && !self.at(.rightParen, .rightBrace, .comma) && !self.atContextualPunctuator("...") && !self.atStartOfDeclaration() && skipProgress.evaluate(currentToken) @@ -929,7 +930,37 @@ extension Parser { specifier = missingToken(misplacedSpecifier.tokenKind, text: misplacedSpecifier.tokenText) } var extraneousSpecifiers: [RawTokenSyntax] = [] - while let extraSpecifier = self.consume(ifAny: [.keyword(.inout), .keyword(.__shared), .keyword(.__owned), .keyword(.isolated), .keyword(._const)]) { + + enum ExtraneousSpecifier: RawTokenKindSubset { + case `inout` + case __shared + case __owned + case isolated + case _const + + var rawTokenKind: RawTokenKind { + switch self { + case .inout: return .keyword(.inout) + case .__shared: return .keyword(.__shared) + case .__owned: return .keyword(.__owned) + case .isolated: return .keyword(.isolated) + case ._const: return .keyword(._const) + } + } + + init?(lexeme: Lexer.Lexeme) { + switch lexeme { + case RawTokenKindMatch(.inout): self = .inout + case RawTokenKindMatch(.__shared): self = .__shared + case RawTokenKindMatch(.__owned): self = .__owned + case RawTokenKindMatch(.isolated): self = .isolated + case RawTokenKindMatch(._const): self = ._const + default: return nil + } + } + } + + while let extraSpecifier = self.consume(ifAnyIn: ExtraneousSpecifier.self) { if specifier == nil { specifier = extraSpecifier } else {