Reputation: 187
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_;
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_;
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?
Upvotes: 2
Views: 868
Reputation: 393114
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\n";
else
std::cerr << "Uhoh\n";
std::cout << karma::format(karma::auto_ % ", ", m) << "\n";
}
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\n";
else
std::cerr << "Uhoh\n";
std::cout << karma::format(karma::auto_ % ", ", m.data) << "\n";
}
Output:
Wokay
42, 45, -9
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) << "\n";
}
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)
Upvotes: 4