文字列操作 解答ページ | Programming Place Plus 新C++編

トップページ新C++編文字列操作

このページの概要

このページは、練習問題の解答例や解説のページです。



解答・解説

問題1 (基本★)

std::string から指定の文字列を探して、別の文字列に置き換える関数を作成してください。


文字列を置き換えるのは、std::string::replaceメンバ関数ですが、この関数は「指定した範囲」にある文字列を置き換えることしかできません(本編解説)。

replaceメンバ関数を使って指定の文字列を置き換えるには、まずその文字列がどの位置にあるかを知ることから始める必要があります。それには findメンバ関数を利用します(本編解説)。

たとえば次のようにして実現できます。

#include <iostream>
#include <string>

// 指定文字列を探して置き換える
//
// str:         対象の文字列
// search_str:  探す文字列
// replace_str: 置換文字列
// 戻り値:      str を返す
std::string& find_replace(std::string& str, const std::string& search_str, const std::string& replace_str)
{
    auto pos = str.find(search_str);
    if (pos == std::string::npos) {
        return str;
    }
    return str.replace(pos, search_str.length(), replace_str);
}

int main()
{
    std::string s {"abc???ghi"};
    std::cout << find_replace(s, "???", "def") << "\n";
    std::cout << find_replace(s, "???", "xxx") << "\n";
}

実行結果:

abcdefghi
abcdefghi

findメンバ関数は文字列の先頭から一致する文字列を探して、発見した位置を返します。発見できなかったときには std::string::npos を返してくるので、その場合には何もせずに return させています。発見できたなら、その位置から置換を行うようにすればいいです。

findメンバ関数で空文字列を探そうとした場合の結果は 0 になるので、結果が不自然に感じるかもしれません。

#include <iostream>
#include <string>

// 指定文字列を探して置き換える
//
// str:         対象の文字列
// search_str:  探す文字列
// replace_str: 置換文字列
// 戻り値:      str を返す
std::string& find_replace(std::string& str, const std::string& search_str, const std::string& replace_str)
{
    auto pos = str.find(search_str);
    if (pos == std::string::npos) {
        return str;
    }
    return str.replace(pos, search_str.length(), replace_str);
}

int main()
{
    std::string s {"abc???ghi"};
    std::cout << find_replace(s, "", "def") << "\n";
    std::cout << find_replace(s, "", "xxx") << "\n";
}

実行結果:

defabc???ghi
xxxdefabc???ghi

空文字列を置換するというイメージからいえば、何も置換されないほうが適切に思えます。そうであれば、空文字列が指定されたときは何もせずに return するといいでしょう。

#include <iostream>
#include <string>

// 指定文字列を探して置き換える
//
// str:         対象の文字列
// search_str:  探す文字列
// replace_str: 置換文字列
// 戻り値:      str を返す
std::string& find_replace(std::string& str, const std::string& search_str, const std::string& replace_str)
{
    if (search_str.empty()) {
        return str;
    }

    auto pos = str.find(search_str);
    if (pos == std::string::npos) {
        return str;
    }
    return str.replace(pos, search_str.length(), replace_str);
}

int main()
{
    std::string s {"abc???ghi"};
    std::cout << find_replace(s, "", "def") << "\n";
    std::cout << find_replace(s, "", "xxx") << "\n";
}

実行結果:

abc???ghi
abc???ghi

さて、この実装で目的は果たせているようにみえますが、対象の文字列に、指定の文字列が複数含まれている場合のことを考えられていません。対象の文字列に "???" を追加して試してみると、1回目の置換では2つ目の "???" が見過ごされていることが分かります。

#include <iostream>
#include <string>

// 指定文字列を探して置き換える
//
// str:         対象の文字列
// search_str:  探す文字列
// replace_str: 置換文字列
// 戻り値:      str を返す
std::string& find_replace(std::string& str, const std::string& search_str, const std::string& replace_str)
{
    if (search_str.empty()) {
        return str;
    }

    auto pos = str.find(search_str);
    if (pos == std::string::npos) {
        return str;
    }
    return str.replace(pos, search_str.length(), replace_str);
}

int main()
{
    std::string s {"abc???ghi???"};
    std::cout << find_replace(s, "???", "def") << "\n";
    std::cout << find_replace(s, "???", "xxx") << "\n";
}

実行結果:

abcdefghi???
abcdefghixxx

この結果で構わないというケースもあるかも知れませんが、すべての置換対象文字列をまとめて置換したいと思うなら、以下のように実装できます。

#include <iostream>
#include <string>

// 指定文字列をすべて探して置き換える
//
// str:         対象の文字列
// search_str:  探す文字列
// replace_str: 置換文字列
// 戻り値:      str を返す
std::string& find_replace_all(std::string& str, const std::string& search_str, const std::string& replace_str)
{
    if (search_str.empty()) {
        return str;
    }

    std::string::size_type pos {};
    while ((pos = str.find(search_str)) != std::string::npos) {
        str.replace(pos, search_str.length(), replace_str);
        pos += search_str.length();
    }

    return str;
}

int main()
{
    std::string s {"abc???ghi???"};
    std::cout << find_replace_all(s, "???", "def") << "\n";
    std::cout << find_replace_all(s, "???", "xxx") << "\n";
}

実行結果:

abcdefghidef
abcdefghidef

問題2 (応用★★)

ペイントスクリプトに、マクロの機能を実装してください。たとえば、色の指定を “255 0 0” と入力する代わりに “RED” と入力できるようにします。ここでは以下の事前に定義されたマクロだけを使えるものします。

マクロ 置換結果
RED 255 0 0
GREEN 0 255 0
BLUE 0 0 255
WHITE 255 255 255
BLACK 0 0 0


マクロによる置換は、要するに文字列の置換に過ぎません。結局、問題① でやったことと同じことができればいいのですが、ペイントスクリプトの中で使うには、どこで置換処理を行うのが適切であるかを考える必要があります。

コマンドの解釈・実行よりも前に置換済みでなければならず、当たり前ですがコマンドとパラメータの入力後でなければなりません。std::vector<std::string> の形に直されてしまうと、"RED""255""0""0" のように再分解するのは難しいので、main関数内のこの位置しかないでしょう。

        std::string input_string {};
        std::getline(std::cin, input_string);
        
        // マクロ置換を行う

        // 入力内容を空白文字ごとに分割して、std::vector に格納する
        const auto command_vec = str_util::split(input_string, " ");
        if (command_vec.empty()) {
            continue;
        }
        
        // 該当するコマンドを探して実行
        if (executor.exec(command_vec) == paint_script::CommandExecutor::ExecResult::exit_program) {
            break;
        }

問題① で作った find_replace_all関数を使うので、ペイントスクリプトのプログラムに追加しましょう。これはペイントスクリプトに固有というより、もっと汎用的に使えるものなので、string_util.cpp/.h に追加します(ここは paint_script名前空間ではなく、str_util名前空間になっています)。

// string_util.cpp
#include "string_util.h"

namespace str_util {

    // 文字列を分割する
    std::vector<std::string> split(const std::string& str, const std::string& delim)
    {
        const auto delimLen = delim.size();
        if (delimLen == 0) {
            return {str};
        }

        std::string::size_type current {0};
        std::string::size_type found {};
        std::vector<std::string> result {};

        // 区切り文字を探しながら、その位置の手前までの文字列を追加することを繰り返す
        while ((found = str.find(delim, current)) != std::string::npos) {
            result.push_back(str.substr(current, found - current));
            current = found + delimLen;
        }

        // 残った部分を追加
        result.push_back(str.substr(current, str.size() - current));

        return result;
    }

    // 指定文字列をすべて探して置き換える
    std::string& find_replace_all(std::string& str, const std::string& search_str, const std::string& replace_str)
    {
        if (search_str.empty()) {
            return str;
        }

        std::string::size_type pos {};
        while ((pos = str.find(search_str)) != std::string::npos) {
            str.replace(pos, search_str.length(), replace_str);
            pos += search_str.length();
        }

        return str;
    }
}
// string_util.h
#ifndef STRING_UTIL_H_INCLUDED
#define STRING_UTIL_H_INCLUDED

