/*	
 *	clipscan.c
 *
 *	コールバックを用いたscanfの実装
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2016 Naoyuki Sawa
 *
 *	* Thu Oct 23 20:43:59 JST 2014 Naoyuki Sawa
 *	- 新規作成。
 *	  http://www.cise.ufl.edu/~cop4600/cgi-bin/lxr/http/source.cgi/lib/stdio/doscan.cを基に改造しました。
 *	- sscanf()系関数は、P/ECE開発環境のEPSONライブラリにも含まれていますが、自分で再実装しました。
 *	  自分で再実装した理由は、以下の通りです:
 *	  ・EPSONライブラリのsscanf()系関数は、'%n'に対応していません。
 *	    '%n'は文字列解析で使うことが結構多いと思うので、'%n'に対応したsscanf()系関数が必要と思いました。
 *	  ・POSIX標準の'm'フラグも便利です。文字列を取得するバッファ溢れを気にしなくて済むからです。
 *	    EPSONライブラリのsscanf()系関数は、'm'にも対応していないので、自分で実装することにしました。
 *	  ・EPSONライブラリに限らず、言語インタプリタの実装時にsscanf()系関数を使いづらい事があります。
 *	    書式指定文字列をユーザーのスクリプトが指定する場合、その書式指定文字列から得られる値を、
 *	    スクリプトへ返すには、結局、言語インタプリタ自身が書式指定文字列を解析しなければなりません。
 *	    書式指定文字列から得られた値をコールバックで通知するように出来れば、上記の問題は解決します。
 *	    既存のsscanf()系関数の実装を調べましたが、良い例が見つかららず、自分で実装することにしました。
 *	- EPSONライブラリでは、doscan()はローカル関数となっていますが、当実装では、グローバル関数としました。
 *	  グローバル関数とした理由は、以下の通りです:
 *	  ・前述のように、書式指定文字列から得られた値をコールバックで通知出来ると、便利な事があります。
 *	    標準のsscanf()系関数のインターフェイスには、そのような仕組みが無いので、
 *	    本来は内部関数であるdoscan()を公開して、コールバックで通知するインターフェイスとしました。
 *	    言語インタプリタの実装時に、doscan()を直接使うと、便利だと思うからです。
 *	* Sun May 29 21:54:56 JST 2016 Naoyuki Sawa
 *	- Visual C++ .NETでコンパイルした時に、scanf()等で警告C4028が表示される問題を回避しました。
 *	  詳細は、以下の通りです。
 *	- VC++2015で当ライブラリをコンパイルしてみた所、当ライブラリの実装で置き換えるための、scanf()等で警告C4028が表示されました。
 *	  原因は、VC++2015では、これらの関数のプロトタイプが、標準的なプロトタイプと異なっている事でした。
 *	  以下に、VC++2015での、scanf()の宣言を示します。(他の関数も同様です)
 *	  □c:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\ucrt\stdio.h
 *	  │_CRT_STDIO_INLINE int __CRTDECL scanf(
 *	  │    _In_z_ _Scanf_format_string_ char const* const _Format,
 *	  │    ...)
 *	  標準的なプロトタイプでは、第一引数は'char const*'ですが、VC++2015では第一引数が'char const* const'になっています。
 *	  (ちなみに、MSDNの関数リファレンスでは前者になっており、VC++2015のヘッダファイルだけが後者の宣言であるようです。)
 *	- 対処する方法は、二種類考えられます。
 *	  ・一つは、Visual C++ .NETならば、当モジュール内の関数の引数型を変更して、Visual C++ .NETの関数プロトタイプに合わせる事です。
 *	  ・もう一つは、Visual C++ .NETならば、警告を無視する方法です。
 *	  本当は前者の方が安全なのでしょうけれど、今回は後者の方法を使う事にしました。
 *	  この問題は明らかにVisual C++ .NETだけの問題であり、この問題に対処するために多くの関数の定義を条件コンパイル分けしたりして、コードが複雑になるのは良くないと思ったからです。
 *	- 本日、clipprnt.cとclipscan.cに、同じ方法で対処を行いました。
 *	  もし今後、対処する方法を変更する場合は、両方について、変更する必要が有るかを検討して下さい。
 *	* Sun Sep 04 21:41:17 JST 2016 Naoyuki Sawa
 *	- 「#pragma warning(disable:...)」〜「#pragma warning(default:...)」を、「#pragma warning(push)」「#pragma warning(disable:...)」〜「#pragma warning(pop)」に変更しました。
 *	  詳細は、clipstd.h の同日のコメントを参照して下さい。
 *	* Wed Dec 14 21:49:23 JST 2016 Naoyuki Sawa
 *	- 型修飾子j,z,tを対応しました。
 *	  ただし、格納するサイズを対応しただけで、読み取りはこれまでと変わらずintで行っている事に注意して下さい。
 */
