* 川合のプログラミング言語自作のためのテキスト第三版#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) 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, tcs = 0, pc, pc1, var[256], tc[1000]; // 変数(var)とトークンコード(tc). String t[1000], ts[256]; // トークン(t)とトークン文字列(ts). 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++) { // プログラム中で使われているすべてのトークンを調べて、ts, tc, tcsの初期化. for (i = 0; i < tcs; i++) { // 登録済みの中から探す. if (strcmp(t[pc], ts[i]) == 0) break; } if (i == tcs) { ts[i] = t[pc]; // 見つからなかったので新規登録. var[i] = strtol(t[pc], 0, 0); // 定数だった場合に初期値を設定(定数ではないときは0になる). tcs++; } tc[pc] = i; } 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); } -このプログラムによって、以下のようなプログラムが実行可能です。 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] == '=' に相当するものです。 -単語は複数の文字から構成されていて扱いにくいので、「トークンコード」というものも用意することにしました。文字に対する「文字コード」がありますが、それのトークン版です。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