﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-λ-calculus（惊愕到手了欧耶，GetBlogPostIds.aspx）-随笔分类-跟vczh看实例学编译原理</title><link>http://www.cppblog.com/vczh/category/20808.html</link><description>【QQ：343056143】【Email：vczh@163.com】【新浪微博：http://weibo.com/vczh】</description><language>zh-cn</language><lastBuildDate>Tue, 11 Feb 2014 05:19:59 GMT</lastBuildDate><pubDate>Tue, 11 Feb 2014 05:19:59 GMT</pubDate><ttl>60</ttl><item><title>跟vczh看实例学编译原理——一：Tinymoe的设计哲学</title><link>http://www.cppblog.com/vczh/archive/2014/02/11/205702.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Tue, 11 Feb 2014 04:53:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2014/02/11/205702.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/205702.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2014/02/11/205702.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/205702.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/205702.html</trackback:ping><description><![CDATA[<p>自从《序》胡扯了快一个月之后，终于迎来了正片。之所以系列文章叫《看实例学编译原理》，是因为整个系列会通过带大家一步一步实现Tinymoe的过程，来介绍编译原理的一些知识点。
</p><p>
&#160;</p><p>但是第一个系列还没到开始处理Tinymoe源代码的时候，首先的跟大家讲一讲我设计Tinymoe的故事。为什么这种东西要等到现在才讲呢，因为之前没有文档，将了也是白讲啊。Tinymoe在github的wiki分为两部分，一部分是介绍语法的，另一部分是介绍一个最小的标准库是如何实现出来的，地址在 <a href="https://github.com/vczh/tinymoe/wiki">https://github.com/vczh/tinymoe/wiki</a> 不带问号的那些都是写完了的。
</p><p style="text-align: justify"><h2>系列文章的目标
</h2></p><p>在介绍Tinymoe之前，先说一下这个系列文章的目标。Ideally，只要一个人看完了这个系列，他就可以在下面这些地方得到<strong>入门</strong>：
</p><ul><li><div style="text-align: justify"><span style="font-size:10pt">词法分析
</span></div></li><li><div style="text-align: justify"><span style="font-size:10pt">歧义与不歧义的语法分析
</span></div></li><li><div style="text-align: justify"><span style="font-size:10pt">语义分析
</span></div></li><li><div style="text-align: justify"><span style="font-size:10pt">符号表
</span></div></li><li><div style="text-align: justify"><span style="font-size:10pt">全文CPS变换
</span></div></li><li><div style="text-align: justify"><span style="font-size:10pt">编译生成高效的其他语言的代码
</span></div></li><li><div style="text-align: justify"><span style="font-size:10pt">编译生成自己的指令集
</span></div></li><li><div style="text-align: justify"><span style="font-size:10pt">带GC的虚拟机
</span></div></li><li><div style="text-align: justify"><span style="font-size:10pt">类型推导（intersection type，union type，concept mapping）
</span></div></li><li><div style="text-align: justify"><span style="font-size:10pt">跨过程分析（inter-procedural analyzing）
</span></div></li></ul><p>
&#160;</p><p>当然，这并不能让你成为一个大牛，但是至少自己做做实验，搞一点高大上的东西骗师妹们是没有问题了。
</p><p style="text-align: justify"><h2>Tinymoe设计的目标
</h2></p><p>虽然想法很多年前就已经有了，但是这次我想把它实现出来，是为了完成《如何设计一门语言》的后续。光讲大道理是没有意义的，至少得有一个例子，让大家知道这些事情到底是什么样子的。因此Tinymoe有一点教学的意义，不管是使用它还是实现它。
</p><p>
&#160;</p><p>首先，<strong>处理Tinymoe需要的知识点多，用于编译原理教学</strong>。既然是为了展示编译原理的基础知识，因此语言本身不可能是那种烂大街的C系列的东西。当然除了知识点以外，还会让大家深刻的理解到，<strong>难实现和难用，是完全没有关系的</strong>！Tinymoe用起来可爽了，啊哈哈哈哈哈。
</p><p>
&#160;</p><p>其次，<strong>Tinymoe容易嵌入其他语言的程序，作为DSL使用，可以调用宿主程序提供的功能</strong>。这严格的来讲不算语言本身的功能，而是实现本身的功能。就算是C++也可以设计为嵌入式，lua也可以被设计为编译成exe的。一个语言本身的设计并不会对如何使用它有多大的限制。为了让大家看了这个系列之后，可以写出至少可用的东西，而不仅仅是写玩具，因此这也是设计的目标之一。
</p><p>
&#160;</p><p>第三，<strong>Tinymoe语法优化于描述复杂的逻辑</strong>，而不是优化与复杂的数据结构和算法（虽然也可以）。Tinymoe本身是不存在任何细粒度控制内存的能力的，而且虽然可以实现复杂的数据结构和算法，但是本身描述这些东西最多也就跟JavaScript一样容易——其实就是不容易。但是Tinymoe设计的时候，是为了让大家把Tinymoe当成是一门可以设计DSL的语言，因此对复杂逻辑的描述能力特别强。唯一的前提就是，你懂得如何给Tinymoe写库。很好的使用和很好地实现一个东西是相辅相成的。我在设计Tinymoe之初，很多pattern我也不知道，只是因为设计Tinymoe遵循了科学的方法，因此最后我发现Tinymoe竟然具有如此强大的描述能力。当然对于读者们本身，也会在阅读系列文章的有类似的感觉。
</p><p>
&#160;</p><p>最后，<strong>Tinymoe是一个动态类型语言</strong>。这纯粹是我的个人爱好了。对一门动态类型语言做静态分析那该多有趣啊，啊哈哈哈哈哈哈。
</p><p style="text-align: justify"><h2>Tinymoe的设计哲学
</h2></p><p>当然我并不会为了写文章就无线提高Tinymoe的实现难度的。为了把他控制在一个正常水平，因此设计Tinymoe的第一条就是：
</p><p>
&#160;</p><p><strong>一、小规模的语言核心+大规模的标准库
</strong></p><p>
&#160;</p><p>其实这跟C++差不多。但是C++由于想做的事情实在是太多了，譬如说视图包涵所有范式等等，因此就算这么做，仍然让C++本身包含的东西过于巨大（其实我还是觉得C++不难怎么办）。
</p><p>
&#160;</p><p>语言核心小，实现起来当然容易。<strong>但是你并不能为了让语言核心小就牺牲什么功能</strong>。因此精心设计一个核心是必须的，因为所有你想要但是不想加入语言的功能，从此就可以用库来实现了。
</p><p>
&#160;</p><p>譬如说，Tinymoe通过有条件地暴露continuation，要求编译器在编译Tinymoe的时候做一次全文CPS变换。这个东西说容易也不是那么容易，但是至少比你做分支循环异常处理什么的全部加起来要简单多了吧。所以我只提供continuation，剩下的控制流全部用库来做。这样有三个好处：
</p><ol><li><div style="text-align: justify"><span style="font-size:10pt"><strong>语言简单，实现难度降低</strong>。
</span></div></li><li><div style="text-align: justify"><span style="font-size:10pt"><strong>为了让库可以发挥应有的作用，语言的功能的选择十分的正交化</strong>。不过这仍然在一定的程度上提高了学习的难度。但是并不是所有人都需要写库对吧，很多人只需要会用库就够了。通过一点点的牺牲，正交化可以充分发挥程序员的想象能力。这对于以DSL为目的的语言来说是不可或缺的。
</span></div></li><li><div style="text-align: justify"><span style="font-size:10pt"><strong>标准库本身可以作为编译器的测试用例</strong>。你只需要准备足够多的测试用例来运行标准库，那么你只要用C++（假设你用C++来实现Tinymoe）来跑他们，那所有的标准库都会得到运行。运行结果如果对，那你对编译器的实现也就有信心了。为什么呢，因为标准库大量的使用了语言的各种功能，而且是无节操的使用。如果这样都能过，那普通的程序就更能过了。
</span></div></li></ol><p>
&#160;</p><p>说了这么多，那到底什么是小规模的语言核心呢？这在Tinymoe上有两点体现。
</p><p>
&#160;</p><p>第一点，就是<strong>Tinymoe的语法元素少</strong>。一个Tinymoe表达式无非就只有三类：函数调用、字面量和变量、操作符。字面量就是那些数字字符串什么的。当Tinymoe的函数的某一个参数指定为不定个数的时候你还得提供一个tuple。委托（在这里是函数指针和闭包的统称）和数组虽然也是Tinymoe的原生功能之一，但是对他们的操作都是通过函数调用来实现的，没有特殊的语法。
</p><p>
&#160;</p><p>简单地讲，就是除了下面这些东西以外你不会见到别的种类的表达式了：
</p><p style="text-align: justify"><span style="font-size:10pt">1
</span></p><p style="text-align: justify"><span style="font-size:10pt">"text"
</span></p><p style="text-align: justify"><span style="font-size:10pt">sum from 1 to 100
</span></p><p style="text-align: justify"><span style="font-size:10pt">sum of (1, 2, 3, 4, 5)
</span></p><p style="text-align: justify"><span style="font-size:10pt">(1+2)*(3+4)
</span></p><p style="text-align: justify"><span style="font-size:10pt">true
</span></p><p>
&#160;</p><p>一个Tinymoe语句的种类就更少了，要么是一个函数调用，要么是block，要么是连在一起的几个block：
</p><p style="text-align: justify"><span style="font-size:10pt">do something bad
</span></p><p style="text-align: justify">
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">repeat with x from 1 to 100
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;do something bad with x
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p style="text-align: justify">
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">try
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;do something bad
</span></p><p style="text-align: justify"><span style="font-size:10pt">catch exception
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;do something worse
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p>
&#160;</p><p>有人可能会说，那repeat和try-catch就不是语法元素吗？这个真不是，他们是标准库定义好的函数，跟你自己声明的函数没有任何特殊的地方。
</p><p>
&#160;</p><p>这里其实还有一个有意思的地方："repeat with x from 1 to 100"的x其实是循环体的参数。Tinymoe是如何给你自定义的block开洞的呢？不仅如此，Tinymoe的函数还可以声明"引用参数"，也就是说调用这个函数的时候你只能把一个变量放进去，函数里面可以读写这个变量。这些都是怎么实现的呢？学下去就知道了，啊哈哈哈哈。
</p><p>
&#160;</p><p>Tinymoe的声明也只有两种，第一种是函数，第二种是符号。函数的声明可能会略微复杂一点，不过除了函数头以外，其他的都是类似配置一样的东西，几乎都是用来定义"catch函数在使用的时候必须是连在try函数后面"啊，"break只能在repeat里面用"啊，诸如此类的信息。
</p><p>
&#160;</p><p>Tinymoe的符号十分简单，譬如说你要定义一年四季的符号，只需要这么写：
</p><p style="text-align: justify"><span style="font-size:10pt">symbol spring
</span></p><p style="text-align: justify"><span style="font-size:10pt">symbol summer
</span></p><p style="text-align: justify"><span style="font-size:10pt">symbol autumn
</span></p><p style="text-align: justify"><span style="font-size:10pt">symbol winter
</span></p><p>
&#160;</p><p>symbol是一个"与众不同的值"，也就是说你在两个module下面定义同名的symbol他们也是不一样的。所有symbol之间都是不一样的，可以用=和&lt;&gt;来判断。symbol就是靠"不一样"来定义其自身的。
</p><p>
&#160;</p><p>至于说，那为什么不用enum呢？因为Tinymoe是动态类型语言，enum的类型本身是根本没有用武之地的，所以干脆就设计成了symbol。
</p><p>
&#160;</p><p>第二点，<strong>Tinymoe除了continuation和select-case以外，没有其他原生的控制流支持</strong>。
</p><p>
&#160;</p><p>这基本上归功于先辈发明continuation passing style transformation的功劳，细节在以后的系列里面会讲。心急的人可以先看 <a href="https://github.com/vczh/tinymoe/blob/master/Development/Library/StandardLibrary.txt">https://github.com/vczh/tinymoe/blob/master/Development/Library/StandardLibrary.txt</a> 。这个文件暂时包含了Tinymoe的整个标准库，里面定义了很多if-else/repeat/try-catch-finally等控制流，甚至连coroutine都可以用continuation、select-case和递归来做。
</p><p>
&#160;</p><p>这也是<strong>小规模的语言核心+大规模的标准库</strong>所要表达的意思。如果可以提供一个feature A，通过他来完成其他必要的feature B0, B1, B2&#8230;的同时，将来说不定还有人可以出于自己的需求，开发DSL的时候定义feature C，那么只有A需要保留下来，所有的B和C都将使用库的方法来实现。
</p><p>
&#160;</p><p>这么做并不是完全有益无害的，只是坏处很小，在"Tinymoe的实现难点"里面会详细说明。<strong>
		</strong></p><p>
