编程语言导论/意义的追寻
语义学是关注编程结构含义定义的领域。有几种不同的符号来定义这种含义。以下是一些示例:
上述三种不同的技术提供了以形式化方式解释编程语言中每个语法元素作用的方法。形式化意味着明确且可机械化。如果一个定义只有一种解释方式,那么它就是明确的。如果我们可以在机器上编写该定义,那么它就是可机械化的。因此,可以获得编程语言的解释器。
形式化有很多优点。首先,它有助于理解编程语言。程序的含义并不总是显而易见的。即使是专家有时也会难以理解代码的行为。而且,在不同语言中实现的两个非常相似的程序可能会有不同的行为。例如,让我们考虑以下在 C 语言中实现的程序:
#include <stdio.h>
int main() {
int x = 1;
x += (x = 2);
printf("x = %d\n", x);
}
该程序打印x = 4。现在,让我们看看另一个程序,这次在 Java 中实现:
public class T {
public static void main(String args[]) {
int x = 1;
x += (x = 2);
System.out.println("x = " + x);
}
}
该程序打印x = 3。令人惊讶,不是吗?那么,哪个程序是错误的?答案是:它们都没有错。它们都按照自己的语义做到了预期的效果。在 C 语言中,赋值x += (x = 2)被翻译成类似以下的东西:x = (x = 2) + x。另一方面,在 Java 中,我们会得到类似以下的东西:tmp = x; x = 2; x = x + tmp.
我们将看到如何使用操作语义来描述编程语言的含义。操作语义通过抽象机来描述含义。抽象机是一个解释器,即一台机器。但是,这台机器不是由螺栓和电线制成的。它是用数学构建的。这就是名称中“抽象”的来源。
为了构建抽象机,我们需要一个数据结构来表示程序。我们已经知道这样的数据结构:它被称为语法树,我们在解析过程中生成它。但是,语法树中有许多元素对程序的含义没有贡献。例如,在 C 命令x = 1; x = x + 1;中,两个分号对存储在x中的最终值没有太大影响。在这种情况下,分号的存在是为了帮助解析器识别命令的结束位置。同样,表达式x * (y + z)中的括号等标记已经编码在语法树的结构中。在构建编程语言的解释器时,我们实际上不需要跟踪这些标记。因此,在设计解释器时,我们需要一些我们称为抽象语法的东西来表示程序。与具体语法不同,抽象语法只包含让解释器在程序结构中导航的必要元素。