编译器的诞生[三]编译器设计概览【转】

我们正在编写什么?

简单来说,是一个计算器。一个用于超级简单的数学语言的编译器。

至少当前来说,我们会尽量避免处理字符串和字符这些复杂的东西,并且集中精力在数字上。当然也不是所有的数字,仅仅整数而已。当前来说系统中仅有的“类型”就是它了。

添加新类型并不困难,实际上是很简单的,但是在这个游戏中,这样做会让我们的设计变得更加复杂。

撰写策略

无论你是构建解释器还是编译器,大多数步骤都是一样的。通常来说,基本步骤如下:

  • 词法分析
  • 解析
  • 语义分析
  • 优化
  • 编码

我们每次只攻打一个步骤。

概览:词法分析

大家之前都写过代码。程序的行为通过“人类可读”的方式进行表达。我们需要处理这些人类可读的内容,让计算机能够明白其中的含义。第一步,就是词法分析。

简短来说,我们需要扫描整个文本,然后报告有什么发现。也就是说,需要分拣出语言中所有不同的独立部分,并赋予一个标识符(token)。每个标识符都与一个用字符表示的文法串(也叫词位)相关联。它会在下一个阶段使用。文法(数字、字符串等等)、关键字和操作符是在这个阶段需要标识的样本。

我们应当提供每个标识符发现的位置,以便错误报告使用。

概览:解析/语法分析

这个阶段也叫做语法分析,为已经发现的标识符赋予各种含义。每个标识符都代表了树形数据结构中的一个对象和位置。

语言的语法会在这个阶段进行验证。以确保接收到的顺序是我们预期的顺序。在 LISP 中,表达式的格式为左括号,操作符或函数,接着是若干个参数,然后是右括号。解析过程会确保输入的内容是按照这个顺序排列。

概览:语义分析

接下来,将检查语言的语义是正确的。如果一个变量定义为一个整数,但是赋值了一个字符串,就会出问题。函数调用的参数与其声明时的参数数量不一致也是一个语义错误。

语义分析还要考虑例如变量作用域,并在使用未定义的变量时产生错误。

概览:优化

这个阶段的名字表明了它要作的事情。它会优化输出,通常是针对速度或大小(内存消耗)。在这个系列中,不会涵盖太多关于此的内容。不过会有一个关于为变量赋值表达式结果,而不是表达式本身的简单列子。

例如,如果为变量 A 赋值语句“2 + 3”的话。生成将两个常量数字相加的代码可能需要很多步完成。这可以优化为预先计算结果,然后在编码时进行赋值,这可以将语句简化为“A = 5”。

概览:编码

这是最后一个步骤。它将输出要发布的低级代码。Java 编译器会输出 Java 字节码。C 编译器会输出汇编。汇编编译器会输出机器码。

向前!向前!向前!

务必牢记这些步骤在你的编译器里可多可少。例如,C 编译器还有预编译步骤。有一些步骤,例如解析和语义分析可以放在一起,成为一个步骤。有各种编译器的设计。为了开阔视野,参阅这个维基百科的页面:编译器(英文)(译注:关于编译器的维基中文页面并未包含编译器前端的内容)。

现在立刻沉迷于编译器的编写当然很好。不幸的是,这是很愚蠢的行为。在不知道语言到底是什么样子的情况下,我们很快就会迷失于到底要写什么的问题之中。没有规则去遵循,就没有方向。

接下来:语言规则说明书!


链接:

编译器的诞生——目录

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

用以致学,学以致用

posted @ 2018-10-28 00:06  小天儿  阅读(233)  评论(0编辑  收藏  举报