Search code examples
c++qtboostboost-spiritqstring

How can I use boost::spirit X3 in conjunction with QString?


Boost has a library named Spirit that makes heavy use of TMP in order to facilitate the easy creation of parsers in a BNF like syntax. By default it is intended to work with standard library strings. I'm using it in a Qt based project, where QString is the primary string type. How can I use boost::spirit with QStrings?


Solution

  • There are two parts to making boost::spirit and QString cooperate; getting data in and getting data out. We'll start with the latter. As far as boost::spirit is concerned, QString is an unknown type. We must give it a few container traits so that it knows how to interact:

    #include <boost/spirit/home/x3/support/traits/container_traits.hpp>
    #include <QString>
    
    namespace boost { namespace spirit { namespace x3 { namespace traits {
    
    template<>
    struct push_back_container<QString>
    {
        template<typename T>
        static bool call(QString& c, T&& val)
        {
            c.push_back(std::move(val));
            return true;
        }
    };
    
    template<>
    struct append_container<QString>
    {
        template<typename Iterator>
        static bool call(QString& c, Iterator first, Iterator last)
        {
            c.append(first, std::distance(first, last));
            return true;
        }
    };
    
    template<>
    struct is_empty_container<QString>
    {
        static bool call(QString const& c)
        {
            return c.isEmpty();
        }
    };
    
    }}}}
    

    Depending on your particular use case, you may also want to parse from QStrings. There is probably a way to teach boost::spirit about QChar (the iterable type of QString), but I don't have the patience for that. The alternative is to create an iterator_adaptor whose value type is char16_t. Although uint32_t is the type boost::spirit uses to represent unicode values, it can't be unambiguously converted to QChar whereas char16_t can. QString operates on a UTF16 basis, so this type is sufficiently wide enough. Note you'll need to turn unicode support on (#define BOOST_SPIRIT_X3_UNICODE).

    #include <boost/iterator/iterator_adaptor.hpp>
    #include <QString>
    
    template<typename QStringIterator>
    class QStringSpiritUnicodeIteratorAdaptor : public boost::iterator_adaptor<
        QStringSpiritUnicodeIteratorAdaptor<QStringIterator>,
        QStringIterator, char16_t, boost::forward_traversal_tag, char16_t>
    {
    public:
        using boost::iterator_adaptor<
            QStringSpiritUnicodeIteratorAdaptor<QStringIterator>,
            QStringIterator, char16_t, boost::forward_traversal_tag,
            char16_t>::iterator_adaptor;
    
    private:
        friend class boost::iterator_core_access;
        char16_t dereference() const
        {
            return static_cast<char16_t>(this->base_reference()->unicode());
        }
    };
    
    using QStringSpiritUnicodeIterator =
        QStringSpiritUnicodeIteratorAdaptor<QString::iterator>;
    using QStringSpiritUnicodeConstIterator =
        QStringSpiritUnicodeIteratorAdaptor<QString::const_iterator>;
    

    This is used as follows:

    QString text = "blah blah blah";
    QStringSpiritUnicodeIterator begin(text.begin());    
    QStringSpiritUnicodeIterator end(text.end());
    
    ...::x3::phrase_parse(begin, end, ...);