Skip to content

Commit 605a5b8

Browse files
committed
Parse shebang as a child of SourceFileSyntax
1 parent b061ee5 commit 605a5b8

27 files changed

+166
-105
lines changed

CodeGeneration/Sources/SyntaxSupport/DeclNodes.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1997,6 +1997,12 @@ public let DECL_NODES: [Node] = [
19971997
parserFunction: "parseSourceFile",
19981998
traits: ["WithStatements"],
19991999
children: [
2000+
Child(
2001+
name: "Shebang",
2002+
kind: .token(choices: [.token(.shebang)]),
2003+
documentation: "A shebang can specify the path of the compiler when using Swift source file as a script.",
2004+
isOptional: true
2005+
),
20002006
Child(
20012007
name: "Statements",
20022008
kind: .collection(kind: .codeBlockItemList, collectionElementName: "Statement")

CodeGeneration/Sources/SyntaxSupport/TokenSpec.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ public enum Token: CaseIterable {
113113
case rightParen
114114
case rightSquare
115115
case semicolon
116+
case shebang
116117
case singleQuote
117118
case stringQuote
118119
case stringSegment
@@ -209,6 +210,8 @@ public enum Token: CaseIterable {
209210
return .punctuator(name: "rightSquare", text: "]")
210211
case .semicolon:
211212
return .punctuator(name: "semicolon", text: ";")
213+
case .shebang:
214+
return .other(name: "shebang", nameForDiagnostics: "shebang")
212215
case .singleQuote:
213216
return .punctuator(name: "singleQuote", text: "\'")
214217
case .stringQuote:

CodeGeneration/Sources/SyntaxSupport/Trivia.swift

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -159,11 +159,6 @@ public let TRIVIAS: [Trivia] = [
159159
]
160160
),
161161

162-
Trivia(
163-
name: "Shebang",
164-
comment: #"A script command, starting with '#!'."#
165-
),
166-
167162
Trivia(
168163
name: "Space",
169164
comment: #"A space ' ' character."#,

Sources/SwiftIDEUtils/SyntaxClassification.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ extension RawTokenKind {
193193
return .none
194194
case .semicolon:
195195
return .none
196+
case .shebang:
197+
return .none
196198
case .singleQuote:
197199
return .stringLiteral
198200
case .stringQuote:

Sources/SwiftParser/Lexer/Cursor.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,11 @@ extension Lexer.Cursor {
905905
case UInt8(ascii: "\\"): _ = self.advance(); return Lexer.Result(.backslash)
906906

907907
case UInt8(ascii: "#"):
908+
// Try lex shebang.
909+
if self.isAtStartOfFile, self.peek(at: 1) == UInt8(ascii: "!") {
910+
self.advanceToEndOfLine()
911+
return Lexer.Result(.shebang)
912+
}
908913
// Try lex a raw string literal.
909914
if let delimiterLength = self.advanceIfOpeningRawStringDelimiter() {
910915
return Lexer.Result(.rawStringPoundDelimiter, stateTransition: .push(newState: .afterRawStringDelimiter(delimiterLength: delimiterLength)))
@@ -1171,12 +1176,6 @@ extension Lexer.Cursor {
11711176
default:
11721177
break
11731178
}
1174-
case UInt8(ascii: "#"):
1175-
guard start.isAtStartOfFile, self.advance(if: { $0 == "!" }) else {
1176-
break
1177-
}
1178-
self.advanceToEndOfLine()
1179-
continue
11801179
case UInt8(ascii: "<"), UInt8(ascii: ">"):
11811180
if self.tryLexConflictMarker(start: start) {
11821181
error = LexingDiagnostic(.sourceConflictMarker, position: start)
@@ -1189,6 +1188,7 @@ extension Lexer.Cursor {
11891188
UInt8(ascii: "}"), UInt8(ascii: "]"), UInt8(ascii: ")"),
11901189
UInt8(ascii: "@"), UInt8(ascii: ","), UInt8(ascii: ";"),
11911190
UInt8(ascii: ":"), UInt8(ascii: "\\"), UInt8(ascii: "$"),
1191+
UInt8(ascii: "#"),
11921192

11931193
// Start of integer/hex/float literals.
11941194
UInt8(ascii: "0"), UInt8(ascii: "1"), UInt8(ascii: "2"),

Sources/SwiftParser/Lexer/RegexLiteralLexer.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,10 @@ extension Lexer.Cursor {
625625
case .arrow, .ellipsis, .period, .atSign, .pound, .backtick, .backslash:
626626
return false
627627

628+
// Shebang does not sequence expressions.
629+
case .shebang:
630+
return false
631+
628632
case .keyword:
629633
// There are a handful of keywords that are expressions, handle them.
630634
// Otherwise, a regex literal can generally be parsed after a keyword.

Sources/SwiftParser/TokenPrecedence.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,8 @@ enum TokenPrecedence: Comparable {
124124
.dollarIdentifier, .identifier,
125125
// '_' can occur in types to replace a type identifier
126126
.wildcard,
127-
// String segment, string interpolation anchor, pound, and regex pattern don't really fit anywhere else
128-
.pound, .stringSegment, .regexLiteralPattern:
127+
// String segment, string interpolation anchor, pound, shebang and regex pattern don't really fit anywhere else
128+
.pound, .stringSegment, .regexLiteralPattern, .shebang:
129129
self = .identifierLike
130130

131131
// MARK: Expr keyword

Sources/SwiftParser/TopLevel.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,12 @@ extension Parser {
4040
/// ``Parser/parse(source:parseTransition:filenameForDiagnostics:languageVersion:enableBareSlashRegexLiteral:)-7tndx``
4141
/// API calls.
4242
mutating func parseSourceFile() -> RawSourceFileSyntax {
43+
let shebang = self.consume(if: .shebang)
4344
let items = self.parseTopLevelCodeBlockItems()
4445
let unexpectedBeforeEndOfFileToken = consumeRemainingTokens()
4546
let endOfFile = self.consume(if: .endOfFile)!
4647
return .init(
48+
shebang: shebang,
4749
statements: items,
4850
RawUnexpectedNodesSyntax(unexpectedBeforeEndOfFileToken, arena: self.arena),
4951
endOfFileToken: endOfFile,

Sources/SwiftParser/TriviaParser.swift

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,6 @@ public struct TriviaParser {
8181
}
8282

8383
case UInt8(ascii: "#"):
84-
// "#!...": .shebang
85-
// NOTE: .shebang appears only if this trivia is at the start of the
86-
// file. We don't know if this trivia is at the start of the file, but
87-
// we believe that the lexer lexed it accordingly.
88-
if position == .leading && pieces.isEmpty && cursor.advance(if: { $0 == "!" }) {
89-
cursor.advanceToEndOfLine()
90-
pieces.append(.shebang(start.text(upTo: cursor)))
91-
continue
92-
}
9384
cursor.advance(while: { $0 == "#" })
9485
pieces.append(.pounds(start.distance(to: cursor)))
9586
continue

Sources/SwiftParser/generated/TokenSpecStaticMembers.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,10 @@ extension TokenSpec {
187187
return TokenSpec(.semicolon)
188188
}
189189

190+
static var shebang: TokenSpec {
191+
return TokenSpec(.shebang)
192+
}
193+
190194
static var singleQuote: TokenSpec {
191195
return TokenSpec(.singleQuote)
192196
}

Sources/SwiftParserDiagnostics/generated/TokenNameForDiagnostics.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ extension TokenKind {
103103
return "]"
104104
case .semicolon:
105105
return ";"
106+
case .shebang:
107+
return "shebang"
106108
case .singleQuote:
107109
return "'"
108110
case .stringQuote:

Sources/SwiftSyntax/SourceLocation.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -616,8 +616,7 @@ fileprivate extension RawTriviaPiece {
616616
body(carriageReturnLineLength)
617617
}
618618
lineLength = .zero
619-
case let .shebang(text),
620-
let .lineComment(text),
619+
case let .lineComment(text),
621620
let .docLineComment(text):
622621
// Line comments are not supposed to contain newlines.
623622
precondition(!text.containsSwiftNewline(), "line comment created that contained a new-line character")

Sources/SwiftSyntax/Trivia.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ extension Trivia {
192192
return Array(repeating: TriviaPiece.carriageReturns(1), count: count)
193193
case .carriageReturnLineFeeds(let count):
194194
return Array(repeating: TriviaPiece.carriageReturnLineFeeds(1), count: count)
195-
case .lineComment, .blockComment, .docLineComment, .docBlockComment, .unexpectedText, .shebang:
195+
case .lineComment, .blockComment, .docLineComment, .docBlockComment, .unexpectedText:
196196
return [piece]
197197
}
198198
})

Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2779,8 +2779,12 @@ public func childName(_ keyPath: AnyKeyPath) -> String? {
27792779
return "constraint"
27802780
case \SomeOrAnyTypeSyntax.unexpectedAfterConstraint:
27812781
return "unexpectedAfterConstraint"
2782-
case \SourceFileSyntax.unexpectedBeforeStatements:
2783-
return "unexpectedBeforeStatements"
2782+
case \SourceFileSyntax.unexpectedBeforeShebang:
2783+
return "unexpectedBeforeShebang"
2784+
case \SourceFileSyntax.shebang:
2785+
return "shebang"
2786+
case \SourceFileSyntax.unexpectedBetweenShebangAndStatements:
2787+
return "unexpectedBetweenShebangAndStatements"
27842788
case \SourceFileSyntax.statements:
27852789
return "statements"
27862790
case \SourceFileSyntax.unexpectedBetweenStatementsAndEndOfFileToken:

Sources/SwiftSyntax/generated/RenamedChildrenCompatibility.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7056,11 +7056,13 @@ extension SourceFileSyntax {
70567056
}
70577057
}
70587058

7059-
@available(*, deprecated, renamed: "SourceFileSyntax(leadingTrivia:_:statements:_:endOfFileToken:_:trailingTrivia:)")
7059+
@available(*, deprecated, renamed: "SourceFileSyntax(leadingTrivia:_:shebang:_:statements:_:endOfFileToken:_:trailingTrivia:)")
70607060
@_disfavoredOverload
70617061
public init(
70627062
leadingTrivia: Trivia? = nil,
7063-
_ unexpectedBeforeStatements: UnexpectedNodesSyntax? = nil,
7063+
_ unexpectedBeforeShebang: UnexpectedNodesSyntax? = nil,
7064+
shebang: TokenSyntax? = nil,
7065+
_ unexpectedBetweenShebangAndStatements: UnexpectedNodesSyntax? = nil,
70647066
statements: CodeBlockItemListSyntax,
70657067
_ unexpectedBetweenStatementsAndEOFToken: UnexpectedNodesSyntax? = nil,
70667068
eofToken: TokenSyntax = .endOfFileToken(),
@@ -7070,7 +7072,9 @@ extension SourceFileSyntax {
70707072
) {
70717073
self.init(
70727074
leadingTrivia: leadingTrivia,
7073-
unexpectedBeforeStatements,
7075+
unexpectedBeforeShebang,
7076+
shebang: shebang,
7077+
unexpectedBetweenShebangAndStatements,
70747078
statements: statements,
70757079
unexpectedBetweenStatementsAndEOFToken,
70767080
endOfFileToken: eofToken,

Sources/SwiftSyntax/generated/TokenKind.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ public enum TokenKind: Hashable {
5858
case rightParen
5959
case rightSquare
6060
case semicolon
61+
case shebang(String)
6162
case singleQuote
6263
case stringQuote
6364
case stringSegment(String)
@@ -156,6 +157,8 @@ public enum TokenKind: Hashable {
156157
return #"]"#
157158
case .semicolon:
158159
return #";"#
160+
case .shebang(let text):
161+
return text
159162
case .singleQuote:
160163
return #"'"#
161164
case .stringQuote:
@@ -347,6 +350,8 @@ public enum TokenKind: Hashable {
347350
return true
348351
case .semicolon:
349352
return true
353+
case .shebang:
354+
return false
350355
case .singleQuote:
351356
return true
352357
case .stringQuote:
@@ -452,6 +457,8 @@ extension TokenKind: Equatable {
452457
return true
453458
case (.semicolon, .semicolon):
454459
return true
460+
case (.shebang(let lhsText), .shebang(let rhsText)):
461+
return lhsText == rhsText
455462
case (.singleQuote, .singleQuote):
456463
return true
457464
case (.stringQuote, .stringQuote):
@@ -519,6 +526,7 @@ public enum RawTokenKind: UInt8, Equatable, Hashable {
519526
case rightParen
520527
case rightSquare
521528
case semicolon
529+
case shebang
522530
case singleQuote
523531
case stringQuote
524532
case stringSegment
@@ -700,6 +708,8 @@ public enum RawTokenKind: UInt8, Equatable, Hashable {
700708
return true
701709
case .semicolon:
702710
return true
711+
case .shebang:
712+
return false
703713
case .singleQuote:
704714
return true
705715
case .stringQuote:
@@ -843,6 +853,8 @@ extension TokenKind {
843853
case .semicolon:
844854
precondition(text.isEmpty || rawKind.defaultText.map(String.init) == text)
845855
return .semicolon
856+
case .shebang:
857+
return .shebang(text)
846858
case .singleQuote:
847859
precondition(text.isEmpty || rawKind.defaultText.map(String.init) == text)
848860
return .singleQuote
@@ -952,6 +964,8 @@ extension TokenKind {
952964
return (.rightSquare, nil)
953965
case .semicolon:
954966
return (.semicolon, nil)
967+
case .shebang(let str):
968+
return (.shebang, str)
955969
case .singleQuote:
956970
return (.singleQuote, nil)
957971
case .stringQuote:

Sources/SwiftSyntax/generated/Tokens.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,22 @@ extension TokenSyntax {
651651
)
652652
}
653653

654+
public static func shebang(
655+
_ text: String,
656+
leadingTrivia: Trivia = [],
657+
trailingTrivia: Trivia = [],
658+
presence: SourcePresence = .present
659+
660+
) -> TokenSyntax {
661+
return TokenSyntax(
662+
.shebang(text),
663+
leadingTrivia: leadingTrivia,
664+
trailingTrivia: trailingTrivia,
665+
presence: presence
666+
667+
)
668+
}
669+
654670
public static func singleQuoteToken(
655671
leadingTrivia: Trivia = [],
656672
trailingTrivia: Trivia = [],

0 commit comments

Comments
 (0)