如何在 flex lex 中实现捕获组?
How to achieve capturing groups in flex lex?
我想匹配以“#”开头的字符串,然后匹配所有内容,直到匹配“#”后面的字符。这可以使用像这样的捕获组来实现:#(.)[^(?1)]*(?1)
(EDIT 这个正则表达式也是错误的)。这匹配#$foo$
,不匹配#%bar&
,匹配#"foo"bar
的前6个字符。
但是由于 flex lex 不支持捕获组,这里有什么解决方法?
如你所说,(f)lex 不支持捕获组,当然也不支持反向引用。
所以没有简单的解决方法,但有解决方法。这里有一些可能性:
你可以使用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;
}
效率更低,但也许更方便,您可以使用 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 */ }
最有效的解决方案(在 (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 行为,则需要对其他建议之一使用一些变体。我稍后会尝试重新访问此答案以解决该功能。
我想匹配以“#”开头的字符串,然后匹配所有内容,直到匹配“#”后面的字符。这可以使用像这样的捕获组来实现:#(.)[^(?1)]*(?1)
(EDIT 这个正则表达式也是错误的)。这匹配#$foo$
,不匹配#%bar&
,匹配#"foo"bar
的前6个字符。
但是由于 flex lex 不支持捕获组,这里有什么解决方法?
如你所说,(f)lex 不支持捕获组,当然也不支持反向引用。
所以没有简单的解决方法,但有解决方法。这里有一些可能性:
你可以使用
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; }
效率更低,但也许更方便,您可以使用
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 */ }
最有效的解决方案(在 (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 行为,则需要对其他建议之一使用一些变体。我稍后会尝试重新访问此答案以解决该功能。