1

I am trying to parse the numeric value from a string with non-digits in between. Is it possible to do it with boost spirit? For example,

std::string s = "AB1234xyz5678C9";
int x = 0;
boost::spirit::qi::parse(s.begin(), s.end(), /* Magic Input */, x);
// x will be equal 123456789
John Zwinck
  • 239,568
  • 38
  • 324
  • 436
alex
  • 193
  • 2
  • 8
  • 2
    I'm not familiar with boot::spirit. You can do it without boost at all: http://ideone.com/Fxdzg6 – Jonas Feb 03 '17 at 08:27

3 Answers3

4

A bit of a hack:

weird_num = as_string[ skip(alpha) [+digit] ] [_val = stol_(_1) ];

This requires you to adapt std::stol for use in the semantic action:

Live On Coliru

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

BOOST_PHOENIX_ADAPT_FUNCTION(long, stol_, std::stol, 1);
namespace qi = boost::spirit::qi;

int main()
{
    std::string const s = "AB1234xyz5678C9";
    qi::rule<std::string::const_iterator, long()> weird_num;

    {
        using namespace qi;
        weird_num = as_string[ skip(alpha) [+digit] ] [_val = stol_(_1) ];
    }

    long x = 0;
    if (boost::spirit::qi::parse(s.begin(), s.end(), weird_num, x))
        std::cout << "Got it: " << x << "\n";
}

Prints

Got it: 123456789
sehe
  • 374,641
  • 47
  • 450
  • 633
  • very nice. I was thinking about this task, but my variant is worse, than yours. +1. – ForEveR Feb 03 '17 at 09:18
  • @ForEveR Thanks. It's still tedious, and not actually "using Spirit" (just integrates with it). Your sample is quite interesting. Could be more generic: http://coliru.stacked-crooked.com/a/8b8240db04f8d003 :) – sehe Feb 03 '17 at 09:55
  • it's very nice, thanks guys, i wonder if the intermediate string can be removed. A hand-crafted loop to check the char one by one then multiply by 10 should run faster. – alex Feb 03 '17 at 10:08
  • Yes. This is not optimal. But what is optimal depends a lot on your context. I can't _imagine_ a grammar where this would actually be useful, but other than that, you could use a filtering iterator or stream – sehe Feb 03 '17 at 10:43
1

I think that can be done easily, however, this is working variant using boost::spirit.

#include <string>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/spirit/include/qi.hpp>

struct integer_collector
{
public:
   void collect(int v) const
   {
      stream << v;
   }

   int get() const
   {
      int result = 0;
      stream >> result;
      return result; 
   }
private:
   mutable std::stringstream stream;
};

int main()
{
   using namespace boost::spirit::qi;
   std::string s = "AB1234xyz5678C9";
   integer_collector collector;
   int x = 0;
   boost::spirit::qi::parse(s.begin(), s.end(),    
   *(omit[*alpha] >> int_ >> omit[*alpha])
   [boost::bind(&integer_collector::collect, boost::ref(collector), 
   boost::placeholders::_1)]);
   x = collector.get();
   std::cout << x << std::endl;
}

live

ForEveR
  • 55,233
  • 2
  • 119
  • 133
1

Here's one way:

namespace qi = boost::spirit::qi;

std::string s = "AB1234xyz5678C9";
int x = 0;
auto f = [&x](char c){ if (::isdigit(c)) x = x * 10 + (c - '0'); };

qi::parse(s.begin(), s.end(), +(qi::char_[f]));

[EDIT] Or, without isdigit:

auto f = [&x](char c){ x = x * 10 + (c - '0'); };

qi::parse(s.begin(), s.end(), +(qi::char_("0-9")[f] | qi::char_));

[EDIT 2] Or, without a lambda:

#include "boost\phoenix.hpp"
...

namespace phx=boost::phoenix;

qi::parse(s.begin(), s.end(),+(qi::char_("0-9")
      [phx::ref(x) = phx::ref(x) * 10 + qi::_1 - '0'] | qi::char_));

[EDIT 3] Or, with a recursive rule:

qi::rule<std::string::iterator, int(int)> skipInt =
    (qi::char_("0-9")[qi::_val = qi::_r1 * 10 + (qi::_1 - '0')]
    | qi::char_[qi::_val = qi::_r1])
       >> -skipInt(qi::_val)[qi::_val = qi::_1];

qi::parse(s.begin(), s.end(), skipInt(0), x);
Boris Glick
  • 202
  • 1
  • 4