2024-12-03

「低レイヤを知りたい人のためのCコンパイラ作成入門」第十四回勉強会まとめ

学習範囲

ステップ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;

関数定義を追加

equalityrelationaladdを追加します。

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型の配列にして文字列を扱うということが勉強になりました。