&#160;</p><p><strong>二、扩展后的东西跟原生的东西外观一致
</strong></p><p>
&#160;</p><p>这是很重要的。如果扩展出来的东西跟原生的东西长得不一样，用起来就觉得很傻逼。Java的string不能用==来判断内容就是这样的一个例子。虽然他们有的是理由证明==的反直觉设计是对的——但是反直觉就是反直觉，就是一个大坑。
</p><p>
&#160;</p><p>这种例子还有很多，譬如说go的数组和表的类型啦，go本身如果不要数组和表的话，是写不出长得跟原生数组和表一样的数组和表的。其实这也不是一个大问题，问题是go给数组和表的样子搞特殊化，还有那个反直觉的slice的赋值问题（会合法溢出！），类似的东西实在是太多了。一个东西特例太多，坑就无法避免。所以其实在我看来，go还不如给C语言加上erlang的actor功能了事。
</p><p>
&#160;</p><p>反而C++在这件事情上就做得很好。如果你对C++不熟悉的话，有时候根本分不清什么是编译器干的，什么是标准库干的。譬如说static_cast和dynamic_cast长得像一个模板函数，因此boost就可以用类似的手法加入lexical_cast和针对shared_ptr的static_pointer_cast和dynamic_pointer_cast，整个标准库和语言本身浑然一体。这样子做的好处是，当你在培养对语言本身的直觉的时候，你也在培养对标准库的直觉，培养直觉这件事情你不用做两次。你对一个东西的直觉越准，学习新东西的速度就越快。所以C++的设计刚好可以让你在熬过第一个阶段的学习之后，后面都觉得无比的轻松。
</p><p>
&#160;</p><p>不过具体到Tinymoe，因为Tinymoe本身的语法元素太少了，所以这个做法在Tinymoe身上体现得不明显。
</p><p style="text-align: justify"><h2>Tinymoe的实现难点
</h2></p><p>首先，<strong>语法分析需要对Tinymoe程序处理三遍</strong>。Tinymoe对于语句设计使得对一个Tinymoe程序做语法分析不是那么直接（虽然比C++什么的还是容易多了）。举个例子：
</p><p style="text-align: justify"><span style="font-size:10pt">module hello world
</span></p><p style="text-align: justify">
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">phrase sum from (lower bound) to (upper bound)
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#8230;
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p style="text-align: justify">
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">sentence print (message)
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#8230;
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p style="text-align: justify">
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">phrase main
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;print sum from 1 to 100
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p>
&#160;</p><p>第一遍分析是词法分析，这个时候得把每一个token的行号记住。第二遍分析是不带歧义的语法分析，目标是把所有的函数头抽取出来，然后组成一个全局符号表。第三遍分析就是对函数体里面的语句做带歧义的语法分析了。因为Tinymoe允许你定义变量，所以符号表肯定是一边分析一边修改的。于是对于"print sum from 1 to 100"这一句，如果你没有发现"phrase sum from (lower bound) to (upper bound)"和"sentence print (message)"，那根本无从下手。
</p><p>
&#160;</p><p>还有另一个例子：
</p><p style="text-align: justify"><span style="font-size:10pt">module exception handling
</span></p><p style="text-align: justify">
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">&#8230;
</span></p><p style="text-align: justify">
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">phrase main
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;try
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;do something bad
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;catch
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;print "bad thing happened"
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;end
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p>
&#160;</p><p>当语法分析做到"try"的时候，因为发现存在<strong>try函数的定义</strong>，所以Tinymoe知道接下来的"do something bad"属于调用try这个块函数所需提供的代码块里面的代码。接下来是"catch"，Tinymoe怎么知道catch是接在try后面，而不是放在try里面的呢？这仍然是由于<strong>catch函数的定义</strong>告诉我们的。关于这方面的语法知识可以<a href="https://github.com/vczh/tinymoe/wiki/Category">点击这里查看</a>。
</p><p>
&#160;</p><p>正因为如此，我们需要首先知道函数的定义，然后才能分析函数体里面的代码。虽然这在一定程度上造成了Tinymoe的语法分析复杂度的提升，但是其复杂度本身并不高。比C++简单就不说了，就算是C、C#和Java，由于其语法元素太多，导致<strong>不需要多次分析</strong>所降低的复杂度被完全的抵消，结果跟实现Tinymoe的语法分析器的难度不相上下。
</p><p>
&#160;</p><p>其次，<strong>CPS变换后的代码需要特殊处理，否则直接执行容易导致call stack积累的没用的东西过多</strong>。因为Tinymoe可以自定义操作符，所以操作符跟C++一样在编译的时候被转换成了函数调用。每一个函数调用都是会被CPS变换的。尽管每一行的函数调用次数不多，但是如果你的程序油循环，<strong>循环是通过递归来描述</strong>（而不是实现，由于CPS变换后Tinymoe做了优化，所以不存在实际上的递归）的，如果直接执行CPS变换后的代码，算一个1加到1000都会导致stack overflow。可见其call stack里面堆积的closure数量之巨大。
</p><p>
&#160;</p><p>我在做Tinymoe代码生成的实验的时候，为了简单我在单元测试里面直接产生了对应的C#代码。一开始没有处理CPS而直接调用，程序不仅慢，而且容易stack overflow。但是我们知道（其实你们以后才会知道），CPS变换后的代码里面几乎所有的call stack项都是浪费的，因此我把整个在生成C#代码的时候修改成，如果需要调用continuation，就返回<strong>调用continuation的语句组成的lambda表达式</strong>，在最外层用一个循环去驱动他直到返回null为止。这样做了之后，就算Tinymoe的代码有递归，call stack里面也不会因为递归而积累call stack item了。于是生成的C#代码执行飞快，而且无论你怎么递归也永远不会造成stack overflow了。这个美妙的特性几乎所有语言都做不到，啊哈哈哈哈哈。
</p><p>
&#160;</p><p>当然这也是有代价的，因为本质上我只是把保存在stack上的context转移到heap上。不过多亏了.net 4.0的强大的background GC，这样做丝毫没有多余的性能上的损耗。当然这也意味着，一个高性能的Tinymoe虚拟机，需要一个牛逼的垃圾收集器作为靠山。context产生的closure在函数体真的被执行完之后就会被很快地收集，所以CPS加上这种做法并不会对GC产生额外的压力，所有的压力仍然来源于你自己所创建的数据结构。
</p><p>
&#160;</p><p>第三，<strong>Tinymoe需要动态类型语言的类型推导</strong>。当然你不这么做而把Tinymoe的程序当JavaScript那样的程序处理也没有问题。但是我们知道，正是因为V8对JavaScript的代码进行了类型推导，才产生了那么优异的性能。因此这算是一个优化上的措施。
</p><p>
&#160;</p><p>最后，<strong>Tinymoe还需要跨过程分析和对程序的控制流的化简</strong>（譬如continuation转状态机等）。目前具体怎么做我还在学习当中。不过我们想，既然repeat函数是通过递归来描述的，那我们能不能通过对所有代码进行inter-procedural analyzing，从而发现诸如
</p><p style="text-align: justify"><span style="font-size:10pt">repeat 3 times
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;do something good
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p>就是一个循环，从而生成用真正的循环指令（譬如说goto）呢？这个问题是个很有意思的问题，我觉得我如果可以通过学习静态分析从而解决它，不进我的能力会得到提升，我对你们的科普也会做得更好。
</p><p style="text-align: justify"><h2>后记
</h2></p><p>虽然还不到五千字，但是总觉得写了好多的样子。总之我希望读者在看完《零》和《一》之后，对接下来需要学习的东西有一个较为清晰的认识。</p> <img src ="http://www.cppblog.com/vczh/aggbug/205702.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2014-02-11 12:53 <a href="http://www.cppblog.com/vczh/archive/2014/02/11/205702.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>跟vczh看实例学编译原理——零：序言</title><link>http://www.cppblog.com/vczh/archive/2014/01/19/205468.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Sat, 18 Jan 2014 17:21:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2014/01/19/205468.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/205468.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2014/01/19/205468.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/205468.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/205468.html</trackback:ping><description><![CDATA[<p>在《如何设计一门语言》里面，我讲了一些语言方面的东西，还有痛快的喷了一些XX粉什么的。不过单纯讲这个也是很无聊的，所以我开了这个《跟vczh看实例学编译原理》系列，意在科普一些编译原理的知识，尽量让大家可以在创造语言之后，自己写一个原型。在这里我拿我创造的一门很有趣的语言 <a href="https://github.com/vczh/tinymoe/">https://github.com/vczh/tinymoe/</a> 作为实例。
</p><p>
&#160;</p><p>商业编译器对功能和质量的要求都是很高的，里面大量的东西其实都跟编译原理没关系。一个典型的编译原理的原型有什么特征呢？
</p><ol><li><div style="text-align: justify"><span style="font-size:10pt">性能低
</span></div></li><li><div style="text-align: justify"><span style="font-size:10pt">错误信息难看
</span></div></li><li><div style="text-align: justify"><span style="font-size:10pt">没有检查所有情况就生成代码
</span></div></li><li><div style="text-align: justify"><span style="font-size:10pt">优化做得烂
</span></div></li><li><div style="text-align: justify"><span style="font-size:10pt">几乎没有编译选项
</span></div></li></ol><p>
&#160;</p><p>等等。Tinymoe就满足了上面的5种情况，因为我的目标也只是想做一个原型，向大家介绍编译原理的基础知识。当然，我对语法的设计还是尽量靠近工业质量的，只是实现没有花太多心思。
</p><p>
&#160;</p><p>为什么我要用Tinymoe来作为实例呢？因为Tinymoe是少有的一种用起来简单，而且库可以有多复杂写多复杂的语言，就跟C++一样。C++11额标准库在一起用简直是愉快啊，Tinymoe的代码也是这么写的。但是这并不妨碍你可以在写C++库的时候发挥你的想象力。Tinymoe也是一样的。为什么呢，我来举个例子。
</p><p>
&#160;</p><p style="text-align: justify"><h2>Hello, world!
</h2></p><p>Tinymoe的hello world程序是很简单的：
</p><p>
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">module hello world
</span></p><p style="text-align: justify"><span style="font-size:10pt">using standard library
</span></p><p style="text-align: justify">
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">sentence print (message)
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;redirect to "printf"
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p style="text-align: justify">
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">phrase main
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;print "Hello, world!"
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p>
&#160;</p><p>module指的是模块的名字，普通的程序也是一个模块。using指的是你要引用的模块——standard library就是Tinymoe的STL了——当然这个程序并没有用到任何standard library的东西。说到这里大家可能意识到了，Tinymoe的名字可以是不定长的token组成的！没错，后面大家会慢慢意识到这种做法有多么的强大。
</p><p>
&#160;</p><p>后面是print函数和main函数。Tinymoe是严格区分语句和表达式的，只有sentence和block开头的函数才能作为语句，而且同时只有phrase开头的函数才能作为表达式。所以下面的程序是不合法的：
</p><p>
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">phrase main
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;(print "Hello, world!") + 1
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p>
&#160;</p><p>原因就是，print是sentence，不能作为表达式使用，因此他不能被+1。
</p><p>
&#160;</p><p>Tinymoe的函数参数都被写在括号里面，一个参数需要一个括号。到了这里大家可能会觉得很奇怪，不过很快就会有解答了。为什么要这么做，下一个例子就会告诉我们。
</p><p>
&#160;</p><p>print函数用的redirect to是Tinymoe声明FFI（Foreign Function Interface）的方法，也就是说，当你运行了print，他就会去host里面找一个叫做printf的函数来运行。不过大家不要误会，Tinymoe并没有被设计成可以直接调用C函数，所以这个名字其实是随便写的，只要host提供了一个叫做printf的函数完成printf该做的事情就行了。main函数就不用解释了，很直白。
</p><p style="text-align: justify"><h2>1加到100等于5050
</h2></p><p>这个例子可以在Tinymoe的主页（<a href="https://github.com/vczh/tinymoe/">https://github.com/vczh/tinymoe/</a>）上面看到：
</p><p>
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">module hello world
</span></p><p style="text-align: justify"><span style="font-size:10pt">using standard library
</span></p><p style="text-align: justify">
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">sentence print (message)
</span></p><p style="text-align: justify"><span style="font-size:10pt">    redirect to "printf"
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p style="text-align: justify">
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">phrase </span><span style="font-size:14pt"><strong>sum from (start) to (end)
</strong></span></p><p style="text-align: justify"><span style="font-size:10pt">    set the result to 0
</span></p><p style="text-align: justify"><span style="font-size:10pt">    repeat with the current number from start to end
</span></p><p style="text-align: justify"><span style="font-size:10pt">        add the current number to the result
</span></p><p style="text-align: justify"><span style="font-size:10pt">    end
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p style="text-align: justify">
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">phrase main
</span></p><p style="text-align: justify"><span style="font-size:10pt">    print "1+ ... +100 = " &amp; </span><span style="font-size:14pt"><strong>sum from 1 to 100</strong></span><span style="font-size:10pt">
		</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p>