#include <string>
#include <vector>


namespace str_util {

    // 文字列を分割する
    //
    // str:   対象の文字列
    // delim: 区切り文字列
    // 戻り値: str を delim で区切った部分文字列を格納した vector
    std::vector<std::string> split(const std::string& str, const std::string& delim);

    // 指定文字列をすべて探して置き換える
    //
    // str:         対象の文字列
    // search_str:  探す文字列
    // replace_str: 置換文字列
    // 戻り値:      str を返す
    std::string& find_replace_all(std::string& str, const std::string& search_str, const std::string& replace_str);

}

#endif

実際のマクロ置換のコードを書くには、どんなマクロが存在するのかが分かっていないといけません。用意されているマクロはペイントスクリプトに固有のものですから、これは paint_script名前空間側のコードに入っているのが適切です。CommandExecutorクラスに入れることも考えられますが、「コマンドを実行する」ことが仕事のクラスなので、役割を盛りすぎているといえるでしょう。

マクロ処理用のクラスを追加することにします。マクロの一覧表と、ヘルプメッセージの出力も作成しておきます。

// macro.cpp
#include "macro.h"
#include <iostream>
#include "string_util.h"

namespace paint_script {

    const std::pair<std::string, std::string> Macro::MacroTable[]{
        {"RED", "255 0 0"},
        {"GREEN", "0 255 0"},
        {"BLUE", "0 0 255"},
        {"WHITE", "255 255 255"},
        {"BLACK", "0 0 0"},
    };



    // マクロ置換を行う
    std::string& Macro::replace(std::string& str)
    {
        for (const auto& macro : MacroTable) {
            str_util::find_replace_all(str, macro.first, macro.second);
        }

        return str;
    }

    // マクロの一覧を出力する
    void Macro::print_help()
    {
        std::cout << "以下のマクロがあります。\n"
                  << std::endl;

        for (const auto& macro : MacroTable) {
            std::cout << macro.first << "\n"
                      << "置換結果: " << macro.second << "\n"
                      << std::endl;
        }
    }
}
// macro.h
#ifndef MACRO_H_INCLUDED
#define MACRO_H_INCLUDED

#include <string>
#include <utility>

namespace paint_script {

    class Macro {
    public:
        Macro() = delete;

        // マクロ置換を行う
        //
        // str: 対象の文字列
        // 戻り値: str を返す
        static std::string& replace(std::string& str);

        // マクロの一覧を出力する
        static void print_help();

    private:
        static const std::pair<std::string, std::string> MacroTable[];
    };

}

#endif

ヘルプメッセージは、helpコマンドを実行するときに呼び出せばいいでしょう。

    void CommandExecutor::print_help() const
    {
        std::cout << "以下のコマンドがあります。\n"
                  << "対応するパラメータがある場合は、その順番どおりに、正しい値をスペースで区切って入力してください。\n"
                  << std::endl;

        for (const auto& data : CommandMap) {
            data.help(this);
        }

        Macro::print_help();
    }

main関数のところはこうなります。

//main.cpp
#include <iostream>
#include "canvas.h"
#include "command_executor.h"
#include "macro.h"
#include "string_util.h"

int main()
{
    std::cout << "コマンドを入力してください。\n"
              << "help と入力すると、コマンドの一覧を表示します。\n"
              << "exit と入力すると、プログラムを終了します。\n";

    paint_script::Canvas canvas {};
    paint_script::CommandExecutor executor {canvas};

    while (true) {
        std::string input_string {};
        std::getline(std::cin, input_string);

        // マクロを置換する
        paint_script::Macro::replace(input_string);
        
        // 空白文字ごとに分割して、std::vector に格納する
        const auto command_vec = str_util::split(input_string, " ");
        if (command_vec.empty()) {
            continue;
        }
        
        // 該当するコマンドを探して実行
        if (executor.exec(command_vec) == paint_script::CommandExecutor::ExecResult::exit_program) {
            break;
        }
    }
}

これで、以下のような入力が可能になります。

fill GREEN
pen RED
rect 10 10 30 30
pen BLUE
rect 50 50 70 70
save test.bmp
exit

問題3 (応用★★)

ペイントスクリプトに、これまで入力したコマンドとパラメータの履歴をテキストファイルに書き出す save_scriptコマンドを実装してください。


ユーザーが入力を行うたびに、その入力内容を std::vector<std::string> に蓄えておけばいいでしょう。save_scriptコマンドが実行されるときに、その中身をテキストファイルに書き出します。

入力内容を蓄えるために、CommandHistoryクラスを追加します。

// command_history.cpp
#include "command_history.h"
#include <fstream>

namespace paint_script {

    // 履歴を追加する
    void CommandHistory::add(const std::string& command)
    {
        m_commands.push_back(command);
    }

    // 履歴をテキストファイルへ書き出す
    bool CommandHistory::save_to_file(const std::string& path)
    {
        std::ofstream ofs {path, std::ios_base::out};
        if (!ofs) {
            return false;
        }

        for (const std::string& str : m_commands) {
            ofs << str << "\n";
        }

        return true;
    }
}
// command_history.h
#ifndef COMMAND_HISTORY_H_INCLUDED
#define COMMAND_HISTORY_H_INCLUDED

#include <string>
#include <vector>

namespace paint_script {

    class CommandHistory {
    public:
        // 履歴を追加する
        //
        // command: 追加するコマンドおよびパラメータの文字列
        void add(const std::string& command);

        // 履歴をテキストファイルへ書き出す
        //
        // path: 出力先のテキストファイルのパス
        // 戻り値: 成否
        bool save_to_file(const std::string& path);

    private:
        std::vector<std::string>    m_commands;
    };

}

#endif

main関数のところから入力内容を渡します。マクロ置換を行う前の文字列を渡さないと、入力履歴を記憶していることになりません。また、save_scriptコマンドの実行は CommandExecutorクラスのメンバ関数内で行われるので、CommandHistoryクラスのオブジェクトにアクセスできるように、CommandExecutorクラスのコンストラクタから CommandHistory の参照を渡すようにします。

//main.cpp
#include <iostream>
#include "canvas.h"
#include "command_executor.h"
#include "command_history.h"
#include "macro.h"
#include "string_util.h"

int main()
{
    std::cout << "コマンドを入力してください。\n"
              << "help と入力すると、コマンドの一覧を表示します。\n"
              << "exit と入力すると、プログラムを終了します。\n";

    paint_script::Canvas canvas {};
    paint_script::CommandHistory history {};
    paint_script::CommandExecutor executor {canvas, history};

    while (true) {
        std::string input_string {};
        std::getline(std::cin, input_string);

        // 入力履歴を保存
        history.add(input_string);

        // マクロを置換する
        paint_script::Macro::replace(input_string);
        
        // 空白文字ごとに分割して、std::vector に格納する
        const auto command_vec = str_util::split(input_string, " ");
        if (command_vec.empty()) {
            continue;
        }
        
        // 該当するコマンドを探して実行
        if (executor.exec(command_vec) == paint_script::CommandExecutor::ExecResult::exit_program) {
            break;
        }
    }
}
// command_executor.cpp(追加箇所のみ)

    const CommandExecutor::CommandData CommandExecutor::CommandMap[] {
        {"help", &help, &print_help_help},
        {"exit", &exit, &print_help_exit},
        {"resize", &resize, &print_help_resize},
        {"fill", &fill, &print_help_fill},
        {"pen", &pen, &print_help_pen},
        {"dot", &dot, &print_help_dot},
        {"rect", &rect, &print_help_rect},
        {"filled_rect", &filled_rect, &print_help_filled_rect},
        {"load", &load, &print_help_load},
        {"save", &save, &print_help_save},
        {"save_script", &save_script, &print_help_save_script},
    };



    CommandExecutor::CommandExecutor(Canvas& canvas, CommandHistory& history) :
        m_canvas {canvas}, m_history {history}
    {

    }

    // スクリプトを保存する
    CommandExecutor::ExecResult CommandExecutor::save_script(const command_params_t& cmd_vec)
    {
        if (cmd_vec.size() < 2) {
            std::cout << "save_script コマンドには1つのパラメータが必要です。\n";
            print_help_save();
            return ExecResult::failed;
        }

        const std::string path {cmd_vec.at(1)};

        if (!m_history.save_to_file(path)) {
            std::cout << "path " << "への保存に失敗しました。\n";
            return ExecResult::failed;
        }

        return ExecResult::successd;
    }

    void CommandExecutor::print_help_save_script() const
    {
        std::cout << "save_script path\n"
                  << "現在までに入力したコマンドとパラメータの履歴をテキストファイルに保存します。\n"
                  << "  path: 出力するテキストファイルのパス。すでに存在する場合は上書きします。\n"
                  << std::endl;
    }
