Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
Разработка программного обеспечения

Parsers in Depth

Boost , Spirit 2.5.2 , In Depth

Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

PrevUpHomeNext

Этот раздел не для слабонервных. Здесь дистиллируется внутренняя работаSpirit.Qiпарсеров, используя в качестве примеров реальный код из библиотекиSpirit. С другой стороны, нет причин бояться читать дальше. Мы пытались объяснить вещи шаг за шагом, подчеркивая важные идеи.

Класс<Parser>является базовым классом для всех парсеров.

template <typename Derived>
struct parser
{
    struct parser_id;
    typedef Derived derived_type;
    typedef qi::domain domain;
    // Requirement: p.parse(f, l, context, skip, attr) -> bool
    //
    //  p:          a parser
    //  f, l:       first/last iterator pair
    //  context:    enclosing rule context (can be unused_type)
    //  skip:       skipper (can be unused_type)
    //  attr:       attribute (can be unused_type)
    // Requirement: p.what(context) -> info
    //
    //  p:          a parser
    //  context:    enclosing rule context (can be unused_type)
    // Requirement: P::template attribute<Ctx, Iter>::type
    //
    //  P:          a parser type
    //  Ctx:        A context type (can be unused_type)
    //  Iter:       An iterator type (can be unused_type)
    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

Парсер, имеющий один объект (например, кленовую звезду).

Primitive Parsers

Для нашего исследования вскрытия мы будем использоватьДухпримитивный,<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 <> // enables short_
struct use_terminal<qi::domain, tag::short_> : mpl::true_ {};

template <> // enables int_
struct use_terminal<qi::domain, tag::int_> : mpl::true_ {};

template <> // enables long_
struct use_terminal<qi::domain, tag::long_> : mpl::true_ {};

template <> // enables long_long
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 (неиспользованными) аргументами. Эти аргументы являются

  1. Фактическое конечное значение, полученное прото. В этом случае либо короткий, либо длинный, либо длинный. Нас это не волнует.
  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>, которые выполняют действительную работу.

Composite Parsers

Позвольте мне представить кленовую звезду (также в духе пространства имен::qi):

template <typename Subject>
struct kleene : unary_parser<kleene<Subject> >
{
    typedef Subject subject_type;
    template <typename Context, typename Iterator>
    struct attribute
    {
        // Build a std::vector from the subject's attribute. Note
        // that build_std_vector may return unused_type if the
        // subject's attribute is an unused_type.
        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
    {
        // ensure the attribute is actually a container type
        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, имеет следующие требования к выражению:

  • p.subject ->Subject parserpявляется<UnaryParser>парсером.
  • P::subject_type ->subject parser typeP<UnaryParser>тип.

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))
{
    // push the parsed value into our attribute
    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> // enables *p
  : 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. Для клена, который является унарным оператором, в последовательности ожидается только один элемент. Этот элемент является предметом клена.

Нам по-прежнему плевать на модификаторов. Посмотрим, как будут меняться модификаторы, когда мы перейдем к глубоким директивам.


PrevUpHomeNext

Статья Parsers in Depth раздела Spirit 2.5.2 In Depth может быть полезна для разработчиков на c++ и boost.




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



:: Главная :: In Depth ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-08-30 11:47:00
2025-07-05 12:13:48/0.0079360008239746/0