1

I'm a spirit beginner

I'd like to parse an imperial string value into a struct using spirit.

The input should accept following syntaxes:

    5'3"1/2
    5'1/2
    3"1/2

the struct imp_constant looks like this, please note stream operator below, I'll print results as this operator does:

struct imp_constant
{
    explicit imp_constant(unsigned int feet=0
                         ,unsigned int inch=0
                         ,unsigned int fracn=0
                         ,unsigned int fracd=1)
        :feet_(feet),inches_(inch),fracn_(fracn),fracd_(fracd){}
    unsigned int feet_,inches_,fracn_,fracd_;
};
std::ostream& operator<<(std::ostream& os, imp_constant const& cst)
{
    if (cst.feet_)
        os << cst.feet_ << '\''; 
    if (cst.inches_)    
        os << cst.inches_ << '"';
    if (cst.fracn_)
        os << cst.fracn_ << '/' << cst.fracd_;
    return os;
}

my pseudo grammar is pretty simple and looks like this:

myrule = (
    (
        (qi::uint_ >> L'\'')
        ||
        (qi::uint_ >> L'"') 
    ) 
    >> -(
            qi::uint_ 
            >> L'/' >> 
            qi::uint_
        )
    );

Here is my quite naive first try to populate my struct:

I've added BOOST_FUSION_ADAPT_STRUCT macro to my struct imp_constant then tried following grammar:

qi::rule<std::string::const_iterator, imp_constant()> 
    impconst = qi::lexeme[ //not sure this is required since no skipper precised
        (
            (qi::uint_[phx::at_c<0>(qi::_val)=qi::_1] >> L'\'')
            ||
            (qi::uint_[phx::at_c<1>(qi::_val)=qi::_1] >> L'"') 
        ) 
        >> -(
            qi::uint_[phx::at_c<2>(qi::_val)=qi::_1] 
            >> L'/' >> 
            qi::uint_[phx::at_c<3>(qi::_val)=qi::_1]
            )
        ];

the result is:

input:5'3"1/2  ==> output:5'3"1/2  (ok)
input:5'1/2    ==> output:5'1"1/2  (__nok__)

I guess I don't get how _1 placeholder behaves in that case.

Since I'm beginner in spirit's world, any advice is welcome

Thank you very much

Here is the full code, this should help

#define BOOST_SPIRIT_DONT_USE_MPL_ASSERT_MSG 1
//#define BOOST_SPIRIT_DEBUG << uncomment to enable debug
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/variant/recursive_wrapper.hpp>
#include <boost/fusion/adapted.hpp>

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

struct imp_constant
{
    explicit imp_constant(unsigned int feet=0
                         ,unsigned int inch=0
                         ,unsigned int fracn=0
                         ,unsigned int fracd=1)
        :feet_(feet),inches_(inch),fracn_(fracn),fracd_(fracd){}
    unsigned int feet_,inches_,fracn_,fracd_;
};

std::ostream& operator<<(std::ostream& os, imp_constant const& cst)
{
    if (cst.feet_)
        os << cst.feet_ << '\''; 
    if (cst.inches_)    
        os << cst.inches_ << '"';
    if (cst.fracn_)
        os << cst.fracn_ << '/' << cst.fracd_;
    return os;
}

BOOST_FUSION_ADAPT_STRUCT(imp_constant, 
                          (unsigned int, feet_)
                          (unsigned int, inches_)
                          (unsigned int, fracn_)
                          (unsigned int, fracd_))

int _tmain(int argc, _TCHAR* argv[])
{
    std::string input;

    std::cout << "\n----------------------\n> ";
    while (std::getline(std::cin, input))
    {
        if (input.empty() || input[0] == 'q' || input[0] == 'Q')
            break;

        std::string::const_iterator f(input.begin()),l(input.end());
        try
        {
            imp_constant result;
            std::cout << "parsing: " << input << "\n";
            bool ok;


            qi::rule<std::string::const_iterator, imp_constant()> 
                impconst = qi::lexeme[ //not sure this is required since
                            //no skipper precised
                    (
                        (qi::uint_[phx::at_c<0>(qi::_val)=qi::_1]
                        >> L'\'')
                        ||
                        (qi::uint_[phx::at_c<1>(qi::_val)=qi::_1]
                        >> L'"') 
                        ) 
                        >> -(
                        qi::uint_[phx::at_c<2>(qi::_val)=qi::_1] 
                    >> L'/' >> 
                        qi::uint_[phx::at_c<3>(qi::_val)=qi::_1]
                    )
                ];

            ok=qi::phrase_parse(f,l,impconst ,qi::space,result);

            if (!ok)
                std::cerr << "invalid input\n";
            else
            {

                std::cout << "\n---------------------------\n";
                std::cout << "result="<< result;
            }
        } 
        catch (const qi::expectation_failure<const char *>& e)
        {
            std::cerr << "expect failure at '" 
                      << std::string(e.first, e.last) << "'\n";
        }
        catch (...)
        {
            std::cerr << "parse error\n";
        }
        if (f!=l) std::cerr << "unparsed: '" << std::string(f,l) << "'\n";
        std::cout << "\n-----------------------\n> ";
    }
    std::getchar();
    return 0;
}
loic
  • 185
  • 1
  • 7

2 Answers2

4

As alternatives to sehe's answer(that you should accept):

