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

  • (by K, 2019.06.29)

(5) TL-2c

  • TL-1cでは変数名も数値定数も1文字しか受け付けないという仕様でした。それはあんまりだと思うので、まずはその制約を解消しようと思います。
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    typedef unsigned char *String;	// こう書くと String は unsigned char * の代用になる.
    
    void loadText(int argc, const char **argv, String t, int siz) → TL-1cと同じなので省略
    
    int isAlphabet(unsigned char c)		// 変数名の一文字目に使える文字かどうか.
    {
        if ('a' <= c && c <= 'z') return 1;
        if ('A' <= c && c <= 'Z') return 1;
        if (c == '_') return 1;
        return 0;
    }
    
    int lexer(String s, String b, String t[])		// プログラムを単語(トークン)に切り分ける.
    {
        int i = 0, j = 0, k = 0;
        for (;;) {
            if (s[i] == ' ' || s[i] == '\t' || s[i] == '\n') {	// スペース、タブ、改行.
                i++;
                continue;
            }
            if (s[i] == 0)	// ファイル終端.
                return k;
            t[k++] = &b[j];	// 単語の先頭を登録.
            if (strchr("(){}[];,", s[i]) != 0) {	// 1文字記号.
                b[j++] = s[i++];
            } else if ('0' <= s[i] && s[i] <= '9') {  // 1文字目が数字.
                while ('0' <= s[i] && s[i] <= '9')
                    b[j++] = s[i++];
            } else if (isAlphabet(s[i]) != 0) {  // 1文字目が英字.
                while (isAlphabet(s[i]) != 0 || ('0' <= s[i] && s[i] <= '9'))
                    b[j++] = s[i++];
            } else if (strchr("=+-*/!%&~|<>?:.", s[i]) != 0) {  // 1文字目が普通の記号.
                while (strchr("=+-*/!%&~|<>?:.", s[i]) != 0)
                    b[j++] = s[i++];
            } else {
                printf("syntax error : %.10s\n", &s[i]);
                exit(1);
            }
            b[j++] = 0; // 単語の終端マーク.
        }
    }
    
    int main(int argc, const char **argv)
    {
        int i, vars = 0, pc, pc1, 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 (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 (strcmp(t[pc + 1], "=") == 0) { // 2単語目が"=".
                if (strcmp(t[pc + 3], ";") == 0) { // 単純代入.
                    var[varNum[pc]] = var[varNum[pc + 2]];
                } else if (strcmp(t[pc + 3], "+") == 0 && strcmp(t[pc + 5], ";") == 0) {  // 加算.
                    var[varNum[pc]] = var[varNum[pc + 2]] + var[varNum[pc + 4]];
                } else if (strcmp(t[pc + 3], "-") == 0 && strcmp(t[pc + 5], ";") == 0) {  // 減算.
                    var[varNum[pc]] = var[varNum[pc + 2]] - var[varNum[pc + 4]];
                } else
                    goto err;
            } else if (strcmp(t[pc], "print") == 0 && strcmp(t[pc + 2], ";") == 0) { // print.
                printf("%d\n", var[varNum[pc + 1]]);
            } else
                goto err;
            while (strcmp(t[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);
    }
  • このプログラムによって、以下のようなプログラムが実行可能です。
    abc = 123;
    def = 456;
    ans = abc + def;
    ans = ans - 321;
    print ans;
  • この例では記号と英数字の間にスペースを入れていますが、それは省略可能です。またスペースは1つではなく複数入れても平気です。

(6) TL-2cの簡単な説明

  • 変数名や定数が一文字であるという保証はなくなったので、まず最初にlexer()という簡単な関数を使って、プログラムを単語に切り分けておきます。こうすれば単語の頭出しが簡単になります。
  • それが終わったら、TL-1cのように実行していくのですが、このとき文字単位での比較はもうできなくなっているので、 strcmp(t[pc + 1], "=") == 0 みたいな書き方で比較します。これは txt[pc + 1] == '=' に相当するものです。
  • ここまでの理屈が分かれば、あとはTL-1cと見比べるだけで容易に理解できると思います。
  • lexer()の結果がどうなるのかよくわからない人のために、説明ページを追加しました。→text0002a
    • lexer()がどうやってこの結果を生成しているのかを必死に理解する必要はないと思います。似たようなことを自分でもやりたければ、これを真似すればいいだけですので。
    • 大事なのは、この結果を使ってどうやって実行しているのか、です。

次回に続く

こめんと欄


コメントお名前NameLink

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