Spirit X3, Как получить тип атрибута, соответствующий типу правила? - PullRequest
1 голос
/ 29 июня 2019

Для разработки парсера Spirit X3 я хочу использовать семантические действия (сноска 1). Для меня важно контролировать, как хранить атрибуты в контейнерах STL.

Этот вопрос о том, как контролировать, чтобы атрибут анализатора: _attr (ctx) соответствовал типу правила: _val (ctx), чтобы его можно было назначить правильно. Может быть, этот вопрос сводится к тому, как применять недокументированные transform_attribute функция. Но, пожалуйста, прочитайте со мной, чтобы увидеть, действительно ли это то, что решает это для меня в примере кода.

Типы печати объектов / переменных

Что мне показалось очень полезным, так это возможность печатать типы _attr (ctx) и _val (ctx) в семантическом действии, когда я экспериментирую с различными грамматическими выражениями.

Итак, основываясь на ответе Говарда Хиннанта , я написал файл заголовка утилиты, чтобы предоставить такие средства в соответствии с моими предпочтениями.

code below is to be put in a file named utility.h

#include <string>
#include <type_traits>
#include <typeinfo>
#include <cxxabi.h>

namespace utility
{

template<typename T>
std::string type2string()
{
  std::string r;
  typedef typename std::remove_reference<T>::type TR;

  std::string space = "";
  if ( std::is_const<TR>::value )
    { r = "const"; space = " "; }
  if ( std::is_volatile<TR>::value )
    { r += space + " volatile"; space = " "; }

  int status;
  char* demangled =
    abi::__cxa_demangle( typeid(TR).name(), nullptr, nullptr, &status );
  switch ( status )
  {
    case  0: { goto proceed; }
    case -1: { r = "type2string failed: malloc failure"; goto fail; }
    case -2: { r = "type2string failed: " + std::string(typeid(TR).name()) +
      " nonvalid C++ ABI name"; goto fail; }
    case -3: { r = "type2string failed: invalid argument(s)"; goto fail; }
    default: { r = "type2string failed: unknown status " +
      status; goto fail; }
  }
  proceed:
  r += space + demangled;
  free( demangled );

  /* references are without a space */
  if ( std::is_lvalue_reference<T>::value ) { r += '&'; }
  if ( std::is_rvalue_reference<T>::value ) { r += "&&"; }

  fail:
  return r;
}

}

Теперь действительный пример кода:

#include <cstddef>
#include <cstdio>
#include <cstdint>

#define BOOST_SPIRIT_X3_DEBUG
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/home/x3.hpp>

#include <string>
#include <vector>
#include <utility> // this is for std::move
#include "utility.h" // to print types

namespace client
{
  namespace x3 = boost::spirit::x3;
  namespace ascii = boost::spirit::x3::ascii;

  namespace semantic_actions
  {
    using x3::_val;  // assign to _val( ctx )
    using x3::_attr; // from _attr( ctx )    

    struct move_assign
    {  
      template <typename Context>
      void operator()(const Context& ctx) const
      {
        printf( "move_assign\n" );
        _val( ctx ) = std::move( _attr( ctx ) );
      }
    };

    struct print_type
    {
      template <typename Context>
      void operator()(const Context& ctx) const
      {
        printf( "print_type\n" );

        std::string str;
        str = utility::type2string< decltype( _attr( ctx ) ) >();
        printf( "_attr type: %s\n", str.c_str() );

        // reuse str
        str = utility::type2string< decltype( _val( ctx ) ) >();
        printf( "_val type: %s\n", str.c_str() );
      }
    };
  }

  namespace parser
  {
    using x3::char_;
    using x3::lit;
    using namespace semantic_actions;

    x3::rule<struct main_rule_class, std::string> main_rule_ = "main_rule";

    const auto main_rule__def = (*( !lit(';') >> char_) >> lit(';'))[print_type()][move_assign()];

    BOOST_SPIRIT_DEFINE( main_rule_ )

    const auto entry_point = x3::skip(x3::space)[ main_rule_ ];
  }
}

