#深夜の真剣レポート60分一本勝負 第2回 お題:「好きな一言」
みなさん、好きな一言はありますか? 私はDiscord bot「うだまなみ」の起動時メッセージ「おはようっ!」です。
友人のDiscordサーバーに導入されているDiscord bot「うだまなみ」には、「!1d6」のようなダイスロールコマンドがあり、極めて好評でした(!6d200でポケモンの種族値を生成する、などの遊び方を観測しました)。
ダイスロールコマンドへの入力として「!1d6*10」などの計算式を試しているひとがいたので、私はふと思い立って電卓機能「!calc」コマンドを開発しPRを送ることにしました。
!calcは、入力文字列を計算式として読み取り、評価し、その結果を返信する、電卓のようなコマンドです。計算式として、以下のものがサポートされます:
- 整数・浮動小数点数
- 四則演算・冪乗・ダイスロール
- 基本的な関数(sinとか、lnとか、†fix†とか)
- 配列・オブジェクト
- †ラムダ式†
……電卓?
!calc 1+2*3
7
!calc (1d(24*7))+"時間ぐらいまでなら遅刻してもええんちゃう?"
"160時間ぐらいまでなら遅刻してもええんちゃう?"
!calc (l => "H:"+l[0]+" A:"+l[1]+" B:"+l[2]+" C:"+l[3]+" D:"+l[4]+" S:"+l[5]+" (total:"+sum(l)+")")(repeat(()=>1d200, 6))
"H:108 A:128 B:43 C:196 D:19 S:112 (total:606)"
!calcの中身は、一般的にはプログラミング言語の「インタプリタ」と呼ばれるものです。これは、以下のように動作します:
- 入力文字列を解釈し、抽象構文木(略称:AST 後段で扱いやすい形式の数式のデータ)を生成する。
- 得られたASTに対して、「評価」(数式の内容に応じて、適切な方法で計算をすること)を行う。
順に見ていきましょう。
「まなみちゃんは、わたしのともだちのいもうとです。」←この文を音読してみてください。
日本語に慣れ親しんだひとであれば、「ま」「な」「み」……と、一文字ずつ区切って読むようなことはしなかったと思います。
おそらく、
「まなみちゃん」は
「わたし」の
「ともだち」の
「いもうと」です。
というように、文中から意味のある単語を切り出し、修飾-被修飾の関係を理解した上で音読したはずです。
同じように、!calcに渡された文字列「1-2*3+4」は、算術演算子の順序を考慮したうえで、次のような抽象構文木に解釈されます:
加算(
減算(
整数要素「1」,
乗算(
整数要素「2」,
整数要素「3」
)
),
整数要素「4」
)
執筆時間が60分しかないので深入りしませんが、このような文字列の解釈をするプログラムは「パーサ」と呼ばれています。今回はnomというパーサを実装するためのライブラリを利用しました。
次に、得られた抽象構文木から計算結果の値を得る必要があります。
ここで、抽象構文木上の要素と計算結果は、は厳密に区別しなければいけません。(区別しないと実装する際に頭がこんがらがって死にます)
抽象構文木上の、加算(整数要素「1」, 整数要素「2」)
や浮動小数点要素「9.8」
は、計算の「命令」のようなものです。
一方で、計算結果の「1」や「9.8」は、計算の「結果」です。
上で出てきた要素のそれぞれについて、以下のような規則で評価を行います:
整数要素「n」
:nを計算結果として返します。加算・減算・乗算(構文木a, 構文木b)
:a
を評価し、b
を評価し、その結果を足したものを計算結果として返します。
この規則で、先に挙げた構文木を評価してみます。
- 一番外側の
加算
を評価するために、その内側の2つの構文木を評価し、結果を足します。- 内側の
減算
を評価するために、さらに内側の乗算
と整数要素
を評価し、結果を引きます。整数要素「1」
を評価すると、計算結果として1が返ります。乗算
を評価するために、整数要素「2」
と整数要素「3」
を評価し、結果を掛けます。整数要素「2」
を評価すると、計算結果として2が返ります。整数要素「3」
を評価すると、計算結果として3が返ります。- そのため、この
乗算
の計算結果は 2*3 = 6です。
- そのため、この
減算
の計算結果は 1-6 = -5です。
整数要素「4」
を評価すると、計算結果として4が返ります。- そのため、この
加算
の計算結果は -5+4 = -1です。
- 内側の
以上のような再帰的な「評価」を繰り返すと、複雑なASTから計算結果を得ることができます。
なお、ラムダ式の評価については「評価戦略」だとか「クロージャー」だとかいった複雑な話が出てきますが、時間切れなので割愛します。気が向いたら来週書く
一般のプログラミング言語でも、「インタプリタ」と呼ばれる種類の動作をするものは、以上の2つの手順で動作しています。なお、コンパイラと呼ばれるものは、2つ目の手順で計算を実際に行う代わりに、より機械寄りのコードを生成します。こいつの実装は100倍大変なので、みなさんもやってみてください。