!!! 対話環境を作る 2年ぶりにワード定義部分を実装し、いっそうプログラミング言語らしくなった。 しかし、まだ何かが足りない。 そう、私の作ったForthは対話的に使うことが出来ない。 ファイルを指定して実行するだけでなく、コンソール上での入力を受け付けるようにしたい。 ファイル指定の場合は、 +ファイルを読み込んで構文解析し、内部コードに変換する +内部コードを実行する +終了 だったのだが、対話環境の場合は、 +ファイル名を指定せずに起動すると、入力待ちのプロンプトを表示する +入力した文字列を構文解析し、内部コードに変換する +内部コードを実行する +再度プロンプトを表示し、入力を待つ となる。 まず変更するところは、構文解析関数parse()である。 これまでparse()は、ファイル名を引数としていたが、これからはForthのプログラムを引数としなければならない。 そのため、main()を以下のように変更した。 int main(int argc, char *argv[]) { char cmd_buf[100]; init(); if (argc >= 2) { load_prog(argv[1], cmd_buf); parse(cmd_buf); proc_prog(); } else { while (!quit_enabled) { scanf("%100[^\n]%*[^\n]", &cmd_buf); parse(cmd_buf); proc_prog(); } } return 0; } cmd_buf[]にファイルから読み込んだプログラムか、または、コンソールで入力されたワードを保存し、それをparse()に渡すようにした。 また、parse()で行っていたファイル読み込みは、load_prog()で行う。 /** * Forthで書かれたプログラムをロードする */ void load_prog(char *fname, char *cmd) { FILE *fp; unsigned int idx = 0; fp = fopen(fname, "r"); while ((c = fgetc(fp)) != EOF) { cmd[idx++] = c; } cmd[idx] = '\x0'; } parse()から呼び出される、get_token()は、対象をファイルから文字列にする。 /** * トークンを切り出す */ void get_token(char **cmd, char *token) { int c; int j = 0; token[0] = '\x0'; /* 英数記号が来るまで読み込む */ c = **cmd; *cmd = *cmd + 1; while (is_separator(c) && **cmd != '\x0') { c = **cmd; *cmd = *cmd + 1; } /* 区切り記号までを読み込む */ while (!is_separator(c) && **cmd != '\x0') { token[j] = c; j++; c = **cmd; *cmd = *cmd + 1; } token[j] = '\x0'; } なんか複雑になった。これはstrtok()使えばいいような気がする。 試してみたが、strtok()は、区切り子が連続して2つ続けてくると、2つ目の区切り子を取り除いてくれない。 というのは勘違いで、区切り子の入れ忘れが2箇所あった。 get_token()は必要なくなった。 これで、parse()の引数をファイル名からプログラムに変更できた。 次は、対話環境である。 ファイル指定なしでForthを起動してみる。 プロンプトが表示されない。後で表示させよう。 そして、"2 3 + ."と入力し、Enterキーを押すが、反応なし。 gdbで調べてみた。 *なぜかscanf()で処理が止まらない *コマンドラインからの入力を保存したcmd_bufをクリアしていない *コマンドラインのワードを処理した後、prog_cntをクリアしていない scanf()が止まらないのは、バッファに'\n'が残っているかららしい。scanf()したあとにgetchar()を入れると、無限ループは止まった。 "2 3 + ."と入れて、"5"が表示された。 しかし、次に"6 2 + ."と入れたが、反応はなく、プロンプトが帰ってくるだけだ。 これは、 *"2 3 + ."を実行、prog_cntが6の位置になる( = prog[6] = CODE_END) *"6 2 + ."を実行、prog_cntがCODE_ENDの位置にあるので、即終了となる が原因だった。 これを修正するには、prog_cntの初期位置を記憶して、入力されたワードを実行後、元に戻すようにする。 0にしてしまえれば一番簡単だが、ワード定義がある場合、その分prog_cntがずれてしまう。 そこで、プログラムカウンタの初期位置を記録する変数を用意する。 int base_prog_cnt; /**< プログラムカウンタの初期位置 */ proc_prog()を呼び出すときは、必ずbase_prog_cntの値に戻す。 void proc_prog(void) { prog_cnt = base_prog_cnt; while (prog[prog_cnt] != CODE_END) { ワード定義があったときは、その分だけ位置をずらす。 } else if (strcmp(token, ";") == 0) { /* ワード終端コードを追加する */ prog[idx] = CODE_RET; idx++; base_prog_cnt = idx; 上記の変更をして、実行してみた。 $ ./moiforth.exe >2 3 + . cr 5 >6 2 + . cr 8 > うまくいった。これで対話環境が完成した。 [ソースコード|http://moi2.sakura.ne.jp/fswiki/wiki.cgi?page=ImplementInteractiveEnvironment&file=moiforth%2D0%2E6%2Ezip&action=ATTACH] [[戻る|Forthを作ってみる]] [[前へ|Forthを作ってみる / ワード定義のテスト]] [[次へ|Forthを作ってみる / Forthを32ビット化する]] {{adsence}}