&#160;</p><p>为什么名字可以是多个token？为什么每一个参数都要一个括号？看加粗的部分就知道了！正是因为Tinymoe想让每一行代码都可以被念出来，所以才这么设计的。当然，大家肯定都知道怎么算start + (start+1) + &#8230; + (end-1) + end了，所以应该很容易就可以看懂这个函数里面的代码具体是什么意思。
</p><p>
&#160;</p><p>在这里可以稍微多做一下解释。the result是一个预定义的变量，代表函数的返回值。只要你往the result里面写东西，只要函数一结束，他就变成函数的返回值了。Tinymoe的括号没有什么特殊意思，就是改变优先级，所以那一句循环则可以通过添加括号的方法写成这样：
</p><p>
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">repeat with (the current number) from (start) to (end)
</span></p><p>
&#160;</p><p>大家可能会想，repeat with是不是关键字？当然不是！repeat with是standard library里面定义的一个block函数。大家知道block函数的意思了吧，就是这个函数可以带一个block。block有一些特性可以让你写出类似try-catch那样的几个block连在一起的大block，特别适合写库。
</p><p>
&#160;</p><p>到了这里大家心中可能会有疑问，循环为什么可以做成库呢？还有更加令人震惊的是，break和continue也不是关键字，是sentence！因为repeat with是有代码的：
</p><p>
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">category
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;start REPEAT
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;closable
</span></p><p style="text-align: justify"><span style="font-size:10pt">block (sentence deal with (item)) </span><span style="font-size:14pt"><strong>repeat with </strong></span><span style="font-size:10pt">(argument item) </span><span style="font-size:14pt"><strong>from </strong></span><span style="font-size:10pt">(lower bound) </span><span style="font-size:14pt"><strong>to </strong></span><span style="font-size:10pt">(upper bound)
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;set the current number to lower bound
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;repeat while the current number &lt;= upper bound
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;deal with the current number
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;add 1 to the current number
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;end
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p>
&#160;</p><p>前面的category是用来定义一些block的顺序和包围结构什么的。repeat with是属于REPEAT的，而break和continue声明了自己只能直接或者间接方在REPEAT里面，因此如果你在一个没有循环的地方调用break或者continue，编译器就会报错了。这是一个花边功能，用来防止手误的。
</p><p>
&#160;</p><p>大家可能会注意到一个新东西：(argument item)。argument的意思指的是，后面的item是block里面的代码的一个参数，对于repeat with函数本身他不是一个参数。这就通过一个很自然的方法给block添加参数了。如果你用ruby的话就得写成这个悲催的样子：
</p><p>
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">repeat_with(1, 10) do |item|
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;xxxx
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p>
&#160;</p><p>而用C++写起来就更悲催了：
</p><p>
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">repeat_with(1, 10, [](int item)
</span></p><p style="text-align: justify"><span style="font-size:10pt">{
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;xxxx
</span></p><p style="text-align: justify"><span style="font-size:10pt">});
</span></p><p>
&#160;</p><p>block的第一个参数sentence deal with (item)就是一个引用了block中间的代码的委托。所以你会看到代码里面会调用它。
</p><p>
&#160;</p><p>好了，那repeat while总是关键字了吧——不是！后面大家还会知道，就连
</p><p>
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">if xxx
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;yyy
</span></p><p style="text-align: justify"><span style="font-size:10pt">else if zzz
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;www
</span></p><p style="text-align: justify"><span style="font-size:10pt">else if aaa
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;bbb
</span></p><p style="text-align: justify"><span style="font-size:10pt">else
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;ccc
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p>
&#160;</p><p>也只是你调用了if、else if和else的一系列函数然后让他们串起来而已。
</p><p>
&#160;</p><p>那Tinymoe到底提供了什么基础设施呢？其实只有select-case和递归。用这两个东西，加上内置的数组，就图灵完备了。图灵完备就是这么容易啊。
</p><p>
&#160;</p><p style="text-align: justify"><h2>多重分派（Multiple Dispatch）
</h2></p><p>讲到这里，我不得不说，Tinymoe也可以写类，也可以继承，不过他跟传统的语言不一样的，类是没有构造函数、析构函数和其他成员函数的。Tinymoe所有的函数都是全局函数，但是你可以使用多重分派来"挑选"类型。这就需要第三个例子了（也可以在主页上找到）：
</p><p>
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">module geometry
</span></p><p style="text-align: justify"><span style="font-size:10pt">using standard library
</span></p><p style="text-align: justify">
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">phrase square root of (number)
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;redirect to "Sqrt"
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p style="text-align: justify">
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">sentence print (message)
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;redirect to "Print"
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p style="text-align: justify">
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">type rectangle
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;width
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;height
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p style="text-align: justify">
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">type triangle
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;a
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;b
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;c
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p style="text-align: justify">
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">type circle
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;radius
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p style="text-align: justify">
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">phrase area of (shape)
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;raise "This is not a shape."
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p style="text-align: justify">
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">phrase area of (shape : rectangle)
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;set the result to field width of shape * field height of shape
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p style="text-align: justify">
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">phrase area of (shape : triangle)
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;set a to field a of shape
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;set b to field b of shape
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;set c to field c of shape
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;set p to (a + b + c) / 2
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;set the result to square root of (p * (p - a) * (p - b) * (p - c))
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p style="text-align: justify">
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">phrase area of (shape : circle)
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;set r to field radius of shape
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;set the result to r * r * 3.14
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p style="text-align: justify">
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">phrase (a) and (b) are the same shape
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;set the result to false
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p style="text-align: justify">
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">phrase (a : rectangle) and (b : rectangle) are the same shape
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;set the result to true
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p style="text-align: justify">
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">phrase (a : triangle) and (b : triangle) are the same shape
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;set the result to true
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p style="text-align: justify">
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">phrase (a : circle) and (b : circle) are the same shape
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;set the result to true
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p style="text-align: justify">
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">phrase main
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;set shape one to new triangle of (2, 3, 4)
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;set shape two to new rectangle of (1, 2)
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;if shape one and shape two are the same shape
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;print "This world is mad!"
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;else
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;print "Triangle and rectangle are not the same shape!"
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;end
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p>
&#160;</p><p>这个例子稍微长了一点点，不过大家可以很清楚的看到我是如何定义一个类型、创建他们和访问成员变量的。area of函数可以计算一个平面几何图形的面积，而且会根据你传给他的不同的几何图形而使用不同的公式。当所有的类型判断都失败的时候，就会掉进那个没有任何类型声明的函数，从而引发一场。嗯，其实try/catch/finally/raise都是函数来的——Tinymoe对控制流的控制就是如此强大，啊哈哈哈哈。就连return都可以自己做，所以Tinymoe也不提供预定义的return。
</p><p>
&#160;</p><p>那phrase (a) and (b) are the same shape怎么办呢？没问题，Tinymoe可以同时指定多个参数的类型。而且Tinymoe的实现具有跟C++虚函数一样的性质——无论你有多少个参数标记了类型，我都可以O(n)跳转到一个你需要的函数。这里的n指的是标记了类型的参数的个数，而不是函数实例的个数，所以跟C++的情况是一样的——因为this只能有一个，所以就是O(1)。至于Tinymoe到底是怎么实现的，只需要看《如何设计一门语言》第五篇（<a href="http://www.cppblog.com/vczh/archive/2013/05/25/200580.html">http://www.cppblog.com/vczh/archive/2013/05/25/200580.html</a>）就有答案了。
</p><p style="text-align: justify"><h2>Continuation Passing Style
</h2></p><p>为什么Tinymoe的控制流都可以自己做呢？因为Tinymoe的函数都是写成了CPS这种风格的。其实CPS大家都很熟悉，当你用jquery做动画，用node.js做IO的时候，那些嵌套的一个一个的lambda表达式，就有点CPS的味道。不过在这里我们并没有看到嵌套的lambda，这是因为Tinymoe提供的语法，让Tinymoe的编译器可以把同一个层次的代码，转成嵌套的lambda那样的代码。这个过程就叫CPS变换。Tinymoe虽然用了很多函数式编程的手段，但是他并不是一门函数是语言，只是一门普通的过程式语言。但是这跟C语言不一样，因为它连C#的yield return都可以写成函数！这个例子就更长了，大家可以到Tinymoe的主页上看。我这里只贴一小段代码：
</p><p>
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">module enumerable
</span></p><p style="text-align: justify"><span style="font-size:10pt">using standard library
</span></p><p style="text-align: justify">
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">symbol yielding return
</span></p><p style="text-align: justify"><span style="font-size:10pt">symbol yielding break
</span></p><p style="text-align: justify"> 
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">type enumerable collection
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;body
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p style="text-align: justify"> 
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">type collection enumerator
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;current yielding result
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;body
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;continuation
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p style="text-align: justify"> 
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">略（这里实现了跟enumerable相关的函数，包括yield return）
</span></p><p style="text-align: justify">
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">block (sentence deal with (item)) repeat with (argument item) in (items : enumerable collection)
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;set enumerator to new enumerator from items
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;repeat
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;move enumerator to the next
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;deal with current value of enumerator
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;end
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p style="text-align: justify">
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">sentence print (message)
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;redirect to "Print"
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p style="text-align: justify"> 
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">phrase main
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;</span><span style="font-size:14pt"><strong>create enumerable to</strong></span><span style="font-size:10pt"> numbers
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;repeat with i from 1 to 10
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;print "Enumerating " &amp; i
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</span><span style="font-size:14pt"><strong>yield return</strong></span><span style="font-size:10pt"> i
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;end
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;end
</span></p><p style="text-align: justify">    
&#160;</p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;</span><span style="font-size:14pt"><strong>repeat with</strong></span><span style="font-size:10pt"> number</span><span style="font-size:14pt"><strong> in</strong></span><span style="font-size:10pt"> numbers
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;if number &gt;= 5
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</span><span style="font-size:14pt"><strong>break
</strong></span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;end
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;print "Printing " &amp; number
</span></p><p style="text-align: justify"><span style="font-size:10pt">&#160;&#160;&#160;&#160;end
</span></p><p style="text-align: justify"><span style="font-size:10pt">end
</span></p><p>
&#160;</p><p>什么叫模拟C#的yield return呢？就是连惰性计算也一起模拟！在main函数的第一部分，我创建了一个enumerable（iterator），包含1到10十个数字，而且每产生一个数字还会打印出一句话。但是接下来我在循环里面只取前5个，打印前4个，因此执行结果就是
</p><p><img src="http://www.cppblog.com/images/cppblog_com/vczh/011814_1721_vczh1.png" alt=""/>
	</p><p>当！
