boost-spirit: need help with simple parser

Hi I would like to use boost::spirit for parsing ipv4 addresses. Here is what I have tried to do: struct Ipv4 { Ipv4() { raw.as_int = 0; } Ipv4( const Ipv4& i ) { raw.as_int = i.raw.as_int; } Ipv4& operator=(const Ipv4& i ) { raw.as_int = i.raw.as_int; return *this; } Ipv4(const boost::tuple<uint8_t, uint8_t, uint8_t, uint8_t>& v) { raw.as_char[3] = v.get<0>(); raw.as_char[2] = v.get<1>(); raw.as_char[1] = v.get<2>(); raw.as_char[0] = v.get<3>(); } union { uint32_t as_int; uint16_t as_short[2]; uint8_t as_char[4]; } raw; }; struct DecOctet : qi::grammar<const char *, uint8_t()> { DecOctet() : DecOctet::base_type(start) { start %= qi::uint_parser<uint8_t, 10, 1, 3>(); } qi::rule<const char *, uint8_t()> start; } dec_octet; struct Ipv4Address : qi::grammar<const char *, Ipv4()> { Ipv4Address() : Ipv4Address::base_type(start) { start %= dec_octet >> qi::lit('.') >> dec_octet >> qi::lit('.') >> dec_octet >> qi::lit('.') >> dec_octet ; } qi::rule<const char *, Ipv4()> start; } ipv4_address; bool parse(const const_string& s, Ipv4& i) { Ipv4 _i; const char * iter = s.begin(); bool r = qi::parse(iter, s.end(), ipv4_address, _i); if( !r || iter != s.end() ) return false; i = _i; return true; } But unfortunately if I parse an address such as "1.2.3.4", I end up with the address "1.0.0.0" in my Ipv4 struct. The IPv4 struct is legacy code, and the union member can therefor not be changed. I can add other constructors, but I prefer non-intrusive changes. So here are my questions: - What have I done wrong? - How should this has been done if I did not have a constructor accepting the tuple? - How would you have done? Best regards Allan W. Nielsen

I would like to use boost::spirit for parsing ipv4 addresses. Here is what I have tried to do:
FWIW, your issue can be reproduced with much shorted code: <skip include> <skip Ipv4 definition> namespace qi = boost::spirit::qi; qi::uint_parser<uint8_t, 10, 1, 3> octet; int main() { Ipv4 ip; std::string s = "1.2.3.4"; bool r = qi::parse(s.begin(), s.end(), octet >> '.' >> octet >> '.'
octet >> '.' >> octet, ip); }
Perhaps, it's worth asking on Spirit ML.

FWIW, your issue can be reproduced with much shorted code:
<skip include> <skip Ipv4 definition>
namespace qi = boost::spirit::qi; qi::uint_parser<uint8_t, 10, 1, 3> octet; int main() { Ipv4 ip; std::string s = "1.2.3.4"; bool r = qi::parse(s.begin(), s.end(), octet >> '.' >> octet >> '.' >> octet >> '.' >> octet, ip); }
or even simpler: boost::tuple<uint8_t, uint8_t, uint8_t, uint8_t> v; bool r = qi::parse(s.begin(), s.end(), octet >> '.' >> octet >> '.'
octet >> '.' >> octet, v);
while the following works correctly: boost::fusion::vector<uint8_t, uint8_t, uint8_t, uint8_t> v; bool r = qi::parse(s.begin(), s.end(), octet >> '.' >> octet >> '.'
octet >> '.' >> octet, v);
So, the attribute gets converted to boost::tuple is some strange way...

or even simpler: boost::tuple<uint8_t, uint8_t, uint8_t, uint8_t> v; bool r = qi::parse(s.begin(), s.end(), octet >> '.' >> octet >> '.'
octet >> '.' >> octet, v);
while the following works correctly: boost::fusion::vector<uint8_t, uint8_t, uint8_t, uint8_t> v; bool r = qi::parse(s.begin(), s.end(), octet >> '.' >> octet >> '.'
octet >> '.' >> octet, v);
So, the attribute gets converted to boost::tuple is some strange way...
Well, finally got it. You have to #include <boost/fusion/include/boost_tuple.hpp> to get boost::tuple adapted correctly.

