Как сделать значение токена 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,


Есть ли способ решить эту проблему?

2

Решение

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

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

именно так, как предложено jv_ в его (или ее) комментарий.

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>).

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

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

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).

1

Другие решения

Других решений пока нет …

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector