Этот раздел не для слабонервных. Здесь дистиллируется внутренняя работаSpirit.Qiпарсеров, используя в качестве примеров реальный код из библиотекиSpirit. С другой стороны, нет причин бояться читать дальше. Мы пытались объяснить вещи шаг за шагом, подчеркивая важные идеи.
Класс<Parser
>
является базовым классом для всех парсеров.
template <typename Derived>
struct parser
{
struct parser_id;
typedef Derived derived_type;
typedef qi::domain domain;
Derived const& derived() const
{
return *static_cast<Derived const*>(this);
}
};
Класс<Parser
>
на самом деле не знает, как что-либо анализировать, но вместо этого полагается на параметр шаблона<Derived
>для фактического анализа. Этот метод известен как «Любопытно повторяющийся шаблон шаблона» в кругах метапрограммирования шаблонов. Эта стратегия наследования дает нам силу полиморфизма без накладных расходов на виртуальную функцию. По сути, это способ реализации компиляции полиморфизма времени.
Полученные парсеры<PrimitiveParser
>
,<UnaryParser
>
,<BinaryParser
>
и<NaryParser
>
обеспечивают необходимые средства для обнаружения парсера, интроспекции, преобразования и посещения.
Производные парсеры должны поддерживать следующее:
bool parse(f, l, context, skip, attr)
f
, l
первая/последняя пара итераторов
context
Включающий контекст правил (может быть неиспользованным_type)
skip
Skipper (может быть неиспользованным)
attr
Атрибут (может быть неиспользованным)
parseявляется основной точкой входа парсера.шкиперможет быть<unused_type
>. Это тип, используемый вДухедля обозначения «не беспокойтесь». В этом случае<unused_type
>будет просто невыполнение. Таким образом, нам не нужно писать несколько функций разбора для разбора фраз и уровней символов.
Вот основные правила для парсинга:
- В случае успеха<
true
>парсер возвращается<false
>. - В случае успеха<
first
>увеличивается число N раз, где N — число символов, парсированных. N может быть нулевым — пустой (эпсилон) матч. - [ORIG_END] -->
- В случае неудачи<
first
>перед входом в функцию парсера сбрасывается в положение.attrне тронут.
void what(context)
context
Включающий контекст правила (может быть<unused_type
>)
Чтофункция должна быть очевидной. Он предоставляет некоторую информацию о& #8220;что& #8221;парсер. Он используется, например, в качестве вспомогательного средства отладки.
P::template attribute<context>::type
P
парсерный тип
context
Тип контекста (может быть неиспользованным)
атрибутметафункция возвращает ожидаемый тип атрибута парсера. В некоторых случаях это зависит от контекста.
В этом разделе мы рассмотрим два типа парсера:
Parsers
PrimitiveParser
Парсер для примитивных данных (например, целочисленное парсирование).
UnaryParser
Парсер, имеющий один объект (например, кленовую звезду).
Для нашего исследования вскрытия мы будем использоватьДухпримитивный,<any_int_parser
>в бустере::Дух:::qi пространство имен.
[primitive_parsers_any_int_parser]
<any_int_parser
>происходит от<PrimitiveParser
><Происходит>
, который, в свою очередь, происходит от<parser<Derived>
>. Поэтому он поддерживает следующие требования:
- Функция члена<
parse
> - Функция члена<
what
> - Вложенная<
attribute
>метафункция
parseявляется основной точкой входа. Для примитивных парсеров первое, что нужно сделать, это позвонить:
qi::skip(first, last, skipper);
to do a pre-skip. After pre-skipping, the parser proceeds to do its thing.
The actual parsing code is placed in extract_int<T, Radix, MinDigits, MaxDigits>::call(first, last, attr);
Этот простой протокол без излишеств является одной из причин, почемуДухбыстр. Если вы знаете внутренние органыДуха.Классическийи, возможно, даже написал с ним парсеры, этот простой механизмДуха— это радость для работы. Нет сканеров и всего этого дерьма.
функцияпросто говорит нам, что это целочисленный парсер. Просто.
Метафункциявозвращает параметр шаблона T. Мы соединяем<any_int_parser
>с некоторыми держателями<short_
>,<int_
>,<long_
>и<long_long
>типов. Но, во-первых, мы позволяем этим заполнителям в пространстве имен повысить:
template <>
struct use_terminal<qi::domain, tag::short_> : mpl::true_ {};
template <>
struct use_terminal<qi::domain, tag::int_> : mpl::true_ {};
template <>
struct use_terminal<qi::domain, tag::long_> : mpl::true_ {};
template <>
struct use_terminal<qi::domain, tag::long_long> : mpl::true_ {};
Обратите внимание, что<any_int_parser
>помещается в усилитель пространства имен::spirit::qi, в то время как этиактиваторынаходятся в усилителе пространства имен::spirit. Причина в том, что эти заполнители разделяются другимиДухомдоменами.Дух.Qi, парсер является одним доменом.Дух.Карма, генератор является другой областью. Другие парсерные технологии могут быть разработаны и помещены в еще одну область. Тем не менее, все они потенциально могут совместно использовать одни и те же заполнители для взаимодействия. Интерпретация этих заполнителей зависит от домена.
Теперь, когда мы включили заполнители, мы должны написать генераторы для них. Make_xxx (в бустере::spirit:::qi namespace):
template <
typename T
, unsigned Radix = 10
, unsigned MinDigits = 1
, int MaxDigits = -1>
struct make_int
{
typedef any_int_parser<T, Radix, MinDigits, MaxDigits> result_type;
result_type operator()(unused_type, unused_type) const
{
return result_type();
}
};
Этот генератор является нашим основным генератором. Это простой функциональный объект с 2 (неиспользованными) аргументами. Эти аргументы являются
- Фактическое конечное значение, полученное прото. В этом случае либо короткий, либо длинный, либо длинный. Нас это не волнует.
- Модификаторы. Нас это тоже не волнует. Это позволяет директивам, таким как<
no_case[p]
>, передавать информацию внутренним узлам парсера. Посмотрим, как это сработает позже.
Теперь:
template <typename Modifiers>
struct make_primitive<tag::short_, Modifiers>
: make_int<short> {};
template <typename Modifiers>
struct make_primitive<tag::int_, Modifiers>
: make_int<int> {};
template <typename Modifiers>
struct make_primitive<tag::long_, Modifiers>
: make_int<long> {};
template <typename Modifiers>
struct make_primitive<tag::long_long, Modifiers>
: make_int<boost::long_long_type> {};
Они специализируются<qi:make_primitive
>на конкретных тегах. Все они унаследованы от<make_int
>, которые выполняют действительную работу.
Позвольте мне представить кленовую звезду (также в духе пространства имен::qi):
template <typename Subject>
struct kleene : unary_parser<kleene<Subject> >
{
typedef Subject subject_type;
template <typename Context, typename Iterator>
struct attribute
{
typedef typename
traits::build_std_vector<
typename traits::
attribute_of<Subject, Context, Iterator>::type
>::type
type;
};
kleene(Subject const& subject_)
: subject(subject_) {}
template <typename F>
bool parse_container(F f) const
{
while (!f (subject))
;
return true;
}
template <typename Iterator, typename Context
, typename Skipper, typename Attribute>
bool parse(Iterator& first, Iterator const& last
, Context& context, Skipper const& skipper
, Attribute& attr_) const
{
traits::make_container(attr_);
typedef detail::fail_function<Iterator, Context, Skipper>
fail_function;
Iterator iter = first;
fail_function f(iter, last, context, skipper);
parse_container(detail::make_pass_container(f, attr_));
first = f.first;
return true;
}
template <typename Context>
info what(Context& context) const
{
return info("kleene", subject.what(context));
}
Subject subject;
};
По форме похож на своего первобытного двоюродного брата<int_parser
>. И, опять же, он имеет те же основные ингредиенты, которые требуются<Derived
>.
- Метафункция вложенного атрибута
- Функция члена парса
- какой член функционирует
Клен представляет собой составной парсер. Это парсер, который составляет другой парсер, его& #8220;предмет& #8221;. Это<UnaryParser
>
и подклассы от него. Как<PrimitiveParser
>
,<UnaryParser
><Получено>
происходит от<parser<Derived>
>.
unary_parser, имеет следующие требования к выражению:
parseявляется основной точкой входа парсера. Поскольку это не примитивный парсер, нам не нужно называть<qi::skip(first,last,skipper)
>.субъект, если он примитивный, будет делать предварительный скип. Если это другой составной парсер, он в конечном итоге назовет примитивный парсер где-то вниз по линии, который будет делать предварительный скип. Это делает его намного более эффективным, чемSpirit.Classic.Spirit.Classicставит бизнес пропуска в так называемый «сканер», который слепо пытается предварительно пропустить каждый раз, когда мы увеличиваем итератор.
unused_type
, затем
атрибут kleene также unused_type
.
traits::build_std_vector
заботится об этом несовершеннолетнем
детали.
.
Итак, давайте разберемся. Во-первых, необходимо предоставить локальный атрибут для предмета:
typename traits::attribute_of<Subject, Context>::type val;
<traits::attribute_of<Subject,Context>
>просто называет вложенную метафункцию субъекта<struct
attribute<Context>
>.
valначинается с инициализации по умолчанию. Это вал, который мы перейдем к функции разбора субъекта.
Клен повторяется бесконечно, в то время как субъект парсер успешно. На каждом успешном разборе мы<push_back
>оцениваем атрибут клена, который, как ожидается, будет, по крайней мере, совместим с<std::vector
>. Другими словами, хотя мы и говорим, что хотим, чтобы наш атрибут был<std::vector
>, мы стараемся быть более снисходительными, чем это. Абонент парса Клин может пройти другой тип атрибута. До тех пор, пока это также совместимый контейнер STL с<push_back
>, мы в порядке. Вот петля клена:
while (subject.parse(first, last, context, skipper, val))
{
traits::push_back(attr, val);
traits::clear(val);
}
return true;
Обратите внимание, что мы не звонили по адресу attr.push_back(val). Вместо этого мы назвали функцию Духа:
traits::push_back(attr, val);
Это повторяющаяся модель. Причина, по которой мы делаем это таким образом, заключается в том, что Attrможетбыть<unused_type
>.<traits::push_back
>заботится об этой детали. Перегрузка для неиспользуемого_типа является безоперационной. Теперь вы можете себе представить, почемуДухбыстр! Парсеры настолько просты, и сгенерированный код так же эффективен, как и ручной цикл. Все эти парсерные композиции и рекурсивные призывы к разбору подробно описаны современным компилятором C++. В конце концов, вы получаете плотную петлю, когда используете клен. Больше никакого лишнего багажа. Если атрибут не используется, то для этого не генерируется код. Вот как устроенДух.
Функция, котораяпросто обертывает выход субъекта в клен& #8220;...& #8221;.
Так, теперь, подобно<int_parser
>, мы должны подключить наш парсер кцидвигателю. Вот как мы это делаем:
Во-первых, мы включаем префикс звездного оператора. В прото это называется "уважение":
template <>
struct use_operator<qi::domain, proto::tag::dereference>
: mpl::true_ {};
Это делается в пространстве имён<boost::spirit
>, как его друг,<use_terminal
>специализация для нашего<int_parser
>. Очевидно, что мы используемuse_operator, чтобы включить ссылку на qi::domain.
Затем мы должны написать наш генератор (в пространстве имен qi):
template <typename Elements, typename Modifiers>
struct make_composite<proto::tag::dereference, Elements, Modifiers>
: make_unary_composite<Elements, kleene>
{};
Это по существу говорит; для всех выражений формы:<*p
>, построить кленовый парсер. Элементы — последовательностьBoost.Fusion. Для клена, который является унарным оператором, в последовательности ожидается только один элемент. Этот элемент является предметом клена.
Нам по-прежнему плевать на модификаторов. Посмотрим, как будут меняться модификаторы, когда мы перейдем к глубоким директивам.