[Boost-users] boost-spirit: need help with simple parser

131 views
Skip to first unread message

Allan Nielsen

unread,
Jul 3, 2012, 6:04:31 AM7/3/12
to boost...@lists.boost.org
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
_______________________________________________
Boost-users mailing list
Boost...@lists.boost.org
http://lists.boost.org/mailman/listinfo.cgi/boost-users

Igor R

unread,
Jul 3, 2012, 6:46:19 AM7/3/12
to boost...@lists.boost.org
> 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.

Igor R

unread,
Jul 3, 2012, 7:00:56 AM7/3/12
to boost...@lists.boost.org
> 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...

Igor R

unread,
Jul 3, 2012, 7:10:59 AM7/3/12
to boost...@lists.boost.org
> 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.

Allan Nielsen

unread,
Jul 3, 2012, 8:02:14 AM7/3/12
to boost...@lists.boost.org
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-semantic-actions/
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


Igor R

unread,
Jul 3, 2012, 8:29:19 AM7/3/12
to boost...@lists.boost.org
> 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;

Allan Nielsen

unread,
Jul 3, 2012, 8:39:49 AM7/3/12
to boost...@lists.boost.org
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;


Thomas Heller

unread,
Jul 3, 2012, 8:24:08 AM7/3/12
to boost...@lists.boost.org
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
Reply all
Reply to author
Forward
0 new messages