boost::spiritの文法内のセマンティックアクションを自作

解決


PenName  2009-07-13 10:02:07  No: 70547

すいません、先の質問(spiritに関する)の続きとなってしまいます。
Banさんのソースでばっちり上手くいったのですが、また別に問題が出てきてしまいまして……。

そのソースでは、セマンティックアクションは定義済みのアクション(boost::spirit::classic::assign_a(name_buffer))が使われていまして、これはまったく問題ありません。
しかし、自分で関数を作り、それをアクションさせようとすると、どうしてもエラーが出てきてしまいます。
最終的にはCScriptParser内のname_bufferに解析したものを保存出来ればいいのですが、それを行うための関数はCScriptParser内のメンバ関数にしたいと思っています。
メンバ関数にしたい理由は、“外部に関数を書くのなら簡単に出来るが、その場合、今度はCScriptParser::name_bufferに保存するための操作が複雑化してしまう”からです。

なにかスマートな記述方法はないでしょうか。
よろしくお願いします。

↓以下には、私が採用させて頂いたBanさんのソースをそのまま転載します↓

class CScriptParser : public boost::spirit::classic::grammar<CScriptParser> 
{
public:
    // parse_info
    boost::spirit::classic::parse_info<> parseInfo;

    // 構文解析時のトークン保存用
    mutable std::string name_buffer;

    template<typename S> struct definition {

        typedef boost::spirit::classic::rule<S> rule_t;
        rule_t A;

        // 構文解析ルールの記述
        definition(const CScriptParser& self)
        {
            A = boost::spirit::classic::alpha_p[boost::spirit::classic::assign_a(self.name_buffer)];
        }

        const rule_t& start() const { return A; }
    };

    explicit    CScriptParser()
    {
        parseInfo    = boost::spirit::classic::parse("a", *this, boost::spirit::classic::space_p);
    }
};


Ban  2009-07-13 10:11:35  No: 70548

> しかし、自分で関数を作り、それをアクションさせようとすると、どうしてもエラーが出てきてしまいます。

どんなコードを書いたら、どんなエラーになったのかを書いた方がいいと思います。
それで、「何がしたいのか」が他人にみえるかもしれませんし、
「どう考えているのか」「何が悪いのか」がみえるかもしれません。

今のところ私には、
"今度はCScriptParser::name_bufferに保存するための操作が複雑化してしまう"がよく分かりません。
定義済みアクションは楽に書くために定義済みなのだと思いますし、
メンバ関数にして何が改善されると考えているのかがよく分かりません。


PenName  2009-07-13 18:57:03  No: 70549

すいません、遅くなりました。

確かに、ご指摘の通りです。
意図がはっきりしていなくて申し訳ございません。

実は、このクラスはこれから拡張していく予定でして、最終的には、単に“name_bufferに文字列が収まればいい”というわけではありません。
ただコードの単純化のために、こういう形を取った方が良いと判断してしまいました。

拡張は、例えば、“他のメンバ変数の値が〜〜だったら、'a'と'b'はappendしない”ですとか、そういったことをする予定です(他にも色々とやりますが)。
そういうわけでして、「何がしたいのか」に関しては、やはり“自作メンバ関数を使いたい”ということです。
(定義済みパーサ/アクションは多々あるので使いこなせば複雑な処理にも対応できる気がしないでもないですが、自作出来た方がてっとり早く確実だと思います。)


PenName  2009-07-13 19:11:14  No: 70550

(外部に関数を書くのなら簡単に出来るが、その場合、)"今度はCScriptParser::name_bufferに保存するための操作が複雑化してしまう"

ということに関してです。

// 外部の関数
void f(const int a)
{// ここでCScriptParserのメンバ変数に対して何かするためには、CScriptParser型のグローバル変数を使うしかない
}

class CScriptParser : public boost::spirit::classic::grammar<CScriptParser> 
{
  略
        // 構文解析ルールの記述
        definition(const CScriptParser& self)
        {
            A = boost::spirit::classic::int_p[&f]
        }
  略
};

コンパイル自体は通るのですが、fによるCScriptParserのメンバ変数へのアクセスがグローバル変数経由になってしまうと思います(そうじゃない方法もある……のかな?  でしたら解決なのですが)。

あと、“複雑化”っていう言い方が正しくなかったですね。
単にグローバル変数を使うってだけですし……。
申し訳ございません。


PenName  2009-07-13 19:37:48  No: 70551

他に試した方法としては、関数オブジェクトによる方法です。
先のfの部分を下のように変え、

struct f_obj {
  std::string&  string_buffer;
  f_obj(std::string& a) : string_buffer(a) { }
  void operator ()(const char a) const
  {// string_bufferに操作
    string_buffer += a;
  }
};

更に、

template<typename S> struct definition {

  f_obj f;

  typedef boost::spirit::classic::rule<S> rule_t;
  rule_t A;

  // 構文解析ルールの記述
  definition(const CScriptParser& self) : f(self.name_buffer)
  {
    A = boost::spirit::classic::alpha_p[f];
  }

  const rule_t& start() const { return A; }
};

という風にルールを記述することで、グローバル変数を使わずに出来ます。
しかし、やはりCScriptParser::name_bufferへのアクセスが複雑な気がしましたので、どうにかメンバ関数が出来ないものかと悶々しているところです。


