(本記事は Zennに投稿したもの と同一です)
はじめに
Cholcは、和音でソースコードを記述する 演奏可能な プログラミング言語です。
brainf*ckに似た構文で、和音を組み合わせたコード進行によって値やポインタを操作します。
言語の概要や言語仕様については前回の記事をご覧ください。
本記事では、実践的な(?)Cholcプログラミングとしてサンプルコードを2つ紹介したいと思います。
1. 2つのセルの値を(asciicodeとして)足し算
まずはシンプルなコードとして、1バイトの整数の足し算を紹介します。
(サンプルコードといえばHello worldですが、esolangゆえに実装が意外と複雑なのでさらにシンプルな例を見ていきます)
C v F v Cm |: C Fm :| C X
このプログラムは、入力の2文字をasciiコードとして解釈し、両者を足した結果のasciiコードを文字として出力します。
例: #-
それではコードを順に見ていきましょう。
入力
まずは入力です。命令 v
でポインタが指すアドレスに、入力文字のasciiコードを読み込みます。
C v F v
まず、C
でアドレス [0]
をインクリメントします。この値は後で上書きしますが、後でコード進行によってポインタ移動をするために指定する必要があります。
続いて、v
でアドレス [0]
に1文字目を格納します。
その後の和音 F
もアドレス移動のためだけに使用します。C -> F
のコード進行によってポインタは [-1]
へ移動します。そして、v
でアドレス [-1]
へ2文字目を格納します。
入力処理はここまでですが、出力のための帳尻合わせとして Cm
でポインタを [0]
へ戻し、[0]
の値をデクリメントしておきます。
加算
続いて、加算の部分です。
|: C Fm :|
ループ |: :|
で C Fm
を繰り返しています。サブドミナントマイナーだいすき
この形は値の移動や加算によく使うパターン(※使用人口1人)なので覚えておいてください。原理は以下の通りです。
1文字目、2文字目のasciiコードをそれぞれ a
, b
とすると、初期状態のメモリは以下の状態になっています。
addr | -1 | 0 |
---|---|---|
ptr | ★ | |
value | b | a-1 |
ポインタが指す値が0ではない1ので、|:
でジャンプせずそのままループの内側へ入ります。
続いて、C
で アドレス [0]
の値をインクリメント、 Fm
でアドレス [-1]
の値をデクリメントします。
addr | -1 | 0 |
---|---|---|
ptr | ★ | |
value | b | a |
addr | -1 | 0 |
---|---|---|
ptr | ★ | |
value | b-1 | a |
そして、 :|
でループの先頭へ戻り上記を繰り返します。アドレス [-1]
が 0
になったタイミングでループを抜けるため、結果的に [0]
には [-1]
に入っていた値が足されるという仕掛けです。
addr | -1 | 0 |
---|---|---|
ptr | ★ | |
value | 0 | a+b-1 |
出力
最後に、計算結果を出力します。その前に、ポインタを計算結果のアドレス [-1]
へ移動する必要があるため、 C
でポインタのインクリメントを行います。
このときに巻き添えで値もインクリメントされてしまうので、ループ直前の Cm
であらかじめ値を1減らしておいていました。
addr | -1 | 0 |
---|---|---|
ptr | ★ | |
value | 0 | a+b |
最後に、結果の a + b
を X
で出力します。
2. 1から30まで順番に表示
続いて、もう少し複雑な例を見ていきます。このプログラムは、01
から 30
まで順に数を表示します。
F F F F F F F F |: C C C C C C Fm :|
Dm Dm Dm Dm Dm Dm Dm Dm Dm
|: Ab Bb G Cm :|
Am Am Am |: |: G Gm X F Bb X D :| G X |: Bbm Dm Fm :| Bb Bbm X A :|
010203040506070809101112131415161718192021222324252627282930
オフセット48を作成
数字を表示するためには、整数を数値の文字に変換する必要があります。'0'
のasciiコード 48
を足すことで数値を数値の文字に変換可能です。
後で使用するので、オフセットを準備しておきましょう。
F F F F F F F F
|: C C C C C C Fm :|
まずは F F F F F F F F
でアドレス [0]
に 8
を作ります。続いて、|: C C C C C C Fm :|
で値を6倍します。
この形式は前述の足し算で出てきたループの応用で、C
でアドレス [1]
を6増やすたびに Fm
で [0]
を1減らすことで6倍する処理になっています。
値をコピー
オフセット 48
は複数回使用するので、使う前に値をコピーしておきます2。またまた足し算のループの応用で、
|: {和音1} {和音2} ... {和音n} {コピー元和音}m :|
とすることでn箇所に値をコピーしています。
どのアドレスへ書き込んでも良いですが、せっかくなので 王道進行 にしてみました3。
|: Ab Bb G Cm :|
ループカウンタと終了条件の用意
1から30まで順番に数字を出すためにループを回します。その前に、ループカウンタや終了条件のしきい値を格納しておきましょう。
まずは、繰り上がり判定のためのループカウンタとして -9
を作ります。
Dm Dm Dm Dm Dm Dm Dm Dm Dm
次に、終了判定として 3
を作ります。10の位が 3
になったらループを抜けます。
Am Am Am
ここで小技を1つ。
両方ともメインループ |: |: G Gm X F Bb X D :| G X |: Bbm Dm Fm :| Bb Bbm X A :|
の直前に実行すれば十分ですが、Dm Dm Dm Dm Dm Dm Dm Dm Dm
をあえて値コピーの |: Ab Bb G Cm :|
のループ直前に実行しています。
:|
がループを抜けるのはポインタが指す値が0のときです。一方、次のループ開始 |:
もアドレスが指す値がなので、そのままではループをスキップしてしまいます。ループの直後に別のループを始めることはできないのです。
|: C C C C C C Fm :| // このループを抜けたら
|: Ab Bb G Cm :| // こちらのループは入らずスキップされてしまう!
そこで、Dm Dm Dm Dm Dm Dm Dm Dm Dm
の処理を間に挟むことでポインタが指す値を非0にして次のループを実行可能にしています。
|: C C C C C C Fm :|
Dm Dm Dm Dm Dm Dm Dm Dm Dm // ポインタが指す値が非0に
|: Ab Bb G Cm :| // ループに入れる
このように、Cholcではループと初期化処理をどのような順序で記述するかが重要です。
メインループ:数値を増やしながら出力
いよいよ本題の、1から30を表示するループです。インデントを付けると以下の構造になっています。
|:
|:
G Gm X
F
Bb
X
D
:|
G
X
|:
Bbm
Dm
Fm
:|
Bb Bbm X
A
:|
1命令ずつ見ていっても良いのですが、疑似言語に書き直したほうがイメージが付きやすいと思います。
(本物のソースコードとインデント、改行位置を合わせています)
// 「ループカウンタと終了条件の用意」での前処理
cnt = -9
limit_10 = 3
// 1の位
digit_1 = 48 // '0'
// 10の位
digit_10 = 48 // '0'
// 1の位の値
i = 0
// メインループ
while (true) {
while (true) { // digit_1を1ずつ増やす
print(digit_10)
i++
digit_1++
print(digit_1)
cnt++; if (cnt == 0) break // 9回目(1の位が9)でcntが0になりループを抜ける
}
digit_10++ // 繰り上がり
print(digit_10)
while (true) { // digit_1を '0', cntを-9に戻す
digit_1--
cnt--
i--; if (i == 0) break
}
print(digit_1) // '0'
limit_10--; if (limit_10 == 0) break // 30になったらループを抜ける
}
出力のためにポインタだけ移動して値を変えたくない場合には、G Gm X
のようにインクリメントとデクリメントを組み合わせて値の変化を相殺しています。
おわりに
以上、Cholcのサンプルコードの紹介でした。esolangの経験が浅く手探りで書いたので色々改良の余地があると思います。Brainf*cker、コードゴルファーの方々のスゴ技をお待ちしております(他力本願)