// command_executor.h(追加箇所のみ)

    class CommandExecutor {
    public:
        // コンストラクタ
        //
        // canvas:  キャンバスの参照
        // history: 履歴管理オブジェクトの参照
        explicit CommandExecutor(Canvas& canvas, CommandHistory& history);

    private:
        ExecResult save_script(const command_params_t& cmd_vec);

        void print_help_save_script() const;

    private:
        CommandHistory&         m_history;
    };

実行後、次のように入力したとすると、

fill GREEN
pen RED
rect 10 10 30 30
pen BLUE
rect 50 50 70 70
save test.bmp
save_script command.txt
exit

生成された command.txt の中身は以下のようになります。

fill GREEN
pen RED
rect 10 10 30 30
pen BLUE
rect 50 50 70 70
save test.bmp
save_script command.txt

問題4 (発展★★★)

ペイントスクリプトに load_scriptコマンドを実装してください。load_scriptコマンドは、load_script script.txt のように、パラメータでファイルのパスを指定し、そのファイルに書かれているスクリプトを実行します。そのファイルは手動で作ったものでも、問題3で作成した save_scriptコマンドで書き出したテキストファイルでも受け付けられるようにしてください。


問題3で履歴情報を書き出すために作った CommandHistoryクラスに、書き出したファイルを読み込むメンバ関数を追加します。

// command_history.cpp

namespace paint_script {

    // 最初の履歴を指すイテレータを返す
    std::vector<std::string>::const_iterator CommandHistory::begin() const
    {
        return std::cbegin(m_commands);
    }

    // 最後の履歴の後ろを指すイテレータを返す
    std::vector<std::string>::const_iterator CommandHistory::end() const
    {
        return std::cend(m_commands);
    }

    // テキストファイルから履歴を読み込む
    std::vector<std::string>::const_iterator CommandHistory::load_from_file(const std::string& path)
    {
        std::ifstream ifs {path};
        if (!ifs) {
            return end();
        }

        // 追加前の一番後ろのインデックスを保存
        // [!] このあと std::vector への追加を行うと、イテレータや参照、ポインタは無効化される可能性があるので、
        //     添字で保存しておく。
        auto start_index = m_commands.size();

        // 1行ずつ読み込みながら、履歴に追加
        while (!ifs.eof()) {
            std::string s {};
            std::getline(ifs, s);
            add(s);
        }

        return std::begin(m_commands) + start_index;
    }
}
// command_history.h
namespace paint_script {

    class CommandHistory {
    public:
        // 最初の履歴を指すイテレータを返す
        std::vector<std::string>::const_iterator begin() const;

        // 最後の履歴の後ろを指すイテレータを返す
        std::vector<std::string>::const_iterator end() const;


        // テキストファイルから履歴を読み込む
        //
        // path: テキストファイルのパス
        // 戻り値: 読み込まれた最初の履歴を指すイテレータ。
        //         読み込みに失敗した場合は、end() が返すイテレータ。
        std::vector<std::string>::const_iterator load_from_file(const std::string& path);
    };

}

書き出す処理を実装した save_to_fileメンバ関数に対応するかたちで作ればいいと思うかもしれませんが、読み込まれた履歴は、CommandExecutorクラスが辿りながら実行できなければいけません。また、ユーザーが load_scriptコマンドを入力する前に、すでにほかのコマンドが入力・実行済みかもしれませんから、CommandHistoryクラスが保持している「これまでの履歴」を上書きしてしまってもいけません。

そこで、戻り値でイテレータを返すようにして、読み込まれた履歴のうち、最初のものを指し示すようにします。指定されたファイルパスにファイルが存在しないなどして、読み込みに失敗したときは、endメンバ関数が返すイテレータを返しておきます(C++ の標準ライブラリがよく使っている手段です)。そのため、endメンバ関数も実装しておきます。なお、外部で履歴を改竄できないように、通常のイテレータではなく、constイテレータを返すほうが適切といえるでしょう。

【上級】beginメンバ関数もセットで作っておくと、範囲for文が使えるようになります。1

CommandExecutorクラスに、CommandHistory::save_to_fileメンバ関数を呼び出すコードを追加します。

// command_executor.cpp

namespace paint_script {

    const CommandExecutor::CommandData CommandExecutor::CommandMap[] {
        {"help", &help, &print_help_help},
        {"exit", &exit, &print_help_exit},
        {"resize", &resize, &print_help_resize},
        {"fill", &fill, &print_help_fill},
        {"pen", &pen, &print_help_pen},
        {"dot", &dot, &print_help_dot},
        {"rect", &rect, &print_help_rect},
        {"filled_rect", &filled_rect, &print_help_filled_rect},
        {"load", &load, &print_help_load},
        {"save", &save, &print_help_save},
        {"load_script", &load_script, &print_help_load_script},
        {"save_script", &save_script, &print_help_save_script},
    };

    CommandExecutor::CommandExecutor(Canvas& canvas, CommandHistory& history) :
        m_canvas {canvas}, m_history {history},
        m_is_load_script {false}
    {

    }

    // スクリプトが保存されたファイルから実行する
    CommandExecutor::ExecResult CommandExecutor::load_script(const command_params_t& cmd_vec)
    {
        if (m_is_load_script) {
            return ExecResult::successd;
        }

        if (cmd_vec.size() < 2) {
            std::cout << "load_script コマンドには1つのパラメータが必要です。\n";
            print_help_load_script();
            return ExecResult::failed;
        }

        const std::string path {cmd_vec.at(1)};

        // スクリプトを読み込んで、履歴情報として復元する
        auto it = m_history.load_from_file(path);
        if (it == m_history.end()) {
            std::cout << path << " の読み込みに失敗しました。\n";
            return ExecResult::failed;
        }


        auto result = ExecResult::successd;

        // スクリプトのロード実行中であることを表すフラグを立てる
        m_is_load_script = true;

        // 履歴情報を辿りながら、1つずつ実行していく
        for ( /**/; it != m_history.end(); ++it) {
            std::string cmd {*it};

            // マクロを置換する
            paint_script::Macro::replace(cmd);

            // 空白文字ごとに分割して、std::vector に格納する
            const auto command_vec = str_util::split(cmd, " ");
            if (command_vec.empty()) {
                continue;
            }

            // 実行。途中でエラーが起きたら、以降の実行は止める
            auto exec_result = exec(command_vec);
            if (exec_result == ExecResult::failed || exec_result == ExecResult::exit_program) {
                result = exec_result;
                break;
            }
        }

        m_is_load_script = false;
        return result;
    }

    void CommandExecutor::print_help_load_script() const
    {
        std::cout << "load_script path\n"
                  << "スクリプトが書き込まれたテキストファイルを読み込んで、その内容を実行します。\n"
                  << "  path: テキストファイルのパス。\n"
                  << std::endl;
    }
}
// command_executor.h
namespace paint_script {

    class CommandExecutor {
    private:
        ExecResult load_script(const command_params_t& cmd_vec);

        void print_help_load_script() const;

        bool                    m_is_load_script;
    };

}

load_scriptコマンドが入力されると、load_scriptメンバ関数のところにやってきます。さきほど実装した CommandHistory::load_from_fileメンバ関数を呼び出して、読み込まれた履歴情報の先頭を指すイテレータを受け取ります。イテレータが末尾に達するまでループさせながら、1つずつ実行処理に回していきます。マクロ置換、std::vector<std::string> への変換、そして execメンバ関数の呼び出しといったように、通常の実行と同じ流れを踏みます。

