【问题标题】:How to achieve capturing groups in flex lex?如何在 flex lex 中实现捕获组?
【发布时间】:2021-06-08 10:43:03
【问题描述】:

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

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

【问题讨论】:

  • 您想到了哪个正则表达式库,它接受[^(?1)] 作为有效的字符类?我不熟悉它。
  • 我只是想到了。无效吗?
  • 我知道的所有允许反向引用的正则表达式库只允许它们作为字符串匹配,而不是作为字符类的成员。但是有很多正则表达式库,我不能声称知道所有这些。
  • 是的,很抱歉。我也没有找到。所以这个特殊问题在所有现有/已知的库中都没有解决。 :)
  • 在大多数正则表达式库中,您会使用非贪婪匹配而不是排除。 (但是 (f)lex 也没有非贪婪匹配。)所以你可以写#(.)(.*?)\1。 (regex101.com/r/bYnaPq/1)(Regex101 很酷,但对 (f)lex 一点用处都没有。)

标签: flex-lexer lex capturing-group


【解决方案1】:

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

所以没有简单的解决方法,但有解决方法。以下是几种可能性:

  1. 您可以使用input()函数一次读取输入的一个字符,直到找到匹配的字符(但您必须创建自己的缓冲区来存储字符,因为input()读取的字符不会被添加到当前令牌)。这不是最有效的,因为一次读取一个字符有点笨拙,但它是 (f)lex 提供的唯一接口。 (以下 sn-p 假设您有某种可扩展的 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. 效率更低,但可能更方便,您可以让 (f)lex 使用 yymore() 累积 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 行为,则需要对其他建议之一使用一些变体。稍后我将尝试重新访问此答案以解决该功能。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多