<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title type="text">博客园_装配中的脑袋</title><subtitle type="text">用大脑装配程序，再用程序装配大脑</subtitle><id>http://feed.cnblogs.com/blog/u/8459/rss</id><updated>2012-01-03T07:08:34Z</updated><author><name>装配脑袋</name><uri>http://www.cnblogs.com/Ninputer/</uri></author><generator>CNBlogs BlogServer</generator><link rel="alternate" type="text/html" href="http://www.cnblogs.com/Ninputer/"/><link rel="self" type="application/atom+xml" href="http://feed.cnblogs.com/blog/u/8459/rss"/><entry><id>http://www.cnblogs.com/Ninputer/archive/2012/01/03/2310945.html</id><title type="text">C++ AMP实战：绘制曼德勃罗特集图像</title><summary type="text">之前我写了一篇用GPU绘制曼德勃罗特（Mandelbrot）集图像的文章，里面使用的技术是与DirectX 11继承在一起的DirectCompute。DirectCompute执行在GPU上的kernel代码，必须用一种特殊的HLSL语言来编写。虽然这种语言有些类似于C，但一些特殊的细节使得没接触过DirectX的开发人员很不适应。相比于kernel代码，驱动HLSL所要进行的准备工作那简直麻烦...</summary><published>2012-01-03T07:02:00Z</published><updated>2012-01-03T07:02:00Z</updated><author><name>装配脑袋</name><uri>http://www.cnblogs.com/Ninputer/</uri></author><link rel="alternate" href="http://www.cnblogs.com/Ninputer/archive/2012/01/03/2310945.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/Ninputer/archive/2012/01/03/2310945.html"/><content type="html">&lt;p&gt;之前我写了&lt;a href="http://www.cnblogs.com/Ninputer/archive/2009/11/24/1609364.html" target="_blank"&gt;一篇用GPU绘制曼德勃罗特（Mandelbrot）集图像的文章&lt;/a&gt;，里面使用的技术是与DirectX 11继承在一起的DirectCompute。DirectCompute执行在GPU上的kernel代码，必须用一种特殊的HLSL语言来编写。虽然这种语言有些类似于C，但一些特殊的细节使得没接触过DirectX的开发人员很不适应。相比于kernel代码，驱动HLSL所要进行的准备工作那简直麻烦得要命，所以我在那篇博客里索性略去了。如果要想要体会一下DirectCompute那麻烦又不直观的API，可以参考&lt;a href="http://www.cnblogs.com/Ninputer/archive/2009/12/11/1622190.html" target="_blank"&gt;我翻译的另一篇博客&lt;/a&gt;。后来我转向了OpenCL，这是一种异构并行计算的行业标准。它的的kernel语言和API相比DirectCompute那可是简单了好几倍，又有.NET封装类库，无疑是个方便的选择。但是经过我的实践，至少在我的AMD 5850显卡上，OpenCL的性能没有DirectCompute那么好。能否鱼和熊掌兼得呢？现在这个选择出现了：C++ AMP&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;C++ AMP全名C++ Accelerated Massive Parallelism（加速大规模并行计算）。是微软提出的基于C++的异构化并行计算平台。它将随Visual Studio 11一起发布，目前为预览版本。所谓异构并行计算，主要的需求就来自于GPU通用计算的崛起。GPU非常适合大规模数据并行算法，即同一程序应多多组不同的数据进行并行运算。然而GPU的架构与主流CPU不同，而且常常更新换代，没法采用传统编程语言来编程。现有的GPU多数编程方案，如DirectCompute和OpenCL，都要使用不同的语言或编译器来编写运行于GPU上的kernel部分和运行在CPU上的host部分。C++ AMP统一了这两部分，可以用同一个编译器，同一种语法来编写kernel代码；无需任何编译器选项或设置。更甚至C++ AMP的API简单到了极致，比OpenCL的方便程度更上了一个层次。下面我们就用C++ AMP来实现经典的曼德勃罗特集图形绘制。&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;先来回顾一下曼德勃罗特集，这个是很多并行计算书都用来做例子的经典问题，因为它是一个典型的易并行算法。选任意复数c，让z从0开始采用以下公式&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/Ninputer/WindowsLiveWriter/GPU_7B55/clip_image002_2.gif"&gt;&lt;img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="clip_image002" border="0" alt="clip_image002" src="http://images.cnblogs.com/cnblogs_com/Ninputer/WindowsLiveWriter/GPU_7B55/clip_image002_thumb.gif" width="126" height="29"&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;进行迭代。不同的c会产生很不一样的效果。有的c可以一直迭代下去（z的模都在半径为2的圆以内），而有的c会导致迭代若干次之后逃逸（其模会变得大于2，进而趋近于无穷）。不同的c迭代次数相差很大，如果把平面上的每个点坐标都当作c来进行迭代，然后把迭代次数绘制成不同的颜色，就会形成绚丽的图形。而不同c可以完全独立地进行独立，不会影响其他c值的计算结果。因此算法可以是完全并行的。&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;首先我们要开辟出一块空间来表示最后生成的图像。图像上的每一个像素将会映射到一个复数c上，这样a x b的图像，就表示a x b个不同的复数，分别进行上述迭代。C++ AMP提供了两种表示数据的方式：array模板类和array_view模板类。array模板类就像C++ 11的STL容器array一样，是一个固定尺寸、固定维度的数组。和STL array唯一的不同是C++ AMP的array直接分配在GPU的显存中。声明array时要传入两个模板参数——array元素的类型和维度。比如我们要构建一个unsigned int类型的二维数组，就可以这样写：&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top"&gt;&lt;pre &gt;&lt;span style="background: white; color: blue"&gt;&lt;font face="Consolas"&gt;int &lt;/font&gt;&lt;/span&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;a = 100, b = 100;&#xD;
&lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;array&lt;/span&gt;&lt;span style="background: white; color: black"&gt;&amp;lt;&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;unsigned int&lt;/span&gt;&lt;span style="background: white; color: black"&gt;, 2&amp;gt; buffer(a, b);&lt;/span&gt;&lt;/font&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;注意这个array模板是定义在Concurrency命名空间里的，需要提前using。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;有时我们需要把CPU上的数据传送到GPU上，或者希望GPU计算的结果直接写入指定的CPU内存中，这时我们就可以用第二个模板类array_view。array_view本身不会分配任何内存，它只是一个CPU数据buffer的包装，其中数据buffer可以是一个指针、数组，也可以是诸如std::vector等各种常用容器。array_view仅仅当数据访问发生时才进行必要的数据拷贝。声明array_view和声明array一样，但必须传入它的基础容器：&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;span style="background: white; color: blue"&gt;&lt;font face="Consolas"&gt;int &lt;/font&gt;&lt;/span&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;a = 100, b = 100;&#xD;
std::&lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;vector&lt;/span&gt;&lt;span style="background: white; color: black"&gt;&amp;lt;&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;unsigned int&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;&amp;gt; myarray(a * b);&#xD;
&#xD;
&lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;array_view&lt;/span&gt;&lt;span style="background: white; color: black"&gt;&amp;lt;&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;unsigned int&lt;/span&gt;&lt;span style="background: white; color: black"&gt;, 2&amp;gt; view(a, b, myarray);&lt;/span&gt;&lt;/font&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;接下来最重要的是让代码在GPU上执行，完成这个任务的入口方法是parallel_for_each方法。它非常像.NET 4里面的Parallel.ForEach方法，是一个通过lambda函数来分派任务的方法。我们先来完成一个简单的任务，现在有两个array_view，要生成一个array，其内容是两个array_view中相应元素的和。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;std::&lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;vector&lt;/span&gt;&lt;span style="background: white; color: black"&gt;&amp;lt;&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;unsigned int&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;&amp;gt; arr1(10);&#xD;
std::&lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;vector&lt;/span&gt;&lt;span style="background: white; color: black"&gt;&amp;lt;&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;unsigned int&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;&amp;gt; arr2(10);&#xD;
&#xD;
&lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;array_view&lt;/span&gt;&lt;span style="background: white; color: black"&gt;&amp;lt;&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;unsigned int&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;, 1&amp;gt; view1(10, arr1);&#xD;
&lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;array_view&lt;/span&gt;&lt;span style="background: white; color: black"&gt;&amp;lt;&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;unsigned int&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;, 1&amp;gt; view2(10, arr2);&#xD;
&#xD;
&lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;array&lt;/span&gt;&lt;span style="background: white; color: black"&gt;&amp;lt;&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;unsigned int&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;, 1&amp;gt; r(10);&#xD;
&#xD;
&lt;strong&gt;parallel_for_each&lt;/strong&gt;(r.grid, [view1, view2, &amp;amp;r](&lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;index&lt;/span&gt;&lt;span style="background: white; color: black"&gt;&amp;lt;1&amp;gt; i) &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;restrict&lt;/span&gt;&lt;span style="background: white; color: black"&gt;(&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;direct3d&lt;/span&gt;&lt;/font&gt;&lt;span style="background: white; color: black"&gt;&lt;font face="Consolas"&gt;)&#xD;
{&#xD;
    r[i] = view1[i] + view2[i];&#xD;
});&lt;/font&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;观察这里的parallel_for_each，有许多有趣的信息。首先它的第一个参数是一个grid类型的对象。grid是C++ AMP用来描述并行计算拓扑结构的对象。因为经过parallel_for_each方法分派的计算是在显卡计算的，我们需要事先指定有多少GPU线程来完成计算，以及这些线程的排列方式。这个任务当中，我们想要把两个array_view表示的数据想加，为了达到最大并行性，我们给数组的每一个元素都分配一个线程。代码里的r.grid就表示线程的排列方式由数组r自身的拓扑结构决定——在这个例子中就是一个1 x 10的一维结构。注意，GPU上通常可以开成百上千的线程，几乎没有任何代价，所以线程的拓扑结构应当尽量达到最大的并行度。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;接下来那个方括号语法是C++的lambda表达式语法。C++ 11开始支持lambda表达式，和其他语言的lambda表达式一样，这是一种就地声明匿名函数的语法。lambda表达式在C++ 11中具有极其重要的地位。一开始的方括号是lambda表达式的&lt;strong&gt;捕获列表&lt;/strong&gt;。与C#等语言的lambda表达式不同，C++得lambda表达式需要显式进行变量捕获，只有列在捕获列表中的变量才能在lambda函数体内使用。注意，捕获的时候直接写名字的变量（如上面例子中的view1和view2）是按值捕获的变量，在lambda函数体内修改它们的值，不会改变原始变量。而名字前加一个&amp;amp;符号的捕获变量是按引用捕获的变量，lambda函数对其修改会反应到原始变量中。在C++ AMP的parallel_for_each调用时有一个规定：&lt;strong&gt;所有变量都必须按值捕获，除了array对象，array对象必须按引用捕获&lt;/strong&gt;。这就是上面例子为何要这样写的原因。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;捕获列表之后是lambda函数的参数，定义方法和普通函数的参数方法是一样的。parallel_for_each要求传入的函数接受一个index类型的特殊参数。这个参数是实际运行时的线程编号。因为线程的拓扑结构已经由grid参数决定了，所以这个index运行时就会是grid表示范围中的一个值，维度和grid一样。比如上面的例子，index就会表示0-9号线程中的某一个，这些线程都是并行执行的。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;参数表之后，出现了一个新的修饰符restrict(direct3d)。这是专为C++ AMP设计的新修饰符，用来表示函数的语法约束。众所周知GPU和CPU是有很多不同的，在GPU上执行的方法无法进行某些操作。比如GPU代码无法调用Windows API，也无法递归。restrict(direct3d)告诉编译器检查函数体中的代码，并且在编译时就能检测出所有不满足约束的语法。restrict修饰符还是函数签名的一部分，可以针对不同的restrict修饰符进行函数重载，这和const等C++原有的修饰符是一样的。除了restrict(direct3d)，还有一种restrict(cpu)修饰符。所有现有C++函数都默认带有restrict(cpu)修饰符。一个函数还可以同时约束为CPU和GPU代码，只要这么写即可：restrict(cpu, direct3d)。在restrict(direct3d)函数中调用其它的函数，会自动选择restrict(direct3d)的重载。稍后我们会看到，C++ AMP定义了一组restrict(direct3d)版本的数学函数，如log和sin等。在restrict(direct3d)代码中调用这些数学函数就会自动使用GPU版本，而普通的CPU代码则会去调用不带修饰的CPU版本。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;最后是lambda函数的函数体，里面的代码非常直白，就是直接对当前线程index所对应的数组元素进行相加。当你运行上述代码，该代码就会在GPU上以并行计算的方式完成。没有任何额外的初始化代码，隐藏代码或者编译器选项设置——只要这么写就可以运行了。是不是很方便？&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;下面我们就来完成GPU上的曼德勃罗特集生成算法。先看头文件mandelbrot.h，里面include了amp.h——C++ AMP的主要头文件。还声明了一个fp_t类型，根据宏来控制它表示float或double。目前Windows 7上的C++ AMP还不支持双精度浮点运算。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;span style="background: white; color: blue"&gt;&lt;font face="Consolas"&gt;#include &lt;/font&gt;&lt;/span&gt;&lt;span style="background: white; color: #a31515"&gt;&lt;font face="Consolas"&gt;"amp.h"&#xD;
&#xD;
&lt;/font&gt;&lt;/span&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: green"&gt;//#define FP64&#xD;
&#xD;
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;#if defined &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;FP64&#xD;
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;#define &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;_F(x) x&#xD;
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;typedef double &lt;/span&gt;&lt;/font&gt;&lt;span style="background: white; color: black"&gt;&lt;font face="Consolas"&gt;fp_t;&#xD;
&lt;/font&gt;&lt;/span&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: blue"&gt;#else&#xD;
#define &lt;/span&gt;&lt;span style="background: white; color: #6f008a"&gt;_F&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;(x) x##f&#xD;
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;typedef float &lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;fp_t&lt;/span&gt;&lt;/font&gt;&lt;span style="background: white; color: black"&gt;&lt;font face="Consolas"&gt;;&#xD;
&lt;/font&gt;&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;&lt;font face="Consolas"&gt;#endif&#xD;
&#xD;
void &lt;/font&gt;&lt;/span&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;generate_mandelbrot(&#xD;
    Concurrency::&lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;array_view&lt;/span&gt;&lt;span style="background: white; color: black"&gt;&amp;lt;&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;unsigned int&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;, 2&amp;gt; result,&#xD;
    &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;unsigned int &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;max_iter,&#xD;
    &lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;fp_t &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;real_min,&#xD;
    &lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;fp_t &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;imag_min,&#xD;
    &lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;fp_t &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;real_max,&#xD;
    &lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;fp_t &lt;/span&gt;&lt;span style="background: white; color: black"&gt;imag_max );&lt;/span&gt;&lt;/font&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;接下来是实现的代码，还顺便生成了一个HSB（色调、饱和度、亮度）到RGB转换的GPU函数：&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;span style="background: white; color: blue"&gt;&lt;font face="Consolas"&gt;#include &lt;/font&gt;&lt;/span&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: #a31515"&gt;"stdafx.h"&#xD;
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;#include &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: #a31515"&gt;"mandelbrot.h"&#xD;
&#xD;
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;using namespace &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;Concurrency;&#xD;
&#xD;
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;unsigned int &lt;/span&gt;&lt;span style="background: white; color: black"&gt;set_hsb (&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;float &lt;/span&gt;&lt;span style="background: white; color: black"&gt;hue, &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;float &lt;/span&gt;&lt;span style="background: white; color: black"&gt;saturate, &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;float &lt;/span&gt;&lt;span style="background: white; color: black"&gt;bright) &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;restrict &lt;/span&gt;&lt;span style="background: white; color: black"&gt;(&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;direct3d&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;);&#xD;
&#xD;
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;void &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;generate_mandelbrot(&#xD;
    &lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;array_view&lt;/span&gt;&lt;span style="background: white; color: black"&gt;&amp;lt;&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;unsigned int&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;, 2&amp;gt; result,&#xD;
    &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;unsigned int &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;max_iter,&#xD;
    &lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;fp_t &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;real_min,&#xD;
    &lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;fp_t &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;imag_min,&#xD;
    &lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;fp_t &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;real_max,&#xD;
    &lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;fp_t &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;imag_max )&#xD;
{&#xD;
    &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;int &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;width = result.extent.get_x();&#xD;
    &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;int &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;height = result.extent.get_y();&#xD;
&#xD;
    &lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;fp_t &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;scale_real = (real_max - real_min) / width;&#xD;
    &lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;fp_t &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;scale_imag = (imag_max - imag_min) / height;&#xD;
&#xD;
    parallel_for_each(result.grid, [=](&lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;index&lt;/span&gt;&lt;span style="background: white; color: black"&gt;&amp;lt;2&amp;gt; i) &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;restrict&lt;/span&gt;&lt;span style="background: white; color: black"&gt;(&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;direct3d&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;)&#xD;
    {&#xD;
        &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;int &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;gx = i.get_x();&#xD;
        &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;int &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;gy = i.get_y();        &#xD;
&#xD;
        &lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;fp_t &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;cx = real_min + gx * scale_real;&#xD;
        &lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;fp_t &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;cy = imag_min + (height - gy) * scale_imag;&#xD;
&#xD;
        &lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;fp_t &lt;/span&gt;&lt;span style="background: white; color: black"&gt;zx = &lt;/span&gt;&lt;span style="background: white; color: #6f008a"&gt;_F&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;(0.0);&#xD;
        &lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;fp_t &lt;/span&gt;&lt;span style="background: white; color: black"&gt;zy = &lt;/span&gt;&lt;span style="background: white; color: #6f008a"&gt;_F&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;(0.0);&#xD;
&#xD;
        &lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;fp_t &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;temp;&#xD;
        &lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;fp_t &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;length_sqr;&#xD;
&#xD;
        &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;unsigned int &lt;/span&gt;&lt;/font&gt;&lt;span style="background: white; color: black"&gt;&lt;font face="Consolas"&gt;count = 0;&#xD;
        &lt;/font&gt;&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;&lt;font face="Consolas"&gt;do&#xD;
        &lt;/font&gt;&lt;/span&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;{&#xD;
            count++;&#xD;
        &#xD;
            temp = zx * zx - zy * zy + cx;&#xD;
            zy = 2 * zx * zy + cy;&#xD;
            zx = temp;&#xD;
&#xD;
            length_sqr = zx * zx + zy * zy;&#xD;
        }&#xD;
        &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;while&lt;/span&gt;&lt;span style="background: white; color: black"&gt;((length_sqr &amp;lt; &lt;/span&gt;&lt;span style="background: white; color: #6f008a"&gt;_F&lt;/span&gt;&lt;/font&gt;&lt;span style="background: white; color: black"&gt;&lt;font face="Consolas"&gt;(4.0)) &amp;amp;&amp;amp; (count &amp;lt; max_iter));&#xD;
    &#xD;
        &lt;/font&gt;&lt;/span&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: green"&gt;//faster using multiplication than division&#xD;
        &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;float &lt;/span&gt;&lt;span style="background: white; color: black"&gt;n = count * 0.0078125f; &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: green"&gt;// n = count / 128.0f; &#xD;
        &#xD;
        &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;float &lt;/span&gt;&lt;/font&gt;&lt;span style="background: white; color: black"&gt;&lt;font face="Consolas"&gt;h = 1.0f - 2.0f * fabs(0.5f - n + floor(n));&#xD;
&#xD;
        &lt;/font&gt;&lt;/span&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: green"&gt;//turn points at maximum iteration to black&#xD;
        &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;float &lt;/span&gt;&lt;span style="background: white; color: black"&gt;bfactor = &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;direct3d&lt;/span&gt;&lt;span style="background: white; color: black"&gt;::clamp((&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;float&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;)(max_iter - count), 0.0f, 1.0f);&#xD;
&#xD;
        result[i] = set_hsb(h, 0.7f, (1.0f - h * h * 0.83f) * bfactor);&#xD;
    });&#xD;
}&#xD;
&#xD;
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;unsigned int &lt;/span&gt;&lt;span style="background: white; color: black"&gt;set_hsb (&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;float &lt;/span&gt;&lt;span style="background: white; color: black"&gt;hue, &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;float &lt;/span&gt;&lt;span style="background: white; color: black"&gt;saturate, &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;float &lt;/span&gt;&lt;span style="background: white; color: black"&gt;bright) &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;restrict &lt;/span&gt;&lt;span style="background: white; color: black"&gt;(&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;direct3d&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;)&#xD;
{   &#xD;
&#xD;
    &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;float &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;red, green, blue;      &#xD;
    &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;float &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;h = (hue * 256) / 60;  &#xD;
    &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;float &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;p = bright * (1 - saturate);  &#xD;
    &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;float &lt;/span&gt;&lt;span style="background: white; color: black"&gt;q = bright * (1 - saturate * (h - (&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;int&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;)h));  &#xD;
    &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;float &lt;/span&gt;&lt;span style="background: white; color: black"&gt;t = bright * (1 - saturate * (1 - (h - (&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;int&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;)h)));  &#xD;
    &#xD;
    &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;switch &lt;/span&gt;&lt;span style="background: white; color: black"&gt;((&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;int&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;)h) {  &#xD;
    &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;case &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;0:   &#xD;
        red = bright,  green = t,  blue = p;  &#xD;
        &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;break&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;;  &#xD;
    &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;case &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;1:  &#xD;
        red = q,  green = bright,  blue = p;  &#xD;
        &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;break&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;;  &#xD;
    &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;case &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;2:  &#xD;
        red = p,  green = bright,  blue = t;  &#xD;
        &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;break&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;;  &#xD;
    &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;case &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;3:  &#xD;
        red = p,  green = q,  blue = bright;  &#xD;
        &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;break&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;;  &#xD;
    &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;case &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;4:  &#xD;
        red = t,  green = p,  blue = bright;  &#xD;
        &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;break&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;;  &#xD;
    &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;case &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;5:  &#xD;
    &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;case &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;6:  &#xD;
        red = bright,  green = p,  blue = q;  &#xD;
        &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;break&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;;  &#xD;
    }  &#xD;
&#xD;
    &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;unsigned int &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;ired, igreen, iblue;&#xD;
    ired = (&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;unsigned int&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;)(red * 255.0f);&#xD;
    igreen = (&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;unsigned int&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;)(green * 255.0f);&#xD;
    iblue = (&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;unsigned int&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;)(blue * 255.0f);&#xD;
    &#xD;
    &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;return &lt;/span&gt;&lt;/font&gt;&lt;span style="background: white; color: black"&gt;&lt;font face="Consolas"&gt;0xff000000 | (ired &amp;lt;&amp;lt; 16) | (igreen &amp;lt;&amp;lt; 8) | iblue;  &#xD;
 }&lt;/font&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;注意，在这段代码中我们使用的array_view是二维的，所以index也相应是一个二维的结构。我们可以从index的get_x()，get_y()等方法得到线程的编号。代码中还使用了若干小技巧，比如判断复数模的平方大于4而不是模大于2。因为开平方运算即使在GPU上也是个比较慢的操作，需要尽量避免。方法的最后部分使用一个特殊的算法将迭代数转化成了颜色。这个公式是我反复试验得到的，只是为了视觉上好看。大家自己实践可以根据自己喜好随便改。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;C++ AMP的parallel_for_each调用是一种“准同步”的调用，当parallel_for_each完成时，GPU计算并不一定结束了，但代码会继续执行。只有当你访问计算的结果——比如包含结果的array_view时，它就会阻塞线程等待GPU计算的完成。我们可以调用array_view的synchornize方法来显式地等待。C++ AMP也提供真正异步接口，但用起来比较麻烦，在这个例子中就不采用了。下面的代码就是调用以及同步的代码。这里展示代码将结果保存在一个位图中：&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: blue"&gt;int &lt;/span&gt;&lt;span style="background: white; color: #6f008a"&gt;_tmain&lt;/span&gt;&lt;span style="background: white; color: black"&gt;(&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;int &lt;/span&gt;&lt;span style="background: white; color: black"&gt;argc, &lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;_TCHAR&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;* argv[])&#xD;
{&#xD;
&#xD;
    &lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;GdiplusStartupInput &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;gdiplusStartupInput;&#xD;
    &lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;ULONG_PTR           &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;gdiplusToken;&#xD;
    GdiplusStartup(&amp;amp;gdiplusToken, &amp;amp;gdiplusStartupInput, &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;nullptr&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;);&#xD;
&#xD;
&#xD;
    &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;const int &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;edge = 1024;&#xD;
&#xD;
    std::&lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;vector&lt;/span&gt;&lt;span style="background: white; color: black"&gt;&amp;lt;&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;unsigned int&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;&amp;gt; a(edge * edge);&#xD;
    &lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;array_view&lt;/span&gt;&lt;span style="background: white; color: black"&gt;&amp;lt;&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;unsigned int&lt;/span&gt;&lt;/font&gt;&lt;span style="background: white; color: black"&gt;&lt;font face="Consolas"&gt;, 2&amp;gt; av(edge, edge, a);&#xD;
&#xD;
    generate_mandelbrot(av, 512, -2, -2, 2, 2);&#xD;
&#xD;
    av.synchronize();&#xD;
&#xD;
    &lt;/font&gt;&lt;/span&gt;&lt;span style="background: white; color: green"&gt;&lt;font face="Consolas"&gt;//construct a image    &#xD;
    &lt;/font&gt;&lt;/span&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;{&#xD;
        &lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;Bitmap &lt;/span&gt;&lt;span style="background: white; color: black"&gt;output(edge, edge, edge * 4, &lt;/span&gt;&lt;span style="background: white; color: #6f008a"&gt;PixelFormat32bppARGB&lt;/span&gt;&lt;span style="background: white; color: black"&gt;, &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;reinterpret_cast&lt;/span&gt;&lt;span style="background: white; color: black"&gt;&amp;lt;&lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;BYTE&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;*&amp;gt;(a.data()));&#xD;
&#xD;
        &lt;/span&gt;&lt;span style="background: white; color: #2b91af"&gt;CLSID &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;pngClsid;&#xD;
        GetEncoderClsid(L&lt;/span&gt;&lt;span style="background: white; color: #a31515"&gt;"image/png"&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;, &amp;amp;pngClsid);&#xD;
&#xD;
        output.Save(L&lt;/span&gt;&lt;span style="background: white; color: #a31515"&gt;"test.png"&lt;/span&gt;&lt;span style="background: white; color: black"&gt;, &amp;amp;pngClsid, &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;nullptr&lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="background: white; color: black"&gt;);&#xD;
    }&#xD;
    &#xD;
    GdiplusShutdown(gdiplusToken);&#xD;
    &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;return &lt;/span&gt;&lt;/font&gt;&lt;span style="background: white; color: black"&gt;&lt;font face="Consolas"&gt;0;&#xD;
}&lt;/font&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;以上代码绘制了实部从-2到2，虚部从-2i到2i范围的曼德勃罗特集图形，绘制到一个边长1024的位图上，最大迭代次数512次。注意其中synchronize方法的调用。所得结果（缩小图）为：&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/Ninputer/201201/201201031353197718.jpg"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="test" border="0" alt="test" src="http://images.cnblogs.com/cnblogs_com/Ninputer/201201/201201031353211174.jpg" width="400" height="400"&gt;&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;曼德勃罗特集图形每一点放大都有无穷无尽美丽的花纹，为了能够任意探索图形的细节，我还编写了一个Windows界面的程序，使用Direct2D将所绘图像展示出来，而且加入了拖拽和鼠标滚轮的交互。实际运行效果非常酷，因为GPU并行计算的强大性能，所有交互都是平滑实时进行的。就好像是一张可以无限放大的图片一样。美中不足的是当前C++ AMP的Windows 7实现只支持单精度浮点数，所以放大到一定倍数就会模糊了。等到Windows 8的稳定版本出现，大家才能体验到双精度浮点的美妙结果。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/Ninputer/201201/201201031353289334.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/Ninputer/201201/201201031353408570.png" width="640" height="640"&gt;&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;我已经将所有源代码上传到了Github上，地址为：&lt;a title="https://github.com/Ninputer/AMP-Demo" href="https://github.com/Ninputer/AMP-Demo" target="_blank"&gt;https://github.com/Ninputer/AMP-Demo&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;主要，要编译和运行此例子，需要以下软硬件环境：&lt;/p&gt;&#xD;
&lt;p&gt;1. Windows 7 （不支持XP和Vista）&lt;/p&gt;&#xD;
&lt;p&gt;2. 硬件支持DirectX 11所有特性的显卡。如Nvidia Geforce 400,500系列显卡，AMD Radeon HD5000,6000,7000系列显卡。无法运行于不支持DirectX 11的显卡上。&lt;/p&gt;&#xD;
&lt;p&gt;3. 需要Visual Studio 11 Developer Preview。&lt;a href="http://www.microsoft.com/download/en/details.aspx?displaylang=en&amp;amp;id=27543ls.aspx?displaylang=en&amp;amp;id=27543" target="_blank"&gt;&lt;font color="#0000ff"&gt;下载地址&lt;/font&gt;&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;4. 需要Windows SDK。&lt;a href="http://www.microsoft.com/downloads/details.aspx?FamilyID=c17ba869-9671-4330-a63e-1fd44e0e2505&amp;amp;displaylang=e" target="_blank"&gt;&lt;font color="#0000ff"&gt;下载地址&lt;/font&gt;&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;我还翻译了一个Build大会关于C++ AMP的视频，是快速了解C++ AMP的最佳途径。观看地址：&lt;a title="http://www.tudou.com/playlist/p/l13690258i105735621.html" href="http://www.tudou.com/playlist/p/l13690258i105735621.html" target="_blank"&gt;http://www.tudou.com/playlist/p/l13690258i105735621.html&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;欢迎关注我的微博&lt;a title="http://weibo.com/ninputer" href="http://weibo.com/ninputer" target="_blank"&gt;http://weibo.com/ninputer&lt;/a&gt;，一起讨论C++ AMP和其他有趣的技术~&lt;/p&gt;&lt;img src="http://www.cnblogs.com/Ninputer/aggbug/2310945.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/Ninputer/archive/2012/01/03/2310945.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/Ninputer/archive/2011/08/18/2145045.html</id><title type="text">GPU并行计算版函数图像生成器</title><summary type="text">前几天技术大牛Vczh同学开发了一个函数图像绘制程序，可以画出方程f(x,y)=0的图像。他的原理是用图像上每一点的坐标带入函数f得到针对x和y的两个方程，再用牛顿迭代法求解得到一组点集，然后画到图像上。用他的程序可以画出各种各样令人惊叹的方程图形。但是他的程序非常慢，因为对每一个点坐标都用牛顿迭代法求解是一项很费时的任务，即使采用了Parallel.For，CPU算起来也很吃力。我研究了他的程序...</summary><published>2011-08-18T15:48:00Z</published><updated>2011-08-18T15:48:00Z</updated><author><name>装配脑袋</name><uri>http://www.cnblogs.com/Ninputer/</uri></author><link rel="alternate" href="http://www.cnblogs.com/Ninputer/archive/2011/08/18/2145045.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/Ninputer/archive/2011/08/18/2145045.html"/><content type="html">&lt;p&gt;前几天技术大牛&lt;a href="http://www.cppblog.com/vczh" target="_blank"&gt;Vczh&lt;/a&gt;同学开发了一个&lt;a href="http://www.cppblog.com/vczh/archive/2011/08/11/153055.html" target="_blank"&gt;函数图像绘制程序&lt;/a&gt;，可以画出方程f(x,y)=0的图像。他的原理是用图像上每一点的坐标带入函数f得到针对x和y的两个方程，再用牛顿迭代法求解得到一组点集，然后画到图像上。用他的程序可以画出各种各样令人惊叹的方程图形。但是他的程序非常慢，因为对每一个点坐标都用牛顿迭代法求解是一项很费时的任务，即使采用了Parallel.For，CPU算起来也很吃力。我研究了他的程序之后觉得可以用擅长并行计算的显卡来加速迭代法求解的过程。用&lt;a href="http://www.khronos.org/opencl/" target="_blank"&gt;OpenCL&lt;/a&gt;来完成这个任务再合适不过了。&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;整个过程还是相当顺利的，完全在Vczh原始程序的基础上改成。仅稍微改变了策略。步骤如下：&lt;/p&gt; &lt;ol&gt; &lt;li&gt;解析输入函数f之后，分别生成∂f/∂x和∂f/∂y两个偏导数，然后将这三个二元函数转化为合法的OpenCL表达式。  &lt;li&gt;用OpenCL实现牛顿迭代法。  &lt;li&gt;将图像上的每一点分派到一个OpenCL线程，然后由无数并行的OpenCL线程计算自己的点。&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;其中OpenCL代码如下：&lt;/p&gt; &lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;fp_t func(fp_t x, fp_t y) &#xD;
{   &#xD;
    &lt;span style="color: blue"&gt;return &lt;/span&gt;{动态生成}; &#xD;
}&#xD;
&#xD;
fp_t df_dx(fp_t x, fp_t y)&#xD;
{&#xD;
    &lt;span style="color: blue"&gt;return &lt;/span&gt;{动态生成};&#xD;
}&#xD;
&#xD;
fp_t df_dy(fp_t x, fp_t y)&#xD;
{&#xD;
    &lt;span style="color: blue"&gt;return &lt;/span&gt;{动态生成};&#xD;
}&#xD;
&#xD;
fp_t solvex(fp_t start, &lt;span style="color: blue"&gt;const &lt;/span&gt;fp_t consty)&#xD;
{&#xD;
    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;i = 0; i &amp;lt; MAX_ITER; ++i)&#xD;
    {&#xD;
        fp_t result = func(start, consty);&#xD;
        &#xD;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(result &amp;lt;= EPSILON &amp;amp;&amp;amp; result &amp;gt;= -EPSILON)&#xD;
        {&#xD;
            &lt;span style="color: blue"&gt;return &lt;/span&gt;start;&#xD;
        }&#xD;
&#xD;
        fp_t d = df_dx(start, consty);&#xD;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(d &amp;lt;= EPSILON &amp;amp;&amp;amp; d &amp;gt;= -EPSILON)&#xD;
        {&#xD;
            &lt;span style="color: blue"&gt;return &lt;/span&gt;NAN;&#xD;
        }&#xD;
        &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;else&#xD;
        &lt;/span&gt;{&#xD;
            start -= result / d;&#xD;
        }&#xD;
    }&#xD;
    &lt;span style="color: blue"&gt;return &lt;/span&gt;NAN;&#xD;
}&#xD;
&#xD;
kernel &lt;span style="color: blue"&gt;void &lt;/span&gt;ComputeX(&#xD;
    global write_only fp_t* points,&#xD;
    &lt;span style="color: blue"&gt;int &lt;/span&gt;unit,&#xD;
    &lt;span style="color: blue"&gt;int &lt;/span&gt;width,&#xD;
    &lt;span style="color: blue"&gt;int &lt;/span&gt;cx,&#xD;
    &lt;span style="color: blue"&gt;int &lt;/span&gt;cy,&#xD;
    &lt;span style="color: blue"&gt;float&lt;/span&gt; origin_x,&#xD;
    &lt;span style="color: blue"&gt;float&lt;/span&gt; origin_y)&#xD;
{&#xD;
    &lt;span style="color: blue"&gt;int &lt;/span&gt;gx = get_global_id(0);&#xD;
    &lt;span style="color: blue"&gt;int &lt;/span&gt;gy = get_global_id(1);&#xD;
&#xD;
    uint write_loc = gx + gy * width;&#xD;
    &#xD;
    fp_t py = origin_y + (fp_t)(gy + 1 - cy) / unit;&#xD;
    fp_t px = origin_x + (fp_t)(cx - gx - 1) / unit;&#xD;
&#xD;
    points[write_loc] = solvex(px, py);&#xD;
}&lt;/font&gt;&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;这是求解f(x, a) = 0部分的代码，求解f(b, y) = 0的与之基本一样。其中fp_t是根据情况定义的typedef，可能是float或double。因为不是所有的OpenCL设备都支持双精度浮点，所以要写成通用类型的算法，用宏来控制。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;在.NET中使用OpenCL，最容易的方法当然是使用&lt;a href="http://sourceforge.net/projects/cloo/" target="_blank"&gt;&lt;font color="#0000ff"&gt;Cloo&lt;/font&gt;&lt;/a&gt;库。Coo库完整地封装了OpenCL（1.1）的所有函数，并且是.NET非常容易使用的对象模型。我只使用了一次Cloo和OpenCL就再也不想忍受DirectX ComputeShader那麻烦的要死的类库。。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;我的程序源代码已经完全上传到github。地址是：&lt;a title="https://github.com/Ninputer/opencl-plot" href="https://github.com/Ninputer/opencl-plot"&gt;https://github.com/Ninputer/opencl-plot&lt;/a&gt; 点击Download即可打包下载所有代码。想直接运行的朋友可以点击&lt;a href="http://files.cnblogs.com/Ninputer/opencl-plot.zip" target="_blank"&gt;&lt;font color="#0000ff"&gt;此处&lt;/font&gt;&lt;/a&gt;下载二进制文件包。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;要想运行这个程序，你必须安装了OpenCL的实现平台。目前Windows上的OpenCL主要有NVidia、AMD和Intel几家提供的实现平台。如果你有一块比较新的NVidia或AMD显卡，那么只需要装了最新的驱动包就自带了OpenCL。以下显卡支持双精度浮点：NVidia GeForce 200系列、400系列、500系列显卡；AMD Radeon HD 5800、5900、6900系列。其中Radeon 6900系列尚不支持官方双精度浮点数（cl_khr_fp64）扩展，所以本程序也支持cl_amd_fp64双精度浮点扩展，功能上完全一样。G80和RV770等架构的显卡以及AMD中低端显卡只能支持单精度浮点数，绘制时的精度可能会略差。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;没有支持OpenCL显卡的同学也可以用多核CPU来进行OpenCL计算，仍然要比原始的C#版本快一些。如果使用Intel Core i3、i5、i7系列CPU，可以使用Intel OpenCL SDK，下载地址：&lt;a title="http://software.intel.com/en-us/articles/opencl-sdk/" href="http://software.intel.com/en-us/articles/opencl-sdk/"&gt;http://software.intel.com/en-us/articles/opencl-sdk/&lt;/a&gt; 其他多核CPU都可以使用AMD APP SDK，下载地址：&lt;a title="http://developer.amd.com/sdks/AMDAPPSDK/downloads/Pages/default.aspx" href="http://developer.amd.com/sdks/AMDAPPSDK/downloads/Pages/default.aspx"&gt;http://developer.amd.com/sdks/AMDAPPSDK/downloads/Pages/default.aspx&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;启动程序后允许先选择OpenCL计算平台和设备，如果安装了多个OpenCL平台可以任意选择。目前本程序暂时不支持多显卡并联技术（SLI、Crossfire）。NVidia CUDA平台界面示例：&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/Ninputer/201108/201108182118215139.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/Ninputer/201108/201108182118246760.png" width="499" height="480"&gt;&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;AMD APP平台界面示例：&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/Ninputer/201108/201108182346389105.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/Ninputer/201108/201108182346415984.png" width="499" height="480"&gt;&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;Intel OpenCL平台界面示例：&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/Ninputer/201108/201108182346447605.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/Ninputer/201108/201108182346473720.png" width="499" height="480"&gt;&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;现在，输入方程，充分发挥你的想象力吧！&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/Ninputer/201108/201108182347475080.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/Ninputer/201108/201108182347568059.png" width="499" height="480"&gt;&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/Ninputer/201108/201108182347595744.png"&gt;&lt;img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/Ninputer/201108/20110818234804298.png" width="499" height="480"&gt;&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/Ninputer/201108/201108182348097818.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/Ninputer/201108/2011081823481724.png" width="499" height="480"&gt;&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/Ninputer/201108/201108182348207153.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/Ninputer/201108/201108182348251674.png" width="499" height="480"&gt;&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;注意采用显卡计算时，最好不要进行游戏，用IE9浏览网页等，负荷过重时可能会导致GPU重置。如果某些公式运算量太大，或显卡较为低端，也容易导致GPU重置，请务必用Windows 7和Vista来进行实验，因为WDDM驱动模型更加稳定（用XP容易蓝屏）。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;欢迎关注我的微薄获取最新动态 &lt;a title="http://weibo.com/ninputer" href="http://weibo.com/ninputer"&gt;http://weibo.com/ninputer&lt;/a&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/Ninputer/aggbug/2145045.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/Ninputer/archive/2011/08/18/2145045.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/Ninputer/archive/2011/08/02/2120435.html</id><title type="text">自己动手开发编译器（十二）生成托管代码</title><summary type="text">前一阶段我们完成了编译器中的重要阶段——语义分析。现在，程序中的每一个变量和类型都有其正确的定义；每一个表达式和语句的类型都是合法的；每一处方法调用都选择了正确的方法定义。现在即将进入下一个阶段——代码生成。代码生成的最终目的，是生成能在目标机器上运行的机器码，或者可以和其他库链接在一起的可重定向对象。代码生成，和这一阶段的各个优化手段，统称为编译器的后端。目前大部分编译器，在代码生成时，都倾向于...</summary><published>2011-08-02T06:57:00Z</published><updated>2011-08-02T06:57:00Z</updated><author><name>装配脑袋</name><uri>http://www.cnblogs.com/Ninputer/</uri></author><link rel="alternate" href="http://www.cnblogs.com/Ninputer/archive/2011/08/02/2120435.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/Ninputer/archive/2011/08/02/2120435.html"/><content type="html">&lt;p&gt;前一阶段我们完成了编译器中的重要阶段——语义分析。现在，程序中的每一个变量和类型都有其正确的定义；每一个表达式和语句的类型都是合法的；每一处方法调用都选择了正确的方法定义。现在即将进入下一个阶段——代码生成。代码生成的最终目的，是生成能在目标机器上运行的机器码，或者可以和其他库链接在一起的可重定向对象。代码生成，和这一阶段的各个优化手段，统称为编译器的后端。目前大部分编译器，在代码生成时，都倾向于先将前段解析的结果转化成一种&lt;strong&gt;中间表示&lt;/strong&gt;，再将中间表示翻译成最终的机器码。比如Java语言会翻译成JVM bytecode，C#语言会翻译成CIL，再经由各自的虚拟机执行；IE9的javascript也会先翻译成一种bytecode，再由解释器执行或者进行JIT翻译；即使静态编译的语言如C++，也存在先翻译成中间语言，再翻译成最终机器码的过程。中间表示也不一定非得是一种bytecode，我们在语法分析阶段生成的抽象语法树（AST）就是一种很常用的中间表示。.NET 3.5引入的Expression Tree正是采用AST作为中间表示的动态语言运行库。那为什么这种做法非常流行呢？因为翻译中中间语言有如下好处：&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;ol&gt; &lt;li&gt;使用中间语言可以良好地将编译器的前端和后端拆分开，使得两部分可以相对独立地进行。  &lt;li&gt;同一种中间语言可以由多种不同的源语言编译而来，而又可以针对多种不同的目标机器生成代码。CLR的CIL就是这一特点的典型代表。  &lt;li&gt;有许多优化可以直接针对中间语言进行，这样优化的结果就可以应用到不同的目标平台。&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;我们这次动手编写编译器，自然也少不了中间语言这一步。为了达到亲手实践的目的，我们将会自己定义中间语言，但是那样的话想要把编译出的程序运行起来就还需要很多工作。为了提前体验运行目标代码的成就感，同时验证编译器前端的正确性，我们这次先将miniSharp编译成CLR的中间语言——CIL（Common IL，MSIL），并且就使用.NET自带的Reflection.Emit库来做。&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;首先来了解一下CIL的特点。CIL是一种bytecode，在.NET的程序集里他是二进制方式存在的。我们常常见到的是用ILDASM或者ILSpy反汇编而成的汇编形态。例如这一段：&lt;/p&gt; &lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top"&gt; &lt;p&gt;&lt;font face="Consolas"&gt;.method public hidebysig newslot&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; instance int32 ComputeFac (&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; int32 num&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; ) cil managed&lt;br&gt;{&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // Method begins at RVA 0x2050&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // Code size 30 (0x1e)&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; .maxstack 6&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; .locals init (&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; [0] int32&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; )&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; IL_0000: ldarg.1&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; IL_0001: ldc.i4.1&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; IL_0002: clt&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; IL_0004: brfalse IL_0010&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; IL_0009: ldc.i4.1&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; IL_000a: stloc.0&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; IL_000b: br IL_001c&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; IL_0010: ldarg.1&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; IL_0011: ldarg.0&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; IL_0012: ldarg.1&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; IL_0013: ldc.i4.1&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; IL_0014: sub&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; IL_0015: call instance int32 Fac::ComputeFac(int32)&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; IL_001a: mul&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; IL_001b: stloc.0&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; IL_001c: ldloc.0&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; IL_001d: ret&lt;br&gt;} // end of method Fac::ComputeFac&lt;/font&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;p&gt;和机器语言相比，CIL是一种高度抽象的中间语言。程序集里有非常丰富的元数据，可以直接对应到源代码里的类和方法。而CIL仅仅用于描述方法体的逻辑。CIL较少反应出运行时真正发生在CPU上的事情，而更多地与源代码中的语句和表达式接近。所以我们说CIL是一种相当高级的中间语言。CIL是一种&lt;strong&gt;栈式机&lt;/strong&gt;。要注意的是，这里的“栈”与运行时的内存堆和栈的“栈”没有任何关系。CIL的栈是一个&lt;strong&gt;运算栈&lt;/strong&gt;（evaluation stack），它在运行时实际是不存在的，但我们必须要在理解CIL运行过程时想象它存在。运算栈在CIL中的作用是保存运算的中间结果，这与寄存器机的寄存器有些类型。&lt;strong&gt;CIL的每一条指令都只能对运算栈顶进行操作&lt;/strong&gt;。&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;看上面的IL代码，第一行ldarg.1指令的作用是将1号实参加载到运算栈的栈顶上，第二条ldc.i4.1指令是将32位整型常量1压入运算栈。注意“ldc.i4.1”是一条指令，它是不带参数的。IL中有许多这种缩短格式的指令，以消除或减少指令参数，从而减少目标代码的体积。经过这两条指令后，运算栈中有两个值：栈顶是32位常量1，其下面是方法的1号参数值。这时遇到指令clt，这条指令会将运算栈先后弹出两个值，并比较它们的大小，如果后弹出的值小于先弹出的值，则将32位整数“1”压入运算栈，反之则将“0”压入运算栈。假设该方法第一个实参传的是“0”，以上过程如图所示：&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;table border="1" cellspacing="0" cellpadding="5" width="600"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top" width="300"&gt;执行指令&lt;/td&gt; &lt;td valign="top" width="300"&gt;运算栈&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="300"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="300"&gt;空&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="300"&gt;ldarg.1&lt;/td&gt; &lt;td valign="top" width="300"&gt;压入参数值0&lt;br&gt; &lt;table border="1" cellspacing="0" cellpadding="1" width="100"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top" width="100"&gt;0&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="300"&gt;ldc.i4.1&lt;/td&gt; &lt;td valign="top" width="300"&gt;压入常数1&lt;br&gt; &lt;table border="1" cellspacing="0" cellpadding="1" width="100"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top" width="100"&gt;1&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="100"&gt;0&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="300"&gt;clt&lt;/td&gt; &lt;td valign="top" width="300"&gt;弹出1&lt;br&gt;弹出0&lt;br&gt;比较0 &amp;lt; 1，所以压入1&lt;br&gt; &lt;table border="1" cellspacing="0" cellpadding="1" width="100"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top" width="100"&gt;1&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;p&gt;接下来的brfalse指令又会弹出运算栈顶的值，并根据这个值决定是否要进行跳转。以此类推，即可理解每条指令的作用。任何一条IL指令总会将一些值压入栈；或者从栈中弹出一些值；或者先弹出一些值，再压入一些值。这些不同的动作称为这条指令的&lt;strong&gt;栈转换行为&lt;/strong&gt;。每条指令都有固定的栈转换行为，只要理解了栈转换行为，就等于完全理解一条IL指令。&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;MSDN中&lt;a href="http://msdn.microsoft.com/en-us/library/x2ebty98(v=VS.80).aspx" target="_blank"&gt;OpCodes类的帮助&lt;/a&gt;中详细介绍了每一条指令的栈转换规则。当我们需要了解CIL指令的含义时，这个帮助就是最好的资料。简单了解了CIL与运算栈之后，大部分指令的行为都是很好理解的。我这里稍微解释一下某些特殊的规则。&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;在CIL指令表当中大家会看到许多指令有多个版本。比如ldloc指令用于将局部变量加载到运算栈顶。这个指令就有ldloc、ldloc.s、ldloc.0、ldloc.1等不同的版本。这其中的ldloc是该指令的长版本，其他指令则是&lt;strong&gt;短版本&lt;/strong&gt;。因为CIL是bytecode，所以这些指令在程序集中都是一个或两个字节的代码。ldloc长版本指令自身编码为两个字节（FE 06），而且它需要一个uint16（两字节）的参数，所以它一共需要占四个字节的空间。我们知道一个方法很少有65536这么多个本地变量，很多也就是1-2个，有十几个的已经算是非常多了。所以都用这么长的指令非常浪费。短版本ldloc.s自身编码只有一个字节（11），而且它的参数是uint8（一个字节），该指令只占2个字节的空间。但是ldloc.s只能加载索引在0-255范围内的本地变量。最后，针对最常用的头4个本地变量，还有四个最短版本。比如ldloc.0，仅占一个字节的编码（06）没有参数。我们在生成代码的时候需要根据访问本地变量的索引来选取不同的指令：&lt;/p&gt; &lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;private void &lt;/span&gt;EmitLoadLocal(&lt;span style="color: blue"&gt;int &lt;/span&gt;locIndex)&#xD;
{&#xD;
    &lt;span style="color: blue"&gt;switch &lt;/span&gt;(locIndex)&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;case &lt;/span&gt;0:&#xD;
            m_ilgen.Emit(&lt;span style="color: #2b91af"&gt;OpCodes&lt;/span&gt;.Ldloc_0);&#xD;
            &lt;span style="color: blue"&gt;break&lt;/span&gt;;&#xD;
        &lt;span style="color: blue"&gt;case &lt;/span&gt;1:&#xD;
            m_ilgen.Emit(&lt;span style="color: #2b91af"&gt;OpCodes&lt;/span&gt;.Ldloc_1);&#xD;
            &lt;span style="color: blue"&gt;break&lt;/span&gt;;&#xD;
        &lt;span style="color: blue"&gt;case &lt;/span&gt;2:&#xD;
            m_ilgen.Emit(&lt;span style="color: #2b91af"&gt;OpCodes&lt;/span&gt;.Ldloc_2);&#xD;
            &lt;span style="color: blue"&gt;break&lt;/span&gt;;&#xD;
        &lt;span style="color: blue"&gt;case &lt;/span&gt;3:&#xD;
            m_ilgen.Emit(&lt;span style="color: #2b91af"&gt;OpCodes&lt;/span&gt;.Ldloc_3);&#xD;
            &lt;span style="color: blue"&gt;break&lt;/span&gt;;&#xD;
        &lt;span style="color: blue"&gt;default&lt;/span&gt;:&#xD;
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(locIndex &amp;lt;= 255)&#xD;
            {&#xD;
                m_ilgen.Emit(&lt;span style="color: #2b91af"&gt;OpCodes&lt;/span&gt;.Ldloc_S, (&lt;span style="color: blue"&gt;byte&lt;/span&gt;)locIndex);&#xD;
            }&#xD;
            &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;else&#xD;
            &lt;/span&gt;{&#xD;
                m_ilgen.Emit(&lt;span style="color: #2b91af"&gt;OpCodes&lt;/span&gt;.Ldloc, (&lt;span style="color: blue"&gt;short&lt;/span&gt;)locIndex);&#xD;
            }&#xD;
            &lt;span style="color: blue"&gt;break&lt;/span&gt;;&#xD;
    }&#xD;
}&lt;/font&gt;&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;下面我们就开始为miniSharp语言编写CIL代码生成器。和语义分析阶段类似，我们只需要编写一个AST的Visitor实现即可。要注意，我们不仅需要生成方法的IL代码，还需要生成程序集、模块、类、方法、构造函数、字段等定义。Reflection.Emit为这些结构提供了各类Builder类型，使用非常方便，但必须注意一些规则：&lt;/p&gt;&#xD;
&lt;ol&gt;&#xD;
&lt;li&gt;为了生成exe，程序集入口的PEFileKind应当是ConsoleApplication（默认是Dll）。 &#xD;
&lt;li&gt;每个类对应一个TypeBuilder，生成一个类之后必须调用其CreateType方法真正生成类型。一个类CreateType之前，它的父类必须已经CreateType过才行。所以要按照继承顺序创建各个类。 &#xD;
&lt;li&gt;TypeBuilder上也有Type类的所有方法，如GetConstructor、GetMethod之类，但只有当TypeBuilder调用过CreateType之后这些方法才能使用。所以我们必须自己保存未完成类型的成员信息。&lt;/li&gt;&lt;/ol&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;下面的代码展示了按照类继承顺序生成各类的代码：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;public override &lt;/span&gt;&lt;span style="color: #2b91af"&gt;AstNode &lt;/span&gt;VisitProgram(&lt;span style="color: #2b91af"&gt;Program &lt;/span&gt;ast)&#xD;
{&#xD;
    &lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;ClassDecl&lt;/span&gt;&amp;gt; classesInHierarchyOrder = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;ClassDecl&lt;/span&gt;&amp;gt;();&#xD;
&#xD;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;topBaseClasses = &lt;span style="color: blue"&gt;from &lt;/span&gt;c &lt;span style="color: blue"&gt;in &lt;/span&gt;ast.Classes &lt;span style="color: blue"&gt;where &lt;/span&gt;c.BaseClass.Type == &lt;span style="color: blue"&gt;null select &lt;/span&gt;c;&#xD;
    classesInHierarchyOrder.AddRange(topBaseClasses);&#xD;
&#xD;
    &lt;span style="color: blue"&gt;while &lt;/span&gt;(classesInHierarchyOrder.Count &amp;lt; ast.Classes.Count)&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;c &lt;span style="color: blue"&gt;in &lt;/span&gt;ast.Classes)&#xD;
        {&#xD;
            &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;b &lt;span style="color: blue"&gt;in &lt;/span&gt;classesInHierarchyOrder.ToArray())&#xD;
            {&#xD;
                &lt;span style="color: blue"&gt;if &lt;/span&gt;(c.BaseClass.Type == b.Type)&#xD;
                {&#xD;
                    classesInHierarchyOrder.Add(c);&#xD;
                }&#xD;
            }&#xD;
        }&#xD;
    }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;c &lt;span style="color: blue"&gt;in &lt;/span&gt;classesInHierarchyOrder)&#xD;
    {&#xD;
        Visit(c);&#xD;
    }&#xD;
&#xD;
    Visit(ast.MainClass);&#xD;
&#xD;
    &lt;span style="color: blue"&gt;return &lt;/span&gt;ast;&#xD;
}&#xD;
&lt;/font&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;下面展示MainClass的生成方法。这里用了一个技巧，即static class = abstract + sealed。&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;span style="color: blue"&gt;public override &lt;/span&gt;&lt;span style="color: #2b91af"&gt;AstNode &lt;/span&gt;VisitMainClass(&lt;span style="color: #2b91af"&gt;MainClass &lt;/span&gt;ast)&#xD;
&lt;font face="Consolas"&gt;{&#xD;
    m_currentType = m_module.DefineType(&#xD;
        ast.Type.Name, &lt;span style="color: #2b91af"&gt;TypeAttributes&lt;/span&gt;.Class | &lt;span style="color: #2b91af"&gt;TypeAttributes&lt;/span&gt;.Abstract | &lt;span style="color: #2b91af"&gt;TypeAttributes&lt;/span&gt;.Sealed);&#xD;
    m_currentMethod = m_currentType.DefineMethod(&#xD;
        &lt;span style="color: #a31515"&gt;"Main"&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;MethodAttributes&lt;/span&gt;.Public | &lt;span style="color: #2b91af"&gt;MethodAttributes&lt;/span&gt;.Static, &lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: blue"&gt;void&lt;/span&gt;), &lt;span style="color: blue"&gt;new&lt;/span&gt;[] { &lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: blue"&gt;string&lt;/span&gt;[]) });&#xD;
&#xD;
    m_ilgen = m_currentMethod.GetILGenerator();&#xD;
&#xD;
    &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;s &lt;span style="color: blue"&gt;in &lt;/span&gt;ast.Statements)&#xD;
    {&#xD;
        Visit(s);&#xD;
    }&#xD;
&#xD;
    m_ilgen.Emit(&lt;span style="color: #2b91af"&gt;OpCodes&lt;/span&gt;.Ret);&#xD;
&#xD;
    m_currentType.CreateType();&#xD;
    m_mainMethod = m_currentMethod;&#xD;
    &lt;span style="color: blue"&gt;return &lt;/span&gt;ast;&#xD;
}&#xD;
&lt;/font&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;搞定类和方法之后，就开始要生成方法体的代码了。这一部分最主要的翻译对象是语句和表达式，有一个要注意的规则：&lt;/p&gt;&#xD;
&lt;ol&gt;&#xD;
&lt;li&gt;表达式执行之后，该表达式的结果应当压入运算栈。 &#xD;
&lt;li&gt;语句执行后，运算栈应当被清空。&lt;/li&gt;&lt;/ol&gt;&#xD;
&lt;p&gt;如果不满足上述规则，生成的代码就很有可能是错的，要非常小心。下面展示两个最基本的语句——if else和while的生成方法。&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;public override &lt;/span&gt;&lt;span style="color: #2b91af"&gt;AstNode &lt;/span&gt;VisitIfElse(&lt;span style="color: #2b91af"&gt;IfElse &lt;/span&gt;ast)&#xD;
{&#xD;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;ifBlock = m_ilgen.DefineLabel();&#xD;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;elseBlock = m_ilgen.DefineLabel();&#xD;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;endif = m_ilgen.DefineLabel();&#xD;
&#xD;
    Visit(ast.Condition);&#xD;
    &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//the e-stack should have a bool value&#xD;
    &lt;/span&gt;m_ilgen.Emit(&lt;span style="color: #2b91af"&gt;OpCodes&lt;/span&gt;.Brfalse, elseBlock);&#xD;
&#xD;
    &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//if block&#xD;
    &lt;/span&gt;m_ilgen.MarkLabel(ifBlock);&#xD;
    Visit(ast.TruePart);&#xD;
    m_ilgen.Emit(&lt;span style="color: #2b91af"&gt;OpCodes&lt;/span&gt;.Br, endif);&#xD;
&#xD;
    &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//elseblock&#xD;
    &lt;/span&gt;m_ilgen.MarkLabel(elseBlock);&#xD;
    Visit(ast.FalsePart);&#xD;
&#xD;
    &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//after if&#xD;
    &lt;/span&gt;m_ilgen.MarkLabel(endif);&#xD;
&#xD;
    &lt;span style="color: blue"&gt;return &lt;/span&gt;ast;&#xD;
}&#xD;
&#xD;
&lt;span style="color: blue"&gt;public override &lt;/span&gt;&lt;span style="color: #2b91af"&gt;AstNode &lt;/span&gt;VisitWhile(&lt;span style="color: #2b91af"&gt;While &lt;/span&gt;ast)&#xD;
{&#xD;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;beforeWhile = m_ilgen.DefineLabel();&#xD;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;afterWhile = m_ilgen.DefineLabel();&#xD;
&#xD;
    m_ilgen.MarkLabel(beforeWhile);&#xD;
&#xD;
    Visit(ast.Condition);&#xD;
    &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//the e-stack should have a bool value&#xD;
    &lt;/span&gt;m_ilgen.Emit(&lt;span style="color: #2b91af"&gt;OpCodes&lt;/span&gt;.Brfalse, afterWhile);&#xD;
&#xD;
    Visit(ast.LoopBody);&#xD;
&#xD;
    m_ilgen.Emit(&lt;span style="color: #2b91af"&gt;OpCodes&lt;/span&gt;.Br, beforeWhile);&#xD;
    m_ilgen.MarkLabel(afterWhile);&#xD;
&#xD;
    &lt;span style="color: blue"&gt;return &lt;/span&gt;ast;&#xD;
}&#xD;
&lt;/font&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;这里if语句采用的是brfalse指令。实际上CIL中有许多条件分支语句，如blt、bge等等，可直接翻译if ( a &amp;gt; b )这样的结构，效率更高。此次我采用偷懒的做法，全都用clt, cgt这样的有返回值的指令来计算大于小于等比较运算，但后统一用brfalse来执行条件跳转。上面这段代码还展示了Label在Emit API中的使用方法。翻译赋值语句和数组赋值语句时要注意，为本地变量、本地参数或类的字段赋值时采用的指令和栈转换动作均有所不同，需要分别考虑。比如ldfld之前必须先将目标对象压栈，如果是this的话应该用ldarg.0指令（实例方法默认第0个参数是this引用）&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;再来演示两个基本表达式的翻译，二元运算符和方法调用：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;public override &lt;/span&gt;&lt;span style="color: #2b91af"&gt;AstNode &lt;/span&gt;VisitBinary(&lt;span style="color: #2b91af"&gt;Binary &lt;/span&gt;ast)&#xD;
{&#xD;
    &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//push operands&#xD;
    &lt;/span&gt;Visit(ast.Left);&#xD;
    Visit(ast.Right);&#xD;
&#xD;
    &lt;span style="color: blue"&gt;switch &lt;/span&gt;(ast.Operator)&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;case &lt;/span&gt;&lt;span style="color: #2b91af"&gt;BinaryOperator&lt;/span&gt;.Add:&#xD;
            m_ilgen.Emit(&lt;span style="color: #2b91af"&gt;OpCodes&lt;/span&gt;.Add);&#xD;
            &lt;span style="color: blue"&gt;break&lt;/span&gt;;&#xD;
        &lt;span style="color: blue"&gt;case &lt;/span&gt;&lt;span style="color: #2b91af"&gt;BinaryOperator&lt;/span&gt;.Substract:&#xD;
            m_ilgen.Emit(&lt;span style="color: #2b91af"&gt;OpCodes&lt;/span&gt;.Sub);&#xD;
            &lt;span style="color: blue"&gt;break&lt;/span&gt;;&#xD;
        &lt;span style="color: blue"&gt;case &lt;/span&gt;&lt;span style="color: #2b91af"&gt;BinaryOperator&lt;/span&gt;.Multiply:&#xD;
            m_ilgen.Emit(&lt;span style="color: #2b91af"&gt;OpCodes&lt;/span&gt;.Mul);&#xD;
            &lt;span style="color: blue"&gt;break&lt;/span&gt;;&#xD;
        &lt;span style="color: blue"&gt;case &lt;/span&gt;&lt;span style="color: #2b91af"&gt;BinaryOperator&lt;/span&gt;.Divide:&#xD;
            m_ilgen.Emit(&lt;span style="color: #2b91af"&gt;OpCodes&lt;/span&gt;.Div);&#xD;
            &lt;span style="color: blue"&gt;break&lt;/span&gt;;&#xD;
        &lt;span style="color: blue"&gt;case &lt;/span&gt;&lt;span style="color: #2b91af"&gt;BinaryOperator&lt;/span&gt;.Less:&#xD;
            m_ilgen.Emit(&lt;span style="color: #2b91af"&gt;OpCodes&lt;/span&gt;.Clt);&#xD;
            &lt;span style="color: blue"&gt;break&lt;/span&gt;;&#xD;
        &lt;span style="color: blue"&gt;case &lt;/span&gt;&lt;span style="color: #2b91af"&gt;BinaryOperator&lt;/span&gt;.Greater:&#xD;
            m_ilgen.Emit(&lt;span style="color: #2b91af"&gt;OpCodes&lt;/span&gt;.Cgt);&#xD;
            &lt;span style="color: blue"&gt;break&lt;/span&gt;;&#xD;
        &lt;span style="color: blue"&gt;case &lt;/span&gt;&lt;span style="color: #2b91af"&gt;BinaryOperator&lt;/span&gt;.Equal:&#xD;
            m_ilgen.Emit(&lt;span style="color: #2b91af"&gt;OpCodes&lt;/span&gt;.Ceq);&#xD;
            &lt;span style="color: blue"&gt;break&lt;/span&gt;;&#xD;
        &lt;span style="color: blue"&gt;case &lt;/span&gt;&lt;span style="color: #2b91af"&gt;BinaryOperator&lt;/span&gt;.LogicalAnd:&#xD;
            m_ilgen.Emit(&lt;span style="color: #2b91af"&gt;OpCodes&lt;/span&gt;.And);&#xD;
            &lt;span style="color: blue"&gt;break&lt;/span&gt;;&#xD;
        &lt;span style="color: blue"&gt;case &lt;/span&gt;&lt;span style="color: #2b91af"&gt;BinaryOperator&lt;/span&gt;.LogicalOr:&#xD;
            m_ilgen.Emit(&lt;span style="color: #2b91af"&gt;OpCodes&lt;/span&gt;.Or);&#xD;
            &lt;span style="color: blue"&gt;break&lt;/span&gt;;&#xD;
        &lt;span style="color: blue"&gt;default&lt;/span&gt;:&#xD;
            m_ilgen.Emit(&lt;span style="color: #2b91af"&gt;OpCodes&lt;/span&gt;.Pop);&#xD;
            m_ilgen.Emit(&lt;span style="color: #2b91af"&gt;OpCodes&lt;/span&gt;.Pop);&#xD;
            m_ilgen.Emit(&lt;span style="color: #2b91af"&gt;OpCodes&lt;/span&gt;.Ldc_I4_0);&#xD;
            &lt;span style="color: blue"&gt;break&lt;/span&gt;;&#xD;
    }&#xD;
    &lt;span style="color: blue"&gt;return &lt;/span&gt;ast;&#xD;
}&#xD;
&#xD;
&lt;span style="color: blue"&gt;public override &lt;/span&gt;&lt;span style="color: #2b91af"&gt;AstNode &lt;/span&gt;VisitCall(&lt;span style="color: #2b91af"&gt;Call &lt;/span&gt;ast)&#xD;
{&#xD;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;methodRInfo = GetClrMethod(ast.Method.MethodInfo);&#xD;
&#xD;
    &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//push target object&#xD;
    &lt;/span&gt;Visit(ast.Target);&#xD;
&#xD;
    &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//push arguments&#xD;
    &lt;/span&gt;&lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;arg &lt;span style="color: blue"&gt;in &lt;/span&gt;ast.Arguments)&#xD;
    {&#xD;
        Visit(arg);&#xD;
    }&#xD;
&#xD;
    m_ilgen.EmitCall(&lt;span style="color: #2b91af"&gt;OpCodes&lt;/span&gt;.Call, methodRInfo, &lt;span style="color: blue"&gt;null&lt;/span&gt;);&#xD;
&#xD;
    &lt;span style="color: blue"&gt;return &lt;/span&gt;ast;&#xD;
}&#xD;
&lt;/font&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;注意这里翻译&amp;amp;&amp;amp;和||运算符时没有生成“短路”操作，因此与C#的语义稍有不同。如果要支持短路也非常容易，大家可以亲自实验一下。翻译二元运算符时，如果语义分析正确无误，不应该进入default分支。所以在此只进行一种错误处理的逻辑，它仍然要保持运算栈的平衡。翻译方法调用时，应当先将方法的目标对象压栈，然后从左到右依次压入每个实参，最后调用call指令完成调用。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;所有的TypeBuilder都调用CreateType之后，最后调用AssemblyBuilder.Save方法，就可以将目标程序集写入磁盘了！&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;public void &lt;/span&gt;Create(Ast.&lt;span style="color: #2b91af"&gt;AstNode &lt;/span&gt;ast, &lt;span style="color: blue"&gt;string &lt;/span&gt;url)&#xD;
{&#xD;
    Visit(ast);&#xD;
&#xD;
    &lt;span style="color: #2b91af"&gt;Debug&lt;/span&gt;.Assert(m_assembly != &lt;span style="color: blue"&gt;null&lt;/span&gt;);&#xD;
&#xD;
    m_assembly.SetEntryPoint(m_mainMethod, &lt;span style="color: #2b91af"&gt;PEFileKinds&lt;/span&gt;.ConsoleApplication);&#xD;
    m_assembly.Save(url);&#xD;
}&#xD;
&lt;/font&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;现在终于可以试试看了，我们来编译一段miniSharp代码试试：（阶乘计算）&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;span style="color: #a31515"&gt;&lt;font face="Consolas"&gt;static class 程序入口&#xD;
{&#xD;
    //中文注释&#xD;
    public static void Main(string[] args)&#xD;
    {&#xD;
        Fac o;&#xD;
        o = new Fac();&#xD;
        System.Console.WriteLine(o.ComputeFac(8));&#xD;
    }&#xD;
}&#xD;
&#xD;
class Fac&#xD;
{&#xD;
    public int ComputeFac(int num)&#xD;
    {&#xD;
        int num_aux;&#xD;
        if (num &amp;lt; 1)&#xD;
            num_aux = 1;&#xD;
        else&#xD;
            num_aux = num * (this.ComputeFac(num - 1));&#xD;
&#xD;
        return num_aux;&#xD;
    }&#xD;
}&#xD;
&lt;/font&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;生成的程序集：&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/Ninputer/201108/201108021445412106.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/Ninputer/201108/201108021445465166.png" width="621" height="352"&gt;&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;运行结果：&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/Ninputer/201108/20110802145518281.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/Ninputer/201108/20110802145522898.png" width="576" height="231"&gt;&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;看到自己的编译器正确地编译源代码，是否觉得很有成就感呢？如果只想做一个托管编程语言，那么生成CIL就是最后一步了。但是CLR帮我们做的实在太多了，不能满足我们的求知欲。所以，下一阶段我们将亲手实现从中间语言到目标机器代码的编译器后端部分。从下一篇开始本系列的间隔时间会变得比较长而且不确定，因为我自己也需要一边学习一边实践。&lt;/p&gt;&#xD;
&lt;p&gt;希望大家继续关注我的VBF项目：&lt;a href="https://github.com/Ninputer/VBF"&gt;https://github.com/Ninputer/VBF&lt;/a&gt; 和我的微博：&lt;a href="http://weibo.com/ninputer"&gt;http://weibo.com/ninputer&lt;/a&gt; 多谢大家支持！&lt;/p&gt;&lt;img src="http://www.cnblogs.com/Ninputer/aggbug/2120435.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/Ninputer/archive/2011/08/02/2120435.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/Ninputer/archive/2011/07/22/2112030.html</id><title type="text">自己动手开发编译器（十一）语义分析</title><summary type="text">上回我们已经用VBF的Parsers.Combinators库生成了miniSharp的语法分析器，并且能够将miniSharp的源代码翻译成抽象语法树（AST）。这一回我们要继续进行下一步——语义分析。就目前大家接触的编程语言，如C#、VB、C++来说，语义分析是编译器前端最复杂的部分。因为这些编程语言的语义都非常复杂。语义分析不像之前词法分析、语法分析那样，有一些特定的工具来帮助。这一部分通常...</summary><published>2011-07-22T03:15:00Z</published><updated>2011-07-22T03:15:00Z</updated><author><name>装配脑袋</name><uri>http://www.cnblogs.com/Ninputer/</uri></author><link rel="alternate" href="http://www.cnblogs.com/Ninputer/archive/2011/07/22/2112030.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/Ninputer/archive/2011/07/22/2112030.html"/><content type="html">&lt;p&gt;上回我们已经用VBF的Parsers.Combinators库生成了miniSharp的语法分析器，并且能够将miniSharp的源代码翻译成抽象语法树（AST）。这一回我们要继续进行下一步——语义分析。就目前大家接触的编程语言，如C#、VB、C++来说，语义分析是编译器前端最复杂的部分。因为这些编程语言的语义都非常复杂。语义分析不像之前词法分析、语法分析那样，有一些特定的工具来帮助。这一部分通常都是要纯手工写代码来完成。我们的miniSharp语义因为已经高度简化，它的语义分析可以说比C#要容易一个数量级。我们只会在选定方法重载的时候见识一下C#复杂语义的冰山一角。&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;所谓编程语言语义，就是这段代码实际的含义。编程语言的代码必须有绝对明确的含义，这样人们才能让程序做自己想做的事情。比如最简单的一行代码：a = 1; 它的语义是“将32位整型常量存储到变量a中”。首先我们对“1”有明确的定义，它是32位有符号整型字面量，这里“32位有符号整型”就是表达式“1”的&lt;strong&gt;类型&lt;/strong&gt;。其次，这句话成为合法的编程语言，32位整型常量必须能够隐式转换为a的类型。假设a就是int型变量，那么这条语句就直接将1存储到a所在内存里。如果a是浮点数类型的，那么这句话就隐含着将整型常量1转换为浮点类型的步骤。在语义分析中，&lt;strong&gt;类型检查&lt;/strong&gt;是贯穿始终的一个步骤。像miniSharp这样的静态类型语言，类型检查通常要做到：&lt;/p&gt; &lt;ol&gt; &lt;li&gt;判定每一个表达式的声明类型  &lt;li&gt;判定每一个字段、形式参数、变量声明的类型  &lt;li&gt;判断每一次赋值、传参数时，是否存在合法的隐式类型转换  &lt;li&gt;判断一元和二元运算符左右两侧的类型是否合法（比如+不就不能在bool和int之间进行）  &lt;li&gt;将所有要发生的隐式类型转换明确化&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;要进行以上操作，需要一个表存储所有已知的类型。如果引用了外部程序集，则也需要将外部程序集中的类型信息放到表中。类型信息包括类型的名字、父类（如果有的话）、成员以及相互隐式转换的规则。我们用如下的类来表示一个miniSharp自定义类型：&lt;/p&gt; &lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;CodeClassType &lt;/span&gt;: &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: #2b91af"&gt;TypeBase&#xD;
&lt;/span&gt;{&#xD;
    &lt;span style="color: blue"&gt;public bool &lt;/span&gt;IsStatic { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;CodeClassType &lt;/span&gt;BaseType { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Collection&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Method&lt;/span&gt;&amp;gt; Methods { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Collection&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Method&lt;/span&gt;&amp;gt; StaticMethods { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;VariableCollection&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Field&lt;/span&gt;&amp;gt; Fields { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;CodeClassType()&#xD;
    {&#xD;
        Methods = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Collection&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Method&lt;/span&gt;&amp;gt;();&#xD;
        StaticMethods = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Collection&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Method&lt;/span&gt;&amp;gt;();&#xD;
        Fields = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;VariableCollection&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Field&lt;/span&gt;&amp;gt;();&#xD;
    }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public override bool &lt;/span&gt;IsAssignableFrom(&lt;span style="color: #2b91af"&gt;TypeBase &lt;/span&gt;type)&#xD;
    {&#xD;
        &lt;span style="color: #2b91af"&gt;CodeClassType &lt;/span&gt;otherClassType = type &lt;span style="color: blue"&gt;as &lt;/span&gt;&lt;span style="color: #2b91af"&gt;CodeClassType&lt;/span&gt;;&#xD;
&#xD;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(otherClassType == &lt;span style="color: blue"&gt;null&lt;/span&gt;)&#xD;
        {&#xD;
            &lt;span style="color: blue"&gt;return false&lt;/span&gt;;&#xD;
        }&#xD;
&#xD;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(otherClassType == &lt;span style="color: blue"&gt;this&lt;/span&gt;)&#xD;
        {&#xD;
            &lt;span style="color: blue"&gt;return true&lt;/span&gt;;&#xD;
        }&#xD;
        &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;else&#xD;
        &lt;/span&gt;{&#xD;
            &lt;span style="color: blue"&gt;return &lt;/span&gt;IsAssignableFrom(otherClassType.BaseType);&#xD;
        }&#xD;
    }&#xD;
&#xD;
}&lt;/font&gt;&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;miniSharp不支持显式类型转换，而唯一支持的隐式类型转换是子类引用到父类引用的转换。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;除了自定义类型之外，我们还需要表示数组类型和基元类型（int和bool），简陋地如下处理：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;PrimaryType &lt;/span&gt;: &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: #2b91af"&gt;TypeBase&#xD;
&lt;/span&gt;{&#xD;
    &lt;span style="color: blue"&gt;public static readonly &lt;/span&gt;&lt;span style="color: #2b91af"&gt;PrimaryType &lt;/span&gt;Int = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;PrimaryType&lt;/span&gt;() { Name = &lt;span style="color: #a31515"&gt;"int" &lt;/span&gt;};&#xD;
    &lt;span style="color: blue"&gt;public static readonly &lt;/span&gt;&lt;span style="color: #2b91af"&gt;PrimaryType &lt;/span&gt;Boolean = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;PrimaryType&lt;/span&gt;() { Name = &lt;span style="color: #a31515"&gt;"bool" &lt;/span&gt;};&#xD;
    &lt;span style="color: blue"&gt;public static readonly &lt;/span&gt;&lt;span style="color: #2b91af"&gt;PrimaryType &lt;/span&gt;String = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;PrimaryType&lt;/span&gt;() { Name = &lt;span style="color: #a31515"&gt;"string" &lt;/span&gt;};&#xD;
    &lt;span style="color: blue"&gt;public static readonly &lt;/span&gt;&lt;span style="color: #2b91af"&gt;PrimaryType &lt;/span&gt;Void = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;PrimaryType&lt;/span&gt;() { Name = &lt;span style="color: #a31515"&gt;"void" &lt;/span&gt;};&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public static readonly &lt;/span&gt;&lt;span style="color: #2b91af"&gt;PrimaryType &lt;/span&gt;Unknown = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;PrimaryType&lt;/span&gt;() { Name = &lt;span style="color: blue"&gt;null &lt;/span&gt;};&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public override bool &lt;/span&gt;IsAssignableFrom(&lt;span style="color: #2b91af"&gt;TypeBase &lt;/span&gt;type)&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(&lt;span style="color: blue"&gt;this &lt;/span&gt;== type)&#xD;
        {&#xD;
            &lt;span style="color: blue"&gt;return true&lt;/span&gt;;&#xD;
        }&#xD;
        &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;else&#xD;
        &lt;/span&gt;{&#xD;
            &lt;span style="color: blue"&gt;return false&lt;/span&gt;;&#xD;
        }&#xD;
&#xD;
    }&#xD;
}&#xD;
&lt;/font&gt;&lt;/pre&gt;&lt;br&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ArrayType &lt;/span&gt;: &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: #2b91af"&gt;TypeBase&#xD;
&lt;/span&gt;{&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;TypeBase &lt;/span&gt;ElementType { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public static readonly &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ArrayType &lt;/span&gt;IntArray = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ArrayType&lt;/span&gt;() { Name = &lt;span style="color: #a31515"&gt;"int[]"&lt;/span&gt;, ElementType = &lt;span style="color: #2b91af"&gt;PrimaryType&lt;/span&gt;.Int };&#xD;
    &lt;span style="color: blue"&gt;public static readonly &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ArrayType &lt;/span&gt;StrArray = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ArrayType&lt;/span&gt;() { Name = &lt;span style="color: #a31515"&gt;"string[]"&lt;/span&gt;, ElementType = &lt;span style="color: #2b91af"&gt;PrimaryType&lt;/span&gt;.String };&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public override bool &lt;/span&gt;IsAssignableFrom(&lt;span style="color: #2b91af"&gt;TypeBase &lt;/span&gt;type)&#xD;
    {&#xD;
        &lt;span style="color: #2b91af"&gt;CodeClassType &lt;/span&gt;elementClassType = ElementType &lt;span style="color: blue"&gt;as &lt;/span&gt;&lt;span style="color: #2b91af"&gt;CodeClassType&lt;/span&gt;;&#xD;
        &lt;span style="color: #2b91af"&gt;ArrayType &lt;/span&gt;arrayType = type &lt;span style="color: blue"&gt;as &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ArrayType&lt;/span&gt;;&#xD;
&#xD;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(elementClassType != &lt;span style="color: blue"&gt;null &lt;/span&gt;&amp;amp;&amp;amp; arrayType != &lt;span style="color: blue"&gt;null&lt;/span&gt;)&#xD;
        {&#xD;
            &lt;span style="color: blue"&gt;return &lt;/span&gt;elementClassType.IsAssignableFrom(arrayType.ElementType);&#xD;
        }&#xD;
&#xD;
        &lt;span style="color: blue"&gt;return false&lt;/span&gt;;&#xD;
    }&#xD;
}&lt;/font&gt;&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;实际上C#会将int和bool直接映射到System.Int32以及System.Boolean结构体。我们的miniSharp不仅仅要翻译成托管代码，所以并没有采用这个规定，但在生成IL的时候仍然做这样的特殊处理。最后因为miniSharp并不支持引用外部程序集，所以我也没有将类型表独立出来，而是将类型信息存储在每个表示class的语法树节点上，以方便语义分析时访问。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;语义分析的第二个主要任务是找到所有标识符的定义。标识符在miniSharp里主要有：类名、字段名、方法名、参数名和本地变量名。遇到每个名称，我们必须解析出标识符表示的类、方法或字段的定义。比如下面这段代码：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;span style="color: blue"&gt;&lt;font face="Consolas"&gt;class &lt;/font&gt;&lt;/span&gt;&lt;font face="Consolas"&gt;&lt;span style="color: #2b91af"&gt;MyClass&#xD;
&lt;/span&gt;{&#xD;
    &lt;span style="color: blue"&gt;int &lt;/span&gt;a;&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public int &lt;/span&gt;Foo()&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;int &lt;/span&gt;a;&#xD;
        a = 1;&#xD;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(&lt;span style="color: blue"&gt;this&lt;/span&gt;.a &amp;gt; 0)&#xD;
        {&#xD;
            &lt;span style="color: blue"&gt;return this&lt;/span&gt;.a;&#xD;
        }&#xD;
        &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;else&#xD;
        &lt;/span&gt;{&#xD;
            &lt;span style="color: blue"&gt;return &lt;/span&gt;a;&#xD;
        }&#xD;
    }&#xD;
}&lt;/font&gt;&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;有一个字段叫a，在过程Foo中又定义了一个同名局部变量a。那么过程内的局部变量a就会覆盖字段的a，这句话的意思是标识符“a”在Foo中将表示局部变量，而不是同名字段。在语义分析里，我们遇到每一个可能代表变量的标识符时，都要按照一套预先设定的规则来寻找其定义。比如按照如下顺序：&lt;/p&gt;&#xD;
&lt;ol&gt;&#xD;
&lt;li&gt;搜索当前的本地符号表，其中包括当前作用域中定义的本地变量和方法参数 &#xD;
&lt;li&gt;搜索当前类的字段&lt;/li&gt;&lt;/ol&gt;&#xD;
&lt;p&gt;如果类的字段不仅仅是private的话，如果类还允许定义属性的话，这里的规则还要多好几条。所幸miniSharp只用以上两条就够了。我们看看怎么表示本地符号表。&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;VariableCollection&lt;/span&gt;&amp;lt;T&amp;gt; : &lt;span style="color: #2b91af"&gt;KeyedCollection&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;string&lt;/span&gt;, T&amp;gt; &lt;span style="color: blue"&gt;where &lt;/span&gt;T : &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: #2b91af"&gt;VariableInfo&#xD;
&lt;/span&gt;{&#xD;
    &lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Stack&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;HashSet&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;string&lt;/span&gt;&amp;gt;&amp;gt; m_levelStack;&#xD;
    &lt;span style="color: blue"&gt;public int &lt;/span&gt;m_Levels;&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;VariableCollection()&#xD;
    {&#xD;
        m_Levels = 0;&#xD;
        m_levelStack = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Stack&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;HashSet&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;string&lt;/span&gt;&amp;gt;&amp;gt;();&#xD;
    }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;protected override string &lt;/span&gt;GetKeyForItem(T item)&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;return &lt;/span&gt;item.Name;&#xD;
    }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public void &lt;/span&gt;PushLevel()&#xD;
    {&#xD;
        m_levelStack.Push(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HashSet&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;string&lt;/span&gt;&amp;gt;());&#xD;
        m_Levels++;&#xD;
    }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public void &lt;/span&gt;PopLevel()&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(m_Levels == 0)&#xD;
        {&#xD;
            &lt;span style="color: blue"&gt;throw new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;InvalidOperationException&lt;/span&gt;();&#xD;
        }&#xD;
&#xD;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;keysInLevel = m_levelStack.Pop();&#xD;
        m_Levels--;&#xD;
&#xD;
        &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;key &lt;span style="color: blue"&gt;in &lt;/span&gt;keysInLevel)&#xD;
        {&#xD;
            Remove(key);&#xD;
        }&#xD;
    }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;protected override void &lt;/span&gt;InsertItem(&lt;span style="color: blue"&gt;int &lt;/span&gt;index, T item)&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;base&lt;/span&gt;.InsertItem(index, item);&#xD;
&#xD;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(m_Levels &amp;gt; 0)&#xD;
        {&#xD;
            &lt;span style="color: blue"&gt;var &lt;/span&gt;keysInLevel = m_levelStack.Peek();&#xD;
            keysInLevel.Add(GetKeyForItem(item));&#xD;
        }&#xD;
&#xD;
&#xD;
    }&#xD;
}&#xD;
&lt;/font&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;为了简便处理这里所用的数据结构都比较粗糙。但基本思想是使用一个Stack，在进入一个新的作用域（大括号包围的语句块）时压入一个新的HashSet，储存这一作用域内声明的变量。当作用域结束时弹出一个HashSet，这个作用域内的变量就从表里删除了。所以，miniSharp允许两个不互相嵌套的语句块内定义同名变量，但不允许在同一个方法内的语句块内覆盖语句块外定义的变量或形式参数。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;接下来我们要讨论方法重载选取的问题。这是miniSharp中唯一一个稍微有些复杂性的语义。miniSharp允许同一个类多个方法具有相同的方法名，只要他们的形式参数表的类型不完全一样即可。而判断一个方法调用表达式到底调用的是哪个方法，一共分为以下几个步骤。&lt;/p&gt;&#xD;
&lt;ol&gt;&#xD;
&lt;li&gt;第一步，找到&lt;strong&gt;当前类&lt;/strong&gt;中所有&lt;strong&gt;签名相符&lt;/strong&gt;的方法。miniSharp和C#一样，当前类中的方法具有比父类更高的优先级。而VB则采取当前类和父类相同优先级（使用Overloads关键字时）。所以miniSharp也要先在当前类中搜索合适的候选。第二个条件是签名相符，它的定义是方法调用的表达式与候选方法的名称相同，参数列表长度一致，并且方法调用的表达式列表中的每一个表达式的类型，都能隐式转换成候选方法参数表中对应位置参数的类型。稍微形式化一下，就是方法F(T1, T2, T3,…,Tn)是调用表达式C(E1, E2, E3,… Em)的签名相符候选方法的条件是F.Name = C，m = n并且对所有i从1到n满足Ti.IsAssignableFrom(typeof(Ei))。 &#xD;
&lt;li&gt;第二步，所有签名相符的候选方法中，找到一个&lt;strong&gt;最佳&lt;/strong&gt;候选。如果有两个候选方法P(P1, P2,…,Pn)和Q(Q1, Q2,…,Qn)，那么我们说P比Q&lt;strong&gt;更佳&lt;/strong&gt;当且仅当：P的每一个参数类型都比Q的相应参数类型更好或至少一样好，同时Q的每一个参数类型都&lt;strong&gt;不&lt;/strong&gt;比P的相应参数类型更好。如果P和Q各自有一些参数类型比对方更好，那么就视为P和Q条件一致，无法做出判断（有歧义）。 &#xD;
&lt;li&gt;调用表达式列表项E所对应的候选方法参数类型TP比TQ&lt;strong&gt;更好&lt;/strong&gt;意味着：TP与typeof(E)相等但TQ与typeof(E)不相等；或者TQ.IsAssignableFrom(TP)，这意味着TP比TQ更“具体”一些。如果TP和TQ之间无法相互隐式转换，或者两者是相同的类型，则视为无法区分。 &#xD;
&lt;li&gt;如果在当前类中没有符合条件的候选，则对父类重复以上步骤。&lt;/li&gt;&lt;/ol&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;真正C#的方法重载判断大体上也是这个步骤，但还要更加复杂得多。因为C#还有param数组型参数，可选参数，命名参数，泛型方法等语法。这里C#的Spec整整写了好几页纸来描述完整的规则。初看起来这段规则转换成代码很难写，所以我采用了一种取巧的方法：定义一个比较两个候选参数好坏的Comparer类，然后用Order By的方式对候选参数进行排序。Comparer类如下：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MethodOverloadingComparer &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IComparer&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Method&lt;/span&gt;&amp;gt;&#xD;
{&#xD;
    &lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IList&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Expression&lt;/span&gt;&amp;gt; m_expressionList;&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;MethodOverloadingComparer(&lt;span style="color: #2b91af"&gt;IList&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Expression&lt;/span&gt;&amp;gt; expressions)&#xD;
    {&#xD;
        &lt;span style="color: #2b91af"&gt;Debug&lt;/span&gt;.Assert(expressions != &lt;span style="color: blue"&gt;null&lt;/span&gt;);&#xD;
        m_expressionList = expressions;&#xD;
    }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public int &lt;/span&gt;Compare(&lt;span style="color: #2b91af"&gt;Method &lt;/span&gt;x, &lt;span style="color: #2b91af"&gt;Method &lt;/span&gt;y)&#xD;
    {&#xD;
        &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//step 1. find one with better conversion.&#xD;
        &lt;/span&gt;&lt;span style="color: blue"&gt;int &lt;/span&gt;lastComparisonResult = 0;&#xD;
        &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;i = 0; i &amp;lt; m_expressionList.Count; i++)&#xD;
        {&#xD;
            &lt;span style="color: blue"&gt;int &lt;/span&gt;result = CompareConversion(x.Parameters[i].Type, y.Parameters[i].Type, m_expressionList[i]);&#xD;
&#xD;
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(lastComparisonResult &amp;lt; 0 &amp;amp;&amp;amp; result &amp;gt; 0 || lastComparisonResult &amp;gt; 0 &amp;amp;&amp;amp; result &amp;lt; 0)&#xD;
            {&#xD;
                &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//none is better&#xD;
                &lt;/span&gt;&lt;span style="color: blue"&gt;return &lt;/span&gt;0;&#xD;
            }&#xD;
            &lt;span style="color: blue"&gt;else if &lt;/span&gt;(result != 0)&#xD;
            {&#xD;
                lastComparisonResult = result;&#xD;
            }&#xD;
        }&#xD;
&#xD;
        &lt;span style="color: blue"&gt;return &lt;/span&gt;lastComparisonResult;&#xD;
    }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;private int &lt;/span&gt;CompareConversion(&lt;span style="color: #2b91af"&gt;TypeBase &lt;/span&gt;leftTarget, &lt;span style="color: #2b91af"&gt;TypeBase &lt;/span&gt;rightTarget, &lt;span style="color: #2b91af"&gt;Expression &lt;/span&gt;source)&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(leftTarget == rightTarget)&#xD;
        {&#xD;
            &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//same type, no better one&#xD;
            &lt;/span&gt;&lt;span style="color: blue"&gt;return &lt;/span&gt;0;&#xD;
        }&#xD;
        &lt;span style="color: blue"&gt;else if &lt;/span&gt;(leftTarget == source.ExpressionType &amp;amp;&amp;amp; rightTarget != source.ExpressionType)&#xD;
        {&#xD;
            &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//left is better;&#xD;
            &lt;/span&gt;&lt;span style="color: blue"&gt;return &lt;/span&gt;-1;&#xD;
        }&#xD;
        &lt;span style="color: blue"&gt;else if &lt;/span&gt;(leftTarget != source.ExpressionType &amp;amp;&amp;amp; rightTarget == source.ExpressionType)&#xD;
        {&#xD;
            &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//right is better;&#xD;
            &lt;/span&gt;&lt;span style="color: blue"&gt;return &lt;/span&gt;1;&#xD;
        }&#xD;
        &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;else&#xD;
        &lt;/span&gt;{&#xD;
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(leftTarget.IsAssignableFrom(rightTarget))&#xD;
            {&#xD;
                &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//right is more specific&#xD;
                &lt;/span&gt;&lt;span style="color: blue"&gt;return &lt;/span&gt;1;&#xD;
            }&#xD;
            &lt;span style="color: blue"&gt;else if&lt;/span&gt;(rightTarget.IsAssignableFrom(leftTarget))&#xD;
            {&#xD;
                &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//left is more specific&#xD;
                &lt;/span&gt;&lt;span style="color: blue"&gt;return &lt;/span&gt;-1;&#xD;
            }&#xD;
            &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;else&#xD;
            &lt;/span&gt;{&#xD;
                &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//both are bad&#xD;
                &lt;/span&gt;&lt;span style="color: blue"&gt;return &lt;/span&gt;0;&#xD;
            }&#xD;
        }&#xD;
    }&#xD;
}&#xD;
&lt;/font&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;最后，我们要将这一系列步骤组合到一起。由于miniSharp的类可以以任何顺序定义，一个类中的方法也可以以任何顺序定义，调用时并不受任何限制。所以我们无法只用一次抽象语法树的遍历来完成语义分析。我采用的做法是分成三次遍历，前两次分别对类的生命和成员的声明进行解析并构建符号表（类型和成员），第三次再对方法体进行解析。这样就可以方便地处理不同顺序定义的问题。总的来说，三次遍历的任务是：&lt;/p&gt;&#xD;
&lt;ol&gt;&#xD;
&lt;li&gt;第一遍：扫描所有class定义，检查有无重名的情况。 &#xD;
&lt;li&gt;第二遍：检查类的基类是否存在，检测是否循环继承；检查所有字段的类型以及是否重名；检查所有方法参数和返回值的类型以及是否重复定义（签名完全一致的情况）。 &#xD;
&lt;li&gt;第三遍：检查所有方法体中语句和表达式的语义。&lt;/li&gt;&lt;/ol&gt;&#xD;
&lt;p&gt;因为上一次抽象语法树的设计已经采用了Visitor模式，所以以上三个阶段的语义分析可以分别写成三个Visitor来进行处理。语义分析模块同时还要报告所有语义错误。下面我给出第一阶段的Visitor实现供大家参考：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;TypeDeclResolver &lt;/span&gt;: &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: #2b91af"&gt;AstVisitor&#xD;
&lt;/span&gt;{&#xD;
    &lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;TypeCollection &lt;/span&gt;m_types;&#xD;
    &lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;CompilationErrorManager &lt;/span&gt;m_errorManager;&#xD;
&#xD;
    &lt;span style="color: blue"&gt;private const int &lt;/span&gt;c_SE_TypeNameDuplicates = 301;&#xD;
    &#xD;
    &lt;span style="color: blue"&gt;public void &lt;/span&gt;DefineErrors()&#xD;
    {&#xD;
        m_errorManager.DefineError(c_SE_TypeNameDuplicates, 0, &lt;span style="color: #2b91af"&gt;CompilationStage&lt;/span&gt;.SemanticAnalysis,&#xD;
            &lt;span style="color: #a31515"&gt;"The program has already defined a type named '{0}'."&lt;/span&gt;);&#xD;
    }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;TypeDeclResolver(&lt;span style="color: #2b91af"&gt;CompilationErrorManager &lt;/span&gt;errorManager)&#xD;
    {&#xD;
        m_errorManager = errorManager;&#xD;
        m_types = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;TypeCollection&lt;/span&gt;();&#xD;
    }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public override &lt;/span&gt;&lt;span style="color: #2b91af"&gt;AstNode &lt;/span&gt;VisitProgram(&lt;span style="color: #2b91af"&gt;Program &lt;/span&gt;ast)&#xD;
    {&#xD;
        Visit(ast.MainClass);&#xD;
&#xD;
        &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;cd &lt;span style="color: blue"&gt;in &lt;/span&gt;ast.Classes)&#xD;
        {&#xD;
            Visit(cd);&#xD;
        }&#xD;
&#xD;
        &lt;span style="color: blue"&gt;return &lt;/span&gt;ast;&#xD;
    }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public override &lt;/span&gt;&lt;span style="color: #2b91af"&gt;AstNode &lt;/span&gt;VisitMainClass(&lt;span style="color: #2b91af"&gt;MainClass &lt;/span&gt;ast)&#xD;
    {&#xD;
        &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//main class must be the first class.&#xD;
        &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Debug&lt;/span&gt;.Assert(m_types.Count == 0);&#xD;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;name = ast.Name.Value;&#xD;
&#xD;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;mainclassType = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;CodeClassType&lt;/span&gt;() { Name = name, IsStatic = &lt;span style="color: blue"&gt;true &lt;/span&gt;};&#xD;
&#xD;
        m_types.Add(mainclassType);&#xD;
        ast.Type = mainclassType;&#xD;
&#xD;
        &lt;span style="color: blue"&gt;return &lt;/span&gt;ast;&#xD;
    }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public override &lt;/span&gt;&lt;span style="color: #2b91af"&gt;AstNode &lt;/span&gt;VisitClassDecl(&lt;span style="color: #2b91af"&gt;ClassDecl &lt;/span&gt;ast)&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;name = ast.Name.Value;&#xD;
&#xD;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(m_types.Contains(name))&#xD;
        {&#xD;
            m_errorManager.AddError(c_SE_TypeNameDuplicates, ast.Name.Span, name);&#xD;
            &lt;span style="color: blue"&gt;return &lt;/span&gt;ast;&#xD;
        }&#xD;
&#xD;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;classType = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;CodeClassType&lt;/span&gt;() { Name = name };&#xD;
&#xD;
        m_types.Add(classType);&#xD;
        ast.Type = classType;&#xD;
&#xD;
        &lt;span style="color: blue"&gt;return &lt;/span&gt;ast;&#xD;
    }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;TypeCollection &lt;/span&gt;Types&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;get &lt;/span&gt;{ &lt;span style="color: blue"&gt;return &lt;/span&gt;m_types; }&#xD;
    }&#xD;
&#xD;
}&#xD;
&lt;/font&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;其中的ErrorManager类是与词法、语法分析阶段共享的语法错误管理类，可以方便地随时定义和保存编译错误。为了减少语义分析的负担，我们规定只有语法分析阶段没有错误才进行语义分析，而且语义分析的三个阶段任何一步有语法错误都可以不再继续执行分析。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;第二个阶段和第三个阶段的代码较长，我就不贴在这里了，大家可以下载我的代码自行观看。在此我只贴一个比较有代表性的Call表达式解析过程，方便大家理解上述方法重载的逻辑（但我还没有仔细进行过测试，所以不保证这段代码完全没有bug）&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;public override &lt;/span&gt;&lt;span style="color: #2b91af"&gt;AstNode &lt;/span&gt;VisitCall(&lt;span style="color: #2b91af"&gt;Call &lt;/span&gt;ast)&#xD;
{&#xD;
    &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;// step 1. resolve each argument&#xD;
    &lt;/span&gt;&lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;argument &lt;span style="color: blue"&gt;in &lt;/span&gt;ast.Arguments)&#xD;
    {&#xD;
        Visit(argument);&#xD;
    }&#xD;
&#xD;
    &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//step 2. resolve object&#xD;
    &lt;/span&gt;Visit(ast.Target);&#xD;
&#xD;
    &lt;span style="color: #2b91af"&gt;CodeClassType &lt;/span&gt;targetType = ast.Target.ExpressionType &lt;span style="color: blue"&gt;as &lt;/span&gt;&lt;span style="color: #2b91af"&gt;CodeClassType&lt;/span&gt;;&#xD;
&#xD;
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(targetType == &lt;span style="color: blue"&gt;null&lt;/span&gt;)&#xD;
    {&#xD;
        m_errorManager.AddError(c_SE_MethodMissing, ast.Method.MethodName.Span, ast.Method.MethodName.Value);&#xD;
        ast.ExpressionType = &lt;span style="color: #2b91af"&gt;PrimaryType&lt;/span&gt;.Unknown;&#xD;
        &lt;span style="color: blue"&gt;return &lt;/span&gt;ast;&#xD;
    }&#xD;
&#xD;
    &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//step 3. resolve method&#xD;
    &lt;/span&gt;ResolveMethod(ast, targetType);&#xD;
&#xD;
    &lt;span style="color: green"&gt;//step 4. TODO: add type conversion node to arg implicit conversions&lt;/span&gt;&#xD;
&#xD;
    &lt;span style="color: blue"&gt;return &lt;/span&gt;ast;&#xD;
}&#xD;
&#xD;
&lt;span style="color: blue"&gt;private void &lt;/span&gt;ResolveMethod(&lt;span style="color: #2b91af"&gt;Call &lt;/span&gt;ast, &lt;span style="color: #2b91af"&gt;CodeClassType &lt;/span&gt;targetType)&#xD;
{&#xD;
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(targetType == &lt;span style="color: blue"&gt;null&lt;/span&gt;)&#xD;
    {&#xD;
        m_errorManager.AddError(c_SE_MethodMissing, ast.Method.MethodName.Span, ast.Method.MethodName.Value);&#xD;
        ast.ExpressionType = &lt;span style="color: #2b91af"&gt;PrimaryType&lt;/span&gt;.Unknown;&#xD;
&#xD;
        &lt;span style="color: blue"&gt;return&lt;/span&gt;;&#xD;
    }&#xD;
&#xD;
    &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;// step 1: collect candidates from current type&#xD;
    &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;candidates = (&lt;span style="color: blue"&gt;from &lt;/span&gt;m &lt;span style="color: blue"&gt;in &lt;/span&gt;targetType.Methods&#xD;
                      &lt;span style="color: blue"&gt;where &lt;/span&gt;&lt;span style="color: #2b91af"&gt;String&lt;/span&gt;.Equals(m.Name, ast.Method.MethodName.Value, &lt;span style="color: #2b91af"&gt;StringComparison&lt;/span&gt;.InvariantCulture) &#xD;
                      &amp;amp;&amp;amp; m.Parameters.Count == ast.Arguments.Count&#xD;
                      &lt;span style="color: blue"&gt;select &lt;/span&gt;m).ToArray();&#xD;
&#xD;
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(candidates.Length == 0)&#xD;
    {&#xD;
        ResolveMethod(ast, targetType.BaseType);&#xD;
        &lt;span style="color: blue"&gt;return&lt;/span&gt;;&#xD;
    }&#xD;
&#xD;
    &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;// step 2: remove unqualifed candidates&#xD;
    &lt;/span&gt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Method&lt;/span&gt;&amp;gt; qualifiedCandidates = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Method&lt;/span&gt;&amp;gt;();&#xD;
    &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;candidate &lt;span style="color: blue"&gt;in &lt;/span&gt;candidates)&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;bool &lt;/span&gt;isQualified = &lt;span style="color: blue"&gt;true&lt;/span&gt;;&#xD;
        &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;i = 0; i &amp;lt; candidate.Parameters.Count; i++)&#xD;
        {&#xD;
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(!candidate.Parameters[i].Type.IsAssignableFrom(ast.Arguments[i].ExpressionType))&#xD;
            {&#xD;
                isQualified = &lt;span style="color: blue"&gt;false&lt;/span&gt;;&#xD;
                &lt;span style="color: blue"&gt;break&lt;/span&gt;;&#xD;
            }&#xD;
        }&#xD;
&#xD;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(isQualified) qualifiedCandidates.Add(candidate);&#xD;
    }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(qualifiedCandidates.Count == 0)&#xD;
    {&#xD;
        ResolveMethod(ast, targetType.BaseType);&#xD;
        &lt;span style="color: blue"&gt;return&lt;/span&gt;;&#xD;
    }&#xD;
&#xD;
    &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;// step 3: choose a "best" one&#xD;
    &lt;/span&gt;&lt;span style="color: blue"&gt;if &lt;/span&gt;(qualifiedCandidates.Count &amp;gt; 1)&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;comparer = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MethodOverloadingComparer&lt;/span&gt;(ast.Arguments);&#xD;
        qualifiedCandidates.Sort(comparer);&#xD;
