复习一下上一节

从现在开始会有很多的概念性的东西,为了对整体有个概念,我们要复习一下我们之前的工作。先明确我们的目标。我们要实现一个解释型的语言,要一句一句的理解代码。那么什么是理解代码呢?首先我要读一段字符串。然后计算机很笨,我们要它连什么是一个语言元素都不知道。于是我们要先教它语言元素是什么。于是我们有了Day2中的标记解析。 那么我们下面应该怎么做呢?此时我们引入我们开发的第二个重要的原则:
复杂的问题要从简单的问题开始分析。

我们要实现的语言的最基本元素

现在我们考虑一下,如果我们的单个元素的基本类型是什么?有什么?它简单到我们如果单单输入就直接可以给出值,不需要复杂的计算。如下表:

  • NIL 空类型
  • TRUE/FALSE 布尔类型
  • Str 字符串类型
  • Symbol 符号类型
  • 还有呢?

其中空(NIL)、布尔、字符串类型比较好理解。那个符号什么意思呢?暂时可以想象成别的语言的变量名(当然完全不是这么回事)。我在读代码的时候出现一个符号,我现在并不知道他对应着什么。可能对应一个字符串也可能对应一个空。但是我现在是识别而不是求值。所以我不关心,我甚至不关心它对应一个操作还是一个值。等一下操作?我们的基本类型中是不是应该还有函数,过程,或者说抽象呢?这个问题我们心里面记着暂时不去考虑。
说了这么多我们还是说的单个元素,你说的语法树呢?你说的树型结构呢?
现在仅仅给元素添加上括号来看看。比如 (1) 、(nil) 、(”name”)、(+)等等。现在这个似乎还是一个元素。只是多了一个括号。那么我们多一个元素呢?(1 2)我再多一个元素呢? (1 2 3)
如果我们把这个括号和里面的东西看成一个整体又是什么呢?有是一个类型,叫做列表(List)。这个才是Lisp语言的灵魂。马上我们的树要来了。那么怎么定义这个列表呢?我们给一个大白话的定义:
用括号括起来,里面有若干个Lisp的元素用空格隔开。

我包含我!

我们上面定义中说若干个Lisp元素,那么列表本身可以做Lisp元素吗?可以。那么我们可以写出一个什么样形式呢?

  1. (1 (2 3))
  2. ((1 2 3) (2) 3)
  3. ((1 (2 3)) 2)

诸如此类还可以更加的复杂。这就是我们说的树了。那用图怎么表示出来呢?那最简单的(1 (2 3))来说,如下。

Day3 开始构建AST(抽象语法树) - 图1现在明白了吧。我们现在要做的就是要做的就是通过一串的token生成这个这个树。而这个树就是AST,抽象语法树。

细化树

如果把代码(1 (2 3))生成上AST是完全可以够用的。但是我们还可以继续完善。比如到最最下面的叶子节点。我们也可以给他加上一个类型。比如1,2,3这样的数。是Int类型。比如结尾是true或者false的是bool类型。再比如是字符串的是Str类型。这样都是用已经确定的类型进行表示。这样的好处是去除了歧义,当然这种情况还没有发生。但是不排除我们理解1为一个符号。这个时候我们就要告诉计算机1是个Sym还是Int。
其次我们不得不讨论一下函数,或者过程,或者叫抽象。注意我说道这个概念的时候他们总是在一起,因为我并不去区分他们的概念。可是我还是要描述它,从某种角度来说我们只要去描述一个匿名函数就好了。因为有名字的函数仅仅是一个变量名和这个函数的绑定而已。于是我们要尝试描述一个函数。
一个函数定义或许如下面这样。

(lamdba (x y) (+ x y))
(lamdba [x y] (* x y))

现在我们是设计者了,记住!我们认为上面两种定义方法都是有效的。所以一个函数其实是一个特殊的列表。一个什么样的列表呢? 第一项是固定的词。我使用是lamdba,而mal的文档里使用了fn这个符号。你可以使用你喜欢的符号。第二项是一个列表。其中的每个符号代表一个形参。第三项是函数体。
第二项表示形参。如果都是使用圆括号未免也太迷乱了。我眼睛都看花了。感觉中括号也可以区分。而且更加的优雅。那么第二项就让中括号的定义也表示同样的意义吧。可是括号表示列表,中括号也表示列表的话,就太奢侈了。干脆用中括号表示数学上的向量吧。这样就好了。
这样我们就又多了两个基本类型。向量和方法。向量和列表类似,但是表示数学上的向量,也就说遇到向量时当成一个数据结构不求值,而列表会正常的求值。(注意这些是我们的规定。)还有,一个匿名函数类型。这个类型是个数据结构,至少包含一个入参描述表。它可以是列表或者向量。还有一个函数体。函数体应该是什么呢?
往回看。是一个列表。那要是函数体很复杂呢?那是什么呢?还是代码嘛。就当做是AST吧。
现在不妨看看现在一个函数的定义后的AST是什么样的。(lamdba [x y] (
x y)) 为例。