PL0源代码

合集下载

PL0编译程序源程序

PL0编译程序源程序

procedure constdeclaration; (*常量定义的处理*) begin if sym = ident then begin getsym; if sym in [eql,becomes] then begin if sym = becomes then error(1); getsym; if sym = number then begin enter(constant); getsym; end else error(2) end else error(3) end else error(4); end; (*constdeclaration 结束*)
until kk = k; id:= a; i:= 1; (*二分法查保留字表*) j:= norw; repeat k:= (i+j) div 2; if id <= word[k] then j:= k-1; if id >= word[k] then i:= k+1 until i>j; if i-1 > j then sym:= wsym[k] else sym:= ident; end else if ch in ['0'..'9'] then begin (*number*) k:= 0; num:= 0; sym:= number; repeat num:= 10 * num+(ord(ch)-ord('0')); k:= k+1; getch until not (ch in ['0'..'9']); if k > nmax then error(30); end else if ch = ':' then begin getch; if ch = '=' then begin sym:= becomes; getch; end else sym:= nul; end else (*<=*)

编译原理(PL0编译程序源代码)

编译原理(PL0编译程序源代码)

/*PL/0编译程序(C语言版)*编译和运行环境:*Visual C++6.0*WinXP/7*使用方法:*运行后输入PL/0源程序文件名*回答是否将虚拟机代码写入文件*回答是否将符号表写入文件*执行成功会产生四个文件(词法分析结果.txt符号表.txt虚拟代码.txt源程序和地址.txt)*/#include <stdio.h>#include"pl0.h"#include"string"#define stacksize 500//解释执行时使用的栈int main(){bool nxtlev[symnum];printf("请输入源程序文件名:");scanf("%s",fname);fin=fopen(fname,"r");//以只读方式打开pl0源程序文件cifa=fopen("词法分析结果.txt","w");fa1=fopen("源程序和地址.txt","w");//输出源文件及各行对应的首地址fprintf(fa1,"输入pl0源程序文件名:");fprintf(fa1,"%s\n",fname);if(fin){printf("是否将虚拟机代码写入文件?(Y/N)");//是否输出虚拟机代码scanf("%s",fname);listswitch=(fname[0]=='y'||fname[0]=='Y');printf("是否将符号表写入文件?(Y/N)");//是否输出符号表scanf("%s",fname);tableswitch=(fname[0]=='y'||fname[0]=='Y');init();//初始化err=0;cc=cx=ll=0;ch=' ';if(-1!=getsym()){fa=fopen("虚拟代码.txt","w");fas=fopen("符号表.txt","w");addset(nxtlev,declbegsys,statbegsys,symnum);nxtlev[period]=true;if(-1==block(0,0,nxtlev)){//调用编译程序fclose(fa);fclose(fa1);fclose(fas);fclose(fin);return 0;}if(sym!=period){error(9);//结尾丢失了句号}if(err!=0){printf("pl0源程序出现错误,退出编译!!!请从第一个错误处开始修改.\n\n");fprintf(cifa,"源程序出现错误,请检查!!!");fprintf(fa1,"源程序出现错误,请检查!!!");fprintf(fa,"源程序出现错误,请检查!!!");fprintf(fas,"源程序出现错误,请检查!!!");}fclose(fa);fclose(fa1);fclose(fas);}fclose(fin);}else{printf("Can't open file!\n");}fclose(cifa);//printf("\n");return 0;}void init(){//初始化int i;for(i=0;i<=255;i++)ssym[i]=nul;//设置单字符符号ssym['+']=plus;ssym['-']=minus;ssym['*']=times;ssym['/']=slash;ssym['(']=lparen;ssym[')']=rparen;ssym['=']=eql;ssym[',']=comma;ssym['.']=period;ssym['#']=neq;ssym[';']=semicolon;strcpy(&(word[0][0]),"begin");//保留字设置,以字母顺序排列便于折半查找strcpy(&(word[1][0]),"call");strcpy(&(word[2][0]),"const");strcpy(&(word[3][0]),"do");strcpy(&(word[4][0]),"end");strcpy(&(word[5][0]),"if");strcpy(&(word[6][0]),"odd");strcpy(&(word[7][0]),"procedure");strcpy(&(word[8][0]),"read");strcpy(&(word[9][0]),"then");strcpy(&(word[10][0]),"var");strcpy(&(word[11][0]),"while");strcpy(&(word[12][0]),"write");wsym[0]=beginsym;//设置保留字类别一字即一类wsym[1]=callsym;wsym[2]=constsym;wsym[3]=dosym;wsym[4]=endsym;wsym[5]=ifsym;wsym[6]=oddsym;wsym[7]=procsym;wsym[8]=readsym;wsym[9]=thensym;wsym[10]=varsym;wsym[11]=whilesym;wsym[12]=writesym;strcpy(&(mnemonic[lit][0]),"lit");//设置指令名称strcpy(&(mnemonic[opr][0]),"opr");strcpy(&(mnemonic[lod][0]),"lod");strcpy(&(mnemonic[sto][0]),"sto");strcpy(&(mnemonic[cal][0]),"cal");strcpy(&(mnemonic[inte][0]),"int");strcpy(&(mnemonic[jmp][0]),"jmp");strcpy(&(mnemonic[jpc][0]),"jpc");for(i=0;i<symnum;i++){//设置符号集declbegsys[i]=false;statbegsys[i]=false;facbegsys[i]=false;}declbegsys[constsym]=true;//设置声明开始符号集declbegsys[varsym]=true;declbegsys[procsym]=true;statbegsys[beginsym]=true;//设置语句开始符号集statbegsys[callsym]=true;statbegsys[ifsym]=true;statbegsys[whilesym]=true;facbegsys[ident]=true;//设置因子开始符号集facbegsys[number]=true;facbegsys[lparen]=true;}//用数组实现集合的集合运算int inset(int e,bool* s){return s[e];}int addset(bool*sr,bool* s1,bool* s2,int n){int i;for(i=0;i<n;i++)sr[i]=s1[i]||s2[i];return 0;}void error(int n){//出错处理,打印出错位置和错误编码char space[81];memset(space, 32,81);space[cc-1]=0;printf("error(%d)",n);fprintf(fa1,"error(%d)",n);switch(n){case 1:printf("\t\t常量说明中的“=”写成“:=”\n");fprintf(fa1,"\t\t常量说明中的“=”写成“:=”\n");break;case 2:printf("\t\t常量说明中的=后应该是数字\n");fprintf(fa1,"\t\t常量说明中的=后应该是数字\n");break;case 3:printf("\t\t常量说明符中的表示符应该是=\n" );fprintf(fa1,"\t\t常量说明符中的表示符应该是=\n");break;case 4:printf("\t\tconst,var,procedure后应为标识符\n" );fprintf(fa1,"\t\tconst,var,procedure后应为标识符\n");break;case 5:printf("\t\t漏掉了“,”或“;”\n" );fprintf(fa1,"\t\t漏掉了“,”或“;”\n" );break;case 6:printf("\t\t过程说明后的符号不正确\n" );fprintf(fa1,"\t\t过程说明后的符号不正确\n");break;case 7:printf("\t\t应是语句开始符\n" );fprintf(fa1,"\t\t应是语句开始符\n" );break;case 8:printf("\t\t程序体语句部分的后跟符不正确\n" );fprintf(fa1,"\t\t程序体语句部分的后跟符不正确\n" );break;case 9:printf("\t\t程序结尾丢了句号“.”\n\n" );fprintf(fa1,"\t\t程序结尾丢了句号“.”\n");break;case 10:printf("\t\t语句之间漏了“;”\n" );fprintf(fa1,"\t\t语句之间漏了“;”\n");break;case 11:printf("\t\t标识符拼写错误或未说明\n" );fprintf(fa1,"\t\t标识符拼写错误或未说明\n");break;case 12:printf("\t\t赋值语句中,赋值号左部标识符属性应是变量\n" );fprintf(fa1,"\t\t赋值语句中,赋值号左部标识符属性应是变量\n");break;case 13:printf("\t\t赋值语句左部标识符后应是复制号“:=”\n" );fprintf(fa1,"\t\t赋值语句左部标识符后应是复制号“:=”\n");break;case 14:printf("\t\tcall后应为标识符\n" );fprintf(fa1,"\t\tcall后应为标识符\n");break;case 15:printf("\t\tcall后标识符属性应为过程\n" );fprintf(fa1,"\t\tcall后标识符属性应为过程\n");break;case 16:printf("\t\t条件语句中丢了then\n" );fprintf(fa1,"\t\t条件语句中丢了then\n");break;case 17:printf("\t\t丢了“end”或“;”\n" );fprintf(fa1,"\t\t丢了“end”或“;”\n");break;case 18:printf("\t\twhile型循环语句中丢了“do”\n" );fprintf(fa1,"\t\twhile型循环语句中丢了“do”\n");break;case 19:printf("\t\t语句后的符号不正确\n" );fprintf(fa1,"\t\t语句后的符号不正确\n" );break;case 20:printf("\t\t应为关系运算符\n" );fprintf(fa1,"\t\t应为关系运算符\n");break;case 21:printf("\t\t表达式标示符属性不能是过程\n" );fprintf(fa1,"\t\t表达式标示符属性不能是过程\n");break;case 22:printf("\t\t表达式漏掉了右括号\n" );fprintf(fa1,"\t\t表达式漏掉了右括号\n");break;case 23:printf("\t\t因子后的非法符号\n" );fprintf(fa1,"\t\t因子后的非法符号\n");break;case 24:printf("\t\t表达式的开始符不能是此符号\n" );fprintf(fa1,"\t\t表达式的开始符不能是此符号\n");break;case 25:printf("\t\t标识符越界\n" );fprintf(fa1,"\t\t标识符越界\n");break;case 26:printf("\t\t非法字符\n" );fprintf(fa1,"\t\t非法字符\n");break;case 31:printf("\t\t数越界\n");fprintf(fa1,"\t\t数越界\n");break;case 32:printf("\t\tread语句括号中的标识符不是变量\n" );fprintf(fa1,"\t\tread语句括号中的标识符不是变量\n");break;case 33:printf("\t\twrite()或read()中应为完整表达式\n" );fprintf(fa1,"\t\twrite()或read()中应为完整表达式\n");break;default:printf("\t\t出现未知错误\n" );fprintf(fa1,"\t\t出现未知错误\n");}err++;}//漏掉空格,读取一个字符,每次读一行,存入line缓冲区,line被getsym取空后再读一//行,被函数getsym调用int getch(){if(cc==ll){if(feof(fin)){printf("program incomplete");return-1;}ll=0;cc=0;printf("\n%d ",cx);fprintf(fa1,"\n%d ",cx);ch=' ';while(ch!=10){if(EOF==fscanf(fin,"%c",&ch)){line[ll]=0;break;}printf("%c",ch);fprintf(fa1,"%c",ch);line[ll]=ch;ll++;}fprintf(cifa,"\n");}ch=line[cc];cc++;return 0;}int getsym(){//词法分析int i,j,k,l;while(ch==' '||ch==10||ch==9)//忽略空格换行 TABgetchdo;if(ch>='a'&&ch<='z'){//以字母开头的为保留字或者标识符k=0,l=1;do{if(k<al){//al为标识符或保留字最大长度a[k]=ch;k++;}if(k==al&&l==1){error(25);l=0;}getchdo;}while(ch>='a'&&ch<='z'||ch>='0'&&ch<='9');a[k]=0;//末尾存零strcpy(id,a);i=0;j=norw-1;do{//开始折半查找k=(i+j)/2;if(strcmp(id,word[k])<=0){j=k-1;}if(strcmp(id,word[k])>=0){i=k+1;}}while(i<=j);if(i-1>j){//找到即为保留字sym=wsym[k];fprintf(cifa,"%s\t\t%ssym\n",id,id);//printf("%s\t\t%ssym\n",id,id);}else{//否则为标识符或数字sym=ident;fprintf(cifa,"%s\t\tident\n",id);//printf("%s\t\tident\n",id);}}else {if(ch>='0'&&ch<='9'){k=0;num=0;sym=number;do{num=10*num+ch-'0';//数字的数位处理k++;getchdo;}while(ch>='0'&&ch<='9');k--;if(k>nmax){//数字的长度限制fprintf(cifa,"0\t\tnumber\n");num=0;error(31);}elsefprintf(cifa,"%d\t\tnumber\n",num);//printf("%d\t\tnumber\n",num);}else{if(ch==':'){//检测赋值符号,:只能和=匹配,否则不能识别getchdo;if(ch=='='){sym=becomes;fprintf(cifa,":=\t\tbecomes\n");getchdo;}else{sym=nul;}}else{if(ch=='<'){getchdo;if(ch=='='){sym=leq;//小于等于fprintf(cifa,"<=\t\tleq\n");getchdo;}else{sym=lss;//小于fprintf(cifa,"<\t\tlss\n");}}else{if(ch=='>'){getchdo;if(ch=='='){sym=geq;//大于等于fprintf(cifa,">=\t\tgeq\n");getchdo;}else{sym=gtr;//大于fprintf(cifa,">\t\tgtr\n");}}else{sym=ssym[ch];//不满足上述条件时按单字//符处理switch(ch){case'+':fprintf(cifa,"%c\t\tplus\n",ch);break;case '-':fprintf(cifa,"%c\t\tminus\n",ch );break;case '*':fprintf(cifa,"%c\t\ttimes\n",ch);break;case '/':fprintf(cifa,"%c\t\tslash\n",ch);break;case '(':fprintf(cifa,"%c\t\tlparen\n",ch);break;case ')':fprintf(cifa,"%c\t\trparen\n",ch);break;case '=':fprintf(cifa,"%c\t\teql\n",ch);break;case ',':fprintf(cifa,"%c\t\tcomma\n",ch);break;case '#':fprintf(cifa,"%c\t\tneq\n",ch);break;case ';':fprintf(cifa,"%c\t\tsemicolon\n",ch);break;case '.':break;default :error(26);}if(sym!=period){//判断是否结束getchdo;}else{printf("\n");fprintf(cifa,".\t\tperiod\n");}}}}}}return 0;}//生成目标代码//目标代码的功能码,层差和位移量int gen(enum fct x,int y,int z){if(cx>=cxmax){//如果目标代码索引过大,报错printf("Program too long");return -1;}code[cx].f=x;code[cx].l=y;code[cx].a=z;cx++;return 0;}//测试字符串int test(bool* s1,bool* s2,int n){if(!inset(sym,s1)){//测试sym是否属于s1,不属于则报错nerror(n);while((!inset(sym,s1))&&(!inset(sym,s2))){//检测不通过时,不停获得符号,直到它属于需要或补救的集合getsymdo;}}return 0;}//编译程序主体//lev:当前分程序所在层,tx:名字表当前尾指针fsys:当前模块后跟符号集合int block(int lev,int tx,bool* fsys){int i;int dx;//名字分配到的相对地址int tx0;//保留初始txint cx0;//保留初始cxbool nxtlev[symnum];dx=3;//相对地址从3开始,前3个单元即0、1、2单元分别为 SL:静态链;//DL:动态链;RA:返回地址tx0=tx;//记录本层的初始位置table[tx].adr=cx;gendo(jmp,0,0);if(lev>levmax){//层数超过3error(32);}do{if(sym==constsym){//收到常量声明printf("该语句为常量定义语句\n");getsymdo;do{constdeclarationdo(&tx,lev,&dx);//常量声明处理,dx 会改//变所以使用指针while(sym==comma){//处理一次多常量定义getsymdo;constdeclarationdo(&tx,lev,&dx);}if(sym==semicolon){//常量声明处理结束getsymdo;}else{error(5);//漏掉了逗号或者分号(一般是分号)}}while(sym==ident);}if(sym==varsym){//收到变量声明printf("该语句为变量声明语句\n");getsymdo;do{vardeclarationdo(&tx,lev,&dx);//变量声明处理while(sym==comma){//处理一次多变量定义getsymdo;vardeclarationdo(&tx,lev,&dx);}if(sym==semicolon){//变量声明处理结束getsymdo;}else{error(5);//漏掉逗号或者分号}}while(sym==ident);}while(sym==procsym){//收到过程声明printf("该语句为过程声明语句\n");getsymdo;if(sym==ident){enter(procedur,&tx,lev,&dx);//记录过程名getsymdo;}else{error(4);//过程声明后应为标识符}if(sym==semicolon){getsymdo;}else{error(5);//漏掉了分号}memcpy(nxtlev,fsys,sizeof(bool)*symnum);nxtlev[semicolon]=true;if(-1==block(lev+1,tx,nxtlev)){return -1;}if(sym==semicolon){getsymdo;memcpy(nxtlev,statbegsys,sizeof(bool)*symnum);nxtlev[ident]=true;nxtlev[procsym]=true;testdo(nxtlev,fsys,6);}else{error(5);}}memcpy(nxtlev,statbegsys,sizeof(bool)*symnum);nxtlev[ident]=true;nxtlev[period]=true;testdo(nxtlev,declbegsys,7);}while(inset(sym,declbegsys));//直到没有声明符号code[table[tx0].adr].a=cx;//开始生成当前过程代码table[tx0].adr=cx;//当前过程代码地址table[tx0].size=dx;cx0=cx;gendo(inte,0,dx);//生成分配存代码if(tableswitch){//输出符号表if(tx0+1<tx){fprintf(fas,"TABLE:\n");//printf("NULL\n");}for(i=tx0+1;i<=tx;i++){switch(table[i].kind){case constant:fprintf(fas,"%d const %s ",i,table[i].name);fprintf(fas,"val=%d\n",table[i].val);break;case variable:fprintf(fas,"%d var %s ",i,table[i].name);fprintf(fas,"lev=%daddr=%d\n",table[i].level,table[i].adr);break;case procedur:fprintf(fas,"%d proc %s ",i,table[i].name);fprintf(fas,"lev=%daddr=%dsize=%d\n", table[i].level,table[i].adr,table[i].size);break;}}}memcpy(nxtlev,fsys,sizeof(bool)*symnum);//每个后跟符号集和都包含上层后跟符//号集和,以便补救nxtlev[semicolon]=true;nxtlev[endsym]=true;statementdo(nxtlev,&tx,lev);gendo(opr,0,0);//每个过程出口都要使用的释放数据指令memset(nxtlev,0,sizeof(bool)*symnum);//分程序没有补救集合testdo(fsys,nxtlev,8);//检测后跟符号集的正确性listcode(cx0);//输出代码return 0;}//k:const var procedure//ptx:符号表尾的指针//pdx:dx为当前应分配变量的相对地址//lev:符号名字所在的层次//往符号表中添加void enter(enum object k,int* ptx,int lev,int* pdx){(* ptx)++;strcpy(table[(*ptx)].name,id);table[(* ptx)].kind=k;switch(k){case constant:if(num>amax){error(31);num=0;}table[(*ptx)].val=num;break;case variable:table[(*ptx)].level=lev;table[(*ptx)].adr=(*pdx);(*pdx)++;break;case procedur:table[(*ptx)].level=lev;break;}}//寻找符号在符号表中的地址int position(char*idt,int tx){int i;strcpy(table[0].name,idt);i=tx;//符号表尾while(strcmp(table[i].name,idt)!=0){i--;}return i;}//常量声明处理int constdeclaration(int* ptx,int lev,int* pdx){if(sym==ident){getsymdo;if(sym==eql||sym==becomes){if(sym==becomes){error(1);}getsymdo;if(sym==number){enter(constant,ptx,lev,pdx);getsymdo;}else{error(2);}}else{error(3);}}else{error(4);}return 0;}//变量声明处理int vardeclaration (int* ptx,int lev,int* pdx){if(sym==ident){enter(variable,ptx,lev,pdx);getsymdo;}else{error(4);}return 0;}//输出目标代码清单void listcode(int cx0){int i;if(listswitch){for(i=cx0;i<cx;i++){fprintf(fa,"%d %s %d %d\n",i,mnemonic[code[i].f],code[i].l,code[i],a);}}}//语句处理int statement(bool* fsys,int* ptx,int lev){int i,cx1,cx2;bool nxtlev[symnum];if(sym==ident){i=position(id,*ptx);if(i==0){error(11);}else{if(table[i].kind!=variable){error(12);i=0;}else{getsymdo;if(sym==becomes){getsymdo;printf("该语句为赋值语句。

