补录与分享本科实验,以示纪念。
一、实验要求
基于前面的实验,编写一个程序对使用 C—语言书写的源代码进行语义分析,输出语义分析中发现的错误(涉及 17 种错误类型)并完成实验报告,实验中主要使用 C 语言。
- 基本要求
a. 对程序进行语法分析,输出语法分析结果;
b. 能够识别多个位置的语法错误。 - 附加要求
a. 修改假设3在文法中加入函数声明(需要修改文法)并能够检查函数声明涉及的两种错误:函数声明了但没定义、函数多次声明冲突。
b. 修改假设 4 在假设“变量的定义可受嵌套作用域的影响,外层语句块中定义的变量可以在内层语句块中重复定义,内层语句块中定义的变量到了外层语句块中就会消亡,不同函数体内定义的局部变量可以相互重名”。
c. 修改前面的C–语言假设5,将结构体间的类型等价机制由名等价改为结构等价。在新的假设5下,完成对错误类型1-17的检查。
二、实验设计
一、文法可视化的建立,方便后续进行语义分析。
二、sematic.c文件
bool dealWithProgram(Node *node);
bool dealWithExtDefList(Node *node);
bool dealWithExtDef(Node *node);
bool dealWithSpecifier(Node *node, ValueTypes *type, char **name);
bool dealWithTYPE(Node *node, ValueTypes *type);
bool dealWithStructSpecifier(Node *node, ValueTypes *type, char **name);
bool dealWithTag(Node *node, char **name);
bool dealWithOptTag(Node *node, char **name);
bool dealWithExtDecList(Node *node, ValueTypes *type, char **name);
bool dealWithVarDec(Node *node, Symbol *s);
bool dealWithDefList(Node *node, Symbol *s);
bool dealWithDef(Node *node, Symbol *s);
bool dealWithDecList(Node *node, Symbol *s, ValueTypes *type, char **name);
bool dealWithDec(Node *node, Symbol *s, Symbol *field);
bool dealWithFunDec(Node *node, Symbol *s);
bool dealWithVarList(Node *node, Symbol *s);
bool dealWithParamDec(Node *node, Symbol *s);
bool dealWithCompSt(Node *node);
bool dealWithStmtList(Node *node);
bool dealWithStmt(Node *node);
bool dealWithExp(Node *node, ExpType *expType);
bool dealWithArgs(Node *node, ParaType *parameters);
语义分析需要对每个匹配的产生式进行分析,因此需要写专门的函数进行操作。每个函数的操作均不一样,但也拥有相同的操作:
bool checkNode(Node *node, Types type) {
if (node == NULL) {
addLogNullNode(type);
return false;
}
else if (node->type != type) {
addLogTypeDismatch(type);
return false;
}
addLogStartDealing(type);
return true;
}
bool hasBrothers(Node *node, int n, ...) {
va_list vaList;
va_start(vaList, n);
Types next;
for (int i = 0; i < n; ++i) {
next = va_arg(vaList, Types);
if (node == NULL || next != node->type) return false;
node = node->brother;
}
return node == NULL;
}
checkNode函数检查每个结点是否规范,hasBrothers函数判断某个节点是否只拥有若干个指定的兄弟。
在每个函数中,基本实现为:
(1)判断结点是否合法;
(2)检查孩子结点
(3)匹配文法并做出相应操作
(4)返回操作是否成功
例如:
bool dealWithProgram(Node *node) {
if (checkNode(node, _Program) == false) return false;
Node *c = node->child;
if (c == NULL) {
addLogNullChild(_Program);
return false;
}
if (hasBrothers(c, 1, _ExtDefList)) {
return dealWithExtDefList(c);
}
else {
addLogWrongChild(_Program);
return false;
}
}
三、log.c文件
采用单独的日志文件进行输出。我们将需要输出的消息存储在一个消息链表里,在分析完文件后顺序输出消息。链表结构为:
#define MESSAGE_LENGTH 256
typedef struct Info {
char *content;
struct Info *next;
} Info;
typedef struct Log {
Info *head;
Info *tail;
} Log;
extern Log *SemanticError;
extern Log *SemanticAnalysisLog;
SemanticError存储语义错误消息,SemanticAnalysisLog存储分析日志,以供调试使用。
其中,日志包含的方法有:
Log *initLog();
bool addLogInfo(Log *log, char *content);
bool outputLog(Log *log, FILE *file);
bool reportError(Log *log, int type, int line, char *message);
在程序中,封装了以下方法,以供显示日志消息:
bool addLogNullNode(Types type) {
char message[MESSAGE_LENGTH];
sprintf(message, "when dealing with %s, this %s is NULL.\n", typeToString(type), typeToString(type));
addLogInfo(SemanticAnalysisLog, message);
return true;
}
bool addLogTypeDismatch(Types type) {
char message[MESSAGE_LENGTH];
sprintf(message, "when dealing with %s, this node is not %s.\n", typeToString(type), typeToString(type));
addLogInfo(SemanticAnalysisLog, message);
return true;
}
bool addLogNullChild(Types type) {
char message[MESSAGE_LENGTH];
sprintf(message, "when dealing with %s, child node is NULL.\n", typeToString(type));
addLogInfo(SemanticAnalysisLog, message);
return true;
}
bool addLogWrongChild(Types type) {
char message[MESSAGE_LENGTH];
sprintf(message, "when dealing with %s, this %s has a wrong child.\n", typeToString(type), typeToString(type));
addLogInfo(SemanticAnalysisLog, message);
return true;
}
bool addLogStartDealing(Types type) {
char message[MESSAGE_LENGTH];
sprintf(message, "Start dealing with %s.\n", typeToString(type));
addLogInfo(SemanticAnalysisLog, message);
return true;
}
bool addLogEmptyNode(Types type) {
char message[MESSAGE_LENGTH];
sprintf(message, "when dealing with %s, this %s is empty.\n", typeToString(type), typeToString(type));
addLogInfo(SemanticAnalysisLog, message);
return true;
}
四、main函数的改动
在main函数中,之前的操作不变,后面添加:如果词法分析和语法分析正确,则进行语义分析,并输出所有的信息:
int main(int argc, char **argv) {
if (argc <= 1) {
printf("pass filename to scanner\n");
return -1;
}
else {
FILE *f = fopen(argv[1], "r");
if (!f) {
printf("fopen failed:%s\n", argv[1]);
return -1;
}
yyrestart(f);
yyparse();
FILE *semanticAnalysisLogFile = fopen("SemanticAnalysisLog.txt", "w");
FILE *grammarTreeFile = fopen("grammarTree.txt", "w");
FILE *hashTableFile = fopen("hashTable.txt", "w");
if (syntax_correct && lexical_correct) {
symbolTable = initializeHashSet(HASH_SIZE);
SemanticAnalysisLog = initLog();
SemanticError = initLog();
printTree(root, 0, grammarTreeFile);
dealWithProgram(root);
checkFunction();
outputLog(SemanticAnalysisLog, semanticAnalysisLogFile);
outputHashTable(symbolTable, hashTableFile);
outputLog(SemanticError, stdout);
// printFunctions();
}
destroyTree(root);
return 0;
}
}
三、实验结果
- test1
int fun(){
int i;
}
int main(){
float f = 0.;
f = f + 0.1;
fun();
return 0;
}
![](https://img-blog.csdnimg.cn/direct/b8006a15718148f4a8d43dbfc4493a37.png#pic_center)
该例子无词法、语法、语义错误,故无输出
- test2
int main(){
int i = 0;
int i; // error 3
j = i + 1; // error 1
inc(i); // error 2
}
int main(){ // error 4
return 3.5; // error 8
}
![](https://img-blog.csdnimg.cn/direct/727a5c42991f407aa23931a47e7b8a4f.png#pic_center)
成功检测出所有语义错误,其中第五行符合多个语义错误,将符合的每一个错误都输出出来了。
- test3
int main(){
int i;
float j = 9; // error 5
i = 3.7; // error 5
10 = i; // error 6
i + 3.7; // error 7
return j; // error 8
}
![](https://img-blog.csdnimg.cn/direct/5fed18308ad347bab125fe7007d6651c.png#pic_center)
这里面的float j=9; 这句我们并没有报错,因为我们在词法分析中认为9可以作为一种浮点数。此外,我们使用gcc编译器实测,float j=9;并不会报错。
其他错误均与示例相同。
- test4
int func(){}
float func2(int m, float n){}
int main(){
int i;
float j[20];
func();
func(i); // error 9
func2(8, 8); // error 9
i[0]; // error 10
i(); // error 11
j[1.5] = 3.2; // error 12
}
![](https://img-blog.csdnimg.cn/direct/ef741329f5704b9e9047c41f366e39a0.png#pic_center)
其中第12行实际上是不符合语法规范的,我们让语法检查对它“放行”,在语义检查中也能查到问题。
- test5
struct sa{
int a;
float b;
int b; // error 15
}c;
struct sf{
int x;
};
struct sf{ // error 16
int y;
};
int main(){
struct sk mm; // error 17
struct sa nn;
int a[10];
c.f; //error 14
c.b;
a.b; //error 13
}
![](https://img-blog.csdnimg.cn/direct/812950c619bc41c2b47bb42733e6d684.png#pic_center)
由于没有实现作用域的附加要求,这里面在第17行多报了一个错。
- testfj1
int f1(); // error 18
int f2();
int f2(int f); // error 19
float f2();
int f3(){
int x;
{int x;}
}
int f4(){
int x;
}
![](https://img-blog.csdnimg.cn/direct/ad5a02ea2cbb4d89a72a59597b2e65a0.png#pic_center)
我们实现了对函数冲突声明和只声明不定义的检查,并能成功报错。由于未实现嵌套作用域的附加要求,第7行和第10行多报了两个错。
- testfj2
struct A{
float a;
int c;
};
struct B{
float b;
float c;
};
struct C{float m; int n;};
int main(){
struct A aa;
struct B bb;
struct C cc;
aa = bb;
aa = cc;
}
![](https://img-blog.csdnimg.cn/direct/87cb4006cdb74accade25ee9d88b2d6c.png#pic_center)
同样地,由于作用域的问题,我们多报了一个错。
在结构体赋值时,由于aa和bb不等价,因此不能赋值,我们给出了报错。
而aa和cc是结构等价的,我们实现了把名等价变成结构等价的附加功能,因此aa和cc之间的赋值没有报错。
四、附完整代码
github 链接:https://github.com/zheliku/byyl---Semantic-Analysis。