问题描述
我想匹配以“#”开头的字符串,然后匹配所有内容,直到匹配“#”后面的字符。这可以使用这样的捕获组来实现:#(.)[^(?1)]*(?1)
(EDIT 这个正则表达式也是错误的)。这匹配 #$foo$
,不匹配 #%bar&
,匹配 #"foo"bar
的前 6 个字符。
但是由于 flex lex 不支持捕获组,这里的解决方法是什么?
解决方法
如您所说,(f)lex 不支持捕获组,当然也不支持反向引用。
所以没有简单的解决方法,但有解决方法。以下是几种可能性:
-
您可以使用
input()
函数一次读取一个字符,直到找到匹配的字符(但您必须创建自己的缓冲区来存储字符,因为input()
读取的字符{1}} 不会添加到当前令牌中)。这不是最有效的,因为一次读取一个字符有点笨重,但它是 (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; }
-
效率更低,但也许更方便,您可以使用
yytext
获得 (f)lex 累积yymore()
中的字符,在开始条件下一次匹配一个字符:%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 */ }
-
最有效的解决方案(在 (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 */ }
如果我要编写一个脚本来生成这些规则,我会对所有分隔符使用十六进制转义,而不是试图找出哪些需要转义。
注意事项:
- Perl 要求在这样的结构中嵌套平衡括号。但是你不能用正则表达式做到这一点;如果您想重现 Perl 行为,您需要对其他建议之一使用一些变体。我稍后会尝试重新访问此答案以解决该功能。