编译原理(PL0编译程序源代码)

编译原理(PL0编译程序源代码)

/*PL/0编译程序(C语言版)*编译和运行环境:*Visual C++*WinXP/7*利用方式:*运行后输入PL/0源程序文件名*回答是不是将虚拟机代码写入文件*回答是不是将符号表写入文件*执行成功会产生四个文件(词法分析结果.txt符号表.txt虚拟代码.txt源程序和地址.txt)*/#include <>#include""#include"string"#define stacksize 500xt","w");fa1=fopen("源程序和地址.txt","w");xt","w");fas=fopen("符号表.txt","w");addset(nxtlev,declbegsys,statbegsys,symnum);nxtlev[period]=true;if(-1==block(0,0,nxtlev)){n\n");fprintf(cifa,"源程序显现错误,请检查!!!");fprintf(fa1,"源程序显现错误,请检查!!!");fprintf(fa,"源程序显现错误,请检查!!!");fprintf(fas,"源程序显现错误,请检查!!!");}fclose(fa);fclose(fa1);fclose(fas);}fclose(fin);}else{printf("Can't open file!\n");}fclose(cifa);]=period;ssym['#']=neq;ssym[';']=semicolon;strcpy(&(word[0][0]),"begin");\n\n" );fprintf(fa1,"\t\t程序结尾丢了句号“.”\n");break;case 10:printf("\t\t语句之间漏了“;”\n" );fprintf(fa1,"\t\t语句之间漏了“;”\n");break;case 11:printf("\t\t标识符拼写错误或未说明\n" );fprintf(fa1,"\t\t标识符拼写错误或未说明\n");break;case 12:printf("\t\t赋值语句中,赋值号左部标识符属性应是变量\n" );fprintf(fa1,"\t\t赋值语句中,赋值号左部标识符属性应是变量\n");break;case 13:printf("\t\t赋值语句左部标识符后应是复制号“:=”\n" );fprintf(fa1,"\t\t赋值语句左部标识符后应是复制号“:=”\n");break;case 14:printf("\t\tcall后应为标识符\n" );fprintf(fa1,"\t\tcall后应为标识符\n");break;case 15:printf("\t\tcall后标识符属性应为进程\n" );fprintf(fa1,"\t\tcall后标识符属性应为进程\n");break;case 16:printf("\t\t条件语句中丢了then\n" );fprintf(fa1,"\t\t条件语句中丢了then\n");break;case 17:printf("\t\t丢了“end”或“;”\n" );fprintf(fa1,"\t\t丢了“end”或“;”\n");break;case 18:printf("\t\twhile型循环语句中丢了“do”\n" ); fprintf(fa1,"\t\twhile型循环语句中丢了“do”\n");break;case 19:printf("\t\t语句后的符号不正确\n" );fprintf(fa1,"\t\t语句后的符号不正确\n" );break;case 20:printf("\t\t应为关系运算符\n" );fprintf(fa1,"\t\t应为关系运算符\n");break;case 21:printf("\t\t表达式内标示符属性不能是进程\n" ); fprintf(fa1,"\t\t表达式内标示符属性不能是进程\n");break;case 22:printf("\t\t表达式漏掉了右括号\n" );fprintf(fa1,"\t\t表达式漏掉了右括号\n");break;case 23:printf("\t\t因子后的非法符号\n" );fprintf(fa1,"\t\t因子后的非法符号\n");break;case 24:printf("\t\t表达式的开始符不能是此符号\n" );fprintf(fa1,"\t\t表达式的开始符不能是此符号\n");break;case 25:printf("\t\t标识符越界\n" );fprintf(fa1,"\t\t标识符越界\n");break;case 26:printf("\t\t非法字符\n" );fprintf(fa1,"\t\t非法字符\n");break;case 31:printf("\t\t数越界\n");fprintf(fa1,"\t\t数越界\n");break;case 32:printf("\t\tread语句括号中的标识符不是变量\n" );fprintf(fa1,"\t\tread语句括号中的标识符不是变量\n");break;case 33:printf("\t\twrite()或read()中应为完整表达式\n" );fprintf(fa1,"\t\twrite()或read()中应为完整表达式\n");break;default:printf("\t\t显现未知错误\n" );fprintf(fa1,"\t\t显现未知错误\n");}err++;}:break;default :error(26);}if(sym!=period){t\tperiod\n");}}}}}}return 0;}=x;code[cx].l=y;code[cx].a=z;cx++;return 0;}dr=cx;gendo(jmp,0,0);if(lev>levmax){dr].a=cx;dr=cx;ize=dx;cx0=cx;gendo(inte,0,dx);ind){case constant:fprintf(fas,"%d const %s ",i,table[i].name);fprintf(fas,"val=%d\n",table[i].val);break;case variable:fprintf(fas,"%d var %s ",i,table[i].name);fprintf(fas,"lev=%daddr=%d\n",table[i].level,table[i].adr);break;case procedur:fprintf(fas,"%d proc %s ",i,table[i].name);fprintf(fas,"lev=%daddr=%dsize=%d\n", table[i].level,table[i].adr,table[i].size);break;}}}memcpy(nxtlev,fsys,sizeof(bool)*symnum);ame,id);table[(* ptx)].kind=k;switch(k){case constant:if(num>amax){error(31);num=0;}table[(*ptx)].val=num;break;case variable:table[(*ptx)].level=lev;table[(*ptx)].adr=(*pdx);(*pdx)++;break;case procedur:table[(*ptx)].level=lev;break;}}ame,idt);i=tx;ame,idt)!=0){i--;}return i;}],code[i].l,code[i],a);}}}ind!=variable){error(12);i=0;}else{getsymdo;if(sym==becomes){getsymdo;printf("该语句为赋值语句。

