漢文プログラミング言語 wenyan-lang でメリークリスマス！

これは[ゆめみ その 2 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/yumemi2) 25 日目の記事です。

# 前書き

最近巷で話題になっている（かもしれない）漢文プログラミング言語：[wenyan-lang](https://github.com/LingDong-/wenyan-lang) を、ご存知ですか？古代中国語の「文言文（漢文）」で記述する独特なプログラミング言語で、しかもなんとチューリング完全らしいです[^turing-completeness]！名前の由来については、中国語で漢文のことを「文言文」と言い、「文言」は拼音で `wenyan`、そして最後は言語の意味を持つ「文」をそのまま英語の `lang` にして、最後くっつけて `wenyan-lang` になったと思われるこの言語ですが、コンパイルすると JavaScript や Python のコードに変換されるから、最初は何かの言語をキーワードだけ置き換えて作られた言語かと思われるかもしれませんが、実はちゃんとした自身の文法があり、ただのキーワード置き換え言語ではないです。

[^turing-completeness]: まあチューリング完全はプログラミング言語の機能性とは関係ないですけどね、あの「ちょっと草植えときますね型言語 Grass」ですらチューリング完全ですから。

簡単な文法解説は既に他にも[記事](https://qiita.com/GandT/items/920af538c0a405199815)を書かれた方がいらっしゃるのでぜひそちらをご覧ください！今日はクリスマスなので、せっかくだからこの wenyan-lang で[ズンドコキヨシ](https://qiita.com/tags/ズンドコキヨシ)をもじったメリークリスマスのプログラムを試作して、そのコードを解説してみたいと思います。

# 元ソースコード

とりあえずまず全コードを載せておきますね：

```wenyan-lang
吾有一術。名之曰「考拉茲」。欲行是術。必先得一數。曰「甲」。乃行是術曰。
        有數零。名之曰「總」。
        為是九千九百九十九遍。
                若「甲」等於一者。乃得「總」。
                若非。
                        除「甲」以二。所餘幾何。名之曰「丙」。
                        若「丙」等於零者。除「甲」以二。名之曰「半」。昔之「甲」者。今「半」也。
                        若非。
                                乘「甲」以三。名之曰「丁」。加一於「丁」。名之曰「戊」。昔之「甲」者。今「戊」是也。
                        加「總」以一。名之曰「己」。
                        昔之「總」者。今「己」是也。
        云云。
是謂「考拉茲」之術也。

吾有一列。名之曰「祝」。充「祝」以「「聖」」。以「「誕」」。以「「快」」。以「「樂」」。

吾有一列。名之曰「回」。
有數一。名之曰「戊」。恆為是。
	
	施「考拉茲」於「戊」。名之曰「寂」。
	除「寂」以四。所餘幾何。名之曰「餘」。
	充「回」以「餘」。
	若「戊」大於四者。
		減「戊」以四。名之曰「甲」。夫「回」之「甲」。加其以一。名之曰「子」。
		減「戊」以三。名之曰「乙」。夫「回」之「乙」。加其以一。名之曰「丑」。
		減「戊」以二。名之曰「丙」。夫「回」之「丙」。加其以一。名之曰「寅」。
		減「戊」以一。名之曰「丁」。夫「回」之「丁」。加其以一。名之曰「卯」。

		吾有一列。名之曰「文」。
		夫「祝」之「子」。充「文」以其。
		夫「祝」之「丑」。充「文」以其。
		夫「祝」之「寅」。充「文」以其。
		夫「祝」之「卯」。充「文」以其。
		吾有一言。曰「文」。書之。
		
		有數零。名之曰「了」。
		若「子」等於一者。加一以「了」。昔之「了」者。今其是矣云云。
		若「丑」等於二者。加一以「了」。昔之「了」者。今其是矣云云。
		若「寅」等於三者。加一以「了」。昔之「了」者。今其是矣云云。
		若「卯」等於四者。加一以「了」。昔之「了」者。今其是矣云云。
		若「了」等於四者。
			吾有一言。曰「「神聖誕辰，樂兮此日。」」。書之。
			乃止。
		也。
	也。

加一以「戊」。昔之「戊」者。今其是矣云云。
```

こちらのソースコードを[オンラインエディタ](http://wenyan-lang.lingdong.works/ide.html)にコピペすれば、実行して下記のような結果が見られます：

```text
聖,誕,樂,快
誕,樂,快,誕
樂,快,誕,聖
// 中略...
樂,誕,聖,誕
誕,聖,誕,快
聖,誕,快,樂
神聖誕辰，樂兮此日。
```

# 解説

最初はズンドコキヨシレベルなら簡単だろと思ってましたが、なんといきなり壁にぶつかってしまったのです：

**執筆時の現時点ではなんと、[wenyan-lang がまだ乱数生成関数を実装していない](https://github.com/LingDong-/wenyan-lang/issues/87)のです😱**

でもラッキーなことに、作者さんはオンラインエディタに様々なサンプルプログラムを用意しています。なのでとりあえずその中からなんか通常の数列からいろんな数字を生成してくれる関数が書かれてあればいけると思いました。というわけで早速[コラッツの問題](https://ja.wikipedia.org/wiki/コラッツの問題)を解決する [collatz](https://github.com/LingDong-/wenyan-lang/blob/master/examples/collatz.wy) というサンプルが見つかりました。これを実行すると、渡された数字がコラッツ処理を掛けられてその過程を出してくれます。例えば `for (let i = 1; i += 1; i <= 10)` の数列を渡すと、下記のような出力を出してくれます：

```text
1,1
2,1,1
3,10,5,16,8,4,2,1,1
4,2,1,1
5,16,8,4,2,1,1
6,3,10,5,16,8,4,2,1,1
7,22,11,34,17,52,26,13,40,20,10,5,16,8,4,2,1,1
8,4,2,1,1
9,28,14,7,22,11,34,17,52,26,13,40,20,10,5,16,8,4,2,1,1
10,5,16,8,4,2,1,1
```

というわけで、最初にこれ見て「おお、これを適当な数字で割った余を取ればちょっとランダム数字っぽくない？」と思って、早速出力を別の命令に代入してみたのですが、いきなり本日二番目の壁にぶつ蹴ったわけです：

**数字の入力は漢数字以外受け付けないのです😱**

文法解説記事やいろんなサンプルコードを読んでみればわかりますが、数字は全て`一`とか`十`とか`十一萬四千五百一十四`とかで記入されているのですが、上記のプログラムは出力がアラビア数字です[^number-output]。そしてアラビア数字の入力はそもそもプログラムに無視されてしまいます…残念。というわけで別のプログラムを探してみたら、同じような処理だけどちゃんと漢数字を出力してくれるサンプル [collatz2](https://github.com/LingDong-/wenyan-lang/blob/master/examples/collatz2.wy) がありました！同じく `for (let i = 1; i += 1; i <= 10)` の数列を渡すと、下記のような出力を出してくれます：

[^number-output]: 色々サンプル見てみたが、おそらく出力が漢数字かアラビア数字かの違いは、出力対象が普通の数字か配列かの違いっぽい；普通の数字なら全部漢数字で出力されますが、配列になると全部アラビア数字に直されて出力されます。

```text
零
一
七
二
五
八
一十六
三
一十九
六
```

ふむふむ、今回はその過程ではなく、何回処理すれば終わるかという出力ですね。というわけでおかげさまでこれでランダム数字っぽいプログラムができました。これが最初の

```wenyan-lang
吾有一術。名之曰「考拉茲」。欲行是術。必先得一數。曰「甲」。乃行是術曰。
        有數零。名之曰「總」。
        為是九千九百九十九遍。
                若「甲」等於一者。乃得「總」。
                若非。
                        除「甲」以二。所餘幾何。名之曰「丙」。
                        若「丙」等於零者。除「甲」以二。名之曰「半」。昔之「甲」者。今「半」也。
                        若非。
                                乘「甲」以三。名之曰「丁」。加一於「丁」。名之曰「戊」。昔之「甲」者。今「戊」是也。
                        加「總」以一。名之曰「己」。
                        昔之「總」者。今「己」是也。
        云云。
是謂「考拉茲」之術也。
```

です。というわけでこのサンプルを見ながら解説していきますね。

まず`吾有一術`は明らかに関数宣言です。そして`名之曰「考拉茲」`でこの関数を`考拉茲`で命名します。さらに`欲行是術。必先得一數。曰「甲」` で、この関数には一つの数字引数があり、その引数名を`甲`に命名します。これで関数宣言が終わります。

次に関数の中身は`乃行是術曰`と`是謂「考拉茲」之術也` で囲まれて、その間の記述が関数処理です。

処理を見てみると、まず`有數零。名之曰「總」`はつまり「値が `0` の数字があり、その名は`總`である」という変数宣言です。wenyan-lang は変数宣言にはいくつかの方法があり、これがその中で一番短い方法です。他の方法はまた別のコードで追って解説します。

そして今度は`為是九千九百九十九遍`というループ処理宣言です。これはつまり `999` 回ループしますよの宣言で、ループの処理は下の`云云`で終わります。

その中のループ処理はさらに`若`による分岐処理が発生します。そして`乃得`で関数の戻り値を返します。なので最初の`若「甲」等於一者。乃得「總」`は引数の`甲`が `1` であれば、`總`を返すということです。

もし`甲`が `1` じゃなかったの処理はさらに`若非`と`也`で囲まれます。これはちょっとインデントがないとわかりにくいところですが。というわけで、まず`除「甲」以二。所餘幾何。名之曰「丙」`は`甲`を `2` で割って、その余を`丙`という変数を作って代入します。この`丙`が `0` なら、`甲`を `2` で割って商を`半`という変数を作って代入し、そして最後`甲`の値を`半`の値で置き換えます。逆にもし`丙`が `0` じゃなかったら、`甲`を `3` で掛けて、その積を`丁`という変数を作って代入し、さらに`丁`に `1` を足して`戊`という変数を作って代入します。最後は`甲`の値を`戊`の値で置き換えます。なおこの`甲`が `1` じゃなかった時の処理を行ったら、`總`に `1` を足して`己`という変数を作って代入し、`總`の値をこの`己`の値で置き換えます。

以上がこの`考拉茲`という関数の処理です。

必要なランダム数っぽい関数を導入したら、今度は初期配列を作ります。ズンドコキヨシの`ズンドコ`ですね。これは

```wenyan-lang
吾有一列。名之曰「祝」。充「祝」以「「聖」」。以「「誕」」。以「「快」」。以「「樂」」。
```

で作られています。まず`吾有一列。名之曰「祝」`は`祝`という名前の配列を宣言します、という意味です。先ほど wenyan-lang には様々な変数宣言方法があると言いましたが、これが一番オーソドックスな方法ですかね。ちなみに`列`や`數`などの型を示すキーワードがありますが、コンパイル対象の JavaScript も Python も Ruby もそもそも型がなかったりしますので、このキーワードは今後の型の拡張のためにあるのか、それともとりあえず漢文っぽい文法のためにあるのかがわかりません。そして最後`充「祝」以「「聖」」。以「「誕」」。以「「快」」。以「「樂」」`はつまりこの`祝`という配列に、`"聖"``"誕"``"快"``"樂"`の漢字を順番に入れます。せっかくの漢文プログラミング言語なので、日本語ではなく中国語の漢文で入れてみました。意味はメリークリスマスそのままです。

初期配列作ったら、今度はランダムで生成された文字を保存する配列を作ります。これはもう解説する必要はないですね、そのままです：

```wenyan
吾有一列。名之曰「回」。
```

これで`回`という空配列が作られました。

次にいよいよ本番のループ処理です。まずはループ回数を示す変数を作成：

```wenyan-lang
有數一。名之曰「戊」。
```

これは先ほどの`有數零。名之曰「總」`と同じように、`戊`という変数に `1` を代入します。簡単ですね。

```wenyan-lang
恆為是。
```

これで `while(true)` が作られます。はいそうです無限ループです。

```wenyan-lang
    施「考拉茲」於「戊」。名之曰「寂」。
    除「寂」以四。所餘幾何。名之曰「餘」。
    充「回」以「餘」。
```

これでループ回数を示す`戊`を`考拉茲`の関数の引数として代入し、その結果を`寂`という変数を作って代入します。さらに`寂`を `4` で割った余をランダム数として`餘`という変数を作って代入します。最後は先ほど作ったランダム数を保存する配列`回`に入れます。

```wenyan-lang
    若「戊」大於四者。
```

目標の文字列は`聖誕快樂`の 4 文字なので、このループの最初の 3 回目はこれ以上何もしません。4 回目からいよいよ文字の取り出し処理を始めます

```wenyan-lang
        減「戊」以四。名之曰「甲」。夫「回」之「甲」。加其以一。名之曰「子」。
        減「戊」以三。名之曰「乙」。夫「回」之「乙」。加其以一。名之曰「丑」。
        減「戊」以二。名之曰「丙」。夫「回」之「丙」。加其以一。名之曰「寅」。
        減「戊」以一。名之曰「丁」。夫「回」之「丁」。加其以一。名之曰「卯」。
```

まずは今の回数`戊`から 4 を減らした数字を`甲`とし、ランダム数を保存している配列`回`の`甲`番目の数字を取り出して、さらにそれに `1` を足して`子`という変数を作って代入します。
さらに同じような処理で順番に`丑``寅``卯`を作ります。ちなみにここでなぜ `1` を足すかというと、wenyan-lang の配列アクセスは通常の多くのプログラミング言語と違って、`0` ではなく`一`番からのアクセスだからです。`4` で割った余は `0~3` の範囲なので、それを `1` 足して `1~4` の範囲を作ります。

ちなみに本当は配列の要素数自体をそもそも `4` にキープできたら一番楽ですが、残念ながら調べた限り wenyan-lang に配列に要素を入れるメソッドがあっても、減らすメソッドが見当たりませんでしたので、このような配列の最後の 4 つの要素を取り出す方法にしました。

ここができたら出力を書きます：

```wenyan-lang
        吾有一列。名之曰「文」。
        夫「祝」之「子」。充「文」以其。
        夫「祝」之「丑」。充「文」以其。
        夫「祝」之「寅」。充「文」以其。
        夫「祝」之「卯」。充「文」以其。
        吾有一言。曰「文」。書之。
```

ここはまず`文`という出力用の配列を作ります。そして初期配列の`祝`から、先ほど取得した`子``丑``寅``卯`番目の文字を取得して`文`に入れます。最後は`文`を出力します。これが出力例に出てきてる`聖,誕,樂,快`とかの部分です。

さてこれではランダムの文字列の出力ができたので、最後は希望している文字列と一致するかどうかの判定で、一致したら別の文字列を出力してプログラムを終わらせる処理ですね。ズンドコキヨシなら`ズ``ン``ド``コ`の出力がでてるかどうかを判定し、できたら`キ・ヨ・シ`の出力ですが、せっかくのクリスマスなのでさらにそれっぽい漢文の`"神聖誕辰，樂兮此日。"`にしてみました。文字通り「神聖たる（イエスキリストの）誕生の<ruby><rb>今日</rb><rp>（</rp><rt>こんにち</rt><rp>）</rp></ruby>を祝福せよ」の意味です（文字通りとは）。

```wenyan-lang
        有數零。名之曰「了」。
        若「子」等於一者。加一以「了」。昔之「了」者。今其是矣云云。
        若「丑」等於二者。加一以「了」。昔之「了」者。今其是矣云云。
        若「寅」等於三者。加一以「了」。昔之「了」者。今其是矣云云。
        若「卯」等於四者。加一以「了」。昔之「了」者。今其是矣云云。
        若「了」等於四者。
            吾有一言。曰「「神聖誕辰，樂兮此日。」」。書之。
            乃止。
        也。
```

残念ながら、本当は`若「子」等於一且「丑」等於二...`の文法があればいいのですが今のところそれがないっぽいのです。なので仕方なくここは達成条件数での判断にしました。一つの条件が達成されるたびに`了`という変数に `1` を足して、合計 4 つの条件があるので`了`が `4` と等しければ全部の条件が達成できたという判定です。

そして`了`の判定条件はつまり一つ目の乱数`子`が `1`、二つ目の乱数`丑`が `2`、三つ目の乱数`寅`が `3`、四つ目の乱数`卯`が `4` であるという単純な判定です。文字列の判定も wenyan-lang では提供されていないっぽいので数値の判定にしました。

最後`了`が `4` になったら、さらに`"神聖誕辰，樂兮此日。"`の文字列を出力し、`乃止`で`恆為是`による無限ループを抜け出します。

ちなみに`云云`も`也`も同じく、`若`の条件判定文の終了キーワードです。どれを使うかは漢文の文法に合わせて選べます。ツヨい。

そしてこの無限ループの最後に、ループ回数`戊`の処理を入れればいいですね：

```wenyan-lang
加一以「戊」。昔之「戊」者。今其是矣云云。
```

これで`戊`に `1` を足して、得られた値をまた`戊`に戻します。つまりループが回るたびに`戊`が大きくなる、というわけです。これで、最後に生成したランダム数じが順番に `1` `2` `3` `4` になったら、`聖,誕,快,樂`が出力されたのち`神聖誕辰，樂兮此日。`が出力されて、プログラムが終了します。

# 後書き

いかがでしたでしょうか、wenyan-lang のメリークリスマスプログラム。残念ながらこのプログラムはランダム数っぽい数字を生成していますが本物のランダム数ではないし、しかも何回実行しても全く同じ結果が出てきてしまいます。本当はもうちょっとブラッシュアップして、せめて現在の時間が取得できたらそれを seed として毎回違う結果が得られるように改造したいのですが、流石に業務時間を使いすぎるのは良くありませんのでこれでよしとします。もしこの解説で少しでも wenyan-lang に興味が湧いて来れたら幸いです。~~なお筆者は宗教上の理由で Swift 以外の言語はこれ以上触りたくないです。~~