gnuplot とは関数のグラフや実験データをプロットするためのフリーソフトウェアの一つです。例えば、次のようなグラフを描くことができます。
さて、gnuplot には作業を自動化するためにバッチファイルを読み込んで実行する機能があります。例えば、上のグラフを表示するには、
plot [-5:5] sin(x) replot cos(x)
という内容を sincos.plt*1 というファイルに書いて、gnuplot 上で
> load "sincos.plt"
というコマンドを入力します。
gnuplot の説明はここまでにして、今回のテーマは gnuplot のバッチファイルで理論上あらゆる計算が実現できるということです。
チューリング完全
まず、あらゆる計算が実現できるということを定めます。
ここでは、計算とは、入力から定められた規則を定められた手続きに従って適用することで出力を構成することを言います。規則というのは、例えば、C言語、BASIC、Fortran、LISP、Haskell、Java、Python、Ruby といったプログラム言語であり、手続きとはそれらの言語で書かれたプログラムのことです。
今、規則Rによってあらゆる計算が実現できるということを、上に挙げたいずれか一つの言語について、その言語で書かれたプログラム(ただし入力を受け取って出力を返すもの。停止しなくてもよい。)のすべてを、規則Rを用いた手続きで同じことができるように書き換えられることと定めます。もし上のいずれか一つについて書き換えができるならば、すべてについてもできることが知られています。というのも上の言語は相互に書き換えが可能だからです。その意味であらゆる計算が実現できると言うのです。
逆に、規則Rを用いて書かれた手続きのすべてを上のどの言語にも書き換えられることも言えるはずです。なぜなら、規則Rを適用するプログラム(ここでいう gnuplot)は何らかのプログラム言語で書かれているからです。
そのような意味であらゆる計算が実現できるような規則Rは、普遍的なプログラム言語のクラスに属していると考えられます。そのクラスに属するすべての言語は、他の言語と相互に書き換えが可能であり、また、C言語と相互に書き換えが可能です。このクラスに属する規則はチューリング完全(Turing complete)であると言われます。
今回のテーマをこの言葉で書き換えるならば、gnuplot のバッチファイルはチューリング完全であるということになります。
証明方法
規則Rのチューリング完全性を証明するには、上にも書いたように、それを用いてあるプログラム言語Lのすべてのプログラムを書き換えられることを示せばいいということになります。具体的な方法としては、LのインタープリタをRを用いて作ればいいのです。
プログラム言語Lのインタープリタ とは、Lで書かれたプログラム と入力 を受け取って、それを実行して得られる出力 を返すプログラムであるとします。すなわち、 です。
プログラム言語Lがチューリング完全であるとき、規則Rがチューリング完全であるとは、Lによって書かれたすべてのプログラム について、Rで書かれた手続き が存在して、すべての に対して となると言えます。(正確には逆も言える必要があります。)もしRによってLのインタープリタ が書けるならば、 より、Rがチューリング完全であることが言えます。
要するに、gnuplot のバッチファイルでいずれかのチューリング完全な言語のインタープリタを作れば、gnuplot のバッチファイルがチューリング完全であるということになります。
brainfuck
とは言うものの、一般的なプログラム言語に対してインタープリタを作ることはあまり簡単ではありません。そこで登場するのが brainfuck*2 という言語です。
brainfuck の特長は何と言っても、チューリング完全であり、かつコンパイラやインタープリタが簡単に書けることです。その代わりにプログラムを書くことが非常に難しいという犠牲を払っています。例えば、Hallo world プログラムは次のようになります。
+++++++++[>++++++++>+++++++++++>+++++<<<-]>.>++.+++++++..+++.>-. ------------.<++++++++.--------.+++.------.--------.>+.
な… 何を言ってるのか わからねーと思うが
言語仕様については
Web上で実行できるインタープリタは
にあります。
ともかく gnuplot のバッチファイルで brainfuck のインタープリタを作ればよいという方針が定まりました。
それでは作ってみよう
というわけで作りました。プログラムは以下の3つのファイルから成り立っています。
bfi.plt: 大本で読み込むファイル
load "bfi_init.plt" load "bfi_run.plt"
bfi_init.plt: 関数の定義および変数の初期化
stoi(s, l) = l == 0 ? 0 : 2 * stoi(s, l - 1) + (s[l:l] eq "0" ? 0 : 1) itos(i, l) = l == 0 ? "" : itos(i / 2, l - 1) . ((i & 1) == 0 ? "0" : "1") repstr(s, i, j, r) = s[:i - 1] . r . s[j + 1:] cset = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ" .\ "[\\]^_'abcdefghijklmnopqrstuvwxyz{|}‾" itoc(i) = (i >= 32 && i <= 126) ? cset[i - 31: i - 31] : "" cmem(k) = k == 0 ? "00000000" : cmem(k - 1) . cmem(k - 1) mget(m, l, i) = stoi(m[l * i + 1:l * (i + 1)], l) mset(m, l, i, v) = repstr(m, l * i + 1, l * (i + 1), itos(v, l)) pptr = 0 ins = program[1:1] pstc = cmem(10) psptr = 0 mem = cmem(10) mptr = 0 iptr = 0 ostr = ""
bfi_run.plt: インタープリタの本体部
if (ins eq ">") mptr = mptr + 1 if (ins eq "<") mptr = mptr - 1 if (ins eq "+") mem = mset(mem, 8, mptr, mget(mem, 8, mptr) + 1) if (ins eq "-") mem = mset(mem, 8, mptr, mget(mem, 8, mptr) - 1) if (ins eq ".") ostr = ostr . itoc(mget(mem, 8, mptr)) if (ins eq ",") mem = mset(mem, 8, mptr, mget(input, 8, iptr)); \ iptr = iptr + 1 if (ins eq "[") pstc = mset(pstc, 16, psptr, pptr); psptr = psptr + 1 if (ins eq "]") pptr = mget(mem, 8, mptr) == 0 ? pptr : \ mget(pstc, 16, psptr - 1) - 1; psptr = psptr - 1 pptr = pptr + 1 ins = program[pptr + 1:pptr + 1] if (ins ne "") reread print ostr
このバッチファイルは、変数 program に brainfuck のプログラムを、input に入力のビット列を指定し、bfi.plt をロードすることで動作します。入力を読まない場合は input は不要です。
それでは先に出した Hello world プログラムを実行してみましょう。
コマンド:
> program = "+++++++++[>++++++++>+++++++++++>+++++<<<-]>.>++.+++++++..++\ +.>-.------------.<++++++++.--------.+++.------.--------.>+." > load "bfi.plt" Hello, world!
出た!
厳密に言うとこのバッチファイルは停止しない限り出力を画面に出さないのですが、出力文字列 ostr を出力と同一視すれば、 brainfuck のあらゆるプログラムを実行できることになる、はず。