学習範囲
ステップ7: 比較演算子
<、<=、>、>=、==、!=を実装
比較演算子は特殊な意味を持っているように見えますが、実際には2つの整数を受け取って1つの整数を返します。つまり普通の2項演算子です。
トークナイザの変更
lenという構造体を追加します。
struct Token {
TokenKind kind; //トークンの型
Token *next; //次の入力トークン
int val; //kindがTK_NUMの場合、その数値
char *str; //トークン文字列
int len; //トークンの長さ
};
上記の変更に伴って、consume関数も変更します。
consume関数の引数を(char op)から(char * op)に変更して、char型(文字型)ではなくstring型(文字列型)を受け取れるようにします。
char * op は文字へのアドレスを保持するため、指し示すメモリ領域の内容を操作することになります。そのため文字列を引数として渡すことができるようになります。
bool consume(char *op) {
if (token->kind != TK_RESERVED ||
strlen(op) != token->len ||
memcmp(token->str, op, token->len))
return false;
token = token->next;
return true;
}
文字列型にしたことでconsume関数を呼び出している箇所で引数の囲いを「''」(シングルクォーテーション)から「""」(ダブルクォーテーション)に変更します。
シングルクォーテーションのままだと以下のエラーがでます。
warning: passing argument 1 of 'expect' makes pointer from integer without a cast [-Wint-conversion]
205 | expect(')');
このエラーが出る原因は「' '」は文字型なので1文字の扱いとなりますが、consume関数で定義したのは文字列型なので型不一致エラーが起きるからです。解消するにはcomsume関数に渡す引数を 「""」で囲み文字列型として扱う必要があります。
抽象構文木のノードの型を追加
<、<=、>、>=、==、!= のノード型を追記します。
typedef enum {
ND_ADD, //+
ND_SUB, //-
ND_MUL, //*
ND_DIV, // /
ND_NUM, // 整数
ND_EQ, // ==
ND_NE, // !=
ND_LT, // <
ND_LTE, // <=
} NodeKind;
関数定義を追加
equality
とrelational
とadd
を追加します。
Node *expr();
Node *equality();
Node *relational();
Node *add();
Node *mul();
Node *unary();
Node *primary();
新しい文法
比較演算子を加えた新しい文法は以下のようになります。
expr = equality
equality = relational ("==" relational | "!=" relational)*
relational = add ("<" add | "<=" add | ">" add | ">=" add)*
add = mul ("+" mul | "-" mul)*
mul = unary ("*" unary | "/" unary)*
unary = ("+" | "-")? primary
primary = num | "(" expr ")"
それぞれをコードに落とし込んだものが以下のようになります。
Node *primary() {
//次のトークンが"("なら、 "(" expr ")"のはず
if (consume("(")) {
Node *node = expr();
expect(")");
return node;
}
//そうでなければ数値のはず
return new_node_num(expect_number());
}
Node *unary() {
if (consume("+"))
return primary();
if (consume("-"))
return new_node(ND_SUB, new_node_num(0), primary());
return primary();
}
Node *mul() {
Node *node = unary();
for(;;) {
if(consume("*"))
node = new_node(ND_MUL, node, unary());
else if (consume("/"))
node = new_node(ND_DIV, node, unary());
else
return node;
}
}
Node *add() {
Node *node = mul();
for (;;) {
if (consume("+"))
node = new_node(ND_ADD, node, mul());
else if (consume("-"))
node = new_node(ND_SUB, node, mul());
else
return node;
}
}
Node *relational() {
Node *node = add();
for (;;) {
if (consume("<"))
node = new_node(ND_LT, node, add());
else if (consume("<="))
node = new_node(ND_LTE, node, add());
else if (consume(">"))
node = new_node(ND_LT, add(), node);
else if (consume(">="))
node = new_node(ND_LTE, add(), node);
else
return node;
}
}
Node *equality() {
Node *node = relational();
for (;;) {
if (consume("=="))
node = new_node(ND_EQ, node, relational());
else if (consume("!="))
node = new_node(ND_NE, node, relational());
else
return node;
}
}
Node *expr() {
return equality();
}
まとめ
<、<=、>、>=、==、!=を新しく追加したので受け取る引数を文字型から、文字列型に変更しました。私はここでエラーが出まして、今までシングルクォーテーションで囲っていた箇所を全てダブルクォーテーションで囲うように変更したところエラーが解消されました。
また、C言語には他の言語にあるようなString型(文字列型)はないで、char型の配列にして文字列を扱うということが勉強になりました。