编译原理课程设计———Simple PASCAL编译器的设计与实现

 

 设计并实现一个简单Pascal语言的编译器。主要包括几个模块:

词法分析模块

-------------------2020/05/29 备注:  本菜鸟只实现了这一个模块----------------------------------------

链接:https://pan.baidu.com/s/1Z2-O-3S4q0CFMfeI5uIzfg 
提取码:256x
------------------------------------------------------------------------------------------------------------

语法分析模块

语义分析模块

 1.Simple-PASCAL语言语法

源程序设计语言 G[<语句表>]    

参考编译原理教材179 上机实习题

<语句表><语句> | <语句>;<语句表>

<语句>→<赋值语句>|<条件语句>||<复合语句>

<赋值语句>→<变量>:=<算术表达式>

<条件语句>→IF<关系表达式>THEN<语句>ELSE<语句>

语句>→WHILE<关系表达式>DO<语句>

<复合语句>→BEGIN<语句表>END

<算术表达式>→<项>|<算术表达式>+<项>|<算术表达式>-<项>

<项>→<因式>|<项>*<因式>|<项>/<因式>

<因式>→<变量>|<常数>|(<算术表达式>)

<关系表达式>→<算术表达式><关系符><算术表达式>

<变量>→<标识符>

<标识符>→<标识符><字母>|<标识符><数字>|<字母>

<常数>→<整数>

<整数>→0|<非零数字><泛整数>

<泛整数>→<数字>|<数字><泛整数>|ε

<关系符>→<|<=|==|>|>=|<>

<字母>

→A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z

<非零数字>→1|2|3|4|5|6|7|8|9

<数字>→<非零数字>|0

注释:从接连出现的/*到下一次接连出现的*/之间的任何文字都是注释。

      从某行接连出现的//到该行的结尾的任何文字都是注释。

白空格:两个单词之间的任何空格,制表符,回车,换行都是白空格,除了用来分隔单词以外,没有意义。

限制条件:标识符的长度不大于8,整数不大于65535。

练习1写出符合上面文法的两个不同源程序,要求基本覆盖所有产生式,包含两种注释, 包含若干白空格, 满足限制条件。

 

1)什么是源程序?

     需要被处理的程序叫做源程序。

     转换程序的输入时源程序。

     在这里,源程序就是使用语句表文法编写的程序

2)什么是文本文件?

   文件每个字节或者若干个字节表示一个字符的文件。

   众所周知,一般情况下,源文件编写完毕以后,保存为特定后缀名的文本文件。

   本次课程设计,所有的源文件后缀名约定为:YJB

   这里强调 本次课程设计要求处理保存在文本文件中的源程序。

练习2将练习1写好的源程序输入计算机,用1.YJB作为文件名保存

2. 词法分析

词法分析任务

主要设计内容(二选一):

(1)利用给出的类PASCAL文法,写出符合文法的两个不同源程序样例;设计相应单词的有限自动机并进行化简;根据简化的有限自动机设计并实现词法分析器,将写好的源程序作为输入,进行词法分析并给出分析结果。

(2)利用给出的类PASCAL文法,写出符合文法的两个不同源程序样例;以Flex、bison及C语言等工具编写的可被正确编译运行的源程序,将写好的源程序作为输入,进行词法分析并给出分析结果。(参考教材93页)

1、词法分析的“处理”做什么事情?

   1)处理文件

读取源文件的内容

标准套路,(参考C语言的文件操作函数,fopen函数等)

 

     2)把源文件的内容分解为词法单元

       词法单元就是单词。

       单词有类别(本次课程设计,请设计好单词的分类,参考教材79页):

标识符为1类:如ABC,XYZ,C

整数设为2类:如123,900

IF为3类

THEN为4类

ELSE, WHILE, DO, BEGIN, END…….

运算符+, -, *, /,>, <, =, >= , <=, <>, :=为101/102……

分隔符类: ;,(,)为201/201…….

白空格不是单词

注释以及注释标记不是单词

      练习3 手工把练习1写好的源程序分解为单词类别和单词词文组成的二元式流

