• トップ
  • ブログ一覧
  • 【第3回】Brainfuckを実装しながら学ぶC++【実装してみよう!中編】
  • 【第3回】Brainfuckを実装しながら学ぶC++【実装してみよう!中編】

    広告メディア事業部広告メディア事業部
    2020.12.22

    IT技術

    今回で、「Brainfuckを実装しながら学ぶC++」シリーズの第3回目となりました。

    ちなみに前回は、今回に備えて、C/C++ の基礎を解説していきましたね!

    【第2回】Brainfuckを実装しながら学ぶC++【実装してみよう!前編】2020.12.15【第2回】Brainfuckを実装しながら学ぶC++【実装してみよう!前編】第2回~Brainfuckを実装しながら学ぶC++連載「Brainfuckを実装しながら学ぶC++」シリーズの第2回目...

    さて今回は、いよいよ本格的な BrainFuck の実装に入っていきます。

    やや長丁場になりますが、一緒に頑張っていきましょう!

    大枠の実装

    前回に引き続き、「brainfuck」プロジェクトを使用します。

    まずは、大枠を作っていきましょう!

    命令の定義

    連載の第1回目に解説したように、Brainfuck には、8つの命令が存在します。

    それらを定数として、まずは定義していきましょう。

    1#include <iostream>
    2using namespace std;
    3
    4// 命令をコンパイル時定数として定義
    5constexpr char INCREMENT =  '+';
    6constexpr char DECREMENT =  '-';
    7constexpr char RIGHT =  '>';
    8constexpr char LEFT =  '<';
    9constexpr char LOOP_START =  '[';
    10constexpr char LOOP_END =  ']';
    11constexpr char OUTPUT =  '.';
    12constexpr char INPUT =  ',';
    13
    14int main() {
    15    
    16    return 0;
    17}

    C++ 11 からサポートされる constexpr 修飾子は、以前まで使われていた #define INCREMENT '+' という、マクロ定義によるコンパイル時定数と同じ役割。

    これらはその名の通り、コンパイル時に評価され展開されるため、実行時間の短縮につながります。

    ちなみに C++11 以降でも、#define で定数定義している人は多くいますが、公式では constexpr の使用を推奨しています。

    各種変数の初期化

    次に、以下を初期化するコードを書いていきましょう。

    1. メモリのサイズ
    2. ポインタの初期位置や値
    3. Brainfuck コード読み取りのポインタ
    4. Brainfuck コードの長さ

    実際のコードは、次の通り。

    1int main() {
    2    
    3    unsigned char memory[MEMORY_SIZE];    //! 1Byte=8bit (0~255) で構成されるメモリを定義
    4    unsigned int ptr = 0;                 //! メモリポインタ (負の値は取らないのでunsigned)
    5    unsigned int code_ptr = 0;            //! Brainfuckコードのポインタ
    6    unsigned int code_len = 0;            //! Brainfuckコードの終端 (長さ)
    7
    8    // メモリの0初期化
    9    memset(memory, 0, sizeof(memory));
    10
    11    
    12    return 0;
    13}

    memset() は、C 言語時代からあるもので、配列要素を一括初期化できる関数です。

    ただ現在では、今回のような単純な配列の初期化などに、使われることが多くなりました。

    ファイルの読み込み

    今回作成しているインタプリタは、以下のような使い方を想定しています。

    1$ brainfuck nanikashirano_code.bf

    ですので、「コマンドラインからの引数受け取り処理」と「そのファイル読み込み機構」を、ここで実装していきたいと思います。

    コマンドライン引数の受け取り

    C++ では、標準でコマンドライン引数を処理する機能があるので、とても簡単!

    メイン関数に、以下のような引数を指定します。

    1int main(int argc, char* argv[]) {
    2    // ...
    3}

    argc は、自身の命令と、後続のコマンドライン引数を含んだ数値が格納されます。

    argv 配列には、それらの文字列が格納されます。

    例えば、$ brainfuck samplecode.bf と実行すれば、

    1> argc = 2
    2> argv[0] = "brainfuck"
    3> argv[1] =  "samplecode.bf"

    となります。

    念のため、引数がなかった場合のエラー処理も書いておきましょう!

    1int main(int argc, char* argv[]) {
    2
    3    /* 省略 */
    4    
    5    /* コマンドライン引数処理 */
    6    if (argc < 2){
    7        cerr << "Error: A Brainfuck code is not passed as a command-line argument." << endl;
    8        cerr << "Please pass an argument as the form, $ brainfuck [FILENAME]." << endl;
    9        return -1;
    10    }
    11}

    cerr は、エラー出力用の出力ストリームです。

    ファイル読み込み

    次に、コマンドライン引数で受け取ったファイル名を元に、ファイルを開く処理を実装しましょう。

    ファイルの扱いは、#include <fstream> で使用可能になります。

    ファイル読み込みは std::ifstream、書き込みは std::ofstream を使用します。

    今回は、読み込みだけなので、以下のようになります。

    1#include <iostream>
    2#include <fstream>  // 追加
    3using namespace std;
    4
    5/* 省略 */
    6
    7int main(int argc, char* argv[]) {
    8
    9    /* 省略 */
    10
    11    ifstream file(argv[1]);     //! ファイル読み込み
    12    if (!file){  // ファイル読みこみ失敗
    13        cerr << "Error: The file, " << argv[1] << ", cannot be opened." << endl;
    14        return -1;
    15    }
    16
    17    return 0;
    18}

    ファイル処理

    それでは、ファイルが上手く開けた場合の処理を考えます。

    ここで取得したいのは、ファイルの中身 (スペース・改行含む) と、コードの長さ。

    ファイル全体を一括で読み込みたいときは、std::stringstream を利用することで、簡単に実現できます。

    1#include <iostream>
    2#include <fstream>
    3#include <string>  //追加
    4#include <sstream> // 追加
    5using namespace std;
    6
    7/* 省略 */
    8
    9int main(int argc, char* argv[]) {
    10
    11    /* 省略 */
    12
    13    /* ファイルの中身を取得 */
    14    stringstream buffer;        //! stringstream用のバッファ
    15    buffer << file.rdbuf();     // ファイルをバッファとして取得
    16    string code(buffer.str());  //! codeをstringとして取得
    17    code_len = code.size();     //! コードサイズ
    18
    19    // デバッグ用
    20    cout << code_len << " Byte.\n" << code << endl;
    21
    22
    23    return 0;
    24}

    ここでビルドディレクトリに、以下のような Brainfuck コードを追加して、動作確認をしましょう。

    1+++++++++[->++++++++>+++++++++++>+++++<<<]>.>++.
    2+++++++..+++.>-.------------.<++++++++.--------.
    3+++.------.--------.>+.

    上のコードを、「hello.bf」として保存します。

    そして、

    1$ cd build
    2$ make
    3$ ./brainfuck hello.bf
    4121 Byte.
    5+++++++++[->++++++++>+++++++++++>+++++<<<]>.>++.
    6+++++++..+++.>-.------------.<++++++++.--------.
    7+++.------.--------.>+.

    このように表示されれば、ファイル読み込みが上手く動作しています。

    コード解析部の大枠作成

    次に、核となる Brainfuck コード解析部分の大枠を作成して、今回は終わりにします。

    基本は、命令を一つずつ読み込んでいくだけなので、以下のようなループが大枠です。

    1/* Brainfuck 解析 */
    2while (code_ptr < code_len){
    3    
    4    code_ptr++;
    5}

    この while 文の中身で、その命令によって処理を分けていくのですが、ここではそれを switch 文を使用して実装していきます。

    1/* Brainfuck 解析 */
    2while (code_ptr < code_len){
    3    switch (code[code_ptr]) {
    4        case INCREMENT:
    5            break;
    6        case DECREMENT:
    7            break;
    8        case RIGHT:
    9            break;
    10        case LEFT:
    11            break;
    12        case LOOP_START:
    13            break;
    14        case LOOP_END:
    15            break;
    16        case OUTPUT:
    17            break;
    18        case INPUT:
    19            break;
    20        default:  // 上記以外の命令はコメント扱い
    21            break;
    22    }
    23    code_ptr++;
    24}

    まだ、各ケースは実装していないので何も起こりませんが、次回からはこの部分を実装していきます。

    ここが完成すれば、Brainfuck インタプリタは出来上がりです!

    第4回目へつづく!

    今回は、インタプリタ実装に本格的に着手しました。

    ただ、まだ「読み込んだコードをそのまま出力するだけ」という、実用性のないコードになっていますね…。

    というわけで次回は、これを基に、どんどんコードを発展させていきましょう!

    具体的には、インタプリタの核を実装していきます。

    では、次回もお楽しみに!

    第4回はこちら!

    【第4回】Brainfuckを実装しながら学ぶC++【実装してみよう!後編】2020.12.22【第4回】Brainfuckを実装しながら学ぶC++【実装してみよう!後編】第4回~Brainfuckを実装しながら学ぶC++「Brainfuckを実装しながら学ぶC++」シリーズも、今回を含め...

    第1回目はこちら!

    brainfuckを実装しながら学ぶC++2020.12.08【第1回】Brainfuckを実装しながら学ぶC++【Brainfuckとは】第1回目~Brainfuckを実装しながらC++学ぼう本連載では、「Brainfuck」の特性を理解しながら、実際に ...

    こちらの記事もオススメ!

    featureImg2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...
    featureImg2020.07.27IT・コンピューターの歴史特集IT・コンピューターの歴史をまとめていきたいと思います!弊社ブログにある記事のみで構成しているため、まだ「未完成状態」...

    ライトコードでは、エンジニアを積極採用中!

    ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。

    採用情報へ

    広告メディア事業部

    広告メディア事業部

    おすすめ記事

    エンジニア大募集中!

    ライトコードでは、エンジニアを積極採用中です。

    特に、WEBエンジニアとモバイルエンジニアは是非ご応募お待ちしております!

    また、フリーランスエンジニア様も大募集中です。

    background