文章目录
0 写在前边
最近在对OJ系统后台数据库中的源代码作预处理,方便后续做一些操作,在这个过程中遇到了一些问题,部分问题也没有搜索到,把思路汇总下来:
1 需求
前期的预处理要求主要如下:
- 删除多余空行 :便于后续对代码文本进行比对,需要把多余空行删去;
- 删除代码行中的注释:注释对于当前阶段的需求任务,暂时没有太大用处,所以也需要作为干扰项给删除掉,其中需要处理的注释包含单行注释"//…“和块注释”/…/";
- 单行多条语句处理为每条一行:学生在刚开始学习写代码的时候,代码不够规范,可能会将多条语句写在一行里,为了避免干扰,使得代码更加直观,需要将写在一行内的多条语句处理为每行一条语句;
2 实现
2.1 删除代码行中的注释
2.1.1 思路
最初实现之前,问了一下ChatGPT,它提供的实现思路是利用python的正则表达式,分别去除单行注释"//…“和块注释”/…/",代码如下:
// ChatGPT给的答案
lines = [re.sub(r'//.*', '', line) for line in lines]
lines = [re.sub(r'/\*.*?\*/', '', line) for line in lines]
但是后续实际测试的时候,单行注释能够顺利删除,但是对于块注释只能处理位于同一行内的,也即是:
// 单行注释可以顺利去除
/* 对于块注释,位于同一行内的注释可以删除 */
/* 对于位于不同行的块注释,
* 类似于这样
* 并不能成功处理
*/
所以就很无语,后来通过搜索,借鉴了一篇博客,通过设置状态位记录当前注释的情况,进而用于处理块注释,与上边ChatGPT结合了一下,成功实现了可以删除C/C++代码段中的行注释与块注释。
2.1.2 实现代码
# 删除在同一行内的//注释和/*...*/注释
lines = [re.sub(r'//.*', '', line) for line in lines]
lines = [re.sub(r'/\*.*?\*/', '', line) for line in lines]
# 删除多行块注释/*...*/
STATE_NORMAL = 0
STATE_BEGIN = 1
STATE_BLOCK_COMMENT = 2
STATE_END = 3
state = STATE_NORMAL
idx_line = 0
# 对于文件中的每一行代码line
for line in lines:
temp = []
idx = 0
# 判断当前的下标是否越界
while idx < len(line):
# 获取当前下标对应的字符
# 通过判断字符的类型('/' '*')赋予不同状态位
cmt_char = line[idx]
if cmt_char == '':
break
if state == STATE_NORMAL:
if cmt_char == '/':
state = STATE_BEGIN
else:
temp.append(cmt_char)
elif state == STATE_BEGIN:
if cmt_char == '*':
state = STATE_BLOCK_COMMENT
else:
temp.append('/'+cmt_char)
state = STATE_NORMAL
elif state == STATE_BLOCK_COMMENT:
if cmt_char == '*':
state = STATE_END
elif state == STATE_END:
if cmt_char == '/':
state = STATE_NORMAL
idx = idx+1
if idx >= len(line):
break
cmt_char = line[idx]
while cmt_char == '\r' or cmt_char == '\n':
break
elif cmt_char == '*':
state = STATE_END
else:
state = STATE_BLOCK_COMMENT
idx = idx + 1
# 将处理后的该行字符拼接为字符串
tempstr = "".join(temp)
# 重新储存到lines中
# 原来没有进行这一步,lines中的数据没有改变
lines[idx_line] = tempstr
idx_line = idx_line + 1
2.2 删除多余空行
2.2.1 思路
这里的实现最初是单独实现的,后来与2.3混在一起了,这里只讲删除空行的思路:
python删除空行的方法可以直接用strip方法,例如下边,ChatGPT给出的方法:
lines = [line.strip() for line in lines if line.strip()]
但是这个方法操作后会把其他非空行代码首尾的空格和制表位全都删除掉,影响了原有代码的缩进结构,所以在进行上述strip()方法操作之前,需要记录下来当前代码行的缩进情况,记录下来后,调用strip()方法,再把缩进添加到处理过后的line中,这样就处理完啦
2.2.2 实现代码
# 因为我对代码做了改动,所以为了直观体现思路,直接贴了上述参考链接的部分代码
# 这里再贴一下参考博客:
# https://blog.csdn.net/zhayujie5200/article/details/78727677
# 计算该行应有的缩进空格(考虑Tab和空格混用的情况)
def count_space(st):
count = 0
if st == '\n':
return 0
for ch in st:
if ch == '\t':
count = count + 4
elif ch == ' ':
count = count + 1
else:
break
return count
2.3 单行多条语句分割
2.3.1 思路
单行中存在多条语句的情况比较复杂,例如非控制语句的并列、控制语句与非控制语句的并列、控制语句与控制语句的并列…目前我只实现了简易版本,能够实现非控制语句的并列、控制语句与非控制语句的并列,也即是通过分号来判断并分割:
- 分号判断思路 :根据某一行内分号数量以及分号分割后列表元素的数量来判断,是单一非控制语句的并列、控制语句与非控制语句的混合并列,如果相等则是前者,如果不等则是后者;
- 分割后处理思路:对于分割后的列表元素,逐个进行添加分号和换行操作,然后写入到txt文件里;
- 缩进问题需要注意:如果是非控制语句的并列,所有列表元素的缩进与分割前语句的缩进保持一致;如果含有非控制语句,那么该列表元素前有n个控制语句,就在原缩进的基础上添加4n个空格
- 当前的局限之处:
① 对于控制语句的多条并列,不能处理(因为是按照分号分割的思路);
② 暂时不能处理for循环(因为for循环的循环条件中本身就含有多个分号),后续需要完善;
③ 对于控制语句缩进的判断,目前支持if、while、switch的判断;
2.3.2 实现代码
line_spt_list = []
# 存储分号数量
num_smt = cnt_smt(clean_line)
if num_smt > 1:
if 'for' not in clean_line:
# 按照分号分割语句
line_spt_list = clean_line.split(';')
cnt = 0
idx_line_spt = 1
for line_spt in line_spt_list:
# 如果分号数量与语句数量相等,则为非控制语句的单一并列,否则则为混合并列
if num_smt == len(line_spt_list):
# 相等的情况下,缩进与当前缩进保持一致,此时cnt=0,sp存储当前缩进情况
line = sp + ' '*cnt + line_spt + ';' + '\n'
fw.write(line)
elif idx_line_spt < len(line_spt_list):
line = sp + ' ' * cnt + line_spt + ';' + '\n'
fw.write(line)
elif idx_line_spt == len(line_spt_list):
# 对于混合并列,分割后的列表中最后一个元素是不需要添加分号的
line = sp + line_spt + '\n'
fw.write(line)
if 'if' in line_spt or 'while' in line_spt or 'switch' in line_spt:
cnt = cnt + 1
idx_line_spt = idx_line_spt + 1
else:
# 暂未处理for循环的并列情况
line = sp + clean_line + '\n'
fw.write(line)
else:
line = sp + clean_line + '\n'
fw.write(line)
3 写在最后
最终的任务是要对数据库中的数据进行读写处理,目前实现了读,但是对于处理后的源代码只是测试保存到txt文件中,需要继续修改给直接保存到对应数据库表中。