[入門]SwiftSyntaxでコードを生成する[SwiftSyntaxBuilder]
[入門]SwiftSyntaxでコードを生成する
SwiftSyntaxでコードを生成する方法を紹介していきます。
レベル別に進めていきます。レベルが高くなるほど安全にコードを生成できるようになっています。
SwiftSyntaxBuilder
のテストであるSwiftSyntaxBuilderTestを参考に記事を作成しました。
Swift AST Explorerを使用することで、ソースの構造を確認することができます。
注意事項
SwiftSyntaxはSwiftの進化についていくために、アップデートごとに破壊的変更が入ります。
この記事ではSwift 5.8
基準である508.0.1のバージョンを基準に進めていきます。
生成したいコード
以下のコードをSwiftSyntax(SwiftSyntaxBuilder)を使って、コードを組み上げていきます。
レベル1, 2, 3に分けて進めていきます。
import Foundation
class SampleModel {
private let title: String = "Hello"
}
レベル1
上のようなコードを出力したい場合、下のようにコードの要素を組んでいく必要があります。
レベル1ではコードを組むのではなく、DeclSyntax
で直接指定しているような形になっています。
これでもいいのですが、レベルを上げて抽象度を上げていきます。
最後に人が見やすいようにformatted()しています。
import SwiftSyntax
import SwiftSyntaxBuilder
let source = SourceFileSyntax {
DeclSyntax("import Foundation")
ClassDeclSyntax(identifier: "SampleModel") {
DeclSyntax(#"private let title: String = "Hello""#)
}
}
print(source.formatted())
レベル2
importの部分をImportDeclSyntax
に置き換えます。
import SwiftSyntax
import SwiftSyntaxBuilder
let source = SourceFileSyntax {
ImportDeclSyntax(
path: AccessPathSyntax([AccessPathComponentSyntax(name: "Foundation")])
)
ClassDeclSyntax(identifier: "SampleModel") {
DeclSyntax(#"private let title: String = "Hello""#)
}
}
print(source.formatted())
レベル3
title
を宣言している部分をVariableDeclSyntax
に置き換えます。
private
let
title
String
"Hello"
の5つの要素を個別に設定します。
private let title: String = "Hello"
import SwiftSyntax
import SwiftSyntaxBuilder
let source = SourceFileSyntax {
ImportDeclSyntax(
path: AccessPathSyntax([AccessPathComponentSyntax(name: "Foundation")])
)
ClassDeclSyntax(identifier: "SampleModel") {
VariableDeclSyntax(
modifiers: [DeclModifierSyntax(name: .private)],
letOrVarKeyword: .let
) {
PatternBindingSyntax(
pattern: PatternSyntax("title"),
typeAnnotation: TypeAnnotationSyntax(
type: TypeSyntax("String")
),
initializer: InitializerClauseSyntax(
value: StringLiteralExprSyntax(content: "Hello")
)
)
}
}
}
print(source.formatted())
サンプル1
let source = SourceFileSyntax {
ImportDeclSyntax(
path: AccessPathSyntax([AccessPathComponentSyntax(name: "Foundation")])
)
StructDeclSyntax(
modifiers: [DeclModifierSyntax(name: .public)],
identifier: "SampleModel"
) {
VariableDeclSyntax(
modifiers: [DeclModifierSyntax(name: .private)],
.let,
name: "title",
type: TypeAnnotationSyntax(type: TypeSyntax("String")),
initializer: InitializerClauseSyntax(
value:StringLiteralExprSyntax(content: "Hello")
)
)
}
}
print(source.formatted())
public struct SampleModel {
private let title: String = "Hello"
}
サンプル2
let source = SourceFileSyntax {
FunctionDeclSyntax(
identifier: TokenSyntax.identifier("area"),
signature: FunctionSignatureSyntax(
input: ParameterClauseSyntax(
parameterList: FunctionParameterListSyntax {
FunctionParameterSyntax(
firstName: TokenSyntax.identifier("width"),
colon: .colonToken(),
type: TypeSyntax("Int")
)
FunctionParameterSyntax(
firstName: TokenSyntax.identifier("height"),
colon: .colonToken(),
type: TypeSyntax("Int")
)
}
),
output: ReturnClauseSyntax(
returnType: SimpleTypeIdentifierSyntax(name: TokenSyntax.identifier("Int"))
)
)
) {
ReturnStmtSyntax(
expression: SequenceExprSyntax {
ExprListSyntax {
IdentifierExprSyntax(identifier: .identifier("width"))
BinaryOperatorExprSyntax(operatorToken: TokenSyntax.spacedBinaryOperator("*"))
IdentifierExprSyntax(identifier: .identifier("height"))
}
}
)
}
}
print(source.formatted())
func area(width: Int, height: Int) -> Int {
return width * height
}
サンプル3
structにprotocolを準拠させる
let source = SourceFileSyntax {
StructDeclSyntax(
modifiers: [DeclModifierSyntax(name: .public)],
identifier: "SampleModel",
inheritanceClause: TypeInheritanceClauseSyntax {
for typeName in ["Codable", "Hashable", "Sendable"] {
InheritedTypeSyntax(typeName: TypeSyntax(stringLiteral: typeName))
}
}
)
}
print(source.formatted())
public struct SampleModel: Codable, Hashable, Sendable {
}
SwiftSyntaxの難易度について
SwiftSyntax全体を理解するのは難しいですが、コード生成に関しては成果物がわかりやすいので、比較的とっつきやすいと思います。
ですが、多くの部分が抽象化(protocol)されており、間違ったコードでも期待したものが出来上がるので、本当にこれでいいのかな感が強いです。
SeleneというパッケージでSwiftSyntaxBuilderを取り入れているので、参考になればと思います!
Discussion