编译原理(PL0编译程序源代码)

编译原理(PL0编译程序源代码)

下载可编辑/*PL/0编译程序(C语言版)*编译和运行环境:*Visual C++6.0*WinXP/7*使用方法:*运行后输入 PL/0 源程序文件名*回答是否将虚拟机代码写入文件*回答是否将符号表写入文件* 执行成功会产生四个文件(词法分析结果.txt符号表.txt虚拟代码.txt源程序和地址.txt)*/#include <stdio.h>#include"pl0.h"#include"string"#define stacksize 500//解释执行时使用的栈int main(){bool nxtlev[symnum];printf("请输入源程序文件名:");scanf("%s",fname);fin=fopen(fname,"r");//以只读方式打开pl0 源程序文件cifa=fopen(" 词法分析结果.txt","w");fa1=fopen(" 源程序和地址 .txt","w");//输出源文件及各行对应的首地址fprintf(fa1,"输入 pl0 源程序文件名:");fprintf(fa1,"%s\n",fname);if(fin){printf("是否将虚拟机代码写入文件?(Y/N)");//是否输出虚拟机代码scanf("%s",fname);listswitch=(fname[0]=='y'||fname[0]=='Y');printf("是否将符号表写入文件?(Y/N)");//是否输出符号表scanf("%s",fname);tableswitch=(fname[0]=='y'||fname[0]=='Y');init();//初始化err=0;cc=cx=ll=0;ch=' ';if(-1!=getsym()){fa=fopen("虚拟代码.txt","w");fas=fopen(" 符号表 .txt","w");addset(nxtlev,declbegsys,statbegsys,symnum);nxtlev[period]=true;if(-1==block(0,0,nxtlev)){//调用编译程序fclose(fa);fclose(fa1);fclose(fas);fclose(fin);return 0;}if(sym!=period){error(9);//结尾丢失了句号}if(err!=0){printf("pl0源程序出现错误,退出编译!!!请从第一个错误处开始修改 .\n\n");fprintf(cifa,"源程序出现错误,请检查!!!");fprintf(fa1,"源程序出现错误,请检查!!!");fprintf(fa,"源程序出现错误,请检查!!!");fprintf(fas,"源程序出现错误,请检查!!!");}fclose(fa);fclose(fa1);fclose(fas);}fclose(fin);}else{printf("Can't open file!\n");}fclose(cifa);//printf("\n");return 0;}void init(){//初始化int i;for(i=0;i<=255;i++)ssym[i]=nul;//设置单字符符号ssym['+']=plus;ssym['-']=minus;ssym['*']=times;ssym['/']=slash;ssym['(']=lparen;ssym[')']=rparen;ssym['=']=eql;ssym[',']=comma;ssym['.']=period;ssym['#']=neq;ssym[';']=semicolon;strcpy(&(word[0][0]),"begin");//保留字设置,以字母顺序排列便于折半查找strcpy(&(word[1][0]),"call");strcpy(&(word[2][0]),"const");strcpy(&(word[3][0]),"do");strcpy(&(word[4][0]),"end");strcpy(&(word[5][0]),"if");strcpy(&(word[6][0]),"odd");strcpy(&(word[7][0]),"procedure");strcpy(&(word[8][0]),"read");strcpy(&(word[9][0]),"then");strcpy(&(word[10][0]),"var");strcpy(&(word[11][0]),"while");strcpy(&(word[12][0]),"write");wsym[0]=beginsym;//设置保留字类别一字即一类wsym[1]=callsym;wsym[2]=constsym;wsym[3]=dosym;wsym[4]=endsym;wsym[6]=oddsym;wsym[7]=procsym;wsym[8]=readsym;wsym[9]=thensym;wsym[10]=varsym;wsym[11]=whilesym;wsym[12]=writesym;strcpy(&(mnemonic[lit][0]),"lit");//设置指令名称strcpy(&(mnemonic[opr][0]),"opr");strcpy(&(mnemonic[lod][0]),"lod");strcpy(&(mnemonic[sto][0]),"sto");strcpy(&(mnemonic[cal][0]),"cal");strcpy(&(mnemonic[inte][0]),"int");strcpy(&(mnemonic[jmp][0]),"jmp");strcpy(&(mnemonic[jpc][0]),"jpc");for(i=0;i<symnum;i++){//设置符号集declbegsys[i]=false;statbegsys[i]=false;facbegsys[i]=false;}declbegsys[constsym]=true;//设置声明开始符号集declbegsys[varsym]=true;declbegsys[procsym]=true;statbegsys[beginsym]=true;//设置语句开始符号集statbegsys[callsym]=true;statbegsys[ifsym]=true;statbegsys[whilesym]=true;facbegsys[ident]=true;//设置因子开始符号集facbegsys[number]=true;facbegsys[lparen]=true;}//用数组实现集合的集合运算int inset(int e,bool* s){return s[e];}int addset(bool*sr,bool* s1,bool* s2,int n){int i;for(i=0;i<n;i++)sr[i]=s1[i]||s2[i];return 0;}void error(int n){//出错处理,打印出错位置和错误编码char space[81];memset(space, 32,81);space[cc-1]=0;printf("error(%d)",n);fprintf(fa1,"error(%d)",n);case 1:printf("\t\t常量说明中的“=”写成“ := ” \n");fprintf(fa1,"\t\t常量说明中的“=”写成“ := ”\n");break;case 2:printf("\t\t常量说明中的=后应该是数字\n");fprintf(fa1,"\t\t常量说明中的 =后应该是数字\n");break;case 3:printf("\t\t常量说明符中的表示符应该是=\n" );fprintf(fa1,"\t\t常量说明符中的表示符应该是=\n");break;case 4:printf("\t\tconst,var,procedure后应为标识符 \n" );fprintf(fa1,"\t\tconst,var,procedure后应为标识符\n");break;case 5:printf("\t\t漏掉了“ , ”或“ ; ” \n" );fprintf(fa1,"\t\t漏掉了“ , ”或“ ; ”\n" );break;case 6:printf("\t\t过程说明后的符号不正确\n" );fprintf(fa1,"\t\t过程说明后的符号不正确\n");break;case 7:printf("\t\t应是语句开始符\n" );fprintf(fa1,"\t\t应是语句开始符\n" );break;case 8:printf("\t\t程序体语句部分的后跟符不正确\n" );fprintf(fa1,"\t\t程序体语句部分的后跟符不正确\n" );break;case 9:printf("\t\t程序结尾丢了句号“. ”\n\n" );fprintf(fa1,"\t\t程序结尾丢了句号“. ” \n");break;case 10:printf("\t\t语句之间漏了“; ” \n" );fprintf(fa1,"\t\t语句之间漏了“; ” \n");break;case 11:printf("\t\t标识符拼写错误或未说明\n" );fprintf(fa1,"\t\t标识符拼写错误或未说明\n");break;case 12:printf("\t\t赋值语句中,赋值号左部标识符属性应是变量\n" );fprintf(fa1,"\t\t赋值语句中,赋值号左部标识符属性应是变量\n");break;case 13:printf("\t\t赋值语句左部标识符后应是复制号“:= ” \n" );fprintf(fa1,"\t\t赋值语句左部标识符后应是复制号“:= ” \n");break;case 14:printf("\t\tcall后应为标识符 \n" );fprintf(fa1,"\t\tcall后应为标识符 \n");break;case 15:printf("\t\tcall后标识符属性应为过程\n" );fprintf(fa1,"\t\tcall后标识符属性应为过程\n");break;case 16:printf("\t\t条件语句中丢了then\n" );fprintf(fa1,"\t\t条件语句中丢了then\n");break;case 17:printf("\t\t丢了“ end”或“ ; ” \n" );fprintf(fa1,"\t\t丢了“ end”或“ ; ”\n");break;case 18:printf("\t\twhile型循环语句中丢了“do” \n" );fprintf(fa1,"\t\twhile型循环语句中丢了“do” \n");break;case 19:printf("\t\t语句后的符号不正确\n" );fprintf(fa1,"\t\t语句后的符号不正确\n" );break;case 20:printf("\t\t应为关系运算符\n" );fprintf(fa1,"\t\t应为关系运算符\n");break;case 21:printf("\t\t表达式标示符属性不能是过程\n" );fprintf(fa1,"\t\t表达式标示符属性不能是过程\n");break;case 22:printf("\t\t表达式漏掉了右括号\n" );fprintf(fa1,"\t\t表达式漏掉了右括号\n");break;case 23:printf("\t\t因子后的非法符号\n" );fprintf(fa1,"\t\t因子后的非法符号\n");break;case 24:printf("\t\t表达式的开始符不能是此符号\n" );fprintf(fa1,"\t\t表达式的开始符不能是此符号\n");break;case 25:printf("\t\t标识符越界 \n" );fprintf(fa1,"\t\t标识符越界 \n");break;case 26:printf("\t\t非法字符 \n" );fprintf(fa1,"\t\t非法字符 \n");break;case 31:printf("\t\t数越界 \n");fprintf(fa1,"\t\t数越界 \n");break;case 32:printf("\t\tread语句括号中的标识符不是变量\n" );fprintf(fa1,"\t\tread语句括号中的标识符不是变量\n");break;case 33:printf("\t\twrite()或 read() 中应为完整表达式\n" );fprintf(fa1,"\t\twrite()或 read() 中应为完整表达式\n");break;default:printf("\t\t出现未知错误 \n" );fprintf(fa1,"\t\t出现未知错误 \n");}err++;}// 漏掉空格,读取一个字符,每次读一行,存入line缓冲区,line被getsym取空后再读一// 行,被函数getsym 调用int getch(){if(cc==ll){if(feof(fin)){printf("program incomplete");return-1;}ll=0;cc=0;printf("\n%d ",cx);fprintf(fa1,"\n%d ",cx);ch=' ';while(ch!=10){if(EOF==fscanf(fin,"%c",&ch)){line[ll]=0;break;}printf("%c",ch);fprintf(fa1,"%c",ch);line[ll]=ch;ll++;}fprintf(cifa,"\n");}ch=line[cc];cc++;return 0;}int getsym(){//词法分析int i,j,k,l;while(ch==' '||ch==10||ch==9)//忽略空格换行TABgetchdo;if(ch>='a'&&ch<='z'){//以字母开头的为保留字或者标识符k=0,l=1;do{if(k<al){//al为标识符或保留字最大长度a[k]=ch;k++;}if(k==al&&l==1){error(25);l=0;}getchdo;}while(ch>='a'&&ch<='z'||ch>='0'&&ch<='9');a[k]=0;//末尾存零strcpy(id,a);i=0;j=norw-1;do{//开始折半查找k=(i+j)/2;if(strcmp(id,word[k])<=0){j=k-1;}if(strcmp(id,word[k])>=0){i=k+1;}}while(i<=j);if(i-1>j){//找到即为保留字sym=wsym[k];fprintf(cifa,"%s\t\t%ssym\n",id,id);//printf("%s\t\t%ssym\n",id,id);}else{//否则为标识符或数字sym=ident;fprintf(cifa,"%s\t\tident\n",id);//printf("%s\t\tident\n",id);}}else {if(ch>='0'&&ch<='9'){k=0;num=0;sym=number;do{num=10*num+ch-'0';//数字的数位处理k++;getchdo;}while(ch>='0'&&ch<='9');k--;if(k>nmax){//数字的长度限制fprintf(cifa,"0\t\tnumber\n");num=0;error(31);}elsefprintf(cifa,"%d\t\tnumber\n",num);//printf("%d\t\tnumber\n",num);}else{if(ch==':'){//检测赋值符号,:只能和=匹配,否则不能识别getchdo;if(ch=='='){sym=becomes;fprintf(cifa,":=\t\tbecomes\n");getchdo;}else{sym=nul;}}else{if(ch=='<'){getchdo;if(ch=='='){sym=leq;//小于等于fprintf(cifa,"<=\t\tleq\n");getchdo;}else{sym=lss;//小于fprintf(cifa,"<\t\tlss\n");}}else{if(ch=='>'){getchdo;if(ch=='='){sym=geq;// 大于等于fprintf(cifa,">=\t\tgeq\n");getchdo;}else{sym=gtr;//大于fprintf(cifa,">\t\tgtr\n");}}else{sym=ssym[ch];//不满足上述条件时按单字// 符处理switch(ch){case'+':fprintf(cifa,"%c\t\tplus\n",ch);break;case '-':fprintf(cifa,"%c\t\tminus\n",ch);break;case '*':fprintf(cifa,"%c\t\ttimes\n",ch);break;case '/':fprintf(cifa,"%c\t\tslash\n",ch);break;case '(':fprintf(cifa,"%c\t\tlparen\n",ch);break;case ')':fprintf(cifa,"%c\t\trparen\n",ch);break;case '=':fprintf(cifa,"%c\t\teql\n",ch);break;case ',':fprintf(cifa,"%c\t\tcomma\n",ch);break;case '#':fprintf(cifa,"%c\t\tneq\n",ch);break;case ';':fprintf(cifa,"%c\t\tsemicolon\n",ch);break;case '.':break;default :error(26);}if(sym!=period){//判断是否结束getchdo;}else{printf("\n");fprintf(cifa,".\t\tperiod\n");}}}}}}return 0;}//生成目标代码 // 目标代码的功能码,层差和位移量int gen(enum fct x,int y,int z){if(cx>=cxmax){//如果目标代码索引过大,报错printf("Program too long");return -1;}code[cx].f=x;code[cx].l=y;code[cx].a=z;cx++;return 0;}//测试字符串int test(bool* s1,bool* s2,int n){if(!inset(sym,s1)){//测试sym是否属于s1,不属于则报错n error(n);while((!inset(sym,s1))&&(!inset(sym,s2))){//检测不通过时,不停获得符号,直到它属于需要或补救的集合getsymdo;}}return 0;}// 编译程序主体//lev:当前分程序所在层,tx: 名字表当前尾指针fsys :当前模块后跟符号集合int block(int lev,int tx,bool* fsys){int i;int dx;//名字分配到的相对地址int tx0;//保留初始txint cx0;//保留初始cxbool nxtlev[symnum];dx=3;//相对地址从 3 开始,前 3 个单元即0、 1、 2 单元分别为SL: 静态链;//DL :动态链; RA:返回地址tx0=tx;//记录本层的初始位置table[tx].adr=cx;gendo(jmp,0,0);if(lev>levmax){//层数超过3error(32);}do{if(sym==constsym){//收到常量声明printf("该语句为常量定义语句\n");getsymdo;do{constdeclarationdo(&tx,lev,&dx);//常量声明处理,dx 会改 // 变所以使用指针while(sym==comma){//处理一次多常量定义getsymdo;constdeclarationdo(&tx,lev,&dx);}if(sym==semicolon){//常量声明处理结束getsymdo;}else{error(5);//漏掉了逗号或者分号(一般是分号)}}while(sym==ident);}if(sym==varsym){//收到变量声明printf("该语句为变量声明语句\n");getsymdo;do{vardeclarationdo(&tx,lev,&dx);//变量声明处理while(sym==comma){//处理一次多变量定义getsymdo;vardeclarationdo(&tx,lev,&dx);}if(sym==semicolon){//变量声明处理结束getsymdo;}else{error(5);//漏掉逗号或者分号}}while(sym==ident);}while(sym==procsym){//收到过程声明printf("该语句为过程声明语句\n");getsymdo;if(sym==ident){enter(procedur,&tx,lev,&dx);//记录过程名getsymdo;}else{error(4);//过程声明后应为标识符}if(sym==semicolon){getsymdo;}else{error(5);//漏掉了分号}memcpy(nxtlev,fsys,sizeof(bool)*symnum);nxtlev[semicolon]=true;if(-1==block(lev+1,tx,nxtlev)){return -1;}if(sym==semicolon){getsymdo;memcpy(nxtlev,statbegsys,sizeof(bool)*symnum);nxtlev[ident]=true;nxtlev[procsym]=true;testdo(nxtlev,fsys,6);}else{error(5);}}memcpy(nxtlev,statbegsys,sizeof(bool)*symnum);nxtlev[ident]=true;nxtlev[period]=true;testdo(nxtlev,declbegsys,7);}while(inset(sym,declbegsys));//直到没有声明符号code[table[tx0].adr].a=cx;//开始生成当前过程代码table[tx0].adr=cx;//当前过程代码地址table[tx0].size=dx;cx0=cx;gendo(inte,0,dx);//生成分配存代码if(tableswitch){//输出符号表if(tx0+1<tx){fprintf(fas,"TABLE:\n");//printf("NULL\n");}for(i=tx0+1;i<=tx;i++){switch(table[i].kind){case constant:fprintf(fas,"%d const %s ",i,table[i].name);fprintf(fas,"val=%d\n",table[i].val);break;case variable:fprintf(fas,"%d var %s ",i,table[i].name);fprintf(fas,"lev=%daddr=%d\n",table[i].level,table[i].adr);break;case procedur:fprintf(fas,"%d proc %s ",i,table[i].name);fprintf(fas,"lev=%daddr=%dsize=%d\n",table[i].level,table[i].adr,table[i].size);break;}}}memcpy(nxtlev,fsys,sizeof(bool)*symnum);//每个后跟符号集和都包含上层后跟符// 号集和,以便补救nxtlev[semicolon]=true;nxtlev[endsym]=true;statementdo(nxtlev,&tx,lev);gendo(opr,0,0);//每个过程出口都要使用的释放数据指令memset(nxtlev,0,sizeof(bool)*symnum);//分程序没有补救集合testdo(fsys,nxtlev,8);//检测后跟符号集的正确性listcode(cx0);//输出代码return 0;}//k:const var procedure//ptx:符号表尾的指针//pdx:dx为当前应分配变量的相对地址//lev: 符号名字所在的层次// 往符号表中添加void enter(enum object k,int* ptx,int lev,int* pdx){(* ptx)++;strcpy(table[(*ptx)].name,id);table[(* ptx)].kind=k;switch(k){case constant:if(num>amax){error(31);num=0;}table[(*ptx)].val=num;break;case variable:table[(*ptx)].level=lev;table[(*ptx)].adr=(*pdx);(*pdx)++;break;case procedur:table[(*ptx)].level=lev;break;}}//寻找符号在符号表中的地址int position(char*idt,int tx){int i;strcpy(table[0].name,idt);i=tx;//符号表尾while(strcmp(table[i].name,idt)!=0){i--;}return i;}//常量声明处理int constdeclaration(int* ptx,int lev,int* pdx){if(sym==ident){getsymdo;if(sym==eql||sym==becomes){if(sym==becomes){error(1);}getsymdo;if(sym==number){enter(constant,ptx,lev,pdx);getsymdo;}else{error(2);}}else{error(3);}}else{error(4);}return 0;}//变量声明处理int vardeclaration (int* ptx,int lev,int* pdx){if(sym==ident){enter(variable,ptx,lev,pdx);getsymdo;}else{error(4);}return 0;}//输出目标代码清单void listcode(int cx0){int i;if(listswitch){for(i=cx0;i<cx;i++){fprintf(fa,"%d %s %d %d\n",i,mnemonic[code[i].f],code[i].l,code[i],a);}}}//语句处理int statement(bool* fsys,int* ptx,int lev){int i,cx1,cx2;bool nxtlev[symnum];if(sym==ident){i=position(id,*ptx);if(i==0){error(11);}else{if(table[i].kind!=variable){error(12);i=0;}else{getsymdo;if(sym==becomes){getsymdo;printf("该语句为赋值语句。

