以前、Salvatore Sanfilippo氏によって開発されたエディタ「Kilo」取り上げたところ、高い関心を集めた。Kiloはclocカウントでソースコードが1000行以下で、しかもcursesなどのライブラリも使っていない。VT100の基本的なエスケープシーケンスとlibcで提供されている機能だけを用いて実装されたエディタで、シンタックスハイライトにも対応している。C言語1000行程度でここまで実用的なエディタが開発できるというのは、教育向けの素材として興味深い。

KiloでKiloのソースコードkilo.cを開いた画面

KiloはCとC++のソースコードのシンタックスハイライトに対応している

本連載では、Kiloのソースコードを読み、どのような仕組みでエディタが作られているかについて紹介する。C言語でソフトウェアを開発する際に利用する機能が網羅されており、学習素材としてなかなかのポテンシャルを持っている。エディタがどのように実装されているのかを知るためにも、一度は読んで見てほしいソースコードだ。

頭から読む、それが無理ならmain()から読む

Kiloのソースコードファイル「kilo.c」はコメントを含んだ状態で1270行程度、空行とコメント行を抜くと1000行未満だ。この程度の行数なら、1行目からひととおり読んでいっても全体を理解できる。学校の授業でC/C++を使ったプログラミングの課題を数回こなした程度だと少々難しいかもしれないが、関数の呼び出しもそれほど深くないので、全体構造を頭の中に入れることは不可能ではない。

とりあえず全部読みたくはないが、仕組みは知りたいという場合はファイルの最後に記述されているmain()に目を通してほしい。Kiloが実行されるとここから処理が始まるからだ。以下が、Kiloのmain()関数のコードだ。

int main(int argc, char **argv) {
if (argc != 2) {
    fprintf(stderr,"Usage: kilo <filename>\n");
    exit(1);
}

initEditor();
editorSelectSyntaxHighlight(argv[1]);
editorOpen(argv[1]);
enableRawMode(STDIN_FILENO);
editorSetStatusMessage(
    "HELP: Ctrl-S = save | Ctrl-Q = quit | Ctrl-F = find");
while(1) {
    editorRefreshScreen();
    editorProcessKeypress(STDIN_FILENO);
}
return 0;

  }

main()関数をさらに本質的な部分だけに絞り込むとすれば、次のようになる。

    editorOpen(argv[1]);
while(1) {
    editorRefreshScreen();
    editorProcessKeypress(STDIN_FILENO);
}

この部分の処理の手順は、以下のようになっている。

  1. ファイルの内容をメモリに読み込む 2.1 メモリ上のデータからターミナルに出力する文字列を持ってきて、ステータスバーとかをつけてまとめて出力する 2.2 キー入力を待ち、入力されたらそのキーに対応した処理をする 2.3 2.1に戻る

Kiloの内部構造は単純だ。編集対象のファイルの中身は1つの文字列データ(文字列というかchar*でアクセスされるまとまったヒープ上のメモリ領域)として確保されている。Kiloはキー入力があると処理を行って、必要があればメモリ上のデータを書き換え、またそこから出力する内容を作って出力する。メモリはreallocで操作するたびに確保されて直されている。単純な構造だ。つまり、仕組みをさらに簡単に書くとこうなる。

  1. ターミナルに1画面分文字を出力する
  2. キー入力待つ
  3. メモリ上のデータ書き換える
  4. 1.に戻る

エディタは特殊な命令を使って画面に出力している文字列を書き換えているのだろう、と考えるが、毎回毎回全部出力され直されているのがKiloの実装だ。特殊なことをしているように思えるが、この仕組みだけなら学校のプログラミングの授業でならったような関数だけでも、エディタが作れるだろう。このように、基本の仕組みはシンプルであることを知っておくことは、プログラミングに対する苦手意識を排除する上でも役に立つ。以下は、もう少し内容を戻したコードだ。

    initEditor();
editorSelectSyntaxHighlight(argv[1]);
editorOpen(argv[1]);
enableRawMode(STDIN_FILENO);
editorSetStatusMessage(
    "HELP: Ctrl-S = save | Ctrl-Q = quit | Ctrl-F = find");
while(1) {
    editorRefreshScreen();
    editorProcessKeypress(STDIN_FILENO);
}

実際は、Kiloをエディタとして動作させるために利用する構造体の中身の初期化やシンタックスハイライトの簡単な設定、ターミナルの動作モードの設定などを行ってから、上記処理に入ってエディタとしての処理が行われている。

ソフトの下層部がわかるとおもしろい

抽象度の高いプログラミング言語や便利なAPIばかり使っていると、こうした下層部、より原理的な部分に近い部分の実装がわからなままになってしまう(そもそも、そこを気にしなくていいように、便利で抽象度の進んだAPIが用意されているんだが)。

下層部を調べてみると、結構泥臭い感じの実装の積み重ねでできていることがわかってくる。こうした部分を知っておくと、ソフトウェアが理解できる内容の積み重ねでできていることが実感でき、C言語などに対する苦手意識を少しは減らすことができると思う。

本連載の後半では、Kiloを読んで知った仕組みを使って、簡単なスクリーン指向(スクリーン指向と言ってもターミナルをスクリーンとして使うという意味だが)のプログラムを作ってみたいと思う。こうした取り組みを通じて、少しでもC言語に興味を持ってもらえれば幸いだ。