Flex & Bison:是否可以在 Flex & Bison 中使用反向引用来解析原始字符串文字?
Flex & Bison : Is it possible to parse Raw string literal using Backreference in Flex&Bison?
我尝试使用 Flex&Bison 解析原始字符串文字。但是无法解析。
输入:
R"foo(Hello World)foo"
弹性:
...
raw_string R"([^\(]*)\(([^\)]*)\)"
%%
{raw_string} {return raw_string;}
%%
野牛:
%{
...
%}
%token raw_string
%start run
%%
run : raw_string;
%%
int main()
{
yyin = stdin;
do
{
yyparse();
} while(!feof(yyin));
return =0;
}
错误:
invalid Syntax: at raw_string
请帮助我使用 Flex 和 Bison 解析原始字符串文字。如果无法进行反向引用,是否有其他方法可以在 flex 和 bison 中解析原始字符串。
Flex patterns 既没有反向引用也没有 non-greedy 匹配项,您需要这两者才能正确识别带有“正则”表达式的原始字符串。 [注1]
然而,flex 确实有一个特性 -- "start conditions" -- 这使得实现这些特性变得非常简单,至少在这种情况下你永远不需要回溯来尝试不同的反向引用子模式.
开始条件是对给定的词汇上下文使用不同规则集的一种方式;在这种情况下,原始字符串中的上下文。
注意:原始代码有一个错误,如果原始字符串紧跟几个 non-whitespace 个字符和另一个双引号。这是一个不太可能发生的情况,这或许可以解释为什么 no-one 注意到了这个错误。我通过删除所有尝试的聪明来修复它。向复制错误代码的任何人道歉。
因此(简化的)C++ 原始字符串的框架解决方案是:
%x C_RAW_STRING
dchar_seq [^()\[:space:]]{0,16}
%%
size_t delim_len;
R["]{dchar_seq}[(] { delim_len = yyleng - 3;
yymore();
BEGIN(C_RAW_STRING);
}
R["]{dchar_seq} { yyerror("Invalid raw string opener"); }
/* Rules for other tokens omitted */
[[:space:]]+ ; /* Ignore whitespace */
. return *yytext; /* Fallback rule */
<C_RAW_STRING>{
[^"]+ yymore();
["] { if (yytext[yyleng - (delim_len + 2)] == ')' &&
memcmp(yytext + yyleng - (delim_len + 1),
yytext + 2, delim_len) == 0) {
BEGIN(INITIAL);
return STRING_LITERAL;
}
else yymore();
}
<<EOF>> { yyerror("Unterminated raw string");
BEGIN(INITIAL);
return 0;
}
}
一些解释
第 1 行:将开始条件 C_RAW_STRING
声明为独占。 (参见上面的 flex 手册,link)。
第 2 行:dchar_seq
匹配 C++ 标准称为“d-char-sequence”的内容(参见 [lex.string],§5.13.5):最多16 个字符,除了括号、反斜杠或空白字符之外的任何字符。 (请注意,"
是 d-char。)有关正则表达式运算符的详细信息,请参阅 Flex 手册中有关模式的章节。
第 4 行:规则部分开头的缩进语句,在第一条规则之前,被简单地插入到 yylext
函数的顶部。因此,它们可用于声明在 yylex
.
的单次调用期间使用的局部变量
第 5-8 行:当我们检测到原始字符串文字的开头时,我们:
- 记住分隔符字符串有多长。 (由于我们会在token的开头留下分隔符字符串,所以我们不需要复制到任何地方。知道它有多长就足够了。)
- 使用
yymore()
告诉词法分析器将下一个匹配附加到当前标记,而不是开始一个新标记。
- 使用
BEGIN
更改为C_RAW_STRING
开始条件。
第 9 行:如果我们发现 R"
并且第 5 行的模式不匹配,则原始字符串定界符不正确,要么是因为它太长,要么因为它包含无效字符或因为它没有以 (
结尾。只匹配 R"
就可以了,但我也选择了匹配最长的 d-char 序列以避免回溯词法分析器,尽管在这种特殊情况下它没有太大区别。由于 longest match rule,只有在前面的规则不触发时才会触发此规则。
从这样的错误中恢复真的很难,因为没有好的方法可以知道引用的字符串应该在哪里结束。在这里我没有尝试任何错误恢复,因为这不是这个答案的重点。
第 15 行:启动 C_RAW_STRING
开始条件的规则块。这是一个 flex 扩展,在其他 lex 实现中不可用。为了兼容性,所有模式都必须单独标记为 <C_RAW_STRING>
.
第 16 行:除了双引号之外的任何字符序列都只是附加到令牌。同样,yymore()
表示我们还没有完成令牌。
第 17-24 行:原始字符串必须以引号结尾,但引号不一定以原始字符串结尾。所以我们检查我们遇到的每一个报价。给定定界符字符串的长度,我们首先确保在我们预期的位置存在 )
,然后使用 memcmp
比较两个定界符序列。 (我们使用 memcmp
而不是 strcmp
因为我们比较的序列不是 NUL-terminated;它们是标记的子串。strncmp
是另一种可能性。)
如果分隔符字符串匹配,我们就找到了令牌的结尾,我们可以 return 它。在这种情况下,我们需要将开始条件重置为 INITIAL
以便正常扫描下一个令牌。如果字符串不匹配,我们使用 yymore()
告诉 yylex
我们还没有完成标记,并继续寻找正确的分隔符。
第 25-29 行:如果在原始字符串中检测到输入结束,则意味着原始字符串未正确终止。我们发出一条错误消息和 return 0 表示遇到了 EOF。此时重置开始条件并不是真正必要的,但这样做似乎更干净。
备注
例如,您可以使用以下 Gnu grep 命令(使用 -P
选项启用 PCRE 正则表达式功能),除了因为它是一个简单的正则表达式搜索而不是标记器,它可能会产生误报(例如,注释中看起来像原始字符串的内容):
grep -Po '"([^()\[:space:]]*)\(.*?\)"'
我尝试使用 Flex&Bison 解析原始字符串文字。但是无法解析。
输入:
R"foo(Hello World)foo"
弹性:
...
raw_string R"([^\(]*)\(([^\)]*)\)"
%%
{raw_string} {return raw_string;}
%%
野牛:
%{
...
%}
%token raw_string
%start run
%%
run : raw_string;
%%
int main()
{
yyin = stdin;
do
{
yyparse();
} while(!feof(yyin));
return =0;
}
错误:
invalid Syntax: at raw_string
请帮助我使用 Flex 和 Bison 解析原始字符串文字。如果无法进行反向引用,是否有其他方法可以在 flex 和 bison 中解析原始字符串。
Flex patterns 既没有反向引用也没有 non-greedy 匹配项,您需要这两者才能正确识别带有“正则”表达式的原始字符串。 [注1]
然而,flex 确实有一个特性 -- "start conditions" -- 这使得实现这些特性变得非常简单,至少在这种情况下你永远不需要回溯来尝试不同的反向引用子模式.
开始条件是对给定的词汇上下文使用不同规则集的一种方式;在这种情况下,原始字符串中的上下文。
注意:原始代码有一个错误,如果原始字符串紧跟几个 non-whitespace 个字符和另一个双引号。这是一个不太可能发生的情况,这或许可以解释为什么 no-one 注意到了这个错误。我通过删除所有尝试的聪明来修复它。向复制错误代码的任何人道歉。
因此(简化的)C++ 原始字符串的框架解决方案是:
%x C_RAW_STRING
dchar_seq [^()\[:space:]]{0,16}
%%
size_t delim_len;
R["]{dchar_seq}[(] { delim_len = yyleng - 3;
yymore();
BEGIN(C_RAW_STRING);
}
R["]{dchar_seq} { yyerror("Invalid raw string opener"); }
/* Rules for other tokens omitted */
[[:space:]]+ ; /* Ignore whitespace */
. return *yytext; /* Fallback rule */
<C_RAW_STRING>{
[^"]+ yymore();
["] { if (yytext[yyleng - (delim_len + 2)] == ')' &&
memcmp(yytext + yyleng - (delim_len + 1),
yytext + 2, delim_len) == 0) {
BEGIN(INITIAL);
return STRING_LITERAL;
}
else yymore();
}
<<EOF>> { yyerror("Unterminated raw string");
BEGIN(INITIAL);
return 0;
}
}
一些解释
第 1 行:将开始条件
C_RAW_STRING
声明为独占。 (参见上面的 flex 手册,link)。第 2 行:
dchar_seq
匹配 C++ 标准称为“d-char-sequence”的内容(参见 [lex.string],§5.13.5):最多16 个字符,除了括号、反斜杠或空白字符之外的任何字符。 (请注意,"
是 d-char。)有关正则表达式运算符的详细信息,请参阅 Flex 手册中有关模式的章节。第 4 行:规则部分开头的缩进语句,在第一条规则之前,被简单地插入到
的单次调用期间使用的局部变量yylext
函数的顶部。因此,它们可用于声明在yylex
.第 5-8 行:当我们检测到原始字符串文字的开头时,我们:
- 记住分隔符字符串有多长。 (由于我们会在token的开头留下分隔符字符串,所以我们不需要复制到任何地方。知道它有多长就足够了。)
- 使用
yymore()
告诉词法分析器将下一个匹配附加到当前标记,而不是开始一个新标记。 - 使用
BEGIN
更改为C_RAW_STRING
开始条件。
第 9 行:如果我们发现
R"
并且第 5 行的模式不匹配,则原始字符串定界符不正确,要么是因为它太长,要么因为它包含无效字符或因为它没有以(
结尾。只匹配R"
就可以了,但我也选择了匹配最长的 d-char 序列以避免回溯词法分析器,尽管在这种特殊情况下它没有太大区别。由于 longest match rule,只有在前面的规则不触发时才会触发此规则。 从这样的错误中恢复真的很难,因为没有好的方法可以知道引用的字符串应该在哪里结束。在这里我没有尝试任何错误恢复,因为这不是这个答案的重点。第 15 行:启动
C_RAW_STRING
开始条件的规则块。这是一个 flex 扩展,在其他 lex 实现中不可用。为了兼容性,所有模式都必须单独标记为<C_RAW_STRING>
.第 16 行:除了双引号之外的任何字符序列都只是附加到令牌。同样,
yymore()
表示我们还没有完成令牌。第 17-24 行:原始字符串必须以引号结尾,但引号不一定以原始字符串结尾。所以我们检查我们遇到的每一个报价。给定定界符字符串的长度,我们首先确保在我们预期的位置存在
)
,然后使用memcmp
比较两个定界符序列。 (我们使用memcmp
而不是strcmp
因为我们比较的序列不是 NUL-terminated;它们是标记的子串。strncmp
是另一种可能性。)如果分隔符字符串匹配,我们就找到了令牌的结尾,我们可以 return 它。在这种情况下,我们需要将开始条件重置为
INITIAL
以便正常扫描下一个令牌。如果字符串不匹配,我们使用yymore()
告诉yylex
我们还没有完成标记,并继续寻找正确的分隔符。第 25-29 行:如果在原始字符串中检测到输入结束,则意味着原始字符串未正确终止。我们发出一条错误消息和 return 0 表示遇到了 EOF。此时重置开始条件并不是真正必要的,但这样做似乎更干净。
备注
例如,您可以使用以下 Gnu grep 命令(使用
-P
选项启用 PCRE 正则表达式功能),除了因为它是一个简单的正则表达式搜索而不是标记器,它可能会产生误报(例如,注释中看起来像原始字符串的内容):grep -Po '"([^()\[:space:]]*)\(.*?\)"'