Как создать правила Yacc / Lex для встраивания фрагментов исходного кода на C? - PullRequest
0 голосов
/ 15 сентября 2018

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

token host<prio=1> = "[Hh][Oo][Ss][Tt]" ;
token ospace = "[ \t]*" ;
token htoken = "[-!#$%&'*+.^_`|~0-9A-Za-z]+" ;
token hfield = "[\t\x20-\x7E\x80-\xFF]*" ;
token space = " " ;
token htab = "\t" ;
token colon = ":" ;

obsFoldStart = 1*( space | htab ) ;
hdrField =
  obsFoldStart hfield
| host colon ospace hfield<print>
| htoken colon ospace hfield
  ;

Лексер основан на максимальном правиле munch, и токены динамически включаются и выключаются в зависимости отконтекст, поэтому нет конфликта между htoken и hfield, а значение приоритета разрешает конфликт между host и htoken.Я планирую реализовать синтаксический анализатор как анализатор таблиц LL (1).Я еще не решил, буду ли я реализовывать сопоставление токенов регулярных выражений, имитируя недетерминированный конечный автомат, или пройду весь путь, чтобы взорвать его до детерминированного конечного автомата.

Теперь я хотел бы включить некоторый C-источниккод на входе моего генератора синтаксического анализатора:

hdrField =
  obsFoldStart hfield
| host {
  parserState->userdata.was_host = 1;
} colon ospace hfield<print>
| htoken {
  parserState->userdata.was_host = 0;
} colon ospace hfield
  ;

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

Как это сделать?Я обрабатываю комментарии, используя BEGIN(COMMENTS) и BEGIN(INITIAL), но я не верю, что такая стратегия будет работать для встроенного C-источника.Кроме того, обработка комментариев может сильно усложнить обработку исходного кода C, поскольку я не верю, что один токен может содержать комментарий в середине.

По сути, мне нужен фрагмент встроенного языка Cкак строку C, которую я могу сохранить в своих структурах данных.

1 Ответ

0 голосов
/ 15 сентября 2018

Итак, я взял часть сгенерированного кода lex и сделал его автономным.

Надеюсь, все в порядке, что я использовал код C ++, хотя я распознал только .ИМХО, это касается только не очень важных частей этого примера кода.(Управление памятью в C гораздо сложнее, чем просто делегировать это std::string.)

scanC.l:

%{

#include <iostream>
#include <string>

#ifdef _WIN32
/// disables #include <unistd.h>
#define YY_NO_UNISTD_H
#endif // _WIN32

// buffer for collected C/C++ code
static std::string cCode;
// counter for braces
static int nBraces = 0;

%}

/* Options */

/* make never interactive (prevent usage of certain C functions) */
%option never-interactive
/* force lexer to process 8 bit ASCIIs (unsigned characters) */
%option 8bit
/* prevent usage of yywrap */
%option noyywrap


