文章目錄
一、實驗準備
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、首先判斷首位是否爲字母、下劃線、$
,是則將這個字符存入儲存單詞的word數組中,程序數組後移繼續判斷,然後進入while(true)判斷是否爲字母、下劃線、$
、數字,是則存入word數字中,不是則退出;
2、判斷是否爲標識符,若爲標識符則判斷是否爲關鍵字,然後進行輸出; - 判斷是否爲數字流程
1、獲取以數字開始的字符串。判斷第一個字符是否爲數字,當不爲空格、結尾、換行、運算符、分隔符時,將字符存入存儲數字的number數組中,程序數組後移繼續判斷,當遇到空格、結尾、換行、運算符、分隔符時退出循環;
2、判斷number數組中的值是否爲數字,是數字則輸出數字,不是則輸出錯誤; - 進入判斷是否爲註釋流程,將註釋過濾;
1、當遇到/
並且下一個是/
或*
時,進入過濾註釋;
2、如果下一個爲/
即單行註釋,program_char後移,當碰見換行時退出;
3、如果下一個爲*
時即多行註釋,program_char後移,當碰見*/
時退出 - 進入判斷運算符流程:
調用函數在文件中查找判斷是否爲運算符,若是則程序數組後移,直接輸出; - 進入判斷分隔符流程:
調用函數在文件中查找判斷是否爲運算符,若爲運算符則程序數組後移,直接輸入; - 進入判斷是否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、判斷是否爲運算符、分隔符
- 將值傳入isOperator或者isSeparator函數
code =lexer.isOperator(temp_word[i]))
code = lexer.isSeparator(temp_word[i]))
- 將內容轉化爲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
進行比較;
- 若相等則退出並返回內部表示碼code;
- 若遇到數組結尾則表示未在文件中找到對應的值,將code重置,退出返回爲空的code;
- 若沒有找到相等且未到數組結尾則繼續循環直到找到或到達結尾。
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)計算數組的長度並在文件中查找需要刪除的內容;
- 遍歷文件,噹噹前字符不爲空格且不爲換行時,將其存入temp_word數組中,計數單詞長度的word_length加1;後移當遇見空格或換行時退出循環;
- 當在文件中找到需要刪除的內容時(
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)將需要刪除行的內容跳過,其餘寫入文件中;
- 當juge爲true,即在文件中找到時;計算對應的內部表示碼長度;
- 當到達需要刪除的內容時,此時i在空格處,i-word_length回到行首,i+len+1跳過內部標識碼和換行,將所在行的內容跳過,其餘內容存入content中;
- 以重寫的方式打開文件,用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的資源路徑,不能及時處理,如添加關鍵字後,重新分析,剛剛添加的關鍵字就不會被識別未關鍵字而是標識符