第四章:语法和语义
大多数编程语言,语言的处理器(无论是解释器或编译器)的操作方式都类似黑箱操作。 在黑箱内部,语言的处理器通常分成子系统。 一个典型的任务划分思路:将处理器分为三个阶段,每个阶段为下一个阶段提供内容。
- 一个词法分析器将字符流分拆成语元并将其送进一个解析器
- 解析器再根据该语言的语法在程序中构建一个表达式的树形表示。这棵树被称为抽象语法树。
- 随即被送进一个求值器,求值器要么直接解释它,要么将其编译成魔种其他语言(如:机器码)。
Common lisp 定义了两个黑箱:读取器、求值器
- 读取器:将文本转为 lisp 对象。
- 求值器:用这些对象实现语言的语义。
每个黑箱都定义了一个语法层面。读取器定义了字符串如何被转换为 S-表达式的 lisp 对象。 S-表达式适用于由任意对象及其他列表所组成的列表,因此 S-表达式可用来表达任意树形表达式。 求值器随后定义了一种构建在 S-表达式之上的 lisp 形式的语法。
此种黑箱划分带来的后果:
- 可将 S-表达式用作一种可暴露的数据格式来表达源代码之外的数据。
- 由于语言的语义是用对象树而非字符串定义而成的,则可通过语言本身来生成代码,则可通过处理现有的数据生成代码,从而达到“可编程的编程语言”的效果,即 lisp 宏的本意。
S-表达式
S-表达式的基本元素是列表、原子。 列表由括号所包围,并可包含任何数量的由空格所分隔的元素。 原子是所有其他内容。
原子:数字、字符串、名字。
数字:任何数位的序列都会被读取为一个数字。可为整数、比值、小数、科学计数法
字符串:用双引号包含的都是字符串。用反斜杠进行转义。
字符:用#\表示,如:#\a 表示字符 a
名字:由称为符号的对象表示。如:format、hello-world、db
十个字符不能出现在名字中:开括号、闭括号、双引号、单引号、反引号、逗号、冒号、分好、反斜杠、竖线。 如真想用的,则需要用反斜杠转义或将含有需要转义的字符名字用竖线包起来。
读取器将名字转化为符号对象的方式:
- 当读取名字时,读取器将所有名字中未转义的字符都转为大写。
- 为了确保同一个文本名字总是被读取成相同的符号,读取器保留这个符号之后,在一个称为包的表汇总查询带有相同名字的已有符号,若找不到,则创建一个新的符号并添加到表中。否则返回那个符号。
名字的约定:
- 全局变量:开始和结尾用。如:db*
- 常量:开始和结尾用+。如:+zero+
lisp 求值方式:将求值器看做一个函数,接收一个句法良好定义的 lisp 形式作为参数并返回一个值。
原子可被分为两个类别:符号和所有其他内容。 符号在作为 lisp 形式被求值时会被看作一个变量名,并会被求值为该变量的当前值。 所有原子都是自求值对象。
lisp 中的真假值为:T 和 NIL 其中 NIL 既是原子也是列表,nil 是空列表。 另一类自求值符号是关键字符号(以冒号开始的符号),当读取器保留这样一个名字时,会自动定义一个以此命名的常量变量并以该符号作为其值。
函数调用
函数调用规则:除第一个以外,所有的列表元素它们自身必须是一个形态良好的 lisp 形式。 调用格式:(函数名 参数列表)
函数的所有参数都在函数被调用之前求值。
特殊操作符
如:if if 规则:对第一个表达式求值,为非 nil 则执行第二个表达式,否则执行第三个表达式。 quote 规则:接收一个参数,并不求值返回。即:将参数不经处理直接返回。语法糖为:'(单引号)
宏
宏是一个以 S-表达式为其参数的函数,并返回一个 lisp 形式然后对其求值并用改制取代宏形式。
宏形式的求值过程:
- 首先宏形式的元素不经求值即被传递到宏函数里
- 其次,由宏函数所返回的形式(展开式)按照正常的求值规则进行求值。 即宏的求值一般为两个阶段:宏展开阶段、展开式求值阶段。
真、假、等价
符号 nil 是唯一的一个假值。其他所有的都是真值。 符号 T 是标准的真值。 等价
eq:用来测试“对象标识”,只有当两个对象相同时,才等价。如:相同值的数字和字符用 eq 比较是不等价的。
eql:与 eq 相似,可保证当相同类型的两个对象标识相同的数字或字符值时,才等价。
equal:具有相同内容,就等价。
equalp:在比较字符串时忽略大小写。
格式化代码
保持美观的代码风格,可方便查找错误及阅读代码。