Hi, Sorry for not providing the simplest possible code to reproduce my problem, but I'm still new to spirit, I think I will miss some details in the conversion. I tried add the inclusion you suggested, but it did not help. In the mean while, I found this page: http://boost-spirit.com/home/2010/01/19/how-to-access-attributes-from-semant... which is a very good match on what I would like to do. The only problem is that I can not make it work... Here is my second attempt (simper that the first, but not as simple as you suggested... sorry) #include <string> #include <string.h> #include <iostream> #include <boost/spirit/include/qi.hpp> #include <boost/spirit/include/phoenix_core.hpp> #include <boost/spirit/include/phoenix_operator.hpp> struct Ipv4 { union { uint32_t as_int; uint8_t as_char[4]; } raw; }; Ipv4 make_ipv4(uint8_t i1, uint8_t i2, uint8_t i3, uint8_t i4) { Ipv4 ip; ip.raw.as_char[0] = i1; ip.raw.as_char[1] = i2; ip.raw.as_char[2] = i3; ip.raw.as_char[3] = i4; return ip; } namespace qi = boost::spirit::qi; qi::uint_parser<uint8_t, 10, 1, 3> octet; struct Ipv4Address : qi::grammar<const char *, Ipv4()> { Ipv4Address() : Ipv4Address::base_type(start) { start = ( octet >> qi::lit('.') >> octet >> qi::lit('.') >> octet >> qi::lit('.') >> octet ) [ //qi::_val = make_ipv4(1, 2, 3, 4) // working qi::_val = make_ipv4(qi::_1, qi::_2, qi::_3, qi::_4) // compile error ] ; } qi::rule<const char *, Ipv4()> start; } ipv4_address; int main() { Ipv4 ip; const char * s = "1.2.3.4"; bool r = qi::parse(s, s+strlen(s), ipv4_address, ip); std::cout << r << " " << (int)ip.raw.as_char[0] << "." << (int)ip.raw.as_char[1] << "." << (int)ip.raw.as_char[2] << "." << (int)ip.raw.as_char[3] << std::endl; } When compiling this I get the following compile error: /tmp/ip.cxx: In constructor 'Ipv4Address::Ipv4Address()': /tmp/ip.cxx:26:72: error: cannot convert 'const _1_type {aka const boost::phoenix::actor<boost::spirit::argument<0> >}' to 'uint8_t {aka unsigned char}' for argument '1' to 'Ipv4 make_ipv4(uint8_t, uint8_t, uint8_t, uint8_t)' Any hints? Best regards Allan W. Nielsen On Tue, Jul 3, 2012 at 1:10 PM, Igor R <boost.lists@gmail.com> wrote:
or even simpler: boost::tuple<uint8_t, uint8_t, uint8_t, uint8_t> v; bool r = qi::parse(s.begin(), s.end(), octet >> '.' >> octet >> '.'
octet >> '.' >> octet, v);
while the following works correctly: boost::fusion::vector<uint8_t, uint8_t, uint8_t, uint8_t> v; bool r = qi::parse(s.begin(), s.end(), octet >> '.' >> octet >> '.'
octet >> '.' >> octet, v);
So, the attribute gets converted to boost::tuple is some strange way...
Well, finally got it. You have to #include <boost/fusion/include/boost_tuple.hpp> to get boost::tuple adapted correctly. _______________________________________________ Boost-users mailing list Boost-users@lists.boost.org http://lists.boost.org/mailman/listinfo.cgi/boost-users

