難読プログラミング言語をサクサク書くためのマクロ機能付超簡易言語
Brainf*ck等の難読プログラミング言語でプログラムを作成するときは、ちょっとしたトランスレータを用意すると格段に書きやすくなります。
1. シンプルなトランスレータ
たとえば、一番シンプルなのはこんな感じ。
step1.awk
#!/usr/bin/nawk -f $1 == "inc" { printf("+") } $1 == "dec" { printf("-") } $1 == "input" { printf(",") } $1 == "print" { printf(".") } $1 == "shift" { printf(">") } $1 == "unshift" { printf("<") } $1 == "while" { printf("[") } $1 == "wend" { printf("]") }
こうすると、以下のようなソースからBrainf*ckプログラムが生成できます。
echo.bfs
# echo program inc while shift input print unshift wend
実行結果
$ nawk -f step1.awk echo.bfs +[>,.<]
2. 連続命令の省略表記
もう少し手を加えて、同じ命令が続く場合に「inc 20」のような省略表記ができるように機能を追加してみます。
step2.awk
#!/usr/bin/nawk -f $1 == "inc" { nprint($2, "+") } $1 == "dec" { nprint($2, "-") } $1 == "input" { nprint($2, ",") } $1 == "print" { nprint($2, ".") } $1 == "shift" { nprint($2, ">") } $1 == "unshift" { nprint($2, "<") } $1 == "while" { printf("[") } $1 == "wend" { printf("]") } function nprint(n, s) { if (n + 0 < 1) n = 1 for (i = 0; i < n; i++) { printf("%s", s) } }
画面へ「foo」と表示するプログラムが、こんな感じで書けるようになりました。
foo.bfs
# print "foo" program inc 102 print shift inc 111 print print
実行結果
$ nawk -f step2.awk foo.bfs ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++++++++++++++.>++++++++++++++++++++++++++++ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++..
3. マクロ機能の実装
この手の言語は、分岐や繰り返し構造を作るために定型的な命令の組み合わせパターンを使うことがよくあります。というわけで最終版では、それらをまとめてマクロ定義できるように機能を追加してみます。またマクロ定義は別ファイルにできると使い勝手が良いのでinclude命令も追加します。
bfs2bf.awk
#!/usr/bin/nawk -f { eval($0) } function eval(line, token, s, pushedfile) { split(line, token) if (token[1] == "inc") nprint(token[2], "+") if (token[1] == "dec") nprint(token[2], "-") if (token[1] == "input") nprint(token[2], ",") if (token[1] == "print") nprint(token[2], ".") if (token[1] == "shift") nprint(token[2], ">") if (token[1] == "unshift") nprint(token[2], "<") if (token[1] == "while") printf("[") if (token[1] == "wend") printf("]") # マクロ定義 if (token[1] == "def") { s = "" while (getline2() > 0) { if (gLineBuffer ~ /end_def/) break s = s "\n" gLineBuffer } gHash[token[2]] = s } # マクロ展開 if (token[1] in gHash) { s = gHash[token[1]] gsub(/\$1/, token[2], s) gsub(/\$2/, token[3], s) gsub(/\$3/, token[4], s) evalLines(s) } # includeファイル処理 if (token[1] == "include") { pushedfile = gCurrentFile gCurrentFile = token[2] gsub(/"/, "", gCurrentFile) while (getline2() > 0) { eval(gLineBuffer) } close(gCurrentFile) gCurrentFile = pushedfile } } function getline2() { if (gCurrentFile == "") { return getline gLineBuffer } else { return getline gLineBuffer < gCurrentFile } } function evalLines(lines, line, n, i) { n = split(lines, line, "\n") for (i = 1; i <= n; i++) { eval(line[i]) } } function nprint(n, s) { if (n + 0 < 1) n = 1 for (i = 0; i < n; i++) { printf("%s", s) } }
def命令で定義されたマクロ定義はgHash[]に格納されます。逆に現在処理中のトークンがgHashのキーに見つかればマクロを展開します。マクロ定義中で$1,$2,$3と仮引数で書かれていた箇所があれば実引数で置き換えます。
includeの方は、インクルードファイルを開いて一行ずつ処理しますが、このときeval()関数が再帰的に呼ばれるため、現在処理中のファイル名をよけておく必要があります。
これを使うと、入力文字が数字である場合に"number"と表示するプログラムはこんな感じで書けます。
isnumber.bfs
include "lib.bfs" # "number"と出力するマクロ定義 def print_number multi 10 11 print inc 7 print dec 8 print dec 11 print inc 3 print inc 13 print end_def # main program # '0'と比較 multi 6 8 dec shift input unshift compare # '9'と比較 shift dup if_not_zero shift 2 inc 11 unshift 2 end_if shift compare # "number"出力 shift if_not_zero shift print_number unshift end_if
ここまでくると、ほとんどオレオレ言語ですね。インクルードされるライブラリはこんな感じになります。
lib.bfs
# 乗算 # *p => arg1 * arg2 def multi inc $1 while shift inc $2 unshift dec wend shift end_def # ゼロクリア # *p => 0 def set_zero while dec wend end_def # 分岐 def if_not_zero while set_zero end_def def end_if wend end_def # *pを*(p+1)に複製 # ただし*(p+2)を作業用に使用する def dup while shift inc shift inc unshift 2 dec wend shift 2 while unshift 2 inc shift 2 dec wend unshift 2 end_def # 比較 # *p >= *(p+1)の場合 *p => 0, *(p+1) => 0 # *p < *(p+1)の場合 *p => 0, *(p+1) => *(p+1) - *p def compare while shift dup shift if_not_zero unshift dec shift end_if unshift 2 dec wend end_def
実行結果
$ nawk -f bfs2bf.awk isnumber.bfs ++++++[>++++++++<-]>->,<[>[>+>+<<-]>>[<<+>>-]<<>[[-]<->]<<-]>[>+>+ <<-]>>[<<+>>-]<<[[-]>>+++++++++++<<]>[>[>+>+<<-]>>[<<+>>-]<<>[[-]< ->]<<-]>[[-]>++++++++++[>+++++++++++<-]>.+++++++.--------.-------- ---.+++.+++++++++++++.<]
Enjoy!