QT/C++实现有界面的词法分析器——编译原理

4 篇文章 0 订阅
4 篇文章 0 订阅

编译原理-词法分析器源文件

一、实验准备

1、实验环境

QT 5.9.7

2、.ui文件的设计

在这里插入图片描述

(1)输入区域文本框

为Input Widgets中的Text Edit
在这里插入图片描述

(2)结果区域文本框

为Display Widgets中的Text Browser
在这里插入图片描述

(3)开始按钮\结束按钮

为Buttons中的Push Button
在这里插入图片描述

(4)按钮的设置

右击按钮,选择转到槽,选择clicked()
在这里插入图片描述
也可以将”转到槽“功能变为自己用connect实现
connect函数的几种写法

connect(发送者,信号,接受者,槽函数)
connect(按钮名字,SIGNAL(clicked()),this,SLOT(槽函数))
  • 槽函数需要在mainwindow.h(不改名字,默认为mainwindow)中声明才能使用
  • 这是右击“转到槽”系统声明的槽函数
    在这里插入图片描述

二、整体流程

1、状态转换图

GC:取一个字符
D:数字
INT:数字
L:字母
ID:标识符
LOOKUP:一个查找函数,查找是否为关键字、分隔符、运算符
DELIM:分隔符
/:就是一个斜杠
SLA:slash,斜杠
COM:注释
COMEND:注释结束

在这里插入图片描述

2、程序部分

输入程序

将程序输入到TextEdit即“程序输入”区域,用ui->textEdit->toPlainText()方法获取整个程序保存到QString program中,再将QString转化为char字符数组program_char以字符的格式保存程序内容;

开始循环

  1. 当遇到空格和换行字符时,跳过这次循环;
  2. 进入判断标识符流程:
    1、首先判断首位是否为字母、下划线、$,是则将这个字符存入储存单词的word数组中,程序数组后移继续判断,然后进入while(true)判断是否为字母、下划线、$、数字,是则存入word数字中,不是则退出;
    2、判断是否为标识符,若为标识符则判断是否为关键字,然后进行输出;
  3. 判断是否为数字流程
    1、获取以数字开始的字符串。判断第一个字符是否为数字,当不为空格、结尾、换行、运算符、分隔符时,将字符存入存储数字的number数组中,程序数组后移继续判断,当遇到空格、结尾、换行、运算符、分隔符时退出循环;
    2、判断number数组中的值是否为数字,是数字则输出数字,不是则输出错误;
  4. 进入判断是否为注释流程,将注释过滤;
    1、当遇到/并且下一个是/*时,进入过滤注释;
    2、如果下一个为/即单行注释,program_char后移,当碰见换行时退出;
    3、如果下一个为*时即多行注释,program_char后移,当碰见*/时退出
  5. 进入判断运算符流程:
    调用函数在文件中查找判断是否为运算符,若是则程序数组后移,直接输出;
  6. 进入判断分隔符流程:
    调用函数在文件中查找判断是否为运算符,若为运算符则程序数组后移,直接输入;
  7. 进入判断是否ERROR流程:
    当遇到不是标识符、关键字、数字、运算符、分隔符时,则输出错误,程序后移回到过滤空格和换行符处。

判断是否为空格、结尾、换行、运算符、分隔符:

if(program_char[i] != ' ' && program_char[i] != '\0' && program_char[i] != '\n' && lexer.isSeparator(program_char[i])=="" && lexer.isOperator(program_char[i]) == "")

三、效果图

1、分析结果

在这里插入图片描述

2、打开文件,查看内容

在这里插入图片描述

3、添加关键字false

在这里插入图片描述

4、删除关键字true

在这里插入图片描述

四、功能实现

1、判断是否为标识符、关键字

(1)判断是否为一个字母、下划线、$;若是则存入word数组中,并将juge_letter置为true;

if(lexer.isLetter(program_char[i]) || program_char[i] == '_' || program_char[i] == '$'){
	int j = 0;
	word[j] = program_char[i];  // 将第一个字符存入
	juge_letter = true;

(2)进入while循环,判断第二个及以后的字符,若为字母、_$、数字时将其存入word数组中,若不是则退出循环;

	while(1){
                i++;j++;
                if(lexer.isLetter(program_char[i]) || program_char[i] == '_' || program_char[i] == '$' || lexer.isInteger(program_char[i]))
                    word[j] = program_char[i];
                else
                    break;
        }
}

