Skip to content

Introduce peek(isAt:) #1942

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 1 commit into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion Sources/SwiftParser/Availability.swift
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ extension Parser {
case (.star, _)?:
entry = self.parseAvailabilitySpec()
case (.identifier, let handle)?:
if self.peek().rawTokenKind == .comma {
if self.peek(isAt: .comma) {
// An argument like `_iOS13Aligned` that isn't followed by a version.
let version = self.eat(handle)
entry = .token(version)
Expand Down
8 changes: 4 additions & 4 deletions Sources/SwiftParser/Declarations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,8 @@ extension Parser {
return RawDeclSyntax(self.parseMacroExpansionDeclaration(attrs, handle))
case nil:
if inMemberDeclList {
let isProbablyVarDecl = self.at(.identifier, .wildcard) && self.peek().rawTokenKind.is(.colon, .equal, .comma)
let isProbablyTupleDecl = self.at(.leftParen) && self.peek().rawTokenKind.is(.identifier, .wildcard)
let isProbablyVarDecl = self.at(.identifier, .wildcard) && self.peek(isAt: .colon, .equal, .comma)
let isProbablyTupleDecl = self.at(.leftParen) && self.peek(isAt: .identifier, .wildcard)

if isProbablyVarDecl || isProbablyTupleDecl {
return RawDeclSyntax(self.parseBindingDeclaration(attrs, .missing(.keyword(.var))))
Expand Down Expand Up @@ -1036,7 +1036,7 @@ extension Parser {
let identifier: RawTokenSyntax
if self.at(anyIn: Operator.self) != nil || self.at(.exclamationMark, .prefixAmpersand) {
var name = self.currentToken.tokenText
if name.count > 1 && name.hasSuffix("<") && self.peek().rawTokenKind == .identifier {
if name.count > 1 && name.hasSuffix("<") && self.peek(isAt: .identifier) {
name = SyntaxText(rebasing: name.dropLast())
}
unexpectedBeforeIdentifier = nil
Expand Down Expand Up @@ -1121,7 +1121,7 @@ extension Parser {
let (unexpectedBeforeSubscriptKeyword, subscriptKeyword) = self.eat(handle)

let unexpectedName: RawTokenSyntax?
if self.at(.identifier) && self.peek().tokenText.hasPrefix("<") || self.peek().rawTokenKind == .leftParen {
if self.at(.identifier) && self.peek().tokenText.hasPrefix("<") || self.peek(isAt: .leftParen) {
unexpectedName = self.consumeAnyToken()
} else {
unexpectedName = nil
Expand Down
67 changes: 24 additions & 43 deletions Sources/SwiftParser/Expressions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ extension Parser {
return parseUnresolvedAsExpr(handle: handle)

case (.async, _)?:
if self.peek().rawTokenKind == .arrow || TokenSpec(.throws) ~= self.peek() {
if self.peek(isAt: .arrow, .keyword(.throws)) {
fallthrough
} else {
return nil
Expand Down Expand Up @@ -348,6 +348,20 @@ extension Parser {
}
}

/// Whether the current token is a valid contextual exprssion modifier like
/// `copy`, `consume`.
///
/// `copy` etc. are only contextually a keyword if they are followed by an
/// identifier or keyword on the same line. We do this to ensure that we do
/// not break any copy functions defined by users.
private mutating func isContextualExpressionModifier() -> Bool {
return self.peek(
isAt: TokenSpec(.identifier, allowAtStartOfLine: false),
TokenSpec(.dollarIdentifier, allowAtStartOfLine: false),
TokenSpec(.self, allowAtStartOfLine: false)
)
}

/// Parse an expression sequence element.
mutating func parseSequenceExpressionElement(
_ flavor: ExprFlavor,
Expand Down Expand Up @@ -430,17 +444,7 @@ extension Parser {
)

case (.copy, let handle)?:
// `copy` is only contextually a keyword, if it's followed by an
// identifier or keyword on the same line. We do this to ensure that we do
// not break any copy functions defined by users. This is following with
// what we have done for the consume keyword.
switch self.peek() {
case TokenSpec(.identifier, allowAtStartOfLine: false),
TokenSpec(.dollarIdentifier, allowAtStartOfLine: false),
TokenSpec(.self, allowAtStartOfLine: false):
break
default:
// Break out of `outer switch` on failure.
if !isContextualExpressionModifier() {
break EXPR_PREFIX
}

Expand All @@ -459,17 +463,7 @@ extension Parser {
)

case (.consume, let handle)?:
// `consume` is only contextually a keyword, if it's followed by an
// identifier or keyword on the same line. We do this to ensure that we do
// not break any copy functions defined by users. This is following with
// what we have done for the consume keyword.
switch self.peek() {
case TokenSpec(.identifier, allowAtStartOfLine: false),
TokenSpec(.dollarIdentifier, allowAtStartOfLine: false),
TokenSpec(.self, allowAtStartOfLine: false):
break
default:
// Break out of the outer `switch`.
if !isContextualExpressionModifier() {
break EXPR_PREFIX
}

Expand All @@ -492,17 +486,7 @@ extension Parser {
return RawExprSyntax(parsePackExpansionExpr(repeatHandle: handle, flavor, pattern: pattern))

case (.each, let handle)?:
// `each` is only contextually a keyword, if it's followed by an
// identifier or 'self' on the same line. We do this to ensure that we do
// not break any 'each' functions defined by users. This is following with
// what we have done for the consume keyword.
switch self.peek() {
case TokenSpec(.identifier, allowAtStartOfLine: false),
TokenSpec(.dollarIdentifier, allowAtStartOfLine: false),
TokenSpec(.self, allowAtStartOfLine: false):
break
default:
// Break out of `outer switch` on failure.
if !isContextualExpressionModifier() {
break EXPR_PREFIX
}

Expand All @@ -517,11 +501,10 @@ extension Parser {
)

case (.any, _)?:
// `any` is only contextually a keyword if it's followed by an identifier
// on the same line.
guard case TokenSpec(.identifier, allowAtStartOfLine: false) = self.peek() else {
if !isContextualExpressionModifier() {
break EXPR_PREFIX
}

// 'any' is parsed as a part of 'type'.
let type = self.parseType()
return RawExprSyntax(RawTypeExprSyntax(type: type, arena: self.arena))
Expand Down Expand Up @@ -995,7 +978,7 @@ extension Parser {
// Check for a [] or .[] suffix. The latter is only permitted when there
// are no components.
if self.at(TokenSpec(.leftSquare, allowAtStartOfLine: false))
|| (components.isEmpty && self.at(.period) && self.peek().rawTokenKind == .leftSquare)
|| (components.isEmpty && self.at(.period) && self.peek(isAt: .leftSquare))
{
// Consume the '.', if it's allowed here.
let period: RawTokenSyntax?
Expand Down Expand Up @@ -1722,7 +1705,7 @@ extension Parser {
let unexpectedBeforeEqual: RawUnexpectedNodesSyntax?
let equal: RawTokenSyntax?
let expression: RawExprSyntax
if self.peek().rawTokenKind == .equal {
if self.peek(isAt: .equal) {
// The name is a new declaration.
(unexpectedBeforeName, name) = self.expect(.identifier, TokenSpec(.self, remapping: .identifier), default: .identifier)
(unexpectedBeforeEqual, equal) = self.expect(.equal)
Expand Down Expand Up @@ -1901,7 +1884,7 @@ extension Parser {
let unexpectedBeforeLabel: RawUnexpectedNodesSyntax?
let label: RawTokenSyntax?
let colon: RawTokenSyntax?
if self.atArgumentLabel(allowDollarIdentifier: true) && self.peek().rawTokenKind == .colon {
if self.atArgumentLabel(allowDollarIdentifier: true) && self.peek(isAt: .colon) {
(unexpectedBeforeLabel, label) = parseArgumentLabel()
colon = consumeAnyToken()
} else {
Expand All @@ -1914,9 +1897,7 @@ extension Parser {
// this case lexes as a binary operator because it neither leads nor
// follows a proper subexpression.
let expr: RawExprSyntax
if self.at(.binaryOperator)
&& (self.peek().rawTokenKind == .comma || self.peek().rawTokenKind == .rightParen || self.peek().rawTokenKind == .rightSquare)
{
if self.at(.binaryOperator) && self.peek(isAt: .comma, .rightParen, .rightSquare) {
let (ident, args) = self.parseDeclNameRef(.operators)
expr = RawExprSyntax(
RawIdentifierExprSyntax(
Expand Down
8 changes: 3 additions & 5 deletions Sources/SwiftParser/Names.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,10 @@ extension Parser {

// Okay, let's look ahead and see if the next token is something that could
// be in an arg label list...
let next = self.peek()

// A close parenthesis, if empty lists are allowed.
let nextIsRParen = flags.contains(.zeroArgCompoundNames) && next.rawTokenKind == .rightParen
let nextIsRParen = flags.contains(.zeroArgCompoundNames) && peek(isAt: .rightParen)
// An argument label.
let nextIsArgLabel = next.isArgumentLabel() || next.rawTokenKind == .colon
let nextIsArgLabel = peek().isArgumentLabel() || peek(isAt: .colon)

guard nextIsRParen || nextIsArgLabel else {
return nil
Expand All @@ -117,7 +115,7 @@ extension Parser {
var loopProgress = LoopProgressCondition()
while !self.at(.endOfFile, .rightParen) && self.hasProgressed(&loopProgress) {
// Check to see if there is an argument label.
precondition(self.atArgumentLabel() && self.peek().rawTokenKind == .colon)
precondition(self.atArgumentLabel() && self.peek(isAt: .colon))
let name = self.consumeAnyToken()
let (unexpectedBeforeColon, colon) = self.expect(.colon)
elements.append(
Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftParser/Patterns.swift
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ extension Parser {

/// If we have something like `x SomeType`, use the indication that `SomeType` starts with a capital letter (and is thus probably a type name)
/// as an indication that the user forgot to write the colon instead of forgetting to write the comma to separate two elements.
if label == nil, colon == nil, self.at(.identifier), peek().rawTokenKind == .identifier, peek().tokenText.isStartingWithUppercase {
if label == nil, colon == nil, self.at(.identifier), peek(isAt: .identifier), peek().tokenText.isStartingWithUppercase {
label = consume(if: .identifier)
colon = self.missingToken(.colon)
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftParser/Statements.swift
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ extension Parser {
guard
self.at(.keyword(.let), .keyword(.var), .keyword(.case))
|| self.at(.keyword(.inout))
|| (lastBindingKind != nil && self.peek().rawTokenKind == .equal)
|| (lastBindingKind != nil && self.peek(isAt: .equal))
else {
// If we lack it, then this is theoretically a boolean condition.
// However, we also need to handle migrating from Swift 2 syntax, in
Expand Down
10 changes: 0 additions & 10 deletions Sources/SwiftParser/SyntaxUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,16 +113,6 @@ extension SyntaxText {
}
}

extension RawTokenKind {
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
}
}

extension RawTriviaPiece {
var isIndentationWhitespace: Bool {
switch self {
Expand Down
51 changes: 51 additions & 0 deletions Sources/SwiftParser/TokenConsumer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,57 @@ extension TokenConsumer {
}
}

extension TokenConsumer {
/// Returns whether the next (peeked) token matches `spec`
@inline(__always)
mutating func peek(isAt spec: TokenSpec) -> Bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts on just at instead of isAt? next could work as well. Or maybe nextAt(_:) instead of peek to match at(_:)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I deliberately decided against peek(at:) because we already have peek(at offset: Int) in the lexer, which peeks offset bytes ahead, which has completely different semantics.

We could consider renaming peek to next altogether. I’m not sure how much of a term of art peek is in parsers in general and whether we should stick to it because of that reason though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could also change peek(at offset: Int) to something else. You don't need to block the PR on this though, easy enough to rename peek(isAt:) if we want to after.

#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 {
Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftParser/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ extension Parser {
second = nil
unexpectedBeforeColon = nil
colon = parsedColon
} else if self.atArgumentLabel(allowDollarIdentifier: true) && self.peek().rawTokenKind == .colon {
} else if self.atArgumentLabel(allowDollarIdentifier: true) && self.peek(isAt: .colon) {
(unexpectedBeforeSecond, second) = self.parseArgumentLabel()
(unexpectedBeforeColon, colon) = self.expect(.colon)
} else {
Expand Down