野牛 3.5.1 坚持明确编写默认操作的代码,假定 C 样式联合
bison 3.5.1 insists on explicitly writing code for default action assuming a C style union
我之前使用的 bison 版本 (2.4.1) 没有明确地为使用它的每个语义规则编写默认操作 -- 但可能只是简单地将 $$ 分配给 $1 一次而不考虑所有默认的任何类型动作规则。
bison 3.5.1 坚持自己将其转储出来——假设堆栈由 C 风格的联合组成。
有什么办法可以说服 bison 3.5.1 沿用旧的风格吗?
我的解决方法是在所有使用它的地方显式写入默认语义操作。
我过去已经经历过几次野牛的变化
-- 这可以从许多注释掉的解析器指令中窥见一斑。
我唯一剩下的解析器指令是
%skeleton "lalr1.cc"
%define namespace "smc"
%define parser_class_name "smc_parser"
%lex-param {location *const, symphony::smc::parserDriver& driver }
%parse-param { symphony::smc::parserDriver& driver }
%locations
我的方法说明:
我已经在不同的项目中使用了很多年。
bison 想要将类型转换为成员访问。
我将其扩展为 set/get 方法。
YYSTYPE 来自 std::variant。
所以假设某个规则产生 TYPE,那么 bison 会自动将 $$ 转换为 $$.TYPE。
在任何规则中编写的代码将如下所示:
$$($1());
这导致
$$.TYPE($1.TYPE());
TYPE(argument) 是一个集合方法。
TYPE(void) 是一种获取方法。
这些方法只是访问底层 std::variant.
Bison 现在将默认实现创建为
$$.TYPE = $1.TYPE;
不编译。
它过去只是简单地转储一份 $$=$1;对于需要默认实现的所有操作(和所有类型)——意味着没有任何特定成员。
这一变化,我认为是在 v3.2 中引入的,仅适用于 C++ 框架(恰好是您要求的框架,lalr1.cc
。)据我所知,有无法禁用它。
虽然 yacc 和 bison 都将 $$ =
描述为“默认操作”,但事实并非如此。历史实现对所有归约执行 $$ =
,无论是否有明确的操作。 (即使 </code> 不存在,右侧为空的产生式也是如此。)对于 C 解析器来说这可能不是什么大问题,但在 C++ 中它可能会产生不幸的后果。例如,语义类型可能没有赋值运算符。或者作业可能会做一些无法轻易撤消的事情。更改的目的是使默认操作实际上成为默认操作,以便在有显式操作时不会发生。</p>
<p>当时有一些关于这个的讨论。最初,该更改是针对 C 和 C++ 提出的,但有人争论——或者更准确地说,我争论——许多现有的 bison 解析器假定了历史实现。一个典型的案例是通过执行 <code>append(, )
甚至 append($$, )
之类的操作将元素附加到列表的操作,并假设这没问题,因为解析器已经设置了 $$ =
。最后,野牛维护者决定限制对 C++ 的更改,以避免破坏遗留的 C 程序。显然,一些遗留的 C++ 程序被破坏了。显然这包括你的。
使用 getters 和 setters 让 std::variant
工作实际上非常聪明,但我不知道有什么简单的方法可以让它在新的 bison 版本上工作。您可以编译自己的 bison 可执行文件,将 string literal at line 301 of reader.c
更改为您想要的默认操作。 (更好的解决方法是将默认操作设置为 bison %define
,但这样会更费力。)但是如果您不想重新编译和维护自己的 bison,您最好还是找到不同的解决方案。
在这种情况下,值得注意的是,在合理范围内,野牛并不真正关心类型标签的外观。你甚至可以包括括号,只要它们是平衡的;无论您使用什么,都将在成员访问运算符 (.
) 之后添加到堆栈引用中。因此,除了定义 getter 和 setter 方法之外,您还可以使用每个类型的成员函数定义包装器类型,其中 return 一个引用(或引用代理),然后使用类型声明,例如 %type <TYPE()> non_terminal
。这应该使 $$ = ;
具有正确的语义成为可能,以便默认操作起作用。这也可能使您的其余代码更具可读性。
我之前使用的 bison 版本 (2.4.1) 没有明确地为使用它的每个语义规则编写默认操作 -- 但可能只是简单地将 $$ 分配给 $1 一次而不考虑所有默认的任何类型动作规则。
bison 3.5.1 坚持自己将其转储出来——假设堆栈由 C 风格的联合组成。
有什么办法可以说服 bison 3.5.1 沿用旧的风格吗?
我的解决方法是在所有使用它的地方显式写入默认语义操作。
我过去已经经历过几次野牛的变化 -- 这可以从许多注释掉的解析器指令中窥见一斑。
我唯一剩下的解析器指令是
%skeleton "lalr1.cc"
%define namespace "smc"
%define parser_class_name "smc_parser"
%lex-param {location *const, symphony::smc::parserDriver& driver }
%parse-param { symphony::smc::parserDriver& driver }
%locations
我的方法说明: 我已经在不同的项目中使用了很多年。 bison 想要将类型转换为成员访问。 我将其扩展为 set/get 方法。 YYSTYPE 来自 std::variant。 所以假设某个规则产生 TYPE,那么 bison 会自动将 $$ 转换为 $$.TYPE。 在任何规则中编写的代码将如下所示:
$$($1());
这导致
$$.TYPE($1.TYPE());
TYPE(argument) 是一个集合方法。 TYPE(void) 是一种获取方法。 这些方法只是访问底层 std::variant.
Bison 现在将默认实现创建为
$$.TYPE = $1.TYPE;
不编译。 它过去只是简单地转储一份 $$=$1;对于需要默认实现的所有操作(和所有类型)——意味着没有任何特定成员。
这一变化,我认为是在 v3.2 中引入的,仅适用于 C++ 框架(恰好是您要求的框架,lalr1.cc
。)据我所知,有无法禁用它。
虽然 yacc 和 bison 都将 $$ =
描述为“默认操作”,但事实并非如此。历史实现对所有归约执行 $$ =
,无论是否有明确的操作。 (即使 </code> 不存在,右侧为空的产生式也是如此。)对于 C 解析器来说这可能不是什么大问题,但在 C++ 中它可能会产生不幸的后果。例如,语义类型可能没有赋值运算符。或者作业可能会做一些无法轻易撤消的事情。更改的目的是使默认操作实际上成为默认操作,以便在有显式操作时不会发生。</p>
<p>当时有一些关于这个的讨论。最初,该更改是针对 C 和 C++ 提出的,但有人争论——或者更准确地说,我争论——许多现有的 bison 解析器假定了历史实现。一个典型的案例是通过执行 <code>append(, )
甚至 append($$, )
之类的操作将元素附加到列表的操作,并假设这没问题,因为解析器已经设置了 $$ =
。最后,野牛维护者决定限制对 C++ 的更改,以避免破坏遗留的 C 程序。显然,一些遗留的 C++ 程序被破坏了。显然这包括你的。
使用 getters 和 setters 让 std::variant
工作实际上非常聪明,但我不知道有什么简单的方法可以让它在新的 bison 版本上工作。您可以编译自己的 bison 可执行文件,将 string literal at line 301 of reader.c
更改为您想要的默认操作。 (更好的解决方法是将默认操作设置为 bison %define
,但这样会更费力。)但是如果您不想重新编译和维护自己的 bison,您最好还是找到不同的解决方案。
在这种情况下,值得注意的是,在合理范围内,野牛并不真正关心类型标签的外观。你甚至可以包括括号,只要它们是平衡的;无论您使用什么,都将在成员访问运算符 (.
) 之后添加到堆栈引用中。因此,除了定义 getter 和 setter 方法之外,您还可以使用每个类型的成员函数定义包装器类型,其中 return 一个引用(或引用代理),然后使用类型声明,例如 %type <TYPE()> non_terminal
。这应该使 $$ = ;
具有正确的语义成为可能,以便默认操作起作用。这也可能使您的其余代码更具可读性。