int main()
{
  printf( "Give me a string to test rule.\n" );
  printf( "Type [q or Q] to quit.\n" );

  std::string input_str;
  std::string output_str;

  while (getline(std::cin, input_str))
  {
    if ( input_str.empty() || input_str[0] == 'q' || input_str[0] == 'Q')
    { break; }

    auto first = input_str.begin(), last = input_str.end();

    if ( parse( first, last, client::parser::entry_point, output_str) )
    {
      printf( "Parsing succeeded\n" );
      printf( "input:  \"%s\"\n", input_str.c_str() );
      printf( "output: \"%s\"\n", output_str.c_str() );
    }
    else
    {
      printf( "Parsing failed\n" );
    }
  }

  return 0;
}

Ввод всегда: abcd;

выход:

Give me a string to test rule.
Type [q or Q] to quit.
<main_rule>
  <try>abcd;</try>
print_type
_attr type: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&
_val type: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&
move_assign
  <success></success>
  <attributes>[a, b, c, d]</attributes>
</main_rule>
Parsing succeeded
input:  "abcd;"
output: "abcd"

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

const auto main_rule__def = (*( !lit(';') >> char_) >> char_(";"))[print_type()];

Примечание. Я удалил семантическое действие [move_assign ()], поскольку оно не может быть скомпилировано из-за несовместимых типов _attr и _val. Теперь вывод:

Give me a string to test rule.
Type [q or Q] to quit.
<main_rule>
  <try>abcd;</try>
print_type
_attr type: boost::fusion::deque<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, char>&
_val type: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&
  <success></success>
  <attributes>[]</attributes>
</main_rule>
Parsing succeeded
input:  "abcd;"
output: ""

Теперь тип _attr boost :: fusion :: deque <> - это не то, что я хочу, я просто то, что должно быть std :: string. Я не понимаю, почему, если у меня есть полная правая часть грамматического назначения в скобках семантического действия, _attr все еще не имеет тип _val. Поможет ли здесь функция X3 transform_attribute? И как мне это применить? Или как еще один хороший способ решить эту проблему, не работая с интерфейсами Boost Fusion или другими деталями реализации.

Текущее решение

Текущий обходной путь для меня - определить другое правило, которое будет назначено из первого правила семантическим действием. Только там _attr имеет тип std :: string.

  namespace parser
  {
    using x3::char_;
    using x3::lit;
    using namespace semantic_actions;

    x3::rule<struct main_rule_class, std::string> main_rule_ = "main_rule";
    x3::rule<struct main_rule2_class, std::string> main_rule2_ = "main_rule2";

    const auto main_rule__def = *( !lit(';') >> char_) >> char_(";");
    const auto main_rule2__def = main_rule_[print_type()][move_assign()];

    BOOST_SPIRIT_DEFINE( main_rule_, main_rule2_ )

    const auto entry_point = x3::skip(x3::space)[ main_rule2_ ];
  }

выход:

Give me a string to test rule.
Type [q or Q] to quit.
<main_rule2>
  <try>abcd;</try>
  <main_rule>
    <try>abcd;</try>
    <success></success>
    <attributes>[a, b, c, d, ;]</attributes>
  </main_rule>
print_type
_attr type: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&
_val type: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&
move_assign
  <success></success>
  <attributes>[a, b, c, d, ;]</attributes>
</main_rule2>
Parsing succeeded
input:  "abcd;"
output: "abcd;"

Я надеюсь, что есть способ без необходимости создавать другое правило, просто чтобы тип _attr соответствовал _val.

* * Тысяча сорок-девять (1) Я не ценю скрытый ум, который авторы вложили в эту библиотеку. Как только одно невинно выглядящее изменение может сломать приложение. Принимая во внимание, что более четкий и продуманный подход будет гораздо яснее сообщать, что происходит Я просто должен снять это с моей груди.

Ответы [ 2 ]

1 голос
/ 30 июня 2019

Прямой ответ

