川合のプログラミング言語自作のためのテキスト第二版#0004

  • (by K, 2019.06.29)

(9) TL-3d

  • TL-3cはたったの119行のインタプリタにもかかわらずループ処理ができてすばらしいのですが、C言語と実行速度を比較すると600倍以上もあり、これは結構悲しい気持ちになります。→参考:text0010
  • これはインタプリタだから遅いというわけではなく、そもそも文字列の比較にstrcmp()を使っていたから遅いのです。だからこれを別の方法で代用する方法を考えましょう。
  • どうすればいいかというと、varNum[]には変数名や定数やそのほかの予約語に対応する番号が入っているので、この値を見ればstrcmp()の代わりができるはずです。・・・TL-3cでは、「;」や「print」などが何番目に登録されるのか事前にはわからないので、ちょっと比較がやりにくいです。ということで、事前にこの予約語に対応する番号は何番であるとこちらから定義してしまうことにします。こうすれば比較は簡単になります。
  • 本当は#defineやenumを使ってプログラムをきれいにすべきなのですが、そうすると行数が急に増えて、複雑さが一気に増したかのように誤解されるかもしれないので、あえて定数をそのまま使っています。
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <time.h>
    
    typedef unsigned char *String;	// こう書くと String は unsigned char * の代用になる.
    
    void loadText(int argc, const char **argv, unsigned char *t, int siz) → TL-1cと同じなので省略
    int isAlphabet(unsigned char c) → TL-2cと同じなので省略
    int lexer(String s, String b, String t[]) → TL-2cと同じなので省略
    
    int main(int argc, const char **argv)
    {
        static String def[] = { ";", "=", "+", "-", "print", "while", "(", "<", ")", "{", "}", "time", 0 };
        int i, vars = 0, pc, pc1, wpc = 0, var[256], varNum[1000];	// 変数と変数番号.
        String t[1000], varName[256];	// トークンと変数名.
        unsigned char txt[10000], buf[10000]; // ソースコードとトークン用のバッファ.
        loadText(argc, argv, txt, 10000);
        pc1 = lexer(txt, buf, t);
        t[pc1] = t[pc1 + 1] = t[pc1 + 2] = t[pc1 + 3] = "";	// エラー表示用のために末尾にいくつか長さ0の文字列を登録しておく.
        for (i = 0; def[i] != 0; i++)
            varName[i] = def[i];
        vars = i;
        for (pc = 0; pc < pc1; pc++) {
            for (i = 0; i < vars; i++) { // 登録済みの中から探す.
                if (strcmp(t[pc], varName[i]) == 0)
                    break;
            }
            if (i == vars) {
                varName[i] = t[pc]; // 見つからなかったので新規登録.
                var[i] = strtol(t[pc], 0, 0);	// 初期値を設定.
                vars++;
            }
            varNum[pc] = i;
        }
        for (pc = 0; pc < pc1; pc++) {
            if (varNum[pc + 1] == 1 /* = */) { // 2単語目が"=".
                if (varNum[pc + 3] == 0 /* ; */) { // 単純代入.
                    var[varNum[pc]] = var[varNum[pc + 2]];
                } else if (varNum[pc + 3] == 2 /* + */ && varNum[pc + 5] == 0 /* ; */) {  // 加算.
                    var[varNum[pc]] = var[varNum[pc + 2]] + var[varNum[pc + 4]];
                } else if (varNum[pc + 3] == 3 /* - */ && varNum[pc + 5] == 0 /* ; */) {  // 減算.
                    var[varNum[pc]] = var[varNum[pc + 2]] - var[varNum[pc + 4]];
                } else
                    goto err;
            } else if (varNum[pc] == 5 /* while */ && varNum[pc + 1] == 6 /* ( */ && varNum[pc + 3] == 7 /* < */ && varNum[pc + 5] == 8 /* ) */ && varNum[pc + 6] == 9 /* { */) {
                wpc = pc;
                if (var[varNum[pc + 2]] < var[varNum[pc + 4]]) {
                    pc += 7 - 1;
                } else {	// 条件不成立なので } の次まで読み飛ばす.
                    while (pc < pc1 && varNum[pc] != 10 /* } */)
                        pc++;
                }
                continue;
            } else if (varNum[pc] == 10 /* } */) {
                pc = wpc - 1;
                continue;
            } else if (varNum[pc] == 11 /* time */ && varNum[pc + 1] == 0 /* ; */) { // time.
                printf("time=%.3f[sec]\n", clock() / (double) CLOCKS_PER_SEC);
            } else if (varNum[pc] == 4 /* print */ && varNum[pc + 2] == 0 /* ; */) { // print.
                printf("%d\n", var[varNum[pc + 1]]);
            } else
                goto err;
            while (varNum[pc] != 0 /* ; */)
                pc++;
        }
        exit(0);
    err:
        printf("syntax error : %s %s %s %s\n", t[pc], t[pc + 1], t[pc + 2], t[pc + 3]);
        exit(1);
    }
  • たった4行しか増えていないですし、全体的な構造もほとんど変わっていませんが、これだけで11.3倍も高速になります。効果絶大です!

次回に続く

こめんと欄


コメントお名前NameLink

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2019-06-29 (土) 17:51:20 (18d)