基本的な考え方は以上ですが、普通にすべて実行に回してしまうと、たとえば読み込んだスクリプトファイルの中に load_scriptコマンドがあると、無限ループに陥る問題があります。そこで、スクリプトファイルからの実行中には、データメンバm_is_load_script を true にしておくようにして、true になっているあいだは、load_scriptメンバ関数は冒頭部分ですぐに return させるようにしています。

また、そもそも load_scriptコマンドが読み込むテキストファイルを作り出すコマンドが save_scriptコマンドなので、save_scriptコマンドも m_is_load_script が true のときは何もしなくていいでしょう。


これで load_scriptコマンドが動作するようになりました。ここまでのすべての変更を行ったペイントスクリプトの全体像を掲載します。

//main.cpp
#include <iostream>
#include "canvas.h"
#include "command_executor.h"
#include "command_history.h"
#include "macro.h"
#include "string_util.h"

int main()
{
    std::cout << "コマンドを入力してください。\n"
              << "help と入力すると、コマンドの一覧を表示します。\n"
              << "exit と入力すると、プログラムを終了します。\n";

    paint_script::Canvas canvas {};
    paint_script::CommandHistory history {};
    paint_script::CommandExecutor executor {canvas, history};

    while (true) {
        std::string input_string {};
        std::getline(std::cin, input_string);

        // 入力履歴を保存
        history.add(input_string);

        // マクロを置換する
        paint_script::Macro::replace(input_string);
        
        // 空白文字ごとに分割して、std::vector に格納する
        const auto command_vec = str_util::split(input_string, " ");
        if (command_vec.empty()) {
            continue;
        }
        
        // 該当するコマンドを探して実行
        if (executor.exec(command_vec) == paint_script::CommandExecutor::ExecResult::exit_program) {
            break;
        }
    }
}
// string_util.cpp
#include "string_util.h"

namespace str_util {

    // 文字列を分割する
    std::vector<std::string> split(const std::string& str, const std::string& delim)
    {
        const auto delimLen = delim.size();
        if (delimLen == 0) {
            return {str};
        }

        std::string::size_type current {0};
        std::string::size_type found {};
        std::vector<std::string> result {};

        // 区切り文字を探しながら、その位置の手前までの文字列を追加することを繰り返す
        while ((found = str.find(delim, current)) != std::string::npos) {
            result.push_back(str.substr(current, found - current));
            current = found + delimLen;
        }

        // 残った部分を追加
        result.push_back(str.substr(current, str.size() - current));

        return result;
    }

    // 指定文字列をすべて探して置き換える
    std::string& find_replace_all(std::string& str, const std::string& search_str, const std::string& replace_str)
    {
        if (search_str.empty()) {
            return str;
        }

        std::string::size_type pos {};
        while ((pos = str.find(search_str)) != std::string::npos) {
            str.replace(pos, search_str.length(), replace_str);
            pos += search_str.length();
        }

        return str;
    }
}
// string_util.h
#ifndef STRING_UTIL_H_INCLUDED
#define STRING_UTIL_H_INCLUDED

#include <string>
#include <vector>


namespace str_util {

    // 文字列を分割する
    //
    // str:   対象の文字列
    // delim: 区切り文字列
    // 戻り値: str を delim で区切った部分文字列を格納した vector
    std::vector<std::string> split(const std::string& str, const std::string& delim);

    // 指定文字列をすべて探して置き換える
    //
    // str:         対象の文字列
    // search_str:  探す文字列
    // replace_str: 置換文字列
    // 戻り値:      str を返す
    std::string& find_replace_all(std::string& str, const std::string& search_str, const std::string& replace_str);

}

#endif
// canvas.cpp
#include "canvas.h"
#include "bmp.h"
#include "pen.h"
#include <algorithm>
#include <cassert>
#include <cstdlib>

namespace paint_script {

    Canvas::Canvas(unsigned int width, unsigned int height, Color color) :
        m_pixels {},
        m_width {0},
        m_height {0}
    {
        resize(width, height, color);
    }

    void Canvas::resize(unsigned int width, unsigned int height, Color color)
    {
        assert(1 <= width);
        assert(1 <= height);

        m_pixels.resize(height);
        for (auto& row : m_pixels) {
            row.resize(width);
        }

        m_width = width;
        m_height = height;

        fill(color);
    }

    void Canvas::fill(Color color)
    {
        for (auto& row : m_pixels) {
            std::fill(std::begin(row), std::end(row), color);
        }
    }

    void Canvas::paint_dot(int x, int y, Pen& pen)
    {
        if (!is_inside(x, y)) {
            return;
        }

        m_pixels[y][x] = pen.get_color();
    }

    // 矩形を描画する
    void Canvas::paint_rect(int left, int top, int right, int bottom, Pen& pen)
    {
        // 上辺
        for (int x {left}; x <= right; ++x) {
            paint_dot(x, top, pen);
        }

        // 左辺
        for (int y {top + 1}; y < bottom; ++y) {
            paint_dot(left, y, pen);
        }

        // 右辺
        for (int y {top + 1}; y < bottom; ++y) {
            paint_dot(right, y, pen);
        }

        // 下辺
        for (int x {left}; x <= right; ++x) {
            paint_dot(x, bottom, pen);
        }
    }

    // 内側を塗りつぶした矩形を描画する
    void Canvas::paint_filled_rect(int left, int top, int right, int bottom, Pen& pen)
    {
        for (int y {top}; y <= bottom; ++y) {
            for (int x {left}; x <= right; ++x) {
                paint_dot(x, y, pen);
            }
        }
    }

    bool Canvas::load_from_bitmap_file(const std::string& path)
    {
        if (!Bmp::load(path, &m_width, &m_height, &m_pixels)) {
            return false;
        }

        return true;
    }

    bool Canvas::save_to_bitmap_file(const std::string& path)
    {
        return Bmp::save(path, m_width, m_height, m_pixels);
    }

    bool Canvas::is_inside(int x, int y) const
    {
        if (x < 0 || static_cast<int>(m_width) <= x) {
            return false;
        }
        if (y < 0 || static_cast<int>(m_height) <= y) {
            return false;
        }
        return true;
    }

}
// canvas.h
#ifndef CANVAS_H_INCLUDED
#define CANVAS_H_INCLUDED

#include <string>
#include <vector>
#include "color.h"

namespace paint_script {

    class Pen;

    class Canvas {
    public:
        static constexpr unsigned int default_width {320};
        static constexpr unsigned int default_height {240};

    public:
        // コンストラクタ
        //
        // width: 横方向のピクセル数。省略時は default_width
        // height: 縦方向のピクセル数。省略時は default_height
        // color: 初期状態の色。省略時は白
        Canvas(unsigned int width = default_width, unsigned int height = default_height, Color color = {255, 255, 255});

    public:
        // キャンバスの大きさを変更する
        // 
        // これまでのキャンバスに描かれていた内容は失われ、
        // color の色で塗りつぶされる。
        //
        // width: 横方向のピクセル数 (1~WidthMax)
        // height: 縦方向のピクセル数 (1~HeightMax)
        // color: 色。省略時は白
        void resize(unsigned int width, unsigned int height, Color color = {255, 255, 255});


        // 全面を塗りつぶす
        //
        // color: 色
        void fill(Color color);


        // 点を描画する
        //
        // x: X座標
        // y: Y座標
        // pen: ペン
        void paint_dot(int x, int y, Pen& pen);

        // 矩形を描画する
        //
        // left: 左端X座標
        // top: 上端Y座標
        // right: 右端X座標
        // bottom: 下端Y座標
        // pen: ペン
        void paint_rect(int left, int top, int right, int bottom, Pen& pen);

        // 内側を塗りつぶした矩形を描画する
        //
        // left: 左端X座標
        // top: 上端Y座標
        // right: 右端X座標
        // bottom: 下端Y座標
        // pen: ペン
        void paint_filled_rect(int left, int top, int right, int bottom, Pen& pen);


        // ビットマップファイルから読み込む
        //
        // path: ビットマップファイルのパス
        // 戻り値: 成否
        bool load_from_bitmap_file(const std::string& path);

        // ビットマップファイルとして書き出す
        //
        // path: 出力先のビットマップファイルのパス
        // 戻り値: 成否
        bool save_to_bitmap_file(const std::string& path);



        // 横方向のピクセル数を返す
        //
        // 戻り値: 横方向のピクセル数
        inline unsigned int get_width() const
        {
            return m_width;
        }
        
        // 縦方向のピクセル数を返す
        //
        // 戻り値: 縦方向のピクセル数
        inline unsigned int get_height() const
        {
            return m_height;
        }

        // 座標がキャンバスの範囲内かどうか調べる
        //
        // x: X座標
        // y: Y座標
        // 戻り値: キャンバス内の座標なら true。そうでなければ false
        bool is_inside(int x, int y) const;

    private:
        std::vector<std::vector<Color>>     m_pixels;
        unsigned int                        m_width;
        unsigned int                        m_height;
    };

}

#endif
// command_executor.cpp
#include "command_executor.h"
#include "canvas.h"
#include "command_history.h"
#include "macro.h"
#include "string_util.h"
#include <algorithm>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <iterator>


namespace paint_script {

    const CommandExecutor::CommandData CommandExecutor::CommandMap[] {
        {"help", &help, &print_help_help},
        {"exit", &exit, &print_help_exit},
        {"resize", &resize, &print_help_resize},
        {"fill", &fill, &print_help_fill},
        {"pen", &pen, &print_help_pen},
        {"dot", &dot, &print_help_dot},
        {"rect", &rect, &print_help_rect},
        {"filled_rect", &filled_rect, &print_help_filled_rect},
        {"load", &load, &print_help_load},
        {"save", &save, &print_help_save},
        {"load_script", &load_script, &print_help_load_script},
        {"save_script", &save_script, &print_help_save_script},
    };



    CommandExecutor::CommandExecutor(Canvas& canvas, CommandHistory& history) :
        m_canvas {canvas}, m_history {history},
        m_is_load_script {false}
    {

    }

    CommandExecutor::ExecResult CommandExecutor::exec(const command_params_t& command_vec)
    {
        const std::string command_name {command_vec.at(0)};

        // コマンドを探す
        const auto command_it = std::find_if(
            std::cbegin(CommandMap),
            std::cend(CommandMap),
            [command_name](const CommandData& data) { return data.name == command_name; });
        if (command_it == std::cend(CommandMap)) {
            return ExecResult::not_found;
        }
        
        // 実行
        try {
            return command_it->impl(this, command_vec);
        }
        catch (const std::invalid_argument&) {
            std::cout << "入力に間違いがあります。\n";
        }
        catch (const std::out_of_range&) {
            std::cout << "有効範囲を越えた入力があります。\n";
        }
        return ExecResult::failed;
    }


    // ヘルプ
    CommandExecutor::ExecResult CommandExecutor::help(const command_params_t&)
    {
        print_help();
        return ExecResult::successd;
    }

    // 終了
    CommandExecutor::ExecResult CommandExecutor::exit(const command_params_t&)
    {
        return ExecResult::exit_program;
    }

    // キャンバスの大きさを変更する
    CommandExecutor::ExecResult CommandExecutor::resize(const command_params_t& cmd_vec)
    {
        if (cmd_vec.size() < 3) {
            std::cout << "resize コマンドには2つのパラメータが必要です。\n";
            print_help_resize();
            return ExecResult::failed;
        }

        const int width {std::stoi(cmd_vec.at(1))};
        if (width < 1) {
            std::cout << "横方向のピクセル数は 1 以上でなければなりません。\n";
            print_help_resize();
            return ExecResult::failed;
        }

        const int height {std::stoi(cmd_vec.at(2))};
        if (height < 1) {
            std::cout << "縦方向のピクセル数は 1 以上でなければなりません。\n";
            print_help_resize();
            return ExecResult::failed;
        }

        m_canvas.resize(static_cast<unsigned int>(width), static_cast<unsigned int>(height));
        return ExecResult::successd;
    }
        
    // キャンバスを塗りつぶす
    CommandExecutor::ExecResult CommandExecutor::fill(const command_params_t& cmd_vec)
    {
        if (cmd_vec.size() < 4) {
            std::cout << "fill コマンドには3つのパラメータが必要です。\n";
            print_help_fill();
            return ExecResult::failed;
        }

        const int red {std::stoi(cmd_vec.at(1))};
        if (red < 0 || 255 < red) {
            std::cout << "赤成分の強さは 0 から 255 の範囲でなければなりません。\n";
            print_help_fill();
            return ExecResult::failed;
        }

        const int green {std::stoi(cmd_vec.at(2))};
        if (green < 0 || 255 < green) {
            std::cout << "緑成分の強さは 0 から 255 の範囲でなければなりません。\n";
            print_help_fill();
            return ExecResult::failed;
        }

        const int blue {std::stoi(cmd_vec.at(3))};
        if (blue < 0 || 255 < blue) {
            std::cout << "青成分の強さは 0 から 255 の範囲でなければなりません。\n";
            print_help_fill();
            return ExecResult::failed;
        }

        m_canvas.fill({static_cast<unsigned char>(red), static_cast<unsigned char>(green), static_cast<unsigned char>(blue)});
        return ExecResult::successd;
    }

    // ペンを変更する
    CommandExecutor::ExecResult CommandExecutor::pen(const command_params_t& cmd_vec)
    {
        if (cmd_vec.size() < 4) {
            std::cout << "pen コマンドには3つのパラメータが必要です。\n";
            print_help_pen();
            return ExecResult::failed;
        }

        const int red {std::stoi(cmd_vec.at(1))};
        if (red < 0 || 255 < red) {
            std::cout << "赤成分の強さは 0 から 255 の範囲でなければなりません。\n";
            print_help_pen();
            return ExecResult::failed;
        }

        const int green {std::stoi(cmd_vec.at(2))};
        if (green < 0 || 255 < green) {
            std::cout << "緑成分の強さは 0 から 255 の範囲でなければなりません。\n";
            print_help_pen();
            return ExecResult::failed;
        }

        const int blue {std::stoi(cmd_vec.at(3))};
        if (blue < 0 || 255 < blue) {
            std::cout << "青成分の強さは 0 から 255 の範囲でなければなりません。\n";
            print_help_pen();
            return ExecResult::failed;
        }

        const Pen pen({static_cast<unsigned char>(red), static_cast<unsigned char>(green), static_cast<unsigned char>(blue)});
        m_pen = pen;
        return ExecResult::successd;
    }

    // 点を描画する
    CommandExecutor::ExecResult CommandExecutor::dot(const command_params_t& cmd_vec)
    {
        if (cmd_vec.size() < 3) {
            std::cout << "dot コマンドには2つのパラメータが必要です。\n";
            print_help_dot();
            return ExecResult::failed;
        }

        const int x {std::stoi(cmd_vec.at(1))};
        const int y {std::stoi(cmd_vec.at(2))};

        m_canvas.paint_dot(x, y, m_pen);
        return ExecResult::successd;
    }

    // 矩形を描画する
    CommandExecutor::ExecResult CommandExecutor::rect(const command_params_t& cmd_vec)
    {
        if (cmd_vec.size() < 5) {
            std::cout << "rect コマンドには4つのパラメータが必要です。\n";
            print_help_rect();
            return ExecResult::failed;
        }

        const int left {std::stoi(cmd_vec.at(1))};
        const int top {std::stoi(cmd_vec.at(2))};
        const int right {std::stoi(cmd_vec.at(3))};
        const int bottom {std::stoi(cmd_vec.at(4))};

        if (left > right) {
            std::cout << "left は right より左になければなりません。\n";
            return ExecResult::failed;
        }
        if (top > bottom) {
            std::cout << "top は bottom より上になければなりません。\n";
            return ExecResult::failed;
        }

        m_canvas.paint_rect(left, top, right, bottom, m_pen);
        return ExecResult::successd;
    }