2、怎样描述词法单元:正规文法,正规表达式

 上面给出的语句表文法实际上是该语言的BNF,其中包了单词描述和文法描述。需要从中提取出描述单词的正规文法或者正规表达式。

   采用正规文法:

<保留字>→IF|THEN|……

       <整数>→ 0|1<泛整数>|2<泛整数>

  采用正规表达式:

          (A|…|Z)(A|…|Z|0|…|9)*|(0|…|9)|(1|…|9)(0|…|9)*|IF|THEN|………….

练习4 写出描述语句表语言的词法单元的正规文法或者正规表达式(参考教材80页)

3、怎样分解词法单元:有穷自动机,递归分析程序

   怎样编写词法分析程序:

1)预处理:把源文件一个字符一个字符的读入词法分析程序设置的输入字符结构体数组中(输入缓冲区),读入过程要删除注释,删除多余的白空格.

2)输入缓冲区要记录每个符号的ASCII码,以及该符号在源程序中的坐标值(行号和列号)       

输入符号结构体数组:

            struct  inchar

            {

               char input;

               int  x,y;

            }INCHAR[];

练习5 编写预处理函数,从源文件得到输入符号流,要求按照上面的形式保留每个符号的坐标

3)从源程序字符数组中获得单词, 编码为二元式.

二元式采用结构体数组存储, 把单词类型和词元记录下来

例如:

   struct dual

   {  int dual_type;

      union {

char lexeme_text[];

int  lexeme_num[];

} lexeme

} DUAL[]

 

4)分解单词的方法:

  1. 利用case多路转换语句根据单词的特点直接编写。
  2. 通过描述单词的正规文法得到相应的有穷自动机,通过case多路转换语句完成有穷自动机的处理流程。参考课本P78的例子
  3. 将有穷自动机转换为状态矩阵,构造状态矩阵驱动程序完成有穷自动机的流程。参考课本p55例子。

    4、编写词法分析程序要注意的问题

    1)检查词法是否有错误

    检查是否有非法字符:如a, aBC, @, &, !

    检查是否有非法单词:如12A, BC_1,

    检查标志符和数字是否满足限制条件

    检查注释符号是否配对          

           

    2)界符分隔单词

    能够区分两个单词的符号为界符

    有些界符不是单词:如白空格

    有些界符仅仅用来分隔:如;

    有些界符本身还是源程序不可缺少的单词,如(, ), +, /, 等等

    有些界符包含两个字符:如<>, >=等等

     

    3)输出词法错误

    如果没有错误,返回0

    如果有错误,需要报告词法错误在源程序中的位置。并且,要能够越过错误,分解下一个单词,直到源程序结束。

     

    4) 输出的二元式流保存在二元式结构体数组中,供语法分析使用。

    为了方便接下来的语法分析能够定位语法错误在源程序中的位置,需要在二元式结构体数组中保留每个单词在源程序中的坐标(单词的首字母所在的行号列号)。

        实际的二元式结构体数组应该是:

            struct dual

       {  int dual_type;

          union {

    char lexeme_text[];

    int  lexeme_num[];

    } lexeme

     

    int x, y;

     

    } DUAL[]

     

       我们还是把它叫做二元式

              

    练习6  编写词法分析程序,从输入符号流得到二元式流(参考教材81页算法)

    词法出错提示格式:

      Error type [错误类型] at line[行号]:说明文字

    错误类型:A(词法错误)

     

    5、词法分析程序的总结

     1)预处理函打开源文件,读取源程序内容到设置好的字符数组(输入缓冲区),这个过程要删除注释,删除多余的白空格,所有的白空格最好都转为单个空格。预处理得到了字符流。

    2)预处理后关闭源文件,对输入缓冲区进行词法处理:分解输入字符流,得到的二元式存入二元式结构体。词法处理以后得到二元式流

    3)错误处理要完善,特别是坐标准确

以上内容是课程设计指导书的部分内容(我只实现了编译器的词法分析部分)

 

词法分析程序设计及实现:

1、单词符号类别码

单词符号

类别码

助记符

说明

$

