Здравствуйте, 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>>