    // 矩形を描画し、内側を塗りつぶす
    CommandExecutor::ExecResult CommandExecutor::filled_rect(const command_params_t& cmd_vec)
    {
        if (cmd_vec.size() < 5) {
            std::cout << "filled_rect コマンドには4つのパラメータが必要です。\n";
            print_help_filled_rect();
            return ExecResult::failed;
        }

        const int left {std::stoi(cmd_vec.at(1))};
        const int top {std::stoi(cmd_vec.at(2))};
        const int right {std::stoi(cmd_vec.at(3))};
        const int bottom {std::stoi(cmd_vec.at(4))};

        if (left > right) {
            std::cout << "left は right より左になければなりません。\n";
            return ExecResult::failed;
        }
        if (top > bottom) {
            std::cout << "top は bottom より上になければなりません。\n";
            return ExecResult::failed;
        }

        m_canvas.paint_filled_rect(left, top, right, bottom, m_pen);
        return ExecResult::successd;
    }

    // ビットマップファイルから読み込む
    CommandExecutor::ExecResult CommandExecutor::load(const command_params_t& cmd_vec)
    {
        if (cmd_vec.size() < 2) {
            std::cout << "load コマンドには1つのパラメータが必要です。\n";
            print_help_load();
            return ExecResult::failed;
        }

        const std::string path {cmd_vec.at(1)};

        if (!m_canvas.load_from_bitmap_file(path)) {
            std::cout << "path " << "の読み込みに失敗しました。\n";
            return ExecResult::failed;
        }
        return ExecResult::successd;
    }

    // ビットマップファイルに保存する
    CommandExecutor::ExecResult CommandExecutor::save(const command_params_t& cmd_vec)
    {
        if (cmd_vec.size() < 2) {
            std::cout << "save コマンドには1つのパラメータが必要です。\n";
            print_help_save();
            return ExecResult::failed;
        }

        const std::string path {cmd_vec.at(1)};

        if (!m_canvas.save_to_bitmap_file(path)) {
            std::cout << "path " << "への保存に失敗しました。\n";
            return ExecResult::failed;
        }
        return ExecResult::successd;
    }

    // スクリプトが保存されたファイルから実行する
    CommandExecutor::ExecResult CommandExecutor::load_script(const command_params_t& cmd_vec)
    {
        if (m_is_load_script) {
            return ExecResult::successd;
        }

        if (cmd_vec.size() < 2) {
            std::cout << "load_script コマンドには1つのパラメータが必要です。\n";
            print_help_load_script();
            return ExecResult::failed;
        }

        const std::string path {cmd_vec.at(1)};

        // スクリプトを読み込んで、履歴情報として復元する
        auto it = m_history.load_from_file(path);
        if (it == m_history.end()) {
            std::cout << path << " の読み込みに失敗しました。\n";
            return ExecResult::failed;
        }


        auto result = ExecResult::successd;

        // スクリプトのロード実行中であることを表すフラグを立てる
        m_is_load_script = true;

        // 履歴情報を辿りながら、1つずつ実行していく
        for ( /**/; it != m_history.end(); ++it) {
            std::string cmd {*it};

            // マクロを置換する
            paint_script::Macro::replace(cmd);

            // 空白文字ごとに分割して、std::vector に格納する
            const auto command_vec = str_util::split(cmd, " ");
            if (command_vec.empty()) {
                continue;
            }

            // 実行。途中でエラーが起きたら、以降の実行は止める
            auto exec_result = exec(command_vec);
            if (exec_result == ExecResult::failed || exec_result == ExecResult::exit_program) {
                result = exec_result;
                break;
            }
        }

        m_is_load_script = false;
        return result;
    }

    // スクリプトを保存する
    CommandExecutor::ExecResult CommandExecutor::save_script(const command_params_t& cmd_vec)
    {
        if (m_is_load_script) {
            return ExecResult::successd;
        }

        if (cmd_vec.size() < 2) {
            std::cout << "save_script コマンドには1つのパラメータが必要です。\n";
            print_help_save();
            return ExecResult::failed;
        }

        const std::string path {cmd_vec.at(1)};

        if (!m_history.save_to_file(path)) {
            std::cout << "path " << "への保存に失敗しました。\n";
            return ExecResult::failed;
        }

        return ExecResult::successd;
    }

    void CommandExecutor::print_help() const
    {
        std::cout << "以下のコマンドがあります。\n"
                  << "対応するパラメータがある場合は、その順番どおりに、正しい値をスペースで区切って入力してください。\n"
                  << std::endl;

        for (const auto& data : CommandMap) {
            data.help(this);
        }

        Macro::print_help();
    }

    void CommandExecutor::print_help_help() const
    {
        std::cout << "help\n"
                  << "ヘルプメッセージを出力します。\n"
                  << std::endl;
    }

    void CommandExecutor::print_help_exit() const
    {
        std::cout << "exit\n"
                  << "スクリプトの実行を終了します。\n"
                  << std::endl;
    }

    void CommandExecutor::print_help_resize() const
    {
        std::cout << "resize width height\n"
                  << "キャンバスの大きさを変更します。\n"
                  << "  width:  横方向のピクセル数を 1 以上の大きさで指定します。\n"
                  << "  height: 縦方向のピクセル数を 1 以上の大きさで指定します。\n"
                  << std::endl;
    }

    void CommandExecutor::print_help_fill() const
    {
        std::cout << "fill red green blue\n"
                  << "キャンバスを1色で塗りつぶします。\n"
                  << "  red:   赤成分の強さを 0 から 255 の範囲で指定します。\n"
                  << "  green: 緑成分の強さを 0 から 255 の範囲で指定します。\n"
                  << "  blue:  青成分の強さを 0 から 255 の範囲で指定します。\n"
                  << std::endl;
    }

    void CommandExecutor::print_help_pen() const
    {
        std::cout << "pen red green blue\n"
                  << "点や線を描くときに使うペンを変更します。\n"
                  << "  red:   赤成分の強さを 0 から 255 の範囲で指定します。\n"
                  << "  green: 緑成分の強さを 0 から 255 の範囲で指定します。\n"
                  << "  blue:  青成分の強さを 0 から 255 の範囲で指定します。\n"
                  << std::endl;
    }

    void CommandExecutor::print_help_dot() const
    {
        std::cout << "dot x y\n"
                  << "現在のペンを使って、点を描画します。\n"
                  << "  x: X座標を指定します。キャンバスの範囲外の場合は何も描かれません。\n"
                  << "  y: Y座標を指定します。キャンバスの範囲外の場合は何も描かれません。\n"
                  << std::endl;
    }

    void CommandExecutor::print_help_rect() const
    {
        std::cout << "rect left top right bottom\n"
                  << "現在のペンを使って、矩形を描画します。\n"
                  << "内側は塗られません。内側を塗る場合は、filled_rect コマンドを使用してください。\n"
                  << "  left:   左端のX座標を指定します。キャンバスの範囲外も指定できます。\n"
                  << "  top:    上端のY座標を指定します。キャンバスの範囲外も指定できます。\n"
                  << "  right:  右端のX座標を指定します。キャンバスの範囲外も指定できます。\n"
                  << "  bottom: 下端のY座標を指定します。キャンバスの範囲外も指定できます。\n"
                  << std::endl;
    }

    void CommandExecutor::print_help_filled_rect() const
    {
        std::cout << "filled_rect left top right bottom\n"
                  << "現在のペンを使って、矩形を描画します。\n"
                  << "内側を塗りつぶします。内側を塗らない場合は、rect コマンドを使用してください。\n"
                  << "  left:   左端のX座標を指定します。キャンバスの範囲外も指定できます。\n"
                  << "  top:    上端のY座標を指定します。キャンバスの範囲外も指定できます。\n"
                  << "  right:  右端のX座標を指定します。キャンバスの範囲外も指定できます。\n"
                  << "  bottom: 下端のY座標を指定します。キャンバスの範囲外も指定できます。\n"
                  << std::endl;
    }

    void CommandExecutor::print_help_load() const
    {
        std::cout << "load path\n"
                  << ".bmpファイルを指定して、キャンバスを作成します。\n"
                  << "  path: 読み込む .bmpファイルのパス。\n"
                  << std::endl;
    }

