Re: Использование метаданных в программах на языке C++
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 08.09.05 12:10
Оценка: 10 (1)
Здравствуйте, eao197, Вы писали:

E>А вот второй подход, основанный на использовании DSL (Domain Specific Language) мне гораздо более симпатичным. Создается простой язык для конкретной предметной области. Создается его транслятор и генератор вспомогательного кода. И создается необходимый набор библиотек для поддержки сгенерированного кода. Каждый компонентик оказывается относительно простым как в написании, так и в использовании. Но, что более важно, -- в сопровождении. Для сопровождения транслятора, созданного на основе, например, bison/flex, совсем не требуется гуру в области C++ шаблонов.


E>А еще интереснее то, что для DSL не обязательно создавать отдельные специализированные языки. Вполне возможно использовать некоторые существующие языки, которые способствуют метапрограммированию. Например, Lisp (см. Metaprogramming et al
Автор:
Дата: 09.07.05
, Re: Metaprogramming et al: Ruby?
Автор: eao197
Дата: 10.08.05
, Re[2]: Metaprogramming et al: Ruby?
Автор: eao197
Дата: 19.08.05
, Вот такой вот препроцессор.
Автор: c-smile
Дата: 20.08.05
, Re: Вот такой вот препроцессор.
Автор: eao197
Дата: 20.08.05
).


Вот как раз по следам этих обсуждений (а так же задачки, предложенной вот здесь
Автор: AndrewVK
Дата: 13.07.05
) я сегодня решился применить кодогенерацию с помощь простенького DSL. Нужно мне было создать C++ класс, который бы хранил несколько полей (с перспективой увеличения их числа в ближайшем будущем). Проблема была в том, что для каждого поля нужно было иметь четыре метода (например, для поля с именем source_addr_ton):
bool is_source_addr_ton_present() const;
oess_1::uchar_t source_addr_ton() const;
void set_source_addr_ton( oess_1::uchar_t v );
void drop_source_addr_ton();


Все еще усугублялось тем, что признак наличия актуального значения у поля должен быть выставлен в специальной битовой маске. И если вызывается метод getter, а бит в этой маске не выставлен, то getter должен порождать исключение. Соответственно, метод setter должен выставлять соответствующий бит в битовой маске. Т.е. что-то типа:
bool is_source_addr_ton_present() const { return ( 0 != ( m_fields_bit_mask & 1 ) ); }
oess_1::uchar_t source_addr_ton() const {
    if( !is_source_addr_ton_present() )
        throw std::runtime_error( "source_addr_ton: no actual value" );
    return m_source_addr_ton;
}
void set_source_addr_ton( oess_1::uchar_t v ) {
    m_source_addr_ton = v;
    m_fields_bit_mask |= 1;
}
void drop_source_addr_ton() {
    m_fields_bit_mask &= ~1;
}


Можно было бы применить обычный copy-paste. А затем править названия полей и методов, ожидая, что где-то что-то окажется забытым.
Можно было бы написать пару макросов и использовать их:
class submit_deliver_t
{
    public :
        DECL_FIELD( source_addr_ton, oess_1::uchar_t, 0, 1 )
        DECL_FIELD( source_addr_npi, oess_1::uchar_t, 0, 2 )
        DECL_FIELD( dest_addr_ton, oess_1::uchar_t, 0, 4 )
        ...
};

IMPL_FIELD( source_addr_ton, oess_1::uchar_t, 0, 1 )
IMPL_FIELD( source_addr_npi, oess_1::uchar_t, 0, 2 )
IMPL_FIELD( dest_addr_ton, oess_1::uchar_t, 0, 4 )


Да только макросы получаются непрозрачными для инструментов типа ctags и doxygen.

В общем, решился я на применение такого Ruby описания (файл submit_deliver.rb):
require 'mbsms_2/smpp/submit_deliver_gen'

submit_deliver(
        "mbsms_2/smpp/h/submit_deliver.detail.hpp",
        "mbsms_2/smpp/submit_deliver.detail.cpp" ) do |c|
    c.field :name => :source_addr_ton,
            :type => "oess_1::uchar_t",
            :bit_mask => 0x1,
            :default => 0

    c.field :name => :source_addr_npi,
            :type => "oess_1::uchar_t",
            :bit_mask => 0x2,
            :default => 0

    c.field :name => :dest_addr_ton,
            :type => "oess_1::uchar_t",
            :bit_mask => 0x4,
            :default => 0

    c.field :name => :dest_addr_npi,
            :type => "oess_1::uchar_t",
            :bit_mask => 0x8,
            :default => 0

    c.field :name => :esm_class,
            :type => "oess_1::uchar_t",
            :bit_mask => 0x10,
            :default => 0

    c.field :name => :protocol_id,
            :type => "oess_1::uchar_t",
            :bit_mask => 0x20,
            :default => 0

    c.field :name => :data_coding,
            :type => "oess_1::uchar_t",
            :bit_mask => 0x40,
            :default => 0
end


Для его использования добавил в проектный файл несколько строчек:
generator( Mxx_ru::Makestyle_generator.new(
    [ "mbsms_2/smpp/h/submit_deliver.detail.hpp",
        "mbsms_2/smpp/submit_deliver.detail.cpp" ],
    [ "mbsms_2/smpp/submit_deliver.rb",
        "mbsms_2/smpp/submit_deliver_gen.rb" ],
    [ "ruby mbsms_2/smpp/submit_deliver.rb" ] ) )

