(1)根据 PL/0 语言的文法规范,编写PL/0语言的词法分析程序;或者调研词法分析程序的自动生成工具LEX或FLEX,设计并实现一个能够输出单词序列的词法分析器。
(2)通过设计调试词法分析程序,实现从源程序中分离出各种类型的单词;加深对课堂教学的理解;提高词法分析方法的实践能力。
(3)掌握从源程序文件中读取有效字符的方法和产生源程序的内部表示文件的方法。
(4)掌握词法分析的实现方法。
(5)上机调试编出的词法分析程序。
根据PL/0语言的文法规范,编写PL/0语言的词法分析程序。要求:
(1)把词法分析器设计成一个独立一遍的过程。
(2)词法分析器的输出形式采用二元式序列,即:(单词种类, 单词的值)
(1)基本字
单词的值 | 单词类型 | 正规式r |
---|---|---|
begin | beginsym | begin |
call | callsym | call |
const | constsym | const |
do | dosym | do |
end | endsym | end |
if | ifsym | if |
odd | oddsym | odd |
procedure | proceduresym | procedure |
read | readsym | read |
then | thensym | then |
var | varsym | var |
while | whilesym | while |
write | writesym | write |
(2)标识符
单词的值 | 单词类型 | 正规式r |
---|---|---|
标识符 | ident | (字母)(字母|数字)* |
(3)常数
单词的值 | 单词类型 | 正规式r |
---|---|---|
常数 | number | (数字)(数字)* |
(4)运算符
单词的值 | 单词类型 | 正规式r |
---|---|---|
+ | plus | + |
- | minus | - |
* | times | * |
/ | slash | / |
= | eql | = |
<> | neq | <> |
< | lss | < |
<= | leq | <= |
> | gtr | > |
>= | geq | >= |
:= | becomes | := |
(5)界符
单词的值 | 单词类型 | 正规式r |
---|---|---|
( | lparen | ( |
) | rparen | ) |
, | comma | , |
; | semicolon | ; |
. | period | . |
下面我们根据上述的正规式来构造该文法的NFA,如下图所示,其中状态0为初态,凡带双圈的状态均为终态,状态24是识别不出单词符号的出错情形,其他状态的识别情况如下图中右边的注释所示。
图1 根据正规式构造的NFA
替换规则如下图所示。
图2 替换规则
根据替换规则对NFA进行分裂,结果如下图所示。
图3 未化简DFA
一个确定有限自动机M的化简是指,寻找一个状态数比M少的DFA M’,使得L(M)=L(M’)。一个DFA M的状态最小化过程旨在将M的状态集分割成一些不相交的子集,使得任何不同的两子集中的状态都是可区别的,而同一子集中的任何两个状态都是等价的。最后,在每个子集中选一个代表,同时消去其它的等价状态。
最小化后的DFA如下图所示。
图4 最小化DFA
词法分析程序的输入是源程序,输出是一个个单词符号的二元组,它的任务是从左至右逐个字符地对源程序进行扫描,产生一个个的单词符号,把作为字符串的源程序改造成为单词符号串的中间程序。
首先逐行扫描源程序,然后遍历串的每一个字符,判断字符是不是空格、数学、字母、运算符或者界符,再进行下一步的判断,如果不符合这几种情况,将会归入出错处理。完整算法流程如下图所示。
图5 算法流程图
#include
#include
using namespace std;
//用于识别是基本字或标识符
void Letter(string str)
{
//识别基本字
if(str=="begin")
cout<<"(beginsym,begin)"<<endl;
else if(str=="call")
cout<<"(callsym,call)"<<endl;
else if(str=="const")
cout<<"(constsym,const)"<<endl;
else if(str=="do")
cout<<"(dosym,do)"<<endl;
else if(str=="end")
cout<<"(endsym,end)"<<endl;
else if(str=="if")
cout<<"(ifsym,if)"<<endl;
else if(str=="odd")
cout<<"(oddsym,odd)"<<endl;
else if(str=="procedure")
cout<<"(proceduresym,procedure)"<<endl;
else if(str=="read")
cout<<"(readsym,read)"<<endl;
else if(str=="then")
cout<<"(thensym,then)"<<endl;
else if(str=="while")
cout<<"(whilesym,while)"<<endl;
else if(str=="var")
cout<<"(varsym,var)"<<endl;
else if(str=="write")
cout<<"(writesym,write)"<<endl;
//识别标识符
else
cout<<"(ident,"<<str<<")"<<endl;
}
int main()
{
string str1,str;
while(cin>>str1)
{
//读入代码(字符串形式)
str = str+' '+str1;
}
//开始处理读入的代码
int length_str = str.length();
for(int i=0;i<length_str;i++)
{
//当遇到空格或换行时,跳过继续执行
if(str[i]==' ' || str[i]=='\n') continue;
//识别常数
else if(isdigit(str[i]))
{
string digit;
//以字符串形式表示这个常数
while(isdigit(str[i]))
{
digit +=str[i];
i++;
}
i--;
cout<<"(number,"<<digit<<")"<<endl;
}
//识别基本字或标识符
else if(isalpha(str[i]))
{
string lett;
//以字符串形式表示这个基本字或者标识符
while(isdigit(str[i])||isalpha(str[i]))
{
lett +=str[i];
i++;
}
i--;
Letter(lett);
}
//识别运算符
else
{
switch(str[i])
{
case '+':
cout<<"(plus,+)"<<endl;
break;
case '-':
cout<<"(minus,-)"<<endl;
break;
case '*':
cout<<"(times,*)"<<endl;
break;
case '/':
cout<<"(slash,/)"<<endl;
break;
case '=':
cout<<"(eql,=)"<<endl;
break;
case '<':
i++;
if(str[i]=='>')
{
cout<<"(neq,<>)"<<endl;
}
else if(str[i]=='=')
{
cout<<"(leq,<=)"<<endl;
}
else
{
i--;
cout<<"(lss,<)"<<endl;
}
break;
case '>':
i++;
if(str[i]=='=')
{
cout<<"(geq,>=)"<<endl;
}
else
{
i--;
cout<<"(gtr,>)"<<endl;
}
break;
case ':':
i++;
if(str[i]=='=')
{
cout<<"(becomes,:=)"<<endl;
}
break;
//识别界符
case '(':
cout<<"(lparen,()"<<endl;
break;
case ')':
cout<<"(rparen,))"<<endl;
break;
case ',':
cout<<"(comma,,)"<<endl;
break;
case ';':
cout<<"(semicolon,;)"<<endl;
break;
case '.':
cout<<"(period,.)"<<endl;
break;
//错误处理
default:
cout<<"error"<<endl;
break;
}
}
}
cout<<"Yes,it is correct."<<endl;
return 0;
}
【样例输入】
const a=10;
var b,c;
begin
read(b);
c:=a+b;
write(c)
end.
【样例输出】
(constsym,const)
(ident,a)
(eql,=)
(number,10)
(semicolon,;)
(varsym,var)
(ident,b)
(comma,,)
(ident,c)
(semicolon,;)
(beginsym,begin)
(readsym,read)
(lparen,()
(ident,b)
(rparen,))
(semicolon,;)
(ident,c)
(becomes,:=)
(ident,a)
(plus,+)
(ident,b)
(semicolon,;)
(writesym,write)
(lparen,()
(ident,c)
(rparen,))
(endsym,end)
(period,.)
样例一结果如下所示
图6 样例一测试结果
【样例输入】
const a=10;
var b,c,d;
begin
read(b);
read(c);
d:=a+b+c;
write(d)
end.
【样例输出】
(constsym,const)
(ident,a)
(eql,=)
(number,10)
(semicolon,;)
(varsym,var)
(ident,b)
(comma,,)
(ident,c)
(comma,,)
(ident,d)
(semicolon,;)
(beginsym,begin)
(readsym,read)
(lparen,()
(ident,b)
(rparen,))
(semicolon,;)
(readsym,read)
(lparen,()
(ident,c)
(rparen,))
(semicolon,;)
(ident,d)
(becomes,:=)
(ident,a)
(plus,+)
(ident,b)
(plus,+)
(ident,c)
(semicolon,;)
(writesym,write)
(lparen,()
(ident,d)
(rparen,))
(endsym,end)
(period,.)
样例二结果如下所示
图7 样例二测试结果
【样例输入】
const a=10;
const b=10;
var c;
begin
c:=a+b;
write(c)
end.
【样例输出】
(constsym,const)
(ident,a)
(eql,=)
(number,10)
(semicolon,;)
(constsym,const)
(ident,b)
(eql,=)
(number,10)
(semicolon,;)
(varsym,var)
(ident,c)
(semicolon,;)
(beginsym,begin)
(ident,c)
(becomes,:=)
(ident,a)
(plus,+)
(ident,b)
(semicolon,;)
(writesym,write)
(lparen,()
(ident,c)
(rparen,))
(endsym,end)
(period,.)
样例三结果如下所示
图8 样例三测试结果
由上一步中的三个测试样例可以得到,所有的测试样例均得到了相应的输出结果,说明代码编写成功,并且在代码中设置了出错处理,以便解决其他情况。
本次实验通过先写出正规式到NFA到DFA再到画出程序的流程图,一步一步地优化自己的编程流程,可以在自己脑海中形成清晰的框架,不会出现一些大的方向上的判断错误。同时也理解了从正规式到最小化DFA的每一步的含义。正规式是正规集的表现形式,通过正规式更好的人工设计成NFA(NFA与正规式的等价性),根据NFA运用子集法转换成DFA,解决了编程问题(DFA和NFA的等价性),最后用最小化简法对DFA进行化简,以达到最简的效果,有利于编程实现。
在程序编写时,用到了C++自带的头文件string,可以很好地处理输入串并对串进行分割,将界符、运算符和其他区分开,便于遍历。在调试程序过程中,一开始出现空格和换行无法识别的情况,于是就把这种情况单独编写了一个函数进行识别,便于串的后续识别。
通过这次实验对之前学到的词法分析有了进一步的了解,加深了对于词法分析的步骤的理解与领悟,对于今后对编译原理的学习有很大的帮助。
最后,感谢刘善梅老师和其他同学在这次实验中给予我的帮助,不胜感激!