pl0编译原理

pl0编译原理

pl0编译原理编译原理是计算机科学中的一门重要课程,它研究的是如何将高级语言转化为机器语言的过程。

在编译原理中,pl0是一种简单的编程语言,它的设计目标是为了教学和研究目的而产生的。

本文将介绍pl0编译原理的基本概念和主要过程。

一、pl0编译原理的基本概念1.1 什么是pl0编程语言pl0是一种结构化的过程性编程语言,它的语法规则简单明了,易于学习和理解。

pl0支持基本的数据类型和控制结构,包括整型、实型、布尔型等。

1.2 pl0编译器的作用pl0编译器的主要作用是将pl0源代码转化为目标代码,使计算机能够理解和执行这些代码。

编译器的工作包括词法分析、语法分析、语义分析、中间代码生成和目标代码生成等。

1.3 pl0编译过程的主要阶段pl0编译过程主要包括词法分析、语法分析、语义分析和代码生成等阶段。

在词法分析阶段,编译器将源代码分解成一个个的词法单元;在语法分析阶段,编译器将词法单元按照语法规则组织成一个抽象语法树;在语义分析阶段,编译器对抽象语法树进行语义检查和类型推导;最后,在代码生成阶段,编译器将抽象语法树转化为目标代码。

二、pl0编译原理的主要过程2.1 词法分析词法分析是编译过程的第一步,它将源代码分解成一个个的词法单元。

