Slack絵文字プログラミング言語 SlackMojicode #kosen10s

この記事は「kosen10s Advent Calendar」3日目の記事です。 前回は @whywaita くんの「電気通信大学に入学してかかるお金を計算してみた」でした。

回想

遡ること半年前…

allajah 「へえ、絵文字でできたプログラミング言語『Emojicode』だって。10s Slackの絵文字で出来たらおもしろそうだねw」

puhitaku 「おおどれどれ、トークンを入れ替えてBotでも作れないか調べてみよう………うわっ!

GitHubに置かれていたのは、視界を飛び出すほどおびただしい量のC++。

まるでそいつは、出来心でForkしようとした僕の未熟さをまざまざと見せつけるかのように、ただ悠然と横たわっていたのだった……………

はじめに

こんにちは。puhitaku a.k.a. ルータ芸人 a.k.a. kosen10s Slack 筆頭絵文字主 です。

10s Advent Calendarをせっかくやるのなら、何か自分らしい内容を書きたいなと思い今回は絵文字にすることにした。

きっと読者の皆さんのほとんどはご存じないと思われるが、10s Slackは絵文字が異常に豊富だ。

f:id:puhitaku:20161203221010p:plain

全絵文字458個のうち実に半分、200個強は私が作った。1つ1つ、切り抜きやリサイズなど心を込めて作っていたらここまで膨れ上がっていた。この絵文字を活かそうというのがこの記事のそもそもの始まりである。

Slack絵文字でプログラミング

半年前の回想でも触れているが、世の中には絵文字でできたプログラミング言語というのが存在する。その存在を知った当時、この絵文字トークンをSlack絵文字の書式(例: 🙆 :ok_woman:)で置き換えてしまえばそのままSlack絵文字のプログラミング言語ができるんじゃないかと思いコードを読んだのだが、いかんせん大規模なコードに最初からさじを投げてしまった。

それから時はちょっと流れ、後述のRPythonで言語を実装する話など実際に聞いたこともあって

「これはもしかして自分で書いたらSlack絵文字の言語が作れるのでは?」

と思いAdvent Calendarの内容にすることにしたのが昨日の出来事である。

こうしてできたのが「SlackMojicode」だ。

github.com

動いている様子

1から900までの総和を計算させてみる。

f:id:puhitaku:20161203223131p:plain

フィボナッチ。

手元のエディタと見やすいトークンで編集してから、後で絵文字の羅列に変換してもらうこともできる。

f:id:puhitaku:20161203223053p:plain

ほとんど全部メンバーの手製の絵文字で構成しているが、内部のトークンは :c_func: といった汎用的な名前にしている。上の画像は既存の絵文字からそれらにAliasを貼ることで実現しているので、クローンして自由に遊んでもらうことが可能だ。

ここから下はその実装の話になる。

プログラミング言語をつくる

昔ながらのプログラミング言語の実装方法は、Lexで字句解析器 (lexical analyzer, lexer) を作り、Yaccで構文解析器 (parser)を作ってASTを出力、実行するものが一番有名だ。しかしこれをするにはそれなりに時間がかかるし、直近で使わないCのコードをガリガリ書くのもやる気が出ない。

そこでRPythonを使うことにした。RPython(以下 RPy)は、PythonでPythonを実装する「PyPy」のために作られた言わば制限付きPythonで、Yacc・Lexが担っていた仕事をすべてPythonで書けるようになるすばらしいものだ。しかも、インタプリタ実装を作るだけでなくC Backendを通じてC言語のコードを吐かせコンパイルすることもできる。

この前のPyCon JP 2016でもRPyでPHPを実装する「PYHP」の発表がなされ、個人的に言語実装が高まっていたときである。これはやるしかない。

実装途中ちょっと近道

字句解析機を書き始めるところで、いい記事に出会った。

Using RPython and RPly to build a language interpreter, part 1 — Josh Sharp

本来、lexer, parserをRPyで実装する際はLexxと同じEBNFで書きはじめることになるのだが、RPyのドキュメントは大規模かつ難解で少々とっつきづらい印象があった。上の記事では、Pythonで書かれたlexer + parserである「PLY」のRPy移植 RPlyを導入し、デコレータといった機能の恩恵を最大限に活かしている。

