Search code examples
boost-spirit-qi

Creating a reusable grammar with Spirit QI


I am newbie in C++, althouth I need to parser a SQL-like expression: country='USA' AND state='CA' AND price >= 100.0, it is just a fake example, but it's feasible.

So, I tried to solve using spirit QI. Each column has a specific type: float, integer, double, char and string. I wanna create a reusable grammar to support those column types with template or/and trait, but I'm blocked.

I want something like that:

template <typename Iterator, typename ColumnType, typename Skipper>
struct test : qi::grammar<Iterator, ColumnType, Skipper>
{
    test() : test::base_type(expression)
    {
        expression = MyTrait<ColumnType>::type >> "AND" >> MyTrait<ColumnType>::type;
    }

    qi::rule<Iterator, ColumnType, Skipper> expression;
};

I tried to use the Signature template parameter, but didn't work.

MyTrait.h: using namespace boost::spirit::qi;

template< typename T >
struct MyTrait
{
    typedef T type;
};

template<> struct MyTrait< double >
{
    typedef double_type type;
};

template<> struct MyTrait< float >
{
    typedef float_type type;
};

The basic idea in MyTrait is just to convert primitive types (double, float...) to spirit QI types, in order to use them in the grammar. Note that MyTrait<\double>::type results in double_type from QI, but in the grammar must be double_.

main.cpp:

int main() {
    std::string input("99.0 AND 2.0");
    std::string::const_iterator iter = input.begin();
    std::string::const_iterator end = input.end();
    test<std::string::const_iterator, double, qi::space_type> test_parser;
    double result;
    bool r = phrase_parse(iter, end, test_parser, qi::space, result);
    return 0;
 }

Am I on the right track?

Follow compiler error messages as required by Chris Beck:

g++-4.8 -I/usr/include/boost -O0 -g3 -Wall -c -fmessage-length=0 --std=c++11  -fpermissive -MMD -MP -MF"src/main.d" -MT"src/main.o" -o "src/main.o" "../src/main.cpp"
../src/main.cpp: In instantiation of ‘test<Iterator, ColumnType, Skipper>::test() [with Iterator = __gnu_cxx::__normal_iterator<const char*, std::basic_string<char> >; ColumnType = double; Skipper = boost::proto::exprns_::expr<boost::proto::tagns_::tag::terminal, boost::proto::argsns_::term<boost::spirit::tag::char_code<boost::spirit::tag::space, boost::spirit::char_encoding::standard> >, 0l>]’:
../src/main.cpp:91:60:   required from here
../src/main.cpp:76:57: error: dependent-name ‘MyTrait<ColumnType>::type’ is parsed as a non-type, but instantiation yields a type
     expression = MyTrait<ColumnType>::type >> "AND" >> MyTrait<ColumnType>::type;
                                                     ^
../src/main.cpp:76:57: note: say ‘typename MyTrait<ColumnType>::type’ if a type is meant
../src/main.cpp:76:48: error: dependent-name ‘MyTrait<ColumnType>::type’ is parsed as a non-type, but instantiation yields a type
     expression = MyTrait<ColumnType>::type >> "AND" >> MyTrait<ColumnType>::type;
                                            ^
../src/main.cpp:76:48: note: say ‘typename MyTrait<ColumnType>::type’ if a type is meant

Solution

  • As far as I understand you want to automatically hardwire a particular parser to a given result_type. Your going to give up much of qi's flexibility in doing so. I recommend reviewing your approach at all.

    The code sample below does what I understood so far what you want to achieve.

    But (!) the actual result of the parsing will not be float or what but a vector. There are two floats in your input string. One of that gets lost. I did not do anything to prevent that.

    And (!) the code below does not address mix and match of different types (columns) you might want to parse. I did not do anything to make that possible.

    The code below just plainly adresses the input you gave. It compiles and I hope this helps to climb the next step. ;)

    #include <boost/spirit/home/qi.hpp>
    #include <boost/utility/enable_if.hpp>
    
    namespace qi = boost::spirit::qi;
    
    template <typename T>
    typename boost::enable_if<boost::is_same<T,double>, qi::double_type>::type
    my_NOT_A_trait()
    {
        return qi::double_type();
    };
    
    template <typename T>
    typename boost::enable_if<boost::is_same<T, float>, qi::float_type>::type
    my_NOT_A_trait()
    {
        return qi::float_type();
    };
    
    template <typename Iterator, typename ColumnType>
    struct test : qi::grammar<Iterator, ColumnType(), qi::space_type>
    {
        qi::rule<Iterator, ColumnType(), qi::space_type> expression;
    
        test() : test::base_type(expression)
        {
            expression = my_NOT_A_trait<ColumnType>() >> qi::lit("AND") >> my_NOT_A_trait<ColumnType>();
        }
    
    };
    
    template<typename Iterator, typename Skipper, typename Result>
    bool parse(Iterator first, Iterator const& last, Skipper const& skipper, Result& result)
    {
        return qi::phrase_parse(first, last, test<Iterator,Result>(), qi::space, result);
    }
    
    int main()
    {
        float result;
    
        std::string input = "99.0 AND 2.0";
        auto b(input.begin()), e(input.end());
    
        if (parse(b, e, qi::space, result))
            std::cout << "Success." << std::endl;
        else
            std::cout << "Failure." << std::endl;
    
        return 0;
    }