Как сделать рекурсивный анализатор Spirit X3 с отдельным классом посетителей - PullRequest
1 голос
/ 21 июня 2019

Приложение парсера, в котором я работаю над вызовами рекурсивных правил. Помимо того, что мы рассмотрим учебные примеры по рекурсивному AST Boost Spirit X3, которые можно найти здесь: https://www.boost.org/doc/libs/develop/libs/spirit/doc/x3/html/index.html, Я искал решение с использованием std :: варианта некоторых типов, а также std :: vector того же самого тип варианта.

В посте StackOverflow под названием: Рекурсивное правило в Spirit.X3 я нашел код из ответа из подходящей отправной точки для моего анализатора.

Я повторил здесь код, но я ограничил входные строки для тестирования. Потому что полный список из оригинала не относится к этому вопросу здесь.

//#define BOOST_SPIRIT_X3_DEBUG
#include <iostream>
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/home/x3.hpp>
#include <string>
#include <vector>
#include <variant>

struct value: std::variant<int,float,std::vector<value>>
{
    using base_type = std::variant<int,float,std::vector<value>>;
    using base_type::variant;

    friend std::ostream& operator<<(std::ostream& os, base_type const& v) {
        struct {
            std::ostream& operator()(float const& f) const { return _os << "float:" << f; }
            std::ostream& operator()(int const& i)   const { return _os << "int:" << i; }
            std::ostream& operator()(std::vector<value> const& v) const {
                _os << "tuple: [";
                for (auto& el : v) _os << el << ",";
                return _os << ']';
            }
            std::ostream& _os;
        } vis { os };

        return std::visit(vis, v);
    }
};

namespace parser {
    namespace x3 = boost::spirit::x3;

    x3::rule<struct value_class, value> const value_ = "value";
    x3::rule<struct o_tuple_class, std::vector<value> > o_tuple_ = "tuple";

    x3::real_parser<float, x3::strict_real_policies<float> > float_;

    const auto o_tuple__def = "tuple" >> x3::lit(':') >> ("[" >> value_ % "," >> "]");

    const auto value__def
        = "float" >> (':' >> float_)
        | "int" >> (':' >> x3::int_)
        | o_tuple_
        ;

    BOOST_SPIRIT_DEFINE(value_, o_tuple_)

    const auto entry_point = x3::skip(x3::space) [ value_ ];
}

int main()
{
    for (std::string const str : {
            "float: 3.14",
            "int: 3",
            "tuple: [float: 3.14,int: 3]",
            "tuple: [float: 3.14,int: 3,tuple: [float: 4.14,int: 4]]"
    }) {
        std::cout << "============ '" << str << "'\n";

        //using boost::spirit::x3::parse;
        auto first = str.begin(), last = str.end();
        value val;

        if (parse(first, last, parser::entry_point, val))
            std::cout << "Parsed '" << val << "'\n";
        else
            std::cout << "Parse failed\n";

        if (first != last)
            std::cout << "Remaining input: '" << std::string(first, last) << "'\n";
    }
}

Однако я хотел бы использовать традиционный класс посетителей, а не делать друзей ostream в варианте класса. Вы знаете просто структуру / класс с кучей функциональных объектов для каждого типа, с которым вы сталкиваетесь в варианте, и цикл for для вектора, который вызывает std :: visit для каждого элемент.

Моя цель для традиционного класса посетителей - иметь возможность поддерживать конечный автомат во время печати. ​​

Мои собственные попытки написать этот класс посетителей потерпели неудачу, потому что я столкнулся с проблемой с моим компилятором GCC 8.1. С GCC во время компиляции std :: variable как-то выглядит как std :: variable_size, и я получил следующую ошибку:

error: incomplete type 'std::variant_size' used in nested name specifier

Подробнее об этом здесь: Использование std :: visit для класса, унаследованного от std :: variable - libstdc ++ против libc ++

Возможно ли дать это ограничение в GCC для написания класса посетителя для примера кода, который я включил, чтобы можно было удалить материал ostream?

1 Ответ

2 голосов
/ 21 июня 2019

Возможно ли дать это ограничение в GCC для написания класса посетителя для примера кода, который я включил, чтобы можно было удалить материал ostream?

Конечно. В основном я вижу три подхода:

1. Добавить шаблон машины

Вы можете специализировать детали реализации, случайно требуемые GCC:

struct value: std::variant<int,float,std::vector<value>> {
    using base_type = std::variant<int,float,std::vector<value>>;
    using base_type::variant;
};

namespace std {
    template <> struct variant_size<value> :
std::variant_size<value::base_type> {};
    template <size_t I> struct variant_alternative<I, value> :
std::variant_alternative<I, value::base_type> {};
}

Посмотреть в прямом эфире на Wandbox (GCC 8.1)

2. Не (снова в прямом эфире )

Расширение пространства имен std чревато (хотя я думаю, что это законно для пользовательские типы). Таким образом, вы можете использовать мой любимый шаблон и скрыть e std::visit отправка в сам объект функции:

template <typename... El>
    void operator()(std::variant<El...> const& v) const { std::visit(*this, v); }

Теперь вы можете просто вызвать функтор, и он автоматически отправит на свой собственный тип, производный от варианта, потому что перегрузка operator() делает НЕ есть проблемы, которые есть у GCC stdlib:

    if (parse(first, last, parser::entry_point, val))
    {
        display_visitor display { std::cout };

        std::cout << "Parsed '";
        display(val);
        std::cout << "'\n";
    }

3. Сделайте вещи явными

Мне это нравится меньше всего, но оно имеет свои достоинства: нет магии и нет приемы:

struct value: std::variant<int,float,std::vector<value>> {
    using base_type = std::variant<int,float,std::vector<value>>;
    using base_type::variant;

    base_type const& as_variant() const { return *this; }
    base_type&       as_variant() { return *this; }
};

struct display_visitor {
    void operator()(value const& v) const { std::visit(*this, v.as_variant()); }
     // ...

Опять в прямом эфире

РЕЗЮМЕ

Подумав немного больше, я бы рекомендовал последний подход из-за относительной простоты. Умный - это часто кодовый запах:)

Полный список для будущих посетителей:

//#define BOOST_SPIRIT_X3_DEBUG
#include <iostream>
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/home/x3.hpp>
#include <string>
#include <vector>
#include <variant>

struct value: std::variant<int,float,std::vector<value>> { 
    using base_type = std::variant<int,float,std::vector<value>>;
    using base_type::variant;

    base_type const& as_variant() const { return *this; }
    base_type&       as_variant() { return *this; }
};

struct display_visitor {
    std::ostream& _os;
    void operator()(value const& v) const { std::visit(*this, v.as_variant()); }
    void operator()(float const& f) const { _os << "float:" << f; }
    void operator()(int const& i)   const { _os << "int:" << i; }
    void operator()(std::vector<value> const& v) const { 
        _os << "tuple: [";
        for (auto& el : v) {
            operator()(el);
            _os << ",";
        }
        _os << ']';
    }
};

namespace parser {
    namespace x3 = boost::spirit::x3;

    x3::rule<struct value_class, value> const value_ = "value";
    x3::rule<struct o_tuple_class, std::vector<value> > o_tuple_ = "tuple";

    x3::real_parser<float, x3::strict_real_policies<float> > float_;

    const auto o_tuple__def = "tuple" >> x3::lit(':') >> ("[" >> value_ % "," >> "]");

    const auto value__def
        = "float" >> (':' >> float_)
        | "int" >> (':' >> x3::int_)
        | o_tuple_
        ;

    BOOST_SPIRIT_DEFINE(value_, o_tuple_)

    const auto entry_point = x3::skip(x3::space) [ value_ ];
}

int main()
{
    for (std::string const str : {
        "float: 3.14",
        "int: 3",
        "tuple: [float: 3.14,int: 3]",
        "tuple: [float: 3.14,int: 3,tuple: [float: 4.14,int: 4]]"
    }) {
        std::cout << "============ '" << str << "'\n";

        //using boost::spirit::x3::parse;
        auto first = str.begin(), last = str.end();
        value val;

        if (parse(first, last, parser::entry_point, val))
        {
            display_visitor display { std::cout };

            std::cout << "Parsed '";
            display(val);
            std::cout << "'\n";
        }
        else
            std::cout << "Parse failed\n";

        if (first != last)
            std::cout << "Remaining input: '" << std::string(first, last) << "'\n";
    }
}
...