Как сделать значение токена Boost.Spirit.Lex подстрокой согласованной последовательности (желательно с помощью группы соответствия регулярных выражений)

Пишу простой парсер выражений. Он построен на грамматике Boost.Spirit.Qi на основе токенов Boost.Spirit.Lex (Boost в версии 1.56).

Токены определяются следующим образом:

using namespace boost::spirit;

template<
    typename lexer_t
>
struct tokens
    : lex::lexer<lexer_t>
{
    tokens()
        : /* ... */,
          variable("%(\\w+)")
    {
        this->self =
            /* ... */ |
            variable;
    }

    /* ... */
    lex::token_def<std::string> variable;
};

Теперь я хотел бы, чтобы значение токена variable было просто именем (соответствующая группа (\\w+)) без символа префикса %. Как я могу это сделать?


Использование соответствующей группы само по себе не помогает. По-прежнему значение - полная строка, включая префикс %.

Есть ли способ принудительно использовать соответствующую группу?

Или в хоть как-то ссылаться на это в рамках действия токена?


Я также пробовал использовать такое действие:

variable[lex::_val = std::string(lex::_start + 1, lex::_end)]

но скомпилировать не удалось. Ошибка утверждала, что ни одна из перегрузок конструктора std::string не может соответствовать аргументам:

(const boost::phoenix::actor<Expr>, const boost::spirit::lex::_end_type)

Еще проще

variable[lex::_val = std::string(lex::_start, lex::_end)]

не удалось скомпилировать. По той же причине теперь только первый тип аргумента был boost::spirit::lex::_start_type.


Наконец, я попробовал это (хотя это выглядит как большая трата):

lex::_val = std::string(lex::_val).erase(0, 1)

но это тоже не удалось скомпилировать. На этот раз компилятор не смог преобразовать const boost::spirit::lex::_val_type в std::string.


Есть ли способ справиться с этой проблемой?


person Adam Badura    schedule 18.02.2016    source источник
comment
Для ваших простых примеров ищите phoenix::construct, для чего-нибудь более сложного phoenix::function (возможно, с адаптирующимся макросом) будет легче работать.   -  person llonesmiz    schedule 18.02.2016


Ответы (1)


Простое решение

Правильная форма построения значения атрибута std::string следующая:

variable[lex::_val = boost::phoenix::construct<std::string>(lex::_start + 1, lex::_end)]

точно так, как предлагает jv_ в его (или ее) comment.

boost::phoenix::construct предоставляется заголовком <boost/phoenix/object/construct.hpp>. Или используйте <boost/phoenix.hpp>.

Решение с регулярным выражением

Однако вышеприведенное решение хорошо работает только в простых случаях. И исключает возможность получения шаблона извне (в частности, из данных конфигурации). Так как изменение шаблона, например, на %(\\w+)% потребует изменения кода построения значения.

Вот почему было бы гораздо лучше иметь возможность ссылаться на группы захвата из регулярного выражения, определяющего токен.

Теперь обратите внимание, что это все еще не идеально, поскольку странные случаи, такие как %(\\w+)%(\\w+)%, по-прежнему требуют изменения кода для правильной обработки. Это можно обойти, настроив не только регулярное выражение для токена, но и средства формирования значения из согласованного диапазона. Но это выходит за рамки вопроса. Прямое использование групп захвата кажется достаточно гибким для многих случаев.

sehe в комментарий в другом месте указано, что нет способа использовать группы захвата из регулярного выражения токена . Не говоря уже о том, что токены на самом деле поддерживают только подмножество регулярных выражений. (Среди заметных различий, например, отсутствие поддержки именования групп захвата или их игнорирования!).

Мои собственные эксперименты в этой области также подтверждают это. К сожалению, нет возможности использовать группы захвата. Однако есть обходной путь - вам нужно просто повторно применить регулярное выражение в своем действии.

Действие Получение диапазона захвата

Чтобы сделать его немного модульным, давайте начнем с простейшей задачи - действия, которое возвращает boost::iterator_range часть совпадения токена, соответствующую указанному захвату.

template<typename Attribute, typename Char, typename Idtype>
class basic_get_capture
{
public:
    typedef lex::token_def<Attribute, Char, Idtype> token_type;
    typedef boost::basic_regex<Char> regex_type;

    explicit basic_get_capture(token_type const& token, int capture_index = 1)
        : token(token),
          regex(),
          capture_index(capture_index)
    {
    }

    template<typename Iterator, typename IdType, typename Context>
    boost::iterator_range<Iterator> operator ()(Iterator& first, Iterator& last, lex::pass_flags& /*flag*/, IdType& /*id*/, Context& /*context*/)
    {
        typedef boost::match_results<Iterator> match_results_type;

        match_results_type results;
        regex_match(first, last, results, get_regex());
        typename match_results_type::const_reference capture = results[capture_index];
        return boost::iterator_range<Iterator>(capture.first, capture.second);
    }

private:
    regex_type& get_regex()
    {
        if(regex.empty())
        {
            token_type::string_type const& regex_text = token.definition();
            regex.assign(regex_text);
        }
        return regex;
    }

    token_type const& token;
    regex_type regex;
    int capture_index;
};

template<typename Attribute, typename Char, typename Idtype>
basic_get_capture<Attribute, Char, Idtype> get_capture(lex::token_def<Attribute, Char, Idtype> const& token, int capture_index = 1)
{
    return basic_get_capture<Attribute, Char, Idtype>(token, capture_index);
}

Действие использует Boost.Regex (включая <boost/regex.hpp>).

Действие Получение записи в виде строки

Теперь, когда диапазон захвата - это хорошо, поскольку он не выделяет никакой новой памяти для строки, в конце концов, это строка, которую мы хотим в конечном итоге. Итак, здесь другое действие основано на предыдущем.

