如何在 flex lex 中实现捕获组?

How to achieve capturing groups in flex lex?

我想匹配以“#”开头的字符串,然后匹配所有内容,直到匹配“#”后面的字符。这可以使用像这样的捕获组来实现:#(.)[^(?1)]*(?1)EDIT 这个正则表达式也是错误的)。这匹配#$foo$,不匹配#%bar&,匹配#"foo"bar的前6个字符。

但是由于 flex lex 不支持捕获组,这里有什么解决方法?

如你所说,(f)lex 不支持捕获组,当然也不支持反向引用。

所以没有简单的解决方法,但有解决方法。这里有一些可能性:

  1. 你可以使用input()函数一次读取输入的一个字符,直到找到匹配的字符(但你必须创建自己的缓冲区来存储字符,因为input() 读取的字符不会添加到当前标记中)。这不是最有效的,因为一次读取一个字符有点笨拙,但它是 (f)lex 提供的唯一接口。 (以下代码片段假定您有某种可扩展的 stringBuilder;如果您使用的是 C++,则只需将其替换为 std::string。)

    #.   { StringBuilder sb = string_builder_new();
           int delim = yytext[1];
           for (;;) {
             int next = input();
             if (next == delim) break;
             if (next == EOF ) { /* Signal error */; break; }
             string_builder_addchar(next);
           }
           yylval = string_builder_release();
           return DELIMITED_STRING;
         }
    
  2. 效率更低,但也许更方便,您可以使用 yymore() 让 (f)lex 累积 yytext 中的字符,一次匹配一个字符起始条件:

    %x DELIMITED
    %%
      int delim;
    
    #.              { delim = yytext[1]; BEGIN(DELIMITED); }
    <DELIMITED>.|\n { if (yytext[0] == delim) { 
                        yylval = strdup(yytext);
                        BEGIN(INITIAL);   
                        return DELIMITED_STRING;
                      }
                      yymore();
                    }
    <DELIMITED><<EOF>> { /* Signal unterminated string error */ }
    
  3. 最有效的解决方案(在 (f)lex 中)是为每个可能的分隔符编写一个规则。虽然规则很多,但可以使用您喜欢的任何脚本语言的小脚本轻松生成它们。而且,实际上,并没有那么多规则,特别是如果您不允许字母和非打印字符作为分隔符。这还有一个额外的好处,如果你想要类似 Perl 的括号定界符(#(Hello) 而不是 #(Hello(),你可以修改个别模式以适应(正如我在下面所做的那样)。 [注1] 由于所有的动作都是一样的;动作使用宏可能更容易,更容易修改。

      /* Ordinary punctuation */
    #:[^:]*:     { yylval = strndup(yytext + 2, yyleng - 3); return DELIMITED_STRING; }
    #:[^:]*:     { yylval = strndup(yytext + 2, yyleng - 3); return DELIMITED_STRING; }
    #![^!]*!     { yylval = strndup(yytext + 2, yyleng - 3); return DELIMITED_STRING; }
    #\.[^.]*\.   { yylval = strndup(yytext + 2, yyleng - 3); return DELIMITED_STRING; }
      /* Matched pairs */
    #<[^>]*>     { yylval = strndup(yytext + 2, yyleng - 3); return DELIMITED_STRING; }
    #\[[^]]*]    { yylval = strndup(yytext + 2, yyleng - 3); return DELIMITED_STRING; }
      /* Trap errors */
    #            { /* Report unmatched or invalid delimiter error */ }
    

    如果我正在编写脚本来生成这些规则,我会对所有分隔符使用十六进制转义,而不是试图找出哪些需要转义。


备注:

  1. Perl 需要在这样的结构中嵌套平衡括号。但是你不能用正则表达式来做到这一点;如果您想重现 Perl 行为,则需要对其他建议之一使用一些变体。我稍后会尝试重新访问此答案以解决该功能。