これは Swift Advent Calendar 2018 24日目の記事です。
昨日23日目は @uounɹɐʇの「Conditional Conformanceで遊ぼう」でした。
前説
先日福岡で行われた 第5回 HAKATA.swift ~福岡でSwiftの勉強会~ でLTとして話した内容です。
apple/swift-syntax とは
READMEの冒頭には
SwiftSyntax is a set of Swift bindings for the libSyntax library. It allows for Swift tools to parse, inspect, generate, and transform Swift source code.
とあり、Swiftの
- 解析
- 検査
- 生成
- 変換
に用いることができ、所謂、メタプログラミングに使用できます。
メタプログラミングについてはこちらがおすすめ
お題
iOSで色を扱う場合にお馴染み、UIKitデフォルトAPIとして提供されるイニシャライザ
UIColor.init(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat)
ですが、これには以下のような不満点があります。
- RGB1~255表現を0~1の少数値に変換して表現するため、直感的ではない
- 特にデザイナーはHexColor (16進トリプレット) で知りたい
ということで、swift-syntaxをを用いて、コメントでHexColorを入れてみようと思います。
想定はこんなコード
Contents.swift
import UIKit let string = "ABCDE" let color1 = UIColor(red: 0.55, green: 0.0, blue: 0.0, alpha: 1.0) let array = [1, 2, 3, 4, 5] let color2 = UIColor(red: 0.0, green: 1.0, blue: 0.0, alpha: 1.0) let dictionary = ["foo": 1, "bar": 2, "baz": 3] let color3 = UIColor.init(red: 0.0, green: 0.0, blue: 1.0, alpha: 1.0)
用意したツールは こちら
$ swift run swift-color-detector help dump \(path): Dump code rewrite \(path): Add HexColor Comments and Save
まずは使ってみる
dump
これをdumpしてみると
$ swift run swift-color-detector dump Contents.swift
import UIKit let string = "ABCDE" let color1 = UIColor(red: 0.55, green: 0.0, blue: 0.0, alpha: 1.0) /* #8C0000 */ let array = [1, 2, 3, 4, 5] let color2 = UIColor(red: 0.0, green: 1.0, blue: 0.0, alpha: 1.0) /* #00FF00 */ let dictionary = ["foo": 1, "bar": 2, "baz": 3] let color3 = UIColor.init(red: 0.0, green: 0.0, blue: 1.0, alpha: 1.0) /* #0000FF */
と出力されます。
rewrite
$ swift run swift-color-detector rewrite Contents.swift
とすると、与えたpathのコードの書き換えも行います。
swift-syntaxを扱う主要なコード
今回のサンプルでswift-syntaxをメインで扱う部分は、以下ファイルの60行強で実現されています。
段階としては
ColorSyntaxRewriter
- イニシャライザに該当するコードを検査
ColorInitializerSyntaxRewriter
UIColor.init(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat)
を検査
ColorLiteralSyntaxRewriter
- Trivial (コメント) を追加
という3ステップで実現しています。
まとめ
いかがでしたでしょうか?
簡単に扱えそうな気がしてきませんか?
そもそも自分もメタプログラミングは挑戦を始めたばかりで、 apple/swift-syntax
も手探りなため、最適解とは限りません。
ぜひ、アドバイスをもらえるとありがたいです。