自己动手构建编程语言:如何设计编译器、解释器和DSL
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.1 确定要编程语言提供的单词和标点符号的类型

编程语言有几种不同的单词和标点符号。在自然语言中,单词按词类进行归类:如名词、动词、形容词等。通过执行以下操作,可以构建与发明的编程语言的词类相对应的类别:

❑定义一组保留字或关键字。

❑在标识符中指定用于命名变量、函数和常量的字符。

❑为内置数据类型的字面常量值创建格式。

❑定义单字母和多字母运算符和标点符号。

作为语言设计文档的一部分,我们应该对每个类别各写出精确描述。某些情况下,我们可能只列出要使用的特定单词或标点符号,但在其他情况下,需要使用模式或其他方式来传达该类别中允许和不允许的内容。

对于保留字,目前只需列出一个清单。对于事物的名称,精确的描述必须包括一些细节,例如名称中允许使用哪些非字母符号。例如,在Java中,名称必须以字母开头,其后可跟字母和数字,允许使用下划线,并等同于字母。在其他语言中,名称间允许使用连字符,所以a、-和b三个符号可以组成一个有效的名字,而不是表示a减去b的结果。当一个精确的描述失败时,一套完整的示例就足够了。

常量值,也称词法,是词法分析器中令人惊讶的主要复杂性来源。在Java中尝试精确描述实数与之类似:Java有两种不同的实数——浮点型和双精度型,但是直到描述最后,它们看起来都一样,其中用一个可选的字母f(或F)或d(或D)区分浮点和双精度。在此之前,实数必须有小数点(.)部分或指数(e或E)部分,或两者都有。如果有小数点,则小数点右侧必须至少有一个数字。如果是指数部分,则必须带一个字符e(或E),后跟一个(可选的)减号和一个或多个数字。更糟糕的是,Java有一种奇怪的十六进制实数常量格式,很少有程序员听说过,这种格式由0x或0X后跟十六进制格式的数字组成,附带可选小数和强制指数部分,由p(或P)组成,后跟十进制格式的数字。

描述运算符和标点符号通常与列出保留字一样容易,主要区别之一是,运算符通常具有优先级规则,并且需要事先确定。例如,在数字处理中,乘法运算符的优先级几乎总是高于加法运算符,因此x+y*z将首先计算y和z的乘积,再与x相加。在大多数编程语言中,至少有3~5个优先级,许多流行的主流语言都有13~20个必须仔细考虑的优先级。图2.1展示了Java的运算符优先级表,我们在Jzero中也需要它。

图2.1 Java的运算符优先级表

由图2.1看出,Java有许多种运算符,尽管我们对此做了简化,但这些运算符仍可分为10个优先级。在拟构建的编程语言中,也许我们只想要较少的优先级,但如果想要构建真正的编程语言,则必须解决运算符优先级问题。

类似的问题还有运算符结合性。在许多编程语言中,大多数运算符都是从左到右进行结合,但也有一些奇怪的运算符是从右到左进行结合。例如,x+y+z表达式等价于(x+y)+z,但x=y=0表达式等价于x=(y=0)。

最小惊吓原则适用于运算符优先级和结合性,也适于确定在语言中首先要使用什么运算符。如果定义算术运算符并赋予它们奇怪的优先级或结合性,则人们将立马拒绝使用这样的语言。如果在语言中碰巧引入了新的、可能是领域特定的数据类型,那么我们可以对新的运算符更自由地定义运算符优先级和结合性。

一旦确定了语言中的单个单词和标点符号应该是什么,就可以用自己的方法构建更大的结构。这是从词法分析到语法的过渡。语法很重要,因为在语法这一层次,代码已变得足够强大,足以对要执行的计算做出明确规定。我们将在后面的章节中对此进行更详细的讨论,但在设计阶段,至少应该考虑程序员将如何指定控制流、声明数据和构建整个程序。首先,必须对控制流做出规划。