トップ 差分 一覧 Farm ソース 検索 ヘルプ RSS ログイン

Forthを作ってみる / 対話環境を作る

対話環境を作る

2年ぶりにワード定義部分を実装し、いっそうプログラミング言語らしくなった。しかし、まだ何かが足りない。そう、私の作ったForthは対話的に使うことが出来ない。ファイルを指定して実行するだけでなく、コンソール上での入力を受け付けるようにしたい。

ファイル指定の場合は、

  1. ファイルを読み込んで構文解析し、内部コードに変換する
  2. 内部コードを実行する
  3. 終了

だったのだが、対話環境の場合は、

  1. ファイル名を指定せずに起動すると、入力待ちのプロンプトを表示する
  2. 入力した文字列を構文解析し、内部コードに変換する
  3. 内部コードを実行する
  4. 再度プロンプトを表示し、入力を待つ

となる。

まず変更するところは、構文解析関数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
>

うまくいった。これで対話環境が完成した。

ソースコード

戻る 前へ 次へ

moiforth-0.6.zip