chibiccを読む~Cコンパイラコードリーディング~ ステップ4
トップページ
jupiteroak.hatenablog.com
「低レイヤを知りたい人のためのCコンパイラ作成入門」のCコンパイラを読んでいきます。
www.sigbus.info
ステップ4に該当
github.com
- 今回作成するコンパイラ
- コンパイラのソースコード
- 追加・修正されたコンパイラのソースコード
- テストコード
- Makefile
コンパイラのソースコード
https://github.com/rui314/chibicc/blob/c6ff1d98a1419e69c31902447e2caa85af4e9844/main.c
#include <ctype.h> #include <stdarg.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> typedef enum { TK_RESERVED, // Keywords or punctuators TK_NUM, // Integer literals TK_EOF, // End-of-file markers } TokenKind; // Token type typedef struct Token Token; struct Token { TokenKind kind; // Token kind Token *next; // Next token int val; // If kind is TK_NUM, its value char *str; // Token string }; // Input program char *user_input; // Current token Token *token; // Reports an error and exit. void error(char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); exit(1); } // Reports an error location and exit. void error_at(char *loc, char *fmt, ...) { va_list ap; va_start(ap, fmt); int pos = loc - user_input; fprintf(stderr, "%s\n", user_input); fprintf(stderr, "%*s", pos, ""); // print pos spaces. fprintf(stderr, "^ "); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); exit(1); } // Consumes the current token if it matches `op`. bool consume(char op) { if (token->kind != TK_RESERVED || token->str[0] != op) return false; token = token->next; return true; } // Ensure that the current token is `op`. void expect(char op) { if (token->kind != TK_RESERVED || token->str[0] != op) error_at(token->str, "expected '%c'", op); token = token->next; } // Ensure that the current token is TK_NUM. int expect_number() { if (token->kind != TK_NUM) error_at(token->str, "expected a number"); int val = token->val; token = token->next; return val; } bool at_eof() { return token->kind == TK_EOF; } // Create a new token and add it as the next token of `cur`. 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; } // Tokenize `user_input` and returns new tokens. Token *tokenize() { char *p = user_input; Token head; head.next = NULL; Token *cur = &head; while (*p) { // Skip whitespace characters. if (isspace(*p)) { p++; continue; } // Punctuator if (*p == '+' || *p == '-') { cur = new_token(TK_RESERVED, cur, p++); continue; } // Integer literal if (isdigit(*p)) { cur = new_token(TK_NUM, cur, p); cur->val = strtol(p, &p, 10); continue; } error_at(p, "expected a number"); } new_token(TK_EOF, cur, p); return head.next; } int main(int argc, char **argv) { if (argc != 2) { error("%s: invalid number of arguments", argv[0]); return 1; } user_input = argv[1]; token = tokenize(); printf(".intel_syntax noprefix\n"); printf(".global main\n"); printf("main:\n"); // The first token must be a number printf(" mov rax, %d\n", expect_number()); // ... followed by either `+ <number>` or `- <number>`. while (!at_eof()) { if (consume('+')) { printf(" add rax, %d\n", expect_number()); continue; } expect('-'); printf(" sub rax, %d\n", expect_number()); } printf(" ret\n"); return 0; }
追加・修正されたコンパイラのソースコード
main関数
https://github.com/rui314/chibicc/commit/c6ff1d98a1419e69c31902447e2caa85af4e9844#diff-a0cb465674c1b01a07d361f25a0ef2b0214b7dfe9412b7777f89add956da10ecR129
https://github.com/rui314/chibicc/blob/c6ff1d98a1419e69c31902447e2caa85af4e9844/main.c#L129
int main(int argc, char **argv) { if (argc != 2) { error("%s: invalid number of arguments", argv[0]); return 1; } user_input = argv[1]; token = tokenize(); printf(".intel_syntax noprefix\n"); printf(".global main\n"); printf("main:\n"); // The first token must be a number printf(" mov rax, %d\n", expect_number()); // ... followed by either `+ <number>` or `- <number>`. while (!at_eof()) { if (consume('+')) { printf(" add rax, %d\n", expect_number()); continue; } expect('-'); printf(" sub rax, %d\n", expect_number()); } printf(" ret\n"); return 0; }
コマンドライン引数の個数をチェックする(変更なし)
if (argc != 2) { error("%s: invalid number of arguments", argv[0]); return 1; }
トークナイズを行う
user_input = argv[1]; token = tokenize();
文字列の何バイト目でエラーが生じたかを計算するために、文字列の先頭アドレスをグローバル変数user_inputに格納してからトークナイズを行います。
https://github.com/rui314/chibicc/commit/c6ff1d98a1419e69c31902447e2caa85af4e9844#diff-a0cb465674c1b01a07d361f25a0ef2b0214b7dfe9412b7777f89add956da10ecR23
https://github.com/rui314/chibicc/blob/c6ff1d98a1419e69c31902447e2caa85af4e9844/main.c#L23
// Input program char *user_input;
部分的にアセンブリコードを生成する①(変更なし)
printf(".intel_syntax noprefix\n"); printf(".global main\n"); printf("main:\n");
部分的にアセンブリコードを生成する②(変更なし)
// The first token must be a number printf(" mov rax, %d\n", expect_number());
部分的にアセンブリコードを生成する③(変更なし)
// ... followed by either `+ <number>` or `- <number>`. while (!at_eof()) { if (consume('+')) { printf(" add rax, %d\n", expect_number()); continue; } expect('-'); printf(" sub rax, %d\n", expect_number()); }
部分的にアセンブリコードを生成する④(変更なし)
printf(" ret\n"); return 0;
error_at関数
https://github.com/rui314/chibicc/commit/c6ff1d98a1419e69c31902447e2caa85af4e9844#diff-a0cb465674c1b01a07d361f25a0ef2b0214b7dfe9412b7777f89add956da10ecR38
https://github.com/rui314/chibicc/blob/c6ff1d98a1419e69c31902447e2caa85af4e9844/main.c#L38
// Reports an error location and exit. void error_at(char *loc, char *fmt, ...) { va_list ap; va_start(ap, fmt); int pos = loc - user_input; fprintf(stderr, "%s\n", user_input); fprintf(stderr, "%*s", pos, ""); // print pos spaces. fprintf(stderr, "^ "); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); exit(1); }
error_at関数は、トークナイズ途中の文字を指定するアドレスloc、引数fmtで指定された文字列、可変部の引数を用いてメッセージを出力し、プログラム(プロセス)を終了させます。
va_list apについて
va_startマクロ・vprintf関数などを使用するために必要となるva_list型の変数apを宣言します。
va_start(ap, fmt)について
va_list型の変数apを初期化して可変個の引数を扱えるようにするために、va_startマクロを呼び出します。
第一引数には先ほど宣言したva_list型の変数apを、第二引数には可変部分の引数の直前にある引数fmtを指定する必要があります。
エラーが生じた文字の算出を計算する
int pos = loc - user_input;
トークナイズの対象となる文字列において、先頭の文字からエラーが生じた文字まで何文字分(何バイト分)あるかを算出します。
fprintf(stderr, "%s\n", user_input)について
fprintf関数は、第一引数のファイルポインタが指定するファイルに、第二引数のchar型へのポインタが指定する文字列データを書き込みます。
今回の場合は、標準エラー出力ファイルに、user_inputが指定する文字列が書き込ます(コンソール画面にトークナイズの対象となる文字列が表示されます)。
fprintf(stderr, "%*s", pos, "")について
fprintf関数を呼び出して、標準エラー出力ファイルに、空白文字をpos文字分書き込みます(コンソール画面に空白文字がpos文字分表示されます)。
書式指定フィールドのwidthフィールドに*が指定されている時は、引数posが指定する文字幅の分だけ表示します。
fprintf(stderr, "^ ")ついて
fprintf関数を呼び出して、標準エラー出力ファイルに ^ を書き込みます(コンソール画面に、トークナイズの対象となる文字列のうちエラーが生じた文字の下に ^ が表示されます)。
vfprintf(stderr, fmt, ap)について
vfprintf関数は、可変個の引数を扱うことができるfprintf関数です。
標準エラー出力ファイルに、fmtが指定する文字列データを書き込みます(コンソール画面に文字列が表示されます)。
fprintf(stderr, "\n")について
fprintf関数を呼び出して、標準エラー出力ファイルに、\nを書き込みます(コンソール画面で改行が行われます)。
exit(1)について
exitシステムコールを呼び出して、このプログラム(プロセス)の処理を終了させます。
引数で指定した1の値が終了ステータスになります。
tokenize関数
https://github.com/rui314/chibicc/commit/c6ff1d98a1419e69c31902447e2caa85af4e9844#diff-a0cb465674c1b01a07d361f25a0ef2b0214b7dfe9412b7777f89add956da10ecR116
https://github.com/rui314/chibicc/blob/c6ff1d98a1419e69c31902447e2caa85af4e9844/main.c#L116
// Tokenize `user_input` and returns new tokens. Token *tokenize() { char *p = user_input; Token head; head.next = NULL; Token *cur = &head; while (*p) { // Skip whitespace characters. if (isspace(*p)) { p++; continue; } // Punctuator if (*p == '+' || *p == '-') { cur = new_token(TK_RESERVED, cur, p++); continue; } // Integer literal if (isdigit(*p)) { cur = new_token(TK_NUM, cur, p); cur->val = strtol(p, &p, 10); continue; } error_at(p, "expected a number"); } new_token(TK_EOF, cur, p); return head.next; }
トークンからなる連結リストのヘッダーを作成する(変更なし)
Token head; head.next = NULL; Token *cur = &head;
空白文字の場合(変更なし)
if (isspace(*p)) { p++; continue; }
+ または - の場合(変更なし)
// Punctuator if (*p == '+' || *p == '-') { cur = new_token(TK_RESERVED, cur, p++); continue; }
数字の場合(変更なし)
// Integer literal if (isdigit(*p)) { cur = new_token(TK_NUM, cur, p); cur->val = strtol(p, &p, 10); continue; }
その他の場合
error_at(p, "expected a number");
上記のいずれにも該当しなかった場合は、error_at関数を呼び出して、メッセージを出力してからプログラムを終了させます。
連結リストの先頭トークンを戻り値としてリターンする(変更なし)
return head.next;
expect関数
https://github.com/rui314/chibicc/commit/c6ff1d98a1419e69c31902447e2caa85af4e9844#diff-a0cb465674c1b01a07d361f25a0ef2b0214b7dfe9412b7777f89add956da10ecR63
https://github.com/rui314/chibicc/blob/c6ff1d98a1419e69c31902447e2caa85af4e9844/main.c#L63
// Ensure that the current token is `op`. void expect(char op) { if (token->kind != TK_RESERVED || token->str[0] != op) error_at(token->str, "expected '%c'", op); token = token->next; }
expect_number関数
https://github.com/rui314/chibicc/commit/c6ff1d98a1419e69c31902447e2caa85af4e9844#diff-a0cb465674c1b01a07d361f25a0ef2b0214b7dfe9412b7777f89add956da10ecR70
https://github.com/rui314/chibicc/blob/c6ff1d98a1419e69c31902447e2caa85af4e9844/main.c#L70
int expect_number() { if (token->kind != TK_NUM) error_at(token->str, "expected a number"); int val = token->val; token = token->next; return val; }
テストコード
test.sh
https://github.com/rui314/chibicc/blob/c6ff1d98a1419e69c31902447e2caa85af4e9844/test.sh
#!/bin/bash assert() { expected="$1" input="$2" ./chibicc "$input" > tmp.s gcc -static -o tmp tmp.s ./tmp actual="$?" if [ "$actual" = "$expected" ]; then echo "$input => $actual" else echo "$input => $expected expected, but got $actual" exit 1 fi } assert 0 0 assert 42 42 assert 21 '5+20-4' assert 41 ' 12 + 34 - 5 ' echo OK
Makefile
https://github.com/rui314/chibicc/blob/c6ff1d98a1419e69c31902447e2caa85af4e9844/Makefile
CFLAGS=-std=c11 -g -static chibicc: main.o $(CC) -o $@ $? $(LDFLAGS) test: chibicc ./test.sh clean: rm -f chibicc *.o *~ tmp* .PHONY: test clean