On 07/03/2012 02:02 PM, Allan Nielsen wrote:
Hi,
Sorry for not providing the simplest possible code to reproduce my problem, but I'm still new to spirit, I think I will miss some details in the conversion.
I tried add the inclusion you suggested, but it did not help. In the mean while, I found this page: http://boost-spirit.com/home/2010/01/19/how-to-access-attributes-from-semant... which is a very good match on what I would like to do.
The only problem is that I can not make it work...
Here is my second attempt (simper that the first, but not as simple as you suggested... sorry)
#include<string> #include<string.h> #include<iostream> #include<boost/spirit/include/qi.hpp> #include<boost/spirit/include/phoenix_core.hpp> #include<boost/spirit/include/phoenix_operator.hpp>
struct Ipv4 { union { uint32_t as_int; uint8_t as_char[4]; } raw; };
Ipv4 make_ipv4(uint8_t i1, uint8_t i2, uint8_t i3, uint8_t i4) { Ipv4 ip; ip.raw.as_char[0] = i1; ip.raw.as_char[1] = i2; ip.raw.as_char[2] = i3; ip.raw.as_char[3] = i4; return ip; }
namespace qi = boost::spirit::qi; qi::uint_parser<uint8_t, 10, 1, 3> octet;
struct Ipv4Address : qi::grammar<const char *, Ipv4()> { Ipv4Address() : Ipv4Address::base_type(start) { start = ( octet>> qi::lit('.')>> octet>> qi::lit('.')>> octet>> qi::lit('.')>> octet ) [ //qi::_val = make_ipv4(1, 2, 3, 4) // working qi::_val = make_ipv4(qi::_1, qi::_2, qi::_3, qi::_4) // compile error ] ; } qi::rule<const char *, Ipv4()> start; } ipv4_address;
int main() { Ipv4 ip; const char * s = "1.2.3.4"; bool r = qi::parse(s, s+strlen(s), ipv4_address, ip); std::cout<< r<< " "<< (int)ip.raw.as_char[0]<< "."<< (int)ip.raw.as_char[1]<< "."<< (int)ip.raw.as_char[2]<< "."<< (int)ip.raw.as_char[3]<< std::endl; }
When compiling this I get the following compile error:
/tmp/ip.cxx: In constructor 'Ipv4Address::Ipv4Address()': /tmp/ip.cxx:26:72: error: cannot convert 'const _1_type {aka const boost::phoenix::actor<boost::spirit::argument<0> >}' to 'uint8_t {aka unsigned char}' for argument '1' to 'Ipv4 make_ipv4(uint8_t, uint8_t, uint8_t, uint8_t)'
Any hints?
make_ipv4 is an eager function, you need a lazy function, that is only executed when the parsing succeeds. Your commented line works as expected because everything involved in the eager function call has the right type. look into phoenix::bind to make the function call lazy
Best regards Allan W. Nielsen
On Tue, Jul 3, 2012 at 1:10 PM, Igor R<boost.lists@gmail.com> wrote:
or even simpler: boost::tuple<uint8_t, uint8_t, uint8_t, uint8_t> v; bool r = qi::parse(s.begin(), s.end(), octet>> '.'>> octet>> '.'
octet>> '.'>> octet, v); while the following works correctly: boost::fusion::vector<uint8_t, uint8_t, uint8_t, uint8_t> v; bool r = qi::parse(s.begin(), s.end(), octet>> '.'>> octet>> '.' octet>> '.'>> octet, v); So, the attribute gets converted to boost::tuple is some strange way...
Well, finally got it. You have to #include<boost/fusion/include/boost_tuple.hpp> to get boost::tuple adapted correctly. _______________________________________________ Boost-users mailing list Boost-users@lists.boost.org http://lists.boost.org/mailman/listinfo.cgi/boost-users
Boost-users mailing list Boost-users@lists.boost.org http://lists.boost.org/mailman/listinfo.cgi/boost-users

Sorry for not providing the simplest possible code to reproduce my problem, but I'm still new to spirit, I think I will miss some details in the conversion.
I tried add the inclusion you suggested, but it did not help.
Sorry, although it works for boost::tuple, it won't work for your Ipv4 struct... But a simple workaround can solve this issue: in Ipv4Address grammar change attribute to boost::tuple<uint8_t, uint8_t, uint8_t, uint8_t>, and in your parse() function use tuple type as temporary, then copy it to Ipv4 argument (you use a temporary anyway). Like this: bool parse(const const_string& s, Ipv4& i) { boost::tuple<uint8_t, uint8_t, uint8_t, uint8_t> _i; const char * iter = s.begin(); bool r = qi::parse(iter, s.end(), ipv4_address, _i); if( !r || iter != s.end() ) return false; i = Ipv4(_i); return true; }

Thanks, I changed it to a lazy function as suggested, and then it works. Just for the record: struct Ipv4Address : qi::grammar<const char *, Ipv4()> { Ipv4Address() : Ipv4Address::base_type(start) { start = ( octet >> qi::lit('.') >> octet >> qi::lit('.') >> octet >> qi::lit('.') >> octet ) [ qi::_val = phx::bind(&make_ipv4, qi::_1, qi::_2, qi::_3, qi::_4 ) ] ; } qi::rule<const char *, Ipv4()> start; } ipv4_address; On Tue, Jul 3, 2012 at 2:29 PM, Igor R <boost.lists@gmail.com> wrote:
Sorry for not providing the simplest possible code to reproduce my problem, but I'm still new to spirit, I think I will miss some details in the conversion.
I tried add the inclusion you suggested, but it did not help.
Sorry, although it works for boost::tuple, it won't work for your Ipv4 struct... But a simple workaround can solve this issue: in Ipv4Address grammar change attribute to boost::tuple<uint8_t, uint8_t, uint8_t, uint8_t>, and in your parse() function use tuple type as temporary, then copy it to Ipv4 argument (you use a temporary anyway). Like this: bool parse(const const_string& s, Ipv4& i) { boost::tuple<uint8_t, uint8_t, uint8_t, uint8_t> _i; const char * iter = s.begin(); bool r = qi::parse(iter, s.end(), ipv4_address, _i);
if( !r || iter != s.end() ) return false;
i = Ipv4(_i); return true; } _______________________________________________ Boost-users mailing list Boost-users@lists.boost.org http://lists.boost.org/mailman/listinfo.cgi/boost-users
participants (3)
-
Allan Nielsen
-
Igor R
-
Thomas Heller