词法分析器:读入输入串,将其转换成被语法分析器分析的记号流。
词法分析器的一些功能:
如果词法分析器消除了空白符,语法分析器就不必再考虑空白符。修改文法使得语法中包含空白符的做法实现起来很难。
举例子,单个数字和数字序列都是整数,如何识别出来,作为一个记号(如num),记号的属性就是识别出来的整数的值。
程序设计语言使用标识符作为变量名、数组名、函数名和一些其他的语言对象名。文法常把标识符作为记号处理。基于这类文法的语法分析器在输入中每遇到一个标识符都赋予他们相同的记号id。如:
count = count + increment;
转换成记号流:
id = id + id;
区分记号id和与这个记号的实例相关的词素count和increment之间的不同是很有用的。
当输入流中出现形成标识符的词素时,我们需要某种机制来确定该词素以前是否出现过。一般用符号表,词素存储在符号表的一个表项中,而指向该词素的指针则成为记号id的一个属性。
如果相同的字符出现在多个记号的词素中,我们会遇到记号分割的问题。例如,Pascal中的<、<=和<>都包含<。如何有效识别这种记号?
词法分析器介于语法分析器和输入流之间,并与这两者交互。词法分析器从输入串读取字符并形成词素,然后将词素生成记号及其属性传递给编译器的下一个阶段。词法分析器需要把记号传给语法分析器之前可能会超前读入一些字符,以确定需要传递给语法分析器的正确记号。例如,在读到>
时,会读入下一个字符看看是不是>=,如果是就一起传,否则要将多读的字符退回。
词法分析器和语法分析器形成了“生产者-消费者”对。产生的记号保存在缓冲区,消费者去那里取。
读入字符和退回字符一般都通过建议一个输入缓冲区来实现。
实现一个简单的词法分析器,识别输入中的整数记号,并且将它对应的整数值设置到全局变量tokenval中。
为了允许数出现在表达式中,对原有的文法做一些修改。引入新的非终结符factor代替单个的数字,引入下面的产生式和语义动作
factor -> (expr)
| num {print(num.value)}
下面是上述factor的实现
factor()
{
if (lookahead == '(') {
match('('); expr(); match(')');
}
else if (lookahead == NUM) {
printf(" %d ", tokenval);match(NUM);
}
else error();
}
下面的是词法分析器的实现,即lexan函数
#include
#include
int lineno = 1;
int tokenval = NONE;
int lexan()
{
int t;
while(1) {
t = getchar();
if (t == ' ' || t == '\t')
;//去除空格和制表符
else if (t == '\n')
lineno = lineno + 1;
else if (isdigit(t)) {
tokenval = t - '0';
t = getchar();
while(isdigit(t)) {
tokenval = tokenval * 10 + t - '0';
t = getchar();
}
ungetc(t, stdin); //放回不是数字的那个字符
return NUM;
}
else {
tokenval = NONE;//表示不是数字
return t;
}
}
}
符号表是一种数据结构,通常用于保存源语言结构的各种信息。编译器在分析阶段收集信息放入符号表,在综合阶段使用符号表中的信息生成目标代码。如,在词法分析中,形成标识符的字符串或词素被存储在符号表的一个表项中。编译器的以后各阶段会在这个表项上逐步添加其他信息,如标识符的类型、用处(如用作过程名、变量名或标号)以及存储位置。在代码生成阶段,编译器使用这些信息生成存取这些变量的正确代码。
这里主要讲上一节讨论的词法分析器如何使用符号表。
与符号表有关的例程的功能主要是存取词素。当一个词素被保存时,我们也保存与该词素相关的记号。下边是在符号表上执行的操作:
insert(s,t):将字符串s和记号t插入符号表,返回相应表项的索引。
lookup(s):到符号表中查找字符串s,如果找到则返回相应表项的索引,否则返回0.
上述符号表子程序能够处理任何保留的关键字的集合。例如,考虑具有div和mod词素的了;两个记号div和mod,调用下面的方法初始化符号表
insert("div",div);
insert("mod",mod);
符号表初始化之后,调用lookup(“div”)将返回记号div,于是div不能再被用作标识符。
符号表数组symtable中的每个表项都是一个包含两个域的记录:一个域是指向词素开始位置的指针域lexptr,另一个域是存储记号的token域。符号表可以由更多的域以存储属性值,这里没有详细讨论。
function lexan:integer;
var lexbuf: array[0..100] of char;
c: char;
begin
loop begin
读到一个字符到c;
if c 是空格或者制表符then
什么都不做
else if c是换行符then
lineno := lineno + 1
else if c 是一个数字then begin
该数字和其后数字的所表示的数的值存入tokenval;
return NUM
end
else if c 是一个字母then begin
将c和其后的连续字母和数字存入lexbuf;
p := lookup(lexbuf);
if p = 0 then
p := insert(lexbuf, ID);
tokenval := p;
return 表项p的token域
end
else begin /*记号是单个字符*/
将tokenval置为NONE; /*没有属性*/
return 字符c的整数编码
end
end
end