bjam_win その9

昨日の予想通りの方法で、テストのターゲット名取得を実装しました。
これでBoostのtest用Jamfile.v2の大半はパースできるようになりました。
以下、実装の解説です。
BBv2のrunルールのプロトタイプ(?)はこうです。

rule run (
    sources + :         # ソースファイルのリスト
    args * :            # コマンドライン引数
    input-files * :     # テストの入力用ファイル
    requirements * :    # ビルドに必要な条件
    target-name ? :     # ターゲット名
    default-build *     # 既定のvariant
)

仮引数の後ろの文字(+/*/?)は拡張BNFの同じ意味です。
つまり、sourcesの1個目(以下、source)だけが必須で、残りはオプションです。
昨日も書きましたが、今回調べたいターゲット名はtarget-nameが空の場合sourceのベース名を使います。
この場合わけ処理をBoost.SpiritとPhoenixでやると大変なので、sourceとtarget-nameを記録して、それをまとめて返すことにしました。

// テスト用パラメータ
struct testing_params
{
    std::string source;
    std::string target_name;

    testing_params(){}

    testing_params(const std::string& src, const std::string& name)
        : source(src), target_name(name)
    {
    }
};

この戻り値とsource/target-nameの3つを保存するためのクロージャを用意しました。

struct test_closure
    : boost::spirit::closure<
        test_closure,
        testing_params, std::string, std::string
    >
{
    member1 val;
    member2 source;
    member3 target_name;
};

boost::spirit::rule<ScannerT, typename test_closure::context_t> run;

クロージャのmember1が戻り値として使われるので、メンバの順序は重要です。
で、肝心のルールはこうです。

run
    =   run_rule
        // sources
        >>  literal
            [
                run.source = construct_<std::string>(arg1, arg2)
            ]
        >> *( varexp )
        >> !( ':' >> *( varexp ) )  // args
        >> !( ':' >> *( varexp ) )  // input-files
        >> !( ':' >> *( varexp ) )  // requirements
        // target-name
        >> !(   ':'
                >>  varexp
                    [
                        run.target_name =
                            construct_<std::string>(arg1, arg2)
                    ]
            )
        >> !( ':' >> *( varexp ) )  // default-build
        >>  eps_p
            [
                run.val =
                    construct_<testing_params>(
                        run.source, run.target_name)
            ]
    ;

eps_pはスキャナの位置を進めない特殊なパーサーで、ルールの最後で戻り値を設定するのに使っています。


ターゲット名の自動生成は呼び出し側のセマンティック・アクションで行いました。

statement
    =   exe
    |   lib
    |   test[push_back_test_actor(self.storage)]
    |   run[push_back_test_actor(self.storage)]
        // 以下略
    ;

セマンティック・アクションはルールのmember1型で結果を受け取れます。

class push_back_test_actor
{
public:
    explicit push_back_test_actor(std::vector<std::string>& vs) : vs_(vs)
    {
    }

    void operator()(const testing_params& val) const
    {
        std::string s = val.target_name;

        if (s.empty())
        {
            s = val.source;

            ::remove_branch_path(s);

            std::string::size_type pos = s.rfind('.');
            if (pos != std::string::npos)
                s.erase(pos);
        }

        // 変数が混じっている場合は未対応なので捨てる
        if (s.find('$') != std::string::npos)
            return;

        vs_.push_back(s);
    }

private:
    std::vector<std::string>& vs_;
};

これは定義どおりですね。


というわけで、今日の差分です。
bjam_grammar.hppの差分
Boost.Spiritにもだいぶ慣れてきました。