Your solution would work if you just changed your rule to:

qi::rule<std::string::const_iterator, imp_constant()> 
        impconst = 
                 (
                     (qi::uint_ >> L'\'')[phx::at_c<0>(qi::_val)=qi::_1]
                     ||
                     (qi::uint_ >> L'"')[phx::at_c<1>(qi::_val)=qi::_1]
                 )
                 >> 
                     -(qi::uint_ >> L'/' >> qi::uint_)
                     [phx::at_c<2>(qi::_val)=qi::_1,phx::at_c<3>(qi::_val)=qi::_2]
        ;

How I would do this:

Changing slightly your imp_constant:

struct fraction
{
   unsigned int n_,d_;
};

struct imp_constant
{
    unsigned int feet_,inches_;
    fraction frac_;
};

BOOST_FUSION_ADAPT_STRUCT(fraction,
                          (unsigned int, n_)
                          (unsigned int, d_)
)

BOOST_FUSION_ADAPT_STRUCT(imp_constant, 
                          (unsigned int, feet_)
                          (unsigned int, inches_)
                          (fraction    , frac_)

And then the rule would be:

qi::rule<std::string::const_iterator,unsigned int()> feet = (qi::uint_ >> L'\'') | qi::attr(0);
qi::rule<std::string::const_iterator,unsigned int()> inches = (qi::uint_ >> L'"') | qi::attr(0);
qi::rule<std::string::const_iterator,fraction()> fract = (qi::uint_ >> L'/' >> qi::uint_) | (qi::attr(0)>> qi::attr(1));
qi::rule<std::string::const_iterator, imp_constant()> impconst=feet>>inches>>fract;

Example on LWS.

  • Thank you very much, it works just well! I'll keep in mind the `| qi::attr(...)` as optional-with-default-value trick. – loic Apr 10 '13 at 13:42
  • +1 from me too :) I played with the idea but opted to stay close to the original and explain what happens. (@loic The attribute trick is in fact what I hinted at briefly with "_explicit defaults_ or qi::hold can fix it") – sehe Apr 10 '13 at 18:52
  • @sehe, indeed, the solution user2266005 found is extremely closed to mine! Apologies for missed "explicit defaults" hint, I've discovered spirit a few days ago (btw, I welcome myself in this new world:) and despite I've already read a lot on it, I'm still not comfortable with some concepts! Thank you both for your help, your contribution here and in many other posts make spirit even more attractive to me. – loic Apr 11 '13 at 07:23
2

You're looking at backtracking.

The '1' in 5'1/2 is first parsed as the qi::uint_ in a potential inches branch, causing the attribute to be assigned.

Only then is the '/' encountered, which results in backtracking from the 'inch branch'. But the attribute has already been set. In this case, explicit defaults or qi::hold can fix it.

Here's something like the way I'd probably phrase things myself:

qi::rule<std::string::const_iterator, unsigned(char)> 
    component = qi::hold [ qi::uint_ >> qi::lit(qi::_r1) ];

qi::rule<std::string::const_iterator, imp_constant()> 
    impconst = 
        component(L'\'') ||
        component(L'"')  ||
        component(L'/')  ||
        qi::uint_
;

BOOST_SPIRIT_DEBUG_NODE(component);
BOOST_SPIRIT_DEBUG_NODE(impconst);

I think this could be a good starting point?

Further notes:

  1. test f!=l to detect unparsed remaining input
  2. the semantic actions in the full code sample were redundant
  3. indeed, lexeme was redundant there

See it live here: http://liveworkspace.org/code/Rpydy$0.

Test output:

----------------------
> parsing: 5'1/2
<impconst>
  <try>5'1/2</try>
  <component>
    <try>5'1/2</try>
    <success>1/2</success>
    <attributes>[5, ']</attributes>
  </component>
  <component>
    <try>1/2</try>
    <fail/>
  </component>
  <component>
    <try>1/2</try>
    <success>2</success>
    <attributes>[1, /]</attributes>
  </component>
  <success></success>
  <attributes>[[5, 0, 1, 2]]</attributes>
</impconst>

---------------------------
result=5'1/2
-----------------------
> 
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Hi sehe, first, thank you very much for your very fast and complete answer (and btw for all other answers you posted on stackoverflow - I've learnt so much reading them). It looks good indeed. Unfortunately, the hold directive complains about a `could not deduce template argument for boost::spirit::multi_pass<...> from 'unsigned int'` around swap. tested with both vs9 and vs10, any idea? Thanks again – loic Apr 10 '13 at 12:48
  • @loic Ah. I found out that `qi::lazy` with the old phoenix code is apparently broken for clang/libc++ (and presumably for MSVC likewise). Adding `#define BOOST_SPIRIT_USE_PHOENIX_V3` before all includes resolves that issue for Clang. I suggest you add it and see how it goes for you. See [http://liveworkspace.org/code/Rpydy$1](http://liveworkspace.org/code/Rpydy$1) too, where you can verify it works for GCC and Clang – sehe Apr 10 '13 at 18:49
  • unfortunately it does not fix the build: `error C2784: 'void boost::spirit::swap(boost::spirit::multi_pass &,boost::spirit::multi_pass &)' : could not deduce template argument for 'boost::spirit::multi_pass &' from 'unsigned int'`. Sad not being able to test your solution which I find cleaner than others. – loic Apr 11 '13 at 07:12