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

  • (by K, 2019.02.25)

(5) TL-2

  • TL-1では変数名も数値定数も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-1と同じなので省略
    
    int isNumber(unsigned char c)	// 数字かどうか.
    {
        if ('0' <= c && c <= '9') return 1;
        return 0;
    }
    
    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 isSymbol(unsigned char c)	// 普通の記号文字.
    {
         if (strchr("=+-*/!%&~|<>?:.", c) != 0) return 1;
         return 0;
    }
    
    int isSymbol1(unsigned char c)	// 1文字で構成される記号文字.
    {
        if (strchr("(){}[];,", c) != 0) 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;
            }
            t[k++] = &b[j];	// 単語の先頭を登録.
            if (s[i] == 0)	// ファイル終端.
                return k;
            if (isSymbol1(s[i]) != 0) {	// 1文字記号.
                b[j++] = s[i++];
            } else if (isNumber(s[i]) != 0) {  // 1文字目が数字.
                while (isNumber(s[i]) != 0)
                    b[j++] = s[i++];
            } else if (isAlphabet(s[i]) != 0) {  // 1文字目が英字.
                while (isAlphabet(s[i]) != 0 || isNumber(s[i]) != 0)
                    b[j++] = s[i++];
            } else if (isSymbol(s[i]) != 0) {  // 1文字目が普通の記号.
                while (isSymbol(s[i]) != 0)
                    b[j++] = s[i++];
            } else {
                printf("syntax error : %.10s\n", &s[i]);
                exit(1);
            }
            b[j++] = 0; // 単語の終端マーク.
        }
    }
    
    int var[256];	// 変数.
    String varName[256];	// 変数名.
    unsigned char txt[10000]; // ソースコード.
    int pc = 0;	// プログラムカウンタ.
    
    int varNumber(String t)		// 変数名から変数番号を得る.
    {
        int i;
        for (i = 0; varName[i] != 0; i++) { // 登録済みの中から探す.
            if (strcmp(t, varName[i]) == 0)
                return i; // 見つかったので番号を返す.
        }
        varName[i] = t; // 見つからなかったので新規登録.
        return i;
    }
    
    int getNumber(String t)
    {
        if (isNumber(t[0]) == 0)	// 1文字目が数字以外.
            return var[varNumber(t)];
        return strtol(t, 0, 0);	// tを数値に変換.
    }
    
    int main(int argc, const char **argv)
    {
        int i, pc0;
        unsigned char buf[10000];
        String t[1000];	// トークン.
        loadText(argc, argv, txt, 10000);
        i = lexer(txt, buf, t);
        t[i + 0] = t[i + 1] = t[i + 2] = t[i + 3] = "";	// エラー表示用のために末尾にいくつか長さ0の文字列を登録しておく.
        for (;;) {
            pc0 = pc;
            if (strcmp(t[pc], "") == 0)		// ファイル終端.
                exit(0);
            if (strcmp(t[pc + 1], "=") == 0) { // 2単語目が"=".
                i = varNumber(t[pc]);
                if (strcmp(t[pc + 3], ";") == 0) { // 単純代入.
                    var[i] = getNumber(t[pc + 2]);
                } else if (strcmp(t[pc + 3], "+") == 0 && strcmp(t[pc + 5], ";") == 0) {  // 加算.
                    var[i] = getNumber(t[pc + 2]) + getNumber(t[pc + 4]);
                } else if (strcmp(t[pc + 3], "-") == 0 && strcmp(t[pc + 5], ";") == 0) {  // 減算.
                    var[i] = getNumber(t[pc + 2]) - getNumber(t[pc + 4]);
                } else
                    goto err;
            } else if (strcmp(t[pc], "print") == 0 && strcmp(t[pc + 2], ";") == 0) { // print.
                printf("%d\n", getNumber(t[pc + 1]));
            } else
                goto err;
            while (strcmp(t[pc], ";") != 0)
                pc++;
            pc++;
        }
    err:
        printf("syntax error : %s %s %s %s\n", t[pc0], t[pc0 + 1], t[pc0 + 2], t[pc0 + 3]);
        exit(1);
    }
  • このプログラムによって、以下のようなプログラムが実行可能です。
    abc = 123;
    def = 456;
    ans = abc + def;
    ans = ans - 321;
    print ans;
  • この例では記号と英数字の間にスペースを入れていますが、それは省略可能です。またスペースは1つではなく複数入れても平気です。

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

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

次回に続く

こめんと欄


コメントお名前NameLink

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2019-02-27 (水) 15:11:36 (775d)