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

Closures

Boost , ,

Closures

Overview

Используя Феникс, в предыдущей главе мы видели, как мы можем получить данные из наших парсеров, используяvar:

    int i;
    integer = int_p[var(i) = arg1];

Пятьдесят! Наше правилоцелое число, если оно успешно, переходит от парсированного целого числа к переменнойi. Каждый раз, когда нам нужно разобрать целое число, мы можем назвать наше правилоцелым числоми просто извлечь парсинговое число из переменнойi. Но есть кое-что, о чем вы должны знать. С точки зрения грамматики переменнаяiявляется глобальной. Когда грамматика становится более сложной, трудно отслеживать текущее состояниеi. И с рекурсивными правилами глобальные переменные просто не будут адекватными.

Закрытие необходимо, если вам нужны ваши правила (или грамматика), чтобы вернуться. Например, правило (или грамматика) может быть названо рекурсивно косвенно или непосредственно само по себе. Калькулятор является хорошим примером. Правило выражения рекурсивно называет себя косвенно, когда оно вызывает правило фактора.

Закрытие обеспечивает названные (ленивые) переменные, связанные с каждым вызовом правила разбора. Переменная закрытия рассматривается с использованием синтаксиса члена:

    rulename.varname

Замыкающая переменнаяR.xможет быть рассмотрена в семантическом действии любого другого правила, вызванногоR; она относится к самому внутреннему замкнутому вызовуR. Если такого призыва не существует, утверждение происходит во время выполнения.

Вернемся к грамматике калькулятора, введенной вФункциональнаяглава. Вот полная грамматика, плюс заявления о закрытии:

    struct calc_closure : boost::spirit::closure<calc_closure, double>
    {
        member1 val;
    };
    struct calculator : public grammar<calculator, calc_closure::context_t>
    {
        template <typename ScannerT>
        struct definition
        {
            definition(calculator const& self)
            {
                top = expression[self.val = arg1];
                expression
                    =   term[expression.val = arg1]
                        >> *(   ('+' >> term[expression.val += arg1])
                            |   ('-' >> term[expression.val -= arg1])
                            )
                    ;
                term
                    =   factor[term.val = arg1]
                        >> *(   ('*' >> factor[term.val *= arg1])
                            |   ('/' >> factor[term.val /= arg1])
                            )
                    ;
                factor
                    =   ureal_p[factor.val = arg1]
                    |   '(' >> expression[factor.val = arg1] >> ')'
                    |   ('-' >> factor[factor.val = -arg1])
                    |   ('+' >> factor[factor.val = arg1])
                    ;
            }
            typedef rule<ScannerT, calc_closure::context_t> rule_t;
            rule_t expression, term, factor;
            rule<ScannerT> top;
            rule<ScannerT> const&
            start() const { return top; }
        };
    };

Полный исходный код можно посмотреть здесь. Это часть духовного распределения.

Конечно, мы прошли долгий путь от оригинальной версии этого калькулятора. С помощью встроенныхлямбда-выражениймы смогли писать самодостаточные грамматики в комплекте с семантическими действиями.

Первое, что следует отметить, это объявлениеcalc_closure.

Заявление о закрытии

Общий синтаксис декларации о закрытии:

    struct name : spirit::closure<name, type1, type2, type3,... typeN>
    {
        member1 m_name1;
        member2 m_name2;
        member3 m_name3;
        ...
        memberN m_nameN;
    };

член1...членNявляются косвенными связями с фактическими переменными закрытия. Их косвенные типы соответствуюттипу1...типN. В нашем примере мы объявилиcalc_closure:

    struct calc_closure : boost::spirit::closure<calc_closure, double>
    {
        member1 val;
    };

calc_closureимеет одну переменнуюvalтипаdouble.

BOOST_SPIRIT_CLOSURE_LIMIT

Дух предопределил максимальный предел закрытия. Этот предел определяет максимальное количество элементов, которые может удерживать затвор. Это число по умолчанию 3. Фактический максимум округляется в кратные 3. Таким образом, если это значение равно 4, то фактический предел равен 6. Максимальный предел в этой реализации составляет 15.не должно быть большеФеникса(см.Феникса). Пример:

// Определите их, прежде чем включать что-либо еще
#defineФЕНИКС_ЛИМИТ 10
#определитьBOOST_SPIRIT_CLOSURE_LIMIT 10

Закрытие

Закрытие может применяться к правилам, подправилам и грамматике (нетерминалам). Закрытие имеет специальныйпарсерный контекст, который можно использовать с этими нетерминалами. Контекст закрытия - это его способ подключиться к нетерминалу. Контекстом закрытияCявляетсяC::context_t.