т.е. файлы submit_deliver.detail.hpp и submit_deliver.detail.cpp перестраиваются при изменении submit_deliver.rb или submit_deliver_gen.rb. Перестройка выполняется с помощью Ruby и скрипта submit_deliver.rb.

Автоматически генерируемые файлы submit_deliver.detail.* используются очень просто:
// Файл submit_deliver.hpp
/*!
    \file
    \brief Класс для хранения опциональных SMPP полей для
        submit_sm/deliver_sm/data_sm PDU.
*/

#if !defined( MBSMS_2__SMPP__SUBMIT_DELIVER_HPP )
#define MBSMS_2__SMPP__SUBMIT_DELIVER_HPP

#include <mbsms_2/h/declspec.hpp>

#include <oess_1/stdsn/h/serializable.hpp>

namespace mbsms_2
{

namespace smpp
{

#include "submit_deliver.detail.hpp"

} /* namespace smpp */

} /* namespace mbsms_2 */

#endif


// Файл submit_deliver.cpp
/*!
    \file
    \brief Класс для хранения опциональных SMPP полей для
        submit_sm/deliver_sm/data_sm PDU.
*/

#include <mbsms_2/smpp/h/submit_deliver.hpp>

#include <oess_1/stdsn/h/inout_templ.hpp>

namespace mbsms_2
{

namespace smpp
{

#include "submit_deliver.detail.cpp"

#include "submit_deliver.ddl.cpp"

} /* namespace smpp */

} /* namespace mbsms_2 */


В результате получается вот такое описание класса (фрагмент):
/*!
    Класс для хранения опциональных значений SMPP-полей для
    submit_sm/deliver_sm/data_sm PDU.

    Поле считается имеющим актуальное значение, если соответствующий
    метод is_*_present возвращает true. В противном случае поле считается
    не заданным и обращение к нему будет приводить к возникновению
    исключения std::exception.
*/
class MBSMS_2_TYPE submit_deliver_t
    :    public oess_1::stdsn::serializable_t
    {
        OESS_SERIALIZER_EX( submit_deliver_t, MBSMS_2_TYPE )
    public :
        /*!
            Конструктор по умолчанию.

            Все поля считаются не имеющими актуального значения.
        */
        submit_deliver_t();
        virtual ~submit_deliver_t();

        /*!
            \return true, если значение для поля source_addr_ton задано.
        */
        bool
        is_source_addr_ton_present() const;
        /*!
            \return значение поля source_addr_ton.
            \throw std::exception при попытке получить значение отсутствующего
            поля.
        */
        oess_1::uchar_t
        source_addr_ton() const;
        /*!
            Установить значение поля source_addr_ton.

            Одновременно выставляется признак того, что поле задано.
        */
        void
        set_source_addr_ton( oess_1::uchar_t v );
        /*!
            Сбросить признак того, что поле source_addr_ton задано.
        */
        void
        drop_source_addr_ton();

        /*!
            \return true, если значение для поля source_addr_npi задано.
        */
        bool
        is_source_addr_npi_present() const;
        /*!
            \return значение поля source_addr_npi.
            \throw std::exception при попытке получить значение отсутствующего
            поля.
        */
        oess_1::uchar_t
        source_addr_npi() const;
...

и вот такая его реализация (фрагмент):
submit_deliver_t::submit_deliver_t()
    :    m_fields_bit_mask( 0 )
    ,    m_source_addr_ton( 0 )
    ,    m_source_addr_npi( 0 )
    ,    m_dest_addr_ton( 0 )
    ,    m_dest_addr_npi( 0 )
    ,    m_esm_class( 0 )
    ,    m_protocol_id( 0 )
    ,    m_data_coding( 0 )

    {}

submit_deliver_t::~submit_deliver_t()
    {}

bool
submit_deliver_t::is_source_addr_ton_present() const
    {
        return ( 1 == ( m_fields_bit_mask & 1 ) );
    }
oess_1::uchar_t
submit_deliver_t::source_addr_ton() const
    {
        if( !is_source_addr_ton_present() )
            throw std::runtime_error( "source_addr_ton: no actual value" );
        return m_source_addr_ton;
    }
void
submit_deliver_t::set_source_addr_ton( oess_1::uchar_t v )
    {
        m_source_addr_ton = v;
        m_fields_bit_mask |= 1;
    }
void
submit_deliver_t::drop_source_addr_ton()
    {
        m_fields_bit_mask &= ~1;
    }
bool
submit_deliver_t::is_source_addr_npi_present() const
    {
        return ( 2 == ( m_fields_bit_mask & 2 ) );
    }
oess_1::uchar_t
submit_deliver_t::source_addr_npi() const
    {
        if( !is_source_addr_npi_present() )
            throw std::runtime_error( "source_addr_npi: no actual value" );
        return m_source_addr_npi;
    }
void
submit_deliver_t::set_source_addr_npi( oess_1::uchar_t v )
    {
        m_source_addr_npi = v;
        m_fields_bit_mask |= 2;
    }
void
submit_deliver_t::drop_source_addr_npi()
    {
        m_fields_bit_mask &= ~2;
    }
...


Самое прикольное, что doxygen по этому делу генерирует нормальную документацию

В итоге получилось, что я написал ~230 строк генерирующего Ruby скрипта submit_deliver_gen.rb, плюс 42 строки DSL-я submit_deliver.rb. И получил ~430 строк сгенерированного C++ текста. Правда, потратил я на написание этого скрипта порядка 1.5 часов. Но, имхо, за это получил инструментик, который легко поможет мне как добавлять новые поля, так и менять реализацию работы с полями в генерируемом классе.
... << RSDN@Home 1.1.4 stable rev. 510>>


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.