昔はよかった…

私が言うのもなんですが、最近のプログラミング言語ときたら、 やたら複雑で、習得が困難なものになっていると思いませんか?

思えばすべての変数がスタティックであった頃は管理も容易でした。 しかし、ALGOL系の言語がローカル変数をスタックに保持するようになったことで、 変数に記憶域期間という謎の概念が追加されてしまいました。 Cで、 ローカル変数へのポインタを戻り値で返してひどい目にあった初心者も多いことでしょう。 さらに、ローカル変数をスタックに置くことで 再帰呼び出しが可能になってしまいましたが、 これまた初心者泣かせです。 クイックソートやハノイの塔のソースを読もうとしたものの、 頭の中がこんがらがって爆発しそうになった、というのは、 誰もが経験していそうです。

さらに最近のプログラミング言語では、オブジェクトを何もかもヒープに置く傾向があり、 これがまた初心者泣かせの概念である ポインタを多用することにつながっています。 え? ポインタじゃなく参照だって? そりゃCのポインタには宣言の構文が腐っているとか実行時チェックがないとかの 問題はあるけれど、参照関係がこんがらがるとひどい目にあうという問題は ポインタと呼ぼうが参照と呼ぼうが同じこと、 今までは1次元のメモリ領域を相手にしていればよかったのが、 ポインタ(参照)が導入されることで、 空間的な把握能力が必要になってしまったのです。

それどころか、オブジェクトという概念は、 プログラム中に状態を持つカタマリがたくさんあるという 概念に他ならず、ということは当然、 プログラマはたくさんあるカタマリの状態を把握し、管理しなければならないのです。 これは、プログラムの複雑度を、その組み合わせの数だけ、 つまり指数関数的に増加させることになります。

あまつさえ、OO言語では、ポリモルフィズムという機能により 適用される手続きすら実行時に決まるのですから、 プログラムのソースを読むこと(静的な解析)はますます困難になってしまっています。 Cなどでは、ある関数のソースを読む際、 目的の関数からどんどん下位へと掘り進めていけばいいわけですが、 OO言語では、 インスタンスフィールドなどに保持されているオブジェクトのメソッドが呼ばれていたら、 そのオブジェクトが生成されているところまで遡って読む必要があります。 しかし、ソースを遡って読む、つまり、 どの実行パスを通ってそこに来たかを探ることは、 単に実行順で読んでいくのに比べて圧倒的に難しいものですし、 それどころか、やっと探り当てたオブジェクトの生成箇所で デザインパターンなんぞ使われていたりすると、 さらにそこから長い旅の始まりです。

いや、まだそれでも、オブジェクトのメソッドがクラスという形で明確に定義され、 継承関係もソース中にきちんと記述されていれば、まだしも追うことができるでしょう。 しかし、プロトタイプベースのOO言語では、 クラスすら明確に定義されず、実行時にへろへろ変化したりするのです。

また、上に書いたように、たくさんのオブジェクトがいろいろな状態を持つから 今の言語は難しいのですが、 それでもオブジェクトの取りうる状態が クラスのフィールドという形で明確に定義されていれば まだ把握できるかもしれません。 しかし、言語によっては、クロージャとかいう謎の機能で、 単なるローカル変数のつもりだったものがいつの間にやら状態を持つ オブジェクトとして扱われてしまったりします。 これでは、どこに状態が隠されているのかさっぱりわかりません。


「昔はよかった…」


と、空を仰いで嘆息するあなたのために、今回開発した言語が「BF-BASIC'n」です。

BF-BASIC'nは、 「現代計算機科学の父」と言われるAlan Turing が考案した最もプリミティブな形の仮想マシンの上に、 あの80年代の、プログラミング言語といえばBASICだった時代の 雰囲気を反映させた言語です。 スタックもヒープもオブジェクトもポリモルフィズムもクロージャもありません。 とりわけ初心者にとって、学習曲線を急にしないという意味で、 このような言語には重要な意味があると私は確信しています。