当juge_letter为true即是标识符时,调用isKeyword函数判断是否为关键字;若返回值code为空则代表为在文件中找到此标识符,若不为空则代表在keywords.txt找到关键字;

if(juge_letter == true){
	code = lexer.isKeyword(word);
        if(code != ""){
		result = "(1 ," + code + ")    ";
                result = result + word + " -------- 关键字";
                ui->textBrowser->append(result);
	}
	else {
		result = "(2 , *)    ";
                result = result + word + " ------- 标识符";
                ui->textBrowser->append(result);   // 将结果显示在结果区域
	}
}
2、isKeywords函数

(1)将储存标识符的字符数组作为参数;将字符数组转化为QString;

// 将字符数组变为QString
QString juge_keyword;   // 程序中的字母
for(int i = 0;;i++){
	if(word[i] == '\0')
            break;
        juge_keyword = juge_keyword + word[i];
}

(2)创建file对象,调用lookUp函数,将文件路径,标识符作为参数在文件中查找关键字;

File_operate file;    
QString filePath = "E://Programing//QT//Lexer//Lexer//LookUp//keywords.txt"; 
return file.lookUp(filePath,juge_keyword);}
3、判断是否为一个数字

(1)当首字母是一个数字时进入循环,当未遇到空格、换行、运算符、分隔符时,将内容存入number数组中;若遇见则退出循环;

if(lexer.isInteger(program_char[i])){
	int j = 0;
	// 获取以数字开始的字符串,存入number数组中
	while(1){
		if(program_char[i] != ' ' && program_char[i] != '\0' && program_char[i] != '\n' && lexer.isSeparator(program_char[i])=="" && lexer.isOperator(program_char[i]) == ""){
			number[j] = program_char[i];
			j++; i++;
                }
                else
                    break;
            }

(2)因为获得的number中有内容可能不为数字,需要判断数组中的值是否为数字;若在number数组中碰见不是数字则将juge_integer置为false并退出循环;若一直到数组结尾均为数字则将juge_integer置为true并退出循环;

	j = 0;
	while(lexer.isInteger(number[j])){
		j++;
                if(!lexer.isInteger(number[j]) && number[j] != '\0'){ // 其中有不为数字 并且 不是数组结尾('\0')不为数字的情况,即不是数字(9main)
                    juge_integer = false;
                    break;
                }
                if(number[j] == '\0'){        // 遇到这个以数字开头的字符数组结尾时还没有碰到不是数字的情况,即为数字(999)
                    juge_integer = true;
                    break;
                }
            }

(3)判断juge_integer的值,输出对应内容;

4、过滤注释

(1)当当前字符为“/”并且后一个字符为“/”或“*”时,调用Filter函数返回i值跳过注释部分;

if('/' == temp_word[i] && ('/' == temp_word[i+1] || '*' == temp_word[i+1]))
	i = lexer.Filter(temp_word,i,counter);

(2)Filter函数
当后一个字符为/即单行注释时,i值加1,程序后移,当遇到程序结束或在遇到换行时,返回i值,即单行注释结束;

if('/' == program_char[i+1]){         	 // 第二个字符为‘/’-----单行注释
        for(;i < counter;i++){
            if('\n' == program_char[i])  // 单行注释遇到‘\n’结束
                break;
        }
    }

当后一个字符为*即多行注释时,i值加2跳过起始的/*,i值加1,程序后移;在遇到*/时,i值加2,跳过*/,返回i值;

if('*' == (program_char[i+1])){  // 多行注释
	i = i+2;   		// 跳过‘/*’
        for(;i < counter;i++){  // 直到遇到‘*/’时结束
            if('*' == (program_char[i]) && '/' == (program_char[i+1])){
                i = i+2;    	// 跳过‘*/’
                break;
            }
        }
    }
4、判断是否为运算符、分隔符
  1. 将值传入isOperator或者isSeparator函数
code =lexer.isOperator(temp_word[i]))
code = lexer.isSeparator(temp_word[i]))
  1. 将内容转化为QString,将文件路径和内容作为参数传入file的LookUp函数,在文件中查找运算符和关键字
 file.lookUp(filePath,juge_operator);
 file.lookUp(filePath,juge_separator);
5、lookUp()函数

