2024-10-11

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

学習内容

ステップ4:エラーメッセージを改良

  • 何バイト目で発生したか
  • どんなエラーが発生したか

解決手順

1.文字列の途中を指すポインタを用意する
2.エラー関数で、ポインタと入力の先頭を指しているポインタとの差を取る
3.マークをつけてエラー分を出力する

1. 文字列の途中を指すポインタをトークン内に用意する

文字列全体をuser_inputに代入

char *user_input;

×user_inputは入力内容、*user_inputは最初は入力内容の最初の位置を表すポインタ。
◯user_inputは”先頭の位置のみ”をあらわしている


- user_input = argv[1];としてるってことは引数全て入れているのに、なんで先頭のアドレスだけでいいのか?

→問題ない。”1 + 2"としたときにuser_inputには1のアドレスしか入っていないが、文字の終わりも定義されているので、読み取ることが可能


入力文字数分ルーブさせてtokenをつくる
下記の部分

while (*p) {
    // 空白文字をスキップ
    (省略)
  }

ここで*pは文字をあらわしている

  • while(*p)しているけど、文字の終わりはどのように判定しているのか?(どこまでが1単語だってどうやったらわかるのか)

→C言語では文字列の最後に\0が入るので、そこで判定している
例:入力内容”1 + 2”を入れたときにメモリには下記のようにはいる。
アドレス: 0x0010 0x0011 0x0012 0x0013 0x0014 0x0015
内容 : '1' ' ' '+' ' ' '2' '\0'

pは入力内容、pはアドレスを示す。
この場合最初の
pは1、pはアドレスである0x0010を指す。

文字1つ読み取ったらアドレスを++1する。
下記の部分

 // 空文字はスキップ
 if (isspace(*p)) {
      p++;
      continue;
    }
    (中略)
 // "+" or "-" のとき
 if (*p == '+' || *p == '-') {
      cur = new_token(TK_RESERVED, cur, p++);
      continue;
    }

 // 数値の時
  if (isdigit(*p)) {
      cur = new_token(TK_NUM, cur, p); // p++ としてはいけない。なぜなら、strtol関数でpが進むから
      cur->val = strtol(p, &p, 10);
      continue;
    }

tokenにポインタを保存する
下記の部分

Token *new_token(TokenKind kind, Token *cur, char *str) {
  Token *tok = calloc(1, sizeof(Token));
  tok->kind = kind;
  tok->str = str; // ここで位置情報を保存
  cur->next = tok;
  return tok;
}

2. エラー関数で、ポインタと入力の先頭を指しているポインタとの差を取る

下記の部分

int pos = loc - user_input;

復習:locは現在位置、user_inputは文字の先頭位置をあらわしている

3. マークをつけてエラーを表示させる

下記の部分

 fprintf(stderr, "%s\n", user_input); // 入力部分を表示
 fprintf(stderr, "%*s", pos, ""); // エラーが発生した位置までpos個の空白を出力
 fprintf(stderr, "^ "); // マークをつける
 vfprintf(stderr, fmt, ap); // エラー文を表示させる

実行結果

test.shにassert 22 " 5 + 21 -+ 4" と記入

5 + 21 -+ 4
        ^ 数ではありません

test.shにassert 41 "foo" と記入

foo
^ トークナイズできません

所感

  • ポインタの理解がかなり難しい。user_inputに入力内容を代入すると先頭位置だけを表すこと、*pが入力内容そのものでありpがアドレスであることを理解するのに時間がかかった。