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。此时重置开始条件并不是真正必要的,但这样做似乎更干净。

备注

  1. 例如,您可以使用以下 Gnu grep 命令(使用 -P 选项启用 PCRE 正则表达式功能),除了因为它是一个简单的正则表达式搜索而不是标记器,它可能会产生误报(例如,注释中看起来像原始字符串的内容):

     grep -Po '"([^()\[:space:]]*)\(.*?\)"'