当初私はこの記事に沿ってlexerを書いていたのだが、途中で「どうせ真似なんだし記事を書いた人の実装を改造したほうが早いのでは」という発想に至りForkすることにした(※)。Joshsharp氏のBraidである。

※ライセンスについて問い合わせたところ「ライセンスを追加し忘れてたからUnlicensedの文言を追加したよ。自由に使ってね!」との快い返事を頂き、そのまま利用させていただいた。Thanks a lot!

あとForkしてから気づいたのだが、Braidは上の記事で網羅していないAST構築とバイトコードへのコンパイルまで含んでいて、これを自分で書こうとしたら2日や3日では終わらなかった。Forkしたのは賢明だったといえるだろう。

Braidの改造

Braidは、上の記事で書かれたlexerを起点に筆者のJoshsharp氏自身が実装した言語である。ほぼ見た目から想像する通りの動きをするとてもシンプルな言語で、言語を実装する講義で使えそうなほどコード量は少なく、コードが追いやすい。

ImmutableをMutableに

特徴的なのはすべての変数がImmutable(不変)なところで、ループに相当する処理は基本的に再帰で書くことを想定しているようだ。ちょっと計算機の勉強をしているようで楽しい。

func sum(a):
  if a == 1:
    1
  else:
    a + sum(a - 1)
  end
end

sum(900)

# >>> 405450

1 + 2+ ... + n を計算するコード。

しかし私としてはSlackで動く楽しいジョーク言語を目指しているので、ループは普通にループで書きたい。そこで変数はすべてMutableになるよう改造した。

詳しくは私に聞くかコミットを見てもらうことにして、おおまかには以下の変更を加えた。

  • 変数宣言文からletを削除
  • 既存変数へのassignmentの際にExceptionをraiseせず書き換えるように変更

ちなみにBraidにはwhileループだけ実装されている。breakも無い。けどwhileの条件文とループ内のifを組み合わせれば基本的に任意のループが実現できるので問題はない。

print関数の再実装

lexer.pyを見た感じ、元々printは関数としてlexされる前に特別枠としてlexするようになっていたようだが、その部分はコメントアウトされている。今は通常の関数としてlexし、その代わりPythonのprint文を間に突っ込むようにしたようだ。

Slackでコミュニケートするために、出力結果は後から取り出せるようにしたいので、これこれのようにlexerからインタプリタまで変更を加えた。…と、今見るとprintのlexと関数一般のlexの改造部分がダブっている。関数一般の方はあとで消すことにしよう。

トークン置き換え・トークンコンバータの実装

funcadd といったトークンを、すべて :c_func: のようなSlack絵文字の表記に置きかえる。同時に対応するlexerのルールなども書き換える。

いくら楽しいSlackの絵文字とはいっても、冒頭の画像のようなコードを手で書かされるのは間違いなく苦痛である。そこで冒頭の画像のように、わかりやすい表記で書いてから変換できるようにもしておく。 converter.pyがそれだが、これはただの文字列置き換えなので特にどうってことはない。

Slack bot部の実装

Slack botのPythonでの実装は、その名もズバリ slackbot が有名なようだ。しかし、これは比較的高レベルなSDKであり、改行を含むメッセージをうまくlistenできないなど少々扱いづらかった。そのため、今回はWebSocket部をお世話するだけの薄いライブラリである python-rtmbot を採用した。書いていて気づいたのだがどうやら公式製のようだ。

github.com

Braid改めSlackMojicodeとrtmbotの間でコードをやりとりする部分をpluginとして書く。SlackでのMentionはBotから見ると内部的なIDで呼ばれる形で見えるため、自分自身のIDを取得するなどSlack APIを少々探ることになる。

コマンドの区別など極めて雑な実装だが、普通に動くのでよしとした。せめてもうちょっとリファクタしたくはある。

おわりに

はーおもしろかった。ねよ。

次の記事は @e10dokup 担当です。