(1)读取文件中的内容。以只读的方式打开文件,调用readAll()函数将文件里的所有内容全部读取出来,赋值给content,再用data()函数将content转化为file字符数组,最后关闭文件;

QFile file_temp(filePath);      
file_temp.open(QIODevice::ReadOnly); // 以只读方式打开      
QByteArray content = file_temp.readAll();      
char *file = content.data();      
file_temp.close();

(2)遍历文件内容,查找关键字,当遇到数组结尾时退出返回code;

for(int i = 0;;i++){         
	temp_word = ""; 
	code = "";         
	if(file[i] == '\0')            
		break;

文件中的格式:内容+空格+内部标识码+换行
(3)当碰见不为空格或换行时,将字符写入temp_word字符数组中,遇到空格或换行代表关键字、运算符或分隔符的内容结束;

	while(file[i] != ' ' && file[i] != '\n'){            
		temp_word = temp_word + file[i];            
		i++;        
	}

(4)根据文件内容的格式,空格后一位为内部表示码的开始,将当前字符存入code字符数组中,后移当遇到换行或数字结束时退出;

	// 获取内部表示码        
	if(file[i] == ' '){           
		while(file[i] != '\n' && file[i] != '\0'){
			code = code + file[i];
			i++;            
		}        
 	}	  

(5)将从文件中获取的内容temp_word与需要匹配的内容word进行比较;

  1. 若相等则退出并返回内部表示码code;
  2. 若遇到数组结尾则表示未在文件中找到对应的值,将code重置,退出返回为空的code;
  3. 若没有找到相等且未到数组结尾则继续循环直到找到或到达结尾。
	if(temp_word == word || file[i] == '\0'){            
		if(temp_word == word)                
			break;            
		else {     // 到结束未找到,重置code                
			code = "";
			break;            
		}        
	}    
}
return code;
6、查看关键字、运算符、标识符

(1)获取文件的路径,若文件路径为空则提示错误;

QString filePath = QFileDialog::getOpenFileName(this,"选择文件","E:\\Programing\\QT\\Lexer\\Lexer\\LookUp","(*.txt)");    
if(filePath.isEmpty()){        
	QMessageBox::warning(this,"Failed!","文件路径为空!");       
	return;    
 }

(2)只读的方式打开文件,用readAll()函数将文件内容全部读取到content中,再将结果输出到显示文本框中,关闭文件。

QFile file(filePath);    
file.open(QIODevice::ReadOnly); // 以只读方式打开    
QByteArray content = file.readAll();    
content = "文件内容:\n" + content;    
ui->textBrowser->setText(content);  // 显示数据   
file.close();
7、添加关键字、运算符、标识符

(1)创建添加模态对话框;创建文件名、内容和内部表示码的提示文本QLabel和输入文本框QEditLabel;

QDialog *dlg = new QDialog;	// 创建模态对话框
QLabel *input_name = new QLabel("文件名:(keywords|operators|separators).txt");	// 创建文件名的提示label
QLineEdit *name_line = new QLineEdit;	// 创建文件名的输入label
// 创建内容和内部提示码的提示label和输入label
...

(2)创建确认按钮,使用connect函数实现点击确认按钮实现将输入内容添加到指定文件中;