在pl0编译器中,常见的词法单元包括关键字、标识符、常量、运算符和界符等。

编译器通过正则表达式和有限自动机等技术来实现词法分析。

2.2 语法分析语法分析是编译过程的第二步,它将词法单元按照语法规则组织成一个抽象语法树。

在pl0编译器中,常见的语法规则包括表达式、语句、函数和过程等。

编译器通过上下文无关文法和递归下降等技术来实现语法分析。

2.3 语义分析语义分析是编译过程的第三步,它对抽象语法树进行语义检查和类型推导。

在pl0编译器中,常见的语义检查包括变量声明检查、类型匹配检查和作用域检查等。

编译器通过符号表和类型推导等技术来实现语义分析。

2.4 代码生成代码生成是编译过程的最后一步,它将抽象语法树转化为目标代码。

PL0编译器源程序分析

PL0编译器源程序分析

PL/0编译器源程序分析PL/0语言是Pascal语言的一个子集,我们这里分析的PL/0的编译程序包括了对PL/0语言源程序进行分析处理、编译生成类PCODE代码,并在虚拟机上解释运行生成的类PCODE代码的功能。

PL/0语言编译程序采用以语法分析为核心、一遍扫描的编译方法。

词法分析和代码生成作为独立的子程序供语法分析程序调用。

