hust编译原理实验报告2Word格式.docx
《hust编译原理实验报告2Word格式.docx》由会员分享,可在线阅读,更多相关《hust编译原理实验报告2Word格式.docx(33页珍藏版)》请在冰豆网上搜索。
<
=>
>
==;
()++!
!
=
上面所包含的运算符与界符不是很全面,因此无法识别一些复杂的运算符,例如&
&
、||、%等。
(3)其他单词是标识符(ID)和整型常数(NUM),通过以下正规式定义:
ID=letter(letter|digit)*
NUM=digitdigit*
标识符由字母和数字组成,且首字符必须为字母;
这里只对整型常数进行识别,浮点数数据在识别过程中会对小数点进行报错。
(4)空格由空白、制表符和换行符组成。
空格一般用来分隔ID、NUM、运算符、界符和关键字,制表符和换行符等都只是对程序起到一个可读性的作用,在词法分析中没有实际意义,因此词法分析阶段通常被忽略。
(5)注释由/**/及其内部内容组成,在词法分析阶段也对程序的分析没有实际意义,因此也要被忽略。
关键字的判定规则是先进行标识符的识别,识别到最终状态后,在关键字数组中查找,看此标识符是否为关键字,如果是则将其划为关键字一类,否则作为标识符。
因此,此正规式的主要分析在标识符ID中会详细分析,在分析完后只要通过一个strcmp函数就可以分辨出是否关键字。
2、标识符与运算符
各种单词符号对应的种别码
单词种别是语法分析需要的信息,而单词自身的值则是编译其他阶段需要的
信息。
因此词法分析器的输出结果表示为:
(单词种别,单词自身的值)
单词的种别采用整数编码表示,下面给出了词法分析器的各个单词符号的
种别码。
Start:
1
If:
2
Then:
3
Int:
4
While:
5
Do:
6
For:
7
End:
8
字母:
10
数字:
11
*:
13
/:
14
+:
15
-:
16
17
>
20
<
23
=:
25
;
26
(:
27
):
28
!
2.1标识符ID:
标识符ID的正则式表示如下:
ID=letter(letter|digit)*
letter=a~z
digit=0~9
根据以上正则式画出NFA如下图。
图1ID的NFA表示
下面将这个NFA转换为DFA。
利用子集构造法,构造上述NFA的DFA步骤如下:
(1)首先计算ԑ—closure
(1),令0=ԑ—closure
(1)={1},0未被标记,它现在是子集族C的唯一成员。
(2)标记0;
令1=ԑ—closure(move(0,letter))={2,3,4,6,9},将1加入到C中,1未被标记。
令2=ԑ—closure(move(0,digit))=Ф,不再计算。
(3)标记1;
计算3=ԑ—closure(move(1,letter))={3,4,5,6,8,9},将3加入到C中,它未被标记。
计算4=ԑ—closure(move(1,digit))={3,4,6,7,8,9},将4加入到C中,它未被标记。
(4)标记3;
计算ԑ—closure(move(3,letter))={3,4,5,6,8,9},即3,它已经在C中了。
计算ԑ—closure(move(3,digit))={3,4,6,7,8,9},即4,它已经在C中了。
(5)标记4;
计算ԑ—closure(move(4,letter))={3,4,5,6,8,9},即3,它已经在C中了。
计算ԑ—closure(move(4,digit))={3,4,6,7,8,9},即4,它已经在C中了。
至此,算法终止共构造了4个子集:
0={1}1={2,3,4,6,9}
3={3,4,5,6,8,9}4={3,4,6,7,8,9}
那么上述NFA构造的DFA为:
(1)S={[0],[1],[3],[4]}
(2)Σ={letter,digit}
(3)D([0],letter)=[1]
D([1],letter)=[3]D([1],digit)=[4]
D([3],letter)=[3]D([3],digit)=[4]
D([4],letter)=[3]D([4],digit)=[4]
(4)0=[0]
(5)=[4]
为了方便书写,将[0],[1],[3],[4]分别重命名为A,B,C,D。
则重新
得到的DFA如下图所示。
图2ID的DFA表示
上面的DFA并不是最小DFA,如果要转换为最小DFA,则只需将B与D状态合并即可。
参考如下。
图3ID的mDFA
根据DFA进行辨别标识符的程序设计,算法分析如下:
(1)得到第一个首单词,确定为字母,记下当前位置为初始位置;
(2)向后取一个单词;
(3)如果该单词为字母或者数字,跳到
(2)继续执行;
(4)如果该单词为空格符或者换行符等界符,则停止执行;
(5)在停止处,去初始位置到当前位置的单词单元到缓冲区,作为整个标识符输出;
(6)程序结束。
该部分程序没有考虑关键字判别,因此,在上述算法中,在第5步之后要进行操作:
根据得到的缓冲区的数据,依次与关键字数组中的关键字进行比较,如果相同,那么这个标识符就是关键字,将它划为关键字一类输出。
含有关键字比较的程序流程图如图4。
图4ID与关键字程序流程
2.2运算符和界符:
运算符和界符的正则式表示为:
mark=+|-|*|/|=|==|(|)|[|]|{|}|!
|!
=|++|<
|<
=|>
|>
=|,|;
|:
运算符的检索也比较简单,因为大多只有一个符号,对于双目运算符则按如下方式识别:
+→++(后一位为+)|+(后一位不为+)
-→负号(在=或者(之后)|减号(不在=或者(之后)
→<
=(后一位为=)|<
(后一位不为=)
→>
=(后一位为=)|>
→!
=(后一位为=)|!
/→注释(后一位为/或者*)|除号(后一位不为/或者*)
按照以上思想,如果碰到上述运算符,则相应的查看后一位的值,按条件识别不同的单目运算符和双目运算符。
2.3整型常数:
整型常数NUM的正则式表示如下:
根据正则式画出相应的NFA如图4。
同样,根据子集构造法,如同ID的转换方法类似可以得到相应的DFA。
NUM的DFA表示如图5。
图5NUM的NFA表示
DFA表示如下:
0={1},1={2,3,5},2={3,5}
(1)S={[0],[1],[2]}
(2)Σ={digit}
(3)D([0],digit)=[1]
D([1],digit)=[2]
D([2],digit)=[2]
(5)=[2]
将[0],[1],[2]分别用A,B,C代替,则可以得到NUM的DFA表示。
图6NUM的DFA表示
同理,根据DFA可以将B状态合并,这样得到的mDFA如图7。
图7NUM的mDFA
因此,整型数据的递归方法与标识符类似,这里不列举其算法思想与流程图了。
在递归过程中的计数方法为依次往后取数的过程中,原值乘十后与最低位相加,算式为:
uWord.value.T2=uWord.value.T2*10+strSource[gnLocate]-'
0'
。
其中,等式左边uWord.value.T2为存放整型数据的缓冲区,strSource[gnLocate]为当前位置的数值的ascii码字符值,因此需减去字符0的ascii码值。
另外,还有负数的问题。
负数问题的解决方式是一样的。
在运算符判断过程中如果已经发现“-”为负号,那么其后的整型常数仍旧按照上述方法取指,但是计算数值的公式为:
uWord.value.T2=uWord.value.T2*10-strSource[gnLocate]-'
这样得到的整个数据即为正确的负数值。
三程序实现
源代码
#include<
stdio.h>
string.h>
iostream>
charprog[80],token[8];
charch;
intsyn,p,m=0,n,row,sum=0;
char*rwtab[8]={"
start"
"
if"
then"
int"
while"
do"
for"
end"
};
usingnamespacestd;
voidscaner()
{
for(n=0;
n<
8;
n++)token[n]=NULL;
ch=prog[p++];
while(ch=='
'
){
ch=prog[p];
p++;
}
if((ch>
='
a'
ch<
z'
)||(ch>
A'
Z'
)){//标示符或者变量名
m=0;
while((ch>
9'
)){
token[m++]=ch;
token[m++]='
\0'
p--;
syn=10;
n++)//将识别出来的字符和已定义的标示符作比较,
if(strcmp(token,rwtab[n])==0){
syn=n+1;
break;
}elseif((ch>
)){//数字
{
sum=0;
sum=sum*10+ch-'
syn=11;
if(sum>
32767)
syn=-1;
}elseswitch(ch){//其他字符
case'
'
if(ch=='
syn=21;
}elseif(ch=='
syn=22;
}else{
syn=23;
syn=24;
syn=20;