connect(ensure_btn, &QPushButton::clicked, [=](){
	// 获取文件名、内容、内部表示码三个LineEdit的值;     
	QString name = name_line->text();     
	QString value = value_line->text();     
	QString code = code_line->text();
	// 将输入的内容转化为QByteArray格式;     
	QByteArray content = ("\n" + value + " " + code).toLatin1();

(3)如果文件名、内容、内部表示码为空则提示错误,反之则判断文件名输入是否正确;

	if(name != "" && value != "" && code != ""){     
		if(name == "keywords.txt" || name == "operators.txt" || name == "separators.txt"){
			QString filePath;       
			// 将文件名加入文件路径中
			filePath = "E://Programing//QT//Lexer//Lexer//LookUp//" + name;     
			QFile file(filePath);       
			file.open(QIODevice::Append); // 追加到末尾方式打开文件       
			if(file.write(content))		// 将内容写入文件中
	                    QMessageBox::warning(dlg,"Success!","添加成功!");
	                else
	                    QMessageBox::warning(dlg,"Failed!","添加失败!");
	
	                dlg->close();   // 关模态对话框
	                file.close();
	
	                // 重新打开文件,显示内容
	                QFile alter_file(filePath);
	                alter_file.open(QIODevice::ReadOnly);
	                QByteArray alter_content = alter_file.readAll();
	                alter_content = "添加后的文件内容:\n" + alter_content;
	                ui->textBrowser->setText(alter_content);
	                alter_file.close();
		}
		else {
	                QMessageBox::warning(dlg,"Failed!","文件名输入错误!");
	                return;
	            }
	else {
		QMessageBox::warning(dlg,"Failed!","还有值未输入!");
		return;
	}
});

(4)取消按钮

QPushButton *cancel_btn = new QPushButton;
connect(cancel_btn, &QPushButton::clicked, [=](){
	dlg->close();
});
dlg->exec(); // 阻塞
8、删除关键字、运算符、标识符

(1)创建添加模态对话框;创建文件名、内容和内部表示码的提示文本QLabel和输入文本框QEditLabel;创建确认按钮,使用connect函数实现点击删除按钮实现将内容在指定文件中删除;

connect(ensure_btn, &QPushButton::clicked, [=](){

(2)获取文件名、内容的值;将文件名添加到路径中,以只读的方式打开文件,将文件中所有的内容读取出来放入字符数组file_content中,将juge置为false;

	...
	char *file_content = file.readAll().data();
	bool juge = false; // 判断是否在文件中找到
	...

(3)计算数组的长度并在文件中查找需要删除的内容;

  1. 遍历文件,当当前字符不为空格且不为换行时,将其存入temp_word数组中,计数单词长度的word_length加1;后移当遇见空格或换行时退出循环;
  2. 当在文件中找到需要删除的内容时(temp_word == value),将juge赋值为true并退出;若到数组结尾则直接退出;
	while(file_content[counter] != '\0'){    // 计算长度
		counter++;  
	}
	// 遍历文件,在文件中查找需要删除的内容,
	for(i = 0;i < counter;i++){    
		word_length = 0;
		QString temp_word = ""; 
		// 获取文件中的内容并计算长度     
		while(file_content[i] != ' ' && file_content[i] != '\n'){    
			temp_word = temp_word + file_content[i];       
			i++;word_length++;    
		}
		// 如果在文件中找到与输入的值相等,将juge置为true并退出
		// 若到数组结尾则直接退出    
		if(temp_word == value || file_content[i] == '\0'){      
			if(temp_word == value)        
				juge = true;     
			break;   
		}
	}

(4)将需要删除行的内容跳过,其余写入文件中;

  1. 当juge为true,即在文件中找到时;计算对应的内部表示码长度;
  2. 当到达需要删除的内容时,此时i在空格处,i-word_length回到行首,i+len+1跳过内部标识码和换行,将所在行的内容跳过,其余内容存入content中;
  3. 以重写的方式打开文件,用write()函数将内容写入文件;然后重新打开文件,将内容读出写入到结果区域中。
	if(juge == true){
	// 计算code的长度
		File_operate operate;
		QString code = operate.lookUp(filePath,value);
		char* code_length = code.toLatin1().data();
		int len;
		for(len = 0;'\0' != code_length[len];len++){/* 啥也不干 */}
		len--;  // 去掉多余的一个
		
		QString content = "";  
		for(int j = 0;j < counter;j++){
		// 跳过需要删除行的内容,此时i在空格处,i-word_length回到行首,i+len+1跳过内部标识码和换行
		if((j >= (i - word_length) && j <= i+len+1 || file_content[j] == '\r')       
			continue;     
		content += file_content[j];
	}
	file.open(QIODevice::ReadWrite|QIODevice::Truncate);  
	if(file.write(content.toLatin1()))
	// 重新打开文件显示内容

五、打包发布

词法分析器安装包
提取码:p8qs

前几天写小游戏,顺便看了一下QT的打包
.exe文件打包为安装包程序

六、结

刚开始的时候没有想好,搞了个内部表示码出来,结果完全没必要,还增加了一些额外的负担,很多时候都的考虑内部标识码的长度,比如删除时就需要考虑内部表示码的长度,才能跳过这一行的内容;
删除增加的时候内部标识码的值也很尴尬,不能重复我也没判断,删除中间内容再添加时会打乱顺序,害,难顶
文件用的是绝对路径,用QT的资源路径,不能及时处理,如添加关键字后,重新分析,刚刚添加的关键字就不会被识别未关键字而是标识符

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值