* 川合のプログラミング言語自作のためのテキスト第三版#2
-(by [[K]], 2021.01.19)

** (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 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 && s[i] != 0)
                 b[j++] = s[i++];
         } else {
             printf("syntax error : %.10s\n", &s[i]);
             exit(1);
         }
         b[j++] = 0; // 単語の終端マーク.
     }
 }
 
 #define MAX_TC  255 // トークンコードの最大値.
 String ts[MAX_TC + 1]; // トークンの内容(文字列)を記憶.
 unsigned char tcBuf[10000];
 int tcs = 0, tcb = 0;
 
 int getTc(String s) // トークンコード(トークン番号)を得るための関数.
 {
     int i;
     for (i = 0; i < tcs; i++) { // 登録済みの中から探す.
         if (strcmp(s, ts[i]) == 0)
             break;
     }
     if (i == tcs) {
         if (tcs >= MAX_TC) {
             printf("too many tokens\n");
             exit(1);
         }
         strcpy(&tcBuf[tcb], s); // 見つからなかったので新規登録.
         ts[i] = &tcBuf[tcb];
         tcb += strlen(s) + 1; // 次はここへコピーする.
         tcs++;
     }
     return i;
 }
 
 int main(int argc, const char **argv)
 {
     int i, pc, pc1, var[MAX_TC + 1], tc[1000];	// 変数(var)とトークンコード(tc).
     String t[1000];	// トークン(t).
     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++) { // プログラム中で使われているすべてのトークンを調べて、tc[]の初期化.
         tc[pc] = getTc(t[pc]);
     }
     for (i = 0; i < tcs; i++) { // 定数の初期値を代入.
         var[i] = strtol(ts[i], 0, 0);	// 定数だった場合に初期値を設定(定数ではないときは0になる).
     }
     for (pc = 0; pc < pc1; pc++) { // プログラム実行開始.
         if (strcmp(t[pc + 1], "=") == 0) { // 2単語目が"=".
             if (strcmp(t[pc + 3], ";") == 0) { // 単純代入.
                 var[tc[pc]] = var[tc[pc + 2]];
             } else if (strcmp(t[pc + 3], "+") == 0 && strcmp(t[pc + 5], ";") == 0) {  // 加算.
                 var[tc[pc]] = var[tc[pc + 2]] + var[tc[pc + 4]];
             } else if (strcmp(t[pc + 3], "-") == 0 && strcmp(t[pc + 5], ";") == 0) {  // 減算.
                 var[tc[pc]] = var[tc[pc + 2]] - var[tc[pc + 4]];
             } else
                 goto err;
         } else if (strcmp(t[pc], "print") == 0 && strcmp(t[pc + 2], ";") == 0) { // print.
             printf("%d\n", var[tc[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);
 }
-プログラムは123行になりました。このプログラムによって、以下のようなプログラムが実行可能です。
 abc = 123;
 def = 456;
 ans = abc + def;
 ans = ans - 321;
 print ans;
-この例では記号と英数字の間にスペースを入れていますが、それは省略可能です。またスペースは1つではなく複数入れても平気です。

** (6) TL-2の簡単な説明
-関数:
--void loadText(int argc, const char **argv, String t, int siz)
---コマンドライン引数で指定されたソースファイルをtに読み込む。sizはtの最大サイズを表す(これを超える長さのファイルは途中で打ち切られる)。
--int isAlphabet(unsigned char c)
---引数で渡された文字コードが、アルファベットであれば1を返す。アルファベットでなければ0を返す。
---アンダースコアもTL-2の中ではアルファベットということにしておく。そうすることで、変数の一文字目に使えるようになる。
--int lexer(String s, String b, String t[])
---sにプログラムのソースコードを渡す。すると、t[]にトークン(単語)に切り分けられた文字列が入って返される。bは切り分けた文字列片をしまうところ。
---より詳しい動作は、下記にあるように[[a21_txt01_2a]]を参照のこと。
--int getTc(String s)
---トークン(単語)をsに渡すと、それに対応するトークンコード(整数)を返す。
--int main(int argc, const char **argv)
---言語処理の本体。

-変数:
--String ts[]
---getTc()が管理している配列変数で、トークンコードからトークン文字列を得るために使う。
--unsigned char tcBuf[]
---getTc()が管理している変数で、トークン文字列の実体を保存しておくための場所。
--int tcs, tcb
---どちらもgetTc()が管理している変数で、tcsは今までに発行したトークンコードの個数(0~tcs-1が発行済み)。
---tcbはtcBuf[]の未使用領域を指している。
----
-変数名や定数が一文字であるという保証はなくなったので、まず最初にlexer()という簡単な関数を使って、プログラムを単語(トークン)に切り分けておきます。こうすれば単語の頭出しが簡単になります。
-それが終わったら、TL-1のように実行していくのですが、このとき文字単位での比較はもうできなくなっているので、 strcmp(t[pc + 1], "=") == 0 みたいな書き方で比較します。これは txt[pc + 1] == '=' に相当するものです。
-単語は複数の文字から構成されていて扱いにくいので、「トークンコード」というものも用意することにしました。文字に対する「文字コード」がありますが、それのトークン版です。1つのトークンには1つのトークンコードが対応するということにできれば、プログラムはTL-1とほとんど同じにできます。
 TL-1での加算の処理:var[txt[pc]] = var[txt[pc + 2]] + var[txt[pc + 4]];
 TL-2での加算の処理:var[tc [pc]] = var[tc [pc + 2]] + var[tc [pc + 4]];
-すごくよく似ていることが伝わるでしょうか・・・。
-とはいえ、文字コードとは異なり、どのトークン文字列が1番なのかは決まっていません(文字コードなら決まっているのに)。ということで、TL-2では、ソースコードでの出現順に、0番, 1番, 2番,... と割り当てています。
-ここまでの仕組みが分かれば、あとはTL-1と見比べるだけで簡単に理解できると思います。

-''lexer()の結果がどうなるのかよくわからない人のために、説明ページを追加しました。→[[a21_txt01_2a]]''
--lexer()がどうやってこの結果を生成しているのかを必死に理解する必要はないと思います。似たようなことを自分でもやりたければ、これを真似すればいいだけですので。
--大事なのは、この結果を使ってどうやって実行しているのか、です。

** 次回に続く
-次回: [[a21_txt01_3]]

*こめんと欄
#comment

トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS