1.4 建立语言需求

在确定我们所做的工作需要一种新的编程语言之后,我们需要花几分钟来确定需求。这个工作是无止境的,取决于要求项目取得什么样的结果。聪明的语言发明者不会从头开始创建全新的语法。相反,他们根据对一种现有流行语言的一系列修改来定义它。许多伟大的编程语言(Lisp、Forth、SmallTalk等)的成功都受到了极大的限制,因为它们的语法与主流语言有着不必要的差异。不过,我们的语言需求包括它看起来像什么,以及语法。

更重要的是,必须在编程语言需要超越现有语言的地方定义一组控制结构或语义。这有时会包括对现有语言及其库不能很好地服务的应用程序域的特殊支持。这种领域特定语言比较常见,整本书都在关注这个话题。我们这本书的目标是专注于为这种语言构建编译器和运行时系统的核心内容,与我们可能从事的领域无关。

在正常的软件工程过程中,需求分析将从功能性和非功能性需求的头脑风暴列表开始。编程语言的功能性需求涉及最终用户开发人员将如何与之交互的细节。我们可能无法预先考虑到语言的所有命令行选项,但可能知道是否需要交互性,或者单独的编译步骤是否可行。1.3节中对解释器和编译器的讨论,以及本书对编译器的介绍,可能会让我们做出这样的选择,但是Python语言是一个提供完全交互式接口的语言示例,即便输入的源代码被压缩成字节码,而不是加以解释。

非功能性需求是编程语言必须实现的属性,这些属性并不直接与最终用户开发人员的交互相关。非功能性需求包括诸如必须在什么操作系统上运行、执行速度必须多快,或者用此编程语言编写的程序必须在多小的空间内运行等。

关于执行速度必须多快的非功能性需求通常决定了我们是可以以软件(字节码)机器为目标还是需要以本机代码为目标。本机代码执行速度更快,但也很难生成,而且它可能会使编程语言在运行时系统特性方面的灵活性大大降低。我们可以选择先以字节码为目标,然后再使用本机代码生成器。

我学习的第一种编程语言是BASIC解释器,其程序必须能够在大小为4KB的内存中运行。当时BASIC对内存占用的要求很低。但是,即使在现代,在一个默认情况下Java无法运行的平台上发现自己也是很常见的!例如,在为用户进程配置了内存限制的虚拟机上,我们可能不得不学习一些笨拙的命令行选项,来编译或运行哪怕是简单的Java程序。

许多需求分析过程也定义了一组用例,并要求开发者为这些用例写说明。发明一种编程语言不同于一般的软件工程项目,但直到发明编程语言的任务完成,我们都有可能把路走偏。用例是我们使用软件应用程序执行的任务。当软件应用程序是一种编程语言时,如果不小心,用例可能过于笼统而没有用处,例如“编写我的应用程序”以及“运行我的程序”。虽然这两种语言可能不是很有用,但我们可能需要考虑编程语言实现是否必须支持程序开发、调试、单独编译和链接,以及与外部语言和库的集成等。虽然这些话题大多超出了本书的讨论范围,但我们将对其中一些话题展开讨论。

由于本书将介绍一种名为Jzero的语言的实现,这里提出一些对它的要求。其中一些要求可能看起来很随意。如果你不清楚其中某个要求来自哪里,那么答案是它要么来自我们的源灵感语言(plzero),要么来自以前教授编译器构造的经验:

❑Jzero应该是Java的严格子集。所有合法的Jzero程序同样应该是合法的Java程序。这个要求允许我们在调试语言实现时检查测试程序的行为。

❑Jzero应该提供足够的特性,以允许实现有趣的计算,包括if语句、while循环、多个函数以及参数。

❑Jzero应该支持一些数据类型,包括布尔、整数、数组和字符串类型。如后文所述,它只需要支持其功能的一个子集。这些类型足以允许将感兴趣的值输入和输出到计算中。

❑Jzero应该发出适当的错误消息,显示文件名和行号,包括试图使用Jzero中没有的Java特性的消息。我们需要合理的错误消息来调试该实现。

❑Jzero应该运行得足够快,以达到实用目的。这个要求很模糊,但它意味着我们不会只做一个纯粹的解释器。纯粹的解释器是一种非常复古的东西,让人想起20世纪60年代和70年代。

❑Jzero应该尽可能简单,这样我们才能对其加以解释。不幸的是,这排除了生成本机代码甚至JVM字节码的可能性。我们将提供自己的简单字节码机器。

随着过程进一步发展,可能还会出现更多的需求,但这也只是一个开始。由于受时间和空间的限制,也许这个需求列表对于还没考虑到的内容,而不是已经考虑到的内容更为重要。通过比较,以下是一些要创建Unicon编程语言的需求。