さて、新しいプログラミング言語を作ったときには、 まず「Hello World!」を表示させてみる、というのがセオリーでしょう。 BF-BASIC'nで、「Hello World!」 と表示させるプログラムのソースは以下のようになります。

すぽぽぽぽぽぽぽぽぽすてらのばぼーんぽぽぽぽぽぽぽぽすびなばびこーん
ばぼーんぽーんすぽぽぽぽぽぽぽすてらのばぼーんぽぽぽぽすびなばびこーん
ばぼーんぽぽーんぽぽぽぽぽぽぽぽーんぽーんぽぽぽぽーんすてらのびなばびこーん
すぽぽぽぽぽぽぽぽすてらのばぼーんぽぽぽぽすびなばびこーん
ばぼーんぽーんすぽぽぽぽぽぽぽぽぽぽぽすてらのばぼーんぽぽぽぽぽすびなばびこーん
ばぼーんぽーんすぽぽぽぽぽぽぽぽすてらのばぼーんぽぽぽすびなばびこーん
ばぼーんぽーんぽぽぽぽーんびびびびびびぽーんびびびびびびびびぽーん
すてらのびなばびこーんすぽぽぽぽぽぽぽぽすてらのばぼーんぽぽぽぽすびなばびこーん
ばぼーんぽぽーんすてらのびなばびこーんぽぽぽぽぽぽぽぽぽぽぽーん

――説明するのも無粋ではありますが、BF-BASIC'nは要するに、 チューリングマシン向けのプログラミング言語であるBrainfuckに 「べーしっ君」の味付けを追加したものです。

Brainfuckについての解説はこちらがわかりやすいでしょう。

K.INABAさんの「いろんなげんご @ 人工言語世界」のBrainf*ckのページ。

http://www.kmonos.net/alang/etc/brainfuck.php

Wikipediaでの説明はこちら。

http://ja.wikipedia.org/wiki/Brainfuck

一方、「べーしっ君」のWikipediaでの説明はこちら。

http://ja.wikipedia.org/wiki/%E3%81%B9%E3%83%BC%E3%81%97%E3%81%A3%E5%90%9B

「べーしっ君」の雰囲気をつかむなら、 「たのみこむ」のこちらのページのほうがよいかもしれません。

http://www.tanomi.com/shop/jyuchu/items01064.html

BrainfuckとBF-BASIC'nのトークンの対応付けは以下のようになります。

BrainfuckBF-BASIC'n
>
<ばぼーん
+
-
.ぽーん
,うすらの
[すてらの
]なばびこーん