语法分析的同时,提供了出错报告和出错恢复的功能。

在源程序没有错误编译通过的情况下,调用类PCODE解释程序解释执行生成的类PCODE代码。

词法分析子程序分析:词法分析子程序名为getsym,功能是从源程序中读出一个单词符号(token),把它的信息放入全局变量sym、id和num中,语法分析器需要单词时,直接从这三个变量中获得。

(注意!语法分析器每次用完这三个变量的值就立即调用getsym子程序获取新的单词供下一次使用。

而不是在需要新单词时才调用getsym过程。

)getsym过程通过反复调用getch子过程从源程序过获取字符,并把它们拼成单词。

getch过程中使用了行缓冲区技术以提高程序运行效率。

词法分析器的分析过程:调用getsym时,它通过getch过程从源程序中获得一个字符。

如果这个字符是字母,则继续获取字符或数字,最终可以拼成一个单词,查保留字表,如果查到为保留字,则把sym变量赋成相应的保留字类型值;如果没有查到,则这个单词应是一个用户自定义的标识符(可能是变量名、常量名或是过程的名字),把sym置为ident,把这个单词存入id变量。

查保留字表时使用了二分法查找以提高效率。

如果getch获得的字符是数字,则继续用getch获取数字,并把它们拼成一个整数,然后把sym置为number,并把拼成的数值放入num变量。

如果识别出其它合法的符号(比如:赋值号、大于号、小于等于号等),则把sym则成相应的类型。

如果遇到不合法的字符,把sym置成nul。

语法分析子程序分析:语法分析子程序采用了自顶向下的递归子程序法,语法分析同时也根据程序的语意生成相应的代码,并提供了出错处理的机制。

PL0编译程序的实现(精)

PL0编译程序的实现(精)

( 0) ( 1) ( 2) ( 3) ( 4) ( 5) ( 6) ( 7) ( 8) ( 9) (10) (11) (12) (13) (14) (15) (16) (17) (18) (19) (20) (21) (22) (23) (24)
jmp jmp int lod lit opr sto opr int opr sto lod lit opr jpc cal lit lod opr opr opr opr sto jmp opr
PL/0编译程序功能的框架 PL/0源程序 PL/0编译程序
类 pcode代码
类 pcode解释程序 输入 输出
第2 章
PL/0编译程序的实现
步骤1. 源语言PL/0与目标代码类pcode 之间的映射 步骤2. PL/0编译程序的总体设计 步骤3. PL/0编译程序词法分析的设计与实现 步骤4. PL/0编译程序语法语义分析的设计与实现 步骤5. PL/0编译程序代码生成的实现 *步骤6. PL/0编译程序错误处理的实现 步骤7. 类pcode代码解释器的设计与实现
步骤1 PL/0程序到类pcode代码的映射
目标代码类pcode是一种假想栈式计算机的汇编 语言。 指令格式 f l a
f
l a
功能码
层次差 (标识符的引用层减去它的定义层) 根据不同的指令有所区别
假想栈式计算机的运行栈 T是栈顶指针 B是基地址:分配给一个过程数据空间的开始位置 T
B
2 1 0
步骤3
PL/0编译程序词法分析的 设计与实现
词法分析如何把单词传递给语法分析 单词定义(见教材288页) type symbol=(nul,ident,number,plus,…, varsym,procsym); ( 定义纯量/枚举类型,类似C的enum) sym:symbol; id:alfa; (type alfa=packed array[1..al] of char) al=10; num:integer;

COMPILER-PL0语言的示例-语法描述

COMPILER-PL0语言的示例-语法描述

补充: PL/0语言及其编译程序的实现✧PL/0语言的编译程序把PL/0语言程序翻译成为一种称为类pcode的假想的栈式计算机汇编语言程序。

这种汇编语言与机器无关,若要在某一机器上实现PL/0语言程序,只需用机器上配置的任何语言对类pcode语言程序进行解释执行。

✧世界著名计算机科学家N.Wirth编写了"PL/0语言的编译程序"。

✧后面附了PL/0编译程序源代码。

⏹何为PL/0语言-PL/0语言是PASCAL语言的子集. 具备了一般高级语言的必备部分.(如: read,write,if-then,do,while,call,begin-end,赋值语句)☐PL/0语言中的数据类型只有整型,没有浮点数,所以圆周率只能近似为3。

数字最多为14位。

标识符的有效长度是10 ☐PL/0语言允许过程嵌套定义和递归调用的。

过程最多可嵌套三层。

☐过程可以引用自己定义的局部标识符,也可以引用包围在它的外过程(包括主程序)定义的标识符。

以下将用扩充的巴科斯-瑙尔范式(BACKUS-NAUR FORM)和语法图(EBNF)两种形式给出PL/0语言的语法描述。

------------------------------------------------------------------------------------------------------------------------------------------------------- (a) PL/0语言文法的EBNF表示EBNF表示的符号说明1)〈〉:用左右尖括号括起来的中文字表示语法构造成分,或称语法单位,为非终结符。

2)∷= :该符号的左部由右部定义,可读作'定义为'。

3)| :表示'或',为左部可由多个右部定义。

<标识符| 常量> 是不允许的. 因为非终结符作为一个独立的单位,不可分割4){ } :花括号表示其内的语法成分可以重复。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

