Невозможно проанализировать пустую структуру C с помощью Boost Spirit X3

#c #boost #boost-spirit #boost-spirit-x3

#c #boost #boost-spirit #boost-spirit-x3

Вопрос:

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

Это мой код:

 #include <boost/spirit/home/x3.hpp>

int main() {
  namespace x3 = boost::spirit::x3;

  // Parse "#if !defined XXX_X_" or "'#ifndef X_X"
  auto Ifndef = x3::skip(x3::space)[(x3::lit('#') >> (x3::lit("ifndef") | (x3::lit("if") >> x3::lit("!defined"))))];
  auto HeaderGuardFirstRow = Ifndef >>  (x3::alnum | '_');

  // Parse "#define XXX_X" or "#  define XXX_X"
  auto Define = x3::skip(x3::space)[(x3::lit('#') >> x3::lit("define"))];
  auto HeaderGuardSecondRow = Define >>  (x3::alnum | '_');

  // Parse
  // "
  //  #if !defined XXX_X_
  //  #define XXX_X
  // "
  auto HeaderGuardBegin = HeaderGuardFirstRow >> HeaderGuardSecondRow;

  // Parse "#endif" or "#  endif"
  auto HeaderGuardEnd = x3::skip(x3::space)[x3::lit('#') >> (x3::lit("endif"))];

  // Parse variable name like "xxx" or "my_var"
  auto VariableName = x3::lexeme[x3::char_("a-zA-Z_") >> *(x3::alnum | x3::lit("_"))];

  // Skipper for C   comments (nested /* */ are not handled for now)
  auto SingleLineComment = "//" >> *(x3::char_ - x3::eol) >> (x3::eol | x3::eoi);
  auto BlockComment = "/*" >> *(x3::char_ - "*/") >> "*/";
  auto Skipper = SingleLineComment | BlockComment | x3::ascii::space;

  // Parse
  // "
  // typedef struct {
  // } MyStruct;
  // "
  // ERROR: This parse does not work
  auto StructType = -x3::lit("typedef") >> x3::skip(Skipper)[x3::lit("struct") >> x3::lit('{')] >>
  x3::skip(Skipper)[x3::lit('}') >> VariableName >> x3::lit(";")];

  // Header grammar. Should parse
  // "
  // #if !defined XXX_H
  //  #define XXX_H
  //  typedef struct {
  //  } MyStruct;
  //  #endif
  // "
  auto grammar = HeaderGuardBegin >> *(StructType) >> HeaderGuardEnd;

  std::string data01(R"xx(
    #if !defined XXX_H
    #define XXX_H
    #endif
  )xx");

  bool r = phrase_parse(
    data01.begin(),
    data01.end(),
    grammar,
    Skipper
    );

    std::string data02(R"xx(
    #if !defined XXX_H
    // Single line comment
    #define XXX_H
    #endif // !XXX_H
  )xx");

  r = phrase_parse(
    data02.begin(),
    data02.end(),
    grammar,
    Skipper
    );

  std::string data03(R"xx(
    #if !defined XXX_H
    #define XXX_H
    typedef struct {
    } MyStruct;
    #endif
  )xx");

  // r = false: This parsing does not work.
  r = phrase_parse(
    data03.begin(),
    data03.end(),
    grammar,
    Skipper
    );
  return 0;
}
 

В коде есть три строки для анализа: одна только с защитой заголовка, вторая похожа на первую, но с некоторыми комментариями C , а третья с пустой структурой.

Это последняя структура, которая не может быть проанализирована, и я не понимаю, почему. В грамматике, которую я использую для структуры StructType , я сначала проверяю наличие необязательного typedef , затем ключевое struct слово с { символом, который может быть присоединен или нет, затем я ищу } символ, за которым следует имя переменной, за которым следует ; .

Я не понимаю, в чем ошибка. Что я делаю не так при анализе пустой структуры?

Ответ №1:

Несколько вещей:

  • пропускники наследуются окружающим контекстом
  • у вас не было lexeme[] «токенов» в защитных элементах заголовка, поэтому он будет соответствовать, включая typedefstruct , потому space что также включает в себя концы строк.

Вы можете упростить вещи:

Live On Coliru

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

