就编译器基本结构而言,词法分析(lexical analysis)是编译过程的第一阶段。实际上,在词法分析之前,有些编译器模型存在编译预处理阶段,C语言就是一个典型的例子。不过,大多数Pascal编译器都不存在预处理器。本书对编译预处理不作讨论。词法分析的任务是从左到右扫描与分析构成源程序的字符流(字符串),把字符流分解为多个单词(token)。每个单词都是具有独立含义且不可再分割的字符序列。在编译器框架中,完成词法分析任务的模块称为词法分析器。
读者可能对“单词”感到有点疑惑,不明白到底什么才是词法分析中所说的“单词”。试图回答这个问题就必须了解几个基本概念。这里,引入几个程序设计语言相关的名词。
(1)标识符:用户自定义的变量名、函数名等字符串。
(2)关键字:具有特殊含义的标识符。
(3)运算符:例如+、一、*、/等。
(4)常量:例如3.24、92等。
(5)界符:具有特殊含义的符号,如分号、括号等。
通常,程序设计语言的关键字也就是它的保留字,编译器不允许此类单词另作他用。不过,这并不是绝对的。有些语言关键字并不一定是保留字,例如,Fortran语言允许用户定义与关键字相同的标识符。因此,读者应该注意关键字与保留字是有区别的。
上述五类元素是最基本的原子,任何表达式、语句、函数、程序都是由这几类元素排列组合而成的。词法分析所讨论的“单词”就是这五类元素的总和。
例2-1 C语言程序段的词法分析结果见表2-1。
表2-1 词法分析的单词流
(www.daowen.com)
注意,表2-1的单词流并不是词法分析器真正的实际输出结果,只是一种逻辑表示而己。更详细的形式将在后续章节中讨论。根据单词的分类标准,可以将单词作如下归类,见表2-2。
表2-2 例2-1单词流的分类
这里,读者可能会有两个疑问:
(1)为什么“++”运算符不会分解为两个“+”运算符呢?
(2)为什么将“inti”分解为“int”和“1,,,而不是“inti”呢?
下面,笔者来解释一下这两个问题。在实际编译器设计中,任何词法分析器都必须满足一个原则,就是在符合词法定义的情况下进行超前搜索识别。例如,当C语言词法分析器读入了一个字符“+”后,由于C语言中存在“++”、“+=”运算符,那么,词法分析器会继续读入下一个字符。如果下一个字符是“+”或“=”时,词法分析器就将这两个字符作为一个运算符。然而,如果下一个字符不是“+”或“=”时,词法分析器就将前一个字符“+”作为一个运算符记录下来后,继续识别下一个单词。
根据这个原则,就可以解释为什么“int”没有被识别为“i”、“n”、“t”了。根据C语言标识符(关键字只是有特殊含义的标识符)定义的规则,标识符必须以字母或下画线开头,后跟字母、数字、下画线的任意组合。因此,当读入“i”后,继续读入“n”,由于“in”是合法的标识符,则继续读入“t”。直到读到“ ”时,发现“int”不满足标识符的定义,则将“int”记录下来即可。
那么,为什么需要超前搜索识别呢?这个问题非常简单,超前搜索保证了词法识别能遵守单词的极大搜索原则。也就是说,在满足词法定义的情况下,尽可能使单词的长度最长。那么,通过超前搜索一个字符,直到读入这个字符后,单词不能满足词法定义,也就完成了一个单词的识别了。如果没有极大搜索原则,当读入类似“int”的字符串时,词法分析器就将分解成“i”、“n”,“t”三个单词。因为,它们都是满足C语言标识符的定义规范的。这样,永远也不可能识别出长度大于1的标识符了。显然,这样的结果是不能接受的,因此,超前搜索识别的存在是必然的。
不过,词法分析器的设计难度很大程度上依赖于程序设计语言本身的规范。例如,C语言中“name”是完全合法的标识符,而在早期的Pascal语言中,“name”却是非法的标识符,因为Pascal语言的标识符定义原则是以字母开始,后跟字母、数字的任意组合。再如,Fortran语言规定除字符串中的空格以外,编译器将忽略其他空格,这意味着在Fortran中“int i”与“inti”是完全相同的。虽然这只是一个很小的规定,但是,读者可以尝试使用超前搜索识别单词。不难发现,这个规定给词法分析的设计带来很大的困难。
当然,程序设计语言毕竟就是人工语言,是人为定义而成的。在设计程序设计语言时,完全可以避免某些不利于编译器实现的语言规则。C、Pascal等语言的设计就吸取了先辈的教训,避免了一些人为造成的不必要的麻烦。因此,在设计一门程序设计语言时,应该尽可能避免关键字非保留字、空格忽略等类似情况的发生,否则将给词法、语法分析造成相当的障碍。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。