可能有掘友会疑惑,为什么我这个系列的第一篇内容是语义分析而不是词法分析和语法分析呢?其实是因为我是负责这部分的,所以先写着。但实际上,在本文提到的代码里,也有涉及到词法分析和语法分析的内容。
前言
相信大家对于语义分析和中间代码生成的基础理论知识也都已经非常熟悉了。但实际上,在编写代码层面,这部分只是一个“硬模拟”的过程,比的就是是否足够细心,能否把问题考虑全面。
预期目标
先来通过一组测试数据看看我们要达到什么效果:
输入:
const a = 10;
var b, c;
//单行注释
/*
* 多行注释
*/
procedure p;
if a <= 10 then
begin
c := b + a;
end;
begin
read(b);
while b # 0 do
begin
call p;
write(2 * c);
read(b);
end;
end.
输出:
语义正确
中间代码:
(1)(syss,_,_,_)
(2)(const,a,_,_)
(3)(=,10,_,a)
(4)(var,b,_,_)
(5)(var,c,_,_)
(6)(procedure,p,_,_)
(7)(j<=,a,10,$8)
(8)(+,b,a,c)
(9)(ret,_,_,_)
(10)(read,b,_,_)
(11)(j#,b,0,$13)
(12)(j=,b,0,$17)
(13)(call,p,_,_)
(14)(*,2,c,T1)
(15)(write,T1,_,_)
(16)(read,b,_,_)
(17)(syse,_,_,_)
符号表:
const a 10
var b 0
var c 0
procedure p
如果语义正确,会输出如上所示的一个四元组的中间代码格式以及符号表。
如果语义错误,会输出类似如下格式:
(语义错误,行号:19)
代码思路
在接下来的内容中,我将以编码者(分析如何一步步写出最终代码)而不是讲解者(讲解代码总体或局部的功能)的视角来描述代码是如何实现的。
首先,我们先定义相应的内容,比如输出的四元组、关键信息(保留字、标识符、运算符等)及其所在的行号等。
int count_lines=1; //初始行数为1
string keywords[N]= {"begin", "end", "if", "then", "else", "while", "write", "read", "do", "call", "const", "var", "procedure"};
int Lex_index=0; //存放词法分析结果数组的下标
struct Lex_Symbol {
int count_lines_num; //行号
string symbol_record; //保留字,标识符,运算符等
};
struct Lex_Symbol symbols[200]; //结构体数组
int Gra_index=0;//当前下标
struct four { //四元式
string type;
string op1;
string op2;
string result;
};
int tab=0; //符号表的当前位置
struct four F[LEN];
int F_i=0; //四元式数组的当前位置
- 接下来是函数声明部分,由于现在还没有想到确切的函数,所以这一部分暂时空出来。等后续有要补充的函数再添加到声明中。
- 接下来是主函数。
对主函数的定位是:程序的入口,读取输入,完成初始化的任务,能最终打印输出,但是过程部分不要过多在此处出现,以免造成冗余。
for(int i=0; i<LEN; i++) { //初始化
F[i].type="_";
F[i].op1="_";
F[i].op2="_";
F[i].result="_";
T[i].size="0";
}
F[F_i].type="syss";
F_i++;
Scanner();
program();
因此,我们可以对语法分析和语义分析设置一个标志位,并假定在前面的Scanner()和program()中,已经能判断出词法和语法是否正确,并且只有正确了才能来到后续步骤。 而这也是合理的,因为在语义分析之前,程序必须已经通过了词法分析和语法分析。
//补充到最前面的定义部分
int grammer=1;
int semantic=1;
......
//main函数内
if(grammar) {
if(semantic) {
cout<<"语义正确"<<endl;
F[F_i].type="syse";
cout<<"中间代码:"<<endl;
for(int k=0; k<=F_i; k++) {
if(F[k].type=="j<=") {
F[k].result="$"+Int_to_String(if_true);
} else if(F[k].type=="j#") {
F[k].result="$"+Int_to_String(while_true);
} else if(F[k].type=="j=") {
F[k].result="$"+Int_to_String(while_false);
}
cout<<"("<<k+1<<")("<<F[k].type<<","<<F[k].op1<<","<<F[k].op2<<","<<F[k].result<<")"<<endl;
}
cout<<"符号表:"<<endl;
for(int i=0; i<tab; i++) {
if(T[i].kind=="procedure") {
cout<<""<<T[i].kind<<" "<<T[i].name<<"";
} else cout<<""<<T[i].kind<<" "<<T[i].name<<" "<<T[i].size<<""<<endl;
}
}
}
return 0;
这样一来,我们就搭建起了代码的整体框架,并且后续的目标也很明确:Scanner负责扫描字符串,并完成词法分析;program()负责完成语法分析。而这两部分都可以借用队友的代码并且稍作修改(没有偷懒的意思,我原本以为语义分析和中间代码生成最复杂):
Scanner
void Scanner() {
string input_str;
char input_char;
while((input_char=getchar())!=EOF) {
input_str = input_str+input_char;
}
int length=input_str.length();
for(int i=0; i<length; i++) {
if(input_str[i]==' '||input_str[i]==' ')
continue;
else if(input_str[i]=='\n') {
count_lines++;
continue;
} else if(isalpha(input_str[i])) {
string lett;
while(isalpha(input_str[i])||isdigit(input_str[i])) {
lett +=input_str[i];
i++;
}
i--;
Letter(lett);
} else if(isdigit(input_str[i])) {
string digit;
int flag=0; //是否是数字开头的数字字母组合
while(isdigit(input_str[i])||isalpha(input_str[i])) {
digit+=input_str[i];
if(isalpha(input_str[i])) {
flag=1;
}
i++; //处理“2a”之类的词法错误
}
i--;
if(!flag) {
if((digit.length())>8)
cout<<"(无符号整数越界,"<<digit<<",行号:"<<count_lines<<")"<<endl;
else {
symbols[Lex_index].symbol_record=digit;
symbols[Lex_index].count_lines_num=count_lines;
Lex_index++;
}
}
else{ //违规输入的判断
cout<<"(非法字符(串),"<<digit<<",行号:"<<count_lines<<")"<<endl;
}
}
else {
switch(input_str[i]) {
case '+':
symbols[Lex_index].symbol_record="+";
symbols[Lex_index].count_lines_num=count_lines;
Lex_index++;
break;
case '-':
symbols[Lex_index].symbol_record="-";
symbols[Lex_index].count_lines_num=count_lines;
Lex_index++;
break;
case '*':
symbols[Lex_index].symbol_record="*";
symbols[Lex_index].count_lines_num=count_lines;
Lex_index++;
break;
case '#':
symbols[Lex_index].symbol_record="#";
symbols[Lex_index].count_lines_num=count_lines;
Lex_index++;
break;
case '/': {
i++;
if(input_str[i]=='/') { // 单行注释
while(1) {
i++;
if(input_str[i]=='\n') {
count_lines++;
break;
}
}
}else if (input_str[i]=='*') { //多行注释
while(1) {
if(input_str[i]=='\n') {
count_lines++;
}
i++;
if(input_str[i]=='*') {
if(input_str[i]=='\n') {
count_lines++;
}
i++;
if(input_str[i]=='/')
break;
}
}
}else { //除法运算
symbols[Lex_index].symbol_record="/";
symbols[Lex_index].count_lines_num=count_lines;
Lex_index++;
}
break;
}
case '=':
symbols[Lex_index].symbol_record="=";
symbols[Lex_index].count_lines_num=count_lines;
Lex_index++;
break;
case '<':
i++;
if(input_str[i]=='>') {
symbols[Lex_index].symbol_record="<>";
symbols[Lex_index].count_lines_num=count_lines;
Lex_index++;
} else if(input_str[i]=='=') {
symbols[Lex_index].symbol_record="<=";
symbols[Lex_index].count_lines_num=count_lines;
Lex_index++;
} else {
i--;
symbols[Lex_index].symbol_record="<";
symbols[Lex_index].count_lines_num=count_lines;
Lex_index++;
}
break;
case '>':
i++;
if(input_str[i]=='=') {
symbols[Lex_index].symbol_record=">=";
symbols[Lex_index].count_lines_num=count_lines;
Lex_index++;
} else {
i--;
symbols[Lex_index].symbol_record=">";
symbols[Lex_index].count_lines_num=count_lines;
Lex_index++;
}
break;
case ':':
i++;
if(input_str[i]=='=') {
symbols[Lex_index].symbol_record=":=";
symbols[Lex_index].count_lines_num=count_lines;
Lex_index++;
}
break;
case '(':
symbols[Lex_index].symbol_record="(";
symbols[Lex_index].count_lines_num=count_lines;
Lex_index++;
break;
case ')':
symbols[Lex_index].symbol_record=")";
symbols[Lex_index].count_lines_num=count_lines;
Lex_index++;
break;
case ',':
symbols[Lex_index].symbol_record=",";
symbols[Lex_index].count_lines_num=count_lines;
Lex_index++;
break;
case ';':
symbols[Lex_index].symbol_record=";";
symbols[Lex_index].count_lines_num=count_lines;
Lex_index++;
break;
case '.':
symbols[Lex_index].symbol_record=".";
symbols[Lex_index].count_lines_num=count_lines;
Lex_index++;
break;
default:
cout<<"(词法错误,行号:"<<count_lines<<")"<<endl;
break;
}
}
}
}
此处Letter()的作用是识别保留字和标识符,并写入词法分析表中:
void Letter(string Lex_str) {
if(Lex_str=="begin"||Lex_str=="call"||Lex_str=="const"||Lex_str=="do"||Lex_str=="end"||Lex_str=="if"||Lex_str=="procedure"||Lex_str=="read"||Lex_str=="then"||Lex_str=="while"||Lex_str=="var"||Lex_str=="write") {
symbols[Lex_index].symbol_record=Lex_str;
symbols[Lex_index].count_lines_num=count_lines;
Lex_index++;
} else if(Lex_str.length()>8) {
cout<<"(词法错误),行号:"<<count_lines<<")"<<endl;
} else {
symbols[Lex_index].symbol_record=Lex_str;
symbols[Lex_index].count_lines_num=count_lines;
Lex_index++;
}
}
Program(): 识别<分程序>这个整体,且跳过点号
//程序--><分程序>.
void program() {
subProgram();
if(symbols[Gra_index].symbol_record==".") {
Gra_index++;
}
return;
}
subProgram():识别<分程序>的具体内容。从这里开始,细分出了很多方法以针对不同的数据类型进行处理。
//分程序-->[<常量说明部分>][<变量说明部分>][<过程说明部分>]<语句>
void subProgram() {
if(symbols[Gra_index].symbol_record=="const") {
conExplain();
}
if (symbols[Gra_index].symbol_record == "var") {
varExplain();
}
if (symbols[Gra_index].symbol_record== "procedure") {
processExplain();
}
statement();
}
其中:conExplain();varExplain();processExplain();的定义分别如下:
void conExplain() { //常量说明部分-->const<常量定义>{,<常量定义>};
if (symbols[Gra_index].symbol_record == "const") {
T[tab].kind="const";
F[F_i].type="const";
Gra_index++;
constDef();
while(symbols[Gra_index].symbol_record == ",") {
tab++;
T[tab].kind="const";
Gra_index++;
constDef();
}
if (symbols[Gra_index].symbol_record == ";") {
Gra_index++;
return;
}
} else {
cout<<"(语法错误,行号:"<<symbols[Gra_index].count_lines_num<<")"<<endl;
grammar=0;
}
}
void varExplain() { //变量说明部分 -->var<标识符>{,<标识符>};
int hasDefined=0;//判断变量是否已被定义
int hasDefined2=0;
if (symbols[Gra_index].symbol_record == "var") {
T[tab].kind="var";
F[F_i].type="var";
Gra_index++;
Gra_index++;
for(int i=0; i<tab; i++) {
if(T[i].kind=="const"||T[i].kind=="var") {
if(symbols[Gra_index-1].symbol_record==T[i].name) {
hasDefined=1;
break;
}
}
}
if(hasDefined) {
cout<<"(语义错误,行号:"<<symbols[Gra_index-1].count_lines_num<<")"<<endl;
semantic=0;
}
T[tab].name=symbols[Gra_index-1].symbol_record;
tab++;
F[F_i].op1=symbols[Gra_index-1].symbol_record;
F_i++;
while(symbols[Gra_index].symbol_record == ",") {
T[tab].kind="var";
F[F_i].type="var";
Gra_index++;
Gra_index++;
for(int i=0; i<tab; i++) { //判断该变量在之前是否被定义过
if(T[i].kind=="const"||T[i].kind=="var") {
if(symbols[Gra_index-1].symbol_record==T[i].name) {
hasDefined2=1;
break;
}
}
}
if(hasDefined2) {
cout<<"(语义错误,行号:"<<symbols[Gra_index-1].count_lines_num<<")"<<endl;
semantic=0;
}
T[tab].name=symbols[Gra_index-1].symbol_record;
tab++;
F[F_i].op1=symbols[Gra_index-1].symbol_record;
F_i++;
}
if(symbols[Gra_index].symbol_record== ";") {
Gra_index++;
return;
} else {
cout<<"(语法错误,行号:"<<symbols[Gra_index].count_lines_num<<")"<<endl;
grammar=0;
Gra_index=Gra_index+2;
}
} else {
cout<<"(语法错误,行号:"<<symbols[Gra_index].count_lines_num<<")"<<endl;
grammar=0;
}
}
void processExplain() { //过程说明部分--><过程首部><分程序>{;<过程说明部分>};
processHead();
subProgram();
if (symbols[Gra_index].symbol_record == ";") {
Gra_index++;
while(symbols[Gra_index].symbol_record == "procedure") {
processExplain();
}
if (symbols[Gra_index].symbol_record == ";") {
Gra_index++;
return;
}
} else {
cout<<"(语法错误,行号:"<<symbols[Gra_index].count_lines_num<<")"<<endl;
grammar=0;
}
}
在前述subProgram()代码的最后,还调用了statement()函数,这个函数是判断语句关键词:
void statement() { //语句--><赋值语句>|<条件语句>|<当型循环语句>|<过程调用语句>|<读语句>|<写语句>|<复合语句>|<空语句>
if(isAssignmentStatement(symbols[Gra_index].symbol_record))
AssignmentStatement();
if (symbols[Gra_index].symbol_record== "if")
ConditionStatement();
else if (symbols[Gra_index].symbol_record == "while")
dowhile();
else if (symbols[Gra_index].symbol_record == "call")
processCall();
else if (symbols[Gra_index].symbol_record == "read")
readStatement();
else if (symbols[Gra_index].symbol_record == "write")
writeStatement();
else if (symbols[Gra_index].symbol_record == "begin")
CompoundStatement();
}
并作相应处理。这里以赋值语句为例:通过读取符号表的分析结果向四元式写入对应的值。
void AssignmentStatement() { //赋值语句--><标识符>:=<表达式>
Gra_index++;
if(symbols[Gra_index].symbol_record==":=") {
F[F_i].result=symbols[Gra_index-1].symbol_record;
Gra_index++;
expression();
if(symbols[Gra_index].symbol_record==";") {
Gra_index++;
} else {
cout<<"(语法错误,行号:"<<symbols[Gra_index-1].count_lines_num<<")"<<endl;
grammar=0;
}
} else {
cout<<"(语法错误,行号:"<<symbols[Gra_index].count_lines_num<<")"<<endl;
grammar=0;
Gra_index=Gra_index+4; //跳转到下一个四元式
}
}
而代码中的expression()则是更进一步地将内容拆分和细化。比如表达式可以理解为是[+|-]<项>后接0个或多个<加减运算><项>
void expression() {
if (symbols[Gra_index].symbol_record == "+" || symbols[Gra_index].symbol_record == "-") {
Gra_index++;
}
item();
while(symbols[Gra_index].symbol_record == "+" || symbols[Gra_index].symbol_record == "-") {
F[F_i].type=symbols[Gra_index].symbol_record;
F[F_i].op1=symbols[Gra_index-1].symbol_record;
AddAndSubtract();
item();
F[F_i].op2=symbols[Gra_index-1].symbol_record;
F_i++;
}
}
而又可以把对项的读取操作和加减操作单独封装为函数:
这就涉及到了项的定义:<因子>后接0个或多个<乘除运算符><因子>
void item() {
int hasDefined=0;//判断变量否被定义过,初始默认未定义
int hasDefined2=0;
factor();
for(int i=0; i<tab; i++) {
if (symbols[Gra_index-1].symbol_record.at(0) >='0' && symbols[Gra_index-1].symbol_record.at(0) <= '9') { //如果是常数,按已被定义处理
hasDefined=1;
break;
}
if(T[i].kind=="const"||T[i].kind=="var") {
if(symbols[Gra_index-1].symbol_record==T[i].name) {
hasDefined=1;
break;
}
}
}
if(!hasDefined) {
cout<<"(语义错误,行号:"<<symbols[Gra_index-1].count_lines_num<<")"<<endl;
semantic=0;
}
while(symbols[Gra_index].symbol_record=="*"|| symbols[Gra_index].symbol_record == "/") {
F[F_i].op1=symbols[Gra_index-1].symbol_record;
MULAndDIV();
F[F_i].type=symbols[Gra_index-1].symbol_record;
factor();
for(int i=0; i<tab; i++) {
if ((symbols[Gra_index-1].symbol_record.at(0) <= '9' && symbols[Gra_index-1].symbol_record.at(0) >= '0')) {
hasDefined2=1;
break;
}
if(T[i].kind=="const"||T[i].kind=="var") {
if(symbols[Gra_index-1].symbol_record==T[i].name) {
hasDefined2=1;
break;
}
}
}
if(!hasDefined2) {
cout<<"(语义错误,行号:"<<symbols[Gra_index-1].count_lines_num<<")"<<endl;
semantic=0;
}
F[F_i].op2=symbols[Gra_index-1].symbol_record;
F[F_i].result="T1";
F_i++;
}
}
而因子又可以分为标识符和无符号整数:
void factor() {
if (symbols[Gra_index].symbol_record.at(0) >= 97&&symbols[Gra_index].symbol_record.at(0) <= 122) {
Gra_index++;
} else if (symbols[Gra_index].symbol_record.at(0) >= '0'&&symbols[Gra_index].symbol_record.at(0) <= '9' ) {
unsInt();
} else if (symbols[Gra_index].symbol_record == "(") {
expression();
if (symbols[Gra_index].symbol_record == ")") {
Gra_index++;
return;
} else {
cout<<"(语法错误,行号:"<<symbols[Gra_index].count_lines_num<<")"<<endl;
grammar=0;
}
} else {
cout<<"(语法错误,行号:"<<symbols[Gra_index].count_lines_num<<")"<<endl;
grammar=0;
}
}
识别加减乘除一类的操作符的过程是很简单的,如果是的话,直接跳过就可以了:
void AddAndSubtract() {\
if (symbols[Gra_index].symbol_record == "+" || symbols[Gra_index].symbol_record == "-") {
Gra_index++;
return;
} else {
cout<<"(语法错误,行号:"<<symbols[Gra_index].count_lines_num<<")"<<endl;
grammar=0;
}
}
void MULAndDIV() {
if (symbols[Gra_index].symbol_record== "*" ||symbols[Gra_index].symbol_record== "/") {
Gra_index++;
return;
} else {
cout<<"(语法错误,行号:"<<symbols[Gra_index].count_lines_num<<")"<<endl;
grammar=0;
}
}
为什么加减和乘除要分开写呢?这是因为运算是有优先级的。乘除运算符连接的内容往往被认为是一个项,而再对项再进行加减操作构成表达式。 这里是对于前面提到的表达式和项定义的口语化表达。
其他的语句同理,这里不再赘述。
以上就是代码思路,也感谢你能看到这里!