语法设计
在聚焦到具体的语法规则内部结构之前,我们要先讨论下语法的整体剖析以及如何形成一套初始的语法骨架。
文法文件通常是由一个命名文法的头和一系列可以彼此调用的规则组成。就像下面的那样:
grammar MyG;
rule1 : «stuff» ;
rule2 : «more stuff» ;
...
设计语法就是要搞清楚«stuff»是什么?哪个规则是开始规则。这要求我们需要知道给定语言的一系列代表性的输入例子。当然,从语言参考手册或者其它语法分析器生成器格式而来的语法也是有帮助的。
正确设计语法的方法是借鉴功能分解或者自顶向下的设计,从粗粒度级别到细粒度级别逐步定义语言结构并把它们编码为语法规则。所以,我们的第一个任务就是找到粗粒度语言结构的名字,同时它也是开始规则。在英语中我们使用sentence,对于XML文件来说它则是document。
设计开始规则的内容是用英语伪代码描述整个输入格式的问题。例如,“a comma-separated-value (CSV) file is a sequence of rows terminated by newlines.”这段文字,在is a左边的至关重要的单词file是规则名字,在is a右边的所有内容则成为在规则定义右则的«stuff»的内容:
file : «sequence of rows that are terminated by newlines» ;
然后我们通过描述在开始规则右侧被确定的元素来进行下一个粒度级别的设计。在规则右侧的名词通常是对记号或尚未定义的规则的引用,这些记号是那些我们在正常情况下视为单词、标点符号、运算符的元素。就像单词是英语句子中的原子成分那样,记号在语法规则中也是如此。规则引用则涉及到像row那样需要被分解为更详细部分的其它语言结构。
进入细节的另外一层,我们可以说row是一系列被逗号分隔的field,而field则是一个数字或字符串。就像以下所示:
row : «sequence of fields separated by commas» ;
field : «number or string» ;
当没有规则需要再定义时,我们就得到了语法的一个粗略的草图。
如果有其它格式的语法作为参考的话设计语法会容易的多,但小心不要盲目地遵循它,否则你会误入歧途的。非ANTLR格式的语法只是让你知道别人是如何决定分解语言中的短语的,它最大的作用就是可以给我们一份规则名称的列表用作参考。
不推荐从参考手册上复制粘贴语法到ANTLR,然后再通过细微的调整让它工作。把它当作一套指南而不是一段代码是更好的办法。为了清晰地描述语法,参考手册通常是相当松散的。这意味着语法能识别大量不在语言中的句子,或者语法可能不够明确,可以用多种方法匹配相同的输入序列。例如,语法可能会说表达式可以调用一个构建器或者访问一个函数,问题是像T(i)这样的输入可以同时匹配两者。理想情况下,在语法中是不能有这样的二义性的,每个输入句子我们只需要一种解释。
在另一个极端,参考手册中的语法有时过于明确地说明了规则。有些约束是需要在分析完输入后实施的,而不是试图对语法结构实施约束。例如,W3C XML语法就显式地指定标签中什么地方必须要有空格以及什么地方的空格可以省略。但事实是我们可以简单地让词法分析器在把记号发送给语法分析器之前去除空格,不需要在语法中到处测试它。
规格还说<?xml …>标签可以有两个附加属性encoding和standalone。我们需要知道约束,但它是很容易去允许任何属性名字,然后在语法分析后检查语法分析树,以确保所有这些限制都满足的。 归根结底,XML只是嵌在文本中的一对标签,因此它的语法结构是相当直白的。唯一的挑战是如何分别对待什么在标签内以及什么在标签外。
识别语法规则并用伪代码表示它们的右侧部分最初是个挑战,但当你为更多的语言构建语法后它会变得越来越容易。一旦我们有了伪代码,我们就需要把它转换成ANTLR表示法,以便能得到一个可工作的语法。