我尝试使用Flex& Bison解析原始字符串文字。但无法解析它。
输入:
R"foo(Hello World)foo"
软硬度:
...
raw_string R"([^\(]*)\(([^\)]*)\)\1"
%%
{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中的原始字符串。
答案 0 :(得分:2)
Flex patterns既没有反向引用也没有非贪婪的匹配,你需要这两个匹配才能正确识别带有“常规”表达式的原始字符串。 [注1]
然而,flex确实有一个功能 - "start conditions" - 这使得实现这些功能变得非常简单,至少在这种情况下,您永远不需要回溯来尝试不同的反向引用子模式。< / p>
开始条件是对给定词汇上下文使用不同规则集的一种方式;在这种情况下,原始字符串中的上下文。
因此(简化的)C ++原始字符串的骨架解决方案是:
%x C_RAW_STRING
d_char_sequence [^()\\[:space:]]{0,16}
%%
size_t delim_len;
R["]{d_char_sequence}[(] { delim_len = yyleng - 3;
yymore();
BEGIN(C_RAW_STRING);
}
/* Rules for other tokens omitted */
[[:space:]]+ ; /* Ignore whitespace */
. return *yytext; /* Fallback rule */
<C_RAW_STRING>{
[^)]+ yymore();
[)]+ yymore();
[)]+{d_char_sequence}["] { if (yytext[yyleng - (delim_len + 2)] == ')' &&
memcmp(yytext + yyleng - (delim_len + 1),
yytext + 2, delim_len) == 0) {
BEGIN(INITIAL);
return raw_string;
}
yymore();
}
<<EOF>> { yyerror("Unterminated raw string");
BEGIN(INITIAL);
return 0;
}
}
一些解释
第1行:将开始条件C_RAW_STRING
声明为独占。 (参见上面的弹性手册,链接)。
第2行:d_char_sequence
匹配C ++标准所称的“d-char-sequence”(参见[lex.string],§5.13.5):最多16个字符,除了括号,反斜杠或空格字符。有关详细信息,请参阅Flex手册章节。
第4行:在第一条规则之前的规则部分开头的缩进语句只是插入yylext
函数的顶部。因此,它们可用于声明在yylex
的单个调用期间使用的局部变量。
第5-8行:当我们检测到原始字符串文字的开头时,我们:
yymore()
告诉词法分析器将下一个匹配附加到当前令牌,而不是启动新令牌。BEGIN
更改为C_RAW_STRING
开始条件。C_RAW_STRING
开始条件的规则块。这是一个flex扩展,在其他lex实现中不可用。为了兼容性,所有模式都必须使用<C_RAW_STRING>
单独标记。yymore()
表示我们尚未完成令牌。第15-22行:这匹配的内容看起来可能是一个接近的分隔符。因为我们是标记而不是正则表达式匹配,所以这将找到下一个这样的段而不是最后一个段,所以它有效地进行了非贪婪的重复。一旦我们匹配它,我们需要查看它是否实际匹配开放分隔符序列,该序列位于我们正在构建的令牌的开头。给定分隔符字符串的长度,我们首先确保起始)
是我们期望的起始位置,然后使用memcmp
来比较两个分隔符序列。 (我们使用memcmp
而不是strcmp
,因为我们比较的序列不是NUL终止的;它们是令牌的子串。strncmp
可能是另一种可能。)
如果分隔符字符串匹配,我们找到了令牌的结尾,我们可以返回它。在这种情况下,我们需要将开始条件重置为INITIAL
,以便正常扫描下一个标记。如果字符串不匹配,我们使用yymore
告诉yylex
我们还没有完成令牌,并继续寻找正确的分隔符。请注意,我们可以跳过整个匹配的结束分隔符模式,因为分隔符字符不能包含右括号,因此下一个试验结束序列不能在此内部开始。
第23-27行:如果在原始字符串中检测到输入结束,则表示原始字符串未正确终止。我们发出错误消息并返回0表示遇到了EOF。此时重置开始条件并不是必需的,但这样做似乎更清晰。
例如,您可以使用以下Gnu grep命令(使用-P
选项启用PCRE正则表达式功能),但由于它是一个简单的正则表达式搜索而不是标记符,因此它可以生成误报(例如,在注释中看起来像原始字符串的东西):
grep -Po '"([^()\\[:space:]]*)\(.*?\)\1"'