我尝试创建一个c ++ flex / bison解析器。我使用this tutorial作为起点并没有改变任何野牛/弹性配置。我现在陷入了试图对词法分析器进行单元测试的问题。
我的单元测试中有一个函数直接调用yylex,并检查它的结果:
private: static void checkIntToken(MyScanner &scanner, Compiler *comp, unsigned long expected, unsigned char size, char isUnsigned, unsigned int line, const std::string &label) {
yy::MyParser::location_type loc;
yy::MyParser::semantic_type semantic; // <---- is seems like the destructor of this variable causes the crash
int type = scanner.yylex(&semantic, &loc, comp);
Assert::equals(yy::MyParser::token::INT, type, label + "__1");
MyIntToken* token = semantic.as<MyIntToken*>();
Assert::equals(expected, token->value, label + "__2");
Assert::equals(size, token->size, label + "__3");
Assert::equals(isUnsigned, token->isUnsigned, label + "__4");
Assert::equals(line, loc.begin.line, label + "__5");
//execution comes to this point, and then, program crashes
}
错误消息是:
program: ../src/__autoGenerated__/MyParser.tab.hh:190: yy::variant<32>::~variant() [S = 32]: Assertion `!yytypeid_' failed.
我试图遵循自动生成的野牛文件中的逻辑,并从中理解它。但我没有成功,最终放弃了。然后,我在网上搜索有关此错误消息的任何建议,但没有找到任何建议。
错误指示的位置具有以下代码:
~variant (){
YYASSERT (!yytypeid_);
}
编辑:只有删除
后问题才会消失%define parse.assert
来自野牛文件的选项。但我不确定这是不是一个好主意......
为获得单元测试目的,获取flex生成的令牌值的正确方法是什么?
答案 0 :(得分:1)
注意:据我所知,我试图解释野牛变种类型。我希望它是准确的,但除了一些玩具实验我还没有使用过它们。假设这种解释以任何方式暗示对界面的认可是错误的。
所谓的&#34;变种&#34; bison的C ++接口提供的类型不是通用的变体类型。这是一个深思熟虑的决定,基于这样一个事实,即解析器总是能够找出与解析器堆栈上的语义值相关联的语义类型。 (这个事实也允许在解析器中安全地使用C union
。)记录&#34;变体中的类型信息&#34;因此是多余的。所以他们没有。从这个意义上讲,它并不是一个真正的歧视联盟,尽管人们可能会期待一种名为&#34;变体&#34;的类型。
(bison变体类型是一个带有整数(非类型)模板参数的模板。该参数是变体中允许的最大类型的字节大小;它不以任何其他方式指定可能的semantic_type
别名用于确保在解析器代码中为每个bison变体对象使用相同的模板参数。)
因为它不是一个有区别的联合,它的析构函数不能破坏当前值;它无法知道如何做到这一点。
这个设计决定实际上是在Bison&#34;变体&#34;的文件(可悲的是不足)中提到的。类型。 (在阅读本文时,请记住它最初是在std::variant
存在之前编写的。现在,std::variant
被拒绝为#{多余&#34;},尽管也可能std::variant
的存在可能有重新审视这一设计决定的愉快结果。在关于C ++变体类型的章节中,我们读到:
警告:我们不使用Boost.Variant,原因有两个。首先,在用户的机器上要求Boost似乎是不可接受的(即,将在其上编译生成的解析器的机器,而不是运行bison的机器)。其次,对于每个可能的语义值,Boost.Variant不仅存储值,还存储指定其类型的标记。但是解析器已经“知道”了语义值的类型,因此会复制信息。
因此我们开发了轻量级变体,其类型标记是外部的(因此它们实际上就像C ++的联合)。
他们确实如此。所以任何使用野牛&#34;变种&#34;必须有一个明确的类型:
build
一个带有要构建类型的参数的变体。 (这是您不需要模板参数的唯一情况,因为类型是从参数推断出来的。只有当参数不是精确类型时才需要使用显式模板参数;例如,一个较小等级的整数。)T
获取对已知类型as<T>
的值的引用。 (如果值具有不同的类型,则这是未定义的行为。)T
销毁已知类型destroy<T>
的值。T
或copy<T>
复制或移动已知类型move<T>
的其他变体中的值。 (move<T>
涉及构建然后销毁T()
,因此如果T
有一个昂贵的默认构造函数,您可能不想这样做。总的来说,我不相信move
方法的语义。它的名称在语义上与std::move
冲突,但它首先出现了。)T
的变体的值与swap<T>
交换。现在,生成的解析器理解所有这些限制,并且它总是知道&#34;变体的真实类型&#34;它有它的支配。但是你可能会以违反约束的方式尝试对其中一个对象做某事。由于该对象实际上没有任何方法可以检查约束,因此您最终会得到未定义的行为,这可能会产生一些灾难性的后果。
因此,他们还实施了一个允许&#34;变体&#34;检查约束。不出所料,这包括添加一个鉴别器。但由于鉴别器仅用于验证而不是用于修改行为,因此它不是在少数已知备选方案之间选择的小整数,而是指向std::typeid
(或NULL
的指针。变体还没有包含值。)(公平地说,在大多数情况下,对齐约束意味着为此目的使用指针并不比使用小枚举更昂贵。所有相同的......)
这就是你遇到的问题。您使用%define parse.assert
启用了断言;该选项专门用于防止您执行您要执行的操作,这使得变体对象的析构函数在变量的值被明确销毁之前运行。
所以&#34;正确&#34;避免问题的方法是在范围的末尾插入显式调用:
// execution comes to this point, and then, without the following
// call, the program will fail on an assertion
semantic.destroy<MyIntType*>();
}
启用解析断言后,变体对象将能够验证指定为semantic.as<T>
和semantic.destroy<T>
的模板参数的类型与存储在对象中的值的类型相同。 (没有parse.assert
,这也是你的责任。)
警告:意见如下。
如果有人读这篇文章,我对使用真实std::variant
类型的偏好来自这样一个事实,即AST节点的语义值实际上很常见,需要一个有区别的联合。通常的解决方案(在C ++中)是构造一个类型层次结构,在某些方面,它完全是人为的,std::variant
很可能更好地表达语义。
在实践中,我使用C接口和我自己的区分联合实现。