Flex 删除预定义的宏

Flex dropping predefined macro

我有以下 flex 源:

%{
#if !defined(__linux__) && !defined(__unix__)
/* Maybe on windows */
#endif
int num_chars = 0;                                                                                                                                                                                                   
%}

%%
.       ++num_chars;                                                                                                                                                                                                 
%%
int main()
{
  yylex();                                                                                                                                                                                                             
  printf("%d chars\n", num_chars);                                                                                                                                                                                     
  return 0;                                                                                                                                                                                                            
}

int yywrap()
{
  return 1;                                                                                                                                                                                                            
}

我通过命令flex flextest.l生成一个C文件,然后用gcc -o fltest lex.yy.c

编译结果

令我惊讶的是,我得到以下输出:

flextest.l:2:37: error: operator "defined" requires an identifier
  #if !defined(__linux__) && !defined(__unix__)

进一步检查,问题似乎是flex实际上已经将__unix__替换为空字符串,如下所示:

$ grep __linux_ lex.yy.c
#if !defined(__linux__) && !defined()

为什么会发生这种情况,是否可以避免?

实际上是m4(当前版本flex使用的宏处理器)将__unix__扩展为空字符串。 m4 的 Gnu 实现将某些符号定义为空宏,以便它们可以用 ifdef.

进行测试

当然,这是(更好的说法是)flex 中的一个错误。 Flex 不应允许 m4 在从扫描仪定义文件复制的用户内容中扩展宏,并且当前版本的 flex 正确安排了要引用的扫描仪描述文件中包含的文本,以便它通过 m4 未修改,即使它恰好包含一个可以被 m4 解释为宏扩展的字符串。

这个bug肯定存在于flex的v2.5.39和v2.6.1中。我没有测试所有以前的版本,但我想它是在将 flex 修改为使用 m4 时引入的,根据 NEWS 文件,它是 v2.5.30。

这个特殊的引用问题已在 v2.6.2 中修复,但当前版本的 flex (2.6.4) 包含各种其他错误修复,因此我建议您升级到最新版本。


如果您真的需要一个既能与有问题的版本又能与 flex 的最新版本一起使用的版本,您可以使用以下两种 hack 之一:

  1. 换一种写法__unix__。一种可能是下面

    #define C(x,y) x##y
    #define UNIX_ C(__un,ix__)
    #if !defined(__linux__) && !UNIX_
    

    该 hack 不适用于 defined,因为 defined(UNIX_) 测试是否定义了 UNIX_,而不是它扩展到的内容是否定义。但通常像 __unix__ 这样的内置符号实际上被定义为 1,如果它们被定义的话,并且 #if 指令将任何不是 #define 的标识符视为 0,这意味着你通常可以离开使用 x 而不是 defined(x)。 (但是,如果 #define x 0 有效,它会产生不同的结果,因此它不是一个完美的替代品。)

  2. Flex 和很多m4 应用程序一样,将m4 的引号重新定义为[[]]。有问题的 flex 和更正后的版本都用一个相当复杂的序列替换了这些引号,该序列有效地引用了引号。但是,有缺陷的版本不会以其他方式引用用户定义的文本,因此将在用户文本中执行宏替换。 (如前所述,这就是 __unix__ 变成空字符串的原因。

    在不引用用户定义文本的 flex 版本中,可以调用重新定义引号的 m4 宏。然后可以使用这些新引号来引用 #if 行,防止 __unix__ 的宏替换。但是,必须恢复引用定义,否则它将完全破坏文件其余部分的宏处理。这有点棘手,因为不可能写 [[。 (Flex 将用不同的字符串替换它。)

    以下似乎可以解决问题。请注意,宏调用位于 C 注释中。如果扩展了 changequote 宏,它们将扩展为空字符串。但是在 v2.6.2 之后的 flex 版本中,用户提供的文本被引用,所以 changequote 宏不会被扩展。将它们放在注释中会对 C 编译器隐藏它们。

    %{
    /*m4_changequote(<<,>>)<<*/
    #if !defined(__linux__) && !defined(__unix__)
    /*>>m4_changequote(<<[>><<[>>,<<]>><<]>>)*/
    
    /* Maybe on windows */
    #endif
    

    (更改引号的 m4 宏是 changequote 但 flex 使用 -P 标志调用 m4changequote 等内置函数更改为 m4_changequote . 在对 changequote 的第二次调用中,组成 [[ 符号的两个 [ 分别用临时 << 引号引起来,这将它们隐藏在修改 use 的 flex 代码中[[.)

    我不知道这个 hack 的可靠性如何,但它适用于我在我的机器上使用过的 flex 版本,包括 2.5.4(M4 之前)2.5.39(错误),2.6。 1(错误)、2.6.2(有些调试)和 2.6.4(更多调试)。