Мы можем видеть в примере, что мы прикрепилиcalc_closureквыражению,терминуифакторуправил в нашей грамматике:

    typedef rule<ScannerT, calc_closure::context_t> rule_t;
    rule_t expression, term, factor; 

Как и сама грамматика:

    struct calculator : public grammar<calculator, calc_closure::context_t>

Возвратная стоимость закрытия

Закрытиечлена1является обратным значением закрытия. Это возвращаемое значение, например, возвращаемоеanychar_p, может использоваться для распространения данных по иерархии парсера или передаваться семантическим действиям. Таким образом,выражение,терминифактор, а также самкалькуляторграмматика, все возвращаютдвойной.

Доступ к переменным закрытия

К переменным закрытия можно получить доступ из семантических действий, как вы бы строили членов: квалифицируя имя участника с его правилом владельца, подправилом или грамматикой. В нашем примере, приведенном выше, обратите внимание на то, как мы ссылались на закрытие члена val. Пример:

    expression.val // refer to expression's closure member val

Инициирование переменных закрытия

Мы не использовали эту функцию в примере, но для полноты...

Иногда нам нужно инициализировать переменные закрытия при входе в нетерминал (правило, подправило или грамматика). Закрытие позволило нетерминалам по умолчанию конструировать переменные по умолчанию при входе в функцию члена разбора. Если это нежелательно, мы можем передать аргументы конструктора в нетерминал. Синтаксис имитирует вызов функции.

Длянадуманногопримера, если вы хотите построитьcalc_closureпеременные3.6, когда мы ссылаемся на выражение, мы пишем:

    expression(3.6) // invoke rule expression and set its closure variable to 3.6

Аргументы конструктора на самом деле являются выражениями лямбды Феникса, поэтому вы можете использовать произвольно сложные выражения. Вот ещенадуманный пример:

    // call rule factor and set its closure variable to (expression.x / 8) * factor.y
    factor((expression.x / 8) * term.y)

Мы можем передать меньше аргументов, чем фактическое число переменных в закрытии. Переменные справа без соответствующих аргументов конструктора построены по умолчанию. Передача большего количества аргументов, чем переменных закрытия, является ошибкой.

См.parameters.cppдля компилируемого примера. Это часть духовного распределения.

Closures and Dynamic parsing

Давайте напишем очень простой парсер для языка XML/HTML с произвольно вложенными тегами. Типичный подход к этому типу вложенного парсинга тегов заключается в делегировании фактического соответствия тега семантическим действиям, возможно, с использованием таблицы символов. Например, семантические действия отвечают за обеспечение вложенности меток (например, этот код:

/p>
является ошибочным).

Spirit позволяет динамически изменять парсер во время выполнения. Способность направлять поведение парсера с помощью семантических действий позволяет обеспечить вложение меток непосредственно в парсер. Посмотрим, как это возможно. Вот грамматика в ее простейшей форме:

    element = start_tag >> *element >> end_tag;

Элемент представляет собойstart_tag(например,), выталкиваемый нулем или более элементов и заканчивающийсяend_tag(например,). Итак, вот первый выстрел в нашstart_tag:

    start_tag = '<' >> lexeme_d[(+alpha_p)] >> '>';

Обратите внимание, чтоend_tag— это то же самое, чтоstart_tagс добавлением слэша:

    end_tag = "</" >> what_we_got_in_the_start_tag >> '>';

Что нам нужно сделать, так это временно сохранить то, что мы получили в нашемstart_tagи использовать это позже для разбора нашегоend_tag. Пятьдесят, мы можем использоватьпараметрический парсерпримитивов для разбора нашегоend_tag:

    end_tag = "</" >> f_str_p(tag) >> '>';

где мы параметризируемf_str_pс тем, что мы храним (метка).

Помните, что наша грамматика рекурсивна. Правило элемента называет себя. Следовательно, мы не можем просто использовать переменную и использоватьphoenix::varилиboost::ref. Вложенная рекурсия просто поглотит переменную. Каждый вызов элемента должен иметь замыкающую переменнуюметка. Вот полная грамматика:

    struct tags_closure : boost::spirit::closure<tags_closure, string> 
    {
        member1 tag;
    };
    struct tags : public grammar<tags>
    {
        template <typename ScannerT>
        struct definition {
            definition(tags const& /*self*/)
            {
                element = start_tag >> *element >> end_tag;
                start_tag =
                        '<'
                    >>  lexeme_d
                        [
                            (+alpha_p)
                            [
                                //  construct string from arg1 and arg2 lazily
                                //  and assign to element.tag
                                element.tag = construct_<string>(arg1, arg2)
                            ]
                        ]
                    >> '>';
                end_tag = "</" >> f_str_p(element.tag) >> '>';
            }
            rule<ScannerT, tags_closure::context_t> element;
            rule<ScannerT> start_tag, end_tag;
            rule<ScannerT, tags_closure::context_t> const&
            start() const { return element; }
        };
    };

