● StrCalc BASIC の実装

6.中断処理の入れ方

ソースコードを自由に編集できるアプリケーションでは、バグ等でプログラムが正常に動作しなかった時のために、BASIC を中断させる機構を用意する必要があります。

6-1.APPPROC コールバックによる監視

テスト1.

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

/* 中断の監視 */
int __stdcall   abortfunc(PSTRCALC_PARAM prm,void *param)
{
    /* Esc キーが押された場合、BASIC を中断する */
    return (_kbhit() && _getch() == 0x1b)?
        STATUS_USERABORT : 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 }
};

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.fevery = (APPPROC)abortfunc;

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

    /* この BASIC プログラムは無限ループします */
    bas.psource = "\
            i = 0\n\
        $endless\n\
            @print i : i = i + 1\n\
            goto $endless\n\
    ";
    bas.src_size = strlen(bas.psource);

    ret = StrCalc_BASIC(&sc,&bas);

    StrCalc_term(&sc);

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

テスト1ソースファイル(zip)

BASICPARAM 構造体 のメンバ fevery にコールバック(APPPROC コールバック)を指定する方法を用いると、どのような形の BASIC プログラムにも対応できる中断処理を構成する事ができます。上記のプログラムでは、fevery コールバックでコンソールのキーバッファを毎回監視し、Esc キーが押された時に BASIC を中断させています。

Windows アプリケーションの場合は、StrCalc BASIC の処理とユーザインターフェースの処理をスレッド分けします。詳細はテストプログラム(zip)を参照してください。

StrCalc BASIC には様々なコールバックが存在しますが、基本機能だけで構成された無限ループをアプリケーションレベルで停止させる事ができるのは、メンバ fevery に指定したコールバックだけです。

例.基本機能だけの無限ループ

    $infinite
        goto $infinite

6-1-1.基本機能以外の処理を行っている場合

ユーザステートメント等のコールバックが処理を行っている間は、fevery による中断が行えません。



コールバック内の処理はできるだけ速やかに行う様にしてください。時間が掛かる事が予測できる場合は、コールバック内の処理にも同様の中断処理を入れてください。

fevery の監視はステートメント毎に行われるので、目的の処理を複数のユーザステートメントで処理するように分割すると、fevery による監視を増やす事ができます。

6-2.BASIC のプログラムに中断のタイミングを決定させる場合

テスト2.

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

/* @esc */
int __stdcall   usr_esc(PSTRCALC_PARAM prm,char *arg,int len,void *param)
{
    /* Esc キーが押された場合、BASIC を中断する */
    return (_kbhit() && _getch() == 0x1b)?
        STATUS_USERABORT : 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 },
    { "esc", 3, usr_esc, 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 = "\
            i = 0\n\
        $endless\n\
            @print i : i = i + 1\n\
            @esc\n\
            goto $endless\n\
    ";
    bas.src_size = strlen(bas.psource);

    ret = StrCalc_BASIC(&sc,&bas);

    StrCalc_term(&sc);

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

テスト2ソースファイル(zip)

この方法では、ユーザステートメントで中断の監視を行います。BASIC プログラム側に監視のためのユーザステートメントを入れる必要がありますが、監視処理の実行タイミングと実行回数を BASIC プログラム側でコントロールする事ができるため、パフォーマンスの点で APPPROC コールバックを使用した場合よりも優れます。



6-3.時限式

テスト3.

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

#define STATUS_TIMER_ERROR              -9999

#define TMIN                            1   /* min. */
#define SLEEPTIME                       200 /* ms */

VOID CALLBACK   timerProc(LPVOID dmy,DWORD dwTimerLow,DWORD dwTimerHigh)
{
}

/* タイマー作成 */
INT __stdcall   startfunc(PSTRCALC_PARAM prm,LPHANDLE phTimer)
{
    SYSTEMTIME      st;
    FILETIME        ft;

    GetSystemTime(&st);
    st.wDay += (st.wHour += (st.wMinute += TMIN) / 60) / 24;
    st.wMinute %= 60; st.wHour %= 24;
    SystemTimeToFileTime(&st,&ft);

    return ((*phTimer = CreateWaitableTimer(NULL,TRUE,NULL)) == NULL ||
        SetWaitableTimer(*phTimer,(LARGE_INTEGER *)&ft,0,
            (PTIMERAPCROUTINE )timerProc,0,FALSE) == FALSE)?
        STATUS_TIMER_ERROR : STATUS_NORMAL;
}

/* 中断の監視 */
INT __stdcall   abortfunc(PSTRCALC_PARAM prm,LPHANDLE phTimer)
{
    return (WaitForSingleObject(*phTimer,SLEEPTIME) == WAIT_OBJECT_0)?
        STATUS_USERABORT : STATUS_NORMAL;
}

/* @print */
INT __stdcall   usr_print(PSTRCALC_PARAM prm,LPSTR arg,INT len,LPVOID 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 }
};

VOID    main(VOID)
{
    STRCALC_PARAM       sc;
    BASICPARAM          bas = {0};
    HANDLE              hTimer = NULL;
    INT                 ret;

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

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

    bas.fprologue = (APPPROC)startfunc;

    bas.proc_exparam = &hTimer;
    bas.fevery = (APPPROC)abortfunc;

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

    /* この BASIC プログラムは無限ループします */
    bas.psource = "\
            i = 0\n\
        $endless\n\
            @print i : i = i + 1\n\
            goto $endless\n\
    ";
    bas.src_size = strlen(bas.psource);

    ret = StrCalc_BASIC(&sc,&bas);

    /*
      タイマイベントのクローズ
      fepilogue コールバックでは、エラーで中断した時に実行されないので、
      StrCalc_BASIC の外でクローズします。
    */
    if (hTimer)
        CloseHandle(hTimer);

    StrCalc_term(&sc);

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

テスト3ソースファイル(zip)

6-1の方法を時間により制御します。上記のプログラムは約 1 分で中断します。アプリケーション側で、中断処理のためのユーザインターフェースを持てない場合等には、このプログラムのように時間で中断させる事も考えられます。