</p><p>
&#160;</p><p>CPS风格的函数的威力在于，每一个函数都可以控制他如何执行函数结束之后写在后面的代码。也就是说，你可以根据你的需要，干脆选择保护现场，然后以后再回复。是不是听起来很像lua的coroutine呢？在Tinymoe，coroutine也可以自己做！
</p><p>
&#160;</p><p>虽然函数最后被转换成了CPS风格的ast，而且测试用的生成C#代码的确也是原封不动的输出了出来，所以运行这个程序耗费了大量的函数调用。但这并不意味着Tinymoe的虚拟机也要这么做。大家要记住，一个语言也好，类库也好，给你的接口的概念，跟实现的概念，有可能完全不同。yield return写出来的确要花费点心思，所以《序言》我也不讲这么多了，后续的文章会详细介绍这方面的知识，当然了，还会告诉你怎么实现的。
</p><p>
&#160;</p><p style="text-align: justify"><h2>尾声
</h2></p><p>这里我挑选了四个例子来展示Tinymoe最重要的一些概念。一门语言，要应用用起来简单，库写起来可以发挥想象力，才是有前途的。yield return例子里面的main函数一样，用的时候多清爽，清爽到让你完全忘记yield return实现的时候里面的各种麻烦的细节。
</p><p>
&#160;</p><p>所以为什么我要挑选Tinymoe作为实例来科普编译原理呢？有两个原因。第一个原因是，想要实现Tinymoe，需要大量的知识。所以既然这个系列想让大家能够看完实现一个Tinymoe的低质量原型，当然会讲很多知识的。第二个原因是，我想通过这个例子向大家将一个道理，就是库和应用 、编译器和语法、实现和接口，完全可以做到隔离复杂，只留给最终用户简单的部分。<span style="font-size:14pt"><strong>你看到的复杂的接口，并不意味着他的实现是臃肿的。你看到的简单的接口，也不意味着他的实现就很简洁</strong></span>。
</p><p>
&#160;</p><p>Tinymoe目前已经可以输出C#代码来执行了。后面我还会给Tinymoe加上静态分析和类型推导。对于这类语言做静态分析和类型推导又很多麻烦，我现在还没有完全搞明白。譬如说这种可以自己控制continuation的函数要怎么编译成状态机才能避免掉大量的函数调用，就不是一个容易的问题。所以在系列一边做的时候，我还会一边研究这个事情。如果到时候系列把编译部分写完的同时，这些问题我也搞明白的话，那我就会让这个系列扩展到包含静态分析和类型推导，继续往下讲。</p> <img src ="http://www.cppblog.com/vczh/aggbug/205468.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2014-01-19 01:21 <a href="http://www.cppblog.com/vczh/archive/2014/01/19/205468.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>