    void CommandExecutor::print_help_save() const
    {
        std::cout << "save path\n"
                  << "現在のキャンバスの状態を .bmp ファイルに保存します。\n"
                  << "  path: 出力する .bmpファイルのパス。すでに存在する場合は上書きします。\n"
                  << std::endl;
    }

    void CommandExecutor::print_help_load_script() const
    {
        std::cout << "load_script path\n"
                  << "スクリプトが書き込まれたテキストファイルを読み込んで、その内容を実行します。\n"
                  << "  path: テキストファイルのパス。\n"
                  << std::endl;
    }

    void CommandExecutor::print_help_save_script() const
    {
        std::cout << "save_script path\n"
                  << "現在までに入力したコマンドとパラメータの履歴をテキストファイルに保存します。\n"
                  << "  path: 出力するテキストファイルのパス。すでに存在する場合は上書きします。\n"
                  << std::endl;
    }
}
// command_executor.h
#ifndef COMMAND_EXECUTOR_H_INCLUDED
#define COMMAND_EXECUTOR_H_INCLUDED

#include <functional>
#include <string>
#include <vector>
#include "pen.h"

namespace paint_script {

    class Canvas;
    class CommandHistory;

    class CommandExecutor {
    public:
        using command_params_t = std::vector<std::string>;  // コマンドとパラメータの型

        // 結果
        enum class ExecResult {
            successd,       // 成功
            failed,         // 失敗
            not_found,      // コマンドが見つからない
            exit_program,   // 成功。プログラムを終了させる
        };

    public:
        // コンストラクタ
        //
        // canvas:  キャンバスの参照
        // history: 履歴管理オブジェクトの参照
        explicit CommandExecutor(Canvas& canvas, CommandHistory& history);

    public:
        // コマンドを実行する
        //
        // command_vec: コマンドとパラメータを含んだ配列
        // 戻り値: 結果
        ExecResult exec(const command_params_t& command_vec);

    private:
        using CommandImpl_t = std::function<ExecResult (CommandExecutor*, const command_params_t&)>;    // 実装関数の型   
        using CommandHelp_t = std::function<void (const CommandExecutor*)>;                     // ヘルプ出力関数の型

        // コマンドデータ
        struct CommandData {
            const char*     name;   // コマンド名
            CommandImpl_t   impl;   // コマンドの実装関数
            CommandHelp_t   help;   // コマンドのヘルプを出力する関数
        };
        static const CommandData CommandMap[];

    private:
        ExecResult help(const command_params_t& cmd_vec);
        ExecResult exit(const command_params_t& cmd_vec);
        ExecResult resize(const command_params_t& cmd_vec);
        ExecResult fill(const command_params_t& cmd_vec);
        ExecResult pen(const command_params_t& cmd_vec);
        ExecResult dot(const command_params_t& cmd_vec);
        ExecResult rect(const command_params_t& cmd_vec);
        ExecResult filled_rect(const command_params_t& cmd_vec);
        ExecResult load(const command_params_t& cmd_vec);
        ExecResult save(const command_params_t& cmd_vec);
        ExecResult load_script(const command_params_t& cmd_vec);
        ExecResult save_script(const command_params_t& cmd_vec);

        void print_help() const;
        void print_help_help() const;
        void print_help_exit() const;
        void print_help_resize() const;
        void print_help_fill() const;
        void print_help_pen() const;
        void print_help_dot() const;
        void print_help_rect() const;
        void print_help_filled_rect() const;
        void print_help_load() const;
        void print_help_save() const;
        void print_help_load_script() const;
        void print_help_save_script() const;

    private:
        Canvas&                 m_canvas;
        CommandHistory&         m_history;
        Pen                     m_pen {{0, 0, 0}};

        bool                    m_is_load_script;
    };

}

#endif
// command_history.cpp
#include "command_history.h"
#include <fstream>

namespace paint_script {

    // 履歴を追加する
    void CommandHistory::add(const std::string& command)
    {
        m_commands.push_back(command);
    }

    // 最初の履歴を指すイテレータを返す
    std::vector<std::string>::const_iterator CommandHistory::begin() const
    {
        return std::cbegin(m_commands);
    }

    // 最後の履歴の後ろを指すイテレータを返す
    std::vector<std::string>::const_iterator CommandHistory::end() const
    {
        return std::cend(m_commands);
    }

    // テキストファイルから履歴を読み込む
    std::vector<std::string>::const_iterator CommandHistory::load_from_file(const std::string& path)
    {
        std::ifstream ifs {path};
        if (!ifs) {
            return end();
        }

        // 追加前の一番後ろのインデックスを保存
        // [!] このあと std::vector への追加を行うと、イテレータや参照、ポインタは無効化される可能性があるので、
        //     添字で保存しておく。
        auto start_index = m_commands.size();

        // 1行ずつ読み込みながら、履歴に追加
        while (!ifs.eof()) {
            std::string s {};
            std::getline(ifs, s);
            add(s);
        }

        return std::begin(m_commands) + start_index;
    }

    // 履歴をテキストファイルへ書き出す
    bool CommandHistory::save_to_file(const std::string& path)
    {
        std::ofstream ofs {path, std::ios_base::out};
        if (!ofs) {
            return false;
        }

        for (const std::string& str : m_commands) {
            ofs << str << "\n";
        }

        return true;
    }
}
// command_history.h
#ifndef COMMAND_HISTORY_H_INCLUDED
#define COMMAND_HISTORY_H_INCLUDED

#include <string>
#include <vector>

namespace paint_script {

    class CommandHistory {
    public:
        // 履歴を追加する
        //
        // command: 追加するコマンドおよびパラメータの文字列
        void add(const std::string& command);


        // 最初の履歴を指すイテレータを返す
        std::vector<std::string>::const_iterator begin() const;

        // 最後の履歴の後ろを指すイテレータを返す
        std::vector<std::string>::const_iterator end() const;


        // テキストファイルから履歴を読み込む
        //
        // path: テキストファイルのパス
        // 戻り値: 読み込まれた最初の履歴を指すイテレータ。
        //         読み込みに失敗した場合は、end() が返すイテレータ。
        std::vector<std::string>::const_iterator load_from_file(const std::string& path);

        // 履歴をテキストファイルへ書き出す
        //
        // path: 出力先のテキストファイルのパス
        // 戻り値: 成否
        bool save_to_file(const std::string& path);

    private:
        std::vector<std::string>    m_commands;
    };

}

#endif
// macro.cpp
#include "macro.h"
#include <iostream>
#include "string_util.h"

namespace paint_script {

    const std::pair<std::string, std::string> Macro::MacroTable[]{
        {"RED", "255 0 0"},
        {"GREEN", "0 255 0"},
        {"BLUE", "0 0 255"},
        {"WHITE", "255 255 255"},
        {"BLACK", "0 0 0"},
    };



    // マクロ置換を行う
    std::string& Macro::replace(std::string& str)
    {
        for (const auto& macro : MacroTable) {
            str_util::find_replace_all(str, macro.first, macro.second);
        }

        return str;
    }

    // マクロの一覧を出力する
    void Macro::print_help()
    {
        std::cout << "以下のマクロがあります。\n"
                  << std::endl;

        for (const auto& macro : MacroTable) {
            std::cout << macro.first << "\n"
                      << "置換結果: " << macro.second << "\n"
                      << std::endl;
        }
    }
}
// macro.h
#ifndef MACRO_H_INCLUDED
#define MACRO_H_INCLUDED

#include <string>
#include <utility>

namespace paint_script {

    class Macro {
    public:
        Macro() = delete;

        // マクロ置換を行う
        //
        // str: 対象の文字列
        // 戻り値: str を返す
        static std::string& replace(std::string& str);

        // マクロの一覧を出力する
        static void print_help();

    private:
        static const std::pair<std::string, std::string> MacroTable[];
    };

}

#endif
// pen.cpp
#include "pen.h"

namespace paint_script {

    Pen::Pen(Color color) :
        m_color {color}
    {

    }

}
// pen.h
#ifndef PEN_H_INCLUDED
#define PEN_H_INCLUDED

#include "color.h"

namespace paint_script {

