老师布置的作业以前由于时间关系,再加上思想上不够重视,所以落下了一大堆没有完成的作业。现在要从头开始,一步步地再把这个作业做一遍,没做的同学也可以参考一下,这是必须交的前三次的作业,包括词法分析和语法分析(同时跪求完成语义分析和代码生成的童鞋的作业):
1、 C风格的注释
这里分为单行注释和多行注释,这样由于他们都是以/开头的,所以DFA便多了三个状态,我分别命名为INCOMMENTBEGIN,INSINGLELINECOMMENT,INMULLINECOMMENT,他们分别表示注释开始,单行注释和多行注释。由于注释是不需要保存的,所以我们不必增加tokenType类型。这样我们再加上getToken识别时的动作便可以了:
//进入c风格注释的开始状态
else if(c == '/'){
save = FALSE;
state = INCOMMENTBEGIN;
}
//c风格的注释,包括多行注释和单行注释
caseINCOMMENTBEGIN:
save = FALSE;
if(c == '/')
state = INSINGLELINECOMMENT;
else if(c == '*'){
state = INMULLINECOMMENT;
}
else{
ungetNextChar();
save = FALSE;
currentToken = ERROR;
}
break;
//多行注释
caseINMULLINECOMMENT:
save = FALSE;
if(c == EOF){
state = DONE;
currentToken =ENDFILE;
}
else if(c == '*' && getNextChar() == '/'){
state = START;
}
break;
//单行注释
caseINSINGLELINECOMMENT:
save = FALSE;
if(c == EOF){
state = DONE;
currentToken = ENDFILE;
}else if(c == '\n'){
state = START;
}
break;
2、 浮点数
在这里我把词法分析和语法分析结合在一起吧,因为你词法分析修改了当然也要修改相应的语法分析,所以我结合在一起:
由于浮点数的整数部分识别过程和NUM是一致的,所以我们需要在case NUM中识别浮点数。标志便是.的出现,所以我们为DFA增加了INNUMFLOAT状态表示进入浮点数状态,相应地我们需要增加一个tokenType,我们命名为FLOAT,这样我们把词法分析修改为:
caseINNUM:
if (!isdigit(c))
{ /* backup in the input */
if(c == '.'){
state =INNUMFLOAT;//进入浮点数状态
}else{
ungetNextChar();
save = FALSE;
state = DONE;
currentToken = NUM;
}
}
break;
//增加的浮点数类型
caseINNUMFLOAT:
if(!(isdigit(c))){
ungetNextChar();
save = FALSE;
state = DONE;
currentToken =FLOAT;
}
break;
这样我们相当于增加了一个数据类型,由于浮点数和整数一样都要参加运算,所以我们相应地要在语法分析那边添加这样的运算,实际上就是修改factor,看代码:
//为增加的浮点类型进行语法关联
case FLOAT:
t = newExpNode(ConstK);
if ((t!=NULL) && (token==FLOAT))
t->attr.val = atof(tokenString);
match(FLOAT);
break;
这里atof函数的作用就是把字符串转换为浮点数,和atoi是相对应的。经过编译,没有报错,通过。注意,如果你设置的TraceScan为TRUE,那么便会打印每一次getToken的结果,由于我们增加了FLOAT,所以我们需要修改printToken这个函数,它在util.c中,修改如下:
caseFLOAT:
fprintf(listing,"FLOAT, val=%s\n",tokenString);
break;
以下内容中如果修改了tokenType,那么我们也需要相应地按照上述方法去修改printToken函数,我们将不再声明。另外还需要注意的是,语法树结点中的attr属性中的val的类型需要做适当改变,因为这个时候一个constK的结点的val可能是int或者float,你可以选择增加属性valfloat,也可以直接进行向上转型,我觉得由于下面的ExpType可以用来说明是int还是float,所以我直接将其向上转型为double。
最后由于按老师的要求还要能识别出.16格式的浮点数,因而我又在START状态下加了一条语句:
else if(c =='.'){//增加的无整数部分的float
state = INNUMFLOAT;
}
这样便既可以识别出12.格式有可以识别出.12格式的浮点数了。
3、 数组
数组其实可以看做是一个exp,因为它实际发生的动作就是一个加法运算,数组的维数可以是一个const NUM,也可以是一个id,甚至是一个exp。所以数组的关键是如何构造抽象语法树。和其他同学讨论了一下,一致意见是将维数或者偏移量new一个结点作为数组id所在结点的子结点。而数组的维数或者偏移的识别,则是根据[和]进行的,于是我们在tokenType中增加LARRAY和RARRAY,分别表示[和],然后我们需要修改语法分析的代码。由于数组可能出现在赋值语句和read语句中(下面我们还会说到声明语句),所以我们将代码做如下修改:
case'['://增加的数组的识别
currentToken = LARRAY;
break;
case ']':
currentToken = RARRAY;
break;
然后修改赋值语句,对于赋值语句如果是为数组元素赋值那么我们是将偏移量作为该语句结点的第二个孩子的,这是因为该语句结点的第一个孩子是赋值号右边的赋值表达式结点:
TreeNode * assign_stmt(void)
{TreeNode * t = newStmtNode(AssignK);
if ((t!=NULL) && (token==ID))
t->attr.name = copyString(tokenString);
match(ID);
//如果是数组便为该节点加上一个子节点指明偏移
if(token == LARRAY){
match(LARRAY);
t->child[1] = exp();
match(RARRAY);
}
match(ASSIGN);
if (t!=NULL) t->child[0] = exp();
return t;
}
修改read语句,将维数new一个结点作为read语句节点的第一个孩子:
TreeNode* read_stmt(void)
{TreeNode * t = newStmtNode(ReadK);
match(READ);
if ((t!=NULL) && (token==ID))
t->attr.name = copyString(tokenString);
match(ID);
//如果是数组便为该节点加上一个子节点指明偏移
if(token == LARRAY){
match(LARRAY);
t->child[0] = exp();
match(RARRAY);
}
return t;
}
修改factor语句,该语句使得参加运算的数组标识符得到识别,也包括write语句,由于数组是紧跟着id被识别的,所以我们只需修改case ID就ok了:
case ID :
t = newExpNode(IdK);
if ((t!=NULL) && (token==ID))
t->attr.name =copyString(tokenString);
match(ID);
//如果是数组便为该节点加上一个子节点指明偏移
if(token == LARRAY){
match(LARRAY);
t->child[0] = exp();
match(RARRAY);
}
break;
最后注意修改printToken函数。测试通过
4、 声明语句
声明语句可能稍微复杂一点,难点还是看你如何去构造抽象语法树。由于声明语句可以是一个类型标识符后跟着一大堆用逗号隔开的变量标识符,而且这些变量还可以在声明语句中通过赋值语句进行初始化,因而我的做法便是新建一种DecK类型的语法树结点,而且每一个变量标识符都为其分配一个结点,而且同一个声明语句中的标识符结点是兄弟结点的关系顺次连接下来,如果标识符是数组,那么其第一个孩子存储其维数。第二个孩子存储初始化赋值表达式。先从词法分析开始:
识别声明语句的开始便是int和float,我们将这两个ID定义为关键字DEC表明声明语句的开始。相应地,我们需要在StmtKind中增加DecK,以表明该语句结点是声明语句。这样我们需要在语法分析中修改statement文法的函数:
case DEC:t = dec_stmt();break;//遇到关键字便是声明语句
添加上面那句话便可以了。同时我们定义声明文法的函数:
//声明语句
TreeNode* dec_stmt(void){
TreeNode * t = newStmtNode(DecK);
TreeNode* temP;//局部指针用来指向兄弟结点的末节点
TreeNode* temN;
//保存类型
ExpType temType;
if(!strcmp(tokenString,"int")){
temType = Integer;
}else{
temType = Float;
}
match(DEC);
t->type = temType;
t->attr.name = tokenString;
match(ID);
//数组
if(token == LARRAY){
match(LARRAY);
t->child[0] = exp();
match(RARRAY);
}
//初始化
if(token == ASSIGN){
match(ASSIGN);
t->child[1] = exp();
}
temP = t;
while(token == COMMA){
temP->sibling = newStmtNode(DecK);
temN = temP->sibling;
match(COMMA);
temN->type = temType;
temN->attr.name = tokenString;
match(ID);
//数组
if(token == LARRAY){
match(LARRAY);
temN->child[0] = exp();
match(RARRAY);
}
//初始化
if(token == ASSIGN){
match(ASSIGN);
temN->child[1] = exp();
}
temP = temN;//向右前进
}
return t;
}
思想如上面所述。编译老师给的四个测试用例均可通过,表明没有问题。