C言語のREPL（的なもの）を実現する

# はじめに
C言語のようなコンパイル言語でもLispにおけるREPLのような対話的な実行環境があれば良いのにと思ったことがある人は少なくないと思います．実際にC/C++でREPL的なものを実装しようという試みは結構古くからあって，代表格ではCERNのROOTプロジェクトのために開発されているCling（あるいはその前身的な位置づけのCINT）が有名でしょうか．ClingはLLVM（とClang）をベースに動いているようです．私はわりとGNU好きでGNUツールチェインベースで（かつC++ではなくCを対象にした）同様のものがあればなと思うのですがあんまりちゃんとしたのはなさそうです．この記事ではGCCベースでREPL的なものを実現する方法を（雑ですが）思いついたのでそのことについてまとめたいと思います．なお**Libgccjitについての記事ではありません**のでそちらを期待された方は申し訳ありません．

# いかにしてEvalるか
REPLを実現するにはいかにしてEval相当の機能を実現するかというのが問題になります．Cのようなコンパイルされる言語でこれを実現するには結局のところ**コンパイラをまるまる実装する**こととなります．GCCはLLVMのようにライブラリ的な使い方を想定した設計ではないのでClingのようなやり方は困難です．Libgccjitというのもありますが結局のところ**自前でCのソースをパースする必要がある**ので相当ヘビーです．ということで結局のところ**入力をすべて保持しておいて毎回まるまるコンパイラに渡す**というのが最善と思われます．

例えば以下のような入力があったとします．

```c
[0] int a = 0;
[1] printf("a = %d\n", a);
```

これをもとに以下のようなソースを生成して毎回コンパイルします．

```c
#include <stdio.h>

int main (void)
{
  int a = 0; // [0]
  printf("a = %d\n", a); // [1]
  return 0;
}
```

ただしこれでは**２つ大きな問題が存在します**．

まず第一に**入力を評価した結果が`Print`できません**．REPLにおける`Print`部分をどのように実装するかはひとまずおいておいて一連のコードを**評価した結果をどうにか取得する**必要があります．上記の例だと`printf`の返り値として`6`が得られないといけません．

もう一つの問題として**変数を再度定義できない**という問題があります．例えば続く入力が以下のように与えられたらどうでしょうか．

```c
[3] double a = 0.5;
```

このような入力を弾くということも考えられますがREPLのように使用することを考えると不便極まりないです．これらの問題を解決するために**複文の式化**と呼ばれるGNU拡張を用います．どんな型の変数でも値を表示できる万能プリント関数を仮に`print`とします．（これについては後述）このとき先程の例に代えて以下のようなコードを生成すれば良さそうです．

```c
#include <stdio.h>

/* printの定義 */

int main (void)
{
  print(
    ({ int a = 0; // [0]
      ({ printf("a = %d\n", a); }); // [1]
    })
  );
  return 0;
}
```

これに続けて先程の`[3]`の入力があった場合でもスコープが**毎回新たに区切られる**ので定義のコンフリクトも起きません．

ただこのままだと毎回`printf`が実行されてしまうので**最後の式以外では標準出力を捨てる**ようにします．

```c
#include <stdio.h>

/* printの定義 */

int main (void)
{
  freopen("/dev/null", "w", stdout); // stdoutを/dev/nullに
  print(
    ({
      int a = 0; // [0]
      ({
         freopen("/dev/tty", "w", stdout); // stdoutを/dev/ttyに
         printf("a = %d\n", a); // [1]
      }); 
    })
  );
  return 0;
}
```

残る問題として途中にやたら**時間がかかる処理があった場合**と**途中でユーザー入力をうける場合**がありますがこれらはREPL的な用途とは離れるので**バッサリ切り捨てる**こととします．したがってあとは**万能プリント関数**を実装すれば適当にGNU Readline的なものでラップすればREPL（的なもの）の完成です．なお関数の定義については**GCCでは関数内で関数を定義できる**ので問題ではありません．

# いかにしてPrintするか
C11の`_Generic`を使います．雑な実装としては以下のような感じです．

```c
#define __fmt_string(v) _Generic((v),\
  int: "%d\n",\
  double: "%lf\n",\
  ...以下同様... \
  default: "%p\n")
#define print(v) printf(__fmt_string(v), v)
```

`_Generic`マクロはいわゆる型オーバーロードを実現するもので引数の型に応じて処理を振り分けることができます．（[こちらの記事](http://marycore.jp/prog/c-lang/function-overloading-in-c/)を参考にしています．）
ただしこれだけでは問題があって`int a = 0`のような入力に対応できません．`({int a = 0;})`の値は`void`になってしまうからです．したがってマクロを使って以下のように振り分けます．

```c
#include <stdio.h>

/* printの定義 */

int main (void)
{
#ifdef PRINT
  print(実行するコード);
#else
  実行するコード
#endif
  return 0;
}
```

まず`gcc -DPRINT...`のようにコンパイルを試みてコンパイルできなかった場合は`gcc ...`としてコンパイルします．それでも失敗した場合は入力を弾けばよいわけです．

# 実装してみた
以上のアイデアをもとに[bashスクリプトとして実装](https://github.com/koji-kojiro/igcc)してみました．なぜbashで書いているのかというと**ある程度ポータブルな方が良い**というのと**シェルスクリプトを華麗に書けるようになりたい**からです．ただ**私のbash力は見ての通りお察し**ですのでちょいちょい壊れてると思います．興味があったら長い目で見ていただけると幸いです．継続的に開発を続けるかはわかりませんが．現状以下のような感じで使えます．

![tty.gif](https://github.com/koji-kojiro/igcc/raw/master/image/tty.gif)

# 終わりに
記事は以上です．お読みいただきありがとうございました．