#include "clip.h"
/****************************************************************************
 *
 ****************************************************************************/
int doscan(const char* str, const char* fmt, void (*callback)(struct doscan_store*, void*), void* param) {
	const char* const org = fmt;
	int bConv = 0;		/* 少なくとも一回の変換が成功したらセットする。'*'や'%n'でもセットする。 */	//□「Linux Programmer's Manual - SCANF」(http://linuxjm.sourceforge.jp/html/LDP_man-pages/man3/scanf.3.html)より引用:
	int nStore = 0;		/* 変換と代入が成功した'入力要素'の数を数える。'*'や'%n'の数は含めない。 */	//│C言語の標準規格では「実行の完了時に返される代入の回数は%n命令の実行では増加しない」となっているが、正誤表の内容はこれと矛盾するようである。おそらく、%n変換が返り値に与える影響についてはどのような仮定もしないのが賢明であろう。
	char* tmp = NULL;	/* 一時的な文字列 */
	for(;;) {
		/* 書式文字列に明示的な空白が有れば… */
		if(isspace(*fmt)) {
			/* 書式文字列の空白を読み飛ばす。 */
			fmt += strspn(fmt, "\t\n\v\f\r ");
			/* 入力文字列の空白を読み飛ばす。 */
			str += strspn(str, "\t\n\v\f\r ");
		}
		/* 書式指定ならば… */
		if(*fmt == '%') {
			/* '%'の次へ進める。 */
			fmt++;
			/* '%%'以外ならば… */
			if(*fmt != '%') {
				struct doscan_store store;
				char* endp;
				int len;
				int aster = 0;	/* 代入抑制文字が指定されたらセットする。 */
				int width = 0;	/* widthフィールドが指定されたら変更する。 */
				if(*fmt == '*') {
					aster = *fmt++;
				}
				/* widthフィールドを取得する。 */
				if(isdigit(*fmt)) {
					width = strtol(fmt, (char**)&fmt, 10);
				}
				/* flagフィールドを取得する。 */
//{{変更:型修飾子j,z,tを対応しました。ただし、格納するサイズを対応しただけで、読み取りはこれまでと変わらずintで行っている事に注意して下さい。
//				if((*fmt == 'h') || (*fmt == 'l') || (*fmt == 'm')) {	//'m'はPOSIX.1-2008で標準化されている。
//↓変更:型修飾子j,z,tを対応しました。ただし、格納するサイズを対応しただけで、読み取りはこれまでと変わらずintで行っている事に注意して下さい。
				if((*fmt == 'h') || (*fmt == 'l') || (*fmt == 'm') ||	//'m'はPOSIX.1-2008で標準化されている。
				   (*fmt == 'j') || (*fmt == 't') || (*fmt == 'z')) {	//'j','t','z'はC99で導入された。
//}}変更:型修飾子j,z,tを対応しました。ただし、格納するサイズを対応しただけで、読み取りはこれまでと変わらずintで行っている事に注意して下さい。
					store.flag = *fmt++;
				}
				/* typeフィールドを取得する。 */
				store.type = *fmt++;
				/* '%c','%[...]','%n'以外ならば… */
				if((store.type != 'c') && (store.type != '[') && (store.type != 'n')) {
					/* 暗黙で入力文字列の空白を読み飛ばす。 */
					str += strspn(str, "\t\n\v\f\r ");
				}
				switch(store.type) {
				case 'n':
					store.value.i = str - org;
					break;
				case 'i':
				case 'd':
				case 'o':
				case 'u':
				case 'x':
				case 'X':
				case 'e':
				case 'E':
				case 'f':
				case 'g':
				case 'G':
					if(!width) { width = INT_MAX; }	/* width指定無しならば、制限無しとする。 */
					tmp = strndup(str, width);
					if(!tmp) { DIE(); }
					switch(store.type) {
					case 'i':
					case 'd':
						store.value.i = strtoul(tmp, &endp, (store.type == 'i') ? 0 : 10);
						break;
					case 'o':
					case 'u':
					case 'x':
					case 'X':
						store.value.i = strtoul(tmp, &endp, (store.type == 'o') ? 8 : (store.type == 'u') ? 10 : 16);
						break;
					default:
						store.value.f = strtod(tmp, &endp);
						break;
					}
					len = endp - tmp;
					if(!len) { goto L_EXIT; }
					str += len;
					break;
				case 's':
				case 'c':
					if(store.type == 's') {
						len = strcspn(str, "\t\n\v\f\r ");
						if(!width || (len < width)) { width = len; }	/* '%s'の場合、width指定無し,又は,指定されたwidthよりも空白までの文字数が少なければ、空白までの文字数とする。 */
					} else {
						if(!width) { width = 1; }			/* '%c'の場合、width指定無しならば、1文字とする。 */
					}
					tmp = strndup(str, width);
					if(!tmp) { DIE(); }
					len = strlen(tmp);
					if(!len) { goto L_EXIT; }
					store.value.s = tmp;
					str += len;
					break;
				case '[':
					{
						int circum = 0;	/* 否定文字クラスが指定されたらセットする。 */
						if(*fmt == '^') {	/* '['の次に'^'が有れば… */
							circum = 1;	/* 否定文字クラスである。 */
							fmt++;		/* '^'の次へ進める。 */
						}
						if(!width) { width = INT_MAX; }	/* width指定無しならば、制限無しとする。 */
						{
							const char* class_start = fmt;
							for(len = 0; (len < width) && str[len]; len++) {
								int lo, hi, match = 0;
								fmt = class_start;
								lo = *fmt++;				/* 初回の下限を取得する。 */
								do {					/* '['の直後の']',又は'[^'の直後の']'は、リテラルと見なし文字クラスを閉じない。 */
									if(!lo) { DIE(); }		/* 文字クラスが閉じていない。 */
									hi = lo;			/* 上限を下限と同じにしておく。 */
									if(*fmt == '-') {		/* 次の文字が'-'ならば… */
										fmt++;			/* '-'の次へ進める。 */
										hi = *fmt++;		/* 上限を取得する。 */
										if(!hi) { DIE(); }	/* 文字クラスが閉じていない。 */
									}
									if((str[len] >= lo) && (str[len] <= hi)) { match = 1; }
									if((str[len] >= hi) && (str[len] <= lo)) { match = 1; }	/* 逆指定も可とする。 */
									lo = *fmt++;			/* 次回の下限を取得する。 */
								} while(lo != ']');			/* 文字クラスが閉じたら抜ける。 */
								/* fmtは']'の次を指している。 */
								if(!(match ^ circum)) { break; }
							}
						}
						if(!len) { goto L_EXIT; }
						/* (len!=0)ならば、fmtは']'の次を指している。 */
						tmp = strndup(str, len);
						if(!tmp) { DIE(); }
						store.value.s = tmp;
						str += len;
					}
					break;
				default:
					DIE();	/* 不正な書式指定 */
				}
				/* 少なくとも一回の変換が成功したらセットする。'*'や'%n'でもセットする。 */
				bConv = 1;
				/* '*'が指定されていなければ… */
				if(!aster) {
					/* コールバックが指定されていれば、呼び出す。 */
					if(callback) { (*callback)(&store, param); }
					/* 変換と代入が成功した'入力要素'の数を数える。'*'や'%n'の数は含めない。 */
					if(store.type != 'n') { nStore++; }
				}
			/* '%%'ならば… */
			} else {
				/* '%'と入力文字を比較する。 */
				goto L_LITERAL;
			}
		/* 書式指定でなければ… */
		} else {
L_LITERAL:		/* 書式文字列が終端か、又は、入力文字と一致しなければ、抜ける。 */
			if(!*fmt || (*fmt++ != *str++)) { break; }
		}
		/* 一時的な文字列が確保されていたら、次のループを開始する前に開放する。 */
		if(tmp) {
			free(tmp);
			tmp = NULL;
		}
	}
L_EXIT:	/* 一時的な文字列が未開放ならば、開放する。 */
	if(tmp) { free(tmp); }
	/* 最初の変換が成功したか、又は、入力文字列の終端以外で不一致が起こった場合は、変換と代入が成功した入力要素の数を返す。
	 * 最初の変換が成功する前に入力文字列の終端に達して一致の失敗が起こった場合は、EOFを返す。 */
	return (bConv || *str) ? nStore : EOF;
}
/****************************************************************************
 *
 ****************************************************************************/