BF-BASIC'nのソースはこちら。

  1: #include <stdio.h>
  2: #include <string.h>
  3: #include <stdlib.h>
  4: #include <ctype.h>
  5: #include <assert.h>
  6: 
  7: typedef struct {
  8:     int bf;
  9:     char *basicn;
 10: } Conv;
 11: 
 12: static Conv st_conv[] = {
 13:     {'>', "す"},
 14:     {'<', "ばぼーん"},
 15:     {'+', "ぽ"},
 16:     {'-', "び"},
 17:     {'.', "ぽーん"},
 18:     {',', "うすらの"},
 19:     {'[', "すてらの"},
 20:     {']', "なばびこーん"},
 21: };
 22: 
 23: typedef struct {
 24:     int bf_code;
 25:     int address;
 26: } BfCode;
 27: 
 28: static BfCode st_bf_code[10000];
 29: static int st_code_count;
 30: 
 31: static int
 32: compare_conv(const void *a, const void *b)
 33: {
 34:     return strlen(((Conv*)b)->basicn) - strlen(((Conv*)a)->basicn);
 35: }
 36: 
 37: static void
 38: sort_conv_table(Conv *table, int table_count)
 39: {
 40:     qsort(table, table_count, sizeof(Conv), compare_conv);
 41: }
 42: 
 43: static void
 44: read_source(FILE *fp)
 45: {
 46:     unsigned char line[4096];
 47:     int i;
 48:     int j;
 49:     int code_idx = 0;
 50:     int stack[256];
 51:     int sp = 0;
 52: 
 53:     while ((fgets(line, 4096, fp)) != NULL) {
 54:         for (i = 0; line[i] != '\0'; ) {
 55:             if (isspace(line[i])) {
 56:                 i++;
 57:                 continue;
 58:             }
 59: 
 60:             for (j = 0; j < sizeof(st_conv)/sizeof(Conv); j++) {
 61:                 int len = strlen(st_conv[j].basicn);
 62:                 if (!strncmp(&line[i], st_conv[j].basicn, len)) {
 63:                     st_bf_code[code_idx].bf_code = st_conv[j].bf;
 64: 
 65:                     if (st_conv[j].bf == '[') {
 66:                         stack[sp] = code_idx;
 67:                         sp++;
 68:                     } else if (st_conv[j].bf == ']') {
 69:                         st_bf_code[stack[sp-1]].address = code_idx;
 70:                         st_bf_code[code_idx].address = stack[sp-1];
 71:                         sp--;
 72:                         if (sp < 0) {
 73:                             fprintf(stderr, "bracket mismatch\n");
 74:                             exit(2);
 75:                         }
 76:                     }
 77:                     code_idx++;
 78:                     i += len;
 79:                     break;
 80:                 }
 81:             }
 82:             if (j == sizeof(st_conv)/sizeof(Conv)) {
 83:                 fprintf(stderr, "syntax error\n");
 84:                 exit(2);
 85:             }
 86:         }
 87:     }
 88:     if (sp != 0) {
 89:         fprintf(stderr, "bracket mismatch\n");
 90:         exit(2);
 91:     }
 92: 
 93:     st_code_count = code_idx;
 94: }
 95: 
 96: static void
 97: interpret(void)
 98: {
 99:     int pc;
100:     int array[65536];
101:     int p = 0;
102: 
103:     for (pc = 0; pc < st_code_count; pc++) {
104:         switch (st_bf_code[pc].bf_code) {
105:         case '>':
106:             p++;
107:             break;
108:         case '<':
109:             p--;
110:             break;
111:         case '+':
112:             array[p]++;
113:             break;
114:         case '-':
115:             array[p]--;
116:             break;
117:         case '.':
118:             putchar(array[p]);
119:             break;
120:         case ',':
121:             array[p] = getchar();
122:             break;
123:         case '[':
124:             if (!array[p]) {
125:                 pc = st_bf_code[pc].address;
126:             }
127:             break;
128:         case ']':
129:             pc = st_bf_code[pc].address - 1;
130:             break;
131:         default:
132:             assert(0);
133:         }
134:     }
135: }
136: 
137: 
138: int
139: main(int argc, char **argv)
140: {
141:     FILE *in_fp;
142: 
143:     sort_conv_table(st_conv, sizeof(st_conv)/sizeof(Conv));
144: 
145:     if (argc == 2) {
146:         in_fp = fopen(argv[1], "r");
147:         if (in_fp == NULL) {
148:             fprintf(stderr, "%s not found.\n", argv[1]);
149:             exit(1);
150:         }
151:     } else {
152:         fprintf(stderr, "Usage:%s filename\n", argv[0]);
153:         exit(1);
154:     }
155: 
156:     read_source(in_fp);
157:     interpret();
158: 
159:     return 0;
160: }

Windows向け実行形式はこちら。

bfbasicn.lzh

各種サンプルソース

実は上で挙げたhello, world.は、 K.INABAさんのページのソースを元にしたものなのですが、 私が自分で書いたhello, world.(K&Rの伝統に従い、メッセージを小文字にした)は こうなりました。

すぽぽぽぽぽぽぽぽぽぽぽぽぽぽぽぽすてらのばぼーんぽぽぽぽぽぽすびなばびこーん
ばぼーんぽぽぽぽぽぽぽぽぽーんびびびぽーんぽぽぽぽぽぽぽぽーんぽーん
ぽぽぽぽーんすぽぽぽぽぽぽぽぽぽぽぽぽぽぽぽぽすてらのすぽぽばぼーんびなばびこーん
すぽぽぽぽぽぽぽぽぽぽぽぽぽーんびびびびびびびびびびびびぽーん
ばぼーんばぼーんぽぽぽぽぽぽぽぽぽーんびびびびびびびびぽーんぽぽぽぽーん
びびびびびびぽーんびびびびびびびびぽーんすすぽぽぽぽぽぽぽぽぽぽぽぽぽぽぽーん