template<typename Attribute, typename Char, typename Idtype>
class basic_get_capture_as_string
{
public:
    typedef basic_get_capture<Attribute, Char, Idtype> basic_get_capture_type;
    typedef typename basic_get_capture_type::token_type token_type;

    explicit basic_get_capture_as_string(token_type const& token, int capture_index = 1)
        : get_capture_functor(token, capture_index)
    {
    }

    template<typename Iterator, typename IdType, typename Context>
    std::basic_string<Char> operator ()(Iterator& first, Iterator& last, lex::pass_flags& flag, IdType& id, Context& context)
    {
        boost::iterator_range<Iterator> const& capture = get_capture_functor(first, last, flag, id, context);
        return std::basic_string<Char>(capture.begin(), capture.end());
    }

private:
    basic_get_capture_type get_capture_functor;
};

template<typename Attribute, typename Char, typename Idtype>
basic_get_capture_as_string<Attribute, Char, Idtype> get_capture_as_string(lex::token_def<Attribute, Char, Idtype> const& token, int capture_index = 1)
{
    return basic_get_capture_as_string<Attribute, Char, Idtype>(token, capture_index);
}

Здесь нет магии. Мы просто делаем std::basic_string из диапазона, возвращаемого более простым действием.

Действие Назначение значения из захвата

Действия, возвращающие значение, для нас малопригодны. Конечная цель - установить значение токена из захвата. И это делается последним действием.

template<typename Attribute, typename Char, typename Idtype>
class basic_set_val_from_capture
{
public:
    typedef basic_get_capture_as_string<Attribute, Char, Idtype> basic_get_capture_as_string_type;
    typedef typename basic_get_capture_as_string_type::token_type token_type;

    explicit basic_set_val_from_capture(token_type const& token, int capture_index = 1)
        : get_capture_as_string_functor(token, capture_index)
    {
    }

    template<typename Iterator, typename IdType, typename Context>
    void operator ()(Iterator& first, Iterator& last, lex::pass_flags& flag, IdType& id, Context& context)
    {
        std::basic_string<Char> const& capture = get_capture_as_string_functor(first, last, flag, id, context);
        context.set_value(capture);
    }

private:
    basic_get_capture_as_string_type get_capture_as_string_functor;
};

template<typename Attribute, typename Char, typename Idtype>
basic_set_val_from_capture<Attribute, Char, Idtype> set_val_from_capture(lex::token_def<Attribute, Char, Idtype> const& token, int capture_index = 1)
{
    return basic_set_val_from_capture<Attribute, Char, Idtype>(token, capture_index);
}

Обсуждение

Действия используются так:

variable[set_val_from_capture(variable)]

При желании вы можете указать второй аргумент, являющийся используемым индексом захвата. По умолчанию это 1, что кажется подходящим в большинстве случаев.

Создание функций

set_val_from_capture (или get_capture_as_string или get_capture соответственно) - это вспомогательная функция, используемая для автоматического вывода аргументов шаблона из token_def. В частности, нам нужен тип Char для создания соответствующего регулярного выражения.

Я не уверен, можно ли этого избежать, и даже если так, это значительно усложнит оператор вызова (особенно если мы будем стремиться кэшировать объект регулярного выражения, а не строить его каждый раз заново). Мои сомнения возникают в основном из-за того, что я не уверен, должен ли Char тип token_def совпадать с типом символа токенизированной последовательности или нет. Я предположил, что они не обязательно должны быть одинаковыми.

Повторение токена

Определенно неприятной частью действия является необходимость предоставить сам токен в качестве аргумента, который повторяется.

Однако токен необходим для типа Char, как описано выше и, чтобы ... получить регулярное выражение!

Мне кажется, что по крайней мере теоретически мы могли бы каким-то образом получить токен «во время выполнения» на основе id аргумента действия (который мы сейчас просто игнорируем). Однако мне не удалось найти способ получить token_def на основе идентификатора токена независимо от того, из аргумента context или самого лексера (который может быть передан в действие как this через функцию создания).

Возможность повторного использования

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

Сначала я пытался добиться чего-то вроде этого:

variable[lex::_val = get_capture_as_string(variable)]

Он кажется более гибким, так как вы можете легко добавить вокруг него больше кода - например, обернуть его в некоторую функцию преобразования.

Но мне не удалось этого добиться. Хотя мне кажется, что я недостаточно старался. Здесь очень поможет узнать больше о Boost.Phoenix.

Двойная работа

Все эти обходные пути не мешают нам выполнять двойную работу. Оба при разборе регулярных выражений и затем сопоставлении. Но, как упоминалось в начале, кажется, что нет лучшего способа (без изменения самого Boost.Spirit).

person Adam Badura    schedule 15.09.2016
comment
Хороший ответ. Здесь представлен подход, который использует дополнительное состояние лексера для работы с отношениями префикс-имя_переменной. Кажется, это работает, но я основывал это только на моем ограниченном понимании некоторых примеров и тестов в раздаче ускорения, поэтому я не могу быть уверен, что это правильно. - person llonesmiz; 15.09.2016
comment
@jv_ Интересно, спасибо. Почему-то я боюсь прямых манипуляций с состоянием лексера. Но всегда лучше знать другие подходы! - person Adam Badura; 16.09.2016
comment
@jv_ Я собирался. Но потом я подумал, по крайней мере, попытаться улучшить его, для чего я уделю себе немного больше времени (в последнее время я довольно занят). Тем временем, угрюмо поможет узнать больше о Boost.Spirit и Boost.Phoenix! - person Adam Badura; 19.09.2016
comment
Отличное отношение. Удачи в ваших усилиях. - person llonesmiz; 19.09.2016