static void vsscanf_callback(struct doscan_store* storePtr, void* param) {
	va_list* apPtr = param;
	switch(storePtr->type) {
	case 'n':
	case 'i':
	case 'd':
	case 'o':
	case 'u':
	case 'x':
	case 'X':
//{{2016/12/14変更:型修飾子j,z,tを対応しました。ただし、格納するサイズを対応しただけで、読み取りはこれまでと変わらずintで行っている事に注意して下さい。
//		if(storePtr->flag == 'h') {
//			*va_arg(*apPtr, short*) = storePtr->value.i;
//		} else {
//			*va_arg(*apPtr,   int*) = storePtr->value.i;
//		}
//↓2016/12/14変更:型修飾子j,z,tを対応しました。ただし、格納するサイズを対応しただけで、読み取りはこれまでと変わらずintで行っている事に注意して下さい。
		switch(storePtr->flag) {
		default:  *va_arg(*apPtr,       int*) = storePtr->value.i; break;
		case 'h': *va_arg(*apPtr,     short*) = storePtr->value.i; break;
		case 'j': *va_arg(*apPtr,  intmax_t*) = storePtr->value.i; break;
		case 't': *va_arg(*apPtr, ptrdiff_t*) = storePtr->value.i; break;
		case 'z': *va_arg(*apPtr,    size_t*) = storePtr->value.i; break;
		}
//}}2016/12/14変更:型修飾子j,z,tを対応しました。ただし、格納するサイズを対応しただけで、読み取りはこれまでと変わらずintで行っている事に注意して下さい。
		break;
	case 'e':
	case 'E':
	case 'f':
	case 'g':
	case 'G':
		if(storePtr->flag == 'l') {
			*va_arg(*apPtr, double*) = storePtr->value.f;
		} else {
			*va_arg(*apPtr,  float*) = storePtr->value.f;
		}
		break;
	case 's':
	case 'c':
	case '[':
		       if(storePtr->flag == 'm') {								//'m'はPOSIX.1-2008で標準化されている。
			      *va_arg(*apPtr, char**) = strdup(storePtr->value.s);
		} else if(storePtr->type == 'c') {
			memcpy(va_arg(*apPtr, char* ),         storePtr->value.s, strlen(storePtr->value.s));	//'%c'は終端のnulを格納しない仕様です。
		} else {
			strcpy(va_arg(*apPtr, char* ),         storePtr->value.s);
		}
		break;
	default:
		DIE();
	}
}
/****************************************************************************
 *
 ****************************************************************************/