EOL ("\n"|"\r"|"\r\n")
SPC ([ \t]|"\\"{EOL})*
LITERAL "\""("\\".|[^\\"])*"\""

%s CODE

%%

<INITIAL>"{" { cCode = '{'; nBraces = 1; BEGIN(CODE); }
<INITIAL>. |
<INITIAL>{EOL} { std::cout << yytext; }
<INITIAL><<EOF>> { return 0; }

<CODE>"{" {
  cCode += '{'; ++nBraces;
  //updateFilePos(yytext, yyleng);
} break;
<CODE>"}" {
  cCode += '}'; //updateFilePos(yytext, yyleng);
  if (!--nBraces) {
    BEGIN(INITIAL);
    //return new Token(filePosCCode, Token::TkCCode, cCode.c_str());
    std::cout << '\n'
      << "Embedded C code:\n"
      << cCode << "// End of embedded C code\n";
  }
} break;

<CODE>"/*" { // C comments
  cCode += "/*"; //_filePosCComment = _filePos;
  //updateFilePos(yytext, yyleng);
  char c1 = ' ';
  do {
    char c0 = c1; c1 = yyinput();
    switch (c1) {
      case '\r': break;
      case '\n':
        cCode += '\n'; //updateFilePos(&c1, 1);
        break;
      default:
        if (c0 == '\r' && c1 != '\n') {
          c0 = '\n'; cCode += '\n'; //updateFilePos(&c0, 1);
        } else {
          cCode += c1; //updateFilePos(&c1, 1);
        }
    }
    if (c0 == '*' && c1 == '/') break;
  } while (c1 != EOF);
  if (c1 == EOF) {
    //ErrorFile error(_filePosCComment, "'/*' without '*/'!");
    //throw ErrorFilePrematureEOF(_filePos);
    std::cerr << "ERROR! '/*' without '*/'!\n";
    return -1;
  }
} break;
<CODE>"//"[^\r\n]* | /* C++ one-line comments */
<CODE>"'"("\\".|[^\\'])+"'" | /*"/* C/C++ character constants */
<CODE>{LITERAL} | /* C/C++ string constants */
<CODE>"#"[^\r\n]* | /* preprocessor commands */
<CODE>[ \t]+ | /* non-empty white space */
<CODE>[^\r\n] { // any other character except EOL
  cCode += yytext;
  //updateFilePos(yytext, yyleng);
} break;
<CODE>{EOL} { // special handling for EOL
  cCode += '\n';
  //updateFilePos(yytext, yyleng);
} break;
<CODE><<EOF>> { // premature EOF
  //ErrorFile error(_filePosCCode,
  //  compose("%1 '{' without '}'!", _nBraces));
  //_errorManager.add(error);
  //throw ErrorFilePrematureEOF(_filePos);
  std::cerr << "ERROR! Premature end of input. (Not enough '}'s.)\n";
}

%%

int main(int argc, char **argv)
{
  return yylex();
}

Пример текста для сканирования scanC.txt:

Hello juhist.

The text without braces doesn't need to have any syntax.
It just echoes the characters until it finds a block:
{ // the start of C code
  // a C++ comment
  /* a C comment
   * (Remember that nested /*s are not supported.)
   */
  #define MAX 1024
  static char buffer[MAX] = "", empty="\"\"";

  /* It is important that tokens are recognized to a limited amount.
   * Otherwise, it would be too easy to fool the scanner with }}}
   * where they have no meaning.
   */
  char *theSameForStringConstants = "}}}";
  char *andCharConstants = '}}}';

  int main() { return yylex(); }
}
This code should be just copied
(with a remark that the scanner recognized the C code a such.)

Greetings, Scheff.

Скомпилировано и протестировано на cygwin64 :

$ flex --version
flex 2.6.4

$ flex -o scanC.cc scanC.l

$ g++ --version
g++ (GCC) 7.3.0

$ g++ -std=c++11 -o scanC scanC.cc

$ ./scanC < scanC.txt
Hello juhist.

The text without braces doesn't need to have any syntax.
It just echoes the characters until it finds a block:

Embedded C code:
{ // the start of C code
  // a C++ comment
  /* a C comment
   * (Remember that nested /*s are not supported.)
   */
  #define MAX 1024
  static char buffer[MAX] = "", empty="\"\"";

  /* It is important that tokens are recognized to a limited amount.
   * Otherwise, it would be too easy to fool the scanner with }}}
   * where they have no meaning.
   */
  char *theSameForStringConstants = "}}}";
  char *andCharConstants = '}}}';

  int main() { return yylex(); }

}// End of embedded C code
This code should be just copied
(with a remark that the scanner recognized the C code a such.)

Greetings, Scheff.
$

Примечания:

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

  2. То, что я увидел при его адаптации: продолжение строки препроцессора не обрабатывается.

  3. Конечно, можно обмануть инструмент творческой комбинацией макросов с несбалансированным { } - то, что мы никогда не сделаем в чистом производительном коде (см. 1.).

Таким образом, это может быть, по крайней мере, началом дальнейшей разработки.

Чтобы сравнить это со спецификацией C lex, у меня есть ANSI C грамматика, спецификация Lex под рукой, хотя ему 22 года.(Возможно, есть более новые, соответствующие текущим стандартам.)

...