var_expand_grammar

簡易bjamインタプリタ実装の手始めとして、変数の展開を行うパーサー(?)var_expand_grammarを作りました。


bjam(というかjam)の変数展開は直積集合を形成するのが特徴的です。

X = a b c ;
ECHO t$(X) ; # 「ta tb tc」を出力

これを素直に表現するため、変数の値はvector<string>としました。


直積を求めるファンクタはこうなります。

struct append_str_vec_actor
{
    typedef std::vector<std::string> result_type;

    std::vector<std::string>
    operator()(
        const std::vector<std::string>& lhs,
        const std::vector<std::string>& rhs) const
    {
        std::vector<std::string> result;
        for (std::size_t i = 0; i < lhs.size(); ++i)
        {
            for (std::size_t j = 0; j < rhs.size(); ++j)
                result.push_back(lhs[i] + rhs[j]);
        }
        return result;
    }
};

積なので、初期値は空ベクトルではなく、空文字列一つのベクタにする必要があります。


パーサーはこんな感じになります。

#include <boost/spirit/core.hpp>
#include <boost/spirit/attribute/closure.hpp>
#include <boost/spirit/phoenix/binders.hpp>

// 変数テーブル
class variables { ... };

struct vars_closure
    : boost::spirit::closure<
        vars_closure,
        std::vector<std::string>
    >
{
    member1 val;
};

// 変数を展開するファンクタ
class vars_expand_actor;

// 一つの文字列からなるベクタを作成するファンクタ
struct make_str_vec_actor;

struct var_expand_grammar
    : boost::spirit::grammar<var_expand_grammar,vars_closure::context_t>
{
    explicit var_expand_grammar(const variables& t)
        : table(t)
    {
    }

    const variables& table;

    template<class ScannerT>
    struct definition
    {
        typedef boost::spirit::rule<
            ScannerT, typename vars_closure::context_t> vars_rule_t;

        vars_rule_t top, expr, variable;

        definition(const var_expand_grammar& self)
        {
            using namespace boost::spirit;
            using namespace phoenix;

            variable
                =   "$("
                    >> expr
                        [
                            variable.val =
                                bind(vars_expand_actor(self.table))(arg1)
                        ]
                    >> ')'
                ;

            expr
                =   // 空文字列1個で初期化 (arg1 == arg2)
                    eps_p[expr.val = bind(make_str_vec_actor())(arg1, arg2)]
                    >>
                   *(   variable
                        [
                            expr.val =
                                bind(append_str_vec_actor())(expr.val, arg1)
                        ]
                    |   (+(anychar_p - '$' - ')'))
                        [
                            expr.val =
                                bind(append_str_vec_actor())(
                                    expr.val, 
                                    bind(make_str_vec_actor())(arg1, arg2)
                                )
                        ]
                    )
                ;

            top
                =   expr[ self.val = arg1 ]
                ;
        }

        const vars_rule_t& start() const
        {
            return top;
        }
    };
};

bindだらけですが、PhoenixのbindはBoost.Lambdaのほど読みにくくないので、まだ意味が分かりますね。
今日の成果物