module y4d_aux.lineparser;

private import std.string;
private import std.ctype; // toupper

///	ライン(1行)を解析するためのツール。文字列、数字の切り出しに便利。
/**
<PRE>
	例)
		データ) "abc def",abc def, 'abc def' ,	abc def
		・このデータの場合、getStr() を呼び出すごとに「abc def」が返る(4回)
		・すなわち、" "か' 'で囲まれたデータか、囲まれていないデータ。
		・先頭、末尾の空白、タブは削除されます
		・セパレータは 「,」
		・\0か、0x0d,0x0a(改行コード)があればそこで終了
		・""で囲む場合は、そのなかで""と書けば"の意味になります。
		・''で囲む場合は、そのなかで''と書けば'の意味になります。

		データ) 123,"123", 123 , '123'
		・このデータの場合、getNum() を呼び出すごとに 123が返ります(4回)

		データ) #　コメント
		・#の書かれた行はそこ以降はコメント扱い

	LineParser line = new LineParser;
	line.setLine("ABC  , DEF  AA, AAA		,BB,CC,'D  D',\"ABC\"abc,AAC");

	while (!line.isEnd()){
		char [] s;
		s = line.getStr();
		printf("<%.*s>\n",s);
		//	<ABC>,<DEF	AA>,<AAA>,<BB>,<CC>,<D	D>,<ABC>,<AAC>
		//	が表示される
	}

	foreach(inout char[] s;line)
		printf("<%.*s>\n",s);

	line.setLine("ABC  , 123,0,0.0,DEF  AA, AAA		,BB,CC,'D  D',\"ABC\"abc,AAC");
	foreach(inout char[] s;line){
		printf("<%.*s> => %d \n",s,line.getNum(s,-1));
	}
</PRE>
*/
class LineParser
{
	///	何も渡さないときのコンストラクタ
	this () { setLine(null); }

	///	1行渡す場合のコンストラクタ
	this (char[] linebuf){ setLine(linebuf); }

	///	ラインバッファを設定する
	void setLine(char[] linebuf)
	{
		buf = linebuf;
		bEnd = false;
		pos = pos2 = 0;
		str = null;
		if (linebuf) scan();
	}

	///	次の文字列取得
	/**
		setLineで設定されたバッファから、次の文字列を拾う。
	*/
	char[] getStr()
	{
		char[] s;
		if (isEnd()) { s = cast(char[]) ""; return s; }
		s = str;
		scan();
		char[] c;
		for(int i=0; i<s.length; ++i) {
			c ~= s[i];
		}
		return c;
	}
	

	///	文字列がマッチするかを調べる
	/**
		setLineで設定されたバッファから、次の文字列が指定されたものと
		マッチするかを調べ、マッチすればそこまで読み込みカウンタを進める。

		caseSensitive == false だと大文字小文字の違いは無視する
	*/
	bool	isMatch(char[] str,bool caseSensitive){
		int pos_ = pos2;
		if (caseSensitive) {
			foreach(char c;str){
				if (pos_ >= buf.length) return false;
				if (buf[pos_++] != c) return false;
			}
		} else {
			foreach(char c;str){
				if (pos_ >= buf.length) return false;
				if (std.ctype.toupper(buf[pos_++])
					!= std.ctype.toupper(c)) return false;
			}
		}
//		if (pos_ >= buf.length) bEnd = true;
		pos = pos_; // 進めておく。
		bEnd = false; // 再スキャンさせるために終了フラグもどす

		scan();	//	スキャンも必要。
		return true;
	}

	///	文字列がマッチするかを調べる
	/**
		大文字，小文字の違いは無視。
	*/
	bool	isMatch(char[] str){
		return isMatch(str,false);
	}

	///	次の数字(long型)を取得
	long getNum(long def)
	/**
		解析失敗のときは、返し値としてdef値が返る
	*/
	{
		if (isEnd()) return def;
		long l = getNum(str,def);
		scan();
		return l;
	}

	///	次の数字(double型)を取得
	double getDecimal(double def)
	/**
		解析失敗のときは、返し値はdef値が返る
		ただし、0のときは"0"というデータでなければならない。
		"0.00"のようなものはエラーとして扱われる。
		これはatofではエラー判定をできないための制限。
	*/
	{
		if (isEnd()) return def;
		double d = getDecimal(str,def);
		scan();
		return d;
	}

	///	バッファの終了判定
	bool isEnd()
	/**
		その行のラインが終了ならば、trueに。
		setLineした瞬間,getNum,getStrするごとに判定される
		isEndでの判定は前判定で良い。
		つまり、以下のプログラムで、すべての要素が表示される。

		while (isEnd()){
			printf("<%.*s>\n" = getStr();
		}
	*/
	{	return bEnd;	}

	///	自前のバッファ(文字列)を数値化(long型)する補助関数
	static long getNum(char[]s,long def)
	/**
		解析バッファとして自前のchar[]を渡すバージョン
		解析失敗のときは、返し値はdef値が返る

		またコーテーションetc..の使用は不可。
		例) "3"　→　error
	*/
	{
		long l =
			std.string.atoi(s);
			//	これ変換エラーを検出できないのだな．．（´Д｀）
		if (s!="0" && l==0){
			l = def; // これ解析失敗か
		}
		return l;
	}

