[Crystal] ソースコード・リーディングのすゝめ

こんなタイトルになってますが，なにも「Crystalコンパイラのソースコードを頭から順に読みましょう」ということではなく（それはそれで勉強にはなると思いますが），プログラミング中に何か困ったことがあったらCrystalコンパイラや標準添付ライブラリのソースコードがお役に立ちますよ，というお話です。

ご存知の通り Crystal はセルフホスティングな言語です。つまり，Crystal コンパイラ自身や標準添付ライブラリなどがCrystal言語を使って作成されており，そのソースコードは GitHub（<i class="fa fa-github" aria-hidden="true"></i> [crystal-lang/cystal](https://github.com/crystal-lang/crystal)）上で全て公開されています。

ということは，コンパイラ自体を作ってる凄い人たちが，様々な用途向けに書いた大量のサンプルコードが読み放題，ということです。

[ドキュメント](https://crystal-lang.org/docs/)や[APIリファレンス](https://crystal-lang.org/api/)だけではよく分からないマクロたライブラリの実用例がわんさと転がっているわけですから，これは活用しない手はありません。

プログラミング言語自身のソースコードというとスーパーハッカーの皆さんが見るものなんでは，と少々敷居が高く感じる方もいるかもしれませんが，私個人はむしろ初〜中級者にこそ積極的に読む価値があると感じています。

というわけで，個人的な <i class="fa fa-github" aria-hidden="true"></i> [crystal-lang/cystal](https://github.com/crystal-lang/crystal) でソースコードを調べたら解決した事例をご紹介します。

## 事例1： CrystalでTLSを使う

<i class="fa fa-github" aria-hidden="true"></i> [arcage/crystal-email](https://github.com/arcage/crystal-email) に STARTTLS コマンドを実装するには，Crystal から TLS 通信を行う必要がありました。

APIリファレンスを漁ってみると， [_**Openssl::SSL::Socket::Client**_](https://crystal-lang.org/api/OpenSSL/SSL/Socket/Client.html) なるクラスが存在していて，なんとなくそれを使えばなんとかなりそうな気配はするものの，_**Openssl::SSL::Socket::Client**_ のAPIリファレンスにはサンプルコードが全くないため，どうすれば実際に使用できるのかがうまくイメージできませんでした。

こんな時，いきなり _**Openssl::SSL::Socket::Client**_ 自体のソースを読むこともできますが，仮に「どのような処理が行われるか」を理解できたとしても，それが「どのように使えば良いか」と直結するとは限りません。ですので，標準添付ライブラリの中で _**Openssl::SSL::Socket::Client**_ が使われている箇所を探します。

例えば，HTTP::Client は HTTPS 接続ができるんだから，そこでも _**Openssl::SSL::Socket::Client**_ を使っているんではないか，と考えて [src/http/client.cr](https://github.com/crystal-lang/crystal/blob/8f053fb0fa501ed3b06f2f9733ad9a3adac7b3dc/src/http/client.cr) ファイル内を「Openssl::SSL::Socket::Client.new」で検索してみたところ，サーバとの接続用ソケットを取得するプライベートメソッド `private def socket` の定義の中で

```rb
tls_socket = OpenSSL::SSL::Socket::Client.new(socket, context: tls, sync_close: true, hostname: @host)
```

という記述を見つけました。

ローカル変数 `socket` は，その行のすぐ上でサーバへの接続用 _**TCPSocket**_ として生成されており，`context` 引数として渡されている `tls` は，遡っていくと一般的な使い方では `initailize` メソッド中で `OpenSSL::SSL::Context::Client.new` で初期化されていることがわかりました。

`Openssl::SSL::Socket::Client.new` の[定義](https://crystal-lang.org/api/OpenSSL/SSL/Socket/Client.html#new%28io%2Ccontext%3AContext%3A%3AClient%3DContext%3A%3AClient.new%2Csync_close%3ABool%3Dfalse%2Chostname%3AString%3F%3Dnil%29-class-method)を見ると，`context`引数が省略された場合のデフォルト値も `OpenSSL::SSL::Context::Client.new` なので，特に問題がなければこれは省略してしまっても良さげですね。

その他，接続先サーバのポート番号は必要ないけれど，相手先のホスト名（`hostname`引数）は明示的に指定する必要がありそう，などといった事が見えてきます。

また，`private def socket` の定義を見ると，https を利用する場合には _**Openssl::SSL::Socket::Client**_ オブジェクトを返し，そうでなければ _**TCPSocket**_ オブジェクトがそのまま返されています。それ以外の場所では，`socket` メソッドの返り値が _**Openssl::SSL::Socket::Client**_ だろうが _**TCPSocket**_ だろうがお構い無しに，同じように読み書きしています。

ということは，_**Openssl::SSL::Socket::Client**_ は _**TCPSocket**_ と同じように読み書きできる，つまり，生成時に渡された _**IO**_ オブジェクトに暗号化のラッパをかますような動きをするっぽい。であれば，`sync_close` 引数は _**Openssl::SSL::Socket::Client**_ を閉じた際に，内部の _**IO**_ も閉じてくれるんだろうな，と当たりをつけて，_**Openssl::SSL::Socket::Client**_ の(正確にはその親クラスの _**Openssl::SSL::Socket**_ の)実装を追いかけてみると，確かにそのように動いているようです。

そんなこんなで，最終的に STARTTLS コマンドに対する220番応答を受け取ったら，それまでサーバとやり取りしていた `@socket`（_**TCPSocket**_）を，自身を引数として生成した _**Openssl::SSL::Socket::Client**_ オブジェクトに置き換え，以降はそちらを読み書きする形で STARTTLS 対応が実現できました。

## 事例2：似たような処理のメソッドを一度に定義する

<i class="fa fa-github" aria-hidden="true"></i> [arcage/crystal-patlite](https://github.com/arcage/crystal-patlite)では，赤/黄/緑3色の信号灯のステータスを制御できますが，そのためのメソッドは赤灯のステータスに関するものだけでも7つ必要です。

```rb
status.red        # 赤灯の状態を取得する
status.red_on     # 赤灯を常時点灯させる
status.red_flash  # 赤灯を点滅させる
status.red_off    # 赤灯を消灯させる
status.red_on?    # 赤灯が常時点灯状態であれば true を返す
status.red_flash? # 赤灯が点滅状態であれば true を返す
status.red_off?   # 赤灯が消灯状態であれば true を返す
```

これを黄灯，緑灯についても同じだけ定義しなければなりません。

それぞれのメソッドは，変数名や定数名が各色に対応したものになるくらいでほぼ同じ処理ですが，いちいちコピペするのも手間ですし，後で修正しようとした際も3色分それぞれ書き換える必要が出てきます。

制御対象は全く違いますが，似たようなインタフェースを持っている標準添付ライブラリ _**Colorize**_ の[ソース](https://github.com/crystal-lang/crystal/blob/18e76172444c7bd07f58bf360bc21981b667668d/src/colorize.cr#L168)を見てみると，

```
  {% for name in COLORS %}
    def {{name.id}}
      @fore = FORE_{{name.upcase.id}}
      self
    end

    def on_{{name.id}}
      @back = BACK_{{name.upcase.id}}
      self
    end
  {% end %}
 ```

というマクロで，色名の配列 `COLORS` から色情報を取り出して，各色についての処理を一度に定義していることが分かりました。

`{{name.id}}` で変数 `name` に納められた色名がそのままコード中に展開され，`{{name.upcase.id}}` で大文字変換された色名がコード中に展開されるようです。

この `name` は，なんとなく Crystal の _**String**_ 型のようにも見えますが，イロイロ調べてみると，_**String**_ 型ではなく，マクロ専用の [_**Crystal::Macros::StringLiteral**_](https://crystal-lang.org/api/Crystal/Macros/StringLiteral.html) 型の変数で，_**Crystal::Macros::StringLiteral**_ にも，Crystal の _**String**_ 型ほどではないものの，いくつかの文字列操作メソッドが用意されているようです。

また，`name.id` は，[_**Crystal::Macros::MacroId**_](https://crystal-lang.org/api/Crystal/Macros/MacroId.html) 型を返すメソッドなのですが，なぜこのような処理が必要かというと，_**Crystal::Macros::StringLiteral**_ 型は文字列そのものではなく構文上の文字列のリテラル表現を表す型で，その値には文字列を括るダブルクォート（`"`）までが含まれているためのようです。例えば，`name` が `"red"` の時に `def {{name}}` とコード中に展開すると，`def "red"`になってしまうのですが，`name.id` によってリテラルそのもの（`"red"`）ではなくそのリテラルのコンテンツ（`red`）を取り出すことができる，ということみたいですね。

この辺のマクロの挙動は，まだ完全に理解できていないのですが，それでもその恩恵を受けることでコードがすっきりして保守性を向上させることができそうです。