Search code examples
c++parsingtemplatesboost-spiritboost-fusion

Boost spirit parse integer to custom list template


I have trouble with boost spirit to parse a file like that :

int       [int, int, int] [ int, int]

...

Nothing really hard, the following grammar works for that:

template<typename Iterator>
struct parser_expression : qi::grammar<Iterator,ascii::space_type> 
{
    parser_expression() : parser_expression::base_type(start) {
        using qi::double_;
        using qi::int_;
        using boost::spirit::qi::char_; 
        using qi::alpha;
        using qi::alnum;
        using qi::digit;
        using qi::eps;
        using qi::_val;
        using boost::phoenix::bind;

        start = int_ >> list1 >> list2 >> char_('=');

        list1 = ('[' >> int_ >> *(char_(',') >> int_ ) >> char_(']')) | (char_('[') >> char_(']'));
        list2 = ('[' >> int_ >> *(char_(',') >> int_ ) >> char_(']')) | -(char_('[') >> char_(']'));
    }



    qi::rule<Iterator,ascii::space_type> start;

    qi::rule<Iterator,ascii::space_type> list1;
    qi::rule<Iterator,ascii::space_type> list2;
};

My problem is that I need to save the result of parsing. For example, I need to save the list1 and list2 of int to a custom list template :

template <typename T>
class SimpleLinkList {
private:
    ChainLink<T>* head;

...
}

where ChainLink is :

template<typename T>
class ChainLink {
private:
    T object;
    ChainLink* next;
...
}

I have a method pushback in SimpleLinkList just like vector but I don't understand how to parse int, save it to a ChainLink and add it to a SimpleLinkList.

I've already seen how to adapt a template structure to a fusion sequence at http://boost-spirit.com/home/2010/02/08/how-to-adapt-templates-as-a-fusion-sequence/.

I need a custom LinkList to be able to delete and add items while looping on it.

I need help to understand how I can arrange all of that to successfully parse my file.

Thx for your help.


Solution

  • You're probably looking for the container attribute customization points:

    For your type, it would look like:

    namespace boost { namespace spirit { namespace traits {
        template <typename T>
            struct container_value<SimpleLinkList<T>, void> {
                typedef T type;
            };
    
        template <typename T>
            struct push_back_container<SimpleLinkList<T>, T, void> {
                static bool call(SimpleLinkList<T>& c, T const& val) {
                    c.push_back(val);
                    return true;
                }
            };
    }}}
    

    A simple demonstration (using a dummy implementation of SimpleLinkList):

    struct AbstractDataType
    {
        int number;
        SimpleLinkList<int> list1, list2;
    };
    
    BOOST_FUSION_ADAPT_STRUCT(AbstractDataType, (int, number)(SimpleLinkList<int>, list1)(SimpleLinkList<int>, list2))
    
    template<typename Iterator>
    struct parser_expression : qi::grammar<Iterator, AbstractDataType(), qi::space_type> 
    {
        parser_expression() : parser_expression::base_type(start) 
        {
            list  = '[' >> -(qi::int_ % ',') >> ']';
            start = qi::int_ >> list >> -list >> '=';
    
            BOOST_SPIRIT_DEBUG_NODES((list)(start))
        }
    
        qi::rule<Iterator, AbstractDataType(),    qi::space_type> start;
        qi::rule<Iterator, SimpleLinkList<int>(), qi::space_type> list;
    };
    

    Note

    • I replaced qi::char_ by (implicit) qi::lit where possible, because you don't actually want to parse the punctuation characters into the attribute (right?)
    • I used the list parser operator % instead of the verbose alternative
    • I used parser operator - to make the element list optional (allow zero elements)
    • Similarly used list >> -list to make the second list optional alltogether.

    The following testcases:

    void test(const std::string input)
    {
        static const parser_expression<std::string::const_iterator> p;
    
        AbstractDataType parsed;
        auto f(input.begin()), l(input.end());
        bool ok = qi::phrase_parse(f, l, p, qi::space, parsed);
    
        if (ok)
            std::cout << "Result: " << parsed.number << " " << parsed.list1 << parsed.list2 << "\n";
        else
            std::cout << "Parse failed\n";
    
        if (f!=l)
            std::cout << "Unparsed: '" << std::string(f,l) << "'\n";
    }
    
    int main()
    {
        test("1 [2, 3, 4] [5, 6] =");
        test("2 []        [6, 7] =");
        test("3 [4, 5, 6] [    ] =");
        test("4 [5, 6, 7]        =");
    }
    

    Print the output:

    Result: 1 [2 3 4 ][5 6 ]
    Result: 2 [][6 7 ]
    Result: 3 [4 5 6 ][]
    Result: 4 [5 6 7 ][]
    

    See it all integrated: http://ideone.com/odqhBz. Preventing linkrot:

    // #define BOOST_SPIRIT_DEBUG
    #include <boost/fusion/adapted.hpp>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    
    namespace qi = boost::spirit::qi;
    
    template <typename T> struct ChainLink;
    
    template <typename T>
    class SimpleLinkList {
      public:
        void push_back(T const& v) { /* todo */ _for_debug.push_back(v); }
    
        std::list<int> _for_debug;
        friend std::ostream& operator<<(std::ostream& os, SimpleLinkList const& list) {
            os << "["; std::copy(list._for_debug.begin(), list._for_debug.end(), std::ostream_iterator<T>(os, " ")); return os << "]";
        }
      private:
        ChainLink<T>* head;
    };
    
    namespace boost { namespace spirit { namespace traits {
        template <typename T>
            struct container_value<SimpleLinkList<T>, void> {
                typedef T type;
            };
    
        template <typename T>
            struct push_back_container<SimpleLinkList<T>, T, void> {
                static bool call(SimpleLinkList<T>& c, T const& val) {
                    c.push_back(val);
                    return true;
                }
            };
    }}}
    
    struct AbstractDataType
    {
        int number;
        SimpleLinkList<int> list1, list2;
    };
    
    BOOST_FUSION_ADAPT_STRUCT(AbstractDataType, (int, number)(SimpleLinkList<int>, list1)(SimpleLinkList<int>, list2))
    
    template<typename Iterator>
    struct parser_expression : qi::grammar<Iterator, AbstractDataType(), qi::space_type> 
    {
        parser_expression() : parser_expression::base_type(start) 
        {
            list  = '[' >> -(qi::int_ % ',') >> ']';
            start = qi::int_ >> list >> -list >> '=';
    
            BOOST_SPIRIT_DEBUG_NODES((list)(start))
        }
    
        qi::rule<Iterator, AbstractDataType(),    qi::space_type> start;
        qi::rule<Iterator, SimpleLinkList<int>(), qi::space_type> list;
    };
    
    void test(const std::string input)
    {
        static const parser_expression<std::string::const_iterator> p;
    
        AbstractDataType parsed;
        auto f(input.begin()), l(input.end());
        bool ok = qi::phrase_parse(f, l, p, qi::space, parsed);
    
        if (ok)
            std::cout << "Result: " << parsed.number << " " << parsed.list1 << parsed.list2 << "\n";
        else
            std::cout << "Parse failed\n";
    
        if (f!=l)
            std::cout << "Unparsed: '" << std::string(f,l) << "'\n";
    }
    
    int main()
    {
        test("1 [2, 3, 4] [5, 6] =");
        test("2 []        [6, 7] =");
        test("3 [4, 5, 6] [    ] =");
        test("4 [5, 6, 7]        =");
    }