&#xD;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;firstCandidate = qualifiedCandidates[0];&#xD;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;secondCandidate = qualifiedCandidates[1];&#xD;
&#xD;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(comparer.Compare(firstCandidate, secondCandidate) &amp;lt; 0)&#xD;
        {&#xD;
            &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//choose first as the best one&#xD;
            &lt;/span&gt;ast.Method.MethodInfo = firstCandidate;&#xD;
            ast.ExpressionType = firstCandidate.ReturnType;&#xD;
        }&#xD;
        &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;else&#xD;
        &lt;/span&gt;{&#xD;
            &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//ambiguous between first &amp;amp; second&#xD;
            &lt;/span&gt;m_errorManager.AddError(c_SE_MethodAmbiguous, ast.Method.MethodName.Span, &#xD;
                firstCandidate.GetSignatureString(), secondCandidate.GetSignatureString());&#xD;
            ast.ExpressionType = &lt;span style="color: #2b91af"&gt;PrimaryType&lt;/span&gt;.Unknown;&#xD;
        }&#xD;
    }&#xD;
    &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;else&#xD;
    &lt;/span&gt;{&#xD;
        ast.Method.MethodInfo = qualifiedCandidates[0];&#xD;
        ast.ExpressionType = qualifiedCandidates[0].ReturnType;&#xD;
    }&#xD;
}&#xD;
&lt;/font&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;经过完善的语义分析，我们就得到了一个具有完整类型信息，并且没有语义错误的AST。下一阶段我们就可以开始为编程语言生成代码了。首先我们将从生成CIL开始，做一个和C#类似的托管语言。之后我们将深入代码生成的各项技术，亲自动手生成目标机器的代码。敬请期待下一篇！&lt;/p&gt;&#xD;
&lt;p&gt;希望大家继续关注我的VBF项目：&lt;a href="https://github.com/Ninputer/VBF"&gt;https://github.com/Ninputer/VBF&lt;/a&gt; 和我的微博：&lt;a href="http://weibo.com/ninputer"&gt;http://weibo.com/ninputer&lt;/a&gt; 多谢大家支持！&lt;/p&gt;&lt;img src="http://www.cnblogs.com/Ninputer/aggbug/2112030.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/Ninputer/archive/2011/07/22/2112030.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/Ninputer/archive/2011/07/08/2099570.html</id><title type="text">自己动手开发编译器（十）miniSharp语法分析器</title><summary type="text">经过前面四篇的铺垫，我们终于拥有了编写语法分析器的强大工具，现在可以正式开发一门编程语言的语法分析器了。我们先来定义miniSharp的语法规则，然后根据LL文法的特点进行一些调整，最后借助解析器组合子生成完整的语法分析器。  miniSharp语言是C#的一个小子集，然而它仍然具有一门完整编程语言的所有要素，而且仍然是一种面向对象的语言。我们把miniSharp的语法分成三类——声明结构、语句和...</summary><published>2011-07-07T17:19:00Z</published><updated>2011-07-07T17:19:00Z</updated><author><name>装配脑袋</name><uri>http://www.cnblogs.com/Ninputer/</uri></author><link rel="alternate" href="http://www.cnblogs.com/Ninputer/archive/2011/07/08/2099570.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/Ninputer/archive/2011/07/08/2099570.html"/><content type="html">&lt;p&gt;经过前面四篇的铺垫，我们终于拥有了编写语法分析器的强大工具，现在可以正式开发一门编程语言的语法分析器了。我们先来定义miniSharp的语法规则，然后根据LL文法的特点进行一些调整，最后借助解析器组合子生成完整的语法分析器。&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;miniSharp语言是C#的一个小子集，然而它仍然具有一门完整编程语言的所有要素，而且仍然是一种面向对象的语言。我们把miniSharp的语法分成三类——声明结构、语句和表达式。声明结构就是类、方法、字段的声明。语句就是诸如if-else、while这样特定含义的指令。而表达式则是表示一种有运算结果的结构，如二元运算符表达式、函数调用表达式等。C#中赋值也是一种表达式，但miniSharp为了简化后续代码生成，将赋值当成一种语句。&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;首先来定义声明结构的文法。为了简化语义分析，我们规定程序中第一个类必须是一个静态类，静态类中只能有一个静态方法Main——这是整个miniSharp唯一允许的静态方法。在静态类之后可以定义多个普通类，普通类之间可以继承。下面定义文法的产生式采用了扩展写法，支持类似克林闭包的*符号。G → X* 代表G → ε; G → H; H → XG。文法中的蓝色字代表终结符（词法分析获得的单词）&lt;/p&gt; &lt;table border="1" cellspacing="0" cellpadding="5" width="600"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top" width="600"&gt; &lt;table border="0" cellspacing="0" cellpadding="0" width="100%"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;Program&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→ &lt;/td&gt; &lt;td valign="top" width="75%"&gt;MainClass ClassDecl*&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;MainClass&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→ &lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;font color="#0000ff"&gt;&lt;strong&gt;static class &lt;/strong&gt;ID&lt;strong&gt; { public static void Main (string[] &lt;/strong&gt;ID&lt;strong&gt;)&lt;br&gt;&lt;/strong&gt;&lt;/font&gt;&lt;font color="#0000ff"&gt;&lt;strong&gt;{&lt;/strong&gt;&lt;/font&gt; Statement+ &lt;strong&gt;&lt;font color="#0000ff"&gt;} }&lt;/font&gt;&lt;/strong&gt;&amp;nbsp;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;ClassDecl&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;font color="#0000ff"&gt;&lt;strong&gt;class &lt;/strong&gt;ID &lt;strong&gt;{&lt;/strong&gt;&lt;/font&gt; FieldDecl* MethodDecl* &lt;strong&gt;&lt;font color="#0000ff"&gt;}&lt;/font&gt;&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;font color="#0000ff"&gt;&lt;strong&gt;class &lt;/strong&gt;ID&lt;strong&gt; : &lt;/strong&gt;ID&lt;strong&gt; {&lt;/strong&gt;&lt;/font&gt; FieldDecl* MethodDecl* &lt;strong&gt;&lt;font color="#0000ff"&gt;}&lt;/font&gt;&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;FieldDecl&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;Type&lt;font color="#0000ff"&gt; ID&lt;strong&gt;;&lt;/strong&gt;&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;MethodDecl&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;font color="#0000ff"&gt;&lt;strong&gt;public Type &lt;/strong&gt;ID&lt;strong&gt; (&lt;/strong&gt;&lt;/font&gt;FormalList&lt;strong&gt;&lt;font color="#0000ff"&gt;) &lt;br&gt;&lt;/font&gt;&lt;font color="#0000ff"&gt;{&lt;/font&gt;&lt;/strong&gt; Statement* &lt;strong&gt;&lt;font color="#0000ff"&gt;return&lt;/font&gt;&lt;/strong&gt; Exp &lt;strong&gt;&lt;font color="#0000ff"&gt;; }&lt;/font&gt;&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;FormalList&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;Type &lt;font color="#0000ff"&gt;ID&lt;/font&gt; FormalRest*&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;ε&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;FormalRest&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;strong&gt;&lt;font color="#0000ff"&gt;, &lt;/font&gt;&lt;/strong&gt;Type &lt;font color="#0000ff"&gt;ID&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;Type&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;strong&gt;&lt;font color="#0000ff"&gt;int[]&lt;/font&gt;&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;strong&gt;&lt;font color="#0000ff"&gt;bool&lt;/font&gt;&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;strong&gt;&lt;font color="#0000ff"&gt;int&lt;/font&gt;&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;font color="#0000ff"&gt;ID&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;语句部分我们将要定义语句块和六种语句。其中if-else语句的else部分是不能省略的。while语句不支持break。剩下四种分别是调用Console.WriteLine的语句、赋值语句、数组元素赋值语句和变量声明语句。&lt;/p&gt; &lt;table border="1" cellspacing="0" cellpadding="5" width="600"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top" width="600"&gt; &lt;table border="0" cellspacing="0" cellpadding="0" width="100%"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;Statement&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;font color="#0000ff"&gt;&lt;strong&gt;{ &lt;/strong&gt;&lt;font color="#000000"&gt;Statement*&lt;/font&gt;&lt;strong&gt; }&lt;/strong&gt;&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;font color="#0000ff"&gt;&lt;strong&gt;if (&lt;/strong&gt; &lt;font color="#000000"&gt;Exp&lt;/font&gt; &lt;strong&gt;)&lt;/strong&gt; &lt;font color="#000000"&gt;Statement&lt;/font&gt; &lt;strong&gt;else&lt;/strong&gt; &lt;/font&gt;&lt;font color="#000000"&gt;Statement&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;strong&gt;&lt;font color="#0000ff"&gt;while (&lt;/font&gt;&lt;/strong&gt; Exp &lt;font color="#0000ff"&gt;&lt;strong&gt;)&lt;/strong&gt;&lt;/font&gt; Statement&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;strong&gt;&lt;font color="#0000ff"&gt;System.Console.WriteLine(&lt;/font&gt;&lt;/strong&gt; Exp &lt;strong&gt;&lt;font color="#0000ff"&gt;)&lt;/font&gt;&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;font color="#0000ff"&gt;ID&lt;/font&gt;&amp;nbsp;&lt;strong&gt;&lt;font color="#0000ff"&gt;=&lt;/font&gt;&lt;/strong&gt; Exp &lt;strong&gt;&lt;font color="#0000ff"&gt;;&lt;/font&gt;&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;font color="#0000ff"&gt;ID &lt;strong&gt;[&lt;/strong&gt;&lt;/font&gt; Exp &lt;strong&gt;&lt;font color="#0000ff"&gt;]&lt;/font&gt;&lt;/strong&gt; &lt;strong&gt;&lt;font color="#0000ff"&gt;=&lt;/font&gt;&lt;/strong&gt; Exp &lt;strong&gt;&lt;font color="#0000ff"&gt;;&lt;/font&gt;&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;Type &lt;font color="#0000ff"&gt;ID&lt;/font&gt; &lt;strong&gt;&lt;font color="#0000ff"&gt;;&lt;/font&gt;&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;表达式部分我们将要定义二元、一元、数组长度、数组访问、字面常量、变量、this引用、new运算、方法调用等多种表达式。&lt;/p&gt; &lt;table border="1" cellspacing="0" cellpadding="5" width="600"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top" width="600"&gt; &lt;table border="0" cellspacing="0" cellpadding="0" width="100%"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;Exp&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;font color="#000000"&gt;Exp &lt;font color="#0000ff"&gt;op&lt;/font&gt; Exp&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;font color="#0000ff"&gt;&lt;font color="#000000"&gt;Exp&lt;/font&gt;&lt;/font&gt;&lt;font color="#0000ff"&gt;&lt;strong&gt;[&lt;/strong&gt; &lt;font color="#000000"&gt;Exp&lt;/font&gt; &lt;strong&gt;]&lt;/strong&gt;&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;Exp &lt;strong&gt;&lt;font color="#0000ff"&gt;.Length&lt;/font&gt;&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;Exp &lt;font color="#0000ff"&gt;&lt;strong&gt;.&lt;/strong&gt;ID&lt;/font&gt; &lt;font color="#0000ff"&gt;&lt;strong&gt;(&lt;/strong&gt;&lt;/font&gt; ExpList &lt;strong&gt;&lt;font color="#0000ff"&gt;)&lt;/font&gt;&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;font color="#0000ff"&gt;INTEGER_LITERAL&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;strong&gt;&lt;font color="#0000ff"&gt;true&lt;/font&gt;&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;strong&gt;&lt;font color="#0000ff"&gt;false&lt;/font&gt;&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;font color="#0000ff"&gt;ID&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;strong&gt;&lt;font color="#0000ff"&gt;this&lt;/font&gt;&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;strong&gt;&lt;font color="#0000ff"&gt;new int [&lt;/font&gt;&lt;/strong&gt; Exp &lt;strong&gt;&lt;font color="#0000ff"&gt;]&lt;/font&gt;&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;font color="#0000ff"&gt;&lt;strong&gt;new&lt;/strong&gt; ID &lt;strong&gt;()&lt;/strong&gt;&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;strong&gt;&lt;font color="#0000ff"&gt;!&lt;/font&gt;&lt;/strong&gt; Exp&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;strong&gt;&lt;font color="#0000ff"&gt;(&lt;/font&gt;&lt;/strong&gt; Exp &lt;strong&gt;&lt;font color="#0000ff"&gt;)&lt;/font&gt;&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;ExpList&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;Exp ExpRest*&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;ε&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;ExpRest&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;strong&gt;&lt;font color="#0000ff"&gt;,&lt;/font&gt;&lt;/strong&gt; Exp&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;p&gt;其中二元运算表达式的op是+、-、*、/、&amp;gt;、&amp;lt;、==、&amp;amp;&amp;amp;和||之一。为了简单起见我们这里的二元运算表达式文法是有歧义而且没有正确定义优先级的。按照C#的语言规范，运算符的优先级关系如下（只提取了miniSharp支持的部分）：&lt;/p&gt; &lt;table border="1" cellspacing="0" cellpadding="5" width="300"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top" width="50"&gt;1&lt;/td&gt; &lt;td valign="top" width="250"&gt;(Exp)&amp;nbsp; new this 变量 常量&lt;br&gt;方法调用 属性访问 数组访问&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="50"&gt;2&lt;/td&gt; &lt;td valign="top" width="250"&gt;!&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="50"&gt;3&lt;/td&gt; &lt;td valign="top" width="250"&gt;* /&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="50"&gt;4&lt;/td&gt; &lt;td valign="top" width="250"&gt;+ -&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="50"&gt;5&lt;/td&gt; &lt;td valign="top" width="250"&gt;&amp;lt; &amp;gt; ==&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="50"&gt;6&lt;/td&gt; &lt;td valign="top" width="250"&gt;&amp;amp;&amp;amp;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="50"&gt;7&lt;/td&gt; &lt;td valign="top" width="250"&gt;||&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;p&gt;有些语法分析器就是使用有歧义的二元运算符文法，在遇到歧义时使用预定义的运算符优先级来解决冲突。现在的语法分析器倾向于直接使用无歧义的文法。下面的文法就是经过精心安排的运算符文法，消除了歧义并使得运算符具有左结合和优先级的区别：&lt;/p&gt; &lt;table border="1" cellspacing="0" cellpadding="5" width="600"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top" width="600"&gt; &lt;table border="0" cellspacing="0" cellpadding="0" width="100%"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;BasicExp&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;font color="#000000"&gt;括号、new、this、变量、常量、方法调用、属性访问、数组访问&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;Factor&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;font color="#0000ff"&gt;&lt;font color="#000000"&gt;Basic&lt;/font&gt;&lt;font color="#000000"&gt;Exp&lt;/font&gt;&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;strong&gt;&lt;font color="#0000ff"&gt;!&lt;/font&gt;&lt;/strong&gt; Factor&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;Term&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;Factor&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;font color="#0000ff"&gt;&lt;font color="#000000"&gt;Term&lt;/font&gt; op&lt;/font&gt;&lt;font color="#000000"&gt; Factor&amp;nbsp;&amp;nbsp; 其中 op 是 * / &lt;/font&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;Comparand&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;font color="#000000"&gt;Term&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;font color="#000000"&gt;Comparand &lt;font color="#0000ff"&gt;op&lt;/font&gt; Term&amp;nbsp;&amp;nbsp; 其中 op 是 + -&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;Comparison&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;font color="#000000"&gt;Comparand&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;font color="#000000"&gt;Comparison &lt;font color="#0000ff"&gt;op&lt;/font&gt; Comparand&amp;nbsp;&amp;nbsp;&amp;nbsp; 其中 op 是 &amp;lt; &amp;gt; ==&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;Logical&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;Comparison&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;font color="#0000ff"&gt;&lt;font color="#000000"&gt;Logical&lt;/font&gt;&lt;strong&gt; &amp;amp;&amp;amp; &lt;/strong&gt;&lt;/font&gt;&lt;font color="#000000"&gt;Comparison&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;Exp&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;Logical&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="20%"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="5%"&gt;→&lt;/td&gt; &lt;td valign="top" width="75%"&gt;&lt;font color="#000000"&gt;Exp &lt;strong&gt;&lt;font color="#0000ff"&gt;||&lt;/font&gt;&lt;/strong&gt; Logical&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;这样我们就定义了miniSharp的完整文法。注意，上述文法仍然存在一些左公因式和左递归的现象。我们用的解析器组合子因为独特的广度优先分支判断方式，其支持的文法实际上已经超越了递归下降语法分析器的LL(k)文法，称作LL(∞)的文法，这种文法非常强大，可描述所有确定性下推自动机DPDA接受的语言。但是，它仍然不允许文法存在左递归，而左公因式也会大大降低解析器的效率。所以消除左递归和尽量避免左公因式仍然是真正实现语法分析器时需要着重考虑的任务。&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;现代语言的语法分析器通常都是将源代码翻译成一棵&lt;strong&gt;抽象语法树&lt;/strong&gt;（Abstract Syntax Tree，&lt;strong&gt;AST&lt;/strong&gt;）。后续的语义分析可以在抽象语法树上继续进行。我们在语法分析篇（六）介绍过“语法分析树”，它是一种在文法推导过程中建立起来的树状数据结构。那么什么是抽象语法树呢？其实就是经过简化和抽象的语法分析树。在完整的语法分析树中每个推导过程的终结符都包含在语法树内，而且每个非终结符都是不同的节点类型。实际上，如果仅仅是要做编译器的话，很多终结符（如关键字、各种标点符号）是无需出现在语法树里的；而前面表达式文法中的Factor、Term也实际上没有必要区分为两种不同的类型，可以将其抽象为BinaryExpression类型。这样简化、抽象之后的语法树，更加利于后续语义分析和代码生成。使用.NET里的面向对象语言来实现语法树，最常见的做法就是用组合模式，将语法树做成一颗对象树，每种抽象语法对应一个节点类。下图就是miniSharp的抽象语法树的所有类。&lt;/p&gt; &lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/Ninputer/201107/201107080119026388.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/Ninputer/201107/2011070801190988.png" width="334" height="835"&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;节选其中一个语法树节点展示一下，比如大家熟悉的IfElse语句的语法树节点类：&lt;/p&gt; &lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IfElse &lt;/span&gt;: &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: #2b91af"&gt;Statement&#xD;
&lt;/span&gt;{&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Expression &lt;/span&gt;Condition { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Statement &lt;/span&gt;TruePart { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Statement &lt;/span&gt;FalsePart { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SourceSpan &lt;/span&gt;IfSpan { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SourceSpan &lt;/span&gt;ElseSpan { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;IfElse(&lt;span style="color: #2b91af"&gt;Expression &lt;/span&gt;cond, &lt;span style="color: #2b91af"&gt;Statement &lt;/span&gt;truePart, &lt;span style="color: #2b91af"&gt;Statement &lt;/span&gt;falsePart, &lt;span style="color: #2b91af"&gt;SourceSpan &lt;/span&gt;ifSpan, &lt;span style="color: #2b91af"&gt;SourceSpan &lt;/span&gt;elseSpan)&#xD;
    {&#xD;
        Condition = cond;&#xD;
        TruePart = truePart;&#xD;
        FalsePart = falsePart;&#xD;
        IfSpan = ifSpan;&#xD;
        ElseSpan = elseSpan;&#xD;
    }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public override &lt;/span&gt;T Accept&amp;lt;T&amp;gt;(&lt;span style="color: #2b91af"&gt;IAstVisitor&lt;/span&gt;&amp;lt;T&amp;gt; visitor)&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;return &lt;/span&gt;visitor.VisitIfElse(&lt;span style="color: blue"&gt;this&lt;/span&gt;);&#xD;
    }&#xD;
}&lt;/font&gt;&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;它的结构非常简单，里面保存了所有作为子节点成分的字段，例如IfElse是由一个Condition表达式和TruePart、FalsePart两个语句组成。另外我们还多储存了两个SourceSpan，分别是if语句中“if”关键字和“else”关键字出现的源代码位置（多少行，多少列）。保存位置是为了后续语义分析中提供错误信息的位置。比如if的条件表达式必须是个bool类型的表达式，但语法分析阶段无法做出类型验证，而到了语义分析阶段分析出了语义错误，仍然需要向编译器用户提供错误的位置，这些SourceSpan就可以派上用场。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;注意节点类最后还实现了一个Accept方法，用来支持语法树的Visitor模式。我们在语义分析阶段和代码生成阶段，需要一次又一次地遍历抽象语法树。为了简化语法树的访问，我们声明一个IAstVisitor&amp;lt;T&amp;gt;接口作为语法树的Visitor，后续过程需要遍历语法树时，就实现这一接口即可。实际上这个接口有一个默认实现——AstVisitor类，允许只重写一部分成员。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;有了Ast，下面我们就开始编写miniSharp的语法分析器。在本系列的第五篇（&lt;a href="http://www.cnblogs.com/Ninputer/archive/2011/06/13/2080094.html"&gt;miniSharp语言的词法分析器&lt;/a&gt;）中我们已经用VBF词法分析库定义了miniSharp的词法，生成了一些Token对象。那么接下来就只要使用Linq语法的解析器组合子，根据本篇开头定义的文法进行组合，并适时使用select语句生成语法树节点的对象即可。比如，文法最开始的Program和MainClass的写法如下：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;PProgram.Reference = &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;// MainClass ClassDecl*&#xD;
    &lt;/span&gt;&lt;span style="color: blue"&gt;from &lt;/span&gt;main &lt;span style="color: blue"&gt;in &lt;/span&gt;PMainClass&#xD;
    &lt;span style="color: blue"&gt;from &lt;/span&gt;classes &lt;span style="color: blue"&gt;in &lt;/span&gt;PClassDecl.Many()&#xD;
    &lt;span style="color: blue"&gt;select new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Program&lt;/span&gt;(main, classes);&#xD;
&#xD;
PMainClass.Reference = &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;// static class id { public static void Main(string[] id) { statement }}&#xD;
    &lt;/span&gt;&lt;span style="color: blue"&gt;from &lt;/span&gt;_static1 &lt;span style="color: blue"&gt;in &lt;/span&gt;K_STATIC&#xD;
    &lt;span style="color: blue"&gt;from &lt;/span&gt;_class &lt;span style="color: blue"&gt;in &lt;/span&gt;K_CLASS&#xD;
    &lt;span style="color: blue"&gt;from &lt;/span&gt;className &lt;span style="color: blue"&gt;in &lt;/span&gt;ID&#xD;
    &lt;span style="color: blue"&gt;from &lt;/span&gt;_1 &lt;span style="color: blue"&gt;in &lt;/span&gt;LEFT_BR&#xD;
    &lt;span style="color: blue"&gt;from &lt;/span&gt;_public &lt;span style="color: blue"&gt;in &lt;/span&gt;K_PUBLIC&#xD;
    &lt;span style="color: blue"&gt;from &lt;/span&gt;_static2 &lt;span style="color: blue"&gt;in &lt;/span&gt;K_STATIC&#xD;
    &lt;span style="color: blue"&gt;from &lt;/span&gt;_void &lt;span style="color: blue"&gt;in &lt;/span&gt;K_VOID&#xD;
    &lt;span style="color: blue"&gt;from &lt;/span&gt;_main &lt;span style="color: blue"&gt;in &lt;/span&gt;K_MAIN&#xD;
    &lt;span style="color: blue"&gt;from &lt;/span&gt;_2 &lt;span style="color: blue"&gt;in &lt;/span&gt;LEFT_PH&#xD;
    &lt;span style="color: blue"&gt;from &lt;/span&gt;_string &lt;span style="color: blue"&gt;in &lt;/span&gt;K_STRING&#xD;
    &lt;span style="color: blue"&gt;from &lt;/span&gt;_3 &lt;span style="color: blue"&gt;in &lt;/span&gt;LEFT_BK&#xD;
    &lt;span style="color: blue"&gt;from &lt;/span&gt;_4 &lt;span style="color: blue"&gt;in &lt;/span&gt;RIGHT_BK&#xD;
    &lt;span style="color: blue"&gt;from &lt;/span&gt;arg &lt;span style="color: blue"&gt;in &lt;/span&gt;ID&#xD;
    &lt;span style="color: blue"&gt;from &lt;/span&gt;_5 &lt;span style="color: blue"&gt;in &lt;/span&gt;RIGHT_PH&#xD;
    &lt;span style="color: blue"&gt;from &lt;/span&gt;_6 &lt;span style="color: blue"&gt;in &lt;/span&gt;LEFT_BR&#xD;
    &lt;span style="color: blue"&gt;from &lt;/span&gt;statements &lt;span style="color: blue"&gt;in &lt;/span&gt;PStatement.Many1()&#xD;
    &lt;span style="color: blue"&gt;from &lt;/span&gt;_7 &lt;span style="color: blue"&gt;in &lt;/span&gt;RIGHT_BR&#xD;
    &lt;span style="color: blue"&gt;from &lt;/span&gt;_8 &lt;span style="color: blue"&gt;in &lt;/span&gt;RIGHT_BR&#xD;
    &lt;span style="color: blue"&gt;select new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MainClass&lt;/span&gt;(className, arg, statements);&lt;/font&gt;&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;这代码是如此的直白以至于没什么可解释的。唯一要注意的是PProgram.Reference这个用法，这里PProgram是ParserReference&amp;lt;T&amp;gt;类的实例。这个类允许先直接new出来，然后再用.Reference = XXX的方式为其指定语法规则。这样就允许一个Parser组合子先使用，后定义（比如上面例子中的PMainClass就先在PProgram的语法定义中使用了，然后下面才定义其语法）。因为文法中的非终结符常常出现递归引用，用ParserReference这个类可以大大简化我们的工作，不用关心Parser的声明先后顺序问题。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;我们重点来看一些需要特殊技巧的例子。首先是声明方法形式参数的文法，采用了FormalList → Type ID FormalRest*这样的定义方法，这是避免左递归的技巧。但是这样一来，方法的第一个参数就和其他的参数分别定义在两个语法当中。我们希望生成的抽象语法树不区分第一个参数和其余参数，所以可以在生成语法树时采用一点点小技巧来办到：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;paramFormal =&#xD;
    &lt;span style="color: blue"&gt;from &lt;/span&gt;paramType &lt;span style="color: blue"&gt;in &lt;/span&gt;PType&#xD;
    &lt;span style="color: blue"&gt;from &lt;/span&gt;paramName &lt;span style="color: blue"&gt;in &lt;/span&gt;ID&#xD;
    &lt;span style="color: blue"&gt;select new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Formal&lt;/span&gt;(paramType, paramName);&#xD;
&#xD;
PFormalList.Reference = &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;// Type id FormalRest* | &amp;lt;empty&amp;gt;&#xD;
    &lt;/span&gt;(&lt;span style="color: blue"&gt;from &lt;/span&gt;first &lt;span style="color: blue"&gt;in &lt;/span&gt;paramFormal&#xD;
     &lt;span style="color: blue"&gt;from &lt;/span&gt;rest &lt;span style="color: blue"&gt;in &lt;/span&gt;PFormalRest.Many()&#xD;
     &lt;span style="color: blue"&gt;select &lt;strong&gt;&lt;font style="background-color: #eeeeee"&gt;new&lt;/font&gt;&lt;/strong&gt;&lt;/span&gt;&lt;font style="background-color: #eeeeee"&gt;&lt;strong&gt;[]&lt;/strong&gt; &lt;strong&gt;{ first }.Concat(rest).ToArray())&lt;/strong&gt;&lt;/font&gt; |&#xD;
    &lt;span style="color: #2b91af"&gt;Parsers&lt;/span&gt;.Succeed(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Formal&lt;/span&gt;[0]);&#xD;
&#xD;
PFormalRest.Reference = &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;// , Type id&#xD;
    &lt;/span&gt;paramFormal.PrefixedBy(COMMA.AsParser());&lt;/font&gt;&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;另外注意扩展的产生式“X*”在VBF解析器组合子库中可以直接使用X.Many()的方式实现。VBF中还定义了数个这种方便的扩展组合子。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;最后要注意的是二元运算符的分析器。我们前面写出的无歧义符合优先级的二元运算符文法仍然是左递归的，用于解析器组合子时必须像上面的FormalList那样改成右递归的。但是这些运算符都是左结合的，我们不想让生成的抽象语法树也变成右递归的形态。因此，这里我们需要用（传统）Linq的Aggregate扩展方法来处理一下生成的语法树：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;termRest =&#xD;
    &lt;span style="color: blue"&gt;from &lt;/span&gt;op &lt;span style="color: blue"&gt;in &lt;/span&gt;(ASTERISK.AsParser() | SLASH.AsParser())&#xD;
    &lt;span style="color: blue"&gt;from &lt;/span&gt;factor &lt;span style="color: blue"&gt;in &lt;/span&gt;PFactor&#xD;
    &lt;span style="color: blue"&gt;select new &lt;/span&gt;{ Op = op, Right = factor };&#xD;
&#xD;
PTerm.Reference = &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;// term * factor | factor&#xD;
    &lt;/span&gt;&lt;span style="color: blue"&gt;from &lt;/span&gt;factor &lt;span style="color: blue"&gt;in &lt;/span&gt;PFactor&#xD;
    &lt;span style="color: blue"&gt;from &lt;/span&gt;rest &lt;span style="color: blue"&gt;in &lt;/span&gt;termRest.Many()&#xD;
    &lt;span style="color: blue"&gt;select &lt;/span&gt;&lt;strong&gt;&lt;font style="background-color: #eeeeee"&gt;rest.Aggregate(factor, (f, r) =&amp;gt; &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Binary&lt;/span&gt;(r.Op, f, r.Right))&lt;/font&gt;&lt;/strong&gt;;&#xD;
&#xD;
&lt;span style="color: blue"&gt;var &lt;/span&gt;comparandRest =&#xD;
    &lt;span style="color: blue"&gt;from &lt;/span&gt;op &lt;span style="color: blue"&gt;in &lt;/span&gt;(PLUS.AsParser() | MINUS.AsParser())&#xD;
    &lt;span style="color: blue"&gt;from &lt;/span&gt;term &lt;span style="color: blue"&gt;in &lt;/span&gt;PTerm&#xD;
    &lt;span style="color: blue"&gt;select new &lt;/span&gt;{ Op = op, Right = term };&#xD;
&#xD;
PComparand.Reference = &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;// comparand + term | term&#xD;
    &lt;/span&gt;&lt;span style="color: blue"&gt;from &lt;/span&gt;term &lt;span style="color: blue"&gt;in &lt;/span&gt;PTerm&#xD;
    &lt;span style="color: blue"&gt;from &lt;/span&gt;rest &lt;span style="color: blue"&gt;in &lt;/span&gt;comparandRest.Many()&#xD;
    &lt;span style="color: blue"&gt;select &lt;/span&gt;rest.Aggregate(term, (t, r) =&amp;gt; &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Binary&lt;/span&gt;(r.Op, t, r.Right));&#xD;
&#xD;
&#xD;
&lt;span style="color: blue"&gt;var &lt;/span&gt;comparisonRest =&#xD;
    &lt;span style="color: blue"&gt;from &lt;/span&gt;op &lt;span style="color: blue"&gt;in &lt;/span&gt;(LESS.AsParser() | GREATER.AsParser() | EQUAL.AsParser())&#xD;
    &lt;span style="color: blue"&gt;from &lt;/span&gt;comparand &lt;span style="color: blue"&gt;in &lt;/span&gt;PComparand&#xD;
    &lt;span style="color: blue"&gt;select new &lt;/span&gt;{ Op = op, Right = comparand };&#xD;
&#xD;
PComparison.Reference = &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;// comparison &amp;lt; comparand | comparand&#xD;
    &lt;/span&gt;&lt;span style="color: blue"&gt;from &lt;/span&gt;comparand &lt;span style="color: blue"&gt;in &lt;/span&gt;PComparand&#xD;
    &lt;span style="color: blue"&gt;from &lt;/span&gt;rest &lt;span style="color: blue"&gt;in &lt;/span&gt;comparisonRest.Many()&#xD;
    &lt;span style="color: blue"&gt;select &lt;/span&gt;rest.Aggregate(comparand, (c, r) =&amp;gt; &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Binary&lt;/span&gt;(r.Op, c, r.Right));&lt;/font&gt;&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;除此之外，剩下的语法翻译成组合子基本上都是水到渠成的工作了。完整的代码全部都在MiniSharpParser.cs中，大家可以自行下载阅读。经过小小的努力，我们终于能将miniSharp的源代码转换为抽象语法树了，接下来我们就要进入下一个编译器重要的阶段——语义分析。敬请期待下一篇！&lt;/p&gt;&#xD;
&lt;p&gt;希望大家继续关注我的VBF项目：&lt;a href="https://github.com/Ninputer/VBF"&gt;https://github.com/Ninputer/VBF&lt;/a&gt; 和我的微博：&lt;a href="http://weibo.com/ninputer"&gt;http://weibo.com/ninputer&lt;/a&gt; 多谢大家支持！&lt;/p&gt;&lt;img src="http://www.cnblogs.com/Ninputer/aggbug/2099570.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/Ninputer/archive/2011/07/08/2099570.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/Ninputer/archive/2011/07/03/2096944.html</id><title type="text">自己动手开发编译器（九）CPS风格的解析器组合子</title><summary type="text">上回我们用函数式编程的方法，结合Linq语法，建立了一套解析器组合子方案，并能成功解析自定义文法的输入字符串。但是，上次做成的解析器组合子有个重要的功能没有完成——错误报告。作为编程语言的语法分析器，不能在遇到语法错误的时候简单地返回null，那样程序员就很难修复代码中的语法错误。我们需要的是准确报告语法错误的位置，更进一步，是程序中所有的语法错误，而不仅仅是头一个。后者要求解析器具有错误恢复的能...</summary><published>2011-07-03T14:16:00Z</published><updated>2011-07-03T14:16:00Z</updated><author><name>装配脑袋</name><uri>http://www.cnblogs.com/Ninputer/</uri></author><link rel="alternate" href="http://www.cnblogs.com/Ninputer/archive/2011/07/03/2096944.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/Ninputer/archive/2011/07/03/2096944.html"/><content type="html">&lt;p&gt;上回我们用函数式编程的方法，结合Linq语法，建立了一套解析器组合子方案，并能成功解析自定义文法的输入字符串。但是，上次做成的解析器组合子有个重要的功能没有完成——错误报告。作为编程语言的语法分析器，不能在遇到语法错误的时候简单地返回null，那样程序员就很难修复代码中的语法错误。我们需要的是准确报告语法错误的位置，更进一步，是程序中&lt;strong&gt;所有的语法错误&lt;/strong&gt;，而不仅仅是头一个。后者要求解析器具有&lt;strong&gt;错误恢复&lt;/strong&gt;的能力，即在遇到语法错误之后，还能恢复到正常状态继续解析。错误恢复不仅仅可以用在检测出所有的语法错误，还可以在存在语法错误的时候仍然提供有意义的解析结果，从而用于IDE的智能感知和重构等功能。手写的递归下降语法分析器可以很容易地加入错误恢复，但需要针对每一处错误手工编写代码来恢复。像C#官方编译器，给出的语法错误信息非常全面、精确、智能，全都是手工编写的功劳。又回到我们是懒人这个残酷的事实，能不能在让解析器组合子生成的解析器自动具有错误恢复能力呢？&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;首先来看上一个版本的四个基本组合子：空产生式的Succeed组合子，token产生式的AsParser组合子，连接运算产生式的SelectMany组合子和并运算产生式的Union组合子。首先Succeed是不会解析失败的，所以它没有必要进行错误恢复。现在来看AsParser组合子，它的逻辑是读取下一个词素，如果词素的单词类型和组合子的参数匹配则解析成功，否则解析失败。代码如下：&lt;/p&gt; &lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;public static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParserFunc&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Lexeme&lt;/span&gt;&amp;gt; AsParser(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Token &lt;/span&gt;token)&#xD;
{&#xD;
    &lt;span style="color: blue"&gt;return &lt;/span&gt;scanner =&amp;gt;&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;lexeme = scanner.Read();&#xD;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(lexeme.TokenIndex == token.Index)&#xD;
        {&#xD;
            &lt;span style="color: blue"&gt;return new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Result&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Lexeme&lt;/span&gt;&amp;gt;(lexeme, scanner);&#xD;
        }&#xD;
        &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;else&#xD;
        &lt;/span&gt;{&#xD;
            &lt;span style="color: blue"&gt;return null&lt;/span&gt;;&#xD;
        }&#xD;
    };&#xD;
}&lt;/font&gt;&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;如果要对失败的情形进行错误恢复，有两种可行的选择：1、假装要解析的Token存在，继续解析（这种做法相当于在原位置&lt;strong&gt;插入&lt;/strong&gt;了一个单词）；2、跳过不匹配的单词，重新进行解析（这种做法相当于&lt;strong&gt;删除&lt;/strong&gt;了一个单词）。如果漏写一个分号或者括号，插入型错误恢复就能有效地恢复错误，如果是多写了一个关键字或标识符造成的错误，删除型错误恢复就能有效地恢复。但问题是，我们怎么能在组合子的代码中判断出哪种错误恢复更有效呢？最优策略是让两种错误恢复的状态都继续解析到末尾，然后看哪种恢复状态整体语法错误最少。但是，只要有一个字符解析失败，就要分支成两个完整解析，那么错误一旦多起来，这个分支的庞大程度将使得错误恢复无法进行。更何况，错误并不仅仅出现在真正的语法错误上，我们还要用错误来判断“并”运算组合子的分支问题。请看上一版本Union组合子的代码：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;public static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParserFunc&lt;/span&gt;&amp;lt;T&amp;gt; Union&amp;lt;T&amp;gt;(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParserFunc&lt;/span&gt;&amp;lt;T&amp;gt; parser1, &lt;span style="color: #2b91af"&gt;ParserFunc&lt;/span&gt;&amp;lt;T&amp;gt; parser2)&#xD;
{&#xD;
    &lt;span style="color: blue"&gt;return &lt;/span&gt;scanner =&amp;gt;&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;scanner1 = scanner;&#xD;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;scanner2 = scanner.Fork();&#xD;
&#xD;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;result1 = parser1(scanner1);&#xD;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(result1 != &lt;span style="color: blue"&gt;null&lt;/span&gt;)&#xD;
        {&#xD;
&#xD;
            &lt;span style="color: blue"&gt;return &lt;/span&gt;result1;&#xD;
        }&#xD;
&#xD;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;result2 = parser2(scanner2);&#xD;
&#xD;
        &lt;span style="color: blue"&gt;return &lt;/span&gt;result2;&#xD;
    };&#xD;
}&lt;/font&gt;&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;在Union中，我们先试验第一个parser1能否解析成功，如果失败才解析parser2。如果解析器有自动错误恢复的功能，那么我们就无法用这种方式判断了，因为两条分支遇到错误之后都会继续进行下去。我们可以让两条分支都解析到底，然后挑错误较少的分支作为正式解析结果。但同上所述，这种做法的分支多得难以置信，效率上决定我们不能采用。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;为了避免效率问题，我们需要一种“广度优先”的处理方案。在遇到错误时产生的“插入”和“删除”两条分支，要同时进行，但要一步一步地进行。这里所谓的一“步”，就是指AsParser组合子读取一个词素。我们看到四种基本组合子中，只有AsParser组合子会用scanner来真正读取词素，其他组合子最终也是要调用到AsParser组合子来进行解析的。我们让两个可能的分支都向前解析一步，然后看是否其中一条分支的结果比另外一条更好。所谓更好，就是一条分支没有进一步遇到错误，而另外一条分支遇到了错误。如果两条分支都没有遇到错误，或者都遇到了错误，我们就再向前推进一步，直到某一步比另外一步更好为止。Union组合子也可以采用同样的策略处理。这是一种贪心算法的策略，我们所得到的结果未必是语法错误最少的解析结果，但它的效率是可以接受的。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;那么怎么进行“广度优先”推进呢？我们上次引入的组合子，当前的组合子无法知道下一个要运行的组合子是什么，更无法控制下一个组合子只向前解析一步。为了达到目的，我们要引入一种新的组合子函数原型，称作&lt;strong&gt;CPS&lt;/strong&gt;（Continuation Pass-in Style）风格的组合子。不知道大家有多少人听说过CPS，这在函数式编程界是一种广为应用的模式，在.NET世界里其实也有采用。.NET 4.0引入的Task Parallel Library库中的Task类，就是一个典型的CPS设计范例。我举一个简单的例子来介绍一下CPS。如果有两个函数A和B，需要按顺序调用，用传统方式编程很简单，就是直接调用：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;void &lt;/span&gt;A()&#xD;
{&#xD;
    &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #a31515"&gt;"A"&lt;/span&gt;);&#xD;
}&#xD;
&#xD;
&lt;span style="color: blue"&gt;void &lt;/span&gt;B()&#xD;
{&#xD;
    &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #a31515"&gt;"B"&lt;/span&gt;);&#xD;
}&#xD;
&#xD;
&lt;span style="color: blue"&gt;void &lt;/span&gt;Run()&#xD;
{&#xD;
    A();&#xD;
    B();&#xD;
}&lt;/font&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;而如果采用CPS，则是把B传递给A，这时我们称B是A的continuation，或者future。&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;void &lt;/span&gt;A(&lt;span style="color: #2b91af"&gt;Action &lt;/span&gt;future)&#xD;
{&#xD;
    &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #a31515"&gt;"A"&lt;/span&gt;);&#xD;
    future();&#xD;
}&#xD;
&#xD;
&lt;span style="color: blue"&gt;void &lt;/span&gt;B()&#xD;
{&#xD;
    &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #a31515"&gt;"B"&lt;/span&gt;);&#xD;
}&#xD;
&#xD;
&lt;span style="color: blue"&gt;void &lt;/span&gt;Run()&#xD;
{&#xD;
    A(B);&#xD;
}&lt;/font&gt;&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;乍一看这也不能实现什么特别的事情，但其实是很有用的。A获得了自己的future之后可以自行决定如何运行它。比如可以异步地运行。这样我们就在Run()方法中，用一种容易理解的方式，构建出了一条异步调用序列。.NET 4.0的Task Parallel Library正是这样的风格，每个Task类通过ContinueWith方法接受自己的future。而我们的函数式解析其组合子，也可以用这种方式，让每个Parser函数接受一个future，并自行决定如何调用future。这里最关键的思想是实现&lt;strong&gt;延迟调用&lt;/strong&gt;future，从而实现“广度优先”的单步解析效果。首先来看看新的Parser类原型（警告，这一篇里采用的函数式技巧比上一篇还要难懂得多，如果看了之后发生头晕，嗜睡等症状，请休息之后重新看……）：&lt;/p&gt;&#xD;
&lt;p&gt;&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;public delegate &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Result&lt;/span&gt;&amp;lt;T&amp;gt; &lt;span style="color: #2b91af"&gt;ParserFunc&lt;/span&gt;&amp;lt;T&amp;gt;(&lt;span style="color: #2b91af"&gt;ForkableScanner &lt;/span&gt;scanner, &lt;span style="color: #2b91af"&gt;ParserContext &lt;/span&gt;context);&#xD;
&lt;span style="color: blue"&gt;public delegate &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParserFunc&lt;/span&gt;&amp;lt;TFuture&amp;gt; &lt;span style="color: #2b91af"&gt;Future&lt;/span&gt;&amp;lt;T, TFuture&amp;gt;(T value);&#xD;
&#xD;
&lt;span style="color: blue"&gt;public abstract class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Parser&lt;/span&gt;&amp;lt;T&amp;gt;&#xD;
{&#xD;
    &lt;span style="color: blue"&gt;public abstract &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParserFunc&lt;/span&gt;&amp;lt;TFuture&amp;gt; BuildParser&amp;lt;TFuture&amp;gt;(&lt;span style="color: #2b91af"&gt;Future&lt;/span&gt;&amp;lt;T, TFuture&amp;gt; future);&#xD;
}&lt;/font&gt;&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/p&gt;&#xD;
&lt;p&gt;ParserFunc方法和上一篇非常类似，但是多了一个ParserContext方法。我们会用这个对象来保存一些错误报告的信息。再来是Future函数的定义，Future返回的类型是一个ParserFunc委托对象；Future的参数T，则是用来让每一个组合子生成的Parser，将自己的解析结果T传给它自己的Future用的。注意这次多了一个Parser&amp;lt;T&amp;gt;抽象类，它代替ParserFunc成为组合子的生成对象。它之所以不能声明成一个委托，是因为它的BuildParser方法要接受一个额外的泛型类型参数TFuture。接下来每一个解析器组合子都需要继承自Parser&amp;lt;T&amp;gt;，并实现它的BuildParser方法。下面我们就来看一看新的CPS型解析器组合子怎么定义。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;首先还是G → ε的组合子，它永远都能解析成功，所以，它的逻辑是生成一个ParserFunc，将预设的解析结果传递给自己的Future:&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SucceedParser&lt;/span&gt;&amp;lt;T&amp;gt; : &lt;span style="color: #2b91af"&gt;Parser&lt;/span&gt;&amp;lt;T&amp;gt;&#xD;
{&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;T Value { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;SucceedParser(T value)&#xD;
    {&#xD;
        Value = value;&#xD;
    }&#xD;
    &lt;span style="color: blue"&gt;public override &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParserFunc&lt;/span&gt;&amp;lt;TFuture&amp;gt; BuildParser&amp;lt;TFuture&amp;gt;(&lt;span style="color: #2b91af"&gt;Future&lt;/span&gt;&amp;lt;T, TFuture&amp;gt; future)&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;return &lt;/span&gt;(scanner, context) =&amp;gt; future(Value)(scanner, context);&#xD;
    }&#xD;
}&lt;/font&gt;&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;这是第一次实践CPS风格，大家一定要注意观察它与上一次传统风格解析器组合子的不同。最关键的一点，就是返回的ParserFunc，必须要调用BuildParser传进来的future函数，传递自己的解析结果。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;接下来就是重头戏G → t，我们要在这个单词解析器组合子中加入期待已久的错误报告和错误恢复功能，请看代码：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;TokenParser &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Parser&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Lexeme&lt;/span&gt;&amp;gt;&#xD;
{&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Token &lt;/span&gt;ExpectedToken { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }&#xD;
    &lt;span style="color: blue"&gt;public string &lt;/span&gt;MissingCorrection { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;TokenParser(&lt;span style="color: #2b91af"&gt;Token &lt;/span&gt;expected)&#xD;
    {&#xD;
        ExpectedToken = expected;&#xD;
        MissingCorrection = expected.ToString();&#xD;
    }&#xD;
&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public override &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParserFunc&lt;/span&gt;&amp;lt;TFuture&amp;gt; BuildParser&amp;lt;TFuture&amp;gt;(&lt;span style="color: #2b91af"&gt;Future&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Lexeme&lt;/span&gt;, TFuture&amp;gt; future)&#xD;
    {&#xD;
        &lt;span style="color: #2b91af"&gt;ParserFunc&lt;/span&gt;&amp;lt;TFuture&amp;gt; scan = &lt;span style="color: blue"&gt;null&lt;/span&gt;;&#xD;
        scan = (scanner, context) =&amp;gt;&#xD;
        {&#xD;
            &lt;span style="color: blue"&gt;var &lt;/span&gt;s1 = scanner.Fork();&#xD;
&#xD;
            &lt;span style="color: blue"&gt;var &lt;/span&gt;l = scanner.Read();&#xD;
&#xD;
            &lt;span style="color: blue"&gt;int &lt;/span&gt;tokenIndex;&#xD;
            tokenIndex = l.TokenIndex;&#xD;
&#xD;
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(tokenIndex == ExpectedToken.Index)&#xD;
            {&#xD;
                &lt;span style="color: blue"&gt;var &lt;/span&gt;r = context.StepResult(0, () =&amp;gt; future(l)(scanner, context));&#xD;
                &lt;span style="color: blue"&gt;return &lt;/span&gt;r;&#xD;
            }&#xD;
            &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;else&#xD;
            &lt;/span&gt;{&#xD;
                &lt;span style="color: #2b91af"&gt;Lexeme &lt;/span&gt;correctionLexeme = l.GetErrorCorrectionLexeme(ExpectedToken.Index, MissingCorrection);&#xD;
                &lt;span style="color: #2b91af"&gt;ErrorCorrection &lt;/span&gt;insertCorrection = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;InsertedErrorCorrection&lt;/span&gt;(ExpectedToken.ToString(), correctionLexeme.Span);&#xD;
&#xD;
                &lt;span style="color: blue"&gt;if &lt;/span&gt;(l.IsEndOfStream)&#xD;
                {&#xD;
                    scanner.Join(s1);&#xD;
                    &lt;span style="color: blue"&gt;return &lt;/span&gt;context.StepResult(1, () =&amp;gt; future(correctionLexeme)(scanner, context), insertCorrection); &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//插入&#xD;
                &lt;/span&gt;}&#xD;
                &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;else&#xD;
                &lt;/span&gt;{&#xD;
                    &lt;span style="color: #2b91af"&gt;ErrorCorrection &lt;/span&gt;deleteCorrection = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DeletedErrorCorrection&lt;/span&gt;(l);&#xD;
                    &lt;span style="color: blue"&gt;return &lt;/span&gt;context.ChooseBest(context.StepResult(1, () =&amp;gt; future(correctionLexeme)(s1, context), insertCorrection), &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//插入&#xD;
                        &lt;/span&gt;context.StepResult(1, () =&amp;gt; scan(scanner, context), deleteCorrection)); &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//删除&#xD;
                &lt;/span&gt;}&#xD;
            }&#xD;
        };&#xD;
&#xD;
        &lt;span style="color: blue"&gt;return &lt;/span&gt;scan;&#xD;
    }&#xD;
}&lt;/font&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;大致描述下来就是生成这样一个ParserFunc：首先通过Scanner读取下一个词素，并判断它是否是期待的单词。如果是，则调用context.StepResult(0, …)方法（稍后解释）；如果不是，则判断是否遇到的输入流的末尾，如果是末尾，则只尝试“插入”修复方案（因为无法删除“流末尾”单词），如果不是末尾则使用context.ChooseBest方法，尝试插入和删除两种修复方案。context.StepResult方法就是产生延迟执行future的关键。它的第一个参数表示该结果的“代价”，0表示这是一个成功解析的结果；1表示是经过错误恢复的结果。第二个参数则是一个延迟执行的委托，这个委托只会在我们需要将解析器“推进一步”的时候才会执行，我们将future函数的调用放在这里并做成延迟执行的方式，就是要等待广度优先一步一步地向前解析时才执行下一步的操作。那么这个context.ChooseBest函数到底是如何实现的呢？请看代码：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Result&lt;/span&gt;&amp;lt;T&amp;gt; ChooseBest&amp;lt;T&amp;gt;(&lt;span style="color: #2b91af"&gt;Result&lt;/span&gt;&amp;lt;T&amp;gt; result1, &lt;span style="color: #2b91af"&gt;Result&lt;/span&gt;&amp;lt;T&amp;gt; result2, &lt;span style="color: blue"&gt;int &lt;/span&gt;correctionDepth)&#xD;
&lt;font face="Consolas"&gt;{&#xD;
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(result1.Type == &lt;span style="color: #2b91af"&gt;ResultType&lt;/span&gt;.Stop)&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;return &lt;/span&gt;result1;&#xD;
    }&#xD;
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(result2.Type == &lt;span style="color: #2b91af"&gt;ResultType&lt;/span&gt;.Stop)&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;return &lt;/span&gt;result2;&#xD;
    }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;step1 = (&lt;span style="color: #2b91af"&gt;StepResult&lt;/span&gt;&amp;lt;T&amp;gt;)result1;&#xD;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;step2 = (&lt;span style="color: #2b91af"&gt;StepResult&lt;/span&gt;&amp;lt;T&amp;gt;)result2;&#xD;
&#xD;
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(step1.Cost &amp;lt; step2.Cost)&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;return &lt;/span&gt;step1;&#xD;
    }&#xD;
    &lt;span style="color: blue"&gt;else if &lt;/span&gt;(step1.Cost &amp;gt; step2.Cost)&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;return &lt;/span&gt;step2;&#xD;
    }&#xD;
    &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;else&#xD;
    &lt;/span&gt;{&lt;/font&gt;&lt;span style="color: green"&gt;&#xD;
&lt;font face="Consolas"&gt;        &lt;/font&gt;&lt;/span&gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;return new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;StepResult&lt;/span&gt;&amp;lt;T&amp;gt;(&lt;span style="color: #2b91af"&gt;Math&lt;/span&gt;.Max(step1.Cost, step2.Cost), &#xD;
            () =&amp;gt; ChooseBest(step1.NextResult, step2.NextResult, correctionDepth + 1), &lt;span style="color: blue"&gt;null&lt;/span&gt;);&#xD;
    }&#xD;
}&lt;/font&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;ChooseBest方法要比较两个Result的代价，并选取代价较小的分支。如果代价一样，则通过延迟计算的方法将比较推至下一轮。我们到处采用延迟计算的手段，以至于整个单词流都输入之后，解析可能仍然没有结束！所以Result类有一个集中取得每一步结果的功能，在单词流输入完毕后还要继续驱动这些延迟计算，直到拿到最终的解析结果。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;接下来是表示连接运算G → X Y的SelectMany组合子。具体方法是将传入的future作为Y的future，再将Y的Parser作为X的future，以此将两者连接起来：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ConcatenationParser&lt;/span&gt;&amp;lt;T1, T2, TR&amp;gt; : &lt;span style="color: #2b91af"&gt;Parser&lt;/span&gt;&amp;lt;TR&amp;gt;&#xD;
{&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Parser&lt;/span&gt;&amp;lt;T1&amp;gt; ParserLeft { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;T1, &lt;span style="color: #2b91af"&gt;Parser&lt;/span&gt;&amp;lt;T2&amp;gt;&amp;gt; ParserRightSelector { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;T1, T2, TR&amp;gt; Selector { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;ConcatenationParser(&lt;span style="color: #2b91af"&gt;Parser&lt;/span&gt;&amp;lt;T1&amp;gt; parserLeft, &lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;T1, &lt;span style="color: #2b91af"&gt;Parser&lt;/span&gt;&amp;lt;T2&amp;gt;&amp;gt; parserRightSelector, &lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;T1, T2, TR&amp;gt; selector)&#xD;
    {&#xD;
        &lt;span style="color: #2b91af"&gt;CodeContract&lt;/span&gt;.RequiresArgumentNotNull(parserLeft, &lt;span style="color: #a31515"&gt;"parserLeft"&lt;/span&gt;);&#xD;
        &lt;span style="color: #2b91af"&gt;CodeContract&lt;/span&gt;.RequiresArgumentNotNull(parserRightSelector, &lt;span style="color: #a31515"&gt;"parserRightSelector"&lt;/span&gt;);&#xD;
        &lt;span style="color: #2b91af"&gt;CodeContract&lt;/span&gt;.RequiresArgumentNotNull(selector, &lt;span style="color: #a31515"&gt;"selector"&lt;/span&gt;);&#xD;
&#xD;
        ParserLeft = parserLeft;&#xD;
        ParserRightSelector = parserRightSelector;&#xD;
        Selector = selector;&#xD;
    }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public override &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParserFunc&lt;/span&gt;&amp;lt;TFuture&amp;gt; BuildParser&amp;lt;TFuture&amp;gt;(&lt;span style="color: #2b91af"&gt;Future&lt;/span&gt;&amp;lt;TR, TFuture&amp;gt; future)&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;return &lt;/span&gt;(scanner, context) =&amp;gt; &#xD;
            ParserLeft.BuildParser(&#xD;
                left =&amp;gt; ParserRightSelector(left).BuildParser(&#xD;
                    right =&amp;gt; future(Selector(left, right))))(scanner, context);&#xD;
    }&#xD;
}&lt;/font&gt;&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;最后的并运算，则是广度优先同时实验两个传入的Parser，即直接用ChooseBest方法选取继续执行的Parser：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;AlternationParser&lt;/span&gt;&amp;lt;T&amp;gt; : &lt;span style="color: #2b91af"&gt;Parser&lt;/span&gt;&amp;lt;T&amp;gt;&#xD;
{&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Parser&lt;/span&gt;&amp;lt;T&amp;gt; Parser1 { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Parser&lt;/span&gt;&amp;lt;T&amp;gt; Parser2 { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;AlternationParser(&lt;span style="color: #2b91af"&gt;Parser&lt;/span&gt;&amp;lt;T&amp;gt; parser1, &lt;span style="color: #2b91af"&gt;Parser&lt;/span&gt;&amp;lt;T&amp;gt; parser2)&#xD;
    {&#xD;
        &lt;span style="color: #2b91af"&gt;CodeContract&lt;/span&gt;.RequiresArgumentNotNull(parser1, &lt;span style="color: #a31515"&gt;"parser1"&lt;/span&gt;);&#xD;
        &lt;span style="color: #2b91af"&gt;CodeContract&lt;/span&gt;.RequiresArgumentNotNull(parser2, &lt;span style="color: #a31515"&gt;"parser2"&lt;/span&gt;);&#xD;
&#xD;
        Parser1 = parser1;&#xD;
        Parser2 = parser2;&#xD;
    }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public override &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParserFunc&lt;/span&gt;&amp;lt;TFuture&amp;gt; BuildParser&amp;lt;TFuture&amp;gt;(&lt;span style="color: #2b91af"&gt;Future&lt;/span&gt;&amp;lt;T, TFuture&amp;gt; future)&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;return &lt;/span&gt;(scanner, context) =&amp;gt;&#xD;
        {&#xD;
            &lt;span style="color: blue"&gt;var &lt;/span&gt;s1 = scanner;&#xD;
            &lt;span style="color: blue"&gt;var &lt;/span&gt;s2 = scanner.Fork();&#xD;
&#xD;
            &lt;span style="color: blue"&gt;return &lt;/span&gt;context.ChooseBest(Parser1.BuildParser(future)(s1, context), Parser2.BuildParser(future)(s2, context));&#xD;
        };&#xD;
    }&#xD;
}&lt;/font&gt;&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;如果大家还不能很清晰地理解上述CPS风格解析器组合子的原理，也不要担心。我也是花了整整两个星期时间反复看论文才理清所有细节的。而且我贴的也是简化的代码，并不完整。大家可以下载VBF库的源代码来仔细研究。当然，如果对Haskell不恐惧的话，看原始的论文也不错。从这里下载论文（点右上方Download下面的PDF图标）：&lt;a title="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.30.7601" href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.30.7601"&gt;http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.30.7601&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;最后，我们像上一篇传统解析器组合子那样，为每种组合子声明一个便于使用的静态函数或者扩展方法。注意，在上述四种基本组合子外，CPS组合子如要正常工作，需要一个特殊的组合子——EndOfStreamParser，它类似于TokenParser但错误恢复的时候从不尝试插入。这里略过它的实现，直接来看辅助函数的定义：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;span style="color: blue"&gt;&lt;font face="Consolas"&gt;public static class &lt;/font&gt;&lt;/span&gt;&lt;font face="Consolas"&gt;&lt;span style="color: #2b91af"&gt;Parsers&#xD;
&lt;/span&gt;{&#xD;
    &lt;span style="color: blue"&gt;public static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Parser&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Lexeme&lt;/span&gt;&amp;gt; AsParser(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Token &lt;/span&gt;token)&#xD;
    {&#xD;
        &lt;span style="color: #2b91af"&gt;CodeContract&lt;/span&gt;.RequiresArgumentNotNull(token, &lt;span style="color: #a31515"&gt;"token"&lt;/span&gt;);&#xD;
&#xD;
        &lt;span style="color: blue"&gt;return new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;TokenParser&lt;/span&gt;(token);&#xD;
    }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Parser&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Lexeme&lt;/span&gt;&amp;gt; Eos()&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;return new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;EndOfStreamParser&lt;/span&gt;();&#xD;
    }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Parser&lt;/span&gt;&amp;lt;T&amp;gt; Succeed&amp;lt;T&amp;gt;(T value)&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;return new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SucceedParser&lt;/span&gt;&amp;lt;T&amp;gt;(value);&#xD;
    }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Parser&lt;/span&gt;&amp;lt;TResult&amp;gt; SelectMany&amp;lt;T1, T2, TResult&amp;gt;(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Parser&lt;/span&gt;&amp;lt;T1&amp;gt; parser, &lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;T1, &lt;span style="color: #2b91af"&gt;Parser&lt;/span&gt;&amp;lt;T2&amp;gt;&amp;gt; parserSelector, &lt;br&gt;        &lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;T1, T2, TResult&amp;gt; resultSelector）&#xD;
    {&#xD;
        &lt;span style="color: #2b91af"&gt;CodeContract&lt;/span&gt;.RequiresArgumentNotNull(parser, &lt;span style="color: #a31515"&gt;"parser"&lt;/span&gt;);&#xD;
        &lt;span style="color: #2b91af"&gt;CodeContract&lt;/span&gt;.RequiresArgumentNotNull(parserSelector, &lt;span style="color: #a31515"&gt;"parserSelector"&lt;/span&gt;);&#xD;
        &lt;span style="color: #2b91af"&gt;CodeContract&lt;/span&gt;.RequiresArgumentNotNull(resultSelector, &lt;span style="color: #a31515"&gt;"resultSelector"&lt;/span&gt;);&#xD;
&#xD;
        &lt;span style="color: blue"&gt;return new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ConcatenationParser&lt;/span&gt;&amp;lt;T1, T2, TResult&amp;gt;(parser, parserSelector, resultSelector);&#xD;
    }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Parser&lt;/span&gt;&amp;lt;T&amp;gt; Union&amp;lt;T&amp;gt;(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Parser&lt;/span&gt;&amp;lt;T&amp;gt; parser1, &lt;span style="color: #2b91af"&gt;Parser&lt;/span&gt;&amp;lt;T&amp;gt; parser2)&#xD;
    {&#xD;
        &lt;span style="color: #2b91af"&gt;CodeContract&lt;/span&gt;.RequiresArgumentNotNull(parser1, &lt;span style="color: #a31515"&gt;"parser1"&lt;/span&gt;);&#xD;
        &lt;span style="color: #2b91af"&gt;CodeContract&lt;/span&gt;.RequiresArgumentNotNull(parser2, &lt;span style="color: #a31515"&gt;"parser2"&lt;/span&gt;);&#xD;
&#xD;
        &lt;span style="color: blue"&gt;return new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;AlternationParser&lt;/span&gt;&amp;lt;T&amp;gt;(parser1, parser2);&#xD;
    }&#xD;
}&lt;/font&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;这样，我们就能和上一篇一样用Linq的语法来组合Parser了。最后我们还需要一个驱动延迟计算的类：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParserRunner&lt;/span&gt;&amp;lt;T&amp;gt;&#xD;
{&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParserContext &lt;/span&gt;Context { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }&#xD;
    &lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParserFunc&lt;/span&gt;&amp;lt;T&amp;gt; m_runner;&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;ParserRunner(&lt;span style="color: #2b91af"&gt;Parser&lt;/span&gt;&amp;lt;T&amp;gt; parser, &lt;span style="color: #2b91af"&gt;ParserContext &lt;/span&gt;context)&#xD;
    {&#xD;
        &lt;span style="color: #2b91af"&gt;CodeContract&lt;/span&gt;.RequiresArgumentNotNull(parser, &lt;span style="color: #a31515"&gt;"parser"&lt;/span&gt;);&#xD;
        &lt;span style="color: #2b91af"&gt;CodeContract&lt;/span&gt;.RequiresArgumentNotNull(context, &lt;span style="color: #a31515"&gt;"context"&lt;/span&gt;);&#xD;
&#xD;
        m_runner = parser.BuildParser(FinalFuture);&#xD;
        &lt;span style="color: #2b91af"&gt;Debug&lt;/span&gt;.Assert(m_runner != &lt;span style="color: blue"&gt;null&lt;/span&gt;);&#xD;
        Context = context;&#xD;
    }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;T Run(&lt;span style="color: #2b91af"&gt;ForkableScanner &lt;/span&gt;scanner)&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;result = m_runner(scanner, Context);&#xD;
        &lt;span style="color: blue"&gt;return &lt;/span&gt;result.GetResult(Context);&#xD;
    }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParserFunc&lt;/span&gt;&amp;lt;T&amp;gt; FinalFuture(T value)&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;return &lt;/span&gt;(scanner, context) =&amp;gt; context.StopResult(value);&#xD;
    }&#xD;
}&lt;/font&gt;&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;这个类里我们定义了整个解析器最终的一个future——它产生令所有分支判断停止的StopResult。这里最关键的是利用result.GetResult虚方法推进广度优先的分支选取，并且收集这条路线上所有的语法错误。我们所有的语法错误就只有两种：“丢失某单词”（采用了插入方式错误恢复）和“发现了未预期的某单词”（采用了删除方式错误恢复）。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;下面的例子演示了真正的VBF.Compilers.Parsers.Combinators.dll库的用法。真正的VBF库除了定义基本组合子之外还定义了许许多多的重载和扩展函数，基本实现了EBNF的所有功能（而且还可以很容易地继续无限扩展）。用VBF库时Linq语句可以直接在Token上使用，而无需到处使用AsParser扩展方法。此外还有大量的代码，限于逻辑无法全部在博客中展现，大家如想了解最好的方法还是直接下载我的代码观看和试用。&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;span style="color: blue"&gt;&lt;font face="Consolas"&gt;class &lt;/font&gt;&lt;/span&gt;&lt;font face="Consolas"&gt;&lt;span style="color: #2b91af"&gt;Node&#xD;
&lt;/span&gt;{&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Node &lt;/span&gt;LeftChild { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Node &lt;/span&gt;RightChild { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }&#xD;
    &lt;span style="color: blue"&gt;public char &lt;/span&gt;Label { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;Node(&lt;span style="color: blue"&gt;char &lt;/span&gt;label, &lt;span style="color: #2b91af"&gt;Node &lt;/span&gt;left, &lt;span style="color: #2b91af"&gt;Node &lt;/span&gt;right)&#xD;
    {&#xD;
        Label = label;&#xD;
        LeftChild = left;&#xD;
        RightChild = right;&#xD;
    }&#xD;
}&#xD;
&#xD;
&lt;span style="color: blue"&gt;class &lt;/span&gt;&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: #2b91af"&gt;Program&#xD;
&lt;/span&gt;{&#xD;
    &lt;span style="color: blue"&gt;static void &lt;/span&gt;Main(&lt;span style="color: blue"&gt;string&lt;/span&gt;[] args)&#xD;
    {&#xD;
        &lt;span style="color: #2b91af"&gt;Lexicon &lt;/span&gt;binaryTreeSyntax = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Lexicon&lt;/span&gt;();&#xD;
        &lt;span style="color: #2b91af"&gt;LexerState &lt;/span&gt;lex = binaryTreeSyntax.DefaultLexer;&#xD;
&#xD;
        &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//定义词法&#xD;
        &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Token &lt;/span&gt;LEFTPH = lex.DefineToken(&lt;span style="color: #2b91af"&gt;RE&lt;/span&gt;.Symbol(&lt;span style="color: #a31515"&gt;'('&lt;/span&gt;));&#xD;
        &lt;span style="color: #2b91af"&gt;Token &lt;/span&gt;RIGHTPH = lex.DefineToken(&lt;span style="color: #2b91af"&gt;RE&lt;/span&gt;.Symbol(&lt;span style="color: #a31515"&gt;')'&lt;/span&gt;));&#xD;
        &lt;span style="color: #2b91af"&gt;Token &lt;/span&gt;COMMA = lex.DefineToken(&lt;span style="color: #2b91af"&gt;RE&lt;/span&gt;.Symbol(&lt;span style="color: #a31515"&gt;','&lt;/span&gt;));&#xD;
        &lt;span style="color: #2b91af"&gt;Token &lt;/span&gt;LETTER = lex.DefineToken(&lt;span style="color: #2b91af"&gt;RE&lt;/span&gt;.Range(&lt;span style="color: #a31515"&gt;'a'&lt;/span&gt;, &lt;span style="color: #a31515"&gt;'z'&lt;/span&gt;) | &lt;span style="color: #2b91af"&gt;RE&lt;/span&gt;.Range(&lt;span style="color: #a31515"&gt;'A'&lt;/span&gt;, &lt;span style="color: #a31515"&gt;'Z'&lt;/span&gt;));&#xD;
&#xD;
        &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//定义语法&#xD;
        &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParserReference&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Node&lt;/span&gt;&amp;gt; NodeParser = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParserReference&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Node&lt;/span&gt;&amp;gt;();&#xD;
        NodeParser.Reference =&#xD;
            (&lt;span style="color: blue"&gt;from &lt;/span&gt;a &lt;span style="color: blue"&gt;in &lt;/span&gt;LETTER&#xD;
             &lt;span style="color: blue"&gt;from &lt;/span&gt;_1 &lt;span style="color: blue"&gt;in &lt;/span&gt;LEFTPH&#xD;
             &lt;span style="color: blue"&gt;from &lt;/span&gt;left &lt;span style="color: blue"&gt;in &lt;/span&gt;NodeParser&#xD;
             &lt;span style="color: blue"&gt;from &lt;/span&gt;_2 &lt;span style="color: blue"&gt;in &lt;/span&gt;COMMA&#xD;
             &lt;span style="color: blue"&gt;from &lt;/span&gt;right &lt;span style="color: blue"&gt;in &lt;/span&gt;NodeParser&#xD;
             &lt;span style="color: blue"&gt;from &lt;/span&gt;_3 &lt;span style="color: blue"&gt;in &lt;/span&gt;RIGHTPH&#xD;
             &lt;span style="color: blue"&gt;select new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Node&lt;/span&gt;(a.Value[0], left, right))&#xD;
            | &lt;span style="color: #2b91af"&gt;Parsers&lt;/span&gt;.Succeed&amp;lt;&lt;span style="color: #2b91af"&gt;Node&lt;/span&gt;&amp;gt;(&lt;span style="color: blue"&gt;null&lt;/span&gt;);&#xD;
&#xD;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;builder = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ForkableScannerBuilder&lt;/span&gt;(binaryTreeSyntax.CreateScannerInfo());&#xD;
&#xD;
        &lt;span style="color: blue"&gt;string &lt;/span&gt;source = &lt;span style="color: #a31515"&gt;"A(B(,),C(,))"&lt;/span&gt;;&#xD;
        &lt;span style="color: #2b91af"&gt;SourceReader &lt;/span&gt;sr = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SourceReader&lt;/span&gt;(&#xD;
            &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;StringReader&lt;/span&gt;(source));&#xD;
&#xD;
&#xD;
        &lt;span style="color: #2b91af"&gt;ForkableScanner &lt;/span&gt;scanner = builder.Create(sr);&#xD;
        &lt;span style="color: #2b91af"&gt;CompilationErrorManager &lt;/span&gt;errorManager = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;CompilationErrorManager&lt;/span&gt;();&#xD;
&#xD;
        &lt;span style="color: #2b91af"&gt;ParserContext &lt;/span&gt;context = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParserContext&lt;/span&gt;(errorManager, 0, 1);&#xD;
        context.DefineDefaultCompilationErrorInfo(0);&#xD;
&#xD;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;runner = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParserRunner&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Node&lt;/span&gt;&amp;gt;(NodeParser.SuffixedBy(&lt;span style="color: #2b91af"&gt;Parsers&lt;/span&gt;.Eos()), context);&#xD;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;result = runner.Run(scanner);&#xD;
&#xD;
        &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;error &lt;span style="color: blue"&gt;in &lt;/span&gt;errorManager.Errors.OrderBy(e =&amp;gt; e.ErrorPosition.StartLocation.CharIndex))&#xD;
        {&#xD;
            &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(error.ToString());&#xD;
        }&#xD;
    }&#xD;
}&lt;/font&gt;&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;注意from语句已经可以直接使用Token类型，Union操作也可以用“|”运算符代替。由于广度优先分支判断的缘故，整个文法在用于解析之前，必须在后面连接一个EndOfStream，代表解析到文件末尾才算结束。最后的代码还演示了如何将解析错误打印出来。大家可以将输入字符串故意改错，看看是否能够检测出来。还可以试试错误太多太离谱时的性能下降现象。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;在下一篇，我们将正式用这套解析器组合子实现miniSharp语言的语法分析器，并且还会接触到VBF库扩展组合子的各种用法。敬请期待！&lt;/p&gt;&#xD;
&lt;p&gt;希望大家继续关注我的VBF项目：&lt;a href="https://github.com/Ninputer/VBF"&gt;https://github.com/Ninputer/VBF&lt;/a&gt; 和我的微博：&lt;a href="http://weibo.com/ninputer"&gt;http://weibo.com/ninputer&lt;/a&gt; 多谢大家支持！&lt;/p&gt;&lt;img src="http://www.cnblogs.com/Ninputer/aggbug/2096944.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/Ninputer/archive/2011/07/03/2096944.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/Ninputer/archive/2011/06/26/2090645.html</id><title type="text">自己动手开发编译器（八）用Linq编写解析器组合子</title><summary type="text">上回我们说到手写递归下降语法分析器。手写递归下降的方式是目前很多编译器采用的方式，如果你想写一个商业质量的编译器，这是首选的方法。但是，一个完善的递归下降解析器需要的代码量也不少，如果要进行错误报告、错误恢复等等那代码量就更大了。作为懒人，我们有时想要一些小型语言的解析器，最好写起来像直接写文法的产生式一样，最好连错误报告和错误恢复也一并自动解决，可能吗？在过去很长一段时间，人们采用的方法是使用解...</summary><published>2011-06-26T12:28:00Z</published><updated>2011-06-26T12:28:00Z</updated><author><name>装配脑袋</name><uri>http://www.cnblogs.com/Ninputer/</uri></author><link rel="alternate" href="http://www.cnblogs.com/Ninputer/archive/2011/06/26/2090645.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/Ninputer/archive/2011/06/26/2090645.html"/><content type="html">&lt;p&gt;上回我们说到手写递归下降语法分析器。手写递归下降的方式是目前很多编译器采用的方式，如果你想写一个商业质量的编译器，这是首选的方法。但是，一个完善的递归下降解析器需要的代码量也不少，如果要进行错误报告、错误恢复等等那代码量就更大了。作为懒人，我们有时想要一些小型语言的解析器，最好写起来像直接写文法的产生式一样，最好连错误报告和错误恢复也一并自动解决，可能吗？在过去很长一段时间，人们采用的方法是使用解析器生成器（parser generator）。因为不管是LL递归下降解析还是LR的移进归约解析，都可以很容易地用计算机来生成所需的规则。这样的著名工具有yacc、ANTLR等。他们的特点是要用一种专门的语法格式来编写文法产生式，然后经过一个翻译程序生成解析器的代码。在函数式语言发展起来之后，有些人发现函数式语言的抽象能力非常强，甚至能够直接用函数式语言的代码来表达文法的产生式，并将解析器“组合”出来，这称作解析器组合子（parser combinator）。如今C#和VB语言也具有函数式语言相当的特征，特别是还有Linq助阵，以至于在C#和VB中也能享受组合子带来的方式。今天我们就来看看怎么做解析器的组合子。这一篇文字描述可能比较模糊，大家一定要认真地看代码，动手实验。&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;解析器组合子的基本思想是“组合”，首先我们要定义一些最基本的产生式作为基础组合子，然后通过组合的方式拼装出最终的解析器来。回想一下正则表达式的定义，它有两个基本表达式要素——空表达式和字符表达式，以及三个基本运算——并、连接和克林闭包。用基本运算连接基本表达式，就能组成任何正则表达式。解析器组合子也需要定义两个基本的产生式和两个基本运算。&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;首先是产生空字符的产生式：&lt;/p&gt; &lt;table border="1" cellspacing="0" cellpadding="5" width="150"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top" width="150"&gt;G → ε&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;p&gt;这个产生式不产生任何单词，换句话说在解析的时候，不解析任何单词就能成功解析出一个G。因此这个产生式的解析器永远都能解析成功。&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;接下来是产生一个单词t的产生式：&lt;/p&gt; &lt;table border="1" cellspacing="0" cellpadding="5" width="150"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top" width="150"&gt;G → t&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;p&gt;产生式产生一个特定单词t，表示在解析的时候，如果遇到单词t，则成功解析出一个G，而遇到其他单词则会解析失败。&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;再来定义两个基本运算。首先是连接运算：&lt;/p&gt; &lt;table border="1" cellspacing="0" cellpadding="5" width="150"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top" width="150"&gt;G → X Y&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;p&gt;产生式先产生X再产生Y，表示在解析的时候先成功解析X，再成功解析Y，就能成功解析出一个G。&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;接下来是并运算：&lt;/p&gt; &lt;table border="1" cellspacing="0" cellpadding="5" width="150"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top" width="150"&gt;G → X&lt;br&gt;G → Y&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;p&gt;这表示，G既可以产生X也可以产生Y。在解析时无论成功解析X还是Y，都能成功解析出一个G。以上四种基本产生式嵌套使用，就能表示任何上下文无关文法。&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;下面定义解析器函数的原型委托：&lt;/p&gt; &lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;public delegate &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IResult&lt;/span&gt;&amp;lt;T&amp;gt; &lt;span style="color: #2b91af"&gt;ParserFunc&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;out &lt;/span&gt;T&amp;gt;(&lt;span style="color: #2b91af"&gt;ForkableScanner &lt;/span&gt;scanner);&#xD;
&#xD;
&lt;span style="color: blue"&gt;public interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IResult&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;out &lt;/span&gt;T&amp;gt;&#xD;
{&#xD;
    T Value { &lt;span style="color: blue"&gt;get&lt;/span&gt;; }&#xD;
    &lt;span style="color: #2b91af"&gt;ForkableScanner &lt;/span&gt;ReturnedScanner { &lt;span style="color: blue"&gt;get&lt;/span&gt;; }&#xD;
}&#xD;
&#xD;
&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Result&lt;/span&gt;&amp;lt;T&amp;gt; : &lt;span style="color: #2b91af"&gt;IResult&lt;/span&gt;&amp;lt;T&amp;gt;&#xD;
{&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;T Value { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ForkableScanner &lt;/span&gt;ReturnedScanner { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;Result(T value, &lt;span style="color: #2b91af"&gt;ForkableScanner &lt;/span&gt;returnedScanner)&#xD;
    {&#xD;
        Value = value;&#xD;
        ReturnedScanner = returnedScanner;&#xD;
    }&#xD;
}&lt;/font&gt;&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;这并不是VBF.Compilers.Parsers.Combinators库最后采用的Parser函数原型，但它非常适合第一次接触解析器组合子的同学们理解。先看第一行委托的结构，它接受一个ForkableScanner作为参数，然后返回一个IResult&amp;lt;T&amp;gt;类型。首先什么是ForkableScanner呢？我们在词法分析篇定义的Scanner类只能不断地向前Read，而在函数式编程风格中，我们需要一个无副作用的Scanner。简而言之，任何一个个ForkableScanner可以随时“Fork”成两个ForkableScanner，而这两个Scanner任何一个向前扫描，都不会影响另外一个，而且他们各自扫描都回得到同样的单词流。这都是为了处理上述“并”运算的解析器，并运算需要两个分支能够互不影响地单独进行。接下来是返回类型IResult&amp;lt;T&amp;gt;，定义成接口是为了能够加上.NET 4泛型协变的“out”关键字。实际类型Result&amp;lt;T&amp;gt;包含一个解析结果T和成功解析之后返回的Scanner，代表余下的输入流。如果返回的整个Result对象为null，则表示解析失败。后面所有解析器组合子最终都是为了生成这样一个委托的对象，一旦生成了这个对象，就可以马上拿来解析了。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;有了解析器函数原型，下面就开始一样一样地定义基础组合子。所谓组合子其实都是一些静态方法（本例中这些静态方法都定义在Parsers静态类中）、返回类型就是上面的解析器委托。由于返回类型也是委托，所以这些组合子实际上都是一些&lt;strong&gt;高阶函数&lt;/strong&gt;（返回函数的函数）。在我们的代码中常常是一个lambda表达式。较少使用lambda表达是的同学第一次看下面的代码可能会略微感到头晕，只需要稍微休息一下再重新看即可…… &lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;首先是空产生式G → ε，它的组合子是：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;public static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParserFunc&lt;/span&gt;&amp;lt;T&amp;gt; Succeed&amp;lt;T&amp;gt;(T result)&#xD;
{&#xD;
    &lt;span style="color: blue"&gt;return &lt;/span&gt;scanner =&amp;gt; &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Result&lt;/span&gt;&amp;lt;T&amp;gt;(result, scanner);&#xD;
}&lt;/font&gt;&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;这个组合子接受一个参数，表示其解析结果。正如前面所介绍，由Succeed组合子生成的解析器，永远都会成功解析，而且会将设定的结果返回。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;第二种是接受一个单词的的产生式G → t，我们将它的组合子定义成一个扩展方法：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;public static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParserFunc&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Lexeme&lt;/span&gt;&amp;gt; AsParser(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Token &lt;/span&gt;token)&#xD;
{&#xD;
    &lt;span style="color: blue"&gt;return &lt;/span&gt;scanner =&amp;gt;&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;lexeme = scanner.Read();&#xD;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(lexeme.TokenIndex == token.Index)&#xD;
        {&#xD;
            &lt;span style="color: blue"&gt;return new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Result&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Lexeme&lt;/span&gt;&amp;gt;(lexeme, scanner);&#xD;
        }&#xD;
        &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;else&#xD;
        &lt;/span&gt;{&#xD;
            &lt;span style="color: blue"&gt;return null&lt;/span&gt;;&#xD;
        }&#xD;
    };&#xD;
}&lt;/font&gt;&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;注意这个组合子生成的解析器是Lexeme（词素）类型的，词素对象是我们在词法分析阶段定义的，里面包含了词素的类型和具体字符串。这个组合子接受一个Token作为参数，而返回的解析器从输入的Scanner中读取下一个词素，如果该词素的单词类型与传入的Token相匹配，就报告解析成功，否则解析失败。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;第三种是两个文法的连接G → X Y。我们需要定义一个组合子，接受两个已经存在的ParserFunc函数，返回一个新的ParserFunc，先后调用两个传入的ParserFunc：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;public static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParserFunc&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Tuple&lt;/span&gt;&amp;lt;T1, T2&amp;gt;&amp;gt; Concat&amp;lt;T1, T2&amp;gt;(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParserFunc&lt;/span&gt;&amp;lt;T1&amp;gt; parser1, &lt;span style="color: #2b91af"&gt;ParserFunc&lt;/span&gt;&amp;lt;T2&amp;gt; parser2)&#xD;
{&#xD;
&#xD;
    &lt;span style="color: blue"&gt;return &lt;/span&gt;scanner =&amp;gt;&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;result1 = parser1(scanner);&#xD;
&#xD;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(result1 == &lt;span style="color: blue"&gt;null&lt;/span&gt;) &lt;span style="color: blue"&gt;return null&lt;/span&gt;;&#xD;
&#xD;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;result2 = parser2(result1.ReturnedScanner);&#xD;
&#xD;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(result2 == &lt;span style="color: blue"&gt;null&lt;/span&gt;) &lt;span style="color: blue"&gt;return null&lt;/span&gt;;&#xD;
&#xD;
        &lt;span style="color: blue"&gt;return new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Result&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Tuple&lt;/span&gt;&amp;lt;T1, T2&amp;gt;&amp;gt;(&lt;span style="color: #2b91af"&gt;Tuple&lt;/span&gt;.Create(result1.Value, result2.Value), result2.ReturnedScanner);&#xD;
    };&#xD;
}&lt;/font&gt;&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;注意我们返回的ParserFunc结果类型是Tuple&amp;lt;T1, T2&amp;gt;，因为结果中需要同时包含T1和T2。&lt;/p&gt;&#xD;
&lt;p&gt;用这种方式定义的连接运算组合子，在实践中非常难用。因为我们的文法常常要包含不止两个符号连接的情形。假如我们的产生式是G → X Y Z，那么必须写成 X.Concat(Y.Concat(Z))，而它的返回类型是Tuple&amp;lt;T1, Tuple&amp;lt;T2, T3&amp;gt;&amp;gt;，如果要取得结果中的Z，只能写r.Item2.Item2。实际上miniSharp这样的语言，文法中出现7-8个符号连接也不是什么稀奇的事情，而如果都用这个组合子的话，Tuple嵌套会复杂到把人的眼睛都搞晕掉。所以这时我们想到了——Linq。Linq的“组合子”中，有一种叫&lt;strong&gt;SelectMany&lt;/strong&gt;，他给我们带来了这种语法糖：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;int&lt;/span&gt;&amp;gt; la = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;int&lt;/span&gt;&amp;gt;() { 1, 2, 3 };&#xD;
&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;string&lt;/span&gt;&amp;gt; lb = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;string&lt;/span&gt;&amp;gt;() { &lt;span style="color: #a31515"&gt;"a"&lt;/span&gt;, &lt;span style="color: #a31515"&gt;"b"&lt;/span&gt;, &lt;span style="color: #a31515"&gt;"c" &lt;/span&gt;};&#xD;
&#xD;
&lt;span style="color: blue"&gt;var &lt;/span&gt;r = &lt;span style="color: blue"&gt;from &lt;/span&gt;a &lt;span style="color: blue"&gt;in &lt;/span&gt;la&#xD;
        &lt;span style="color: blue"&gt;from &lt;/span&gt;b &lt;span style="color: blue"&gt;in &lt;/span&gt;lb&#xD;
        &lt;span style="color: blue"&gt;select &lt;/span&gt;a + b;&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;它实际可以翻译成：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;r = la.SelectMany(a =&amp;gt; lb.SelectMany(b =&amp;gt; a + b));&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;也就是说，连续from语句，其实是SelectMany扩展方法的嵌套调用。这种调用方法有把lambda嵌套“打平”的功能，非常类似于&lt;strong&gt;单子&lt;/strong&gt;风格中的Bind运算。实际上C#和VB允许在任何自定义类型上扩展SelectMany方法，然后就允许用Linq语法的from去调用。有些人非常鄙视语法糖，但这个语法糖却是无法替代的，这是C#版解析器组合子关键中的关键！由此就可以将连接运算定义成一个SelectMany组合子：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;public static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParserFunc&lt;/span&gt;&amp;lt;TResult&amp;gt; SelectMany&amp;lt;T1, T2, TResult&amp;gt;(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParserFunc&lt;/span&gt;&amp;lt;T1&amp;gt; parser,&#xD;
    &lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;T1, &lt;span style="color: #2b91af"&gt;ParserFunc&lt;/span&gt;&amp;lt;T2&amp;gt;&amp;gt; parserSelector, &lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;T1, T2, TResult&amp;gt; resultSelector)&#xD;
{&#xD;
&#xD;
    &lt;span style="color: blue"&gt;return &lt;/span&gt;scanner =&amp;gt;&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;result1 = parser(scanner);&#xD;
&#xD;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(result1 == &lt;span style="color: blue"&gt;null&lt;/span&gt;) &lt;span style="color: blue"&gt;return null&lt;/span&gt;;&#xD;
&#xD;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;parser2 = parserSelector(result1.Value);&#xD;
&#xD;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;result2 = parser2(result1.ReturnedScanner);&#xD;
&#xD;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(result2 == &lt;span style="color: blue"&gt;null&lt;/span&gt;) &lt;span style="color: blue"&gt;return null&lt;/span&gt;;&#xD;
&#xD;
        &lt;span style="color: blue"&gt;return new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Result&lt;/span&gt;&amp;lt;TResult&amp;gt;(resultSelector(result1.Value, result2.Value), result2.ReturnedScanner);&#xD;
    };&#xD;
}&lt;/font&gt;&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;这个神奇的SelectMany组合子不但消除了嵌套Tuple带来的混乱，还允许我们用一个自定义的select语句生成连接运算的结果，这在生成语法树的时候尤为方便。我们一会再看例子，先继续看最后一种基本组合子。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;最后一种基本组合子是并运算。并运算要求产生式产生两种可能的分支。对应到解析器组合子上，连接运算也要接受两个现成的解析器作为参数，但是选择哪一个呢？这里我们没有办法做分支预测，所以只好采取尝试的办法。有一种尝试的方法就是先试用第一个解析器，如果失败了，再试用第二个，这是一种类似深度优先搜索的办法：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;public static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParserFunc&lt;/span&gt;&amp;lt;T&amp;gt; Union&amp;lt;T&amp;gt;(&lt;span style="color: blue"&gt;this &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParserFunc&lt;/span&gt;&amp;lt;T&amp;gt; parser1, &lt;span style="color: #2b91af"&gt;ParserFunc&lt;/span&gt;&amp;lt;T&amp;gt; parser2)&#xD;
{&#xD;
    &lt;span style="color: blue"&gt;return &lt;/span&gt;scanner =&amp;gt;&#xD;
    {&#xD;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;scanner1 = scanner;&#xD;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;scanner2 = scanner.Fork();&#xD;
&#xD;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;result1 = parser1(scanner1);&#xD;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(result1 != &lt;span style="color: blue"&gt;null&lt;/span&gt;)&#xD;
        {&#xD;
&#xD;
            &lt;span style="color: blue"&gt;return &lt;/span&gt;result1;&#xD;
        }&#xD;
&#xD;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;result2 = parser2(scanner2);&#xD;
&#xD;
        &lt;span style="color: blue"&gt;return &lt;/span&gt;result2;&#xD;
    };&#xD;
}&lt;/font&gt;&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;仅仅使用以上四个组合子函数，就可以来写Parser了！是否还半信半疑呢？我们就来写上一次写过的二叉树字符串表示的语法分析器。忘记的同学建议打开&lt;a href="http://www.cnblogs.com/Ninputer/archive/2011/06/21/2085527.html"&gt;上一篇&lt;/a&gt;看看。我们把文法再抄一遍：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="150"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top" width="150"&gt;&#xD;
&lt;p&gt;N → a ( N, N )&lt;br&gt;N → ε&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;这里面涉及的单词包括字母、左右括号和逗号，我们都用词法分析篇的方法将他们定义出来。然后再用解析器组合子组合出上述文法的解析器。完整的代码如下：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: #2b91af"&gt;Lexicon &lt;/span&gt;binaryTreeSyntax = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Lexicon&lt;/span&gt;();&#xD;
&lt;span style="color: #2b91af"&gt;LexerState &lt;/span&gt;lex = binaryTreeSyntax.DefaultLexer;&#xD;
&#xD;
&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//定义词法&#xD;
&lt;/span&gt;&lt;span style="color: #2b91af"&gt;Token &lt;/span&gt;LEFTPH = lex.DefineToken(&lt;span style="color: #2b91af"&gt;RE&lt;/span&gt;.Symbol(&lt;span style="color: #a31515"&gt;'('&lt;/span&gt;));&#xD;
&lt;span style="color: #2b91af"&gt;Token &lt;/span&gt;RIGHTPH = lex.DefineToken(&lt;span style="color: #2b91af"&gt;RE&lt;/span&gt;.Symbol(&lt;span style="color: #a31515"&gt;')'&lt;/span&gt;));&#xD;
&lt;span style="color: #2b91af"&gt;Token &lt;/span&gt;COMMA = lex.DefineToken(&lt;span style="color: #2b91af"&gt;RE&lt;/span&gt;.Symbol(&lt;span style="color: #a31515"&gt;','&lt;/span&gt;));&#xD;
&lt;span style="color: #2b91af"&gt;Token &lt;/span&gt;LETTER = lex.DefineToken(&lt;span style="color: #2b91af"&gt;RE&lt;/span&gt;.Range(&lt;span style="color: #a31515"&gt;'a'&lt;/span&gt;,&lt;span style="color: #a31515"&gt;'z'&lt;/span&gt;) | &lt;span style="color: #2b91af"&gt;RE&lt;/span&gt;.Range(&lt;span style="color: #a31515"&gt;'A'&lt;/span&gt;,&lt;span style="color: #a31515"&gt;'Z'&lt;/span&gt;));&#xD;
&#xD;
&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//定义语法&#xD;
&lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParserFunc&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Node&lt;/span&gt;&amp;gt; NodeParser = &lt;span style="color: blue"&gt;null&lt;/span&gt;;&#xD;
NodeParser = &#xD;
    (&lt;span style="color: blue"&gt;from &lt;/span&gt;a &lt;span style="color: blue"&gt;in &lt;/span&gt;LETTER.AsParser()&#xD;
     &lt;span style="color: blue"&gt;from &lt;/span&gt;_1 &lt;span style="color: blue"&gt;in &lt;/span&gt;LEFTPH.AsParser()&#xD;
     &lt;span style="color: blue"&gt;from &lt;/span&gt;left &lt;span style="color: blue"&gt;in &lt;/span&gt;NodeParser&#xD;
     &lt;span style="color: blue"&gt;from &lt;/span&gt;_2 &lt;span style="color: blue"&gt;in &lt;/span&gt;COMMA.AsParser()&#xD;
     &lt;span style="color: blue"&gt;from &lt;/span&gt;right &lt;span style="color: blue"&gt;in &lt;/span&gt;NodeParser&#xD;
     &lt;span style="color: blue"&gt;from &lt;/span&gt;_3 &lt;span style="color: blue"&gt;in &lt;/span&gt;RIGHTPH.AsParser()&#xD;
     &lt;span style="color: blue"&gt;select new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Node&lt;/span&gt;(a.Value[0], left, right))&#xD;
    .Union(&lt;span style="color: #2b91af"&gt;Parsers&lt;/span&gt;.Succeed&amp;lt;&lt;span style="color: #2b91af"&gt;Node&lt;/span&gt;&amp;gt;(&lt;span style="color: blue"&gt;null&lt;/span&gt;));&#xD;
