トップ 一覧 Farm 検索 ヘルプ RSS ログイン

Forthを作ってみる / 対話環境を作るの変更点

  • 追加された行はこのように表示されます。
  • 削除された行はこのように表示されます。
!!! 対話環境を作る

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を作ってみる / ワード定義のテスト]] [[次へ|Make32bitForth]]
[[戻る|Forthを作ってみる]] [[前へ|Forthを作ってみる / ワード定義のテスト]] [[次へ|Forthを作ってみる / Forthを32ビット化する]]

{{adsence}}