0

FINISH

结束符

BEGIN

1

BEGIN

保留字

IF

2

IF

保留字

WHILE

3

WHILE

保留字

THEN

4

THEN

保留字

DO

5

DO

保留字

END

6

END

保留字

ELSE

7

ELSE

保留字

VAR

8

VAR

保留字

INTEGER

9

INTEGER

保留字

整数

11

INT

整数不大于

65535

标识符

10

ID

标识符长度

不大于8

+

101

ADD

 

-

102

SUB

 

*

103

MUL

 

/

10

DIV

 

:

105

COLON

 

:=

106

COL_EQ

赋值符号

<

107

LT

 

<>

108

NE

不等于符号

<=

109

LE

 

>

110

GT

 

>=

111

GE

 

=

112

EQ

 

;

201

SEMI

 

(

202

LP

 

)

203

RP

 

,

204

COMMA

  1.  

2、源程序

写出符合上面文法G[<语句表>] 的两个不同源程序,要求基本覆盖所有产生式,包含两种注释, 包含若干白空格, 满足限制条件。

 

源程序test1.txt:

BEGIN

    1A := 200; /*Program Passed1;Var A,B,B3Y,C12A1,DD : integer;*/

    B3Y := 100 ;

    IF@ A > B3Y THEN

        A := A + B3Y

    ELSE

        C12A1 := 2 * A;

    DD := C12A1 + 100;

    IF 2 < B THEN

        IF DD <> C12A1 THEN

            C12A1 := C12A1 + C12A1 * 4;

        ELSE

            A := 0;

    ELSE

        BEGIN

            A := 2 * 2 * 4;

            B := 300 ;

        END

END $

源程序test2.txt:

BEGIN

  VAR A,B,C : INTEGER;//A,B,C is int

  A := 10;

  BDEFGHHIJ := 800000;

  IF A > B3 THEN

  A := A + B;

  ELSE

  B := aB + A;

  DO

  C_1 := A + 1;

  WHILE C <= 100;

END $

3、数据结构

从源程序获取的单词,编码为二元式。二元式采用结构体数组进行存储,把单词类型,词元和坐标都记录下来

4、单词识别方法和识别流程

使用C标准库 ctype.h中的字符分类函数来识别从文件读取的每一个字符,保存到临时的存储数组,然后利用case多路转换语句根据单词的特点识别单词类别,最后将合法的单词存储到二元式结构体数组中。

编译原理课程设计———Simple PASCAL编译器的设计与实现_第1张图片 单词符号识别流程图

 

5、单词符号分类

符号 编号 助记符
结束符 0 FINISH
BEGIN 1 GEGIN
IF 2 IF  
WHILE 3 WHILE
THEN 4 THEN
DO 5 DO  
END 6 END  
ELSE 7 ELSE  
VAR 8 VAR  
INTEGER 9 INTEGER
整数 11 INT  
标识符 ID 10 ID  
 + 101 ADD  
  -   102 SUB  
* 103 MUL  
/ 104 DIV  
: 105 COLON
:= 106 COL_EQ
< 107 LT  
<> 108 NE  
<= 109 LE  
> 110 GT  
>= 111 GE  
= 112 EQ  

6、附上代码吧。

pascal.h

//二元组结构体数组 

struct pascal{ 
	int type;//类别码 
	union {
	char text[10];//值 
	int num;
	}lexeme;
	int x, y;//行号、列号 
};
extern pascal P[200];

extern int num2;//二元组结构体数组下标 

int TESTscan();//词法分析函数 
int TESTparse();//语法分析函数 

scan.cpp

#include 
#include "pascal.h"
#include  
#include  
#include 
using namespace std;  
 //临时二元组结构体数组
struct dual{ 
	int dual_type;//类别码 
	union {
	char lexeme_text[10];//值 
	char lexeme_num[10];
	}lexeme;
	int x, y;//行号、列号 
}DUAL[200];
int num=0; //临时二元组结构体数组下标
 
extern pascal P[200];
extern int num2;
int num2=0;
//保留字表
const char  *keyword[] = {"BEGIN","IF","WHILE","THEN","DO","END","ELSE","VAR","INTEGER"};

