编程艺术-元编程

元编程(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 是其中最标准的。

参考:

  1. 元编程简介
  2. 从中间件的历史来看移动App开发的未来
  3. 嵌入式SQL