	///	自前のバッファ(文字列)を数値化(double)する補助関数
	static double getDecimal(char[]s,double def)
	/**
		解析バッファとして自前のchar[]を渡すバージョン
		解析失敗のときは、返し値はdef値が返る
		ただし、0のときは"0"というデータでなければならない。
		"0.00"のようなものはエラーとして扱われる。
		これはatofではエラー判定をできないための制限。

		コーテーションetc..の使用は不可。
		例) "3"　→　error
	*/
	{
		double d =
			std.string.atof(s);
			//	これ変換エラーを検出できないのだな．．（´Д｀）
		if (s!="0" && d==0){
			d = def; // これ解析失敗か
		}
		return d;
	}

	///	foreachに対応させるためのもの
	int opApply(int delegate(inout char[]) dg)
	{
		int result = 0;
		char s[];
		while (!isEnd())
		{
			s = getStr();
			result = dg(s);
			if (result)	break;
		}
		return result;
	}

	///	foreachに対応させるためのもの
	int opApply(int delegate(inout int,inout char[]) dg)
	{
		int result = 0;
		int i = 0;
		char s[];
		while (!isEnd())
		{
			s = getStr();
			result = dg(i,s);
			++i;
			if (result)	break;
		}
		return result;
	}

private:
	void scan(){
		if (isEnd()) return ;
		pos2 = pos;
		int start_pos = pos;
		int end_pos = -1;

		bool bQuote = false;  //	' ' による囲み
		bool bWQuote = false; //	" " による囲み
		bool bUseEscapeQuote = false;
		bool bUseEscapeWQuote = false;
		//	エスケープ文字を使われたときに
		//	最終的に置換しなければならないのでそのためのフラグ

		char c;

		//	先頭文字のスキャン
		while (1){
			if (pos >= buf.length) { bEnd = true; return ; }
			c = buf[pos++];
			if (/*c == '\0' ||*/ c == 0x0d || c == 0x0a || c=='#')
				{ bEnd = true; return ; } // あかんわ
			if (c == '\'') { bQuote = true; start_pos = pos; break; }
			if (c == '"')  { bWQuote = true; start_pos = pos; break; }
			if (c == ' ' || c == '\t') continue;
			if (c == ',') { str = cast(char[]) ""; return ; }
			start_pos = pos-1; break; // なんやろ？文字？
		}
		//	中間の文字のスキャン
		while (1){
			if (pos >= buf.length) {
				if (end_pos == -1){
					end_pos = pos-1;
				}
				goto ExitScan;
			}
			c = buf[pos++];
			if (/*c == '\0' ||*/ c == 0x0d || c == 0x0a ||
				(!bQuote && !bWQuote && c=='#'))
				//	quote中は # 無視
			{
			//	bEnd = true;
				if (end_pos == -1){
					end_pos = pos-2;
				}
				pos --;
				goto ExitScan;
			}
			if (bQuote && c=='\'') {
				if (pos >= buf.length) {
					// 次がないのに使うなやゴルァ
//					break;
				} else if (buf[pos] == '\''){
					bUseEscapeQuote=true;
					pos++;
					continue;
				}
			}
			if (bWQuote && c=='"') {
				if (pos >= buf.length) {
					// 次がないのに使うなやゴルァ
//					break;
				} else if (buf[pos] == '"'){
					bUseEscapeWQuote=true;
					pos++;
					continue;
				}
			}
			if (bQuote && c=='\'') { bQuote=false; end_pos=pos-2; break; }
			if (bWQuote && c=='"') { bWQuote=false; end_pos=pos-2; break; }
			if (!(bQuote || bWQuote) && c == ',') {
				if (end_pos == -1)
					end_pos=pos-2;
				goto ExitScan;
			}
			if (!bQuote && !bWQuote){
				if (c == ' ' || c=='\t'){
				//	終了文字候補
					if (end_pos == -1)
						end_pos = pos-2;
				} else {
					end_pos = pos-1;
				}
			}
		}
		//	終了文字のスキャン
		while (1) {
			if (pos >= buf.length) {
				if (end_pos == -1){
					end_pos = pos-1;
				}
				break;
			}
			c = buf[pos++];
			if (/*c == '\0' ||*/ c == 0x0d || c == 0x0a || c=='#') {
		//		bEnd2 = true;
				if (end_pos == -1){
					end_pos = pos-2;
				}
				pos--;
				break;
			}
			if (c==',') break;
		}
ExitScan:
		str = buf[start_pos..end_pos+1];
		if (bUseEscapeQuote) {
			//	エスケープ文字が使ってあるならば、そのための処理が必要
			char[] tmp = new char[str.length];
			int j;
			for(int i=0;i<str.length;++i){
				char cc = str[i];
				if (cc=='\'') {
					++i;
					if (i==str.length) break;
					cc = str[i];
				}
				tmp[j++] = cc;
			}
			tmp.length = j; // 切りつめる
			str = tmp;
		}
		if (bUseEscapeWQuote) {
			//	エスケープ文字が使ってあるならば、そのための処理が必要
			char[] tmp = new char[str.length];
			int j;
			for(int i=0;i<str.length;++i){
				char cc = str[i];
				if (cc=='\"') {
					++i;
					if (i==str.length) break;
					cc = str[i];
				}
				tmp[j++] = cc;
			}
			tmp.length = j; // 切りつめる
			str = tmp;
		}
	}

	char [] buf;	//	1行
	int pos;		//	scanで解析中の位置
	int pos2;		//	解析中の位置(ひとつ前のscan呼び出し時のposの位置)
	char [] str;	//	scanが呼ばれたときに次のトークンを切り出しておく
	bool bEnd;		//	いま終りかえ?
}
