将C: Convert A ? B : C转换为if (A) B else C

6

我正在寻找一种工具,可以将C代码表达式转换为以下形式:

a = (A) ? B : C;

使用if/else语句将其转换为“default”语法:

if (A)
  a = B
else
  a = C

有没有一款工具可以完成这样的转换?

我使用的是GCC 4.4.2,并且使用-E创建了一个预处理文件,但不希望在其中出现这样的结构。

编辑: 下面的代码也应该被转换:

a = ((A) ? B : C)->b;

23
为什么?你应该考虑到有些情况下没有语义等效的说法。三元运算符并没有什么问题。 - GManNickG
3
回应并放大GMan的观点:三元运算符是一个表达式,具有值,而“等效”的if语句则没有。盲目地将它们互换是不安全的 - dmckee --- ex-moderator kitten
2
考虑使用d = tan(theta = A ? B : C)或甚至只是d = tan(A ? B : C)或者其他不是简单赋值语句的形式。当然可以使用if语句来编写等效代码,但需要一些非平凡的理解。 - dmckee --- ex-moderator kitten
5
在C++中,int& i = flag ? a : b;没有相应的语法。在使用可变长度数组(VLA)的C语言中,int somearray[flag ? 10 : 20]也没有相应的语法。也许两种语言中都存在这样的语法,但我仍然看不出废弃这个语言特性的任何理由。:] - GManNickG
2
tur1ng: 修复你的分析程序以理解所有 C 代码可能会更容易。 - Greg Hewgill
显示剩余10条评论
5个回答

12

Coccinelle可以很容易地完成这个任务。

Coccinelle是一个程序匹配和转换引擎,提供了语言SmPL(Semantic Patch Language),用于在C代码中指定所需的匹配和转换。 Coccinelle最初针对Linux中执行附带演变而设计。这些演变包括需要在客户端代码中进行更改以响应库API的演变的更改,可能包括修改,例如重命名函数,添加一个某种程度上依赖于上下文的函数参数以及重新组织数据结构。除了附带演变外,我们和其他人成功地使用Coccinelle在系统代码中找到和修复错误。

编辑:一个语义补丁的示例:

@@ expression E; constant C; @@
(
  !E & !C
|
- !E & C
+ !(E & C)
)

根据官方文档:

模式 !x&y。这种形式的表达式几乎总是没有意义的,因为它结合了布尔运算符和位运算符。特别地,如果 y 的最右边一位是0,则结果将始终为0。该语义补丁重点关注 y 是常量的情况。

你可以在这里找到一组很好的例子。

邮件列表非常活跃且有用。


4
以下是针对Coccinelle的语义补丁,可以进行转换。
@@
expression E1, E2, E3, E4;
@@

- E1 = E2 ? E3 : E4;
+ if (E2)
+   E1 = E3;
+ else
+   E1 = E4;

@@
type T;
identifier E5;
T *E3;
T *E4;
expression E1, E2;
@@

- E1 = ((E2) ? (E3) : (E4))->E5;
+ if (E2)
+   E1 = E3->E5;
+ else
+   E1 = E4->E5;


@@
type T;
identifier E5;
T E3;
T E4;
expression E1, E2;
@@

- E1 = ((E2) ? (E3) : (E4)).E5;
+ if (E2)
+   E1 = (E3).E5;
+ else
+   E1 = (E4).E5;

1

DMS软件重构工具包可以通过应用程序转换来实现此功能。

一个特定的DMS转换可以匹配您的具体示例:

domain C.

rule ifthenelseize_conditional_expression(a:lvalue,A:condition,B:term,C:term):
stmt -> stmt
=  " \a = \A ? \B : \C; "
-> " if (\A) \a = \B;  else \a=\C ; ".

你需要另一条规则来处理你的其他情况,但同样容易表达。

转换操作基于源代码结构而不是文本,因此布局和注释不会影响识别或应用。规则中的引号不是传统的字符串引号,而是元语言引号,用于将规则语法语言与用于指定要更改的具体语法的模式语言分开。

如果您打算保留预处理指令,则存在一些问题。由于您显然愿意使用预处理器扩展的代码,因此可以要求DMS在转换步骤中执行预处理;它内置了完整的GCC4和GCC4兼容预处理器。

正如其他人所观察到的那样,这是一个相当简单的情况,因为您指定它在完整语句的级别上工作。如果您想消除任何类似于此语句的赋值,其中这些赋值嵌入在各种上下文中(初始化程序等),则可能需要更大的一组变换来处理各种特殊情况,并且您可能需要制造其他代码结构(例如,适当类型的临时变量)。像DMS这样的工具的好处在于它可以明确计算任意表达式的符号类型(因此需要的任何临时变量的类型声明),并且您可以编写这样一个更大的集合,然后直接应用所有这些变换。

所有这些说法都不确定做三元条件表达式消除操作的实际价值。一旦编译器掌握了结果,你可能会得到与未进行任何转换时相似的目标代码。毕竟,编译器也可以应用等效保持的转换。
然而,总体上进行定期变动显然是有价值的。
(DMS可以对许多语言应用源代码转换,包括C、C++、Java、C#和PHP)。

0
如果语句非常规律,为什么不通过一个小的Perl脚本运行您的文件呢?对于您的示例行执行查找和转换的核心逻辑非常简单。以下是一种基本方法:
use strict;
while(<>) {
    my $line = $_;
    chomp($line);
    if ( $line =~ m/(\S+)\s*=\s*\((\s*\S+\s*)\)\s*\?\s*(\S+)\s*:\s*(\S+)\s*;/ ) {
        print "if(" . $2 . ")\n\t" . $1 . " = " . $3 . "\nelse\n\t" . $1 . " = " . $4 . "\n";
    } else {
        print $line . "\n";
    }
}
exit(0);

你可以这样运行它:

perl transformer.pl < foo.c > foo.c.new

当然,如果文本模式不像你发布的那样规则,它会变得越来越困难。但是免费、快速且易于尝试。


我确实警告过你这只是针对你提供的示例。 :) 与其尝试编写一些超级正则表达式,你可以很容易地将其作为第二个elsif情况:m/(\S+)\s*=\s(\s((\s\S+\s))\s?\s(\S+)\s*:\s*(\S+)\s)\s->\s*(\S+)\s*;/ -- 解引用调用现在是匹配组$4。 - Ian C.
1
@tur1ng:我认为使用“->”的示例可能无法在没有临时变量的情况下完成。 - Brian Postow

0

我不知道是否有三元运算符内嵌于语言规范中作为对 if 逻辑的快捷方式... 我唯一能想到的方法是手动查找这些行并将其重写成使用 if 的形式... 一般的共识是,三元运算符的工作方式如下:

expr_is_true ? exec_if_expr_is_TRUE : exec_if_expr_is_FALSE;

如果表达式被评估为真,则执行?:之间的部分,否则执行:;之间的最后一部分。如果表达式被评估为假,情况将反转。

expr_is_false ? exec_if_expr_is_FALSE : exec_if_expr_is_TRUE;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接