TechCraft – エンジニアのためのスキルアップメモ

エンジニアのスキルアップを少しでも加速する技術ブログ

Brainfuckコンパイラ自作シリーズ 第4回

Brainfuckコンパイラ自作シリーズ 第4回:Cコード生成と実行

はじめに

本記事では、BrainfuckコンパイラのCコード生成部分を実装し、BrainfuckコードをC言語に変換してコンパイル・実行するまでの流れを解説します。

前回までに 字句解析(Lexing)構文解析(Parsing) を実装しました。今回はその解析結果をもとに C言語ソースコードを生成し、コンパイルして実行する までをカバーします。


1. BrainfuckからCコードへの変換

Brainfuckの各命令をC言語に変換する方法を考えます。以下のように対応させるのが一般的です:

Brainfuck C言語
> ptr++;
< ptr--;
+ (*ptr)++;
- (*ptr)--;
. putchar(*ptr);
, *ptr = getchar();
[ while (*ptr) {
] }

この変換ルールを基に generator.cpp を実装します。


2. Cコード生成の実装

2.1 generator.h の作成

まず、Cコード生成を担当する generator.h を作成します。

#ifndef GENERATOR_H
#define GENERATOR_H

#include <string>

// BrainfuckコードをC言語コードに変換する関数
std::string generateCCode(const std::string& bfCode);

#endif // GENERATOR_H

2.2 generator.cpp の実装

次に、BrainfuckのコードをCコードへ変換するロジックを generator.cpp に実装します。

#include "generator.h"
#include <iostream>
#include <sstream>

std::string generateCCode(const std::string& bfCode) {
    std::ostringstream cCode;
    cCode << "#include <stdio.h>\n";
    cCode << "int main() {\n";
    cCode << "    unsigned char tape[30000] = {0};\n";
    cCode << "    unsigned char *ptr = tape;\n";

    for (char c : bfCode) {
        switch (c) {
            case '>': cCode << "    ptr++;\n"; break;
            case '<': cCode << "    ptr--;\n"; break;
            case '+': cCode << "    (*ptr)++;\n"; break;
            case '-': cCode << "    (*ptr)--;\n"; break;
            case '.': cCode << "    putchar(*ptr);\n"; break;
            case ',': cCode << "    *ptr = getchar();\n"; break;
            case '[': cCode << "    while (*ptr) {\n"; break;
            case ']': cCode << "    }\n"; break;
            default: break; // 無視する
        }
    }

    cCode << "    return 0;\n";
    cCode << "}\n";

    return cCode.str();
}

3. Cコードを生成しコンパイル・実行する

次に、メインプログラム main.cpp からこの generateCCode を呼び出し、C言語のファイルを作成してコンパイルします。

#include <iostream>
#include <fstream>
#include <cstdlib>
#include "generator.h"

int main(int argc, char* argv[]) {
    if (argc != 2) {
        std::cerr << "使い方: " << argv[0] << " <Brainfuckファイル>\n";
        return 1;
    }

    // Brainfuckファイルを読み込む
    std::ifstream bfFile(argv[1]);
    if (!bfFile) {
        std::cerr << "ファイルを開けません: " << argv[1] << "\n";
        return 1;
    }

    std::string bfCode((std::istreambuf_iterator<char>(bfFile)), std::istreambuf_iterator<char>());
    
    // Cコードを生成
    std::string cCode = generateCCode(bfCode);

    // C言語のファイルを書き出す
    std::ofstream cFile("output.c");
    cFile << cCode;
    cFile.close();

    // Cコードをコンパイル(GCCを使用)
    int result = system("gcc output.c -o output");
    if (result != 0) {
        std::cerr << "コンパイルに失敗しました\n";
        return 1;
    }

    std::cout << "コンパイル成功: 実行ファイル 'output' が作成されました。\n";
    std::cout << "実行するには './output' を実行してください。\n";

    return 0;
}

4. 動作確認

4.1 簡単な Brainfuck プログラムの用意

まず、Hello, World! を表示するBrainfuckプログラムを作成します。

hello.bf:

++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.

4.2 コンパイルと実行

ターミナルで以下のコマンドを実行します。

$ g++ main.cpp generator.cpp -o bf_compiler
$ ./bf_compiler hello.bf

成功すると、output.c が生成され、GCCコンパイルされます。

$ ./output
Hello, World!

5. まとめと次回予告

今回は、BrainfuckのコードをC言語に変換し、コンパイルして実行する流れを実装しました。
これにより、Brainfuckコンパイラの基本的な仕組みが完成しました。

次回は、最適化とエラー処理 に焦点を当て、以下の内容を実装します。

  • +++++++=6 に変換する最適化
  • 無駄な移動 (><<> など) の削除
  • 構文エラー([ の対応漏れ)のチェック
  • 実用的なBrainfuckプログラムのテスト

この最適化を加えることで、Brainfuckコンパイラをより実用的なものに仕上げていきます。


参考文献

次回もお楽しみに!