среда, 12 июня 2013 г.

[prog] Вспомнил про свою статью об использовании embedded DSL на Ruby

В Google+ увидел ссылку вот на это небольшое обсуждение. Немного улыбнуло. По-моему, в Рунете заговорили об использовании DSL уже довольно давно, никак не позже 2007-го. Вспомнил про свою статью, которую в 2008-м написал как раз для того, чтобы поделиться опытом использования DSL на Ruby для облегчения одной из моих тогдашних задач: Пример использования Ruby для генерации C++ кода в библиотеке разбора EMI протокола.

Статья довольно большая. Я в ней не делал явного акцента на том, в чем именно выгода от DSL. В статье рассказывается обо всем понемножку: от исходной задачи к аргументам в пользу DSL и альтернативам, а так же немного деталей устройства самого DSL и его использования. Так что ответ на вопрос "А в чем именно выгода от DSL" нужно будет искать в нескольких ее местах.

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

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

Упомянутый в статье DSL использовался для описания структуры пакетов прикладного протокола UCP/EMI. Из этого описания затем генерировались C++классы для работы с этими пакетами в C++ программах. Пример описания одного пакета и фрагментов сгенерированного кода находятся под катом.

Пример описания структуры одного семейства пакетов UCP/EMI на языке Ruby:

require 'emi_pdu_1/pdu_generator'

pdu_class :operation_base_t do |pdu|
   pdu.decl_file :script_relative => 'ucp5x.operation.hpp.inl'
   pdu.impl_file :script_relative => '../ucp5x.operation.cpp.inl'

   pdu.field :AdC, num_char( 116 ), :presence => :mandatory
   pdu.field :OAdC, any_char( 22 ), :presence => :mandatory:visibility => :protected
   pdu.field :AC, num_char( 116 )
   pdu.field :NRq, left_padded_uint( 1 )
   pdu.field :NAdC, num_char( 116 )
   pdu.field :NT, left_padded_uint( 1 )
   pdu.field :NPID, num_char( 4 )
   pdu.field :LRq, num_char( 1 )
   pdu.field :LRAd, num_char( 116 )
   pdu.field :LPID, num_char( 4 )
   pdu.field :DD, num_char( 1 )
   pdu.field :DDT, num_char( 10 )
   pdu.field :VP, num_char( 10 )
   pdu.field :RPID, num_char( 4 )
   pdu.field :SCTS, num_char( 12 )
   pdu.field :Dst, left_padded_uint( 1 )
   pdu.field :Rsn, num_char( 3 )
   pdu.field :DSCTS, num_char( 12 )
   pdu.field :MT, left_padded_uint( 1 ), :presence => :mandatory:visibility => :protected
   pdu.field :NB, num_char( 14 ), :visibility => :protected
   pdu.field :Msg, hex_char( 640 ), :visibility => :protected
   pdu.field :MMS, num_char( 1 )
   pdu.field :PR, any_char( 1 )
   pdu.field :DCs, left_padded_uint( 1 )
   pdu.field :MCLs, left_padded_uint( 1 )
   pdu.field :PRI, num_char( 1 )
   pdu.field :CPg, num_char( 1 )
   pdu.field :RPLy, num_char( 1 )
   pdu.field :OTOA, num_char( 4 )
   pdu.field :HPLMN, num_char( 116 )
   pdu.field :XSer, hex_char( 400 )
   pdu.field :RES4, num_char( 128 )
   pdu.field :RES5, num_char( 128 )
end

Фрагмент описания C++ класса, сгенерированного из Ruby-нового DSL:

class operation_base_t
  : public fields_bunch_t
  {
  public :
    operation_base_t();
    virtual ~operation_base_t();

    /*!
     * \name Реализация интерфейса fields_bunch.
     * \{
     */
    virtual void
    encode( oess_1::io::ostream_t & to ) const;

    virtual void
    decode( const unclassified_fields_t & fields );

    virtual void
    debug_dump( std::ostream & to ) const;
    /*!
     * \}
     */

    /*!
     * \name Методы доступа к открытым полям.
     * \{
     */
    //! Установлено ли значение AdC.
    bool
    is_AdC_defined() const;
    //! Сброс значения AdC.
    void
    drop_AdC();
    //! Получение значения AdC.
    /*!
     * \throw ex_t если значение не установлено.
     */
    const std::string &
    query_AdC() const;
    //! Получение значения AdC если оно определено,
    //! или значения по умолчанию в противном случае.
    std::string
    fetch_AdC( const std::string & default_value ) const;
    //! Установка значения AdC.
    void
    set_AdC( const std::string & v );

    //! Установлено ли значение AC.
    bool
    is_AC_defined() const;
    //! Сброс значения AC.
    void
    drop_AC();
    //! Получение значения AC.
    /*!
     * \throw ex_t если значение не установлено.
     */
    const std::string &
    query_AC() const;
    //! Получение значения AC если оно определено,
    //! или значения по умолчанию в противном случае.
    std::string
    fetch_AC( const std::string & default_value ) const;
    //! Установка значения AC.
    void
    set_AC( const std::string & v );

Фрагмент реализации C++ класса, сгенерированного из Ruby-нового DSL:

operation_base_t::operation_base_t()
  : m_AdC( "AdC" )
  , m_OAdC( "OAdC" )
  , m_AC( "AC" )
  , m_NRq( "NRq" )
  , m_NAdC( "NAdC" )
  , m_NT( "NT" )
  , m_NPID( "NPID" )
  , m_LRq( "LRq" )
  , m_LRAd( "LRAd" )
  , m_LPID( "LPID" )
  , m_DD( "DD" )
  , m_DDT( "DDT" )
  , m_VP( "VP" )
  , m_RPID( "RPID" )
  , m_SCTS( "SCTS" )
  , m_Dst( "Dst" )
  , m_Rsn( "Rsn" )
  , m_DSCTS( "DSCTS" )
  , m_MT( "MT" )
  , m_NB( "NB" )
  , m_Msg( "Msg" )
  , m_MMS( "MMS" )
  , m_PR( "PR" )
  , m_DCs( "DCs" )
  , m_MCLs( "MCLs" )
  , m_PRI( "PRI" )
  , m_CPg( "CPg" )
  , m_RPLy( "RPLy" )
  , m_OTOA( "OTOA" )
  , m_HPLMN( "HPLMN" )
  , m_XSer( "XSer" )
  , m_RES4( "RES4" )
  , m_RES5( "RES5" )

  {}


operation_base_t::~operation_base_t()
  {}

void
operation_base_t::encode( oess_1::io::ostream_t & to ) const
  {
    m_AdC.encode( to ); to << fields_separator;
    m_OAdC.encode( to ); to << fields_separator;
    m_AC.encode( to ); to << fields_separator;
    m_NRq.encode( to ); to << fields_separator;
    m_NAdC.encode( to ); to << fields_separator;
    m_NT.encode( to ); to << fields_separator;
    m_NPID.encode( to ); to << fields_separator;
    m_LRq.encode( to ); to << fields_separator;
    m_LRAd.encode( to ); to << fields_separator;
    m_LPID.encode( to ); to << fields_separator;
    m_DD.encode( to ); to << fields_separator;
    m_DDT.encode( to ); to << fields_separator;
    m_VP.encode( to ); to << fields_separator;
    m_RPID.encode( to ); to << fields_separator;
    m_SCTS.encode( to ); to << fields_separator;
    m_Dst.encode( to ); to << fields_separator;
    m_Rsn.encode( to ); to << fields_separator;
    m_DSCTS.encode( to ); to << fields_separator;
    m_MT.encode( to ); to << fields_separator;
    m_NB.encode( to ); to << fields_separator;
    m_Msg.encode( to ); to << fields_separator;
    m_MMS.encode( to ); to << fields_separator;
    m_PR.encode( to ); to << fields_separator;
    m_DCs.encode( to ); to << fields_separator;
    m_MCLs.encode( to ); to << fields_separator;
    m_PRI.encode( to ); to << fields_separator;
    m_CPg.encode( to ); to << fields_separator;
    m_RPLy.encode( to ); to << fields_separator;
    m_OTOA.encode( to ); to << fields_separator;
    m_HPLMN.encode( to ); to << fields_separator;
    m_XSer.encode( to ); to << fields_separator;
    m_RES4.encode( to ); to << fields_separator;
    m_RES5.encode( to ); to << fields_separator;

  }

void
operation_base_t::decode( const unclassified_fields_t & fields )
  {
    if33 != fields.fields_count() )
      throw ex_t( "operation_base_t: pdu field count mismatch; "
          "expected count: 33; actual count: " +
          cpp_util_2::slexcast( fields.fields_count() ) );

    m_AdC.decode(
        fields.data() + fields.field_at( 0 ).offset(),
        fields.field_at( 0 ).length() );
    m_OAdC.decode(
        fields.data() + fields.field_at( 1 ).offset(),
        fields.field_at( 1 ).length() );
    m_AC.decode(
        fields.data() + fields.field_at( 2 ).offset(),
        fields.field_at( 2 ).length() );
    m_NRq.decode(
        fields.data() + fields.field_at( 3 ).offset(),
        fields.field_at( 3 ).length() );
    m_NAdC.decode(
        fields.data() + fields.field_at( 4 ).offset(),
        fields.field_at( 4 ).length() );
    m_NT.decode(
        fields.data() + fields.field_at( 5 ).offset(),
        fields.field_at( 5 ).length() );
    m_NPID.decode(
        fields.data() + fields.field_at( 6 ).offset(),
        fields.field_at( 6 ).length() );
    m_LRq.decode(
        fields.data() + fields.field_at( 7 ).offset(),
        fields.field_at( 7 ).length() );
    m_LRAd.decode(
        fields.data() + fields.field_at( 8 ).offset(),
        fields.field_at( 8 ).length() );
    m_LPID.decode(
        fields.data() + fields.field_at( 9 ).offset(),
        fields.field_at( 9 ).length() );
    m_DD.decode(
        fields.data() + fields.field_at( 10 ).offset(),
        fields.field_at( 10 ).length() );
    m_DDT.decode(
        fields.data() + fields.field_at( 11 ).offset(),
        fields.field_at( 11 ).length() );
    m_VP.decode(
        fields.data() + fields.field_at( 12 ).offset(),
        fields.field_at( 12 ).length() );
    m_RPID.decode(
        fields.data() + fields.field_at( 13 ).offset(),
        fields.field_at( 13 ).length() );
    m_SCTS.decode(
        fields.data() + fields.field_at( 14 ).offset(),
        fields.field_at( 14 ).length() );
    m_Dst.decode(
        fields.data() + fields.field_at( 15 ).offset(),
        fields.field_at( 15 ).length() );
    m_Rsn.decode(
        fields.data() + fields.field_at( 16 ).offset(),
        fields.field_at( 16 ).length() );
    m_DSCTS.decode(
        fields.data() + fields.field_at( 17 ).offset(),
        fields.field_at( 17 ).length() );
    m_MT.decode(
        fields.data() + fields.field_at( 18 ).offset(),
        fields.field_at( 18 ).length() );
    m_NB.decode(
        fields.data() + fields.field_at( 19 ).offset(),
        fields.field_at( 19 ).length() );
    m_Msg.decode(
        fields.data() + fields.field_at( 20 ).offset(),
        fields.field_at( 20 ).length() );
    m_MMS.decode(
        fields.data() + fields.field_at( 21 ).offset(),
        fields.field_at( 21 ).length() );
    m_PR.decode(
        fields.data() + fields.field_at( 22 ).offset(),
        fields.field_at( 22 ).length() );
    m_DCs.decode(
        fields.data() + fields.field_at( 23 ).offset(),
        fields.field_at( 23 ).length() );
    m_MCLs.decode(
        fields.data() + fields.field_at( 24 ).offset(),
        fields.field_at( 24 ).length() );
    m_PRI.decode(
        fields.data() + fields.field_at( 25 ).offset(),
        fields.field_at( 25 ).length() );
    m_CPg.decode(
        fields.data() + fields.field_at( 26 ).offset(),
        fields.field_at( 26 ).length() );
    m_RPLy.decode(
        fields.data() + fields.field_at( 27 ).offset(),
        fields.field_at( 27 ).length() );
    m_OTOA.decode(
        fields.data() + fields.field_at( 28 ).offset(),
        fields.field_at( 28 ).length() );
    m_HPLMN.decode(
        fields.data() + fields.field_at( 29 ).offset(),
        fields.field_at( 29 ).length() );
    m_XSer.decode(
        fields.data() + fields.field_at( 30 ).offset(),
        fields.field_at( 30 ).length() );
    m_RES4.decode(
        fields.data() + fields.field_at( 31 ).offset(),
        fields.field_at( 31 ).length() );
    m_RES5.decode(
        fields.data() + fields.field_at( 32 ).offset(),
        fields.field_at( 32 ).length() );

  }

