在 C++ Flex 中更改 yylex

Change yylex in C++ Flex

我想将 yylex 更改为 alpha_yylex,它也接受一个向量作为参数。

.
.
#define YY_DECL int yyFlexLexer::alpha_yylex(std::vector<alpha_token_t> tokens)
%}
.
.
. in main()
std::vector<alpha_token_t> tokens;
while(lexer->alpha_yylex(tokens) != 0) ;

我想我知道为什么会失败,因为显然在 FlexLexer.h 中没有 alpha_yylex ,但我不知道如何实现我想要的...

如何制作自己的 alpha_yylex() 或修改现有的?

您确实无法编辑 yyFlexLexer 的定义,因为 FlexLexer.h 实际上是一个 system-wide header 文件。但您当然可以将其子class,它将提供您所需要的大部分内容。

Subclassing yyFlexLexer

Flex 允许您使用 %option yyclass(或 --yyclass command-line 选项)来指定子 class 的名称,它将代替 yyFlexLexer定义yylex。 Subclassing yyFlexLexer 允许您包含自己的 header,它定义了您的 subclass' 成员,甚至可能还有其他函数,以及它的构造函数;简而言之,如果您的意图只是用连续的标记填充 std::vector<alpha_token_t>,您可以通过将 AlphaLexer 定义为 yyFlexLexer 的子 class 来轻松地做到这一点,其中一个名为 tokens 的实例成员(或者,也许,具有访问函数)。

您还可以向新的 class 添加额外的成员函数,这可能会提供您需要这些额外参数的功能。

虽然在 C 接口中使用 YY_DECL 宏可以很容易地完成,但不完全是 straight-forward 的事情是更改由生成的扫描函数的名称和原型柔性。可以做到(见下文),但不清楚它是否真正受支持。无论如何,对于 C++,它可能不太重要。

除了由 Flex 的 C++ classes [注 1] 的奇怪组织造成的小问题外,子 class 词法分析器 class 很简单。您需要从 yyFlexLexer [注 2] 中导出 class,它在 FlexLexer.h 中声明,并且您需要告诉 Flex 您的 class 的名称是什么,要么通过在 Flex 文件中使用 %option yyclass,或在命令行中使用 --yyclass.

指定名称

yyFlexLexer 包括用于操作输入缓冲区的各种方法,以及标准框架使用的词法扫描器的所有可变状态。 (其中大部分实际上是从基础 class FlexLexer 派生的。)它还包括一个带有原型

的虚拟 yylex 方法
virtual int yylex();

当您使用 subclass yyFlexLexer 时,yyFlexLexer::yylex() 被定义为通过调用 yyFlexLexer::LexerError(const char*) 发出错误信号并且生成的扫描器被定义为 class 定义为 yyclass。 (如果不subclass,生成的scanner是yyFlexLexer::yylex()。)

一个问题是您需要声明子class 的方式。通常,您会在 header 文件中这样做:

文件:myscanner.h(不要使用这个版本)

#pragma once

// DON'T DO THIS; IT WON'T WORK (flex 2.6)
#include <yyFlexLexer.h>

class MyScanner : public yyFlexLexer {
  // whatever
};

然后您将 #include "myscanner.h" 在任何需要使用扫描仪的文件中,包括生成的扫描仪本身

不幸的是,这不会起作用,因为它会导致 FlexLexer.h 在生成的扫描器中被包含两次; FlexLexer.h 没有通常意义上的包含保护,因为它被设计为被多次包含以支持 prefix 选项。所以需要定义两个header个文件:

文件:myscanner-internal.h

#pragma once
// This file depends on FlexLexer.h having already been included
// in the translation unit. Don't use it other than in the scanner
// definition.
class MyScanner : public yyFlexLexer {
  // whatever
};

文件:myscanner.h

#pragma once
#include <FlexLexer.h>
#include "myscanner.h"

然后在每个需要了解扫描仪的文件中使用 #include "myscanner.h" 除了 扫描仪定义本身。在您的 myscanner.ll 文件中,您将 #include "myscanner-internal.h",这是有效的,因为 Flex 在插入扫描仪定义中的序言 C++ 代码之前已经包含 FlexLexer.h

更改 yylex 原型

你真的不能改变yylex的原型(或名称),因为它是在FlexLexer.h中声明的,而且如上所述,定义为发出错误信号。但是,您可以重新定义 YY_DECL 以创建 new 扫描仪界面。为此,您必须首先 #undef 现有的 YY_DECL 定义,至少在您的扫描仪定义中,因为带有 %option yyclass="MyScanner" contains #define YY_DECL int MyScanner::yylex 的扫描仪(). That would make your myscanner-internal.h` 文件看起来像这样:

#pragma once
// This file depends on FlexLexer.h having already been included
// in the translation unit. Don't use it other than in the scanner
// definition.

#undef YY_DECL
#define YY_DECL int MyScanner::alpha_yylex(std::vector<alpha_token_t>& tokens)

#include <vector>
#include "alpha_token.h"

class MyScanner : public yyFlexLexer {
  public:
    int alpha_yylex(std::vector<alpha_token_t>& tokens);

    // whatever else you need
};

事实上 MyScanner object 仍然有一个(不是很实用)yylex 方法可能不是问题。 FlexLexer 中有一些未记录的接口调用 yylex(),但如果您不使用它们,这些都没有关系。 (无论如何,它们并不是那么有用。)但是您至少应该知道该接口存在。

无论如何,我不认为重命名 yylex 有什么意义(但也许您的审美不同)。它已经通过成为特定 class(MyScanner,上文)的成员而有效地命名空间,因此 yylex 不会真正造成任何混淆。

std::vector<alpha_token_t>& 参数的特殊情况下,在我看来更简洁的解决方案是将引用作为成员变量放在 MyScanner class 中并设置它与构造函数或访问器方法。除非您实际上在词法分析的不同点使用不同的向量——在您的问题的示例代码中并不明显——没有必要让每个调用站点都需要将向量的地址传递到 yylex称呼。由于词法分析器操作是在 yylex 内部编译的,它是 MyScanner 的成员函数,实例变量——甚至是私有实例变量——在词法分析器操作中是可用的秒。当然,这不是额外 yylex 参数的唯一用例,但这是一个很常见的用例。


备注

  1. 根据生成代码中的注释,“C++ 接口一团糟”。

  2. 使用%option prefix,您可以根据需要将yy更改为其他内容。这个功能据说是为了让你在同一个项目中包含多个词法扫描器。但是,如果您计划使用 subclassing,所有这些词法扫描器的基础 classes 将是相同的(除了它们的名称)。因此,具有不同的基 classes 几乎没有意义。使用 %option prefix 重命名扫描仪 class 不如 subclassing 灵活且效率不高,并且它会产生额外的 header 并发症。 (有关详细信息,请参阅 this older answer。)所以我建议坚持使用 subclassing.