如何处理flex中的嵌套注释

how to handle nested comment in flex

本文关键字:嵌套 注释 flex 何处理 处理      更新时间:2023-10-16

我正在为一种支持嵌套注释的语言编写一个flex扫描程序,如下所示:

/*
/**/
*/

我曾经在ocaml/ocamlex上工作,它非常优雅地支持递归调用lex scanner。但我现在正在切换到c++/flex,如何处理这样的嵌套注释?

假设只有注释可以嵌套在注释中,对于一个简单的计数器来说,堆栈是一个非常昂贵的解决方案。例如:

%x SC_COMMENT
%%
  int comment_nesting = 0;  /* Line 4 */
"/*"             { BEGIN(SC_COMMENT); }
<SC_COMMENT>{
  "/*"           { ++comment_nesting; }
  "*"+"/"        { if (comment_nesting) --comment_nesting;
                   else BEGIN(INITIAL); }
  "*"+           ; /* Line 11 */
  [^/*n]+       ; /* Line 12 */
  [/]            ; /* Line 13 */
  n             ; /* Line 14 */
}

一些解释:

第4行:第一条规则之前的缩进行插入yylex函数的顶部,用于声明和初始化局部变量。我们使用它在每次调用yylex时将注释嵌套深度初始化为0。必须保持的不变量是comment_nestingINITIAL状态下总是0。

第11-13行:一个更简单的解决方案是单一模式CCD_ 5,但这将导致每个评论字符都被视为一个单独的子标记。即使相应的操作什么都不做,这也会导致扫描循环中断,并对每个字符执行操作切换语句。因此,通常最好尝试同时匹配几个字符。

不过,我们需要小心/*字符;我们只能忽略那些我们确信不是终止(可能嵌套的)注释的*的星号。因此,第11行和第12行。(第12行不会匹配后面跟着/的星号序列,因为这些星号已经被上面第9行的模式匹配了。)如果后面没有*。因此,第13行。

第14行:然而,匹配过大的代币也可能是次优的。

首先,flex没有针对大型令牌进行优化,并且注释可能非常大。如果flex需要在令牌中间重新填充其缓冲区,它将在新的缓冲区中保留打开的令牌,然后从令牌的开头重新扫描。

其次,柔性扫描仪可以自动跟踪当前的行号,而且相对高效。扫描程序只在与可能匹配换行符的模式匹配的标记中检查换行符。但整个比赛都需要扫描。

我们通过将注释中的换行符作为单独的标记进行匹配来减少这两个问题的影响。(第14行,另请参见第12行)这将yylineno扫描限制为单个字符,还限制了内部注释标记的预期长度。注释本身可能非常大,但每一行都可能被限制在合理的长度内,从而避免了缓冲区重新填充时潜在的二次重新扫描。

我通过使用yy_push_state、yy_pop_state和启动条件来解决这个问题,如下所示:

%x comment 
%%
"/*" {
  yy_push_state(comment);
}
<comment>{
  "*/" {
    yy_pop_state();
  }
  "/*" {
    yy_push_state(comment);
  }
}
%%

通过这种方式,我可以处理任何级别的嵌套注释。