&#xD;
&#xD;
&lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//运行部分&#xD;
&lt;/span&gt;&lt;span style="color: #2b91af"&gt;ForkableScannerBuilder &lt;/span&gt;builder = &#xD;
    &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ForkableScannerBuilder&lt;/span&gt;(binaryTreeSyntax.CreateScannerInfo());&#xD;
&#xD;
&lt;span style="color: blue"&gt;string &lt;/span&gt;source = &lt;span style="color: #a31515"&gt;"A(B(,),C(,))"&lt;/span&gt;;&#xD;
&lt;span style="color: #2b91af"&gt;SourceReader &lt;/span&gt;sr = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SourceReader&lt;/span&gt;(&#xD;
    &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;StringReader&lt;/span&gt;(source));&#xD;
&#xD;
&lt;span style="color: #2b91af"&gt;ForkableScanner &lt;/span&gt;scanner = builder.Create(sr);&#xD;
&#xD;
&lt;span style="color: blue"&gt;var &lt;/span&gt;tree = NodeParser(scanner);&lt;/font&gt;&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;重点来看“定义语法”部分，我们来看看产生式都是如何转变为组合子调用的。首先，N → ε转化为了一句Parsers.Succeed调用，代表总能解析成功，而且不消耗输入单词的解析器。然后是N → a ( N, N )，连续的连接转化为一连串Linq的from子句。而其中出现了终结符的地方，则通过AsParser扩展方法将Token转化为Parser。最后再用一个Union组合子将两个N产生式组合到一起，中间我们还看到了用select子句方便地构造想要的解析结果能力。再一次，赞叹SelectMany的神奇力量！初看起来，Linq用来写文法感觉怪怪的，但是习惯了之后，可以非常快速地将各种产生式以Linq语句的方式表达出来。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;解析器组合子最大的优点就是无论实现还是使用都非常简洁，高度体现了函数式编程的优势。但它最大的缺点是难以调试。倘若大家用解析器组合子组合出来的解析器有错误，无法获得想要的解析结果，那可就麻烦了。大家可以试试用Visual Studio的调试器跟踪一下解析器组合子，会发现它的跳转非常频繁，而且根本看不出当前在干什么（因为运行时已经生成了Lambda函数，无法获得组合子传入的参数），也无法看出下一步会运行什么。所以，采用解析器组合子唯一确保正确的做法，就是编写足够的测试用例。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;还有一个重要的问题要解决——语法错误。大家可以试一试输入一个不符合语法的字符串，比如去掉一个括号，看看会是什么结果？答案是直接返回null——和一开始的设定一样。无法知道错误出在了哪里。作为编程语言的解析器，不仅应该能报告错误出现的位置，而且还应该能自动进行某种错误恢复，这样就可以继续完成解析，从而获得所有的语法错误，而不仅仅是头一个。这个功能非常重要，但我们今天设计的解析器组合子结构却非常不擅长进行错误报告和恢复。比如说Union组合子，干脆就是通过解析错误来判断要不要采用这个分支，如果每个分支都错了，它又如何决定报告哪条分支的错误呢？可以设定一些规则，但是我们想要更好、更智能的错误报告和恢复功能。这就留到下一篇，正式介绍VBF库中采用的CPS风格解析器组合子了。敬请期待！&lt;/p&gt;&#xD;
&lt;p&gt;希望大家继续关注我的VBF项目：&lt;a href="https://github.com/Ninputer/VBF"&gt;https://github.com/Ninputer/VBF&lt;/a&gt; 和我的微博：&lt;a href="http://weibo.com/ninputer"&gt;http://weibo.com/ninputer&lt;/a&gt; 多谢大家支持！&lt;/p&gt;&lt;img src="http://www.cnblogs.com/Ninputer/aggbug/2090645.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/Ninputer/archive/2011/06/26/2090645.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/Ninputer/archive/2011/06/21/2085527.html</id><title type="text">自己动手开发编译器（七）递归下降的语法分析器</title><summary type="text">上回我们说到语法分析使用的上下文无关语言，以及描述上下文无关文法的产生式、产生式推导和语法分析树等概念。今天我们就来讨论实际编写语法分析器的方法。今天介绍的这种方法叫做递归下降（recursive descent）法，这是一种适合手写语法编译器的方法，且非常简单。递归下降法对语言所用的文法有一些限制，但递归下降是现阶段主流的语法分析方法，因为它可以由开发人员高度控制，在提供错误信息方面也很有优势。...</summary><published>2011-06-20T16:22:00Z</published><updated>2011-06-20T16:22:00Z</updated><author><name>装配脑袋</name><uri>http://www.cnblogs.com/Ninputer/</uri></author><link rel="alternate" href="http://www.cnblogs.com/Ninputer/archive/2011/06/21/2085527.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/Ninputer/archive/2011/06/21/2085527.html"/><content type="html">&lt;p&gt;上回我们说到语法分析使用的上下文无关语言，以及描述上下文无关文法的产生式、产生式推导和语法分析树等概念。今天我们就来讨论实际编写语法分析器的方法。今天介绍的这种方法叫做&lt;strong&gt;递归下降&lt;/strong&gt;（recursive descent）法，这是一种适合手写语法编译器的方法，且非常简单。递归下降法对语言所用的文法有一些限制，但递归下降是现阶段主流的语法分析方法，因为它可以由开发人员高度控制，在提供错误信息方面也很有优势。就连微软C#官方的编译器也是手写而成的递归下降语法分析器。&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;使用递归下降法编写语法分析器无需任何类库，编写简单的分析器时甚至连前面学习的词法分析库都无需使用。我们来看一个例子：现在有一种表示二叉树的字符串表达式，它的文法是：&lt;/p&gt; &lt;table border="1" cellspacing="0" cellpadding="5" width="150"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top" width="150"&gt;N → &lt;font color="#0000ff"&gt;a (&lt;/font&gt; N&lt;font color="#0000ff"&gt;,&lt;/font&gt; N &lt;font color="#0000ff"&gt;)&lt;br&gt;&lt;/font&gt;N → &lt;font color="#0000ff"&gt;ε&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;p&gt;其中终结符a表示任意一个英文字母，&lt;font color="#000000"&gt;ε&lt;/font&gt;表示空。这个文法的含义是，二叉树的节点要么是空，要么是一个字母开头，并带有一对括号，括号中逗号左边是这个节点的左儿子，逗号右边是这个节点的右儿子。例如字符串 &lt;strong&gt;A(B(,C(,)),D(,))&lt;/strong&gt;就表示这样一棵二叉树：&lt;/p&gt; &lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/Ninputer/201106/201106202147104765.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="bintree" border="0" alt="bintree" src="http://images.cnblogs.com/cnblogs_com/Ninputer/201106/201106202147113818.png" width="184" height="251"&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;注意，文法规定节点即使没有儿子（儿子是空），括号和逗号也是不可省略的，所以只有一个节点的话也要写成&lt;strong&gt;A(,)&lt;/strong&gt;。现在我们要写一个解析器，输入这种字符串，然后在内存中建立起这棵二叉树。其中内存中的二叉树是用下面这样的类来表示的：&lt;/p&gt; &lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top"&gt;&lt;pre &gt;&lt;span style="color: blue"&gt;&lt;font face="Consolas"&gt;class &lt;/font&gt;&lt;/span&gt;&lt;font face="Consolas"&gt;&lt;span style="color: #2b91af"&gt;Node&#xD;
&lt;/span&gt;{&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Node &lt;/span&gt;LeftChild { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Node &lt;/span&gt;RightChild { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }&#xD;
    &lt;span style="color: blue"&gt;public char &lt;/span&gt;Label { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }&#xD;
&#xD;
    &lt;span style="color: blue"&gt;public &lt;/span&gt;Node(&lt;span style="color: blue"&gt;char &lt;/span&gt;label, &lt;span style="color: #2b91af"&gt;Node &lt;/span&gt;left, &lt;span style="color: #2b91af"&gt;Node &lt;/span&gt;right)&#xD;
    {&#xD;
        Label = label;&#xD;
        LeftChild = left;&#xD;
        RightChild = right;&#xD;
    }&#xD;
}&lt;/font&gt;&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;这是一道微软面试题，曾经难倒了不少参加面试的候选人。不知在座各位是否对写出这段程序有信心呢？不少参选者想到了要用栈，或者用递归，去寻找逗号的位置将字符串拆解开来等等方法。但是若是使用递归下降法，这个程序写起来非常容易。我们来看看编写递归下降语法分析器的一般步骤：&lt;/p&gt;&#xD;
&lt;ol&gt;&#xD;
&lt;li&gt;使用一个索引来记录当前扫描的位置。通常将它做成一个整数字段。 &#xD;
&lt;li&gt;为每个非终结符编写一个方法。 &#xD;
&lt;li&gt;如果一个非终结符有超过一个的产生式，则在这个方法中对采用哪个产生式进行&lt;strong&gt;分支预测&lt;/strong&gt;。 &#xD;
&lt;li&gt;处理单一产生式时，遇到正确终结符则将第一步创建的扫描索引位置向前移动；如遇到非终结符则调用第二步中创建的相应方法。 &#xD;
&lt;li&gt;如果需要产生解析的结果（比如本例中的二叉树），在方法返回之前将它构造出来。&lt;/li&gt;&lt;/ol&gt;&#xD;
&lt;p&gt;我们马上来试验一下。首先建立一个类，然后存放一个索引变量来保存当前扫描位置。然后要为每一个非终结符创建一个方法，我们的文法中只有一个非终结符N，所以只需创建一个方法：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;span style="color: blue"&gt;&lt;font face="Consolas"&gt;class &lt;/font&gt;&lt;/span&gt;&lt;font face="Consolas"&gt;&lt;span style="color: #2b91af"&gt;BinaryTreeParser&#xD;
&lt;/span&gt;{&#xD;
    &lt;span style="color: blue"&gt;private string &lt;/span&gt;m_inputString;&#xD;
    &lt;span style="color: blue"&gt;private int &lt;/span&gt;m_index;&#xD;
&#xD;
    &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//初始化输入字符串和索引的构造函数，略&#xD;
&#xD;
    &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Node &lt;/span&gt;ParseNode()&#xD;
    {&#xD;
        &#xD;
    }&#xD;
}&lt;/font&gt;&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;回到刚才的产生式，我们看到非终结符N有两个产生式，所以在ParseNode方法的一开始我们必须做出分支预测。分支预测的方法是&lt;strong&gt;超前查看&lt;/strong&gt;（look ahead）。就是说我们先“偷窥”当前位置前方的字符，然后判断应该用哪个产生式继续分析。非终结符N的两个产生式其中一个会产生a(N, N)这个的结构，而另一个则直接产生空字符串。那现在知道，起码有一种可能就是会遇到一个字母，这时候应该采用N → a(N, N)这个产生式继续分析。那么什么时候应该采用N → ε进行分析呢？我们观察产生式右侧所有出现N的地方，倘若N是空字符串，那么N后面的字符就会直接出现，也就是逗号和右括号。于是这就是我们的分支预测：&lt;/p&gt;&#xD;
&lt;ol&gt;&#xD;
&lt;li&gt;如果超前查看遇到英文字母，预测分支N → a(N, N) &#xD;
&lt;li&gt;如果超前查看遇到逗号、右括号预测分支N → ε&lt;/li&gt;&lt;/ol&gt;&#xD;
&lt;p&gt;转化成代码就是这样：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: #2b91af"&gt;Node &lt;/span&gt;ParseNode()&#xD;
{&#xD;
    &lt;span style="color: blue"&gt;int &lt;/span&gt;lookAheadIndex = m_index;&#xD;
&#xD;
    &lt;span style="color: blue"&gt;char &lt;/span&gt;lookAheadChar = m_inputString[lookAheadIndex];&#xD;
&#xD;
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(&lt;span style="color: #2b91af"&gt;Char&lt;/span&gt;.IsLetter(lookAheadChar))&#xD;
    {&#xD;
        &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//采用N → a(N, N)继续分析&#xD;
    &lt;/span&gt;}&#xD;
    &lt;span style="color: blue"&gt;else if &lt;/span&gt;(lookAheadChar == &lt;span style="color: #a31515"&gt;',' &lt;/span&gt;|| lookAheadChar == &lt;span style="color: #a31515"&gt;')' &lt;/span&gt;)&#xD;
    {&#xD;
        &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//采用N → ε继续分析&#xD;
    &lt;/span&gt;}&#xD;
    &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;else&#xD;
    &lt;/span&gt;{&#xD;
        &lt;span style="color: blue"&gt;throw new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Exception&lt;/span&gt;(&lt;span style="color: #a31515"&gt;"语法错误"&lt;/span&gt;);&#xD;
    }&#xD;
}&#xD;
&lt;/font&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;接下来我们分别来看两个分支怎么处理。先来看N → ε，这种情况下非终结符是个空字符串，所以我们不需要移动当前索引，直接返回null表示空节点。再来看N → a(N, N) 分支，倘若输入的字符串没有任何语法错误，那就应该依次遇到字母、左括号、N、逗号、N右括号。根据上面的规则，凡是遇到终结符，就移动当前索引，直接向前扫描；而要是遇到非终结符，就递归调用相应节点的方法。所以（不考虑语法错误）的完整方法代码如下：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&lt;pre &gt;&lt;font face="Consolas"&gt;&lt;span style="color: #2b91af"&gt;Node &lt;/span&gt;ParseNode()&#xD;
{&#xD;
    &lt;span style="color: blue"&gt;int &lt;/span&gt;lookAheadIndex = m_index;&#xD;
&#xD;
    &lt;span style="color: blue"&gt;char &lt;/span&gt;lookAheadChar = m_inputString[lookAheadIndex];&#xD;
&#xD;
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(&lt;span style="color: #2b91af"&gt;Char&lt;/span&gt;.IsLetter(lookAheadChar))&#xD;
    {&#xD;
        &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//采用N → a(N, N)继续分析&#xD;
        &lt;/span&gt;&lt;span style="color: blue"&gt;char &lt;/span&gt;label = m_inputString[m_index++]; &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//解析字母&#xD;
        &lt;/span&gt;m_index++; &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//解析左括号，因为不需要使用它的值，所以直接跳过&#xD;
&#xD;
        &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Node &lt;/span&gt;left = ParseNode(); &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//非终结符N，递归调用&#xD;
&#xD;
        &lt;/span&gt;m_index++; &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//解析逗号，跳过&#xD;
&#xD;
        &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Node &lt;/span&gt;right = ParseNode(); &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//非终结符N，递归调用&#xD;
&#xD;
        &lt;/span&gt;m_index++; &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//解析右括号，跳过&#xD;
&#xD;
        &lt;/span&gt;&lt;span style="color: blue"&gt;return new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Node&lt;/span&gt;(label, left, right);&#xD;
    }&#xD;
    &lt;span style="color: blue"&gt;else if &lt;/span&gt;(lookAheadChar == &lt;span style="color: #a31515"&gt;',' &lt;/span&gt;|| lookAheadChar == &lt;span style="color: #a31515"&gt;')'&lt;/span&gt;)&#xD;
    {&#xD;
        &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: green"&gt;//采用N → ε继续分析&#xD;
        //无需消耗输入字符，直接返回null&#xD;
        &lt;/span&gt;&lt;span style="color: blue"&gt;return null&lt;/span&gt;;&#xD;
    }&#xD;
    &lt;/font&gt;&lt;font face="Consolas"&gt;&lt;span style="color: blue"&gt;else&#xD;
    &lt;/span&gt;{&#xD;
        &lt;span style="color: blue"&gt;throw new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Exception&lt;/span&gt;(&lt;span style="color: #a31515"&gt;"语法错误"&lt;/span&gt;);&#xD;
    }&#xD;
}&lt;/font&gt;&#xD;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;因为存在语法约束，所以一旦我们完成了分支预测，就能清楚地知道下一个字符或非终结符一定是什么，无需再进行任何判断（除非要进行语法错误检查）。因此根本就不需要寻找逗号在什么位置，我们解析到逗号时，逗号一定就在那，这种感觉是不是很棒？只需要寥寥几行代码就已经写出了一个完整的Parser。大家感兴趣可以继续补全一些辅助代码，然后用真正的字符串输入试验一下，是否工作正常。前面假设输入字符串的语法是正确的，但真实世界的程序总会写错，所以编译器需要能够帮助检查语法错误。在上述程序中加入语法错误检查非常容易，只要验证每个位置的字符，是否真的等于产生式中规定的终结符就可以了。这就留给大家做个练习吧。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;上面我们采用的分支预测法是“人肉观察法”，编译原理书里一般都有一些计算FIRST集合或FOLLOW集合的算法，可以算出一个产生式可能开头的字符，这样就可以用自动的方法写出分支预测，从而实现递归下降语法分析器的自动化生成。ANTLR就是用这种原理实现的一个著名工具。有兴趣的同学可以去看编译原理书。其实我觉得“人肉观察法”在实践中并不困难，因为编程语言的文法都特别有规律，而且我们天天用编程语言写代码，都很有经验了。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;下面我们要研究一下递归下降法对文法有什么限制。首先，我们必须要通过超前查看进行分支预测。&lt;strong&gt;支持递归下降的文法，必须能通过从左往右超前查看k个字符决定采用哪一个产生式&lt;/strong&gt;。我们把这样的文法称作&lt;strong&gt;LL(k)&lt;/strong&gt;文法。这个名字中第一个L表示从左往右扫描字符串，这一点可以从我们的index变量从0开始递增的特性看出来；而第二个L表示&lt;strong&gt;最左推导&lt;/strong&gt;，想必大家还记得上一篇介绍的最左推导的例子。大家可以用调试器跟踪一遍递归下降语法分析器的分析过程，就能很容易地感受到它的确是最左推导的（总是先展开当前句型最左边的非终结符）。最后括号中的k表示需要超前查看k个字符。如果在每个非终结符的解析方法开头超前查看k个字符不能决定采用哪个产生式，那这个文法就不能用递归下降的方法来解析。比如下面的文法：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="150"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top" width="150"&gt;F → id&lt;br&gt;F → ( E )&lt;br&gt;E → F * F&lt;br&gt;E → F / F&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;当我们编写非终结符E的解析方法时，需要在两个E产生式中进行分支预测。然而两个E产生式都以F开头，而且F本身又可能是任意长的表达式，无论超前查看多少字符，都无法判定到底应该用乘号的产生式还是除号的产生式。遇到这种情况，我们可以用&lt;strong&gt;提取左公因式&lt;/strong&gt;的方法，将它转化为LL(k)的文法：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="150"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top" width="150"&gt;F → id&lt;br&gt;F → ( E )&lt;br&gt;&lt;strong&gt;G → * F&lt;br&gt;G → / F&lt;br&gt;&lt;/strong&gt;E → F&lt;strong&gt;G&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;我们将一个左公因式F提取出来，然后将剩下的部分做成一个新的产生式G。在解析G的时候，很容易进行分支预测。而解析E的时候则无需再进行分支预测了。在实践中，提取左公因式不仅可以将文法转化为LL(k)型，还能有助于减少重复的解析，提高性能。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;下面我们来看LL(k)文法的第二个重要的限制——不支持左递归。所谓左递归，就是产生式产生的第一个符号有可能是该产生式本身的非终结符。下面的文法是一个直截了当的左递归例子：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="150"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top" width="150"&gt;F → id&lt;br&gt;E → E + F&lt;br&gt;E → F&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;这个表达式类似于我们上篇末尾得到的无歧义二元运算符的文法。但这个文法存在左递归：E产生的第一个符号就是E本身。我们想像一下，如果在编写E的递归下降解析函数时，直接在函数的开头递归调用自己，输入字符串完全没有消耗，这种递归调用就会变成一种死循环。所以，左递归是必须要消除的文法结构。解决的方法通常是将左递归转化为等价的右递归形式：&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="150"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top" width="150"&gt;F → id&lt;br&gt;E → F&lt;strong&gt;G&lt;/strong&gt;&lt;br&gt;&lt;strong&gt;G → + FG&lt;br&gt;G → &lt;font color="#000000"&gt;ε&lt;/font&gt;&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;大家应该牢牢记住这个例子，这不仅仅是个例子，更是解除大部分左递归的万能公式！我们将要在编写miniSharp语法分析器的时候一次又一次地用到这种变换。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;由于LL(k)文法不能带有左递归和左公因式，很多常见的文法转化成LL(k)之后显得不是那么优雅。有许多程序员更喜欢使用&lt;strong&gt;LR(k)&lt;/strong&gt;文法的语法分析器。LR代表从左到右扫描和最右推导。LR型的文法允许左递归和左公因式，但是并不能用于递归下降的语法分析器，而是要用&lt;strong&gt;移进-归约&lt;/strong&gt;型的语法分析器，或者叫&lt;strong&gt;自底向上&lt;/strong&gt;的语法分析器来分析。我个人认为LR型语法分析器的原理非常优雅和精妙，但是限于本篇的定位我不准备介绍它。我想任何一本编译原理书里都有详细介绍。当然如果未来我的VBF库支持了LR型语法分析器，我也许会追加一些特别篇，谁知道呢？&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;希望大家看了今天这篇文章之后，都能用递归下降法写出一些LL(k)文法的语法分析器来。下一篇我将介绍使用C#和VB中神奇的Linq语法来“组合”出语法分析器来，敬请期待！&lt;/p&gt;&#xD;
&lt;p&gt;希望大家继续关注我的VBF项目：&lt;a href="https://github.com/Ninputer/VBF"&gt;https://github.com/Ninputer/VBF&lt;/a&gt; 和我的微博：&lt;a href="http://weibo.com/ninputer"&gt;http://weibo.com/ninputer&lt;/a&gt; 多谢大家支持！&lt;/p&gt;&lt;img src="http://www.cnblogs.com/Ninputer/aggbug/2085527.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/Ninputer/archive/2011/06/21/2085527.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/Ninputer/archive/2011/06/18/2084383.html</id><title type="text">自己动手开发编译器（六）上下文无关语言和文法</title><summary type="text">上回我们已经学习了语法分析第一阶段——词法分析的原理和工具，介绍了正则表达式、正则语言和DFA等工具。今次我们要开始涉及编译器前端最重要的阶段——语法分析。简单而言，这一步就要完整地分析整个编程语言的语法结构。上回说到词法分析的结果是将输入的字符串分解成一个个的单词流，也就是诸如关键字、标识符这样有特定意义的单词。一种完整的编程语言，必须在此基础上定义出各种声明、语句和表达式的语法规则。观察我们所...</summary><published>2011-06-18T12:27:00Z</published><updated>2011-06-18T12:27:00Z</updated><author><name>装配脑袋</name><uri>http://www.cnblogs.com/Ninputer/</uri></author><link rel="alternate" href="http://www.cnblogs.com/Ninputer/archive/2011/06/18/2084383.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/Ninputer/archive/2011/06/18/2084383.html"/><content type="html">&lt;p&gt;上回我们已经学习了语法分析第一阶段——词法分析的原理和工具，介绍了正则表达式、正则语言和DFA等工具。今次我们要开始涉及编译器前端最重要的阶段——语法分析。简单而言，这一步就要完整地分析整个编程语言的语法结构。上回说到词法分析的结果是将输入的字符串分解成一个个的单词流，也就是诸如关键字、标识符这样有特定意义的单词。一种完整的编程语言，必须在此基础上定义出各种声明、语句和表达式的语法规则。观察我们所熟悉的编程语言，其语法大都有某种递归的性质。例如四则运算与括号的表达式，其每个运算符的两边，都可以是任意的表达式。比如1+a是表达式，(1+a)*(2 – c)也是表达式，((a+b) + c) * (d – e)也是表达式。再比如if语句，其if的块和else的块中还可以再嵌套if语句。我们在词法分析中引入的正则表达式和正则语言无法描述这种结构，如果用DFA来解释，DFA只有有限个状态，它没有办法追溯这种无限递归。所以，编程语言的表达式，并&lt;strong&gt;不是正则语言&lt;/strong&gt;。我们要引入一种表现能力更强的语言——&lt;strong&gt;上下文无关语言&lt;/strong&gt;。&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;要介绍上下文无关语言，我们先来了解一下定义上下文无关文法的工具——&lt;strong&gt;产生式&lt;/strong&gt;的写法。我们还是使用编程语言的表达式作为例子，但这次我们假设表达式只有三种——单个表示变量名标识符、括号括起来的表达式和两个表达式相加。比如a是一个变量表达式，a+b是两个变量表达式相加的表达式，(a+b)是一个括号表达式。我们用符号E来表示一个表达式，那么这三种表达式分别可以定义为：&lt;/p&gt; &lt;table border="1" cellspacing="0" cellpadding="10" width="130"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top" width="300"&gt;E →&lt;font color="#0000ff"&gt; id&lt;/font&gt;&lt;br&gt;E → E &lt;font color="#0000ff"&gt;+&lt;/font&gt; E&lt;br&gt;E → &lt;font color="#0000ff"&gt;(&lt;/font&gt; E &lt;font color="#0000ff"&gt;)&lt;/font&gt; &lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;p&gt;这种形式的定义就叫做&lt;strong&gt;产生式&lt;/strong&gt;。出现在→左侧符号E称作&lt;strong&gt;非终结符&lt;/strong&gt;（&lt;strong&gt;nonterminal symbol&lt;/strong&gt;），代表可以继续产生新符号的“文法变量”。 符号→表示非终结符可以“产生”的东西。而上述产生式中的蓝色&lt;font color="#0000ff"&gt;id&lt;/font&gt;、&lt;font color="#0000ff"&gt;+&lt;/font&gt;、&lt;font color="#0000ff"&gt;（&lt;/font&gt;等符号，是具有固定意义的单词，它们不再会产生新的东西，称作&lt;strong&gt;终结符&lt;/strong&gt;（&lt;strong&gt;terminal symbol&lt;/strong&gt;）。注意，非终结符可以出现在产生式的右侧，这就是具有递归性质文法的来源。产生式经过一系列的&lt;strong&gt;推导&lt;/strong&gt;，就能够生成各种完全由终结符组成的句子。比如，我们演示一下表达式(a + b) + c的推导过程：&lt;/p&gt; &lt;p&gt;E&amp;nbsp; =&amp;gt;&amp;nbsp; E + E&amp;nbsp; =&amp;gt;&amp;nbsp; (E) + E&amp;nbsp; =&amp;gt;&amp;nbsp; (E + E) + E&amp;nbsp; =&amp;gt;&amp;nbsp; (a + E) + E&amp;nbsp; =&amp;gt;&amp;nbsp; (a + b) + E&amp;nbsp; =&amp;gt;&amp;nbsp; (a + b) + c&lt;/p&gt; &lt;p&gt;推导过程中的=&amp;gt;代表将当前句型中的一个非终结符替换成产生式右侧的内容。以上推导过程中，我们每次都将句型中最左边一个非终结符展开，所以这种推导称为&lt;strong&gt;最左推导&lt;/strong&gt;。当然也有&lt;strong&gt;最右推导&lt;/strong&gt;，不同之处就算是每次将句型中最右边的非终结符展开：&lt;/p&gt; &lt;p&gt;E&amp;nbsp; =&amp;gt;&amp;nbsp; E + E&amp;nbsp; =&amp;gt;&amp;nbsp; E + c&amp;nbsp; =&amp;gt;&amp;nbsp; (E) + c&amp;nbsp; =&amp;gt;&amp;nbsp; (E + E) + c&amp;nbsp; =&amp;gt;&amp;nbsp; (E + b) + c&amp;nbsp; =&amp;gt;&amp;nbsp; (a + b) + c&lt;/p&gt; &lt;p&gt;可见，同一个结果可以具有多种不同的推导过程。使用最左推导时，句型的左侧逐渐变得只有终结符；而最右推导正好相反，推导过程中句型的右侧逐渐变得只有终结符，最终结果都是整个句子变为终结符。所有符合文法定义的句子，都可以用文法的产生式推导出来。&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;我们语法分析的目的是解析输入的单词流(a + b) + c，得到它的&lt;strong&gt;语法分析树&lt;/strong&gt;。先来看看语法分析树是什么样的。还是以(a + b) + c为例，语法分析树是这样的：&lt;/p&gt; &lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/Ninputer/201106/201106182026405770.png"&gt;&lt;img style="display: inline" title="gramma1" alt="gramma1" src="http://images.cnblogs.com/cnblogs_com/Ninputer/201106/2011061820264146.png" width="371" height="443"&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;语法分析树的每一个节点都是一个非终结符或者终结符，其中终结符都是树的叶子结点（没有子节点），而非终结符都是有子节点的。一旦我们得到了语法分析树，就可以很容易地进行后续的语义分析，比如这个表达式的语义是“先将a和b代表的变量相加，再把所得的结果与c代表的变量相加”。那么语法分析树是怎么得到的呢，其实刚才的产生式推导过程，就可以顺便建立语法分析树，只要在展开非终结符的同时，在语法分析树中相应的节点下加入非终结展开的结果即可生成。下面我们用动画演示上述产生式通过最左推导和最右推导产生(a + b) + c语法分析树的过程：&lt;/p&gt; &lt;table border="1" cellspacing="0" cellpadding="0" width="745"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top" width="372"&gt;最左推导&lt;/td&gt; &lt;td valign="top" width="372"&gt;最右推导&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="372"&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/Ninputer/201106/201106182026437180.gif"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="最左推导" border="0" alt="最左推导" src="http://images.cnblogs.com/cnblogs_com/Ninputer/201106/201106182026448217.gif" width="371" height="443"&gt;&lt;/a&gt;&lt;/td&gt; &lt;td valign="top" width="372"&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/Ninputer/201106/201106182026468382.gif"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="最右推导" border="0" alt="最右推导" src="http://images.cnblogs.com/cnblogs_com/Ninputer/201106/201106182026493347.gif" width="371" height="443"&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;p&gt;我们可以看到最左推导和最右推导的语法分析树是一样的，这证明用相同的文法解析同样的输入也至少存在两种不同的分析方法。后续篇章介绍的&lt;strong&gt;递归下降法&lt;/strong&gt;就是一种最左推导的分析方法，而另一类非常流行的LR分析器则是基于最右推导的分析方法。目前流行的编译器开发方式是在语法分析阶段构造一棵真正的语法分析树，然后再通过遍历语法树的方法进行后续的分析，所以最左推导和最右推导的过程对我们来讲区别不大。&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;我们刚才举的例子中，表达式(a + b) + c只能有一种语法分析树。但另外一些语法分析的输入，可能存在多种语法分析树， 这称为&lt;strong&gt;歧义&lt;/strong&gt;。刚才的文法其实就是有歧义的（在哪里？请大家思考一下），但为了更清楚地表达歧义的危害，我们再举一个新的例子，它在前面例子中增加了乘法：&lt;/p&gt; &lt;table border="1" cellspacing="0" cellpadding="10" width="130"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top" width="300"&gt;E →&lt;font color="#0000ff"&gt; id&lt;/font&gt;&lt;br&gt;E → E &lt;font color="#0000ff"&gt;+&lt;/font&gt; E&lt;br&gt;E → E &lt;font color="#0000ff"&gt;*&lt;/font&gt; E&lt;br&gt;E → &lt;font color="#0000ff"&gt;(&lt;/font&gt; E &lt;font color="#0000ff"&gt;)&lt;/font&gt; &lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;p&gt;如果用上述产生式推导出表达式a * b + c，就有两种可能的最左推导：&lt;/p&gt; &lt;p&gt;最左推导1：E&amp;nbsp; =&amp;gt;&amp;nbsp; E + E&amp;nbsp; =&amp;gt;&amp;nbsp; E * E + E&amp;nbsp; =&amp;gt;&amp;nbsp; a * E + E&amp;nbsp; =&amp;gt;&amp;nbsp; a * b + E&amp;nbsp; =&amp;gt; a * b + c&lt;/p&gt; &lt;p&gt;最左推导2：E&amp;nbsp; =&amp;gt;&amp;nbsp; E * E&amp;nbsp; =&amp;gt;&amp;nbsp; a * E&amp;nbsp; =&amp;gt;&amp;nbsp; a * E + E&amp;nbsp; =&amp;gt;&amp;nbsp; a * b + E&amp;nbsp; =&amp;gt;&amp;nbsp; a * b + c&lt;/p&gt; &lt;p&gt;这两种推导的语法树是不一样的：&lt;/p&gt; &lt;table border="1" cellspacing="0" cellpadding="0" width="745"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top" width="372"&gt;推导1&lt;/td&gt; &lt;td valign="top" width="372"&gt;推导2&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="372"&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/Ninputer/201106/201106182026504875.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="推导1" border="0" alt="推导1" src="http://images.cnblogs.com/cnblogs_com/Ninputer/201106/201106182026519990.png" width="371" height="347"&gt;&lt;/a&gt;&lt;/td&gt; &lt;td valign="top" width="372"&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/Ninputer/201106/201106182026523154.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="推导2" border="0" alt="推导2" src="http://images.cnblogs.com/cnblogs_com/Ninputer/201106/201106182026536318.png" width="371" height="347"&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;p&gt;我们刚才讨论了，语法分析树将用于下一步的语义分析。而在语义分析中，上述两个语法树的不同主要体现在运算符的优先级上。如果按照推导1的语法树，应该先将a和b相乘，再加上c；而如果按照推导2的语法树，则应该先把b和c相加，再和a相乘。很明显，这两种语义的计算结果是可以不一样的。我们不想编程语言中的同一种表达式有两种语义，所以有歧义的文法是不适合用在语法分析的。实践中应该使用没有歧义的文法来确保同一段程序仅存在唯一一种语法分析树。比如我们可以修改一下上述文法的产生式，让运算符具有左结合的特性，并让乘法一开始就有高于加法的优先级：&lt;/p&gt; &lt;table border="1" cellspacing="0" cellpadding="10" width="130"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top" width="300"&gt;F →&lt;font color="#0000ff"&gt; id&lt;/font&gt;&lt;br&gt;F → &lt;font color="#0000ff"&gt;(&lt;/font&gt; E &lt;font color="#0000ff"&gt;)&lt;br&gt;&lt;/font&gt;T → T &lt;font color="#0000ff"&gt;*&lt;/font&gt; F&lt;br&gt;T → F&lt;br&gt;E → E &lt;font color="#0000ff"&gt;+&lt;/font&gt; T&lt;br&gt; E → T&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;p&gt;修改文法之后，*号的两侧，不允许直接出现带+号的表达式，而只能出现带括号的表达式和变量名；同时，连续的加法或乘法必须从左侧开始运算。这就限制了推导可能进行的方式。在新文法下表达式a * b + c就只存在一种语法分析树了：&lt;/p&gt; &lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/Ninputer/201106/201106182026549274.png"&gt;&lt;img style="display: inline" title="无歧义" alt="无歧义" src="http://images.cnblogs.com/cnblogs_com/Ninputer/201106/201106182026567454.png" width="371" height="539"&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;我们最后用在miniSharp的文法就和这一个非常类似。在实践当中，我们通常需要仔细观察和思考所用的文法是否具有歧义。如果有一些文法拿不定主意，我建议大家去参考C# spec，里面对C#的文法进行极其详细的定义，我相信大家看过Spec之后会更加了解一门现代的编程语言的文法。我也将在后续篇章中介绍一些常见语法结构的设计方法。&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;在本篇的最后，我想再多介绍一点点上下文无关语言。有些同学可能从刚才开始就想问为何这种语言和文法叫做“上下文无关”呢？其实这里的“上下文无关”是指文法中的产生式都可以无条件展开为箭头右侧的内容。另外存在一种上下文相关文法，它的产生式都需要在一定条件下才能展开。上下文相关语言要比上下文无关文法复杂得多，而其没有一种通用的方法可以有效地解析上下文相关语言，因此它也不会用在编程语言的设计当中。同学们也许已经意识到，即使是上下文无关文法和语言，也要比正则表达式和正则语言复杂得多。在此我没有办法详细地描述上下文无关语言的性质，但是我可以给感兴趣的同学稍微科普一下。就像正则表达式存在一种等价的计算模型——有穷自动机——可以用来解析正则语言一样，上下文无关文法也存在一种等价的计算模型——&lt;strong&gt;下推自动机&lt;/strong&gt;（Put-Down Automation, &lt;strong&gt;PDA&lt;/strong&gt;）。下推自动机除了一组有限的状态和状态转换以外，还带有一个无限容量的&lt;strong&gt;栈&lt;/strong&gt;。和有穷自动机不同，下推自动机的状态并不仅根据输入字符和当前状态来进行转移，还要根据栈顶的字符；而且下推自动机还必须决定何时向栈中压入或弹出字符。和有穷自动机类似，下推自动机也存在&lt;strong&gt;非确定性下推自动机&lt;/strong&gt;（NPDA）和&lt;strong&gt;确定性下推自动机&lt;/strong&gt;（DPDA）两种。这两种下推自动机是&lt;strong&gt;不等价&lt;/strong&gt;的。其中非确定性下推自动机对应于整个上下文无关语言，而确定性下推自动机则对应于上下文无关语言的一个真子集。NPDA所具有的“猜测”能力要比NFA强大得多，以至于我们无法很容易地用计算机来模拟。我们只能够模拟DPDA来进行解析。所幸的是，几乎所有编程语言的文法，都是能用一个DPDA所接受的。我们在接下来的篇章中引入的语法分析机制，有些甚至还达不到DPDA的能力，也就是说我们只能处理上下文无关文法中的一小部分。但即使是这一小部分，也足够将C#这样的语法描述出来了。&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;下一篇中我们将介绍递归下降语法分析器的实现方法。希望大家继续关注我的VBF项目：&lt;a href="https://github.com/Ninputer/VBF"&gt;https://github.com/Ninputer/VBF&lt;/a&gt; 和我的微博：&lt;a href="http://weibo.com/ninputer"&gt;http://weibo.com/ninputer&lt;/a&gt; 多谢大家支持！&lt;/p&gt;&lt;img src="http://www.cnblogs.com/Ninputer/aggbug/2084383.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/Ninputer/archive/2011/06/18/2084383.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/Ninputer/archive/2011/06/16/2083092.html</id><title type="text">自己动手开发编译器特别篇&amp;mdash;&amp;mdash;用词法分析器解决背诵圣经问题</title><summary type="text">这几天比较忙，让大家久等了。但是我语法分析篇还需要一些准备，所以今天带来一个特别娱乐项目。其实也正好想多举一些例子，介绍VBF.Compilers.Scanner库的使用方法。今天的问题来自于一道腾讯的PHP面试题，原题如下： 我们碰到了大麻烦，一个新来的传教士惹恼了上帝，上帝很愤怒，要求我们把圣经背熟，直至他说哪个单词，我们就要飞快的回答出这个单词在第几行第几个单词位置。听说你是个优秀的程序员，...</summary><published>2011-06-16T13:30:00Z</published><updated>2011-06-16T13:30:00Z</updated><author><name>装配脑袋</name><uri>http://www.cnblogs.com/Ninputer/</uri></author><link rel="alternate" href="http://www.cnblogs.com/Ninputer/archive/2011/06/16/2083092.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/Ninputer/archive/2011/06/16/2083092.html"/><content type="html">&lt;p&gt;这几天比较忙，让大家久等了。但是我语法分析篇还需要一些准备，所以今天带来一个特别娱乐项目。其实也正好想多举一些例子，介绍VBF.Compilers.Scanner库的使用方法。今天的问题来自于一道腾讯的PHP面试题，原题如下：&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;table border="1" cellspacing="0" cellpadding="5" width="600"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top" width="598"&gt; &lt;p&gt;我们碰到了大麻烦，一个新来的传教士惹恼了&lt;a name="baidusnap2"&gt;&lt;/a&gt;上帝，上帝很愤怒，要求我们把圣经背熟，直至他说哪个单词，我们就要飞快的回答出这个单词在第几行第几个单词位置。听说你是个优秀的程序员，那么髟助我们完成这个不可能的任务吧。  &lt;p&gt;要求如下：  &lt;p&gt;1）/myworks/example/bbe.txt，98版本英文圣经一本  &lt;p&gt;2）输入部分要求如下：php ./example.php [单词]  &lt;p&gt;3）输出部分如下：[单词] 1,2 2,4 5,6表示：此单词在1行2列（第二个单词），2行4列...&lt;/p&gt; &lt;p&gt;说明：  &lt;p&gt;1）此文本4MB之巨...  &lt;p&gt;2）单词的含义：由英文字母（大小写），数字（0-9）组成的串  &lt;p&gt;3）提供给你的机器OS为ubuntu 9.10，内存只有1G，而且，很不幸的，其中700M用来做了别的  &lt;p&gt;4）上机考试不允许上网，但我装了man文档以及读取CHM以及PDF的阅读器，在电脑的桌面的CHM文件夹中，有相应的PHP参考手册  &lt;p&gt;5）算法复杂度要求不能大于O（N^2）（就是N的平方）  &lt;p&gt;6）什么？PHP低效且用起来不顺手，好的，你可以用别的语言来实现。但注意：提供给你的机器上只有python 2.4/perl 5.8/gcc[g++] 4.1&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;原题是要求使用PHP的，我们只是娱乐，不是真面试，当然就无视各种规定了。这道题不必使用词法分析的原理，可以写出很快的算法。但是用词法分析库来实现也是个不错的注意，因为DFA词法分析是O(N)的算法而且实际执行起来效率相当不错。下面我们就用VBF.Compilers.Scanner库来解决这道题：&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;table border="1" cellspacing="0" cellpadding="5" width="100%"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top"&gt;&lt;pre &gt;&lt;span style="color: blue"&gt;Imports &lt;/span&gt;VBF.Compilers.Scanners&#xD;
&lt;span style="color: blue"&gt;Imports &lt;/span&gt;VBF.Compilers.Scanners.&lt;span style="color: #2b91af"&gt;RegularExpression&#xD;
&lt;/span&gt;&lt;span style="color: blue"&gt;Imports &lt;/span&gt;System.IO&#xD;
&#xD;
&lt;span style="color: blue"&gt;Module &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Program&#xD;
&#xD;
    &lt;/span&gt;&lt;span style="color: blue"&gt;Sub &lt;/span&gt;Main(args &lt;span style="color: blue"&gt;As String&lt;/span&gt;())&#xD;
        &lt;span style="color: blue"&gt;Dim &lt;/span&gt;findword = args(0)&#xD;
&#xD;
        &lt;span style="color: blue"&gt;Dim &lt;/span&gt;bibleLexicon &lt;span style="color: blue"&gt;As New &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Lexicon&lt;/span&gt;()&#xD;
        &lt;span style="color: blue"&gt;Dim &lt;/span&gt;lex = bibleLexicon.DefaultLexer&#xD;
&#xD;
        &lt;span style="color: green"&gt;'定义要寻找单词的词法&#xD;
         &lt;/span&gt;&lt;span style="color: blue"&gt;Dim &lt;/span&gt;TARGET = lex.DefineToken(Literal(findword))&#xD;
        &lt;span style="color: green"&gt;'定义一般单词的词法&#xD;
         &lt;/span&gt;&lt;span style="color: blue"&gt;Dim &lt;/span&gt;WORD = lex.DefineToken((Range(&lt;span style="color: #a31515"&gt;"0"c&lt;/span&gt;, &lt;span style="color: #a31515"&gt;"9"c&lt;/span&gt;) &lt;span style="color: blue"&gt;Or&#xD;
                                    &lt;/span&gt;Range(&lt;span style="color: #a31515"&gt;"a"c&lt;/span&gt;, &lt;span style="color: #a31515"&gt;"z"c&lt;/span&gt;) &lt;span style="color: blue"&gt;Or&#xD;
                                    &lt;/span&gt;Range(&lt;span style="color: #a31515"&gt;"A"c&lt;/span&gt;, &lt;span style="color: #a31515"&gt;"Z"c&lt;/span&gt;)).Many1)&#xD;
        &lt;span style="color: green"&gt;'定义换行&#xD;
         &lt;/span&gt;&lt;span style="color: blue"&gt;Dim &lt;/span&gt;LF = lex.DefineToken(Symbol(vbLf) &lt;span style="color: blue"&gt;Or &lt;/span&gt;Literal(vbCrLf))&#xD;
        &lt;span style="color: green"&gt;'定义其他所有符号均忽略&#xD;
         &lt;/span&gt;&lt;span style="color: blue"&gt;Dim &lt;/span&gt;OTHER = lex.DefineToken(Range(ChrW(0), ChrW(255)))&#xD;
&#xD;
        &lt;span style="color: blue"&gt;Dim &lt;/span&gt;bibleScanner &lt;span style="color: blue"&gt;As New &lt;/span&gt;&lt;span style="color: #2b91af"&gt;PeekableScanner&lt;/span&gt;(bibleLexicon.CreateScannerInfo())&#xD;
        bibleScanner.SetSkipTokens(OTHER.Index)&#xD;
&#xD;
        &lt;span style="color: blue"&gt;Using &lt;/span&gt;sr &lt;span style="color: blue"&gt;As New &lt;/span&gt;&lt;span style="color: #2b91af"&gt;StreamReader&lt;/span&gt;(&lt;span style="color: #a31515"&gt;"bible.txt"&lt;/span&gt;)&#xD;
            &lt;span style="color: blue"&gt;Dim &lt;/span&gt;source &lt;span style="color: blue"&gt;As New &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SourceReader&lt;/span&gt;(sr)&#xD;
            bibleScanner.SetSource(source)&#xD;
&#xD;
            &lt;span style="color: blue"&gt;Dim &lt;/span&gt;scannerWatch &lt;span style="color: blue"&gt;As New &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Stopwatch&#xD;
&#xD;
            &lt;/span&gt;&lt;span style="color: blue"&gt;Dim &lt;/span&gt;lines = 1, columns = 1, totalwords = 0, targetwords = 0&#xD;
            scannerWatch.Start()&#xD;
            &lt;span style="color: blue"&gt;Do While &lt;/span&gt;bibleScanner.Peek() &amp;lt;&amp;gt; bibleScanner.ScannerInfo.EndOfStreamTokenIndex&#xD;
                &lt;span style="color: blue"&gt;Dim &lt;/span&gt;x &lt;span style="color: blue"&gt;As &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Lexeme &lt;/span&gt;= bibleScanner.Read()&#xD;
&#xD;
                &lt;span style="color: blue"&gt;Select Case &lt;/span&gt;x.TokenIndex&#xD;
                    &lt;span style="color: blue"&gt;Case &lt;/span&gt;TARGET.Index&#xD;
                        &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #a31515"&gt;"第{0}行,第{1}列"&lt;/span&gt;, lines, columns, x.Value)&#xD;
                        columns += 1&#xD;
                        targetwords += 1&#xD;
                        totalwords += 1&#xD;
                    &lt;span style="color: blue"&gt;Case &lt;/span&gt;WORD.Index&#xD;
                        columns += 1&#xD;
                        totalwords += 1&#xD;
                    &lt;span style="color: blue"&gt;Case &lt;/span&gt;LF.Index&#xD;
                        lines += 1&#xD;
                        columns = 1&#xD;
                &lt;span style="color: blue"&gt;End Select&#xD;
            Loop&#xD;
            &lt;/span&gt;scannerWatch.Stop()&#xD;
&#xD;
            &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #a31515"&gt;"总单词数: " &lt;/span&gt;&amp;amp; totalwords)&#xD;
            &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #a31515"&gt;"目标单词出现次数: " &lt;/span&gt;&amp;amp; targetwords)&#xD;
            &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #a31515"&gt;"消耗时间: " &lt;/span&gt;&amp;amp; scannerWatch.ElapsedMilliseconds)&#xD;
        &lt;span style="color: blue"&gt;End Using&#xD;
    End Sub&#xD;
&#xD;
End Module&#xD;
&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;这就是完整的代码。为了统计是第几个单词，我们按照题目的规定，定义了一般单词的词法，目标单词的词法，并且忽略所有其他字符（设定为SkipTokens）。分析过程就是不断读取下一个单词，直到文件的末尾。注意，这次我展示的是具有超前查看功能的&lt;strong&gt;PeekableScanner&lt;/strong&gt;类，它可以超前查看任意多个单词，其实也可以用普通的Scanner而且性能更好。现在大家可以试试圣经中出现了什么单词，比如我们试一下apple：&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;table border="1" cellspacing="0" cellpadding="5" width="300"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top" width="300"&gt;&lt;pre&gt;第5769行,第29列&#xD;
第14112行,第8列&#xD;
第16578行,第14列&#xD;
第17558行,第8列&#xD;
第17646行,第25列&#xD;
第20351行,第34列&#xD;
第22304行,第23列&#xD;
第22908行,第31列&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;可见我手里这本圣经出现了8次apple（我特意看了前面，亚当和夏娃吃的是fruit，不是apple……）。如果搜microsoft的话发现圣经中并没有出现，怪不得苹果最近这么风光……&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;源代码和圣经文件可以在这里下载：&lt;a href="http://files.cnblogs.com/Ninputer/BibleFinder.7z"&gt;BibleFinder.7z&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;另外有不少同学问虎书是什么书，这里有龙书、虎书和鲸书的介绍：&lt;a title="http://unistd.blog.51cto.com/1126453/260372" href="http://unistd.blog.51cto.com/1126453/260372"&gt;http://unistd.blog.51cto.com/1126453/260372&lt;/a&gt;。下一篇开始我们正式进入语法分析部分。希望大家继续关注我的VBF项目：&lt;a href="https://github.com/Ninputer/VBF"&gt;https://github.com/Ninputer/VBF&lt;/a&gt; 和我的微博：&lt;a href="http://weibo.com/ninputer"&gt;http://weibo.com/ninputer&lt;/a&gt; 多谢大家支持！&lt;/p&gt;&lt;img src="http://www.cnblogs.com/Ninputer/aggbug/2083092.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/Ninputer/archive/2011/06/16/2083092.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry></feed>
