Skip to content

Commit 6944b43

Browse files
authored
Merge pull request #873 from DougGregor/regex-literal-parsing
2 parents 74d1987 + f192bfd commit 6944b43

File tree

3 files changed

+48
-1
lines changed

3 files changed

+48
-1
lines changed

Sources/SwiftParser/Declarations.swift

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1406,6 +1406,29 @@ extension Parser {
14061406
}
14071407

14081408
extension Parser {
1409+
/// Are we at a regular expression literal that could act as an operator?
1410+
private func atRegexLiteralThatCouldBeAnOperator() -> Bool {
1411+
guard self.at(.regexLiteral) else {
1412+
return false
1413+
}
1414+
1415+
/// Try to re-lex at regex literal as an operator. If it succeeds and
1416+
/// consumes the entire regex literal, we're done.
1417+
return self.currentToken.tokenText.withBuffer {
1418+
(buffer: UnsafeBufferPointer<UInt8>) -> Bool in
1419+
var cursor = Lexer.Cursor(input: buffer, previous: 0)
1420+
guard buffer[0] == UInt8(ascii: "/") else { return false }
1421+
1422+
switch (cursor.lexOperatorIdentifier(cursor, cursor)) {
1423+
case (.unknown, _):
1424+
return false
1425+
1426+
default:
1427+
return cursor.input.isEmpty
1428+
}
1429+
}
1430+
}
1431+
14091432
@_spi(RawSyntax)
14101433
public mutating func parseFuncDeclaration(
14111434
_ attrs: DeclAttributes,
@@ -1414,7 +1437,10 @@ extension Parser {
14141437
let (unexpectedBeforeFuncKeyword, funcKeyword) = self.eat(handle)
14151438
let unexpectedBeforeIdentifier: RawUnexpectedNodesSyntax?
14161439
let identifier: RawTokenSyntax
1417-
if self.at(anyIn: Operator.self) != nil || self.at(any: [.exclamationMark, .prefixAmpersand]) {
1440+
if self.at(anyIn: Operator.self) != nil ||
1441+
self.at(any: [.exclamationMark, .prefixAmpersand]) ||
1442+
self.atRegexLiteralThatCouldBeAnOperator()
1443+
{
14181444
var name = self.currentToken.tokenText
14191445
if name.count > 1 && name.hasSuffix("<") && self.peek().tokenKind == .identifier {
14201446
name = SyntaxText(rebasing: name.dropLast())

Sources/SwiftParser/Lexer.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2078,6 +2078,7 @@ extension Lexer.Cursor {
20782078
DELIMITLOOP: while true {
20792079
defer { escaped = false }
20802080

2081+
let previousByte = Tmp.previous
20812082
switch Tmp.advance() {
20822083
case nil:
20832084
return nil
@@ -2095,6 +2096,14 @@ extension Lexer.Cursor {
20952096
continue DELIMITLOOP
20962097
}
20972098
}
2099+
2100+
// A regex literal may not end in a space or tab.
2101+
if !isMultiline && poundCount == 0 &&
2102+
(previousByte == UInt8(ascii: " ") ||
2103+
previousByte == UInt8(ascii: "\t")) {
2104+
return nil
2105+
}
2106+
20982107
Tmp = EndLex
20992108
break DELIMITLOOP
21002109
case let .some(next) where !Unicode.Scalar(next).isASCII:

Tests/SwiftParserTest/Declarations.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,18 @@ final class DeclarationTests: XCTestCase {
4242
DiagnosticSpec(locationMarker: "DIAG1", message: "expected argument list in function declaration"),
4343
DiagnosticSpec(locationMarker: "DIAG2", message: "expected '=' and right-hand type in same type requirement"),
4444
])
45+
46+
AssertParse("func /^/ (lhs: Int, rhs: Int) -> Int { 1 / 2 }")
47+
48+
AssertParse(
49+
"func #^DIAG^#/^notoperator^/ (lhs: Int, rhs: Int) -> Int { 1 / 2 }",
50+
diagnostics: [
51+
DiagnosticSpec(message: "expected identifier in function"),
52+
DiagnosticSpec(message: "unexpected text '/^notoperator^/' before parameter clause")
53+
]
54+
)
55+
56+
AssertParse("func /^ (lhs: Int, rhs: Int) -> Int { 1 / 2 }")
4557
}
4658

4759
func testFuncAfterUnbalancedClosingBrace() {

0 commit comments

Comments
 (0)