Skip to content

Commit 7fd8a8b

Browse files
authored
Merge pull request swiftlang#139 from akkyie/swift-syntax-builder
Initial implementation of declarative and type-safe wrapper
2 parents 89ce45a + 3dc22c9 commit 7fd8a8b

14 files changed

+715
-1
lines changed

Package.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ let package = Package(
88
.target(name: "_CSwiftSyntax"),
99
.target(name: "SwiftSyntax", dependencies: ["_CSwiftSyntax"]),
1010
.testTarget(name: "SwiftSyntaxTest", dependencies: ["SwiftSyntax"], exclude: ["Inputs"]),
11+
.target(name: "SwiftSyntaxBuilder", dependencies: ["SwiftSyntax"]),
12+
.testTarget(name: "SwiftSyntaxBuilderTest", dependencies: ["SwiftSyntaxBuilder"]),
1113
.target(name: "lit-test-helper", dependencies: ["SwiftSyntax"])
1214
]
1315
)
@@ -20,6 +22,8 @@ import Darwin.C
2022

2123
if getenv("SWIFT_SYNTAX_BUILD_SCRIPT") == nil {
2224
package.products.append(.library(name: "SwiftSyntax", targets: ["SwiftSyntax"]))
25+
package.products.append(.library(name: "SwiftSyntaxBuilder", targets: ["SwiftSyntaxBuilder"]))
2326
} else {
2427
package.products.append(.library(name: "SwiftSyntax", type: .dynamic, targets: ["SwiftSyntax"]))
25-
}
28+
package.products.append(.library(name: "SwiftSyntaxBuilder", type: .dynamic, targets: ["SwiftSyntaxBuilder"]))
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2019 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+
// MARK: - Protocols
16+
17+
public protocol DeclListBuildable: SyntaxListBuildable {
18+
func buildDeclList(format: Format, leadingTrivia: Trivia) -> [DeclSyntax]
19+
}
20+
21+
public protocol DeclBuildable: SyntaxBuildable, DeclListBuildable {
22+
func buildDecl(format: Format, leadingTrivia: Trivia) -> DeclSyntax
23+
}
24+
25+
extension DeclBuildable {
26+
public func buildSyntax(format: Format, leadingTrivia: Trivia) -> Syntax {
27+
buildDecl(format: format, leadingTrivia: leadingTrivia)
28+
}
29+
30+
public func buildDeclList(format: Format, leadingTrivia: Trivia) -> [DeclSyntax] {
31+
[buildDecl(format: format, leadingTrivia: leadingTrivia)]
32+
}
33+
}
34+
35+
// MARK: - Function Builder
36+
37+
@_functionBuilder
38+
public struct DeclListBuilder {
39+
public static func buildBlock(_ builders: DeclListBuildable...) -> DeclListBuildable {
40+
DeclList(builders: builders)
41+
}
42+
}
43+
44+
// MARK: - List
45+
46+
public struct DeclList: DeclListBuildable {
47+
let builders: [DeclListBuildable]
48+
49+
public func buildDeclList(format: Format, leadingTrivia: Trivia) -> [DeclSyntax] {
50+
builders.flatMap { $0.buildDeclList(format: format, leadingTrivia: leadingTrivia) }
51+
}
52+
53+
public func buildSyntaxList(format: Format, leadingTrivia: Trivia) -> [Syntax] {
54+
buildDeclList(format: format, leadingTrivia: leadingTrivia)
55+
}
56+
}
57+
58+
extension DeclList {
59+
public static let empty: DeclList = DeclList(builders: [])
60+
}
61+
62+
// MARK: - Buildables
63+
64+
// MARK: Import
65+
66+
public struct Import: DeclBuildable {
67+
let moduleName: String
68+
69+
public init(_ moduleName: String) {
70+
self.moduleName = moduleName
71+
}
72+
73+
public func buildDecl(format: Format, leadingTrivia: Trivia) -> DeclSyntax {
74+
let importToken = Tokens.import.withLeadingTrivia(leadingTrivia)
75+
let moduleNameToken = SyntaxFactory.makeIdentifier(moduleName)
76+
77+
return ImportDeclSyntax {
78+
$0.useImportTok(importToken)
79+
$0.addPathComponent(AccessPathComponentSyntax {
80+
$0.useName(moduleNameToken)
81+
})
82+
}
83+
}
84+
}
85+
86+
// MARK: Variables
87+
88+
public protocol VariableMutability {
89+
static var token: TokenSyntax { get }
90+
}
91+
92+
public enum VariableLetMutability: VariableMutability {
93+
public static let token = Tokens.let
94+
}
95+
96+
public enum VariableVarMutability: VariableMutability {
97+
public static let token = Tokens.var
98+
}
99+
100+
public typealias Let = Variable<VariableLetMutability>
101+
public typealias Var = Variable<VariableVarMutability>
102+
103+
public struct Variable<Mutability: VariableMutability>: DeclBuildable {
104+
let name: String
105+
let type: String
106+
let initializer: ExprBuildable?
107+
108+
public init(_ name: String, of type: String, value: ExprBuildable? = nil) {
109+
self.name = name
110+
self.type = type
111+
self.initializer = value
112+
}
113+
114+
public func buildDecl(format: Format, leadingTrivia: Trivia) -> DeclSyntax {
115+
let mutabilityKeyword = Mutability.token.withLeadingTrivia(leadingTrivia)
116+
117+
let nameIdentifier = SyntaxFactory.makeIdentifier(name)
118+
let namePattern = SyntaxFactory.makeIdentifierPattern(identifier: nameIdentifier)
119+
120+
let typeIdentifier = SyntaxFactory.makeTypeIdentifier(type)
121+
let typeAnnotation = SyntaxFactory.makeTypeAnnotation(
122+
colon: Tokens.colon,
123+
type: typeIdentifier
124+
)
125+
126+
let initClause = initializer.flatMap { builder -> InitializerClauseSyntax in
127+
let expr = builder.buildExpr(format: format, leadingTrivia: .zero)
128+
return SyntaxFactory.makeInitializerClause(equal: Tokens.equal, value: expr)
129+
}
130+
131+
return VariableDeclSyntax {
132+
$0.useLetOrVarKeyword(mutabilityKeyword)
133+
$0.addBinding(PatternBindingSyntax {
134+
$0.usePattern(namePattern)
135+
$0.useTypeAnnotation(typeAnnotation)
136+
137+
if let initClause = initClause {
138+
$0.useInitializer(initClause)
139+
}
140+
})
141+
}
142+
}
143+
}
144+
145+
// MARK: Struct
146+
147+
public struct Struct: DeclBuildable {
148+
let name: String
149+
let memberList: DeclListBuildable
150+
151+
public init(
152+
_ name: String,
153+
@DeclListBuilder buildMemberList: () -> DeclListBuildable = { DeclList.empty }
154+
) {
155+
self.name = name
156+
self.memberList = buildMemberList()
157+
}
158+
159+
public func buildDecl(format: Format, leadingTrivia: Trivia) -> DeclSyntax {
160+
let structKeyword = Tokens.struct.withLeadingTrivia(leadingTrivia)
161+
162+
let declList = memberList.buildDeclList(
163+
format: format._indented(),
164+
leadingTrivia: .zero
165+
)
166+
167+
return StructDeclSyntax {
168+
$0.useStructKeyword(structKeyword)
169+
$0.useIdentifier(SyntaxFactory.makeIdentifier(name))
170+
$0.useMembers(MemberDeclBlockSyntax {
171+
$0.useLeftBrace(Tokens.leftBrace.withLeadingTrivia(.spaces(1)))
172+
$0.useRightBrace(Tokens.rightBrace.withLeadingTrivia(.newlines(1) + format._makeIndent()))
173+
174+
let format = format._indented()
175+
for decl in declList {
176+
let member = SyntaxFactory
177+
.makeMemberDeclListItem(decl: decl, semicolon: nil)
178+
.withLeadingTrivia(.newlines(1) + format._makeIndent())
179+
$0.addMember(member)
180+
}
181+
})
182+
}
183+
}
184+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2019 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+
// MARK: - Protocols
16+
17+
public protocol ExprListBuildable: SyntaxListBuildable {
18+
func buildExprList(format: Format, leadingTrivia: Trivia) -> [ExprSyntax]
19+
}
20+
21+
public protocol ExprBuildable: SyntaxBuildable, ExprListBuildable {
22+
func buildExpr(format: Format, leadingTrivia: Trivia) -> ExprSyntax
23+
}
24+
25+
extension ExprBuildable {
26+
public func buildSyntax(format: Format, leadingTrivia: Trivia) -> Syntax {
27+
buildExpr(format: format, leadingTrivia: leadingTrivia)
28+
}
29+
30+
public func buildExprList(format: Format, leadingTrivia: Trivia) -> [ExprSyntax] {
31+
[buildExpr(format: format, leadingTrivia: leadingTrivia)]
32+
}
33+
}
34+
35+
// MARK: - Buildables
36+
37+
// MARK: Integer Literal
38+
39+
public struct IntegerLiteral: ExprBuildable {
40+
let value: Int
41+
42+
public init(_ value: Int) {
43+
self.value = value
44+
}
45+
46+
public func buildExpr(format: Format, leadingTrivia: Trivia) -> ExprSyntax {
47+
SyntaxFactory.makeIntegerLiteralExpr(
48+
digits: SyntaxFactory.makeIntegerLiteral(String(value))
49+
).withLeadingTrivia(leadingTrivia)
50+
}
51+
}
52+
53+
extension IntegerLiteral: ExpressibleByIntegerLiteral {
54+
public init(integerLiteral value: Int) {
55+
self.init(value)
56+
}
57+
}
58+
59+
// MARK: String Literal
60+
61+
public struct StringLiteral: ExprBuildable {
62+
let value: String
63+
64+
public init(_ value: String) {
65+
self.value = value
66+
}
67+
68+
public func buildExpr(format: Format, leadingTrivia: Trivia) -> ExprSyntax {
69+
SyntaxFactory.makeStringLiteralExpr(value)
70+
.withLeadingTrivia(leadingTrivia)
71+
}
72+
}
73+
74+
extension StringLiteral: ExpressibleByStringLiteral {
75+
public init(stringLiteral value: String) {
76+
self.init(value)
77+
}
78+
}
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2019 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+
public struct Format {
16+
public let indentWidth: Int
17+
18+
private var indents: Int = 0
19+
20+
public init(indentWidth: Int = 4) {
21+
self.indentWidth = indentWidth
22+
}
23+
}
24+
25+
extension Format {
26+
public func _indented() -> Format {
27+
var copy = self
28+
copy.indents += 1
29+
return copy
30+
}
31+
32+
public func _makeIndent() -> Trivia {
33+
return indents == 0 ? .zero : Trivia.spaces(indents * indentWidth)
34+
}
35+
}

Sources/SwiftSyntaxBuilder/README.md

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# SwiftSyntaxBuilder
2+
3+
Declarative and type-safe wrapper around SwiftSyntax.
4+
5+
## Example Usage
6+
7+
```swift
8+
let sourceFile = SourceFile {
9+
Import("SwiftSyntax")
10+
11+
Struct("ExampleStruct") {
12+
Let("syntax", of: "Syntax")
13+
}
14+
}
15+
16+
let syntax = sourceFile.buildSyntax(format: format, leadingTrivia: .zero)
17+
18+
var text = ""
19+
syntax.write(to: &text)
20+
print(text)
21+
```
22+
23+
prints:
24+
25+
```swift
26+
import SwiftSyntax
27+
struct ExampleStruct {
28+
let syntax: Syntax
29+
}
30+
```

0 commit comments

Comments
 (0)