среда, 1 июля 2020 г.

[prog.flame] Может уже и пришло время закапывать C++, но пока он позволяет в коде выписывать ABNF грамматики...

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

Иногда складывается впечатление, что эти стенания повсюду. Особенно на профильных ресурсах, типа Reddit, LOR, RSDN (может и на Хабре, но до Хабра сейчас просто уже руки не доходят).

Финальным гвоздем стали очередные вбросы в Telegram канале C++ Russia от известного своим праздным звездежом хаскелиста Александра Гранина, у которого, походу, второй натурой стал троллинг C++а и C++ников. С традиционным уже участием на C++ Russia с докладом про функциональщину.

В общем, послушать активно испражняющихся в Интернетах специалистов по всем вопросам на свете и может показаться, что хуже C++ только мучительная смерть от бубонной чумы. Да и то не факт.

В тоже самое время этот самый современный C++, в который, как искренне верят неасиляторы, напихали всякой ненужной чуйни, лично мне позволяет в декларативном виде записывать в коде ABNF грамматики из RFC по HTTP/1.1.

Причем это даже не самый еще современный C++, а всего лишь C++14.

И что-то мне подсказывает, что сделать подобное на каких-то "типа альтернативах" C++ будет не так уж и просто. Особенно с учетом накладных расходов в run-time. Если вообще возможно.

Ну а вот и сам упомянутый фрагмент. Который отвечает за парсинг содержимого HTTP-заголовка Transfer-Encoding согласно спецификации.

/*!
 * This struct represents parsed value of HTTP-field Transfer-Encoding
 * (see https://tools.ietf.org/html/rfc7230#section-3.3.1 and
 * https://tools.ietf.org/html/rfc7230#section-4):
@verbatim
Transfer-Encoding  = 1#transfer-coding
transfer-coding    = "chunked"
                   / "compress"
                   / "deflate"
                   / ("gzip" | "x-gzip")
                   / transfer-extension
transfer-extension = token *( OWS ";" OWS transfer-parameter )
transfer-parameter = token BWS "=" BWS ( token / quoted-string )
@endverbatim
 */
static auto make_parser()
{
   return produce< transfer_encoding_value_t >(
      non_empty_comma_separated_list_p< value_container_t >(
         produce< value_t >(
            alternatives(
               expected_caseless_token_p("chunked") >> just_result(chunked),
               expected_caseless_token_p("compress") >> just_result(compress),
               expected_caseless_token_p("deflate") >> just_result(deflate),
               expected_caseless_token_p("gzip") >> just_result(gzip),
               expected_caseless_token_p("x-gzip") >> just_result(gzip),
               produce< transfer_extension_t >(
                  token_p() >> to_lower() >> &transfer_extension_t::token,
                  params_with_value_p()
                        >> &transfer_extension_t::transfer_parameters
               ) >> as_result()
            )
         )
      ) >> &transfer_encoding_value_t::values
   );
}

Где transfer_encoding_value_t и пр. вещи описываются следующей структурой:

struct transfer_encoding_value_t
{
   //! Enumeration for transfer-coding values from RFC7230.
   enum class known_transfer_coding_t
   {
      chunked,
      compress,
      deflate,
      gzip,
   };

   static constexpr known_transfer_coding_t chunked =
         known_transfer_coding_t::chunked;
   static constexpr known_transfer_coding_t compress =
         known_transfer_coding_t::compress;
   static constexpr known_transfer_coding_t deflate =
         known_transfer_coding_t::deflate;
   static constexpr known_transfer_coding_t gzip =
         known_transfer_coding_t::gzip;

   //! Description of transfer-extension.
   struct transfer_extension_t
   {
      std::string token;
      parameter_with_mandatory_value_container_t transfer_parameters;

      RESTINIO_NODISCARD
      bool
      operator==( const transfer_extension_t & o ) const noexcept
      {
         return std::tie(this->token, this->transfer_parameters) ==
               std::tie(o.token, o.transfer_parameters);
      }
   };

   //! Type for one value from Transfer-Encoding HTTP-field.
   using value_t = variant_t<
         known_transfer_coding_t,
         transfer_extension_t
      >;

   using value_container_t = std::vector< value_t >;

   value_container_t values;
};

4 комментария:

Dmitry Popov комментирует...

Да ну, это похоже на монадные парсер-комбинаторы, которые только ленивый не делал на всем подряд. И с таким количеством скобок и знаков препинания, что можно лишь насмехаться.

Тем временем на "альтернативах С++" можно взять именно вариант BNF, как у вас в комменте выше, отдать его библиотечной ф-ии, она в компайл-тайме распарсит и построит парсер, который можно использовать как в рантайме, так и в компайл-тайме тут же. https://github.com/PhilippeSigaud/Pegged

eao197 комментирует...

@Dmitry Popov

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

А вот сейчас можно. И это всего лишь C++14.

Так что не удивлюсь, если лет через 10-15 на C++ будут делать то, что сейчас кажется совсем невероятным.

А вот будет ли D нужен кому-то через 10 лет... Вот это вопрос.

Dmitry Popov комментирует...

Ну Boost Spirit еще с начала 2000-х существует... Сейчас, наверное, проще стало такие штуки делать.

eao197 комментирует...

Для меня что Boost.Spirit, что Boost.Lambda в свое время были проектами, которые показывают, что в принципе можно сделать, но что делать в принципе не следует. Хотя, конечно, если бы не проекты подобного рода, то C++ до своего сегодняшнего уровня мог бы и не развиться.