Parser(语法分析器)的编写相对于 Tokenizer (词法分析器)要复杂得多,因此,在编写之前可能也会铺垫得更多一些。当然,本系列旨在“写出”一个编译器,所以理论方面只会简单介绍 tao 语言所涉及的部分。
之前的几章中,我纯手写了tao 语言的 Tokenizer。但如果我准备也纯手写一个 Parser,那将是非常麻烦且繁琐的一件事情。实际上,就在在写出这篇文章之前,我已完成了 Parser 的编写,并测试妥当,因此我可以在此面对各位得出这个结论。
我将使用这么一种方式“制造”出 Parser:
将 tao 语言的所有语法细节描述出来,即定义 tao 语言。
写一个能”根据定义,生成 tao 语言的 Parser“的程序。
如果以上描述有些让人困惑,那我举个通俗点的例子吧:
假如我想要制作一双鞋子,通常的方案是,我会买好材料,并把鞋子做出来。但还有另一种方案,我先画出鞋子的设计图,再造一台能依照设计图造出鞋子的机器,然后把设计图交给机器,再发动机器,得到鞋子。
在”制造鞋子的世界“中,除非我要开鞋厂,否则若我仅仅想造双鞋子,那么前一个方案显然更好。但在”制造编译器的世界“中,却与直觉相反,当语言本身足够复杂的时候,后一种方案比前一种方案要方便得多。
至此,我需要一个能读懂 tao 语言的定义,并根据定义生成 Parser 的一个程序。这种程序我们称之为 Compiler-compiler 。这样的程序(或称工具)有很多现成的可供选择(包括在 Java 平台上可用的),但既然我这个系列叫做《从零开始写个编译器吧》,那显然如果我用现成的工具,那是犯规行为。
因此,我还要写一个 Compiler-compiler 出来才行。
那么,让我先贴一张图,以描述我将会写出的 Compiler-compiler 的工作原理吧。
Compiler-compiler 会将 tao 语言的定义编译成某种数据结构,而这种数据结构是 Parser 初始化的参数。Parser 只有获得了这种数据结构才能正常工作。
当 Parser 初始化之后,它会读取 Tokenizer 生成的 Token 序列,并同时通过解释 Compiler-compiler 生成的数据结构,最后生成 Syntax Tree。
至此,在编写 Parser 的章节中,我必须完成如下三个任务。
定义 tao 语言的语法细节,并挑选一个合适的形式描述出来。
编写一个 Compiler-compiler,它能编译 tao 语言的定义,并生成某种数据结构。
编写一个 Parser,它通过解释 Compiler-compiler 生成的数据结构,将 Token 序列编译成 Syntax Tree。