10
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-12-25

これはゆめみ その 2 Advent Calendar 2019 25 日目の記事です。

前書き

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

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

元ソースコード

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

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

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

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

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

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

こちらのソースコードをオンラインエディタにコピペすれば、実行して下記のような結果が見られます:

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

解説

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

執筆時の現時点ではなんと、wenyan-lang がまだ乱数生成関数を実装していないのです😱

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

有數一。名之曰「戊」。

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

恆為是。

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

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

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

    若「戊」大於四者。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

後書き

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

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

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

10
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?