//单分界符 
char singleword[50]="+-*/();,=";

//双分界符首字符 
char doubleword[10]="><:"; 

//接收输入文件名 
char Scanin[100];

using namespace std;  
//指向输入文件的指针 
FILE *fin; 

//词法分析函数
int TESTscan() {

char ch,temp;//ch:每次读入的字符,token
int j,n;//j数组下标,n 中间变量 
int row=1;//行号 
int clumn=1;//列号 
int es=0;//es错误代码,0表示无错误 


cout<<"请输入源程序文件名(包括路径):";
cin>>Scanin;
fin=fopen(Scanin,"r");

if(fin==NULL){//判断输入文件名是否正确 
	cout<<"\n打开词法分析输入文件出错!"<8){
		cout<<"WARNING:标识符超长"<65535)
			DUAL[num].dual_type=-7;	
			else
			DUAL[num].dual_type=11;
		}
		
		
		if(DUAL[num].dual_type==11)
		{
			cout<<"["<0)//单分界符处理
	{ 
	    int isZhuShi=0; 
		switch(ch){
			case '+' :
				DUAL[num].dual_type=101;
				DUAL[num].lexeme.lexeme_text[0]=ch;
				DUAL[num].lexeme.lexeme_text[1]='\0';
				DUAL[num].x=row;
				DUAL[num].y=clumn++;
				cout<<"["<0){//双分界符处理 
		DUAL[num].lexeme.lexeme_text[0]=ch;
		DUAL[num].y=clumn++;
		DUAL[num].x=row;
		char ch2=ch;
		ch=getc(fin);
	
		switch(ch2) {
			case ':':
				if(ch=='='){// 是双分界符:= 
				DUAL[num].lexeme.lexeme_text[1]=ch;	
				DUAL[num].lexeme.lexeme_text[2]='\0';
				DUAL[num].dual_type=106;
				clumn++; 
				
				}else{// 是单分界符:
					DUAL[num].dual_type=105;
					DUAL[num].lexeme.lexeme_text[1]='\0';
				}break;
			case '>':
					if(ch=='='){// 是双分界符>= 
				DUAL[num].lexeme.lexeme_text[1]=ch;	
				DUAL[num].lexeme.lexeme_text[2]='\0';
				DUAL[num].dual_type=111;
				clumn++; 
				
				}else{//单分界符>
					DUAL[num].dual_type=110;
					DUAL[num].lexeme.lexeme_text[1]='\0';
				}break;
			case '<':
					if(ch=='='){//是双分界符<=
				DUAL[num].lexeme.lexeme_text[1]=ch;	
				DUAL[num].lexeme.lexeme_text[2]='\0';
				DUAL[num].dual_type=109;
				clumn++; 
				
				}
				else if(ch=='>')
				{//是双分界符<>
					DUAL[num].lexeme.lexeme_text[1]=ch;	
				    DUAL[num].lexeme.lexeme_text[2]='\0';
				    DUAL[num].dual_type=108;
				    clumn++; 
				
				}
				else{//单分界符<
					DUAL[num].dual_type=107;
					DUAL[num].lexeme.lexeme_text[1]='\0';
				}			
		}
		
		int type=DUAL[num].dual_type;
		switch(type){
			case 105:
				cout<<"["<

main.cpp

#include 
#include "pascal.h"
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
using namespace std; 

extern pascal P[200];
extern int num2; 
int main(int argc, char** argv) {
	
int i;
int es=0;
	es=TESTscan();
	if(es>0)
	cout<<"词法分析出错!编译停止。"<

比起词法,语法分析更难了一点。(没有完成整个语法模块,就不发出来了。只做了词法分析,本渣渣的课设也过了。浓浓师生情啊),扩广文法,求FOLLOW集还是比较好做的,麻烦的是自动机的构造。要是哪一步写错,改起来要改动多个项目集。构造出了自动机,构造SLR分析表的时候就需要仔细的填写对应的值了。

你可能感兴趣的:(课程设计)