Простое решение
Правильная форма построения значения атрибута 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
phoenix::construct
, для чего-нибудь более сложногоphoenix::function
(возможно, с адаптирующимся макросом) будет легче работать. - person llonesmiz   schedule 18.02.2016