Мы прикрепили семантическое действие к(+alpha_p)части стартового тега. Там мы сохранили парсированный тег вэлементе, закрывающую переменнуютег. Позже, вend_tag, мы просто использовалиэлемент's закрывающая переменнаятегдля параметризации нашегоf_str_pпарсера. Просто и элегантно. Если некоторые детали начинают выглядеть как греческие (например, что такоеconstruct_?), пожалуйста, обратитесь к главеPhoenix.

Полный исходный код можно посмотреть здесь. Это часть духовного распределения.

Closures in-depth

Что такое закрытие?

Закрытие — это объект, который"закрывает"над локальными переменными функции, делая их видимыми и доступными вне функции. Более интересно то, что закрытие фактически упаковывает локальный контекст (рамку стека, в которой находятся некоторые переменные) и делает ее доступной за пределами области, в которой они действительно существуют. Информация по существу"захвачена"путем закрытия, что позволяет ссылаться на нее в любом месте и в любое время, даже до фактического создания переменных.

На следующей диаграмме изображена ситуация, когда функцияA(или правило) обнажает ее замыкание, а другая функцияBссылается напеременные Aчерез ее замыкание.

Закрытие как объект, который"закрывает"над локальными переменными функции, делая их видимыми и доступными вне функции

Конечно, функцияAдолжна быть активной, когдаA.xупоминается. Это означает, что функцияВзависит от функцииА(еслиВявляется вложенной функциейА, это всегда будет так). Свободная форма законов Духа позволяет получить доступ к переменной закрытия в любое время и в любом месте. Доступ кA.xэквивалентен ссылке на самую верхнюю переменную стекаxфункцииA. Если функцияAне активна при упоминанииA.x, запускается исключение во время выполнения.

Вложенные функции

Чтобы полностью понять важность замыканий, лучше всего взглянуть на язык, который позволяет вложенные функции. Поскольку мы имеем дело с C++, давайте предположим, что C++ позволяет вложенные функции. Рассмотрим следующийпсевдокод C++:

    void a()
    {
        int va;
        void b()
        {
            int vb;
            void c()
            {
                int vc;
            }
            c();
        }
        b();
    }

У нас есть три функцииa,bиc, гдеcвложены вbиbвложены вa. У нас также есть три переменныеva,vbиvc. Срок службы каждой из этих локальных переменных начинается, когда вводится функция, где она объявлена, и заканчивается, когда функция выходит. Объем локальной переменной охватывает все вложенные функции внутри ограждающей функции, где переменная объявлена.

При переходе от функцииaк функцииc, когда функция a введена, переменнаяvaбудет создана в стеке. Когда функцияbвведена (называемаяa),vaочень хороша по объему и видна вb. В этот момент на стеке создается новая переменнаяvb. Когда функцияcвведена, обаvaиvbвидимо находятся в объеме, и создается свежая локальная переменнаяvc.

Идя вверх по течению,vcне является и не может быть видимым вне функцииc.vcжизнь уже истекла после выходаc. То же самое верно и дляvb; vb доступен в функцииc, но не в функцииa.

Nested Mutually Recursive Rules

Теперь рассмотрим, чтоa,bиcявляются правилами:

    a = b >> *(('+' >> b) | ('-' >> b));
    b = c >> *(('*' >> c) | ('/' >> c));
    c = int_p | '(' >> a >> ')' | ('-' >> c) | ('+' >> c);

Мы можем визуализироватьa,bиcкак взаимно рекурсивные функции, гдеaвызываетb,bвызываетcиcрекурсивно вызываетa. Теперь представьте, еслиa,bиcкаждый имеет локальную переменную, названнуюзначением, которая может быть упомянута в нашей грамматике посредством явной квалификации:

    a.value // refer to a's value local variable
    b.value // refer to b's value local variable
    c.value // refer to c's value local variable

Как и выше, при вводеaна стеке создается локальная переменнаязначение. На эту переменную можно ссылаться какb, так иc. Опять же, когдаbназываетсяa,bсоздает локальную переменнуюзначение. Эта переменная доступнаc, но неa.

Здесь теперь заканчивается аналогия с вложенными функциями: когда называетсяс, создается свежая переменнаязначение, которая, как обычно, длится весь срок жизнис. Обратите внимание, чтоcможет называтьaрекурсивно. Когда это происходит,aможет теперь относиться к локальной переменнойc



Статья Closures раздела может быть полезна для разработчиков на c++ и boost.




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



:: Главная :: ::


реклама


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

Время компиляции файла: 2024-08-30 11:47:00
2025-05-20 04:43:48/0.027222871780396/1