transform_attribute еще не пока задокументировано для X3 (https://www.boost.org/doc/libs/1_70_0/libs/spirit/doc/x3/html/index.html), но вы можете найти его аналог Qi здесь: https://www.boost.org/doc/libs/1_70_0/libs/spirit/doc/html/spirit/advanced/customize/transform.html.

Может ли функция X3 transform_attribute помочь здесь? И как мне это применить?

Несмотря на это, это деталь реализации, к которой вы можете легко получить доступ, используя правила. Мне нравится использовать анонимные правила, чтобы помочь с этим:

template <typename T>
    struct as_type {
        template <typename E>
        constexpr auto operator[](E e) const { return x3::rule<struct _, T> {} = e; }
    };

template <typename T>
    static inline constexpr as_type<T> as;

Теперь вы можете написать

const auto main_rule__def = as<std::string> [ (*(char_ - ';') >> char_(';')) ];

Live On Coliru

#include <iostream>
//#define BOOST_SPIRIT_X3_DEBUG
#include <boost/spirit/home/x3.hpp>
#include <iomanip> // std::quoted

namespace client {
    namespace x3 = boost::spirit::x3;
    namespace ascii = boost::spirit::x3::ascii;

    namespace parser {
        using x3::char_;
        using x3::lit;

        x3::rule<struct main_rule_class, std::string> main_rule_ = "main_rule";

        template <typename T>
            struct as_type {
                template <typename E>
                constexpr auto operator[](E e) const { return x3::rule<struct _, T> {} = e; }
            };

        template <typename T>
            static inline constexpr as_type<T> as;

        const auto main_rule__def = as<std::string> [ (*(char_ - ';') >> char_(';')) ];

        BOOST_SPIRIT_DEFINE(main_rule_)

        const auto entry_point = x3::skip(x3::space)[main_rule_];
    } // namespace parser
} // namespace client

int main() {
    std::string output_str;
    for(std::string const input_str : { "abcd;" }) {
        auto first = input_str.begin(), last = input_str.end();

        if (parse(first, last, client::parser::entry_point, output_str)) {
            std::cout << "Parsing succeeded\n";
            std::cout << "input:  " << std::quoted(input_str) << "\n";
            std::cout << "output:  " << std::quoted(output_str) << "\n";
        } else {
            std::cout << "Parsing failed\n";
        }
    }
}

печать

Parsing succeeded
input:  "abcd;"
output:  "abcd;"

Теоретически это может привести к снижению производительности, но я сильно подозреваю, что все компиляторы здесь все встроат, поскольку ничто не имеет внешних связей или таблиц vtables, и все это const / constexpr.

Альтернативы, упрощения:

Использование x3::raw

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

Live On Coliru

const auto main_rule__def = x3::raw [ *(char_ - ';') >> ';' ];

Не используйте rule<> всегда

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

Live On Coliru

#include <iostream>
#include <boost/spirit/home/x3.hpp>
#include <iomanip> // std::quoted

namespace x3 = boost::spirit::x3;
namespace client::parser {
    auto const entry_point = x3::raw [ *(x3::char_ - ';') >> ';' ];
}

int main() {
    for(std::string const input : { "abcd;" }) {
        std::string output;
        if (parse(input.begin(), input.end(), client::parser::entry_point, output)) {
            std::cout << "Parsing succeeded\n";
            std::cout << "input:  " << std::quoted(input) << "\n";
            std::cout << "output: " << std::quoted(output) << "\n";
        } else {
            std::cout << "Parsing failed\n";
        }
    }
}

Наконец - о пропуске

Я не думаю, что вы хотите char_ - ';' (или более сложный способ, которым вы написали это: !lit(';') >> char_). С помощью шкипера он будет анализировать пробелы ("ab c\nd ;" -> "abcd;" `).

Возможно, вы захотите сделать правило более ограничительным (например, lexeme [+(graph - ';')] или даже просто raw[lexeme[+(alnum|'_')] или lexeme[+char_("a-zA-Z0-9_")]).

См. Проблемы с бустером духа

1 голос
/ 30 июня 2019

При использовании char_ (';') атрибут состоит из 2 частей. Обе части должны быть добавлены в _val. Что-то вроде:

namespace semantic_actions
{
  using x3::_val;  // assign to _val( ctx )
  using x3::_attr; // from _attr( ctx )    
  using boost::fusion::at_c;

  struct move_assign
  {  
    template <typename Context>
    void operator()(const Context& ctx) const
    {
      printf( "move_assign\n" );
      auto attr=_attr( ctx );
      _val( ctx ) = at_c<0>( attr );
      _val( ctx ) += at_c<1>( attr );       
    }
  };
.
.
.
}
...