QT/C++實現有界面的詞法分析器——編譯原理

編譯原理源文件

一、實驗準備

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(按鈕名字,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()))
	// 重新打開文件顯示內容

五、打包發佈

詞法分析器安裝包
提取碼:ouxe

前幾天寫小遊戲,順便看了一下QT的打包
.exe文件打包爲安裝包程序

六、結

剛開始的時候沒有想好,搞了個內部表示碼出來,結果完全沒必要,還增加了一些額外的負擔,很多時候都的考慮內部標識碼的長度,比如刪除時就需要考慮內部表示碼的長度,才能跳過這一行的內容;
刪除增加的時候內部標識碼的值也很尷尬,不能重複我也沒判斷,刪除中間內容再添加時會打亂順序,害,難頂
文件用的是絕對路徑,用QT的資源路徑,不能及時處理,如添加關鍵字後,重新分析,剛剛添加的關鍵字就不會被識別未關鍵字而是標識符

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章