いまいちこっちは後半が単調で…

ところで、 実のところBrainfuckのトークンを置換した言語というのは別に私の独創ではなく、 萌えというかエロというかな言語を確か2ch経由で見かけたことがありますし (最近探したけど見つかりませんでした)、他にも同様の試みはいろいろあることでしょう。

まあせっかく処理系を作ってみたことですし、 変換テーブルを以下のように差し替えると、

static Conv st_conv[] = {
  {'>', "あ"},
  {'<', "あぷぱ!!"},
  {'+', "た"},
  {'-', "ほおあ"},
  {'.', "ひでぶっ"},
  {',', "お前はもう死んでいる "},
  {'[', "たわば!!"},
  {']', "あべし!!"},
};

上記「hello, world.」はこうなります。

あたたたたたたたたたたたたたたたた
たわば!!
あぷぱ!!
たたたたたたあほおあ
あべし!!
あぷぱ!!
たたたたたたたた
ひでぶっ
ほおあほおあほおあ
ひでぶっ
たたたたたたた
ひでぶっ
ひでぶっ
たたた
ひでぶっ
あたたたたたたたたたたたたたたたた
たわば!!
あたた
あぷぱ!!
ほおあ
あべし!!
あたたたたたたたたたたたた
ひでぶっ
ほおあほおあほおあほおあほおあほおあほおあほおあほおあほおあほおあほおあ
ひでぶっ
あぷぱ!!
あぷぱ!!
たたたたたたたた
ひでぶっ
ほおあほおあほおあほおあほおあほおあほおあほおあ
ひでぶっ
たたた
ひでぶっ
ほおあほおあほおあほおあほおあほおあ
ひでぶっ
ほおあほおあほおあほおあほおあほおあほおあほおあ
ひでぶっ
ああたたたたたたたたたたたたたた
ひでぶっ

また、以下のように置き換えれば、

static Conv st_conv[] = {
    {'>', "ふるえるぞハート!"},
    {'<', "燃えつきるほどヒート!!"},
    {'+', "オラ"},
    {'-', "無駄"},
    {'.', "ァ!"},
    {',', "やれやれだぜ"},
    {'[', "おまえの次のセリフは「"},
    {']', "」という!"},
};

こうなります。

ふるえるぞハート!
オラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラ
おまえの次のセリフは「燃えつきるほどヒート!!オラオラオラオラオラオラ
ふるえるぞハート!無駄」という!
燃えつきるほどヒート!!オラオラオラオラオラオラオラオラァ!
無駄無駄無駄ァ!
オラオラオラオラオラオラオラァ!ァ!
オラオラオラァ!ふるえるぞハート!
オラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラ
おまえの次のセリフは「ふるえるぞハート!オラオラ燃えつきるほどヒート!!無駄」という!
ふるえるぞハート!オラオラオラオラオラオラオラオラオラオラオラオラァ!
無駄無駄無駄無駄無駄無駄無駄無駄無駄無駄無駄無駄ァ!
燃えつきるほどヒート!!燃えつきるほどヒート!!オラオラオラオラオラオラオラオラァ!
無駄無駄無駄無駄無駄無駄無駄無駄ァ!
オラオラオラァ!
無駄無駄無駄無駄無駄無駄ァ!
無駄無駄無駄無駄無駄無駄無駄無駄ァ!
ふるえるぞハート!
ふるえるぞハート!
オラオラオラオラオラオラオラオラオラオラオラオラオラオラァ!

ライセンスについて

「BF-Basic'n」および本ページで挙げたすべてのプログラムや 変換テーブルののライセンスは、 NYSL Version 0.9982とします。

再配布するのも、変換テーブルを差し替えて自作言語として公開するのも 完全に自由です。



前の雑記 | ひとつ上のページに戻る | トップページに戻る