void
operation_base_t::debug_dump( std::ostream & to ) const
  {
    to << m_AdC.field_name() << '=';
    if( m_AdC.is_defined() )
      to << '"' << cpp_util_2::hex_dumps::hex_escaped_string_dumper( m_AdC.value() ) << "\"/";
    else
      to << '/';
    to << m_OAdC.field_name() << '=';
    if( m_OAdC.is_defined() )
      to << '"' << cpp_util_2::hex_dumps::hex_escaped_string_dumper( m_OAdC.value() ) << "\"/";
    else
      to << '/';
    to << m_AC.field_name() << '=';
    if( m_AC.is_defined() )
      to << '"' << cpp_util_2::hex_dumps::hex_escaped_string_dumper( m_AC.value() ) << "\"/";
    else
      to << '/';
    to << m_NRq.field_name() << '=';
    if( m_NRq.is_defined() )
      to << m_NRq.value() << '/';
    else
      to << '/';
    to << m_NAdC.field_name() << '=';
    if( m_NAdC.is_defined() )
      to << '"' << cpp_util_2::hex_dumps::hex_escaped_string_dumper( m_NAdC.value() ) << "\"/";
    else
      to << '/';
    to << m_NT.field_name() << '=';
    if( m_NT.is_defined() )
      to << m_NT.value() << '/';
    else
      to << '/';
    to << m_NPID.field_name() << '=';
    if( m_NPID.is_defined() )
      to << '"' << cpp_util_2::hex_dumps::hex_escaped_string_dumper( m_NPID.value() ) << "\"/";
    else
      to << '/';
    to << m_LRq.field_name() << '=';
    if( m_LRq.is_defined() )
      to << '"' << cpp_util_2::hex_dumps::hex_escaped_string_dumper( m_LRq.value() ) << "\"/";
    else
      to << '/';
    to << m_LRAd.field_name() << '=';
    if( m_LRAd.is_defined() )
      to << '"' << cpp_util_2::hex_dumps::hex_escaped_string_dumper( m_LRAd.value() ) << "\"/";
    else
      to << '/';
    to << m_LPID.field_name() << '=';
    if( m_LPID.is_defined() )
      to << '"' << cpp_util_2::hex_dumps::hex_escaped_string_dumper( m_LPID.value() ) << "\"/";
    else
      to << '/';
    to << m_DD.field_name() << '=';
    if( m_DD.is_defined() )
      to << '"' << cpp_util_2::hex_dumps::hex_escaped_string_dumper( m_DD.value() ) << "\"/";
    else
      to << '/';
    to << m_DDT.field_name() << '=';
    if( m_DDT.is_defined() )
      to << '"' << cpp_util_2::hex_dumps::hex_escaped_string_dumper( m_DDT.value() ) << "\"/";
    else
      to << '/';
    to << m_VP.field_name() << '=';
    if( m_VP.is_defined() )
      to << '"' << cpp_util_2::hex_dumps::hex_escaped_string_dumper( m_VP.value() ) << "\"/";
    else
      to << '/';
    to << m_RPID.field_name() << '=';
    if( m_RPID.is_defined() )
      to << '"' << cpp_util_2::hex_dumps::hex_escaped_string_dumper( m_RPID.value() ) << "\"/";
    else
      to << '/';
    to << m_SCTS.field_name() << '=';
    if( m_SCTS.is_defined() )
      to << '"' << cpp_util_2::hex_dumps::hex_escaped_string_dumper( m_SCTS.value() ) << "\"/";
    else
      to << '/';
    to << m_Dst.field_name() << '=';
    if( m_Dst.is_defined() )
      to << m_Dst.value() << '/';
    else
      to << '/';
    to << m_Rsn.field_name() << '=';
    if( m_Rsn.is_defined() )
      to << '"' << cpp_util_2::hex_dumps::hex_escaped_string_dumper( m_Rsn.value() ) << "\"/";
    else
      to << '/';
    to << m_DSCTS.field_name() << '=';
    if( m_DSCTS.is_defined() )
      to << '"' << cpp_util_2::hex_dumps::hex_escaped_string_dumper( m_DSCTS.value() ) << "\"/";
    else
      to << '/';
    to << m_MT.field_name() << '=';
    if( m_MT.is_defined() )
      to << m_MT.value() << '/';
    else
      to << '/';
    to << m_NB.field_name() << '=';
    if( m_NB.is_defined() )
      to << '"' << cpp_util_2::hex_dumps::hex_escaped_string_dumper( m_NB.value() ) << "\"/";
    else
      to << '/';
    to << m_Msg.field_name() << '=';
    if( m_Msg.is_defined() )
      to << '"' << cpp_util_2::hex_dumps::hex_escaped_string_dumper( m_Msg.value() ) << "\"/";
    else
      to << '/';
    to << m_MMS.field_name() << '=';
    if( m_MMS.is_defined() )
      to << '"' << cpp_util_2::hex_dumps::hex_escaped_string_dumper( m_MMS.value() ) << "\"/";
    else
      to << '/';
    to << m_PR.field_name() << '=';
    if( m_PR.is_defined() )
      to << '"' << cpp_util_2::hex_dumps::hex_escaped_string_dumper( m_PR.value() ) << "\"/";
    else
      to << '/';
    to << m_DCs.field_name() << '=';
    if( m_DCs.is_defined() )
      to << m_DCs.value() << '/';
    else
      to << '/';
    to << m_MCLs.field_name() << '=';
    if( m_MCLs.is_defined() )
      to << m_MCLs.value() << '/';
    else
      to << '/';
    to << m_PRI.field_name() << '=';
    if( m_PRI.is_defined() )
      to << '"' << cpp_util_2::hex_dumps::hex_escaped_string_dumper( m_PRI.value() ) << "\"/";
    else
      to << '/';
    to << m_CPg.field_name() << '=';
    if( m_CPg.is_defined() )
      to << '"' << cpp_util_2::hex_dumps::hex_escaped_string_dumper( m_CPg.value() ) << "\"/";
    else
      to << '/';
    to << m_RPLy.field_name() << '=';
    if( m_RPLy.is_defined() )
      to << '"' << cpp_util_2::hex_dumps::hex_escaped_string_dumper( m_RPLy.value() ) << "\"/";
    else
      to << '/';
    to << m_OTOA.field_name() << '=';
    if( m_OTOA.is_defined() )
      to << '"' << cpp_util_2::hex_dumps::hex_escaped_string_dumper( m_OTOA.value() ) << "\"/";
    else
      to << '/';
    to << m_HPLMN.field_name() << '=';
    if( m_HPLMN.is_defined() )
      to << '"' << cpp_util_2::hex_dumps::hex_escaped_string_dumper( m_HPLMN.value() ) << "\"/";
    else
      to << '/';
    to << m_XSer.field_name() << '=';
    if( m_XSer.is_defined() )
      to << '"' << cpp_util_2::hex_dumps::hex_escaped_string_dumper( m_XSer.value() ) << "\"/";
    else
      to << '/';
    to << m_RES4.field_name() << '=';
    if( m_RES4.is_defined() )
      to << '"' << cpp_util_2::hex_dumps::hex_escaped_string_dumper( m_RES4.value() ) << "\"/";
    else
      to << '/';
    to << m_RES5.field_name() << '=';
    if( m_RES5.is_defined() )
      to << '"' << cpp_util_2::hex_dumps::hex_escaped_string_dumper( m_RES5.value() ) << "\"/";
    else
      to << '/';

  }

bool
operation_base_t::is_AdC_defined() const
  {
    return m_AdC.is_defined();
  }
void
operation_base_t::drop_AdC()
  {
    m_AdC.drop();
  }
const std::string &
operation_base_t::query_AdC() const
  {
    return m_AdC.value();
  }
std::string
operation_base_t::fetch_AdC( const std::string & default_value ) const
  {
    if( m_AdC.is_defined() )
      return m_AdC.value();
    else
      return default_value;
  }
void
operation_base_t::set_AdC( const std::string & v )
  {
    m_AdC.set( v );
  }

Комментариев нет: