概要
この記事はG* Advent Calendar13日目の記事です。
今回はGroovyでBrainf*ck派生言語の実装を行ったのでその紹介をしたいと思います。
Brainf*ck
皆さん、Brainf*ckは知ってますよね?(汚い言葉なので*で伏字になっています)
言わずと知れた何回プログラミング言語のひとつです。実装がとても単純なのでさまざまな言語で実装されています。(
知らない人はWikipedia)
また命令が8つしかないのでそれぞれの文字を置き換えたネタ言語がアニメが話題になるたびに出てきます。
- ジョジョ言後
- ニャル子言語
- etc...
さてそんな中で私も去年ひとつ実装しました。
プログラミング言語Tippy
ここあさん、ティッピーが言語になったんです!
ということでごちうさ言語Tippyの紹介です。
アニメ「ご注文はうさぎですか?」を題材にしたBrainf*ck実装になっています。
コードがぴょんぴょんする仕様です。
仕様
Brainf*ck命令に対応したTippyの命令は下記のとおりです。
Instruction
instruction | brainf*ck | tippy |
---|---|---|
NEXT | > | ふわ |
PREVIOUS | < | どき |
INC | + | ぴょん |
DEC | - | つーんだ |
GET | , | まち? |
PUT | . | こころ |
LOOP | [ | ここあ |
JUMP | ] | ちの |
最初ポインタ移動のNEXTのほうがぴょんぴょんしてる感あるのでそっちに割り当ててたんですけど、いざコード書いたらポインタってそんなに移動させないし連続でぴょんぴょんさせることあまりないので加算命令のほうに割り当てなおしました。
Brainf*ck系言語見たことある人ならなんとなくわかると思います。
HelloWorld
下記のコードを読み込ませると「Hello, world!」と出力します。
ぴょんぴょんぴょんぴょんぴょんぴょんぴょんぴょんぴょんここあふわぴょんぴょんぴょんぴょんぴょんぴょんぴょんぴょんふわぴょんぴょんぴょんぴょんぴょんぴょんぴょんぴょんぴょんぴょんぴょんふわぴょんぴょんぴょんぴょんぴょんどきどきどきつーんだちのふわこころふわぴょんぴょんこころぴょんぴょんぴょんぴょんぴょんぴょんぴょんこころこころぴょんぴょんぴょんこころふわつーんだこころつーんだつーんだつーんだつーんだつーんだつーんだつーんだつーんだつーんだつーんだつーんだつーんだこころどきぴょんぴょんぴょんぴょんぴょんぴょんぴょんぴょんこころつーんだつーんだつーんだつーんだつーんだつーんだつーんだつーんだこころぴょんぴょんぴょんこころつーんだつーんだつーんだつーんだつーんだつーんだこころつーんだつーんだつーんだつーんだつーんだつーんだつーんだつーんだこころふわぴょんこころ
残念ながらまだ日本語には非対応です。
改行もコメントも非対応です。
実装
実装についてはBrainfckエンジン部分と命令後の定義を分けています。
そのため、Tippy以外でもさまざまな実装を行うことができます。
言語エンジン名は少し悩んだのですが、Groovyの流儀として頭文字にGが来ることとBrainfck系なので卑猥な単語ということをあわせてGSpotという名前にしました。(今見直したらひどい命名だ)
エンジンの実装自体はJavaでもありましたしその他言語でもよく見かけるので、そのまま流用してもよかったのですが面白くなかったのでいちから作りました。
ただ他の実装と同じじゃ面白くないので少しGroovyらしさを出してみました。(と言っても100行にも満たないソースですが)
また使用ツールは、言語はGroovy、テストフレームワークはSpock、ビルドツールはGradleと標準的なGroovy開発セットです。
特徴
GroovyはJavaと違い、enumのそれぞれの値に対し後付でメソッドを追加することができます(ExpandoMetaClass)
そこでクラス内に次のようなenumをつくり
public class GSpot {
protected enum Token {
NEXT,
PREVIOUS,
INC,
DEC,
GET,
PUT,
LOOP,
JUMP,
}
}
それぞれに対しinterpretという命令を追加しました。(https://github.com/ligun/tippy/blob/master/src/main/groovy/net/ligun/tippy/GSpot.groovy)
Token.NEXT.metaClass.interpret { ++ptr }
Token.PREVIOUS.metaClass.interpret { --ptr }
Token.INC.metaClass.interpret { data[ptr] = data[ptr]? data[ptr]+1: 1 }
Token.DEC.metaClass.interpret { --data[ptr] }
Token.GET.metaClass.interpret { data[ptr] = is.read() }
Token.PUT.metaClass.interpret { os.write(data[ptr]); os.flush() }
Token.LOOP.metaClass.interpret { /* 長いので省略 */ }
こうすることにより実行時に命令を解釈したTokenのリスト(program)に対して一律にprogram[pc].interpret()
(pcはプログラムカウンタ)とすることで処理を実行できます。
命令はTokenをKeyとしたstaticなMapとしてもっているので、新しい言語を作りたい場合GSpotクラスを継承したサブクラスを作り下記のように記述するだけです。
public class Tippy extends GSpot {
static {
TOKEN_LIST[Token.NEXT] = 'ふわ'
TOKEN_LIST[Token.PREVIOUS] = 'どき'
TOKEN_LIST[Token.INC] = 'ぴょん'
TOKEN_LIST[Token.DEC] = 'つーんだ'
TOKEN_LIST[Token.GET] = 'まち?'
TOKEN_LIST[Token.PUT] = 'こころ'
TOKEN_LIST[Token.LOOP] = 'ここあ'
TOKEN_LIST[Token.JUMP] = 'ちの'
}
public Tippy(param) {
super(param)
}
}
スクリプトを実行するときはコンストラクタにStringを渡してあげないといけないので、Mainメソッドくらい作って他方がよかったかな・・・・・・
GroovyCliBuilderとかでスクリプトファイル読み込ませられるようにしたいですね。
最後に
昨日とはうって変わって何の実用にもならない俺俺ライブラリの紹介でした。
また一週間後(20日)に記事を投稿する予定ですが、そちらも残念ながらネタ系です。
ありがとうございました。