[英]unparse the intermediate representation of c code back to c
我有一个用C编程语言编写的文件,并使用CIL进行了预处理。 现在在此文件中调用了一个名为foo()的函数。 我想修改此文件中的C代码,以使对foo()的所有调用都在#ifdef保护之下。 我只希望保护调用而不是函数主体,以便对调用有更好的控制。 调用可以在if条件或while循环内。 宏名称的规则:名称以MACRO_开头,并以原始代码中的函数调用foo()的行号结尾。
这将在工具内部自动进行,我正在寻找可以对此进行解析的C代码的编译器。
例:
输入源文件
void foo(int x){
// do something
}
int main(){
int a;
printf("doing something");
foo(a);
printf("doing something again");
foo(a);
return 0;
}
所需的输出
void foo(int x){
// do something
}
int main(){
int a;
printf("doing something");
#ifdef MACRO_1
foo(a);
#endif
printf("doing something again");
#ifdef MACRO_2
foo(a);
#endif
return 0;
}
您可以自定义一些免费的软件编译器。 如果使用最新的GCC ,则可以使用MELT (一种Lispy域专用语言来扩展gcc
和g++
等)自定义它。
您可能不想生成惯用的C代码。 定制您的编译器(例如,GCC-或Clang / LLVM ...)以具有所需的行为会容易得多。
转换某些内部编译器表示形式(例如,用于GCC的Gimple )比输出C代码要简单一些。 它可能仍然意味着需要花费数周的时间(因为C和C ++是相当复杂的语言,并且编译器具有非常复杂的内部表示形式)。
注意,您的问题没有考虑在某个宏(或某些C ++模板扩展,甚至某些内联函数)中调用foo
时发生的情况。 这说明了为什么值得在编译器的中间表示形式上进行工作。
顺便说一句,您可能会对coccinelle感兴趣, coccinelle是自由软件转换器的来源。
您原则上也可以使用Clang (将C或C ++代码编译为LLVM ),然后使用llvm-cbe (将LLVM转换为C的后端)
对于SIMPLE源代码,显然可以使用您喜欢的脚本语言(perl,php,awk,python等)的简单脚本和一些正则表达式来执行此操作。 但是,如果您开始决定在if语句中支持函数调用,成员函数调用等[并且希望最终得到实际编译为正确程序的输出代码],这确实变得越来越困难。
在这种情况下,您需要可以读取(并“理解”)C或C ++并产生某种中间形式的内容,然后可以对其进行处理并重新发布带有修改的源代码。 无论从哪里开始,编写这样的代码都不是一件容易的事。 一种解决方案是将Clang用作库。 它具有从抽象语法树(AST)形式重写C或C ++代码的功能。 此链接显示了此类重写器的示例: http : //eli.thegreenplace.net/2012/06/08/basic-source-to-source-transformation-with-clang
如果您有类似以下的代码,我不确定您要做什么:
if (x)
foo();
bar();
显然,只需插入#if即可调用foo();
将仅在x
为true时才调用bar()
,这可能不是您想要的...
如果代码的结构使得可以简单地注释掉foo
调用来保护行,并且不需要处理更复杂的表达式,例如bar(), foo(a)
,则可以使用如下awk:
awk '/^\s*foo\(/ { print "#ifdef MACRO_" NR; print; print "#endif"; next } 1' filename.c
这将
/^\s*foo\(/ { # handle lines that begin with foo( preceded
# optionally by whitespaces specially by:
print "#ifdef MACRO_" NR # printing #ifdef MACRO_linenumber before
print
print "#endif" # and #endif after the line.
next
}
1 # all other lines are printed unchanged.
请注意 ,这是一个肮脏的程序,它不会尝试正确地解析C代码。 您可以通过多种方法来解决此问题,其中包括:
if(something)
foo(a);
和
foo(
a
);
那会出来
if(something)
#ifdef MACRO_foo
foo(a);
#endif
和
#ifdef MACRO_foo
foo(
#endif
a
);
分别。 它可能适用于您的特定情况,但它不是通用的C代码处理工具。
如果任务是在未定义(或定义)某个宏时从代码中排除对foo(int)
调用,则以下方法可能会更好:
void foo(int x){
#ifdef MACRO_foo
// do something
#endif
}
int main(){
int a;
printf("doing something");
foo(a);
printf("doing something again");
foo(a);
return 0;
}
因此,您可以只排除函数体,而在整个程序中保留函数调用。
我认为您是在要求CIL做CIL无法做的事情。 由于它对预处理的源代码进行操作,因此它不表示预处理器指令,因此您不能“将它们放入CIL表示形式”进行重新生成。 当遇到特殊情况时,您可能可以破解CIL实现本身以吐出指令,但是很难相信这样的hack在任何情况下都是普遍的。
您说您正在寻找“无法解析C代码的编译器来执行此操作”。 如果您坚持认为“ this”涉及CIL,我认为您很不幸。 只有CIL本身可以做到这一点。
如果您放弃CIL并考虑使用其他工具,那么我想我有一个答案,它将像CIL一样做, 可以在表示中保留预处理器指令(和/或允许您根据自定义规则插入它们),并可以重新生成有效的C源代码文本。
该工具是我们的DMS软件再造工具包 ,通用程序转换引擎及其C前端 。 DMS将C代码解析为AST,然后将其解析为有效的源代码,包括保留注释。 它可以用于混合使用其AST操纵库中的过程调用和/或使用表面语法进行源到源重写的源到源转换。
在大多数情况下,DMS会捕获AST中的预处理器指令(它们只是“更多语法!”);有时您需要(永久地)稍微修改源代码以使预处理器指令可口DMS为C提供符号表和控制和数据流分析;这些将需要进行一些修订以处理预处理程序的条件。
为了匹配您对CIL所做的工作,您可以要求DMS进行预处理。 现在,您最终获得了不含预处理程序的AST。 DMS现有的符号表,CF和DF机械现在可以直接处理这种情况。 因此,您可以使用该附加信息对AST进行复杂的操作,其方式不同于但等同于CIL。 此外,您仍然可以修改AST以插入预处理程序指令,这似乎是您的关键问题。
为了实现特定于呼叫站点的条件的特定效果,您可以利用DMS的表面语法从源到源的转换功能。 以下DMS转换可以完成您想要的操作:
rule wrap_function_call(i: Identifier, a:arguments ):statement -> statement
" \i(\a); "
->
" #ifdef \generate_macro_name\(\i\)
\i(\a);
#endif
"
if want_to_wrap(i);
该规则查找与函数调用相对应的任何语法树作为语句 ,并将其包装在条件语句中。 (如果函数调用是表达式的一部分,您没有说要做什么;这种情况需要更多的转换,但也可以处理)。 定制帮助器函数generate_macro_name使用与与该函数名称匹配的标识符AST节点关联的源位置信息来制造宏名称。 转换以另一个自定义帮助器函数want_to_wrap为条件,该函数检查每个匹配的名称以确定是否应该包装该名称。
完成代码转换后,您将调用DMS的prettyprinter机制将AST打印为源文本。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.