//{{2016/05/29追加:Visual C++ .NETでコンパイルした時に、警告C4028が表示される問題を回避しました。
// * Sun May 29 21:54:56 JST 2016 Naoyuki Sawa
// - Visual C++ .NETでコンパイルした時に、scanf()等で警告C4028が表示される問題を回避しました。
//   詳細は、以下の通りです。
// - VC++2015で当ライブラリをコンパイルしてみた所、当ライブラリの実装で置き換えるための、scanf()等で警告C4028が表示されました。
//   原因は、VC++2015では、これらの関数のプロトタイプが、標準的なプロトタイプと異なっている事でした。
//   以下に、VC++2015での、scanf()の宣言を示します。(他の関数も同様です)
//   □c:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\ucrt\stdio.h
//   │_CRT_STDIO_INLINE int __CRTDECL scanf(
//   │    _In_z_ _Scanf_format_string_ char const* const _Format,
//   │    ...)
//   標準的なプロトタイプでは、第一引数は'char const*'ですが、VC++2015では第一引数が'char const* const'になっています。
//   (ちなみに、MSDNの関数リファレンスでは前者になっており、VC++2015のヘッダファイルだけが後者の宣言であるようです。)
// - 対処する方法は、二種類考えられます。
//   ・一つは、Visual C++ .NETならば、当モジュール内の関数の引数型を変更して、Visual C++ .NETの関数プロトタイプに合わせる事です。
//   ・もう一つは、Visual C++ .NETならば、警告を無視する方法です。
//   本当は前者の方が安全なのでしょうけれど、今回は後者の方法を使う事にしました。
//   この問題は明らかにVisual C++ .NETだけの問題であり、この問題に対処するために多くの関数の定義を条件コンパイル分けしたりして、コードが複雑になるのは良くないと思ったからです。
// - 本日、clipprnt.cとclipscan.cに、同じ方法で対処を行いました。
//   もし今後、対処する方法を変更する場合は、両方について、変更する必要が有るかを検討して下さい。
#if     (defined(_MSC_VER) && (_MSC_VER > 1200))
//{{2016/09/04変更:「#pragma warning(disable:...)」〜「#pragma warning(default:...)」を、「#pragma warning(push)」「#pragma warning(disable:...)」〜「#pragma warning(pop)」に変更しました。
//#pragma warning(disable: 4028)	//Visual C++ .NETならば、この範囲で警告C4028を抑制します。↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
//↓2016/09/04変更:「#pragma warning(disable:...)」〜「#pragma warning(default:...)」を、「#pragma warning(push)」「#pragma warning(disable:...)」〜「#pragma warning(pop)」に変更しました。
#pragma warning(push)
#pragma warning(disable: 4028)		//Visual C++ .NETならば、この範囲で警告C4028を抑制します。↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
//}}2016/09/04変更:「#pragma warning(disable:...)」〜「#pragma warning(default:...)」を、「#pragma warning(push)」「#pragma warning(disable:...)」〜「#pragma warning(pop)」に変更しました。
#endif//(defined(_MSC_VER) && (_MSC_VER > 1200))
//}}2016/05/29追加:Visual C++ .NETでコンパイルした時に、警告C4028が表示される問題を回避しました。
/****************************************************************************
 *
 ****************************************************************************/
