- ステートメント名だけの場合
実行に必要な情報が無い場合や、アプリケーション側が既に情報を持っている場合は、ステートメント名だけを記述する形にします。
例1. テンポラリ属性の変数を全て削除する
@clear
#include "sc39.h"
int __stdcall usr_clear(PSTRCALC_PARAM prm,char *arg,int len,void *param)
{
/* ステートメント名の後ろ側をチェック */
if (len)
return STATUS_ERROR;
/* 実際の処理 */
return (StrCalc_delete_tempparam(prm))?
STATUS_NORMAL : STATUS_NOTENOUGHMEM;
}
ステートメント名の後ろに何も記述してはいけない場合は、len がゼロであるかを調べます。
- 情報が一つだけの場合
ここでは arg の指す文字列全体が目的の情報とします。
例2. 数値の表示
@print 数値または式
#include <stdio.h>
#include "sc39.h"
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;
}
@print の後ろには数値か式を記述しますので、プログラムを簡単にするため、後ろ側の書式を計算式規則と同じにしてしまいます。
arg には表示する数値・式が渡されますので、丸ごと 関数StrCalc に渡し、計算処理と同時に書式関連のエラーを StrCalc 側に検出させます。
- 情報が二つある場合
arg の指す文字列には、アプリケーションが定めた書式で二つの情報が入っていますので、文字列を解読し、情報を得ます。
例3. ファイルのコピー
@copyfile "コピー元ファイル名" to "コピー先ファイル名"
#include <windows.h>
#include <string.h>
#include <ctype.h>
#include "sc39.h"
#define STATUS_SRC_FILENAMEERR -10000
#define STATUS_DEST_FILENAMEERR -10001
#define STATUS_COPYFILE_SYNTAXERROR -10002
#define STATUS_COPYFILE_FAIL -10003
/* ファイルのコピー */
INT __stdcall usr_copyfile(PSTRCALC_PARAM prm,LPSTR arg,INT len,LPVOID param)
{
CHAR src[MAX_PATH+8],dest[MAX_PATH+8];
LPSTR ptr;
/* コピー元の取得 */
if (*(ptr = StrCalc_stringcopy(src,arg,sizeof(src))) != '\"')
return STATUS_SRC_FILENAMEERR;
/* 後続に to が記述されているかをチェック */
do{
ptr++;
}while(isspace(*ptr));
if (!isspace(ptr[-1]) ||
_strnicmp(ptr,"to",2) ||
!isspace(ptr[2]))
return STATUS_COPYFILE_SYNTAXERROR;
ptr += 2; /* "to" 分、進める */
/* コピー先の取得 */
if (*(ptr = StrCalc_stringcopy(dest,ptr,sizeof(dest))) != '\"')
return STATUS_DEST_FILENAMEERR;
/* 末尾側に余計な文字があるかチェック */
do{
ptr++;
}while(isspace(*ptr));
if (*ptr)
return STATUS_COPYFILE_SYNTAXERROR;
/* 上書きはしない */
return (CopyFile(src,dest,TRUE) != FALSE)?
STATUS_NORMAL : STATUS_COPYFILE_FAIL;
}
予約語の "to" が使用されていますが、変数名に使用する訳ではないので、この場合は問題ありません。
例3 では、arg の指す文字列を、先頭側から順に見ています。関数StrCalc_stringcopy でコピー元、コピー先のファイル名を取得し、間にある "to" や、末尾側の不要文字列のチェックを行います。
この例では、to の前後に空白を必要とします。
if (!isspace(ptr[-1]) ||
_strnicmp(ptr,"to",2) ||
!isspace(ptr[2]))
return STATUS_COPYFILE_SYNTAXERROR;
この場合の「空白」は、半角空白だけでなくタブ文字も含みますので、チェックには関数isspace(標準:ctype.h)を用います。
アプリケーションの仕様で、空白を除いた状態を許可する場合は、次のようにもできます。
/*
空白が無い状態とは、前後のファイル名を括っているダブルクォートが
to の前後にある状態の事を指します(ファイル名はダブルクォートで括
られている必要がある)。
"src" to "dest"
"src"to"dest"
*/
#define SPC_OR_DQ(C) (isspace(C) || (C) == '\"')
if (!SPC_OR_DQ(ptr[-1]) ||
_strnicmp(ptr,"to",2) ||
!SPC_OR_DQ(ptr[2]))
return STATUS_COPYFILE_SYNTAXERROR;
- 式がワードの間にある場合
ワード間にある式を計算し、情報を得ます。
例4. 等差数列の和
@ar_prog start 初期値 finish 最終値 step 増分
#include <string.h>
#include <ctype.h>
#include "sc39.h"
#define STATUS_ERROR1 -1000
#define STATUS_ERROR2 -1001
#define STATUS_ERROR3 -1002
/* 等差数列の和 */
int __stdcall usr_ar_prog(PSTRCALC_PARAM prm,char *arg,int len,void *param)
{
int ret;
char *ptr;
double start,finish,step;
/* ステートメント名直後は start である事 */
if (_strnicmp(arg,"start",5) || !isspace(arg[5]))
return STATUS_ERROR1;
prm->str = arg + 5;
/* start から finish の間を計算 */
if (*(ptr = StrCalc_search_word(arg,prm->str,"finish",6)) == 0)
return STATUS_ERROR2;
*ptr = 0;
if ((ret = StrCalc(prm)) != STATUS_NORMAL)
return ret;
start = prm->ans;
prm->str = ptr + 6;
/* finish から step までの間を計算 */
if (*(ptr = StrCalc_search_word(arg,prm->str,"step",4)) == 0)
return STATUS_ERROR3;
*ptr = 0;
if ((ret = StrCalc(prm)) != STATUS_NORMAL)
return ret;
finish = prm->ans;
prm->str = ptr + 4;
/* step から行末までの間を計算 */
if ((ret = StrCalc(prm)) != STATUS_NORMAL)
return ret;
step = prm->ans;
/* 目的の計算 */
printf("ans : %.16G\n",
(2.0 * start + (finish - 1.0) * step ) * finish / 2.0);
return STATUS_NORMAL;
}
式の直後にあるワードを関数StrCalc_search_word で検索し、発見した位置にヌル文字を入れます。そのまま検索開始位置を StrCalc に渡せば計算を行う事ができます。
初期値の計算
検索開始位置
↓
@ar_prog start 初期値 finish …
↑
関数StrCalc_search_word が返す位置
arg の指す文字列は内容が保護されています。上記例4 や関数strtok(標準:string.h)のように、内容を破壊しながら処理するプログラムを使用しても、次回の呼び出しで元に戻ります。
初期値の式に "finish" が変数名として使用されると、その変数をステートメントのキーワードと認識してしまうため、例4 の関数は正常に動作しません。そのため、呼び出し元環境(STRCALC_PARAM構造体)のメンバ ppcheck_paramname 、nametbl_cnt に "finish" を登録し、変数名として使用できないようにする必要があります。
STRCALC_PARAM sc;
static char *tbl[] = {
"finish"
};
・
・
・
/* StrCalc_init 実行直後に行う */
sc.ppcheck_paramname = tbl;
sc.nametbl_cnt = sizeof(tbl) / sizeof(char *);
同じ原理で "step" も問題になりそうですが、step は予約語であり、StrCalc の仕様・機能上、最初から変数名として使用できませんので、アプリケーション側は step について対応する必要はありません。
複雑な処理になってしまう場合は、文字列が期待通りに抽出できているかを確認しながら作成するとスムーズに実装できます(プログラム例(zip))。
|