编程语言语法
不像英语或者汉语这种自然语言,计算机语言必须是精确的。它们的结构(语法)和意义(语义)必须是无异议的,从而开发者和计算机可以知道一个程序要做什么。为了提供这种精确,语言设计者和实现者使用正式的语法和语义符号。为了便于讨论语言的特性,我们本章首先介绍语法,然后第4章介绍语义。
一个示例,考虑阿拉伯数字的语法表示。这些数(numberal)由数字(digit)组成,可以通过这种方式枚举('|'表示或)
digit -> 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8| 9
digit 是表示 number 的语法块。通常意义上,我们说自然数是由任意长度的非空 digit 字符串表示,以非零 digit 开始:
non_zero_digit -> 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8| 9
natural_number -> non_zero_digit digit*
最后的 *
号表示其左侧符号零次或者多次重复。
当然,digit 仅仅是符号:纸上的墨水点或者屏幕上的像素。本身没有意义。我们需要为其添加语义,表示从零到九的自然数,由数学家定义。另外,我们也可以说它们表示颜色,或者日历中一周的日子。这些组成了相同语法的不同语义。类似的,我们来定义自然数的语义基于10,每个 digit 字符串的解释。可以为有理数,精度有限的实数,算术,赋值,控制流,声明,甚至所有的编程语言通过类似方式设计语法规则和语义解释。
区分语法和语义至少两个原因。首先,不同的编程语言经常通过不同的语法提供类似的语义。如果人们能够识别出不同语法的相似语义就能更容易的学习新语言。其次,存在一些高效优雅的算法帮助 compiler 或者 interpreter 发现计算机程序的语法结构(但是不是语义!),这些算法可以驱动编译或者解释过程。
本章我们集中于语法:我们如何设计编程语言的结构规则,compiler 如何识别给定程序的结构。有两个任务--确定语法规则然后根据这些规则解析给定的程序--是有区别的。对想要编写有效程序的程序员对一个任务感兴趣,compiler 对第二个任务感兴趣。第一个任务依赖 regular expression 和 context-free grammars,这些表明了如何构建有效的程序。第二个任务依赖 scanners 和 parsers,可以识别程序的结构。我们在 2.1 展开第一个任务,2.2 和 2.3 展开第二个任务。
在 2.4 ,我们更深入的讨论 scanning 和 parsing 的理论知识。在理论上,scanner 是有限状态机(DFA),识别出程序的 token。parser 是确定的 push-down automaton(PDA),识别出程序的上下文无关语法。并且证明了可以从 regular expression 和 context-free grammars 生成 scanner 和 parser。这个任务可以通过 Unix 下的 lex yacc
工具完成。可能除了计算机没有其他领域理论与实践之间的联系如此紧密清晰。