编程艺术-元编程
元编程(Meta Programming)
用来生成代码的程序有时被称为 元程序(metaprogram);编写这种程序就称为 元编程(metaprogramming).如C++的泛型编程,模板元编程等。通俗的说就是生成代码的编程,代码生成程序编写。
适用范围:
- 使语法更好
- 自动化样板生成
- 编写声明式子程序
用来创建领域特有语言的工具通常称为 宏语言(macro language)。 文本宏(textual macro) 是可以直接影响编程语言中的文本的宏,它们并不需要了解或处理语言的意义。两个最广泛使用的文本宏系统是 C 预处理器和 M4 宏处理器。
C 语言中的 #define 宏:
#define SWAP(a, b, type) { type __tmp_c; c = b; b = a; a = c; }
文本替换是一种有效但是却非常有限的特性。这种特性有以下问题:
- 文本替换在与其他表达式一起使用时,可能会变得非常混乱。
- C 预处理器对于自己的宏只允许使用有限数目的参数。
- 由于 C 语言的类型系统,通常需要对不同类型的参数定义不同的宏,至少必须传递一个参数类型作为参数。
- 由于只进行文本替换,因此如果这与传递给它的参数冲突,C 语言就无法智能地对临时变量重新进行命名。如果传递 __tmp_c 变量,那么我们这个宏就会完全失败了。
M4 宏处理器: 是最高级的文本宏处理系统之一。它的声望主要是由于这是流行的 sendmail 配置文件所使用的辅助工具。
代码生成器的考虑因素:
- GNU/Linux 系统提供了几个用来编写程序的程序。最常见的有:
- Flex,这是一个词汇分析器生成器
- Bison,语法分析器生成器
- Gperf,一个很好的 hash 函数生成器
这些工具都可以为 C 语言生成一些文件。
例如,将数据库访问方法集成到一种语言中通常非常繁琐。要让这个过程变得又简单、又标准化,那么嵌入式 SQL 就是一个很好的元编程系统,可以在 C 语言中简单地合并数据库访问的功能。
虽然在 C 语言中有很多库可以用来访问数据库,但是使用诸如嵌入式 SQL 之类的代码生成器可以使合并 C 和数据库访问的功能更加简单:它将 SQL 实体的功能作为语言的一种扩展合并到了 C 语言中。然而,很多嵌入式 SQL 的实现通常都是一些专用的宏处理器,可以生成 C 程序作为输出结果。使用嵌入式 SQL 可以让对数据库的访问比直接使用库函数来访问数据库更加自然、直观,而且程序员可以更少犯错误。使用嵌入式 SQL,数据库编程的复杂性可以通过一些宏子语言来屏蔽。
嵌入SQL语句的应用程序叫做宿主程序,书写该程序的语言称为宿主语言。 --宿主语言可以是C、C++、JAVA等。嵌入的SQL语句与交互式SQL在语法上类似 --但是嵌入式SQL在个别语句上有所扩充 --嵌入的SQL语句主要有两种类型:执行性SQL语句和说明性SQL语句。执行性SQL语句可用来 --定义数据、查询和操纵数据库中的数据,每一执行性语句真正对数据库进行操作。说明性语句 --用来说明通信域和SQL语句中用到的变量。说明性语句不生成执行代码 --在C语言中使用嵌入式SQL --SQLSERVER提供对C的嵌入式语言的支持,在用于C语言的嵌入式SQL(以下简称ESQL/C) --大部分语法都来源于TSQL语法,只是在某些细节上略有不同。
代码生成器
适用范围:
- 需要提前生成数据表的程序
- 有大量样板文件的程序,但是无法抽象成函数
- 使用开发语言不具备的特性的程序
我们将构建一个代码生成器,它要对一个整数执行一个或一组函数,并为结果构建一个查找表。
假设我们希望得到这样一个查找表:它返回 5 到 20 之间各个数字的平方根。我们可以编写一个简单的程序来生成这样一个查找表
静态查找表
double square_roots[] = {
/* these are the ones we skipped */ 0.0, 0.0, 0.0, 0.0, 0.0
2.236068, /* Square root of 5 */
2.449490, /* Square root of 6 */
2.645751, /* Square root of 7 */
2.828427, /* Square root of 8 */
3.0, /* Square root of 9 */
...
4.472136 /* Square root of 20 */
};
我们需要的是这样一个程序,它可以生成这些结果,并将其输出到上面这样的表中,这样就可以在编译时加载了。
生成编译时平方根表的理想方法
/* sqrt.in */
/* Our macro invocation to build us the table. The format is: */
/* TABLE:array name:type:start index:end index:default:expression */
/* VAL is used as the placeholder for the current index in the expression */
TABLE:square_roots:double:5:20:0.0:sqrt(VAL)
int main()
{
printf("The square root of 5 is %f\n", square_roots[5]);
return 0;
}
只需要一个程序将这个宏转换成标准的 C 语言就可以了。对于这个简单的例子来说,我们将使用 Perl 来实现这个程序,因为它可以对字符串中的用户代码进行评测,其语法也与 C 语言非常类似。这样我们就可以动态加载并处理用户代码了。
这个表宏的代码生成器
#!/usr/bin/perl # #tablegen.pl # ##Puts each program line into $line while(my $line = <>) { #Is this a macro invocation? if($line =~ m/TABLE:/) { #If so, split it apart into its component pieces my ($dummy, $table_name, $type, $start_idx, $end_idx, $default, $procedure) = split(m/:/, $line, 7); #The main difference between C and Perl for mathematical expressions is that #Perl prefixes its variables with a dollar sign, so we will add that here $procedure =~ s/VAL/\$VAL/g; #Print out the array declaration print "${type} ${table_name} [] = {\n"; #Go through each array element foreach my $VAL (0 .. $end_idx) { #Only process an answer if we have reached our starting index if($VAL >= $start_idx) { #evaluate the procedure specified (this sets $@ if there are any errors) $result = eval $procedure; die("Error processing: $@") if $@; } else { #if we haven't reached the starting index, just use the default $result = $default; } #Print out the value print "\t${result}"; #If there are more to be processed, add a comma after the value if($VAL != $end_idx) { print ","; } print "\n" } #Finish the declaration print "};\n"; } else { #If this is not a macro invocation, just copy the line directly to the output print $line; } }
在 Scheme 编程语言中,这种语言本身可以表示成一个链表,并且 Scheme 编程语言就是为进行列表处理而开发的。这使得 Scheme 非常适合于创建被转换的程序,要对程序进行分析并不需要大量的处理,Scheme 本身就是一种列表处理语言。 Scheme 有几个宏系统,但是 syntax-rules 是其中最标准的。
参考:
