【C++】インタプリタを初めから丁寧に(第03回)

前回に引き続き字句解析プログラムの内容について考えます。
成田さんに添削していただいた部分も合わせて冗長な部分を削除した・・・つもりです。

1. 演算子

まず、演算子について考えます。
演算子とは、いまさら確認するまでもないかもしれませんが a = b や a++ にある '=' や '++' のことです。

ちなみに上記の例はただ単に2個出しただけではありません。
演算子にも区別をしなければいけない点があます。

これらを判断しなければいけません。・・・といってもやはり考えることは簡単で、単に前の項が

  • 識別子
  • 整数
  • 文字列
  • 右括弧
  • ブール型定数

の、いずれでもない場合は単項演算子、そうでない場合は二項演算子と判断できます。

次にその演算子がなんであるかを判別します。これは前回と同じでその文字列が'='や'++'であるかどうか文字列比較すれば判断できます。 しかし、演算子数が増えてくれば当然ながら配列に文字列を突っ込みそれを一個ずつ比較することになります。

ここで こちらの記事 を見ていただければ分かるように配列の数字が何をさしているのかわからないのはマズイ、ということがわかります。
つまり a_Operator[3] = '+' なんて書き方をすると後々チェックミスが起こるだけでなく、添削という形で怒られるハメになります。(実際自分はこのような書き方をしていたため、そのまま書いて怒られるところでした・・・。)
よって配列の中に何が入っているか明確にするためにa_Operator[OP_PLUS] = '+'のような書き方をします。

これらをふまえた上で演算子について判別するコードを書きます。


std::string str; // スクリプトの内容
static int Pos; // スクリプトの位置
// 比較文字列の定義
static const string a_Operator[] = {
"OP_SINGLE_DOUBLE_BEGIN",  // 単項でも二項でも成り立つ場合の演算子軍です
"=",
"+", "-", "*", "/", ... // 沢山ありますが省略します。
"OP_SIGLE_DOUBLE_END",
"OP_DOUBLE_BEGIN",
"==", ... ,
"OP_DOUBLE_END"
"OP_SINGLE_BEGIN", // 単項演算子の開始
"S_PLUS". S_MINUS",... //単項+や単項-
"S_END"
}
enum {
OP_SINGLE_DOUBLE_BEGIN,
OP_ASSIGN,
OP_PLUS, OP_MINUS, OP_MULTIPLY, OP_DEVIDE, ...
OP_SINGLE_DOUBLE_END,
OP_DOUBLE_BEGIN,
OP_EQUAL, ....
OP_DOUBLE_END, // 演算子の最後です、ここまで比較した場合それは演算子ではないと判断するために使用します。
OP_SINGLE_BEGIN,
OP_S_PLUS, OP_S_MINUS,...
OP_S_END // 単項演算子の最後です
}
// 演算子を読み込み
bool ReadOperator() {
// 文字列比較
int length; // 演算子の長さ
for(int id = OP_SINGLE_DOUBLE_BEGIN + 1; id != OP_DOUBLE_END; id++)
{
length = a_Operator[id].length();
if(str.compare(Pos, length, a_Operator[id]) == 0) break;
}
if(id == OP_DOUBLE_END)
return false;
// 単項なのか、それとも二項なのかの判別
if(id < OP_DOUBLE_BEGIN && id > OP_SINGLE_DOUBLE_BEGIN)
{
if((Tokens.empty())
id += OP_DOUBLE_END - OP_SINGLE_DOUBLE_BEGIN - 1;
else(
{
// トークンの前の値が存在した場合
int next = Tokens.back().GetID() // 判別用に用意したTokenクラスからidを返す関数です。
if(next != INTEGER &\& next != STRING && ... ) // 整数や文字列などではない場合
id += OP_END - OP_ASSIGN -1;
}
}
// トークンの作成
Tokens.push_back(CToken(id, 0, a_Operator[id]);
Pos += length;
return true;
}

これで演算子が何であるかについて理解することができました。

2.制御文判別子

制御文判別子とは if や for, switch、また宣言に使われる int や string などのことです。 これも前述の演算子と同じように文字列比較で判別すればよいです。注意すべき点は。

  • 定数にif_calcやifabcなどの定数が存在した場合、ifと混同しないようにする

これについては第二回にあるIsChar(char c)を使用していましたが、標準関数のint isalpha(char c)が存在するのでこちらを使用します。 実装については次のとおりです。

static const string a_Keyword[] = {
"KEYWORD_DEF_BEGIN",
"int", "string", "bool",... // 型宣言に使用する名前です
"KEYWORD_DEF_END",
"KEYWORD_OP_BEGIN",
"if", "for",... // 制御命令に使用する名前です
"KEYWORD_OP_END"
}
enum {
KEYWORD_DEF_BEGIN,
KEY_INT, KEY_STR, KEY_BOOL,...
KEYWORD_DEF_END,
KEYWORD_OP_BEGIN,
KEY_IF, KEY_FOR,...
KYEWORD_OP_END
}
// 制御文判別子を読み取る
bool ReadKeyword()
{
for(int id = KEYWORD_DEF_BEGIN + 1; id != KEYWORD_OP_END; id++)
{
int length = a_Keyword[id].length();
// 制御文判別子と判断できた場合
if(str.compare(Pos, length, a_Keyword[id])==0 && !isalpha(str[Pos+length]))
break;
}
if(id == KEYWORD_OP_END) return false;
Tokens.push_back(CToken(id, 0, a_Keyword[id])
Pos += length;
return true;
}

以上で制御文判別子も理解することができるようになりました。 次回に3.区切り記号と4.関数判別子を実装し、字句解析は終了になります。