Flex / Bison:不能使用semantic_type

时间:2018-06-09 15:56:37

标签: c++ unit-testing bison flex-lexer

我尝试创建一个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生成的令牌值的正确方法是什么?

1 个答案:

答案 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>的值。
  • 您可以使用Tcopy<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接口和我自己的区分联合实现。