    class Pen {
    public:
        // コンストラクタ
        //
        // color: 色
        explicit Pen(Color color);

    public:
        // 色を返す
        //
        // 戻り値: 色
        inline Color get_color() const
        {
            return m_color;
        }

    private:
        Color           m_color;
    };
}

#endif
// color.h
#ifndef COLOR_H_INCLUDED
#define COLOR_H_INCLUDED

namespace paint_script {

    // 色
    struct Color {
        unsigned char  red;         // 赤成分
        unsigned char  green;       // 緑成分
        unsigned char  blue;        // 青成分
    };
}

#endif
// bmp.cpp
#include "bmp.h"
#include "color.h"
#include <cassert>
#include <cstdint>
#include <fstream>

namespace paint_script {

    bool Bmp::save(const std::string& path, unsigned int width, unsigned int height, const std::vector<std::vector<Color>>& pixels)
    {
        std::ofstream ofs {path, std::ios_base::out | std::ios_base::binary};
        if (!ofs) {
            return false;
        }

        // ----- ファイルヘッダ部 -----
        // Windows API の BITMAPFILEHEADER構造体にあたる。
        // https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapfileheader

        // ファイルタイプ
        // 必ず 0x4d42 ("BM" のこと)
        std::uint16_t file_type {0x4d42};
        ofs.write(reinterpret_cast<const char*>(&file_type), sizeof(file_type));

        // ファイルサイズ
        // 縦横のピクセル数 * 1ピクセル当たりのバイト数(PaintScript では 4バイト固定) + ヘッダ部のバイト数。
        // ヘッダ部の大きさは、sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) より。
        std::uint32_t file_size {width * height * 4 + 54};
        ofs.write(reinterpret_cast<const char*>(&file_size), sizeof(file_size));

        // 予約領域
        std::uint32_t reserved {0};
        ofs.write(reinterpret_cast<const char*>(&reserved), sizeof(reserved));

        // ファイル先頭から、ピクセル情報までの距離
        std::uint32_t offset_to_pixels {54};
        ofs.write(reinterpret_cast<const char*>(&offset_to_pixels), sizeof(offset_to_pixels));


        // ----- ビットマップ情報ヘッダ部 -----
        // Windows API の _BITMAPINFOHEADER構造体にあたる。
        // https://learn.microsoft.com/ja-jp/windows/win32/wmdm/-bitmapinfoheader

        // ビットマップ情報ヘッダ部のサイズ
        std::uint32_t bitmap_info_header_size {40};
        ofs.write(reinterpret_cast<const char*>(&bitmap_info_header_size), sizeof(bitmap_info_header_size));

        // 横方向のピクセル数
        std::int32_t w {static_cast<std::int32_t>(width)};
        ofs.write(reinterpret_cast<const char*>(&w), sizeof(w));

        // 縦方向のピクセル数
        std::int32_t h {static_cast<std::int32_t>(height)};
        ofs.write(reinterpret_cast<const char*>(&h), sizeof(h));

        // プレーン数。必ず 1
        std::uint16_t planes {1};
        ofs.write(reinterpret_cast<const char*>(&planes), sizeof(planes));

        // 1ピクセル当たりのビット数。PaintScript では 24 に固定
        std::uint16_t bit_count {24};
        ofs.write(reinterpret_cast<const char*>(&bit_count), sizeof(bit_count));

        // 圧縮形式。無圧縮は 0
        std::uint32_t compression {0};
        ofs.write(reinterpret_cast<const char*>(&compression), sizeof(compression));

        // 画像サイズ。無圧縮であれば 0 で構わない
        std::uint32_t image_size {0};
        ofs.write(reinterpret_cast<const char*>(&image_size), sizeof(image_size));

        // メートル当たりの横方向のピクセル数の指示。不要なら 0 にできる
        std::int32_t x_pixels_per_meter {0};
        ofs.write(reinterpret_cast<const char*>(&x_pixels_per_meter), sizeof(x_pixels_per_meter));

        // メートル当たりの縦方向のピクセル数の指示。不要なら 0 にできる
        std::int32_t y_pixels_per_meter {0};
        ofs.write(reinterpret_cast<const char*>(&y_pixels_per_meter), sizeof(y_pixels_per_meter));

        // カラーテーブル内の色のうち、実際に使用している個数。パレット形式でなければ無関係
        std::uint32_t clr_used {0};
        ofs.write(reinterpret_cast<const char*>(&clr_used), sizeof(clr_used));

        // カラーテーブル内の色のうち、重要色である色の個数。パレット形式でなければ無関係
        std::uint32_t clr_important {0};
        ofs.write(reinterpret_cast<const char*>(&clr_important), sizeof(clr_important));


        // ----- ピクセル情報 -----
        // Windows API の RGBQUAD に当たる。
        // https://learn.microsoft.com/ja-jp/windows/win32/api/wingdi/ns-wingdi-rgbquad
        for (std::int32_t y {h - 1}; y >= 0; --y) {
            for (std::int32_t x {0}; x < w; ++x) {
                const Color pixel {pixels.at(y).at(x)};
                ofs.write(reinterpret_cast<const char*>(&pixel.blue), sizeof(pixel.blue));
                ofs.write(reinterpret_cast<const char*>(&pixel.green), sizeof(pixel.green));
                ofs.write(reinterpret_cast<const char*>(&pixel.red), sizeof(pixel.red));
            }
        }

        return true;
    }

    bool Bmp::load(const std::string& path, unsigned int* width, unsigned int* height, std::vector<std::vector<Color>>* pixels)
    {
        assert(width);
        assert(height);
        assert(pixels);

        std::ifstream ifs {path, std::ios_base::in | std::ios_base::binary};
        if (!ifs) {
            return false;
        }

        // 不要なところを読み飛ばす
        ifs.seekg(18, std::ios_base::beg);

        // 横方向のピクセル数
        std::int32_t w {};
        ifs.read(reinterpret_cast<char*>(&w), sizeof(w));
        if (w < 1) {
            return false;
        }
        *width = static_cast<unsigned int>(w);
        
        // 縦方向のピクセル数
        std::int32_t h {};
        ifs.read(reinterpret_cast<char*>(&h), sizeof(h));
        if (h < 1) {
            return false;
        }
        *height = static_cast<unsigned int>(h);

        // 不要なところを読み飛ばす
        ifs.seekg(28, std::ios_base::cur);

        // ピクセル情報
        // vector は、ビットマップの大きさに合わせて resize する。
        pixels->resize(h);
        for (auto& row : *pixels) {
            row.resize(w);
        }
        for (std::int32_t y {h - 1}; y >= 0; --y) {
            for (std::int32_t x {0}; x < w; ++x) {
                std::uint8_t b {};
                ifs.read(reinterpret_cast<char*>(&b), sizeof(b));
                std::uint8_t g {};
                ifs.read(reinterpret_cast<char*>(&g), sizeof(g));
                std::uint8_t r {};
                ifs.read(reinterpret_cast<char*>(&r), sizeof(r));

                pixels->at(y).at(x) = Color{r, g, b};
            }
        }

        return true;
    }
}
// bmp.h
#ifndef BMP_H_INCLUDED
#define BMP_H_INCLUDED

#include <string>
#include <vector>

namespace paint_script {

    struct Color;

    class Bmp {
    public:
        Bmp() = delete;

        // ファイルに書き出す
        //
        // path: ファイルパス
        // width: 横方向のピクセル数
        // height: 縦方向のピクセル数
        // pixels: ピクセル情報
        // 戻り値: 成否
        static bool save(const std::string& path, unsigned int width, unsigned int height, const std::vector<std::vector<Color>>& pixels);

        // ファイルから読み込む
        //
        // path: ファイルパス
        // width: 横方向のピクセル数を受け取るポインタ。ヌルポインタ不可
        // height: 縦方向のピクセル数を受け取るポインタ。ヌルポインタ不可
        // pixels: ピクセル情報を受け取るポインタ。ヌルポインタ不可
        // 戻り値: 成否
        static bool load(const std::string& path, unsigned int* width, unsigned int* height, std::vector<std::vector<Color>>* pixels);
    };

}

#endif


参考リンク



更新履歴




はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る