要求
一、目的和内容
1.实验目的:通过完成词法分析程序,了解词法分析的过程。
2.实验内容:用C/C++实现对Pascal的子集程序设计语言的词法识别程序。
3.实验要求:将该语言的源程序,也就是相应字符流转换成内码,并根据需要是否对于标识符填写相应的符号表供编译程序的以后各阶段使用。
二、程序设计语言的描述
程序设计语言的描述采用扩充的BNF表示:
<程序>→<程序首部><分程序>.
<程序首部>→PROGRAM标识符;
<分程序>→[<常量说明部分>][<变量说明部分>][<过程说明部分>]<复合语句>
<常量说明部分>→CONST<常量定义>{,<常量定义>};
<常量定义>→标识符=无符号整数
<变量说明部分>→VAR<变量定义>(;<变量定义>);
<变量定义>→标识符{,标识符}:<类型>
<类型>→INTEGER|LONG
<过程说明部分>→<过程首部><分程序>;{<过程首部><分程序>;}
<过程首部>→PROCEDURE标识符;| PROCEDURE标识符(标识符:<类型>);
<语句>→<赋值语句>|<条件语句>|<当型循环语句>|<过程调用语句>
|<读语句>|<写语句>|<复合语句>|ε
<赋值语句>→标识符:=<表达式>
<条件语句>→IF<条件>THEN<语句>
<当型循环语句>→WHILE<条件>DO<语句>
<过程调用语句>→标识符 | 标识符(<表达式>)
<读语句>→READ(标识符,{标识符})
<写语句>→WRITE(<表达式>{,<表达式>})
<复合语句>→BEGIN<语句>{;<语句>}END
<条件>→<表达式><关系运算符><表达式> | ODD<表达式>
<表达式>→[+|-]<项>{<加型运算符><项>}
<项>→<因子>{<乘型运算符><因子>}
<因子>→标识符 | 无符号整数 | (<表达式>)
<加型运算符>→+|-
<乘型运算符>→* | /
<关系运算符>→=|<>|<|<=|>|>=
其中:
< >:用左右尖括号括起的字符串表示非终结符号
::= : 定义为
{ }:表示该语法成分可以0—n次重复。
[ ]:表示方括号内为可选项,即0或1次。
三、程序设计语言单词的内部编码
如表1-1为词法分析中的内码单词对照表。
表1-1 内部码对照表
内码 | 单词 | 内码 | 单词 | 内码 | 单词 | 内码 | 单词 |
1 | PROGRAM | 2 | CONST | 3 | VAR | 4 | INTEGER |
5 | LONG | 6 | PROCEDURE | 7 | IF | 8 | THEN |
9 | WHILE | 10 | DO | 11 | READ | 12 | WRITE |
13 | BEGIN | 14 | END | 15 | ODD | 16 | + |
17 | - | 18 | * | 19 | / | 20 | = |
21 | <> | 22 | < | 23 | <= | 24 | > |
25 | >= | 26 | . | 27 | . | 28 | ; |
29 | : | 30 | := | 31 | ( | 32 | ) |
33 | 无符号整数 | 34 | 标识符 | 35 | # |
四、词法分析程序的设计思想
为了实现的编译程序实用,这里规定源程序可采用自由书写格式,即一行内可以书写多个语句,一个语句也可以占领多行书写;标识符的前20个字符有效;整数用2个字节表示;长整数用4个字节表示。这样词法分析程序的主要工作为:
(1)从源程序文件中读入字符。
(2)统计行数和列数用于错误单词的定位。
(3)删除空格类字符,包括回车、制表符空格。
(4)按拼写单词,并用(内码,属性)二元式表示。
(5)根据需要是否填写标识符表供以后各阶段使用。
代码实现
1.判断函数
// 判断是否为关键字
int isKeyword(char* s) {
const char* letter[15] = { "program", "const", "var", "integer", "long", "procedure", "if", "then", "while", "do", "read", "write", "begin", "end", "odd" };
for (int i = 0; i < 15; i++){
if (strcmp(s, letter[i]) == 0)
return 1;
}
return 0;
}
// 判断是否为算符或界符
int isOpdelimiter(char* s) {
const char* opdelimiter[17] = { "+", "-", "*", "/", "=", "<>", "<", "<=", ">", ">=", ",", ".", ";", ":", ":=", "(", ")" };
for (int i = 0; i < 17; i++){
if (strcmp(s, opdelimiter[i]) == 0)
return 1;
}
return 0;
}
// 判断是否为数字字符串
int isDigitString(char* s) {
int i, f = 1; // 初始化 f 为 1,假设字符串都是数字
for (i = 0; i < strlen(s); i++) {
if (s[i] < '0' || s[i] > '9') {
f = 0; // 如果发现非数字字符,设置 f 为 0
break;
}
}
return f;
}
// 判断是否为合法标识符
int isValidIdentifier(char* s) {
// 检查字符串的第一个字符是否为字母或下划线
if (!isalpha(s[0]) && s[0] != '_')
return 0;
for (int i = 1; s[i] != '\0'; i++) {
if (!isalnum(s[i]) && s[i] != '_')
return 0;
}
return 1;
}
//判断“#”
int isJin(char* s){
if (s[0] == '#')
return 1;
else
return 0;
}
//判断类型
int findType(char* s){
if (isKeyword(s)) // 关键字
return 1;
else if (isOpdelimiter(s)) // 算符和界符
return 2;
else if (isDigitString(s)) // 数字字符串
return 3;
else if (isValidIdentifier(s)) // 标识符
return 4;
else if (isJin(s)) // '#'字符
return 5;
else
return 0;
}
2.对应内码函数
//对应内码
int codeOfkey(char* str){
const char* key[15] = { "PROGRAM","CONST","VAR","INTEGER","LONG","PROCEDURE","IF","THEN","WHILE","DO","READ","WRITE","BEGIN","END","ODD" };
const int arr[15] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,24,15 };
for (int i = 0; i < 15; i++){
if (strcmp(str, key[i]) == 0)
return arr[i];
}
}
int codeOfopdelimiter(char* str)
{
const char* opebounder[17] = { "+","-","*","/","=", "<>","<", "<=",">",">=",",",".",";",":",":=","(",")" };
const int arr[17] = { 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32 };
for (int i = 0; i < 17; i++){
if (strcmp(str, opebounder[i]) == 0)
return arr[i];
}
}
3.返回二元式
void printBinaryform(char* s){
int type = findType(s);
switch (type) {
case 1:printf("(%d,%s)", codeOfkey(s), s); break; //关键字
case 2:printf("(%d,%s)", codeOfopdelimiter(s), s); break; //算符与界符
case 3:printf("(33,%s)", s); break; //数字字符串
case 4:printf("(34,%s)", s); break; //标识符
case 5:printf("(35,%s)", s); break; //“#”字符
default:printf("(NULL,%s)", s);
}
}
4.GETSYM
int GETSYM(const char* filename) {
FILE* file = fopen(filename, "r");
if (file == NULL) {
printf("无法打开文件\n");
return 0;
}
int row = 1; // 初始化行计数器
int line = 1; // 初始化列计数器
int ch;
//Getch
while ((ch = fgetc(file)) != EOF) {
if (isspace(ch)) {
continue;
}
if (ch == '\n') {
row++;
line = 1;
printf("\n");
}
else
line++;
char word[100];
int index = 0;
// 读取当前单词
while (!isspace(ch) && ch != EOF) {
word[index++] = ch;
ch = fgetc(file); // 继续读取下一个字符
}
word[index] = '\0';
// 发生错误
if (findType(word) == 0) {
printBinaryform(word);
printf("*****Error information: row=%d, line=%d\n", row, line);
/*
fclose(file);
return 0;
*/
}
else {
printBinaryform(word);
}
}
fclose(file);
return 1;
}
结果展示
Demo:
PROGRAM SimplePascal ;
VAR
num1 , num2 , sum : INTEGER ;
BEGIN
WRITE ( ' Enter the first integer : ' ) ;
READ ( num1 ) ;
WRITE ( ' Enter the second integer : ' ) ;
READ ( num2 ) ;
sum : = num1 + num2 ;
WRITE ( ' The sum of the two integers is : ' , sum ) ;
WRITE ;
READ ; { Wait for the user to press a key to prevent the program from exiting immediately }
END .
结果:
不足:
因为程序是按照“ ”区分字符串,在测试的Pascal代码中,算符、界符必须与其他字符之间有“ ”。