Ban  2009-07-14 11:19:32  No: 70552

> やはりCScriptParser::name_bufferへのアクセスが複雑な気がしましたので、
> どうにかメンバ関数が出来ないものかと悶々しているところです。

メンバ関数へのアクセスは、Phoenixでは例えばこうなるわけですが、
これで満足ですか?
これを見比べた上でも、メンバ関数へのアクセスは簡素だと思いますか。

class CScriptParser : public boost::spirit::classic::grammar<CScriptParser> 

  ...

  int
  append(char ch)const
  {
    name_buffer  += ch;
    return 0;
  }

  template<typename S> struct definition {

    typedef boost::spirit::classic::rule<S> rule_t;
    rule_t A;

    // 構文解析ルールの記述
    definition(const CScriptParser& self)
    {
      phoenix::member_function_ptr<int, const CScriptParser, char>  append_  = &CScriptParser::append;
      A = boost::spirit::classic::alpha_p    [append_(phoenix::var(self), phoenix::arg1)];
    }

  ...


Ban  2009-07-14 11:43:29  No: 70553

>“他のメンバ変数の値が〜〜だったら、'a'と'b'はappendしない
Phoenixならメンバ変数を条件式にすることもできるわけで、
必ずしもメンバ関数である必要はないように思いますが…好みの問題ですかね。
# かなり複雑な式を書くなら「関数名」という名前をつけた方がみやすいでしょうし、
# 「(他にも色々とやりますが)」がわからないと結局判断不能ですし。

    definition(const CScriptParser& self)
    {
      using phoenix::if_;

      A = boost::spirit::classic::alpha_p    [
                            if_( ! self.name_buffer.empty())
                            [
                              phoenix::var(self.name_buffer)  = phoenix::arg1
                            ]
                            .else_
                            [
                              phoenix::var(self.name_buffer)  += phoenix::arg1
                            ]
                          ];

    }

    const rule_t& start() const { return A; }
  };


Ban  2009-07-14 12:00:25  No: 70554

すみません、補足と訂正です。

> 簡素だと思いますか。
append_(phoenix::member_function_ptr)の存在を考えると、
個人的にはファンクタ等と比べてさほど簡素になった気がしない、
ということです。

で、「“他のメンバ変数の値が〜〜だったら」程度の話なら、
こういう方(↓)が簡素に感じるわけです。でも結局は好みだなぁと。

      using phoenix::if_;

      A = boost::spirit::classic::alpha_p    [
                                                if_(self.name_buffer.empty())
                                                [
                                                  phoenix::var(self.name_buffer)  = phoenix::arg1
                                                ]
                                                .else_
                                                [
                                                  phoenix::var(self.name_buffer)  += phoenix::arg1
                                                ]
                                              ];

# 先の例だと、name_bufferがemptyの方が+=になってますね…orz


Ban  2009-07-14 12:17:03  No: 70555

> なにかスマートな記述方法はないでしょうか。
# ラムダ式がスマートに感じる私はC++厨だなぁと。
# まぁBoost.Spirit使おうとする時点で既に…(以下略


PenName  2009-07-14 18:44:28  No: 70556

解答ありがとうございます。

phoenix……調べる途中で何度も見かけたのですが、いまいち使い方と効果がよく分からなかったので、これを機に勉強したいと思います。

>簡素だと思いますか。
こちらの方が十分スリムだと思いますっ。
ファンクタはPhoenixに比べ、
・関数1つに対して構造体1つを外部に作らなくてはならない
・作ったoperator()(...)内で使う変数への参照が必要
・構造体が持つ参照メンバ変数に初期化時に値を渡す必要があるため、その参照のための初期化子を持つコンストラクタが必要
・コンストラクタに引数が必要なせいで、definitionのコンストラクタ(ルール記述)の初期化リストが膨大なことに
といったことでしょうかね。

この子、見た分には、たぶん、私の意図している通りのことをしてくれそうですし。
しかし、使い方が把握できていないので、しばらくは勉強になりますねTT

本当にありがとうございました。

# いいじゃないですか、C++厨でっ


PenName  2009-07-14 19:01:29  No: 70557

解決踏み忘れTT


Ban  2009-07-14 19:14:14  No: 70558

> こちらの方が十分スリムだと思いますっ。
お考えは分かりました。
多分意図通りのはずです>phoenix::member_function_ptr
Phoenixだと、一度この手のものが必要っぽいです。
# Boost.lambdaだとboost::lambda::bind(&CScriptParser::append, self, ...)とか直接かけるはずですが、
Phoenixと混ぜて使ったことはないです<ラムダ

# Spirit2.xだと代わってるのかもしれませんがまだそこまで調査が回ってません。
# 今のところ、Spirit2.xで分割されたqiとkarma, lexへの移行/把握で手一杯…

Phoenixは(事実上Spirit用の)無名関数(ラムダ)ライブラリです。
# Boostには別にLambdaってのがありますので…。>Spirit用

んで、Spirit自体がPhoenixを前提にしており、
Phonexの機能を使うと、SAを記述する[]の中に、if_やelse_、
whule_など、色々な処理を直接記述できるわけです。

# メンバ関数を別途作る必要もなく、definitionで完結するのがスマート。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加