Skip to content

Commit 1e8152f

Browse files
committed
Generate grammar doc comments for layout nodes
Generate a `Grammar` and a `Children` section as doc-comments for layout nodes. The `Grammar` section is particularly useful for fairly simple syntax nodes to convey their structure. The `Children` section allows you to see the children of the syntax node at a glance in the source order.
1 parent 9b513ab commit 1e8152f

File tree

9 files changed

+2980
-0
lines changed

9 files changed

+2980
-0
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SwiftSyntax
14+
15+
/// Generates grammar doc comments for syntax nodes.
16+
struct GrammarGenerator {
17+
let lookThroughCollections: Bool
18+
19+
private func grammar(for tokenChoice: TokenChoice) -> String {
20+
switch tokenChoice {
21+
case .keyword(text: let text):
22+
return "`'\(text)'`"
23+
case .token(tokenKind: let tokenKind):
24+
let token = SYNTAX_TOKEN_MAP[tokenKind]!
25+
if let tokenText = token.text {
26+
return "`'\(tokenText)'`"
27+
} else {
28+
return "`<\(token.swiftKind)>`"
29+
}
30+
}
31+
}
32+
33+
private func grammar(for child: Child) -> String {
34+
let optionality = child.isOptional ? "?" : ""
35+
switch child.kind {
36+
case .node(let kind):
37+
return "``\(kind.syntaxType)``\(optionality)"
38+
case .nodeChoices(let choices):
39+
let choicesDescriptions = choices.map { grammar(for: $0) }
40+
return "(\(choicesDescriptions.joined(separator: " | ")))\(optionality)"
41+
case .collection(let kind, _):
42+
if lookThroughCollections {
43+
let collectionElementTypes = SYNTAX_NODE_MAP[kind]!.collectionNode!.elementChoices
44+
if collectionElementTypes.count == 1 {
45+
return "``\(collectionElementTypes.first!.syntaxType)`` `*`"
46+
} else {
47+
let choicesDescriptions = collectionElementTypes.map { "``\($0.syntaxType)``" }
48+
return "(\(choicesDescriptions.joined(separator: " | "))) `*`"
49+
}
50+
} else {
51+
return "``\(kind.syntaxType)``"
52+
}
53+
case .token(let choices, _, _):
54+
if choices.count == 1 {
55+
return "\(grammar(for: choices.first!))\(optionality)"
56+
} else {
57+
let choicesDescriptions = choices.map { grammar(for: $0) }
58+
return "(\(choicesDescriptions.joined(separator: " | ")))\(optionality)"
59+
}
60+
}
61+
}
62+
63+
/// Generates a single line grammar for a syntax node.
64+
static func grammar(for children: [Child]) -> String {
65+
let generator = GrammarGenerator(lookThroughCollections: true)
66+
return
67+
children
68+
.filter { !$0.isUnexpectedNodes }
69+
.map { generator.grammar(for: $0) }
70+
.joined(separator: "\n")
71+
}
72+
73+
/// Generates a
74+
static func childrenList(for children: [Child]) -> String {
75+
let generator = GrammarGenerator(lookThroughCollections: false)
76+
return
77+
children
78+
.filter { !$0.isUnexpectedNodes }
79+
.map { " - `\($0.varName)`: \(generator.grammar(for: $0))" }
80+
.joined(separator: "\n")
81+
}
82+
}

CodeGeneration/Sources/SyntaxSupport/Node.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,24 @@ public struct LayoutNode {
208208
preconditionFailure("NodeLayoutView must wrap a Node with data `.layout`")
209209
}
210210
}
211+
212+
public var grammar: SwiftSyntax.Trivia {
213+
guard !children.isEmpty else {
214+
return []
215+
}
216+
217+
return docCommentTrivia(
218+
from: """
219+
### Grammar
220+
221+
\(GrammarGenerator.grammar(for: children))
222+
223+
### Children
224+
225+
\(GrammarGenerator.childrenList(for: children))
226+
"""
227+
)
228+
}
211229
}
212230

213231
/// Provides a view into a collection node that offers access to the

CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/SyntaxNodesFile.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ func syntaxNode(emitKind: SyntaxNodeKind) -> SourceFileSyntax {
4040
// MARK: - \(raw: node.kind.syntaxType)
4141
4242
\(raw: node.documentation)
43+
\(raw: node.documentation.isEmpty ? "" : "///")
44+
\(raw: node.grammar)
4345
public struct \(raw: node.kind.syntaxType): \(raw: node.baseType.syntaxBaseName)Protocol, SyntaxHashable
4446
"""
4547
) {

0 commit comments

Comments
 (0)