● StrCalc BASIC の実装

3.ユーザステートメント

ユーザステートメントとは、アプリケーションが設定できるステートメントです。処理内容だけでなく BASIC プログラム上のインターフェースも、ある程度はアプリケーション側が決定する事ができます。
I/Oや、BASIC には荷が重い処理を低水準で行いたい場合、アプリケーションやOSの機能を BASIC 側に提供したい場合等に使用する事ができます。

StrCalc BASIC は、様々なアプリケーションに組み込まれる事を想定しているため、特定のI/Oを持ちません。必要なI/Oがある場合は、アプリケーションがユーザステートメントとして処理する必要があります。

3-1.ステートメント名と書式、処理内容を決定する

ステートメント名は名付け規則の範囲内で自由に設定する事ができますが、通常はシンボリックな名称にします。

    @ユーザステートメント名   必要な情報 

ステートメント名の後ろには、処理に必要な情報を記述します(以下、「引数」と称します)。ステートメント名との間は、半角空白またはタブ文字で区切ります
引数の書式は、基本規則の範囲内で自由に決定する事ができます。

例. 単一変数をコピーする

           @paramcopy     コピー元変数名  to  コピー先変数名
               ↑                         ↑
 ユーザステートメント名                  引数

ステートメントの処理内容を決定します(引数の処理も含む)。
BASIC でユーザステートメントが使用された時、アプリケーションが用意したコールバック関数が呼び出されます。コールバック関数には、引数の部分が文字列で渡されますので、アプリケーションはその情報を処理します。

引数を複雑な構文にする事もできますが、それに応じてコールバック関数の処理も複雑になるので、必要以上に複雑にする事は避けた方が良いでしょう。

3-2.コールバック関数

決定した仕様から処理手順を考えます。
  1. コピー元変数名の範囲を調べるため、"to" を検索する。
    無ければエラーとする。

  2. 先頭 〜 "to" の間からコピー元変数名を抽出し、その名前を使って変数環境から情報を検索する。
    変数名が長過ぎる場合や変数が見つからない場合はエラーとする。

  3. "to" 〜 末尾の間からコピー先変数名を抽出し、その名前を使って変数環境から情報を検索する。
    変数名が長過ぎる場合や変数が見つからない場合はエラーとする。

  4. 属性が合わない場合や、単一変数ではなかった場合はエラーとする。

  5. 内容のコピーを行う。
この手順をコールバック関数に具現化します。

例. 

#include <ctype.h>
#include "sc39.h"

/* 空白を除く文字列のコピー */
int     copy(char *pdest,char *psrc,int cnt)
{
    for(;*psrc;psrc++){
        if (isspace(*psrc))
            continue;
        if (cnt <= 0)
            return 0;
        *pdest++ = *psrc;
        cnt--;
    }

    *pdest = 0;
    return 1;
}

/* コールバック:単一変数のコピー @paramcopy */
int __stdcall   usr_paramcopy(PSTRCALC_PARAM prm,char *arg,int len,void *param)
{
    char            src[SC_MAX_PARAMNAME+1],dest[SC_MAX_PARAMNAME+1];
    char            *ptr;
    PARAM_INFO      sprm,dprm;

    /* "to" の検索 */
    ptr = StrCalc_search_word(arg,arg,"to",2);
    if (!*ptr)
        return STATUS_ERROR;
    *ptr = 0; /* コピー処理のためヌル文字を置く */

    /* 先頭 〜 "to" の間を取得 → コピー元 */
    if (!copy(src,arg,SC_MAX_PARAMNAME) || !StrCalc_search_param(prm,src,&sprm))
        return STATUS_ERROR;

    /* "to" 〜 末尾の間を取得 → コピー先 */
    if (!copy(dest,ptr + 2,SC_MAX_PARAMNAME) || !StrCalc_search_param(prm,dest,&dprm))
        return STATUS_ERROR;

    /* 属性が一致しないか、単一変数ではない場合はエラー */
    if (sprm.type != dprm.type || sprm.type != PRM_SINGLE)
        return STATUS_ERROR;

    *dprm.pval = *sprm.pval; /* 内容のコピー */

    return STATUS_NORMAL;
}

引数prm には、その BASIC 処理で使用されている STRCALC_PARAM 構造体の情報が渡されます。また、arg にはユーザステートメントの引数を格納したバッファの先頭アドレスが渡され、len には arg が指す文字列の長さが渡されます。

arg が指す文字列は末尾ヌルで、先頭側の空白を除いてあります。arg の内容は、次回のコールバック関数呼び出し時に元に戻りますので、変更しても問題はありません。

コールバック関数の戻り値には状態コードを使用します。正常終了は STATUS_NORMAL 、それ以外の場合は負の値を返します(STATUS_NORMAL = 0)。正常終了以外の値を返すと BASIC プログラムは中断し、アプリケーションにはコールバックが返した戻り値を返します。

3-3.USERSTATEMENT構造体のテーブル

作成したコールバックを BASICPARAM構造体に関連付け、StrCalc BASIC上で使用できるようにします。

USERSTATEMENT       table[] = {
    { "paramcopy",  9, usr_paramcopy, NULL },
    { "clear",      5, usr_clear,     NULL },
    { "print",      5, usr_print,     NULL },
    { "copyfile",   8, usr_copyfile,  NULL },
    { "ar_prog",    7, usr_ar_prog,   NULL }
};

設定メンバについては USERSTATEMENT構造体を参照してください。
ステートメント情報をテーブルにし、そのポインタと要素数を、BASICPARAM構造体のメンバpususcnt にセットします。

    BASICPARAM      bas;

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



ユーザステートメントには全角文字を使用する事もできます。

例.

    @計算結果の表示   「1 + 2 + 3 + 4 + 5」をコンソールに表示します。
                                ↑
                     この部分に計算式を置く

このような書式にする事も可能です。ただし、コールバックの処理を S-JIS に対応させる必要があります(参考:プログラム例(zip))。