* 川合のプログラミング言語自作のためのテキスト第三版#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; } #define MAX_TC 255 // トークンコードの最大値. String ts[MAX_TC + 1]; // トークンの内容(文字列)を記憶. int tl[MAX_TC + 1]; // トークンの長さ. unsigned char tcBuf[(MAX_TC + 1) * 10]; // トークン1つ当たり平均10バイトを想定. int tcs = 0, tcb = 0; int getTc(String s, int len) // トークン番号を得るための関数. { int i; for (i = 0; i < tcs; i++) { // 登録済みの中から探す. if (len == tl[i] && strncmp(s, ts[i], len) == 0) break; } if (i == tcs) { if (tcs >= MAX_TC) { printf("too many tokens\n"); exit(1); } strncpy(&tcBuf[tcb], s, len); // 見つからなかったので新規登録. tcBuf[tcb + len] = 0; // 終端文字コード. ts[i] = &tcBuf[tcb]; tl[i] = len; tcb += len + 1; tcs++; } return i; } int lexer(String s, int tc[]) // プログラムをトークンコード列に変換する. { int i = 0, j = 0, len; for (;;) { if (s[i] == ' ' || s[i] == '\t' || s[i] == '\n') { // スペース、タブ、改行. i++; continue; } if (s[i] == 0) // ファイル終端. return j; len = 0; if (strchr("(){}[];,", s[i]) != 0) { // 1文字記号. len = 1; } else if ('0' <= s[i] && s[i] <= '9') { // 1文字目が数字. while ('0' <= s[i + len] && s[i + len] <= '9') len++; } else if (isAlphabet(s[i]) != 0) { // 1文字目が英字. while (isAlphabet(s[i + len]) != 0 || ('0' <= s[i + len] && s[i + len] <= '9')) len++; } else if (strchr("=+-*/!%&~|<>?:.", s[i]) != 0) { // 1文字目が普通の記号. while (strchr("=+-*/!%&~|<>?:.", s[i + len]) != 0) len++; } else { printf("syntax error : %.10s\n", &s[i]); exit(1); } tc[j] = getTc(&s[i], len); i += len; j++; } } int main(int argc, const char **argv) { int i, pc, pc1, var[MAX_TC + 1], tc[1000]; // 変数(var)とトークンコード(tc). unsigned char txt[10000]; // ソースコード用のバッファ. loadText(argc, argv, txt, 10000); pc1 = lexer(txt, buf, t); tc[pc1] = tc[pc1 + 1] = tc[pc1 + 2] = tc[pc1 + 3] = getTc(" ", 1); // エラー表示用のために末尾にスペースを登録しておく. for (i = 0; i < tcs; i++) { // 定数の初期値を代入. var[i] = strtol(ts[i], 0, 0); // 定数だった場合に初期値を設定(定数ではないときは0になる). } int semi = getTc(";", 1); for (pc = 0; pc < pc1; pc++) { // プログラム実行開始. if (tc[pc + 1] == getTc("=", 1)) { // 2単語目が"=". if (tc[pc + 3] == semi) { // 単純代入. var[tc[pc]] = var[tc[pc + 2]]; } else if (tc[pc + 3] == getTc("+", 1) && tc[pc + 5] == semi) { // 加算. var[tc[pc]] = var[tc[pc + 2]] + var[tc[pc + 4]]; } else if (tc[pc + 3] == getTc("-", 1) && tc[pc + 5] == semi) { // 減算. var[tc[pc]] = var[tc[pc + 2]] - var[tc[pc + 4]]; } else goto err; } else if (tc[pc] == getTc("print", 5) && tc[pc + 2] == semi) { // print. printf("%d\n", var[tc[pc + 1]]); } else goto err; while (tc[pc] != semi) pc++; } exit(0); err: printf("syntax error : %s %s %s %s\n", ts[tc[pc]], ts[tc[pc + 1]], ts[tc[pc + 2]], ts[tc[pc + 3]]); exit(1); } -プログラムは125行になりました。このプログラムによって、以下のようなプログラムが実行可能です。 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 getTc(String s, int len) ---トークン(単語)をsに渡すと、それに対応するトークンコード(整数)を返す。 --int lexer(String s, int tc[]) ---sにプログラムのソースコードを渡す。すると、tc[]にトークンコード(単語番号)に変換させられた数列が入って返される。 ---より詳しい動作は、下記にあるように[[a21_txt01_2a]]を参照のこと。 --int main(int argc, const char **argv) ---言語処理の本体。 -変数: --String ts[] ---getTc()が管理している配列変数で、トークンコードからトークン文字列を得るために使う。 --int tl[] ---getTc()が管理している配列変数で、トークンコードからトークン文字列の長さを得るために使う。 --unsigned char tcBuf[] ---getTc()が管理している変数で、トークン文字列の実体を保存しておくための場所。 --int tcs, tcb ---どちらもgetTc()が管理している変数で、tcsは今までに発行したトークンコードの個数(0~tcs-1が発行済み)。 ---tcbはtcBuf[]の未使用領域を指している。 --もしtcBuf[]やtcbの役割がピンとこない場合は、[[a21_txt01_2b]]を参照。 ---- -変数名や定数が一文字であるという保証はなくなったので、まず最初にlexer()という簡単な関数を使って、プログラムを単語(トークン)に切り分けておきます。そして文字コードで比較するのではなく、トークンコード(単語の番号)で比較するようにします。 -もしこれをやらないで文字コードのままで処理しようとすると、処理がかなり複雑になってしまいます。だからこのトークンコードに変換してから処理を進めるのは、処理を単純にするためのテクニックです。 -以下の比較を見れば、文字コードでの処理方法と、トークンコードでの処理方法がよく似ているというか、ほぼ同じだということがわかってもらえると思います。 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