int main() {
    namespace x3 = boost::spirit::x3;

    // Parse "#if !defined XXX_X_" or "'#ifndef X_X"
    auto const Ifndef               = (x3::lit('#') >> (x3::lit("ifndef") | (x3::lit("if") >> x3::lit("!defined"))));
    auto const HeaderGuardFirstRow  = Ifndef >> x3::lexeme[ (x3::alnum | '_')];

    // Parse "#define XXX_X" or "#  define XXX_X"
    auto const Define               = (x3::lit('#') >> x3::lit("define"));
    auto const HeaderGuardSecondRow = Define >> x3::lexeme[ (x3::alnum | '_')];

    // Parse
    // "
    //  #if !defined XXX_X_
    //  #define XXX_X
    // "
    auto const HeaderGuardBegin     = HeaderGuardFirstRow >> HeaderGuardSecondRow;

    // Parse "#endif" or "#  endif"
    auto const HeaderGuardEnd       = x3::lit('#') >> (x3::lit("endif"));

    // Parse variable name like "xxx" or "my_var"
    auto const VariableName         = x3::lexeme[x3::char_("a-zA-Z_") >> *(x3::alnum | x3::lit("_"))];

    // Skipper for C   comments (nested /* */ are not handled for now)
    auto const SingleLineComment    = "//" >> *(x3::char_ - x3::eol) >> (x3::eol | x3::eoi);
    auto const BlockComment         = "/*" >> *(x3::char_ - "*/") >> "*/";
    auto const Skipper              = SingleLineComment | BlockComment | x3::ascii::space;

    auto const StructType = 
        -x3::lit("typedef") 
        >> "struct" >> '{' >> '}' >> VariableName
        >> ";"
        ;

    // Header grammar.
    auto grammar = HeaderGuardBegin >> *StructType >> HeaderGuardEnd;

    for (std::string const data : {
            R"xx(
    #if !defined XXX_H
    #define XXX_H
    #endif
  )xx",
            R"xx(
    #if !defined XXX_H
    // Single line comment
    #define XXX_H
    #endif // !XXX_H
  )xx",
            R"xx(
    #if !defined XXX_H
    #define XXX_H
    typedef struct {
        // aloha
    } MyStruct;

    typedef struct { /* caramba */ } MyOtherStruct
;
    #endif
  )xx" }) {
        auto f = data.begin(), l = data.end();
        std::cout << "Parsing " << std::quoted(data) << "n";
        if (phrase_parse(f, l, grammar, Skipper)) {
            std::cout << "Parsedn";
        } else {
            std::cout << "Failed to parsen";
        }

        if (f!=l) {
            std::cout << "Remaining unparsed: " << std::quoted(std::string(f,l)) << "n";
        }
    }
}
 

С принтами

 Parsing "
    #if !defined XXX_H
    #define XXX_H
    #endif
  "
Parsed
Parsing "
    #if !defined XXX_H
    // Single line comment
    #define XXX_H
    #endif // !XXX_H
  "
Parsed
Parsing "
    #if !defined XXX_H
    #define XXX_H
    typedef struct {
        // aloha
    } MyStruct;

    typedef struct { /* caramba */ } MyOtherStruct
;
    #endif
  "
Parsed
 

Ответ №2:

sehe проделал отличную работу по сохранению вашего кода. Но поскольку у меня было собственное представление о том, что вам нужно, я решил опубликовать. Из этого я узнал, что шкипер может быть довольно сложным. С другой стороны, я обычно пропускал omit .

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

Просто другой способ взглянуть на это.

 #include <iostream>
#define BOOST_SPIRIT_X3_DEBUG
#include <boost/spirit/home/x3.hpp>
using namespace boost::spirit::x3;

// Parse all/skip"#..."
auto const def = lit("#") >> omit[lexeme[*char_("a-zA-Z_! ")]];
// Skipper for C   comments (nested /* */ are not handled for now)
auto const comment = ("//" >> omit[*(char_ - eol)]) | ("/*" >> omit[*(char_ - "*/")] >> "*/");
auto const skipper = *(def | comment | space);

// Parse variable name like "xxx" or "my_var"
auto const name = rule<struct name, std::string>("name") = *char_("a-zA-Z_");

auto const struct_rule = lit("typedef") >> "struct" >> '{' >> omit[*(char_-'}')] >> '}' >> name >> ';';

auto const final = skipper >> -struct_rule >> skipper;

void parse(char* in)
{
    std::string str(in);
    auto it = str.begin();
    std::string dest;
    bool r = phrase_parse(it, str.end(), final, space, dest);// , dest);
    std::cout << std::boolalpha << "r: " << r << std::endl
        << std::string(it, str.end()) << std::endl
        << "DEST: " << dest << std::endl;
}

int main()
{
    parse(R"xx(
        #if !defined XXX_H
        #define XXX_H
        #endif
      )xx");

    parse(R"xx(
        #if !defined XXX_H
        // Single line comment
        #define XXX_H
        #endif // !XXX_H
      )xx");

    parse(R"xx(
        #if !defined XXX_H
        #define XXX_H
        typedef struct {
        } MyStruct;
        #endif
      )xx");
    return 0;
}
 

С принтами:

 r: true

DEST:
r: true

DEST:
<name>
  <try> MyStruct;ntt#endifn</try>
  <success>;ntt#endifnt  </success>
  <attributes>[M, y, S, t, r, u, c, t]</attributes>
</name>
r: true

DEST:   MyStruct
 

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

1. Спасибо. Я бы хотел отбросить защиту заголовка, но мне нужно определить, потому что их значение может использоваться как размер массива в строгом. Но я хочу делать один шаг за раз.

2. Рассматривали ли вы возможность использования libclang для выполнения того, что вам нужно, вместо того, чтобы изобретать известное сложное колесо?

3. @lakeweb если мы допустим упрощения, вы могли бы рассмотреть возможность использования seek[] вместо этого: coliru.stacked-crooked.com/a/f7176342d826e00d

4. Спасибо @sehe. seek гораздо более лаконичен для такого рода работы.