自作コンパイラ開発メモ(2020/11/08)

低レイヤを知りたい人のための C コンパイラ作成入門を読んでコンパイラ自作してる時の作業記録です。

対象箇所

if 文に続く else をパースし対応するアセンブリを出力できるようにしました。

実装

  • if というキーワードを解析しトークン化できるようにした。
  • if のノードを検知してアセンブリとして出力できるようにした。

メモ

トークナイズ

if (strncmp(p, "else", 4) == 0 && !is_alnum(p[4]) && cur->kind != TK_IDENT)
{
    cur = new_token(TK_ELSE, cur, p, 4);
    p += 4;
    continue;
}

上記のようなトークン化処理をキーワードごとに書くのは間違いの元になりそうなので下のようにキーワードをトークナイズする処理を抽象化する関数を作ってみたけどなぜか無限ループに入ってしまってた。printf()してみてわかった。

gdbデバッグしてみたがよくわからなかったのでいったん保留にした。

Token *tokenize_keyword(char *p, Token *cur, int kind)
{
    char *keyword;
    switch (kind)
    {
    case TK_RETURN:
        keyword = "return";
        break;
    case TK_IF:
        keyword = "if";
        break;
    case TK_ELSE:
        keyword = "else";
        break;
    case TK_WHILE:
        keyword = "while";
        break;
    case TK_FOR:
        keyword = "for";
        break;
    default:
        return NULL;
    }
    int len = strlen(keyword);
    if (strncmp(p, keyword, len) != 0 || is_alnum(p[len]) || cur->kind == TK_IDENT)
    {
        return NULL;
    }
    printf("%d\n", kind);
    Token *tok = new_token(kind, cur, p, len);
    p += len;
    return tok;
}

elseの文はnode->ethenに入れることにした。

アセンブリ出力

if 文のみだった実装から else に対応できるように改修した。

node が ethen を持っていたら else に対応するアセンブリを出力するようにする。

条件式が true の場合は else のブロックに入らないようにLendXXXラベルまで処理を飛ばす。

case ND_IF:
    gen(node->cond);
    printf("  pop rax\n");
    printf("  cmp rax, 0\n");
    if (node->ethen)
    {
        printf("  je .Lels%d\n", label_num);
        gen(node->then);
        printf("  jmp .Lend%d\n", label_num);
        printf(".Lels%d:\n", label_num);
        gen(node->ethen);
        printf(".Lend%d:\n", label_num++);
    }
    else
    {
        printf("  je .Lend%d\n", label_num);
        gen(node->then);
        printf(".Lend%d:\n", label_num++);
    }

jmp命令で指定のアドレスまで処理をジャンプすることができる。

ラベルはアドレスに付与できる識別子。

.Lで始まるラベルは自動的にファイルスコープになる。

ハマったところ

tokenize_keyword()が意図せず繰り返し実行される。