如何创建 Yacc/Lex 嵌入 C 源代码片段的规则?

How to create Yacc/Lex rules for embedding C source code snippets?

我正在实现一个带有嵌入式词法分析器和解析器的自定义解析器生成器,以 event-driven 状态机方式解析 HTTP headers。下面是最终的解析器生成器可以使用的一些定义,用于解析最后没有 CRLF 的单个 header 字段:

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
  ;

词法分析器基于最大咀嚼规则,根据上下文动态打开和关闭令牌,因此 htokenhfield 之间没有冲突,并且优先级值解决了 hosthtoken 之间的冲突。我计划将解析器实现为 LL(1) table 解析器。我还没有决定是通过模拟非确定性有限自动机来实现正则表达式标记匹配,还是一直将其分解为确定性有限自动机。

现在,我想在我的解析器生成器输入中包含一些 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 字符串存储到我的数据结构中。

因此,我采用了一些生成的 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 grammar, Lex specification,尽管它已经 22 岁了。 (可能有符合当前标准的更新版本。)