● StrCalc BASIC の実装

補足 : 変数環境の変更

関数StrCalc_BASIC の一番目の引数には、その BASIC 処理で使用する変数の環境を指定します。指定したアドレスは、内部の変数管理や計算処理で使用されます。また、ユーザステートメント等のコールバック関数にも渡されます。

変数環境のアドレスは、BASIC が終了するまで変更する事ができません。しかし、そのアドレスが指す実体の方は、(BASIC 実行に支障を来たさない程度に)変更が可能です。変数環境の内容を他の変数環境と入れ替える事で、BASIC 実行中に新しい環境に切り替える事ができます。

テスト.

#include <stdio.h>
#include <string.h>
#include <memory.h>
#include "sc39.h"

#define STATUS_USR_ALREADY          -9999
#define STATUS_USR_NOTREADY         -9998

int                 gbActive;
STRCALC_PARAM       gtemp_sc;

/* @enter */
int __stdcall   usr_enter(PSTRCALC_PARAM prm,char *arg,int len,void *param)
{
    /* ここでは簡単のため、後続に文字列が存在する場合はエラーとします。 */
    if (len)
        return STATUS_ERROR;

    /* 既に @enter が実行されている場合はエラーとする */
    if (gbActive)
        return STATUS_USR_ALREADY;

    memcpy(&gtemp_sc,prm,sizeof(STRCALC_PARAM)); /* 現在の環境を退避 */

    if (!StrCalc_init(prm)){ /* 新しい環境を構築する */

        /* 初期化に失敗した場合は、退避しておいた情報を元に戻す */
        memcpy(prm,&gtemp_sc,sizeof(STRCALC_PARAM));
        return STATUS_NOTENOUGHMEM;
    }

    gbActive = 1; /* 切り替え成功 */
    return STATUS_NORMAL;
}

/* @leave */
int __stdcall   usr_leave(PSTRCALC_PARAM prm,char *arg,int len,void *param)
{
    /* ここでは簡単のため、後続に文字列が存在する場合はエラーとします。 */
    if (len)
        return STATUS_ERROR;

    /* @enter が実行されていない場合はエラーとする */
    if (!gbActive)
        return STATUS_USR_NOTREADY;

    StrCalc_term(prm); /* 現在の環境を破棄 */
    memcpy(prm,&gtemp_sc,sizeof(STRCALC_PARAM)); /* 以前の環境を戻す */

    gbActive = 0; /* 復元成功 */
    return STATUS_NORMAL;
}

/* @print */
int __stdcall   usr_print(PSTRCALC_PARAM prm,char *arg,int len,void *param)
{
    int         ret;

    prm->str = arg;
    if ((ret = StrCalc(prm)) != STATUS_NORMAL)
        return ret;

    printf("%.16G\n",prm->ans);

    return STATUS_NORMAL;
}

USERSTATEMENT       table[] = {
    { "print", 5, usr_print, NULL },
    { "enter", 5, usr_enter, NULL },
    { "leave", 5, usr_leave, NULL }
};

void    main(void)
{
    STRCALC_PARAM       sc;
    BASICPARAM          bas = {0};
    int                 ret;

    if (!StrCalc_init(&sc)){
        printf("初期化に失敗\n");
        return;
    }

    bas.stackcnt_if = 16;
    bas.stackcnt_for = 16;
    bas.stackcnt_gosub = 16;

    bas.pus = table;
    bas.uscnt = sizeof(table) / sizeof(USERSTATEMENT);

    bas.psource = "\
\n\
        a = 10 : b = 20 : c = 30\n\
        gosub $test_sub\n\
        @print a : @print b : @print c\n\
        end\n\
\n\
    $test_sub\n\
        @enter\n\
\n\
        a = 1000 : b = 2000 : c = 3000\n\
        @print a : @print b : @print c\n\
\n\
        @leave\n\
\n\
        return\n\
    ";
    bas.src_size = strlen(bas.psource);

    ret = StrCalc_BASIC(&sc,&bas);

    /*
      エラーや記述ミス等で @leave が実行されなかった時は、
      ここで @leave の処理を実行する
    */
    if (gbActive && usr_leave(&sc,"",0,NULL) != STATUS_NORMAL)
        printf("debug : @leave error.\n");

    StrCalc_term(&sc);

    if (ret != STATUS_NORMAL)
        printf("line : %lu     error.   %d\n",bas.stop_lineno,ret);
}

ソースファイル(zip)

このプログラムでは、ユーザステートメント "@enter" 、"@leave" で変数環境の切り替えを行っています。
@enter が実行されると、それまでの変数環境を退避し、新たに環境を作成します。@leave は、@enter で作成した変数環境を破棄し、退避しておいた環境を元に戻します。

        a = 10 : b = 20 : c = 30
        gosub $test_sub
        @print a : @print b : @print c
        end

    $test_sub
        @enter

        a = 1000 : b = 2000 : c = 3000
        @print a : @print b : @print c

        @leave

        return

@enter と @leave の間は独立した変数環境下で処理されるので、以前の環境に影響を与えません。

実行結果.

1000
2000
3000
10
20
30

上記は試験的なものなので、一度に切り替えられる個数は一つだけですが、スタックやテーブルを用いれば複数の環境に切り替える事もできるようになります。また、引数と戻り値が扱えるように改良すれば、再帰的な処理等も行い易くなります。

変数の切り替えは、for ループ中に行わないように注意してください。ループ中に制御用変数へアクセス出来なくなるとエラーになります。

悪い例.

        for i = 0 to 10
            @enter
        next

この場合、for ステートメントで制御用変数 i を登録後、@enter により変数環境が変わるので、for は変数 i にアクセス出来なくなるため、STATUS_ILLEGAL_FORLOOP を返して終了します。