Crystal本体のコードを眺める コンパイルの下準備編

この記事は [Crystal Advent Calendar 2018](https://qiita.com/advent-calendar/2018/crystal) の23日目の記事です。

# はじめに

Crystalの日本コミュニティでは主に[makenowjust](https://qiita.com/make_now_just)さんがCrystal本体
https://github.com/crystal-lang/crystal
に多大にコントリビュートされてます。

https://github.com/crystal-lang/crystal/graphs/contributors
を見ると4番目

makenowさん曰く「Crystalの型推論には無限のバグがありそうな気がする」とのことなので、私もそろそろコンパイラ本体は見ておきたい感がしてきました。
~~後少しでも盛り上げないといよいよ寂れそうで~~

この記事はCrystal本体のソースのコードリーディングの記録をまとめます。
22日に[arcage](https://qiita.com/arcage)さんの[記事](http://arcage.hatenadiary.jp/entry/2018/12/22/071026)でも触れていますが、そちらが公式発の情報ですので、確かな情報を知りたい場合はそちらをお勧めします。
本記事は自力で頭からコードを読んでいこうというものですので。

何かしらの成果があるとは限りませんし、時間いっぱい（23日の日付が変わるまで）の制限付きです。
また筆者はコンパイラ未経験者ですので、間違い等もあると思いますがそれを鑑みて以下をご覧ください。（ご指摘歓迎）

# 主な構成

まずエンドポイントはどこなのかを探していきます。
手っ取り早いのがカレントにあるMakefileを見てみます。

```
$(O)/crystal: $(DEPS) $(SOURCES)
	@mkdir -p $(O)
	$(BUILD_PATH) $(EXPORTS) ./bin/crystal build $(FLAGS) -o $@ src/compiler/crystal.cr -D without_openssl -D without_zlib
```

とあり、`src/compiler/crystal.cr`を見に行っています。

では`src/compiler/crystal.cr`を見てみます。

```ruby
# This is the file that is compiled to generate the
# executable for the compiler.

{% raise("Please use `make crystal` to build the compiler, or set the i_know_what_im_doing flag if you know what you're doing") unless env("CRYSTAL_HAS_WRAPPER") || flag?("i_know_what_im_doing") %}

require "./crystal/**"

Crystal::Command.run

```

`src/compiler`直下のcrystalディレクトリ以下のファイルをロードした上で`Crystal::Command.run`を実行しています。
一旦Command.runの方を見ていきます。

```ruby
class Crystal::Command
(ry)
  def self.run(options = ARGV)
    new(options).run
  end
```

```ruby
  def run
    command = options.first?
    case
    when !command
      puts USAGE
      exit
(以下条件分岐)
```
build以外にはinit（テンプレート生成する）やplay(playground立ち上げ)、run(実行ファイルを生成しないでそのまま実行)など。

```ruby
    when "build".starts_with?(command)
      options.shift
      build
```

buildメソッドを見ます。

```ruby
  private def build
    config = create_compiler "build"
    config.compile
  end
```

create_compilerの中身を見てみます。

```ruby
  private def create_compiler(command, no_codegen = false, run = false,
                              hierarchy = false, cursor_command = false,
                              single_file = false)
```

```ruby
OptionParser.parse(options) do |opts|
```
で延々とオプションを解釈し、コンパイラへと設定を追加していっています。

```ruby
@config = CompilerConfig.new compiler, sources, output_filename, original_output_filename, arguments, specified_output, hierarchy_exp, cursor_location, output_format
```

最後にCompilerConfigに上記の設定を追加してインスタンスを生成しています。
上記を踏まえた上でもう一度buildメソッドに戻ります。

```ruby
  private def build
    config = create_compiler "build"
    config.compile
  end
```

compileコマンドを追ってみます。

```ruby
  record CompilerConfig,
    compiler : Compiler,
    (ry)
    def compile(output_filename = self.output_filename)
      compiler.emit_base_filename = original_output_filename
      compiler.compile sources, output_filename
    end

    def top_level_semantic
      compiler.top_level_semantic sources
    end
  end
```

コンパイルの実行箇所は
`src/compiler/crystal/compiler.cr`　にあります。

```ruby
module Crystal
  class Compiler
    def compile(source : Source | Array(Source), output_filename : String) : Result
      source = [source] unless source.is_a?(Array)
      program = new_program(source)
      node = parse program, source
      node = program.semantic node, cleanup: !no_cleanup?
      result = codegen program, node, source, output_filename unless @no_codegen

      @progress_tracker.clear
      print_macro_run_stats(program)
      print_codegen_stats(result)

      Result.new program, node
    end
```

new_programメソッドを見てみます。

```ruby
  private def new_program(sources)
    program = Program.new
    program.filename = sources.first.filename
    program.cache_dir = CacheDir.instance.directory_for(sources)
    program.target_machine = target_machine
    program.flags << "release" if release?
    program.flags << "debug" unless debug.none?
    program.flags << "static" if static?
    program.flags.concat @flags
(ry)
    program
  end
```

対象となるコードの情報とコンパイルのフラグを見ているようです。

もう一度compileメソッドに戻ります。

```ruby
node = parse program, source
```

parseを追ってみます。

```ruby
    private def parse(program, sources : Array)
      @progress_tracker.stage("Parse") do
        nodes = sources.map do |source|
          # We add the source to the list of required file,
          # so it can't be required again
          program.add_to_requires source.filename
          parse(program, source).as(ASTNode)
        end
        nodes = Expressions.from(nodes)

        # Prepend the prelude to the parsed program
        location = Location.new(program.filename, 1, 1)
        nodes = Expressions.new([Require.new(prelude).at(location), nodes] of ASTNode)

        # And normalize
        program.normalize(nodes)
      end
    end
```

個別のファイルを解析している所を見ます。

```ruby
parse(program, source).as(ASTNode)
```

同じ名前のメソッドですけど、第2引数がSource型の方です。（Crystalはオーバーロード対応）

```ruby
    private def parse(program, source : Source)
      parser = Parser.new(source.code, program.string_pool)
      parser.filename = source.filename
      parser.wants_doc = wants_doc?
      parser.parse
    rescue ex : InvalidByteSequenceError
      stderr.print colorize("Error: ").red.bold
      stderr.print colorize("file '#{Crystal.relative_filename(source.filename)}' is not a valid Crystal source file: ").bold
      stderr.puts ex.message
      exit 1
    end
```

ここでいよいよコンパイラの肝の部分である解析を行い箇所に到達しました。

```ruby
parser.parse
```

中身は`compiler/crystal/syntax/parser.cr`にあります。
と、今日はここまでにします。
parserの中身については25日に続きを書きます。