/*PL/0 编译系统C版本头文件 */# define norw 13 ET and Visual C++.NET 2003*WinNT, Win 200, WinXP and Win2003*2 gcc version (Red Hat Linux Fedora core 1*Intel 32 platform*使用方法:*运行后输入PL/0 源程序文件名*回答是否输出虚拟机代码*回答是否输出名字表* 输出虚拟机代码* 输出源文件及其各行对应的首地址* 输出结果* 输出名字表*/#include<>#include""#include""/*解释执行时使用的栈*/#define stacksize 500int main(){bool nxtlev[symnum];printf("Input pl/0 file ");scanf("%s",fname); /*输入文件名*/ fin=fopen(fname,"r"); ]=period;ssym[';']=semicolon;/*设置保留字名字,按照字母顺序,便于折半查找*/ strcpy(&(word[0][0]),"begin");strcpy(&(word[1][0]),"call");strcpy(&(word[2][0]),"const");strcpy(&(word[3][0]),"do");strcpy(&(word[4][0]),"end");strcpy(&(word[5][0]),"if");strcpy(&(word[6][0]),"odd");strcpy(&(word[7][0]),"procedure");strcpy(&(word[8][0]),"read");strcpy(&(word[9][0]),"then");strcpy(&(word[10][0]),"var");strcpy(&(word[11][0]),"while");strcpy(&(word[12][0]),"write");/*设置保留字符号*/wsym[0]=beginsym;wsym[1]=callsym;wsym[2]=constsym;wsym[3]=dosym;wsym[4]=endsym;wsym[5]=ifsym;wsym[6]=oddsym;wsym[7]=procsym;wsym[8]=readsym;wsym[9]=thensym;wsym[11]=whilesym;wsym[12]=writesym;/*设置指令名称*/strcpy(&(mnemonic[lit][0]),"lit"); strcpy(&(mnemonic[opr][0]),"opr"); strcpy(&(mnemonic[lod][0]),"lod"); strcpy(&(mnemonic[sto][0]),"sto"); strcpy(&(mnemonic[cal][0]),"cal"); strcpy(&(mnemonic[inte][0]),"int"); strcpy(&(mnemonic[jmp][0]),"jmp"); strcpy(&(mnemonic[jpc][0]),"jpc");/*设置符号集*/for(i=0;i<symnum;i++){declbegsys[i]=false;statbegsys[i]=false;facbegsys[i]=false;}/*设置声明开始符号集*/declbegsys[constsym]=true; declbegsys[varsym]=true; declbegsys[procsym]=true;/*设置语句开始符号集*/statbegsys[beginsym]=true;statbegsys[callsym]=true;statbegsys[ifsym]=true;statbegsys[whilesym]=true;/*设置因子开始符号集*/facbegsys[ident]=true;facbegsys[number]=true;facbegsys[lparen]=true;}/**用数组实现集合的集合运算*/int inset(int e,bool* s){return s[e];}int addset(bool* sr,bool* s1,bool* s2,int n) {int i;for(i=0;i<n;i++){sr[i]=s1[i]||s2[i];}return 0;}int subset(bool* sr,bool* s1,bool* s2,int n) {int i;for(i=0;i<n;i++){sr[i]=s1[i]&&(!s2[i]);}return 0;}int mulset(bool* sr,bool* s1,bool* s2,int n){int i;for(i=0;i<n;i++){sr[i]=s1[i]&&s2[i];}return 0;}/**出错处理,打印出错位置和错误编码*/void error(int n){char space[81];memset(space,32,81); printf("-------%c\n",ch);space[cc-1]=0;=x;code[cx].l=y;code[cx].a=z;cx++;}/**测试当前符号是否合法**在某一部分(如一条语句,一个表达式)将要结束时时我们希望下一个符号属于某集合*(该部分的后跟符号) test 负责这项检测,并且负责当检测不通过时的补救措施*程序在需要检测时指定当前需要的符号集合和补救用的集合(如之前未完成部分的后跟*符号),以及不通过时的错误号**S1:我们需要的符号*s2:如果不是我们需要的,则需要一个补救用的集合*n:错误号*/int test(bool* s1,bool* s2,int n){if(! inset(sym,s1)){error(n);/*当检测不通过时,不停获取符号,直到它属于需要的集合或补救的集合*/while((! inset(sym,s1))&&(! inset(sym,s2))){getsymdo;}}}/**编译程序主体**lev:当前分程序所在层*tx:名字表当前尾指针*fsys:当前模块后跟符号集合*/int block(int lev,int tx,bool* fsys){int i;int dx; /*名字分配到的相对地址*/int tx0; /*保留初始tx*/int cx0; /*保留初始cx*/bool nxtlev[symnum]; /*在下级函数的参数中,符号集合均为值参,但由于使用数组实现,传递进来的是指针,为防止下级函数改变上级函数的集合,开辟新的空间传递给下级函数*/dx=3;tx0=tx; /*记录本层名字的初始位置*/table[tx].adr=cx;gendo(jmp,0,0);if(lev > levmax){error(32);}do{if(sym==constsym) /*收到常量声明符号,开始处理常量声明*/{getsymdo;do{constdeclarationdo(&tx,lev,&dx); /*dx的值会被constdeclaration改变,使用指针*/while(sym==comma){getsymdo;constdeclarationdo(&tx,lev,&dx);}if(sym==semicolon){getsymdo;}else{error(5); /*漏掉了逗号或者分号*/}}while(sym==ident);}if(sym==varsym)/*收到变量声名符号,开始处理变量声名*/{getsymdo;do{vardeclarationdo(&tx,lev,&dx);while(sym==comma){getsymdo;vardeclarationdo(&tx,lev,&dx);}if(sym==semicolon){getsymdo;}else{error(5);}}while(sym==ident);}while(sym==procsym)/*收到过程声名符号,开始处理过程声名*/ {getsymdo;if(sym==ident){enter(procedur,&tx,lev,&dx);/*记录过程名字*/getsymdo;}else{error(4);/*procedure后应为标识符*/}if(sym==semicolon){getsymdo;}else{error(5);/*漏掉了分号*/}memcpy(nxtlev,fsys,sizeof(bool)*symnum);nxtlev[semicolon]=true;if(-1==block(lev+1,tx,nxtlev)){return -1;/*递归调用*/}if(sym==semicolon){getsymdo;memcpy(nxtlev,statbegsys,sizeof(bool)*symnum); nxtlev[ident]=true;nxtlev[procsym]=true;testdo(nxtlev,fsys,6);}else{error(5); /*漏掉了分号*/}}memcpy(nxtlev,statbegsys,sizeof(bool)*symnum);nxtlev[ident]=true;nxtlev[period]=true;testdo(nxtlev,declbegsys,7);}while(inset(sym,declbegsys)); /*直到没有声明符号*/code[table[tx0].adr].a=cx; /*开始生成当前过程代码*/table[tx0].adr=cx; /*当前过程代码地址*/table[tx0].size=dx; /*声明部分中每增加一条声明都会给dx增加1,声明部分已经结束,dx就是当前过程数据的size*/cx0=cx;gendo(inte,0,dx); /*生成分配内存代码*/if(tableswitch) /*输出名字表*/{printf("TABLE:\n");if(tx0+1>tx){printf("NULL\n");}for(i=tx0+1;i<=tx;i++){switch(table[i].kind){case constant:printf("%d const %s",i,table[i].name);printf("val=%d\n",table[i].val);fprintf(fas,"%d const %s",i,table[i].name);fprintf(fas,"val=%d\n",table[i].val);break;case variable:printf("%d var%s",i,table[i].name);printf("lev=%d addr=%d\n",table[i].level,table[i].adr);fprintf(fas,"%d var %s",i,table[i].name);fprintf(fas,"lev=%daddr=%d\n",table[i].level,table[i].adr);break;case procedur:printf("%d proc%s",i,table[i].name);printf("lev=%d addr=%d size=%d\n",table[i].level,table[i].adr,table[i].size);fprintf(fas,"%d proc%s",i,table[i].name);fprintf(fas,"lev=%d adr=%d size=%d \n",table[i].level,table[i].adr,table[i].size);break;}}printf("\n");}/*语句后跟符号为分号或end*/memcpy(nxtlev,fsys,sizeof(bool)*symnum);/*每个后跟符号集和都包含上层后跟符号集和,以便补救*/nxtlev[semicolon]=true;nxtlev[endsym]=true;statementdo(nxtlev,&tx,lev);gendo(opr,0,0); /*每个过程出口都要使用的释放数据段命令*/memset(nxtlev,0,sizeof(bool)*symnum); /*分程序没有补救集合*/test(fsys,nxtlev,8); /*检测后跟符号正确性*/listcode(cx0); /*输出代码*/return 0;}/**在名字表中加入一项**k:名字种类const,var or procedure*ptx:名字表尾指针的指针,为了可以改变名字表尾指针的数值*lev:名字所在的层次,以后所有的lev都是这样*pdx:为当前应分配的变量的相对地址,分配后要增加1*/void enter (enum object k,int *ptx,int lev, int *pdx){(*ptx)++;strcpy(table[(*ptx)].name,id); /*全局变量id中已存有当前名字的名字*/ table[(*ptx)].kind=k;switch(k){case constant: /*常量名字*/if (num>amax){error(31);num=0;}table[(*ptx)].val=num;break;case variable: /*变量名字*/ table[(*ptx)].level=lev;table[(*ptx)].adr=(*pdx);(*pdx)++;break; /*过程名字*/ case procedur:table[(*ptx)].level=lev;break;}}/**查找名字的位置*找到则返回在名字表中的位置,否则返回0**idt: 要查找的名字*tx::当前名字表尾指针*/int position(char * idt,int tx){int i;strcpy(table[0].name,idt);i=tx;while(strcmp(table[i].name,idt)!=0){i--;}return i;}/**常量声明处理*/int constdeclaration(int * ptx,int lev,int * pdx){if(sym==ident){getsymdo;if(sym==eql ||sym==becomes){if(sym==becomes){error(1); /*把=写出成了:=*/ }getsymdo;if(sym==number){enter(constant,ptx,lev,pdx);getsymdo;}else{error(2); /*常量说明=后应是数字*/}}else{error(3); /*常量说明标识后应是=*/ }}else{error(4); /*const后应是标识*/ }return 0;}/***/int vardeclaration(int * ptx,int lev,int * pdx){if(sym==ident){enter(variable,ptx,lev,pdx);],code[i].l,code[i].a);fprintf(fa,"%d %s %d %d\n",i,mnemonic[code[i].f],code[i].l,code[i].a); }}}/**语句处理*/int statement(bool* fsys,int * ptx,int lev) {int i,cx1,cx2;bool nxtlev[symnum];if(sym==ident){i=position(id,*ptx);if(i==0){error(11);}else{if(table[i].kind!=variable){error(12);i=0;}else{getsymdo;if(sym==becomes){}else{error(13);}memcpy(nxtlev,fsys,sizeof(bool)* symnum);expressiondo(nxtlev,ptx,lev);if(i!=0){gendo(sto,lev-table[i].level,table[i].adr);}}}}else{if(sym==readsym){getsymdo;if(sym!=lparen){error(34);}else{do{if(sym==ident){i=position(id, *ptx);}else{i=0;}if(i==0){error(35);}else{gendo(opr,0,16);gendo(sto,lev-table[i].level,table[i].adr); /* 储存到变量*/}getsymdo;}while (sym==comma); /*一条read语句可读多个变量 */}if(sym!=rparen){error(33); /* 格式错误,应是右括号*/while(!inset(sym,fsys))/* 出错补救,直到收到上层函数的后跟符号*/{}}else{getsymdo;}}else{if(sym==writesym) /* 准备按照write语句处理,与read类似*/{getsymdo;if(sym==lparen){do{getsymdo;memcpy(nxtlev,fsys,sizeof(bool)*symnum);nxtlev[rparen]=true;nxtlev[comma]=true; /* write的后跟符号为)or,*/expressiondo(nxtlev,ptx,lev);/* 调用表达式处理,此处与read不同,read为给变量赋值*/gendo(opr,0,14);/* 生成输出指令,输出栈顶的值*/}while(sym==comma);if(sym!=rparen){error(33);/* write()应为完整表达式*/}else{getsymdo;}}gendo(opr,0,15); /* 输出换行*/}else{if(sym==callsym) /* 准备按照call语句处理*/ {getsymdo;if(sym!=ident){error(14); /*call后应为标识符*/ }else{i=position(id,*ptx);if(i==0){error(11); /*过程未找到*/}else{if(table[i].kind==procedur){gendo(cal,lev-table[i].level,table[i].adr); /*生成call指令*/}else{error(15); /*call后标识符应为过程*/}}getsymdo;}}else{if(sym==ifsym) /*准备按照if语句处理*/{getsymdo;memcpy(nxtlev,fsys,sizeof(bool)*symnum);nxtlev[thensym]=true;nxtlev[dosym]=true; /*后跟符号为then或do*/conditiondo(nxtlev,ptx,lev); /*调用条件处理(逻辑运算)函数*/if(sym==thensym){getsymdo;}else{error(16); /*缺少then*/}cx1=cx; /*保存当前指令地址*/gendo(jpc,0,0); /*生成条件跳转指令,跳转地址暂写0*/statementdo(fsys,ptx,lev); /*处理then后的语句*/code[cx1].a=cx; /*经statement处理后,cx为then后语句执行完的位置,它正是前面未定的跳转地址*/}else{if(sym==beginsym) /*准备按照复合语句处理*/{getsymdo;memcpy(nxtlev,fsys,sizeof(bool)*symnum);nxtlev[semicolon]=true;nxtlev[endsym]=true;/*后跟符号为分号或end*//*循环调用语句处理函数,直到下一个符号不是语句开始符号或收到end*/statementdo(nxtlev,ptx,lev);while(inset(sym,statbegsys)||sym==semicolon){if(sym==semicolon){getsymdo;}else{error(10);/*缺少分号*/}statementdo(nxtlev,ptx,lev);}if(sym==endsym){getsymdo;}else{error(17); /*缺少end或分号*/}}else{if(sym==whilesym)/*准备按照while语句处理*/{cx1=cx; /*保存判断条件超作的位置*/getsymdo;memcpy(nxtlev,fsys,sizeof(bool)*symnum);nxtlev[dosym]=true;/*后跟符号为do*/conditiondo(nxtlev,ptx,lev); /*调用条件处理*/cx2=cx; /*保存循环体的结束的下一个位置*/gendo(jpc,0,0);/*生成条件跳转,但跳出循环的地址未知*/if(sym==dosym){getsymdo;}else{error(18); /*缺少do*/}statementdo(fsys,ptx,lev); /*循环体*/gendo(jmp,0,cx1);/*回头重新判断条件*/code[cx2].a=cx; /*反填跳出循环的地址,与if类似*/}else{memset(nxtlev,0,sizeof(bool)*symnum);/*语句结束无补救集合*/testdo(fsys,nxtlev,19);/*检测语句结束的正确性*/}}}}}}}return 0;}/**表达式处理*/int expression(bool*fsys,int*ptx,int lev){enum symbol addop; /*用于保存正负号*/bool nxtlev[symnum];if(sym==plus||sym==minus) /*开头的正负号,此时当前表达式被看作一个正的或负的项*/{addop=sym; /*保存开头的正负号*/getsymdo;memcpy(nxtlev,fsys,sizeof(bool)*symnum);nxtlev[plus]=true;nxtlev[minus]=true;termdo(nxtlev,ptx,lev); /*处理项*/if(addop==minus){gendo(opr,0,1); /*如果开头为负号生成取负指令*/ }}else /*此时表达式被看作项的加减*/{memcpy(nxtlev,fsys,sizeof(bool)*symnum);nxtlev[plus]=true;nxtlev[minus]=true;termdo(nxtlev,ptx,lev); /*处理项*/ }while(sym==plus||sym==minus){addop=sym;getsymdo;memcpy(nxtlev,fsys,sizeof(bool)*symnum);nxtlev[plus]=true;nxtlev[minus]=true;termdo(nxtlev,ptx,lev); /*处理项*/if(addop==plus){gendo(opr,0,2); /*生成加法指令*/ }else{gendo(opr,0,3); /*生成减法指令*/ }}return 0;}/**项处理*/int term(bool*fsys,int *ptx,int lev){enum symbol mulop; /*用于保存乘除法符号*/memcpy(nxtlev,fsys,sizeof(bool)*symnum) ;nxtlev[times]=true;nxtlev[slash]=true;factordo(nxtlev,ptx,lev); /*处理因子*/while(sym==times||sym==slash){mulop=sym;getsymdo;factordo(nxtlev,ptx,lev);if(mulop==times){gendo(opr,0,4); /*生成乘法指令*/ }else{gendo(opr,0,5); /*生成除法指令*/ }}return 0;}/**因子处理*/int factor(bool*fsys,int *ptx,int lev){int i;testdo(facbegsys,fsys,24); /*检测因子的开始符好号*/while(inset(sym,facbegsys)) /*循环直到不是因子开始符号*/{if(sym==ident) /*因子为常量或者变量*/{i=position(id,*ptx); /*查找名字*/if(i==0){error(11); /*标识符未声明*/}else{switch(table[i].kind){case constant: /*名字为常量*/gendo(lit,0,table[i].val); /*直接把常量的值入栈*/break;case variable: /*名字为变量*/gendo(lod,lev-table[i].level,table[i].adr); /*找到变量地址并将其值入栈*/break;case procedur: /*名字为过程*/error(21); /*不能为过程*/break;}}getsymdo;}else{if(sym==number) /*因子为数*/{if(num>amax){error(31);num=0;}gendo(lit,0,num);getsymdo;}else{if(sym==lparen) /*因子为表达式*/{getsymdo;memcpy(nxtlev,fsys,sizeof(bool)*symnum);nxtlev[rparen]=true;expressiondo(nxtlev,ptx,lev);if(sym==rparen){getsymdo;}else{error(22); /*缺少右括号*/}}testdo(fsys,facbegsys,23); /*银子后有非法符号*/}}}return 0;}/*条件处理*/int condition(bool* fsys,int* ptx,int lev){enum symbol relop;bool nxtlev[symnum];if(sym==oddsym) /*准备按照odd运算处理*/{getsymdo;expressiondo(fsys,ptx,lev);gendo(opr,0,6); /*生成odd指令*/ }else{memcpy(nxtlev,fsys,sizeof(bool)*symnum);nxtlev[eql]=true;nxtlev[neq]=true;nxtlev[lss]=true;nxtlev[leq]=true;nxtlev[gtr]=true;nxtlev[geq]=true;expressiondo(nxtlev,ptx,lev);if(sym!=eql&&sym!=neq&&sym!=lss&&sym!=leq&&sym!=gtr&&sym!=geq){error(20);}else{relop=sym;getsymdo;expressiondo(fsys,ptx,lev);switch(relop){case eql:gendo(opr,0,8);break;case neq:gendo(opr,0,9);break;case lss:gendo(opr,0,10);break;case geq:gendo(opr,0,11);break;case gtr:gendo(opr,0,12);break;case leq:gendo(opr,0,13);break;}}}return 0;} /*解释程序*/void interpret(){int p,b,t; /*指令指针,指令基址,栈顶指针*/struct instruction i; /*存放当前指令*/int s[stacksize]; /*栈*/printf("start pl0\n");t=0;b=0;p=0;s[0]=s[1]=s[2]=0;do{i=code[p]; /*读当前指令*/p++;switch{case lit: /*将a的值取到栈顶*/s[t]=;t++;break;case opr: /*数字、逻辑运算*/switch{case 0:t=b;p=s[t+2];b=s[t+1];break;case 1:s[t-1]=-s[t-1];break;case 2:s[t-1]=s[t-1]+s[t];break;case 3:t--;s[t-1]=s[t-1]-s[t];break;case 4:t--;s[t-1]=s[t-1]*s[t];break;case 5:t--;s[t-1]=s[t-1]/s[t];break;case 6:s[t-1]=s[t-1]%2;break;case 8:t--;s[t-1]=(s[t-1]==s[t]);break;case 9:t--;s[t-1]=(s[t-1]!=s[t]);break;case 10:s[t-1]=(s[t-1]<s[t]);break;case 11:t--;s[t-1]=(s[t-1]>=s[t]);break;case 12:t--;s[t-1]=(s[t-1]>s[t]);break;case 13:t--;s[t-1]=(s[t-1]<=s[t]);break;case 14:printf("%d",s[t-1]);fprintf(fa2,"%d",s[t-1]);t--;break;case 15:printf("\n");fprintf(fa2,"\n");break;case 16:printf("");fprintf(fa2,"");scanf("%d",&(s[t]));fprintf(fa2,"%d\n",s[t]);t++;break;}break;case lod: /*取相对当前过程的数据基地址为a的内存的值到栈顶*/ s[t]=s[base,s,b)+];t++;break;case sto: /*栈顶的值存到相对当前过程的数据基地址为a的内存*/ t--;s[base,s,b)+]=s[t];break;case cal: /*调用子程序*/s[t]=base,s,b); /*将父过程基地址入栈*/s[t+1]=b; /*将本过程基地址入栈,此两项用于base函数*/s[t+2]=p; /*将当前指令指针入栈*/b=t; /*改变基地址指针值为新过程的基地址*/p=; /*跳转*/break;case inte: /*分配内存*/t+=;break;case jmp: /*直接跳转*/p=;break;case jpc: /*条件跳转*/t--;if(s[t]==0){p=;}break;}}while (p!=0);}/*通过过程基址求上1层过程的基址*/int base(int l,int * s,int b){int b1;b1=b;while(l>0){b1=s[b1];l--;}return b1;}。

相关文档
最新文档