一直很了解人们对于 parser 的误解,可是一直都提不起兴趣来阐述对它的观点。然而我觉得是有必要解释一下这个问题的时候了。我感觉得到大部分人对于 parser 的误解之深,再不澄清一下,恐怕这些谬误就要写进歪曲的历史教科书,到时候就没有人知道真相了。
什么是 Parser
首先来科普一下。所谓 parser,一般是指把某种格式的文本(字符串)转换成某种数据结构的过程。最常见的 parser,是把程序文本转换成编译器内部的一种叫做“抽象语法树”(AST)的数据结构。也有简单一些的 parser,用于处理 CSV,JSON,XML 之类的格式。
举个例子,一个处理算数表达式的 parser,可以把“1+2”这样的,含有1,+,2三个字符的字符串,转换成一个对象(object)。这个对象就像new BinaryExpression (ADD, new Number (1), new Number (2))这样的 Java 构造函数调用生成出来的那样。
之所以需要做这种从字符串到数据结构的转换,是因为编译器是无法直接操作“1+2”这样的字符串的。实际上,代码的本质根本就不是字符串,它本 来就是一个具有复杂拓扑的数据结构,就像电路一样。“1+2”这个字符串只是对这种数据结构的一种“编码”,就像 ZIP 或者 JPEG 只是对它们压缩的数据的编码一样。
这种编码可以方便你把代码存到磁盘上,方便你用文本编辑器来修改它们,然而你必须知道,文本并不是代码本身。所以从磁盘读取了文本之后,你必须先“解码”,才能方便地操作代码的数据结构。比如,如果上面的 Java 代码生成的 AST 节点叫node,你就可以用node.operator来访问ADD,用node.left来访问1,node.right来访问2。这是很方便的。
对于程序语言,这种解码的动作就叫做 parsing,用于解码的那段代码就叫做 parser。
Parser 在编译器中的地位
那么貌似这样说来,parser 是编译器里面很关键的一个部分了?显然,parser 是必不可少的,然而它并不像很多人想象的那么重要。Parser 的重要性和技术难度,被很多人严重的夸大了。一些人提到“编译器”,就跟你提 LEX,YACC,ANTLR 等用于构造 parser 的工具,仿佛编译器跟 parser 是等价的似的。还有些人,只要听说别人写了个 parser,就觉得这人编程水平很高,开始膜拜了。这些其实都显示出人的肤浅。
我喜欢把 parser 称为“万里长征的第 0 步”,因为等你 parse 完毕得到了 AST,真正精华的编译技术才算开始。一个先进的编译器包含许多的步骤:语义分析,类型检查/推导,代码优化,机器代码生成,…… 这每个步骤都是在对某种中间数据结构(比如 AST)进行分析或者转化,它们完全不需要知道代码的字符串形式。也就是说,一旦代码通过了 parser,在后面的编译过程里,你就可以完全忘记 parser 的存在。所以 parser 对于编译器的地位,其实就像 ZIP 之于 JVM,就像 JPEG 之于 PhotoShop。Parser 虽然必不可少,然而它比起编译器里面最重要的过程,是处于一种辅助性,工具性,次要的地位。
鉴于这个原因,好一点的大学里的程序语言(PL)课程,都完全没有关于 parser 的内容。学生们往往直接用 Scheme 这样代码数据同形的语言,或者直接使用 AST 数据结构来构造程序。在 Kent Dybvig 这样编译器大师的课程上,学生直接跳过 parser 的构造,开始学习最精华的语义转换和优化技术。实际上,Kent Dybvig 根本不认为 parser 算是编译器的一部分。因为 AST 数据结构其实才是程序本身,而程序的文本只是这种数据结构的一种编码形式。
Parser 技术发展的误区
既然 parser 在编译器中处于次要的地位,可是为什么还有人花那么大功夫研究各种炫酷的 parser 技术呢。LL,LR,GLR,LEX, YACC,Bison,parser combinator,ANTLR,PEG,…… 制造 parser 的工具似乎层出不穷,每出现一个新的工具都号称可以处理更加复杂的语法。
很多人盲目地设计复杂的语法,然后用越来越复杂的 parser 技术去 parse 它们,这就是 parser 技术仍然在发展的原因。其实,向往复杂的语法,是程序语言领域流传非常广,危害非常大的错误倾向。在人类历史的长河中,留下了许多难以磨灭的历史性糟粕, 它们固化了人类对于语言设计的理念。很多人设计语言似乎不是为了拿来好用的,而是为了让用它的人迷惑或者害怕。
有些人假定了数学是美好的语言,所以他们盲目的希望程序语言看起来更加像数学。于是他们模仿数学,制造了各种奇怪的操作符,制定它们的优先级,这样你就可以写出2