スキャナを作る

ゲームに簡単なプログラムを組み込むためにスクリプト言語とそれを実行するインタープリタをC++で作ってしまおうというコーナーです。
前回でスクリプト内の値を格納するクラスを作りました。

スキャナ

今回は文字列をもらってきてトークン列に変換するスキャナを作ります。
単純に考えて、スキャナとトークンのクラスを用意して、スキャナにはトークンのリストを入れておきます。
トークン列は確かスタックかキューのどちらかでよかったと思うのですが、今回は深く考えずリストです。<

class CMSToken
{
public:
	CMSToken() {}
	virtual ~CMSToken() {}
};

class CMSScanner  
{
public:
	std::list<CMSToken*> m_TokenList;
	void Scan(char* str);
	CMSScanner() {}
	virtual ~CMSScanner() {
		for (std::list<CMSToken*>::iterator it=m_TokenList.begin(); it!=m_TokenList.end(); it++) {
			delete *it;
		}
	}
};

スキャナというからにはスキャンするのでしょう。
Scan関数で文字列を受け取って字句解析を行うようにしましょう。

void CMSScanner::Scan(TCHAR* str)
{
	for (TCHAR* ptr=str; ptr!='\0'; ptr++) {
		// ここで、一文字ずつ見ていく
	}
}

ループの終了条件から見て、NULL終端文字列を前提としていますね。
今コメントで書いている部分に字句解析の本体を書くんですね。
とりあえず今日のところは整数でも解析してみましょう。

数値を解釈する

ということは数値のトークンが必要になります。CMSTokenから派生させて作りましょう。

class CMSNumericalValueToken : public CMSToken
{
public:
	double m_dValue;
	CMSNumericalValueToken(double value) { m_dValue=value; }
};

例によってあまり深く考えていません。
さて、数値の解析の方針を立てましょう。
最初はシンプルに整数だけ…。

  1. 0~9の文字が来たら数値解析開始
  2. その文字に対応する数値を記録しておく
  3. 一文字読み進める
  4. また0~9の文字が来たら記録した数値を10倍して文字に対応する数値を足す
  5. 0~9以外の文字が来るまで3~4を繰り返す
  6. 0~9以外の文字が来たら数値解析終了
  7. 数字以外を一文字読み込んでしまったので一文字戻す
  8. 結果の数値をトークンとしてトークンのリストに加える

プログラムにしてみると、こうなります。

void CMSScanner::Scan(char* str)
{
	for (char* ptr=str; *ptr!='\0'; ptr++) {
		// ここで、一文字ずつ見ていく
		if ('0'<=*ptr && *ptr<='9') {
l1:			;// 1. 0~9の文字が来たら数値解析開始
l2:			double value = *ptr-'0';	// 2. その文字に対応する数値を記録しておく
l3:			ptr++;	// 3. 一文字読み進める
			if ('0'<=*ptr && *ptr<='9') {
				// 4. また0~9の文字が来たら記録した数値を10倍して文字に対応する数値を足す
l4:				value = value*10+*ptr-'0';
l5:				goto l3;	// 5. 0~9以外の文字が来るまで3~4を繰り返す
			}else {
l6:				;	// 6. 0~9以外の文字が来たら数値解析終了
			}
l7:			ptr--;	// 7. 数字以外を一文字読み込んでしまったので一文字戻す
l8:			m_TokenList.push_back(
				new CMSNumericalValueToken(value)
				);	// 8. 結果の数値をトークンとしてトークンのリストに加える
		}
	}
}

ラベルがうるさいし、コメントもうるさいし、C++のくせにgotoなんて使ってるし、あまり華麗ではありません。
もうちょっとマシなコードに書き直しましょう。

void CMSScanner::Scan(char* str)
{
	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++;
			}
			ptr--;
			m_TokenList.push_back(new CMSNumericalValueToken(value));
		}
	}
}

部分的に改悪されているような気がしないでもありませんが、とりあえず読みやすくはなりました。
以下のプログラムで動作を確かめてみます。
言うまでもありませんが、スキャナに文字列を渡して、その後のトークン列に格納されている数値を順次表示しています。

int main(int argc, char* argv[])
{
	// テスト
	CMSScanner scanner;
	scanner.Scan("5323 3233 555");
	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
3233.000000
555.000000

わりと簡単に小数も解析できるようになりそうですが、そのあたりは次回へお預けって事で。

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