pockestrap

Programmer's memo

WhitespaceをRubyに埋め込む

Whitespaceというプログラミング言語があります。 この言語はスペースとタブと改行文字だけで構成された言語です。

たとえばHello, world!を出力するプログラムは次のようになります。

gist.github.com

このWhitespaceをRubyで実装してみました。Akazaという名前1のgemです。

github.com

使い方

まずはgem installします。

$ gem install akaza

Akaza.evalメソッドにWhitespaceのコードを渡すと、解釈して実行します。

gist.github.com

なおこのプログラムではRubyのパターンマッチ構文2を使用しているため、Rubyのtrunkが必要です。3

単にプログラムを実行しているだけで面白味に欠けますね。単なるWhitespaceのインタプリタなら実装がいくらでもあるでしょう。

そこでAkazaにはインターフェイスをもう1つ用意しています。こちらのインターフェイスはかっこいいです。

かっこいい使い方

Akaza.evalメソッドを使う方法ではプログラムをStringリテラルとして定義していました。 これはあまりかっこよくありませんね。

そこでWhitespaceの言語仕様を少し思い出してみましょう。Whitespaceの構成文字はスペース、タブ、改行文字の3種類だけでしたね。 この3種類の文字しか使わないことには明確なメリットがあります。それは「Rubyのコードに直接Whitespaceのコードを埋め込んでもSyntax Errorにならない」ことです。

Rubyでは空白文字はだいたい無視されるので、少し注意すればほとんどの場所にWhitespcaeのコードを埋め込むことができます。

そこでwhitespaceという修飾子を作ってみました。whitespace修飾子をつけてメソッドを定義すると、そのメソッドの中身がWhitespaceのコードとして解釈されます。

gist.github.com

Akaza::Annotationextendしたクラスの中で、whitespace修飾子をつけてsumメソッドを定義しています。 そして、なにやらsumメソッド本文には不穏な空白がありますね。この空白は2つの数値の合計を出力するWhitespaceのプログラムです。

このsumメソッドを実行すると、ちゃんと数値の合計が出力されます。

whitespace修飾子を使うと、このようにメソッド定義に直接Whitespaceのプログラムを書くことができます!とてもかっこいいですね!

ちなみに、whitespaceでは空白文字以外はコメントとして無視されるので、RubyのSyntax Errorがでない限りは、空白を含まない任意の文字列をコメントとして書くことができます。 保守性にも優れていますね。

もう1つの使い方

whitespace修飾子は便利ですが、メソッドの引数がIOに限定されてしまうのは少々使いづらいです。先ほどのsumメソッドは数値を2つ受け取って1つ返すようなメソッドにしたほうが自然ですね。 そのような需要を満たすため、whitespace修飾子では任意の位置にWhitespaceのプログラムを埋め込むプレースホルダを用意しています。

メソッド中にAkaza::Bodyと記述しておくと、これがWhitespaceのプログラムに置き換えられます。 そしてプレースホルダなしの場合とは異なり、メソッド中に記述したコードはRubyのコードとして実行されます。

次にsumメソッドをプレースホルダを使って書き直したコードを紹介します。Whitespaceのコードは全く同じですが、中にRubyのコードも含まれています。

gist.github.com

Akaza::Bodyより前にinputoutput変数にIOを代入しておくと、それがWhitespaceのIOとして使われます。

先ほどよりも使いやすいインターフェイスになっていますね。

内部実装

WhitespaceのVMはパターンマッチを使って実装しています。

https://github.com/pocke/akaza/blob/6856397674cddd03c49deb4bfdb8345fa5b5a618/lib/akaza/vm.rb

また、whitespace修飾子はRubyVM::AbstractSyntaxTreeを使ってコードを書き換えることで実現しています。

https://github.com/pocke/akaza/blob/6856397674cddd03c49deb4bfdb8345fa5b5a618/lib/akaza/annotation.rb

RubyVM::AbstractSyntaxTreeについては別の記事でより詳しく解説しているので、そちらを参照ください。

pocke.hatenablog.com

まとめ

Akaza gemによってWhitespaceをRubyに直接埋め込む方法を紹介しました。 Whitespaceは一番自然にRubyプログラムに埋め込むことができる言語でしょう。 たとえばPythonJavaScriptなどの言語をRubyに直接埋め込もうとしたら、構文がぶつかってうまくいきません。

Rubyに飽きて他の言語を使いたいけど、Rubyプログラムと自然に連携したい」、そんな時にAkaza gemとWhitespaceを使ってみてはいかがでしょうか。

参考文献

WhitespaceのVM、パーサの実装の際には、主に次の文献を参考にしました。 Whitespaceのオリジナルの紹介ページが死んでいる(?)ため、言語仕様を探すのに苦労しました。つらい…


  1. 見えなさそうなので

  2. https://rubykaigi.org/2019/presentations/k_tsj.html#apr18

  3. 2019/04/28現在