int scanf(const char* fmt, ...) {
	int retval;
	va_list ap;
	va_start(ap, fmt);
	retval = vscanf(fmt, ap);
	va_end(ap);
	return retval;
}
int vscanf(const char* fmt, va_list ap) {
	return vfscanf(stdin, fmt, ap);
}
/*--------------------------------------------------------------------------*/
int sscanf(const char* str, const char* fmt, ...) {
	int retval;
	va_list ap;
	va_start(ap, fmt);
	retval = vsscanf(str, fmt, ap);
	va_end(ap);
	return retval;
}
int vsscanf(const char* str, const char* fmt, va_list ap) {
	return doscan(str, fmt, vsscanf_callback, &ap);
}
/*--------------------------------------------------------------------------*/
int fscanf(FILE* stream, const char* fmt, ...) {
	int retval;
	va_list ap;
	va_start(ap, fmt);
	retval = vfscanf(stream, fmt, ap);
	va_end(ap);
	return retval;
}
int vfscanf(FILE* stream, const char* fmt, va_list ap) {
	DIE();	//未実装
}
/****************************************************************************
 *
 ****************************************************************************/
//{{2016/05/29追加:Visual C++ .NETでコンパイルした時に、警告C4028が表示される問題を回避しました。
#if     (defined(_MSC_VER) && (_MSC_VER > 1200))
//{{2016/09/04変更:「#pragma warning(disable:...)」〜「#pragma warning(default:...)」を、「#pragma warning(push)」「#pragma warning(disable:...)」〜「#pragma warning(pop)」に変更しました。
//#pragma warning(default: 4028)	//Visual C++ .NETならば、この範囲で警告C4028を抑制します。↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
//↓2016/09/04変更:「#pragma warning(disable:...)」〜「#pragma warning(default:...)」を、「#pragma warning(push)」「#pragma warning(disable:...)」〜「#pragma warning(pop)」に変更しました。
#pragma warning(pop)			//Visual C++ .NETならば、この範囲で警告C4028を抑制します。↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
//}}2016/09/04変更:「#pragma warning(disable:...)」〜「#pragma warning(default:...)」を、「#pragma warning(push)」「#pragma warning(disable:...)」〜「#pragma warning(pop)」に変更しました。
#endif//(defined(_MSC_VER) && (_MSC_VER > 1200))
//}}2016/05/29追加:Visual C++ .NETでコンパイルした時に、警告C4028が表示される問題を回避しました。
/****************************************************************************
 *
 ****************************************************************************/
