Spirit.Lexочень модульный, который следует общему принципу построения библиотекSpirit. Вы никогда не платите за функции, которые не используете. Он хорошо интегрирован с другими частямиSpirit, но тем не менее может использоваться отдельно для создания отдельных лексических анализаторов. Первый пример быстрого запуска описывает отдельно стоящее приложение: подсчет символов, слов и строк в файле, очень похожий на то, что делает хорошо известная команда Unix<wc
>(для полного примера кода см. здесь:word_count_functor.cpp)..
Для этого требуется только<#include
>Дух.Лексследует. Это обертка для всех необходимых определений, чтобы использоватьSpirit.Lexв одиночку, и поверх библиотекиLexertl. Для этого мы используем два из них:<boost::bind()
>и<boost::ref()
>.
#include <boost/spirit/include/lex_lexertl.hpp>
#include <boost/bind.hpp>
#include <boost/ref.hpp>
Чтобы сделать весь код ниже более читаемым, мы вводим следующие пространства имен.
namespace lex = boost::spirit::lex;
Наиболее важным шагом при создании лексера с использованиемSpirit.Lexявляется определение токенов, которые должны быть распознаны во входной последовательности. Обычно это делается путем определения регулярных выражений, описывающих соответствующие последовательности символов, и, необязательно, их соответствующих идентификаторов токенов. Кроме того, определенные токены должны быть связаны с экземпляром объекта лексера, как это предусмотрено библиотекой. Следующий фрагмент кода показывает, как это можно сделать с помощьюSpirit.Lex.
Шаблон<word_count_tokens
>определяет три различных токена:<ID_WORD
>,<ID_EOL
>и<ID_CHAR
>, представляя слово (все, кроме белого пространства или новой линии), символ новой линии и любой другой символ (<ID_WORD
>,<ID_EOL
>и<ID_CHAR
>являются числовыми значениями, представляющими идентификаторы токена, но также могут быть и все остальное, конвертируемое в целое число). Прямой базовый класс любого класса определения токена должен быть шаблоном<lex::lexer<>
>, где соответствующий параметр шаблона (здесь:<lex::lexertl::lexer<BaseIterator>
>) определяет, какой базовый лексерный двигатель должен использоваться для обеспечения требуемой функциональности машины состояния. В этом примере мы используем лексерный двигатель на основе Lexertl в качестве основного типа лексера.
template <typename Lexer>
struct word_count_tokens : lex::lexer<Lexer>
{
word_count_tokens()
{
this->self.add
("[^ \t\n]+", ID_WORD)
("\n", ID_EOL)
(".", ID_CHAR)
;
}
};
Мы будем использовать настройку, где мы хотим, чтобы библиотекаSpirit.Lexвызывала заданную функцию после распознавания любого из генерируемых токенов. По этой причине нам нужно реализовать функтор, принимая по крайней мере сгенерированный токен в качестве аргумента и возвращая булево значение, позволяющее остановить процесс токенизации. Тип токена по умолчанию, используемый в этом примере, несет значение токена типа<boost::iterator_range
><<BaseIterator>
>, указывающее на соответствующий диапазон в базовой входной последовательности.
В этом примере структура «счетчик» используется в качестве функтора, подсчитывающего символы, слова и линии в анализируемой последовательности ввода, идентифицируя совпадающие токены, переданные из.Spirit.LexБиблиотека.
struct counter
{
template <typename Token>
bool operator()(Token const& t, std::size_t& c, std::size_t& w, std::size_t& l) const
{
switch (t.id()) {
case ID_WORD:
++w; c += t.value().size();
break;
case ID_EOL:
++l; ++c;
break;
case ID_CHAR:
++c;
break;
}
return true;
}
};
Все, что осталось, это написать какой-то шаблонный код, помогающий связать воедино описанные фрагменты. Для упрощения этого примера мы называем функцию<lex::tokenize()
>, реализованную вSpirit.Lex(более подробное описание этой функции см. здесь:FIXME), даже если бы мы могли написать цикл для итерации над итераторами лексера<first
>,<last
>).
Основная функция просто загружает данный файл в память (как<std::string
>), инстанцирует экземпляр шаблона определения токена с использованием правильного типа итератора (<word_count_tokens<charconst*>
>) и, наконец, вызывает<lex::tokenize
>, пропуская экземпляр объекта функции счетчика. Возвратное значение<lex::tokenize()
>будет<true
>, если вся входная последовательность была успешно токенизирована, и<false
>в противном случае.
int main(int argc, char* argv[])
{
std::size_t c = 0, w = 0, l = 0;
std::string str (read_from_file(1 == argc ? "word_count.input" : argv[1]));
word_count_tokens<lex::lexertl::lexer<> > word_count_functor;
char const* first = str.c_str();
char const* last = &first[str.size()];
bool r = lex::tokenize(first, last, word_count_functor,
boost::bind(counter(), _1, boost::ref(c), boost::ref(w), boost::ref(l)));
if (r) {
std::cout << "lines: " << l << ", words: " << w
<< ", characters: " << c << "\n";
}
else {
std::string rest(first, last);
std::cout << "Lexical analysis failed\n" << "stopped at: \""
<< rest << "\"\n";
}
return 0;
}
Этот пример был намеренно выбран, чтобы быть максимально похожим на эквивалентнуюFlexпрограмму (см. ниже), которая не слишком отличается от того, что должно быть написано при использованииSpirit.Lex.
![[Note]](/img/note.png) | Note |
---|
Интересно, что сравнения производительности лексических анализаторов, написанных с использованиемSpirit.Lexс эквивалентными программами, генерируемымиFlex, показывают, что оба имеют сопоставимые скорости исполнения! Как правило, благодаря высоко оптимизированной библиотекеLexertlи благодаря тщательно продуманной интеграции сSpiritштраф за абстракцию, который должен быть оплачен за использованиеSpirit.Lex, является незначительным. |
Остальные примеры в этом руководстве будут использовать более сложные функцииSpirit.Lex, в основном, чтобы позволить дополнительное упрощение кода, который будет написан, сохраняя сходство с соответствующими функциямиFlex.Spirit.Lexбыл разработан, чтобы быть как можно более похожим наFlex. Именно поэтому эта документация предоставит соответствующийFlexкод для показанныхпримеров Spirit.Lexпрактически везде. Таким образом, здесь приведен кодFlex, соответствующий примеру, как показано выше.
%{
#define ID_WORD 1000
#define ID_EOL 1001
#define ID_CHAR 1002
int c = 0, w = 0, l = 0;
%}
%%
[^ \t\n]+ { return ID_WORD; }
\n { return ID_EOL; }
. { return ID_CHAR; }
%%
bool count(int tok)
{
switch (tok) {
case ID_WORD: ++w; c += yyleng; break;
case ID_EOL: ++l; ++c; break;
case ID_CHAR: ++c; break;
default:
return false;
}
return true;
}
void main()
{
int tok = EOF;
do {
tok = yylex();
if (!count(tok))
break;
} while (EOF != tok);
printf("%d %d %d\n", l, w, c);
}