学習内容
ステップ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がアドレスであることを理解するのに時間がかかった。