Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
126 views
in Technique[技术] by (71.8m points)

c++ - pass attribute to child rule in boost spirit

I have two rules with the same attribute.

Is it possible to pass the attribute of the matrix_ rule to the matrixBlock_ child rule? I want to keep the repeat directive from creating an attribute of the form vector< >. Instead it should just keep writing into the attribute of matrix_ (numBlocks's times). I tried to pass the attribute as inherited attribute to the child rule and it compiles(see below). But I get several "ghost" entries in my vector which come not from the phoenix::push_back. Also this seems not to be the optimal way for me. Is it possible to us automatic attribute propagation in matrixBlock_ instead of semantic actions?

typedef vector<columnT> Matrix;
matrix_ = repeat(numBlocks)[ matrixBlock_(_val) ];
matrixBlock_ = *column[phoenix::push_back(_r1, _1)];

qi::rule<Iterator, Matrix(), ascii::space_type> matrix_;
qi::rule<Iterator, void(Matrix&), ascii::space_type> matrixBlock_;

Update

to clarify the question:

if I write the rule with no semantic actions the synthesized attribute of matrix_ would be

vector< vector< columnT > >

-

typedef vector<columnT> Matrix;
matrix_ = repeat(numBlocks)[ matrixBlock_ ];
matrixBlock_ = *column;

qi::rule<Iterator, Matrix(), ascii::space_type> matrix_;
qi::rule<Iterator, Matrix(), ascii::space_type> matrixBlock_;

I want it to have the same attribute type as matrixBlock_, a 1-dimansional array.


my actual solution is to use only one rule. (looks to easy :-) )

typedef vector<columnT> Matrix;
matrix_ = repeat(numBlocks)[ *column_[ phoenix::push_back(_val, _1) ] ];
//matrixBlock_ = *column;

qi::rule<Iterator, Matrix(), ascii::space_type> matrix_;
//qi::rule<Iterator, Matrix(), ascii::space_type> matrixBlock_;

Update

I was able to reproduce the the phantom entries with this code in vs2010 and boost 1.46.1

http://liveworkspace.org/code/505091dc4631a379763567168a728e0c

output was: 42, 45, -9, 3, 2, 1, 12, 34, 56, 0, 0, 0

My mistake was using an old Boost version. There are no phontoms with 1.5.

Now I have two working versions of my grammar. Is it possible to redesign the grammar without the use of the push_back semantic action?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Updated

Answering your edited question: Yes you can do this without semantic actions, doing simply:

template<typename It>
struct Parser : qi::grammar<It, Matrix(), qi::space_type>
{
    Parser() : Parser::base_type(matrix_)
    {
        matrixBlock_ = qi::lit(";") >> *qi::int_;
        matrix_      = qi::repeat(3)[ matrixBlock_ ];
    }
    qi::rule<It, Matrix(), qi::space_type> matrixBlock_, matrix_;
};

Note that you may want to validate the number of rows/columns. See my extended sample, which uses an extra semantic action to check that (note the subtle change from *int_ to +int_ to avoid empty rows):

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/karma.hpp>
#include <boost/spirit/include/phoenix.hpp>

namespace qi    = boost::spirit::qi;
namespace karma = boost::spirit::karma;
namespace phx   = boost::phoenix;

typedef std::vector<int> Matrix;

template<typename It>
struct Parser : qi::grammar<It, Matrix(), qi::space_type>
{
    Parser() : Parser::base_type(matrix_)
    {
        using namespace qi;
        matrixBlock_ = lit(";") >> +int_ >> eps( 0 == (phx::size(_val) % 3));
        matrix_      = repeat(3)[ matrixBlock_ ];
    }
    qi::rule<It, Matrix(), qi::space_type> matrixBlock_, matrix_;
};

int main()
{
    std::string test = ";42 45 -9; 3 2 1; 12 34 56";

    std::string::const_iterator f(test.begin()), l(test.end());

    Parser<std::string::const_iterator> parser;
    Matrix m;

    if (qi::phrase_parse(f,l,parser,qi::space, m))
        std::cout << "Wokay
";
    else
        std::cerr << "Uhoh
";

    std::cout << karma::format(karma::auto_ % ", ", m) << "
";
}

Old answer:

Yes, you can use Spirit's customization points to treat your user-defined type as a container. The documentation entry I'd suggest for this is here:

Here is a simple example showing how to use it, live:

Side note with regards to 'phantom entries', in general:

Note that there is a bit of a FAQ related to backtracking grammars and container attributes. The thing is, for performance reasons, parsers won't undo ('rollback') changes to their underlying containers on backtracking. You can force this behaviour using qi::hold but it may worth the effort to redesign the grammar to either

  • avoid backtracking or
  • commit to the attribute at a later stage (using semantic actions)

Full code sample:

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/karma.hpp>

namespace qi = boost::spirit::qi;
namespace karma = boost::spirit::karma;

struct Matrix
{
   std::vector<int> data;
};

namespace boost { namespace spirit { namespace traits {
   template <>
      struct is_container<Matrix>
      {
      };

   template <typename Attrib>
      struct push_back_container<Matrix, Attrib>
      {
         static bool call(Matrix& c, Attrib const& val)
         {
            c.data.push_back(val);
            return true;
         }
      };

   template <>
      struct container_value<Matrix>
      {
         typedef int type;
      };
} } }

template<typename It>
struct Parser : qi::grammar<It, Matrix(), qi::space_type>
{
   Parser() : Parser::base_type(start)
   {
      start = *qi::int_;
   }
   qi::rule<It, Matrix(), qi::space_type> start;
};

int main()
{
   std::string test = "42 45 -9";

   std::string::const_iterator f(test.begin()),
      l(test.end());

   Parser<std::string::const_iterator> parser;
   Matrix m;

    if (qi::phrase_parse(f,l,parser,qi::space, m))
      std::cout << "Wokay
";
   else
      std::cerr << "Uhoh
";

   std::cout << karma::format(karma::auto_ % ", ", m.data) << "
";
}

Output:

Wokay
42, 45, -9

Update

A little more background:

Of course, for a trivial example like this, that just wraps a standard supported container type, it would be fairly easy to employ fusion adaptation instead: ( http://liveworkspace.org/code/56aea8619867451a21cd49fddb1e93bd )

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/karma.hpp>
#include <boost/fusion/adapted/struct.hpp>

namespace qi = boost::spirit::qi;
namespace karma = boost::spirit::karma;

struct Matrix { std::vector<int> data; }; 
BOOST_FUSION_ADAPT_STRUCT(Matrix, (std::vector<int>, data));

int main()
{
    std::string test = "42 45 -9";
    std::string::const_iterator f(test.begin()), l(test.end());

    Matrix m;
    if (qi::phrase_parse(f,l, qi::eps >> *qi::int_, qi::space, m))
        std::cout << karma::format(karma::auto_ % ", ", m.data) << "
";
}

Note that the qi::eps is necessary due to a bug (AFAICT) with structs that contain only one data element. See e.g. discussion here (and some other mentions)


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...