文章目录
一、实验准备
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、首先判断首位是否为字母、下划线、$
,是则将这个字符存入储存单词的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()))
// 重新打开文件显示内容
五、打包发布
词法分析器安装包
提取码:p8qs
前几天写小游戏,顺便看了一下QT的打包
.exe文件打包为安装包程序
六、结
刚开始的时候没有想好,搞了个内部表示码出来,结果完全没必要,还增加了一些额外的负担,很多时候都的考虑内部标识码的长度,比如删除时就需要考虑内部表示码的长度,才能跳过这一行的内容;
删除增加的时候内部标识码的值也很尴尬,不能重复我也没判断,删除中间内容再添加时会打乱顺序,害,难顶
文件用的是绝对路径,用QT的资源路径,不能及时处理,如添加关键字后,重新分析,刚刚添加的关键字就不会被识别未关键字而是标识符