编译原理是大学的主要课程之一。可是我们在学习时不一定很好的了解。主要的问题可能是课程抽象和没有太多的基础知识和程序结构的了解等。其实通过我前面的CPU设计就可是知道汇编语言是怎么在计算机上运行的了。下面就来介绍一下我们写的高级语言怎么转换为低级语言的。这样我们就可以比较完整的了解程序的运行原理。
编译原理一般分为词法分析,语法分析,语义分析和代码优化及目标代码生成等一系列过程。下面我们结合来了解一下:本文的程序主要使用了孙悦红的编译原理及实行书中的代码。并且她的代码我发邮件要了一份。本来我打了不少可是实在太多。最后的模拟机等部分我就懒得打了。就按照书上的说明要了一份。在此感谢孙老师。同时我修改了部分代码。为了更好的学习我发改了的部分给大家。
本程序可以同时在BC和Visual C++下运行。我测试过了的。下面先将代码贴给大家。其中如果你只想先了解词法分析部分,你可以将其余的注释就可以。我建议大家学习时一步一步来,从词法分析学,然后学语法分析。其他的就类似了。
如果你在DOS下运行,由于使用了汉字,请将其自行换成英文方便您的识别。
程序中的解释就是编译的基本原理。如果还不清楚建议大家看孙悦红的编译原理及实现清华的。
#include
#include
#include
#include "stdio.h"
//下面定义保留,为简化程序,使用字符指针数组保存所有保留字。
//如果想增加保留字,可继续添加,并修改保留字数目
#define keywordSum 8
#define maxvartablep 500//定义符号表的容量
char *keyword[keywordSum]={ "if","else","for","while","do","int","read","write"};
//下面定义纯单分界符,如需要可添加
char singleword[50]="+-*(){};,:";
//下面定义双分界符的首字符
char doubleword[10]="><=!";
//函数调用
int TESTparse();
int program();
int compound_stat();
int statement();
int expression_stat();
int expression();
int bool_expr();
int additive_expr();
int term();
int factor();
int if_stat();
int while_stat();
int for_stat();
int write_stat();
int read_stat();
int declaration_stat();
int declaration_list();
int statement_list();
int compound_stat();
int name_def(char *name);
char token[20],token1[40];//token保存单词符号,token1保存单词值
char Scanout[300],Codeout[300]; //保存词法分析输出文件名
FILE *fp,*fout; //用于指向输入输出文件的指针
struct{//定义符号表结构
char name[8];
int address;
}vartable[maxvartablep];//改符号表最多容纳maxvartablep个记录
int vartablep=0,labelp=0,datap=0;
int TESTscan();
char Scanin[300],Errorfile[300]; //用于接收输入输出以及错误文件名
FILE *fin; //用于指向输入输出文件的指针
int TESTscan()//词法分析函数
{
char ch,token[40]; //ch为每次读入的字符,token用于保存识别出的单词
int es=0,j,n; //es错误代码,0表示没有错误。j,n为临时变量,控制组合单词时的下标等
printf("请输入源程序文件名(包括路径):");
scanf("%s",Scanin);
printf("请输入词法分析输出文件名(包括路径):");
scanf("%s",Scanout);
if ((fin=fopen(Scanin,"r"))==NULL) //判断输入文件名是否正确
{
printf("/n打开词法分析输入文件出错!/n");
return(1);//输入文件出错返回错误代码1
}
if ((fout=fopen(Scanout,"w"))==NULL) //判断输出文件名是否正确
{
printf("/n创建词法分析输出文件出错!/n");
return(2); //输出文件出错返回错误代码2
}
ch=getc(fin);
while(ch!=EOF)
{
while (ch==' '||ch=='/n'||ch=='/t') ch=getc(fin);
if (ch==EOF) break;
if (isalpha(ch)) //如果是字母,则进行标识符处理
{
token[0]=ch; j=1;
ch=getc(fin);
while(isalnum(ch)) //如果是字母数字则组合标识符;如果不是则标识符组合结束
{
token[j++]=ch; //组合的标识符保存在token中
ch=getc(fin); //读下一个字符
}
token[j]='/0'; //标识符组合结束
//查保留字
n=0;
while ((n
fprintf(fout,"%s/t%s/n","ID",token); //输出标识符符号
else//是保留字,输出保留字
fprintf(fout,"%s/t%s/n",token,token); //输出保留字符号
} else if (isdigit(ch))//数字处理
{
token[0]=ch; j=1;
ch=getc(fin); //读下一个字符
while (isdigit(ch)) //如果是数字则组合整数;如果不是则整数组合结束
{
token[j++]=ch; //组合整数保存在token中
ch=getc(fin); //读下一个字符
}
token[j]='/0'; //整数组合结束
fprintf(fout,"%s/t%s/n","NUM",token); //输出整数符号
} else if (strchr(singleword,ch)>0) //单分符处理
{
token[0]=ch; token[1]='/0';
ch=getc(fin);//读下一个符号以便识别下一个单词
fprintf(fout,"%s/t%s/n",token,token); //输出单分界符符号
}else if (strchr(doubleword,ch)>0) //双分界符处理
{
token[0]=ch;
ch=getc(fin); //读下一个字符判断是否为双分界符
if (ch=='=') //如果是=,组合双分界符
{
token[1]=ch;token[2]='/0'; //组合双分界符结束
ch=getc(fin); //读下一个符号以便识别下一个单词
} else//不是=则为单分界符
token[1]='/0';
fprintf(fout,"%s/t%s/n",token,token); //输出单或双分界符符号
} else if (ch=='/') //注释处理
{
ch=getc(fin); //读下一个字符
if (ch=='*') //如果是*,则开始处理注释
{ char ch1;
ch1=getc(fin); //读下一个字符
do
{ ch=ch1;ch1=getc(fin);} //删除注释
while ((ch!='*' || ch1!='/')&&ch1!=EOF); //直到遇到注释结束符*/或文件尾
ch=getc(fin);//读下一个符号以便识别下一个单词
} else //不是*则处理单分界符/
{
token[0]='/'; token[1]='/0';
fprintf(fout,"%s/t%s/n",token,token); //输出单分界符/
}
} else//错误处理
{
token[0]=ch;token[1]='/0';
ch=getc(fin); //读下一个符号以便识别下一个单词
es=3; //设置错误代码
fprintf(fout,"%s/t%s/n","ERROR",token); //输出错误符号
}
}
fclose(fin);//关闭输入输出文件
fclose(fout);
return(es); //返回主程序
}
//语法、语义分析及代码生成
//插入符号表动作@name-def↓n, t的程序如下:
int name_def(char *name)
{
int i,es=0;
if (vartablep>=maxvartablep) return(21);
for(i=vartablep-1;i==0;i--)//查符号表
{
if (strcmp(vartable[i].name,name)==0)
{
es=22;//22表示变量重复声明
break;
}
}
if (es>0) return(es);
strcpy(vartable[vartablep].name,name);
vartable[vartablep].address=datap;
datap++;//分配一个单元,数据区指针加1
vartablep++;
return(es);
}
//查询符号表返回地址
int lookup(char *name,int *paddress)
{
int i,es=0;
for(i=0;i
if (strcmp(vartable[i].name,name)==0)
{
*paddress=vartable[i].address;
return(es);
}
}
es=23;//变量没有声明
return(es);
}
//语法、语义分析及代码生成程序
int TESTparse()
{
int es=0;
if((fp=fopen(Scanout,"r"))==NULL)
{
printf("/n打开%s错误!/n",Scanout);
es=10;
return(es);
}
printf("请输入目标文件名(包括路径):");
scanf("%s",Codeout);
if((fout=fopen(Codeout,"w"))==NULL)
{
printf("/n创建%s错误!/n",Codeout);
es=10;
return(es);
}
if (es==0) es=program();
printf("==语法、语义分析及代码生成程序结果==/n");
switch(es)
{
case 0: printf("语法、语义分析成功并抽象机汇编生成代码!/n");break;
case 10: printf("打开文件 %s失败!/n",Scanout);break;
case 1: printf("缺少{!/n");break;
case 2: printf("缺少}!/n");break;
case 3: printf("缺少标识符!/n");break;
case 4: printf("少分号!/n");break;
case 5: printf("缺少(!/n");break;
case 6: printf("缺少)!/n");break;
case 7: printf("缺少操作数!/n");break;
case 21: printf("符号表溢出!/n");break;
case 22: printf("变量重复定义!/n");break;
case 23: printf("变量未声明!/n");break;
}
fclose(fp);
fclose(fout);
return(es);
}
//program::={
int program()
{
int es=0,i;
fscanf(fp,"%s %s/n",token,token1);
printf("%s %s/n",token,token1);
if(strcmp(token,"{"))//判断是否'{'
{
es=1;
return(es);
}
fscanf(fp,"%s %s/n",&token,&token1);
printf("%s %s/n",token,token1);
es=declaration_list();
if (es>0) return(es);
printf(" 符号表/n");
printf(" 名字 地址/n");
for(i=0;i
es=statement_list();
if (es>0) return(es);
if(strcmp(token,"}"))//判断是否'}'
{
es=2;
return(es);
}
fprintf(fout," STOP/n");//产生停止指令
return(es);
}
//
//
//改成
int declaration_list()
{
int es=0;
while (strcmp(token,"int")==0)
{
es=declaration_stat();
if (es>0) return(es);
}
return(es);
}
//
int declaration_stat()
{
int es=0;
fscanf(fp,"%s %s/n",&token,&token1);printf("%s %s/n",token,token1);
if (strcmp(token,"ID")) return(es=3); //不是标识符
es=name_def(token1);//插入符号表
if (es>0) return(es);
fscanf(fp,"%s %s/n",&token,&token1);printf("%s %s/n",token,token1);
if (strcmp(token,";") ) return(es=4);
fscanf(fp,"%s %s/n",&token,&token1);printf("%s %s/n",token,token1);
return(es);
}
//
//改成
int statement_list()
{
int es=0;
while (strcmp(token,"}"))
{
es=statement();
if (es>0) return(es);
}
return(es);
}
//
// |
int statement()
{
int es=0;
if (es==0 && strcmp(token,"if")==0) es=if_stat();//
if (es==0 && strcmp(token,"while")==0) es=while_stat();//
if (es==0 && strcmp(token,"for")==0) es=for_stat();//
//可在此处添加do语句调用
if (es==0 && strcmp(token,"read")==0) es=read_stat();//
if (es==0 && strcmp(token,"write")==0) es=write_stat();//
if (es==0 && strcmp(token,"{")==0) es=compound_stat();//<复合语句>
if (es==0 && (strcmp(token,"ID")==0||strcmp(token,"NUM")==0||strcmp(token,"(")==0)) es=expression_stat();//<表达式语句>
return(es);
}
//
/*
if (
[ else < statement >] @SETlabel↓label2
其中动作符号的含义如下
@BRF↑label1 :输出 BRF label1,
@BR↑label2:输出 BR label2,
@SETlabel↓label1:设置标号label1
@SETlabel↓label2:设置标号label2
*/
int if_stat(){
int es=0,label1,label2; //if
fscanf(fp,"%s %s/n",&token,&token1);
printf("%s %s/n",token,token1);
if (strcmp(token,"(")) return(es=5); //少左括号
fscanf(fp,"%s %s/n",&token,&token1);
printf("%s %s/n",token,token1);
es=expression();
if (es>0) return(es);
if (strcmp(token,")")) return(es=6); //少右括号
label1=labelp++;//用label1记住条件为假时要转向的标号
fprintf(fout," BRF LABEL%d/n",label1);//输出假转移指令
fscanf(fp,"%s %s/n",&token,&token1);
printf("%s %s/n",token,token1);
es=statement();
if (es>0) return(es);
label2=labelp++;//用label2记住要转向的标号
fprintf(fout," BR LABEL%d/n",label2);//输出无条件转移指令
fprintf(fout,"LABEL%d:/n",label1);//设置label1记住的标号
if (strcmp(token,"else")==0)//else部分处理
{
fscanf(fp,"%s %s/n",&token,&token1);
printf("%s %s/n",token,token1);
es=statement();
if (es>0) return(es);
}
fprintf(fout,"LABEL%d:/n",label2);//设置label2记住的标号
return(es);
}
//
//
//
//动作解释如下:
//@SETlabel↑label1:设置标号label1
//@BRF↑label2 :输出 BRF label2,
//@BR↑label1:输出 BR label1,
//@SETlabel↓label2:设置标号label2
int while_stat()
{
int es=0,label1,label2;
label1=labelp++;
fprintf(fout,"LABEL%d:/n",label1);//设置label1标号
fscanf(fp,"%s %s/n",&token,&token1);
printf("%s %s/n",token,token1);
if (strcmp(token,"(")) return(es=5); //少左括号
fscanf(fp,"%s %s/n",&token,&token1);
printf("%s %s/n",token,token1);
es=expression();
if (es>0) return(es);
if (strcmp(token,")")) return(es=6); //少右括号
label2=labelp++;
fprintf(fout," BRF LABEL%d/n",label2);//输出假转移指令
fscanf(fp,"%s %s/n",&token,&token1);
printf("%s %s/n",token,token1);
es=statement();
if (es>0) return(es);
fprintf(fout," BR LABEL%d/n",label1);//输出无条件转移指令
fprintf(fout,"LABEL%d:/n",label2);//设置label2标号
return(es);
}
//
/*
@SETlabel↓label1< expression >@BRF↑label2@BR↑label3;
@SETlabel↓label4 < expression >@BR↑label1)
@SETlabel↓label3 <语句 >@BR↑label4@SETlabel↓label2
动作解释:
1. @SETlabel↓label1:设置标号label1
2. @BRF↑label2 :输出 BRF label2,
3. @BR↑label3:输出 BR label3,
4. @SETlabel↓label4:设置标号label4
5. @BR↑label1:输出 BR label1,
6. @SETlabel↓label3:设置标号label3
7. @BR↑label4:输出 BR label4,
8. @SETlabel↓label2:设置标号label2
*/
int for_stat()
{
int es=0,label1,label2,label3,label4;
fscanf(fp,"%s %s/n",&token,&token1);
printf("%s %s/n",token,token1);
if (strcmp(token,"(")) return(es=5); //少左括号
fscanf(fp,"%s %s/n",&token,&token1);
printf("%s %s/n",token,token1);
es=expression();
if (es>0) return(es);
if (strcmp(token,";")) return(es=4); //少分号
label1=labelp++;
fprintf(fout,"LABEL%d:/n",label1);//设置label1标号
fscanf(fp,"%s %s/n",&token,&token1);
printf("%s %s/n",token,token1);
es=expression();
if (es>0) return(es);
label2=labelp++;
fprintf(fout," BRF LABEL%d/n",label2);//输出假条件转移指令
label3=labelp++;
fprintf(fout," BR LABEL%d/n",label3);//输出无条件转移指令
if (strcmp(token,";")) return(es=4); //少分号
label4=labelp++;
fprintf(fout,"LABEL%d:/n",label4);//设置label4标号
fscanf(fp,"%s %s/n",&token,&token1);
printf("%s %s/n",token,token1);
es=expression();
if (es>0) return(es);
fprintf(fout," BR LABEL%d/n",label1);//输出无条件转移指令
if (strcmp(token,")")) return(es=6); //少右括号
fprintf(fout,"LABEL%d:/n",label3);//设置label3标号
fscanf(fp,"%s %s/n",&token,&token1);
printf("%s %s/n",token,token1);
es=statement();
if (es>0) return(es);
fprintf(fout," BR LABEL%d/n",label4);//输出无条件转移指令
fprintf(fout,"LABEL%d:/n",label2);//设置label2标号
return(es);
}
//
//
//动作解释:
//@ OUT:输出 OUT
int write_stat()
{
int es=0;
fscanf(fp,"%s %s/n",&token,&token1);
printf("%s %s/n",token,token1);
es=expression();
if (es>0)return(es);
if (strcmp(token,";")) return(es=4); //少分号
fprintf(fout," OUT/n");//输出指令
fscanf(fp,"%s %s/n",&token,&token1);
printf("%s %s/n",token,token1);
return(es);
}
//
//
//动作解释:
//@LOOK↓n↑d:查符号表n,给出变量地址d; 没有,变量没定义
//@IN:输出IN
//@STI↓d:输出指令代码STI d
int read_stat()
{
int es=0,address;
fscanf(fp,"%s %s/n",&token,&token1);
printf("%s %s/n",token,token1);
if (strcmp(token,"ID")) return(es=3); //少标识符
es=lookup(token1,&address);
if (es>0) return(es);
fprintf(fout," IN /n");//输入指令
fprintf(fout," STI %d/n",address);//指令
fscanf(fp,"%s %s/n",&token,&token1);
printf("%s %s/n",token,token1);
if (strcmp(token,";")) return(es=4); //少分号
fscanf(fp,"%s %s/n",&token,&token1);
printf("%s %s/n",token,token1);
return(es);
}
//
int compound_stat(){ //复合语句函数
int es=0;
fscanf(fp,"%s %s/n",&token,&token1);
printf("%s %s/n",token,token1);
es=statement_list();
return(es);
}
//
int expression_stat()
{
int es=0;
if (strcmp(token,";")==0)
{
fscanf(fp,"%s %s/n",&token,&token1);
printf("%s %s/n",token,token1);
return(es);
}
es=expression();
if (es>0) return(es);
if (strcmp(token,";")==0)
{
fscanf(fp,"%s %s/n",&token,&token1);
printf("%s %s/n",token,token1);
return(es);
} else
{
es=4;
return(es);//少分号
}
}
//
int expression()
{
int es=0,fileadd;
char token2[20],token3[40];
if (strcmp(token,"ID")==0)
{
fileadd=ftell(fp); //@ASSIGN记住当前文件位置
fscanf(fp,"%s %s/n", &token2,&token3);
printf("%s %s/n",token2,token3);
if (strcmp(token2,"=")==0) //'='
{
int address;
es=lookup(token1,&address);
if (es>0) return(es);
fscanf(fp,"%s %s/n",&token,&token1);
printf("%s %s/n",token,token1);
es=bool_expr();
if (es>0) return(es);
fprintf(fout," STO %d/n",address);
} else
{
fseek(fp,fileadd,0); //若非'='则文件指针回到'='前的标识符
printf("%s %s/n",token,token1);
es=bool_expr();
if (es>0) return(es);
}
} else es=bool_expr();
return(es);
}
//
// |< additive_expr >(>|<|>=|<=|==|!=)< additive_expr >
/*
|< additive_expr >>
|< additive_expr ><
|< additive_expr >>=
|< additive_expr ><=< additive_expr >@LE
|< additive_expr >==< additive_expr >@EQ
|< additive_expr >!=< additive_expr >@NOTEQ
*/
int bool_expr()
{
int es=0;
es=additive_expr();
if(es>0) return(es);
if ( strcmp(token,">")==0 || strcmp(token,">=")==0
||strcmp(token,"<")==0 || strcmp(token,"<=")==0
||strcmp(token,"==")==0|| strcmp(token,"!=")==0)
{
char token2[20];
strcpy(token2,token);//保存运算符
fscanf(fp,"%s %s/n",&token,&token1);
printf("%s %s/n",token,token1);
es=additive_expr();
if(es>0) return(es);
if ( strcmp(token2,">")==0 ) fprintf(fout," GT/n");
if ( strcmp(token2,">=")==0 ) fprintf(fout," GE/n");
if ( strcmp(token2,"<")==0 ) fprintf(fout," LES/n");
if ( strcmp(token2,"<=")==0 ) fprintf(fout," LE/n");
if ( strcmp(token2,"==")==0 ) fprintf(fout," EQ/n");
if ( strcmp(token2,"!=")==0 ) fprintf(fout," NOTEQ/n");
}
return(es);
}
//
//< additive_expr>::=
int additive_expr()
{
int es=0;
es=term();
if(es>0) return(es);
while (strcmp(token,"+")==0 || strcmp(token,"-")==0)
{
char token2[20];
strcpy(token2,token);
fscanf(fp,"%s %s/n",&token,&token1);
printf("%s %s/n",token,token1);
es=term();
if(es>0) return(es);
if ( strcmp(token2,"+")==0 ) fprintf(fout," ADD/n");
if ( strcmp(token2,"-")==0 ) fprintf(fout," SUB/n");
}
return(es);
}
//< term >::=
//< term >::=
int term()
{
int es=0;
es=factor();
if(es>0) return(es);
while (strcmp(token,"*")==0 || strcmp(token,"/")==0)
{
char token2[20];
strcpy(token2,token);
fscanf(fp,"%s %s/n",&token,&token1);
printf("%s %s/n",token,token1);
es=factor();
if(es>0) return(es);
if ( strcmp(token2,"*")==0 ) fprintf(fout," MULT/n");
if ( strcmp(token2,"/")==0 ) fprintf(fout," DIV/n");
}
return(es);
}
//< factor >::=(
//< factor >::=(< expression >)| ID↑n@LOOK↓n↑d@LOAD↓d |NUM↑i@LOADI↓i
int factor()
{
int es=0;
if (strcmp(token,"(")==0)
{
fscanf(fp,"%s %s/n",&token,&token1);
printf("%s %s/n",token,token1);
es=expression();
if (es>0) return(es);
if (strcmp(token,")")) return(es=6); //少右括号
fscanf(fp,"%s %s/n",&token,&token1);
printf("%s %s/n",token,token1);
} else
{
if (strcmp(token,"ID")==0)
{
int address;
es=lookup(token1,&address);//查符号表,获取变量地址
if (es>0) return(es);//变量没声明
fprintf(fout," LOAD %d/n",address);
fscanf(fp,"%s %s/n",&token,&token1);
printf("%s %s/n",token,token1);
return(es);
}
if (strcmp(token,"NUM")==0)
{
fprintf(fout," LOADI %s/n",token1);
fscanf(fp,"%s %s/n",&token,&token1);
printf("%s %s/n",token,token1);
return(es);
}else
{
es=7;//缺少操作数
return(es);
}
}
return(es);
}
//主程序
void main(){
int es=0;
es=TESTscan();//调词法分析
if (es>0) printf("词法分析有错,编译停止!");
else printf("词法分析成功!/n");
if (es==0)
{
es=TESTparse(); //调语法、语义分析并生成代码
if (es==0) printf("语法、语义分析并生成代码成功!/n");
else printf("语法、语义分析并生成代码错误!/n");
}
}
下面我们可以进行测试:如下我挑了几个典型的。大家可以看看。
下面就是一个从高级语言到低级语言的转变过程:
Lexical.txt:
{
int a;
int bb;
/*this is a test*/
a=20;
bb=10*a;
}
Grammar.txt:
{ {
int int
ID a
; ;
int int
ID bb
; ;
ID a
= =
NUM 20
; ;
ID bb
= =
NUM 10
* *
ID a
; ;
} }
Translation.txt:
在如上的分析后,我们就看到其已经转换为类似汇编语言格式的语言。结合前面的精简计算机系统,这样我们就可以明白了它是怎么在计算机中运行的了。在孙悦红的书中还有Test语言的模拟机。大家可以看看,其实程序的基本运行原理也是类似。谢谢!
这样,我们就大概明白和了解了编译的基本原理了。
从上面的翻译看来和我们前面的翻译有点区别,这就是实际的使用了。其中还有很多其他的知识比如堆等及CPU的寄存器等。另外学习。还有其他的翻译大家自己试试看。谢谢!