小数と指数

ゲームに簡単なプログラムを組み込むためにスクリプト言語とそれを実行するインタープリタをC++で作ってしまおうというコーナーです。
前回で整数を読み込めるスキャナができたので今回はそれを発展させて小数や指数も読み込めるようにしてみます。

小数

前回で整数がわかるようになったので、その続きで小数もわかるようにしましょう。
前回の「6. 0〜9以外の文字が来たら数値解析終了」と「7. 数字以外を一文字読み込んでしまったので一文字戻す」の間に、「'.'が来たら小数解析開始」を入れてみましょう。
今回はPerlやPHPに似せてしまったせいで'.'が来ても小数とは断定できないのでそのあたりも考慮しておきます。

  1. '.'が来たら小数解析開始
  2. 次の文字が0〜9だったら小数とみなせる部分まで読み続ける
  3. 0〜9じゃなかったら小数解析をしなかったことにして終了
for (char* ptr=str; *ptr!='\0'; ptr++) {
	// ここで、一文字ずつ見ていく
	if ('0'<=*ptr && *ptr<='9') {
		// 数値だ!
		double value = 0;
		while ('0'<=*ptr && *ptr<='9') {
			value = value*10+*ptr-'0';
			ptr++;
		}
		// ここまでが前回の分
		// 1. '.'が来たら小数解析開始
		if (*ptr=='.') {
			ptr++;
			// (この時点で*ptrは'.'の次の文字)
			if ('0'<=*ptr && *ptr<='9') {
				// 2. 次の文字が0〜9だったら小数とみなせる部分まで読み続ける
				double fraction = 1;
				while ('0'<=*ptr && *ptr<='9') {
					fraction /= 10;
					value = value+(*ptr-'0')*fraction;
					ptr++;
				}
				// (この時点で*ptrは小数の次の文字)
			}else {
				// 3. 0〜9じゃなかったら小数解析をしなかったことにして終了
				ptr--;
				// (この時点で*ptrは'.')
			}
		}
		// ここから先は前回と同様
		ptr--;
		m_TokenList.push_back(new CMSNumericalValueToken(value));
	}
}

指数

さて、今度は指数を認識できるようにしましょう。
先ほどまでで小数まで読み込んだわけですが、この後更に続けて'E'か'e'があれば指数を使っているので10を掛けたり割ったりしなければいけません。
指数部分には'+'や'-'もつけることができるので対応が必要そうです(数値全体にも符号はつきますがそちらは単項演算子で対応可能です)。
で、'+'がつく場合と、'-'がつく場合と、どちらもつかない場合の3通りがあるわけですが、そのどれでも数値の扱いや失敗した場合の処理に違いがあるので、ここは素直に3通りに条件分岐します。
多少指数関数的にコード量が増えてる気がしますが、指数を扱っている以上仕方ないといえましょう(どこが)。

for (char* ptr=str; *ptr!='\0'; ptr++) {
	// ここで、一文字ずつ見ていく
	if ('0'<=*ptr && *ptr<='9') {
		// 数値だ!
		double value = 0;
		while ('0'<=*ptr && *ptr<='9') {
			value = value*10+*ptr-'0';
			ptr++;
		}
		// ここまでが前回の分
		// 1. '.'が来たら小数解析開始
		if (*ptr=='.') {
			ptr++;
			if ('0'<=*ptr && *ptr<='9') {
				// 2. 次の文字が0〜9だったら小数とみなせる部分まで読み続ける
				double fraction = 1;
				while ('0'<=*ptr && *ptr<='9') {
					fraction /= 10;
					value = value+(*ptr-'0')*fraction;
					ptr++;
				}
			}else {
				// 3. 0〜9じゃなかったら小数解析をしなかったことにして終了
				ptr--;
			}
		}
		// ここからは指数だ
		if (*ptr=='e' || *ptr=='E') {
			ptr++;
			if ('0'<=*ptr && *ptr<='9') {
				// 直接指数が書かれているので普通に処理
				int exponent = 0;
				while ('0'<=*ptr && *ptr<='9') {
					exponent = exponent*10+*ptr-'0';
					ptr++;
				}
				for (;exponent>0; exponent--) value *= 10;
			}else if(*ptr=='+') {
				// '+'なので10を掛けまくる
				ptr++;
				if ('0'<=*ptr && *ptr<='9') {
					double exponent = 0;
					while ('0'<=*ptr && *ptr<='9') {
						exponent = exponent*10+*ptr-'0';
						ptr++;
					}
					for (;exponent>0; exponent--) value *= 10;
				}else {
					// '+'とその次の文字を余分に読みすぎたので2文字戻す
					ptr-=2;
				}
			}else if(*ptr=='-') {
				// '-'なので10で割りまくる
				ptr++;
				if ('0'<=*ptr && *ptr<='9') {
					double exponent = 0;
					while ('0'<=*ptr && *ptr<='9') {
						exponent = exponent*10+*ptr-'0';
						ptr++;
					}
					for (;exponent>0; exponent--) value /= 10;
				}else {
					// '-'とその次の文字を余分に読みすぎたので2文字戻す
					ptr-=2;
				}
			}else {
				ptr--;
			}
		}
		// ここから先は前回と同様
		ptr--;
		m_TokenList.push_back(new CMSNumericalValueToken(value));
	}
}

テスト

前回のようにテストしてみます。
今回はいつもより意地悪な文字列を渡しています。

int main(int argc, char* argv[])
{
	// テスト
	CMSScanner scanner;
	scanner.Scan("5323 55.25 4949.0 0.001 5e5 4.4e4 5.4e+3 6.5e-4 7.\"わはは\" 7.7e+-5 999e999 1e-1000");
	for (std::list<CMSToken*>::iterator it=scanner.m_TokenList.begin(); it!=scanner.m_TokenList.end(); it++) {
		printf("%f\n", ((CMSNumericalValueToken*)*it)->m_dValue);
	}
	return 0;
}

結果は次の通り。

5323.000000
55.250000
4949.000000
0.001000
500000.000000
44000.000000
5400.000000
0.000650
7.000000
7.700000
5.000000
1.#INF00
0.000000

ラスト4つはありえなさ過ぎる気がしないでもありませんが一応結果を返してくれています。
けなげさを感じます。

今回までのソース(VC++6)