Rubyで作るおもちゃのプログラミング言語「Stoffle」入門
プログラミング言語は、不思議でありながら魅力的な道具だと感じたことはありませんか?そのフードを覗き込み、内部で何が起きているのかを理解できたら素晴らしいと思いませんか?ゼロからプログラミング言語を作ってみたいという興味をお持ちなら、この記事と今後のシリーズ記事がきっと役立つはずです。
このシリーズでは、非常にシンプルなインタプリタ型・動的型付けのプログラミング言語を、ステップバイステップで一緒に作り上げていきます。「インタプリタ型」「動的型付け」といった用語の意味が今ひとつピンとこなくても、心配はいりません。実装には親しみやすいRubyを使い、初心者から上級者まで誰でもついてこられるよう、各ステップを丁寧に解説します。この言語の名前は「Stoffle(ストッフル)」。南アフリカにいる同名のハニーバジャー(ミツアナグマ)にちなんで名付けました。
なぜプログラミング言語を作るのか?
StoffleがPythonやRubyに取って代わることは、おそらくないでしょう。それでも、わざわざ開発する価値はあるのでしょうか?楽しいという理由に加えて、このシリーズを通じてお伝えしたいのは、「言語を作ることは最高のプログラミング演習になる」という点です。この経験から得られるメリットは複数あります。
プログラミング言語への謎が解ける(ひいては開発者ツールへの理解も深まる)。私たちはこれらのツールの「消費者」であるだけでなく、必要や欲求があれば自ら「創造者」にもなれる存在だと実感できるようになります。
日常の開発ではほとんど出会うことのない珍しいプログラミングの課題に挑戦できます。
言語実装の枠を超えて役立つコンポーネントについても学べます。例えばパーサーは、上司が「新しいデータ取り込み機能として対応しろ」と言ってきた厄介なレガシーテキストファイルの処理に、無数の難解な正規表現を書く代わりとして活用できます。
プログラミング言語はどのように動くのか?
プロジェクトの全体像を見る前に、まず少し視野を広げて、コンピュータ上でプログラムがどのように実行されるのかを理解しておきましょう。ご想像の通り、CPUはRubyのような高水準言語を直接サポートしていません。しかしCPUには、アーキテクチャごとにサポートされる一連の非常に低レベルな命令が備わっています。
ちなみに、PCやMacでこの記事を読んでいるなら、あなたのコンピュータはおそらくx86アーキテクチャで動いています。興味があれば調べてみてください。
つまりプログラミング言語の役割は、高水準のコードをCPUが理解できる機械語へ変換することです。この変換を実現する手法は数多くあります。Stoffleはインタプリタ型言語、つまりプログラムの実行中にインタプリタがソースコードを機械語へ翻訳していく方式を採用します。
Cのようなコンパイル型言語はまったく別物です。コンパイルという工程でソースコードを機械語に変換し、ターゲットのCPUがそのまま実行できるバイナリを生成します。もうひとつの手法として、ソースファイルを既存の別の(多くの場合高水準の)言語へ変換するやり方もあります。これは一般に「トランスパイル」と呼ばれます。
ただし、現実の言語はここまで単純明快ではありません。実際の言語の多くは、こうした(そしてその他の)さまざまな実装戦略の側面や技法を、多かれ少なかれ組み合わせています。基礎を押さえた後で、ぜひ自分の好きな言語がどの道を辿っているのか調べてみてください。
Stoffleの全体像
前述の通り、Stoffleはごく基本的な、インタプリタ型・動的型付けのプログラミング言語です。少数の基本データ型、4つの基本算術演算子、比較演算子と等価演算子、論理演算子、if / else、whileループ、関数、そしてコンソールへの出力機能だけを持つシンプルな仕様です。
豆知識:Stoffleのように学習や実験を主目的とした言語は、「おもちゃ言語(toy language)」と呼ばれることがよくあります。
Stoffleのインタプリタは、先ほど述べた通り信頼のおけるRubyで実装します。インタプリタを起動すると(stoffle hello_world.sfe)、ソースファイルは実行されるまでに以下のコンポーネントとフェーズを通過します。

インタプリタの構成要素と、.sfeファイル実行時の流れ。
字句解析器(Lexer)
「スキャナー」とも呼ばれる字句解析器の使命は、ただの文字列を「トークン」と呼ばれる意味のあるまとまりへ変換することです。例えば my_var という変数を宣言したとしましょう。字句解析器はこれらの文字を読み取り、Token::VARIABLE というトークンを生成します。
構文解析器(Parser)
ソースコードについて考えるとき、その入れ子構造は無視できません。条件式を思い浮かべてください。真の場合と偽の場合の分岐は入れ子になっており、条件の評価結果に応じて実行されます。
構文解析器の主な仕事は、フラットなトークンの列を、トークン間の関係性を表現できるデータ構造へ変換することです。さらに重要な役割として、コードに間違いがあったときに構文エラーとして報告してくれる点も挙げられます。
インタプリタ(Interpreter)
Stoffleのインタプリタはシンプルにつくり、パーサーが生成したデータ構造を直接扱います。インタプリタはこの構造を少しずつ解析しながら、その場で実行していきます。
Stoffleにおける機械語への変換は、インタプリタ自体がRubyプログラムであることで実現します(つまり、私たちがインタプリタを実行するとき、それはRubyのインタプリタによって解釈されているわけです!)。
まとめ
今回は、Stoffleを形にするまでの大まかな流れを概観しました。最初は「プログラミング言語の実装なんて凡人には無理」と思っていた方にも、少しでも自信を持ってもらえていたら嬉しいです。私自身と同じくらい、皆さんもワクワクしていることを願っています。
次回の記事では、いよいよ手を動かしてStoffleの字句解析器を実装していきます。次回が終わる頃には、Stoffleのソースコードを読み込み、味気ない文字の並びをより構造化された(そして面白い!)トークンの列へ変換できるRubyプログラムが完成しているはずです。
それでは、シリーズ次回でお会いしましょう!
-
Rubyで実践する関数型プログラミング完全ガイド ― 純粋関数・イミュータブルデータ・カリー化の基本
Rubyを書いていると、「関数型プログラミング」という言葉を目にする機会が増えてきます。しかし、実際にどんなものなのか、自分のコードに取り入れるべきなのか、疑問に感じている方も多いのではないでしょうか。 関数型プログラミングとは、具体的に何を指すのか? オブジェクト指向プログラミング(OOP)とは何が違うのか? Rubyでも関数型的な書き方を採用すべきなのか? この記事では、これらの疑問にわかりやすく答えながら、Rubyで今日から使える関数型プログラミングの考え方とテクニックを解説します。 関数型プログラミングとは? 関数型プログラミングは一時的な流行や難しい専門用語ではなく、長い歴史を持
-
Rubyネットワークプログラミング入門!ソケットの基本とTCPサーバーの作り方
Rubyでオリジナルのネットワーククライアントやサーバーを作りたいと思ったことはありませんか?あるいは、その仕組みを理解したいだけかもしれません。 その場合、必ず「ソケット」と向き合うことになります。 この記事では、Rubyネットワークプログラミングの基礎を学び、Rubyを使って他のサーバーやクライアントと通信を始めるための方法をご紹介します。 ソケットとは何か? ソケットとは、通信チャネルのエンドポイント(終端)のことです。クライアントとサーバーの両方が、このソケットを使って通信を行います。 その仕組みは非常にシンプルです。 接続が確立されると、ソケットにデータを書き込むことでデータが相手側