Skip to content

Commit 7186e5a

Browse files
committed
Add *Syntax.parse methods for the nodes that we support parsing
Adds a new `SyntaxParseable` protocol with a single `parse(from:)` method. Any nodes with `parser_function` defined implement this protocol. Other updates: - `SyntaxExpressibleByStringInterpolation` nodes to use these new methods - Parser tests to check against `*Syntax` rather than `Raw*Syntax` nodes - Builder methods to to take nodes (which are expressible by string literals) rather than `String`
1 parent 2c46bac commit 7186e5a

26 files changed

+731
-472
lines changed

Package.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,11 @@ let package = Package(
112112
dependencies: ["SwiftDiagnostics", "SwiftSyntax"],
113113
exclude: [
114114
"CMakeLists.txt",
115-
"README.md",
116-
"TypeAttribute.swift.gyb",
117115
"DeclarationModifier.swift.gyb",
118116
"DeclarationAttribute.swift.gyb",
117+
"Parser+Entry.swift.gyb",
118+
"README.md",
119+
"TypeAttribute.swift.gyb",
119120
]
120121
),
121122
.target(

Sources/SwiftCompilerSupport/ConsistencyCheck.swift

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
import SwiftOperators
14-
@_spi(Testing) @_spi(RawSyntax) import SwiftParser
14+
import SwiftParser
1515
import SwiftParserDiagnostics
16-
@_spi(RawSyntax) import SwiftSyntax
16+
import SwiftSyntax
1717

1818
extension Syntax {
1919
/// Whether this syntax node is or is enclosed within a #if.
@@ -51,11 +51,11 @@ public func _parserConsistencyCheck(
5151
var parser = Parser(buffer)
5252
return withExtendedLifetime(parser) { () -> CInt in
5353
// Parse the source file
54-
let rawSourceFile = parser.parseSourceFile()
54+
let sourceFile = SourceFileSyntax.parse(from: &parser)
5555

5656
// Round-trip test.
5757
if flags & 0x01 != 0 {
58-
if rawSourceFile.raw.syntaxTextBytes != [UInt8](buffer) {
58+
if sourceFile.syntaxTextBytes != [UInt8](buffer) {
5959
print(
6060
"\(String(cString: filename)): error: file failed to round-trip")
6161
return 1
@@ -66,7 +66,6 @@ public func _parserConsistencyCheck(
6666
if flags & 0x02 != 0 {
6767
var anyDiags = false
6868

69-
let sourceFile = Syntax(raw: rawSourceFile.raw).as(SourceFileSyntax.self)!
7069
let diags = ParseDiagnosticsGenerator.diagnostics(
7170
for: sourceFile)
7271
for diag in diags {

Sources/SwiftParser/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ add_library(SwiftParser STATIC
3434

3535
gyb_generated/DeclarationAttribute.swift
3636
gyb_generated/DeclarationModifier.swift
37+
gyb_generated/Parser+Entry.swift
3738
gyb_generated/TypeAttribute.swift)
3839

3940
target_link_libraries(SwiftParser PUBLIC
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
%{
2+
from gyb_syntax_support import *
3+
# -*- mode: Swift -*-
4+
# Ignore the following admonition it applies to the resulting .swift file only
5+
}%
6+
//// Automatically Generated From Entry.swift.gyb.
7+
//// Do Not Edit Directly!
8+
//===----------------------------------------------------------------------===//
9+
//
10+
// This source file is part of the Swift.org open source project
11+
//
12+
// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
13+
// Licensed under Apache License v2.0 with Runtime Library Exception
14+
//
15+
// See https://swift.org/LICENSE.txt for license information
16+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
17+
//
18+
//===----------------------------------------------------------------------===//
19+
20+
@_spi(RawSyntax) import SwiftSyntax
21+
22+
extension Parser {
23+
/// Parse the source code in the given string as Swift source file. See
24+
/// `Parser.init` for more details.
25+
public static func parse(
26+
source: String,
27+
parseTransition: IncrementalParseTransition? = nil
28+
) -> SourceFileSyntax {
29+
var parser = Parser(source)
30+
return SourceFileSyntax.parse(from: &parser)
31+
}
32+
33+
/// Parse the source code in the given string as Swift source file. See
34+
/// `Parser.init` for more details.
35+
public static func parse(
36+
source: UnsafeBufferPointer<UInt8>,
37+
maximumNestingLevel: Int? = nil,
38+
parseTransition: IncrementalParseTransition? = nil
39+
) -> SourceFileSyntax {
40+
var parser = Parser(source, maximumNestingLevel: maximumNestingLevel)
41+
return SourceFileSyntax.parse(from: &parser)
42+
}
43+
}
44+
45+
public protocol SyntaxParseable: SyntaxProtocol {
46+
static func parse(from parser: inout Parser) -> Self
47+
}
48+
49+
% for node in SYNTAX_NODES:
50+
% if node.parser_function:
51+
extension ${node.name}: SyntaxParseable {
52+
public static func parse(from parser: inout Parser) -> ${node.name} {
53+
let node = parser.${node.parser_function}()
54+
return parser.parseRemainder(into: node).syntax
55+
}
56+
}
57+
58+
% end
59+
% end
60+
fileprivate extension Parser {
61+
mutating func parseRemainder<R: RawSyntaxNodeProtocol>(into: R) -> R {
62+
guard let layout = into.raw.layoutView else {
63+
return into
64+
}
65+
66+
let remainingTokens = self.consumeRemainingTokens()
67+
if remainingTokens.isEmpty {
68+
return into
69+
}
70+
71+
let unexpected = RawUnexpectedNodesSyntax(elements: remainingTokens, arena: self.arena)
72+
let withUnexpected = layout.replacingChild(at: layout.children.count - 1, with: unexpected.raw, arena: self.arena)
73+
return R.init(withUnexpected)!
74+
}
75+
}

Sources/SwiftParser/Parser.swift

Lines changed: 29 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -12,39 +12,6 @@
1212

1313
@_spi(RawSyntax) import SwiftSyntax
1414

15-
extension Parser {
16-
/// Parse the source code in the given string as Swift source file.
17-
public static func parse(
18-
source: String,
19-
parseTransition: IncrementalParseTransition? = nil
20-
) -> SourceFileSyntax {
21-
var source = source
22-
source.makeContiguousUTF8()
23-
return source.withUTF8 { buffer in
24-
return parse(source: buffer, parseTransition: parseTransition)
25-
}
26-
}
27-
28-
/// Parse the source code in the given string as Swift source file.
29-
/// If `maximumNestingLevel` is set, the parser will stop if a nesting level
30-
/// that is greater than this value is reached to avoid overflowing the stack.
31-
/// The nesting level is increased whenever a bracketed expression like `(`
32-
/// or `{` is stared.
33-
public static func parse(
34-
source: UnsafeBufferPointer<UInt8>,
35-
maximumNestingLevel: Int? = nil,
36-
parseTransition: IncrementalParseTransition? = nil
37-
) -> SourceFileSyntax {
38-
var parser = Parser(source, maximumNestingLevel: maximumNestingLevel)
39-
// Extended lifetime is required because `SyntaxArena` in the parser must
40-
// be alive until `Syntax(raw:)` retains the arena.
41-
return withExtendedLifetime(parser) {
42-
let rawSourceFile = parser.parseSourceFile()
43-
return rawSourceFile.syntax
44-
}
45-
}
46-
}
47-
4815
/// A parser for the Swift programming language.
4916
///
5017
/// `Parser` implements a recursive descent parser that produces a SwiftSyntax
@@ -142,17 +109,37 @@ public struct Parser: TokenConsumer {
142109
public static let defaultMaximumNestingLevel = 256
143110
#endif
144111

145-
/// Initializes a Parser from the given input buffer.
146-
///
147-
/// The lexer will copy any string data it needs from the resulting buffer
148-
/// so it is the caller's responsibility to free it.
112+
/// Initializes a `Parser` from the given string.
113+
public init(_ input: String, maximumNestingLevel: Int? = nil) {
114+
self.maximumNestingLevel = maximumNestingLevel ?? Self.defaultMaximumNestingLevel
115+
116+
self.arena = ParsingSyntaxArena(
117+
parseTriviaFunction: TriviaParser.parseTrivia(_:position:))
118+
119+
var input = input
120+
input.makeContiguousUTF8()
121+
let interned = input.withUTF8 { [arena] buffer in
122+
return arena.internSourceBuffer(buffer)
123+
}
124+
125+
self.lexemes = Lexer.tokenize(interned)
126+
self.currentToken = self.lexemes.advance()
127+
}
128+
129+
/// Initializes a `Parser` from the given input buffer.
149130
///
150131
/// - Parameters
151132
/// - input: An input buffer containing Swift source text.
133+
/// - maximumNestingLevel: To avoid overflowing the stack, the parser will
134+
/// stop if a nesting level greater than this value
135+
/// is reached. The nesting level is increased
136+
/// whenever a bracketed expression like `(` or `{`
137+
/// is started. `defaultMaximumNestingLevel` is used
138+
/// if this is `nil`.
152139
/// - arena: Arena the parsing syntax are made into. If it's `nil`, a new
153140
/// arena is created automatically, and `input` copied into the
154-
/// arena. If non-`nil`, `input` must be the registered source
155-
/// buffer of `arena` or a slice of the source buffer.
141+
/// arena. If non-`nil`, `input` must be within its registered
142+
/// source buffer or allocator.
156143
public init(_ input: UnsafeBufferPointer<UInt8>, maximumNestingLevel: Int? = nil, arena: ParsingSyntaxArena? = nil) {
157144
self.maximumNestingLevel = maximumNestingLevel ?? Self.defaultMaximumNestingLevel
158145

@@ -166,6 +153,7 @@ public struct Parser: TokenConsumer {
166153
parseTriviaFunction: TriviaParser.parseTrivia(_:position:))
167154
sourceBuffer = self.arena.internSourceBuffer(input)
168155
}
156+
169157
self.lexemes = Lexer.tokenize(sourceBuffer)
170158
self.currentToken = self.lexemes.advance()
171159
}
@@ -419,7 +407,7 @@ extension Parser {
419407
/// This should be set if keywords aren't strong recovery marker at this
420408
/// position, e.g. because the parser expects a punctuator next.
421409
@_spi(RawSyntax)
422-
public mutating func expectIdentifier(allowIdentifierLikeKeywords: Bool = true, keywordRecovery: Bool = false) -> (RawUnexpectedNodesSyntax?, Token) {
410+
public mutating func expectIdentifier(allowIdentifierLikeKeywords: Bool = true, keywordRecovery: Bool = false) -> (RawUnexpectedNodesSyntax?, RawTokenSyntax) {
423411
if allowIdentifierLikeKeywords {
424412
if let (_, handle) = self.canRecoverTo(anyIn: IdentifierTokens.self) {
425413
return self.eat(handle)
@@ -454,7 +442,7 @@ extension Parser {
454442
}
455443

456444
@_spi(RawSyntax)
457-
public mutating func expectIdentifierOrRethrows() -> (RawUnexpectedNodesSyntax?, Token) {
445+
public mutating func expectIdentifierOrRethrows() -> (RawUnexpectedNodesSyntax?, RawTokenSyntax) {
458446
if let (_, handle) = self.canRecoverTo(anyIn: IdentifierOrRethrowsTokens.self) {
459447
return self.eat(handle)
460448
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
//// Automatically Generated From Entry.swift.gyb.
2+
//// Do Not Edit Directly!
3+
//===----------------------------------------------------------------------===//
4+
//
5+
// This source file is part of the Swift.org open source project
6+
//
7+
// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
8+
// Licensed under Apache License v2.0 with Runtime Library Exception
9+
//
10+
// See https://swift.org/LICENSE.txt for license information
11+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
@_spi(RawSyntax) import SwiftSyntax
16+
17+
extension Parser {
18+
/// Parse the source code in the given string as Swift source file. See
19+
/// `Parser.init` for more details.
20+
public static func parse(
21+
source: String,
22+
parseTransition: IncrementalParseTransition? = nil
23+
) -> SourceFileSyntax {
24+
var parser = Parser(source)
25+
return SourceFileSyntax.parse(from: &parser)
26+
}
27+
28+
/// Parse the source code in the given string as Swift source file. See
29+
/// `Parser.init` for more details.
30+
public static func parse(
31+
source: UnsafeBufferPointer<UInt8>,
32+
maximumNestingLevel: Int? = nil,
33+
parseTransition: IncrementalParseTransition? = nil
34+
) -> SourceFileSyntax {
35+
var parser = Parser(source, maximumNestingLevel: maximumNestingLevel)
36+
return SourceFileSyntax.parse(from: &parser)
37+
}
38+
}
39+
40+
public protocol SyntaxParseable: SyntaxProtocol {
41+
static func parse(from parser: inout Parser) -> Self
42+
}
43+
44+
extension DeclSyntax: SyntaxParseable {
45+
public static func parse(from parser: inout Parser) -> DeclSyntax {
46+
let node = parser.parseDeclaration()
47+
return parser.parseRemainder(into: node).syntax
48+
}
49+
}
50+
51+
extension ExprSyntax: SyntaxParseable {
52+
public static func parse(from parser: inout Parser) -> ExprSyntax {
53+
let node = parser.parseExpression()
54+
return parser.parseRemainder(into: node).syntax
55+
}
56+
}
57+
58+
extension StmtSyntax: SyntaxParseable {
59+
public static func parse(from parser: inout Parser) -> StmtSyntax {
60+
let node = parser.parseStatement()
61+
return parser.parseRemainder(into: node).syntax
62+
}
63+
}
64+
65+
extension TypeSyntax: SyntaxParseable {
66+
public static func parse(from parser: inout Parser) -> TypeSyntax {
67+
let node = parser.parseType()
68+
return parser.parseRemainder(into: node).syntax
69+
}
70+
}
71+
72+
extension PatternSyntax: SyntaxParseable {
73+
public static func parse(from parser: inout Parser) -> PatternSyntax {
74+
let node = parser.parsePattern()
75+
return parser.parseRemainder(into: node).syntax
76+
}
77+
}
78+
79+
extension MemberDeclBlockSyntax: SyntaxParseable {
80+
public static func parse(from parser: inout Parser) -> MemberDeclBlockSyntax {
81+
let node = parser.parseMemberDeclList()
82+
return parser.parseRemainder(into: node).syntax
83+
}
84+
}
85+
86+
extension SourceFileSyntax: SyntaxParseable {
87+
public static func parse(from parser: inout Parser) -> SourceFileSyntax {
88+
let node = parser.parseSourceFile()
89+
return parser.parseRemainder(into: node).syntax
90+
}
91+
}
92+
93+
extension SwitchCaseSyntax: SyntaxParseable {
94+
public static func parse(from parser: inout Parser) -> SwitchCaseSyntax {
95+
let node = parser.parseSwitchCase()
96+
return parser.parseRemainder(into: node).syntax
97+
}
98+
}
99+
100+
extension CatchClauseSyntax: SyntaxParseable {
101+
public static func parse(from parser: inout Parser) -> CatchClauseSyntax {
102+
let node = parser.parseCatchClause()
103+
return parser.parseRemainder(into: node).syntax
104+
}
105+
}
106+
107+
extension GenericParameterClauseSyntax: SyntaxParseable {
108+
public static func parse(from parser: inout Parser) -> GenericParameterClauseSyntax {
109+
let node = parser.parseGenericParameters()
110+
return parser.parseRemainder(into: node).syntax
111+
}
112+
}
113+
114+
fileprivate extension Parser {
115+
mutating func parseRemainder<R: RawSyntaxNodeProtocol>(into: R) -> R {
116+
guard let layout = into.raw.layoutView else {
117+
return into
118+
}
119+
120+
let remainingTokens = self.consumeRemainingTokens()
121+
if remainingTokens.isEmpty {
122+
return into
123+
}
124+
125+
let unexpected = RawUnexpectedNodesSyntax(elements: remainingTokens, arena: self.arena)
126+
let withUnexpected = layout.replacingChild(at: layout.children.count - 1, with: unexpected.raw, arena: self.arena)
127+
return R.init(withUnexpected)!
128+
}
129+
}

Sources/SwiftSyntax/Raw/RawSyntaxLayoutView.swift

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,5 @@ public struct RawSyntaxLayoutView {
166166
public var children: RawSyntaxBuffer {
167167
layoutData.layout
168168
}
169-
170-
/// The number of children, `present` or `missing`, in this node.
171-
@_spi(RawSyntax)
172-
public var numberOfChildren: Int {
173-
return children.count
174-
}
175169
}
176170

Sources/SwiftSyntax/Raw/RawSyntaxNodeProtocol.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,10 +236,13 @@ public protocol RawSyntaxToSyntax: RawSyntaxNodeProtocol {
236236
associatedtype SyntaxType: SyntaxProtocol
237237
}
238238

239+
// TODO: Is it worth having this?
240+
// The only place that should be using it is the parser, which only converts
241+
// to `Syntax` in a few places anyway - we could just do the case there.
239242
@_spi(RawSyntax)
240243
public extension RawSyntaxToSyntax {
241244
/// Realizes a `Syntax` node for this `RawSyntax` node.
242245
var syntax: SyntaxType {
243-
return Syntax(raw: raw).as(SyntaxType.self)!
246+
return Syntax(raw: raw).cast(SyntaxType.self)
244247
}
245248
}

0 commit comments

Comments
 (0)