PYQT5:基于QsciScintilla的代码编辑器分析6--获取c文件的函数名列表
上一篇文章已经剧透了下面的内容:1.获取光标所在单词;2.如何知道函数体在哪个文件哪一行。来看第一项。
1.获取光标所在单词
要获取光标所在单词,QsciScintilla提供了标准的函数供用户使用:
pos = self.SendScintilla(QsciScintilla.SCI_GETCURRENTPOS) #取得当前位置
start = self.SendScintilla(QsciScintilla.SCI_WORDSTARTPOSITION, pos-2) # 纯英文下,运行正常
end = self.SendScintilla(QsciScintilla.SCI_WORDENDPOSITION, pos-2)
但是我使用上面代码时,纯英文的文档没有问题;如果有中文的字符就出错,莫名其妙的获取到后面的字符,还不止包含单词,还可能包含"({"这样的特殊字符。所以我就只能自己重写一个函数。先取得光标所在的行数,把整行文字作为参数传入。
def getFuncNameInLine(self, linestr): #在行中查找函数名
start=0
end=linestr.find('(')
if end == 0:
return None
for i in range(end-1, -1, -1): #从 左括号字符 “(” 往回查询到空格、= 、制表符 就停止
if start !=0 and linestr[i] in " =\t":
end = start
start = i+1
break
# 左括号 和 函数名 之间有可能包含空格
elif start ==0 and linestr[i] != ' ' : #非空格,就是函数名最后一个字符,把位置保存在start
start = i+1
else:
end=0
return linestr[start: end]
2.获取函数名列表
获取函数名列表的方法做成一个名为 lexer_c_getfunc 的类。传入c文件名(完整路径),返回文件名(无路径)和包含了函数名,行数的字典。
2.1文件名函数名字典结构
先看看上一章提到的字典fileFuncDict 的数据结构,这是一个包含子字典的字典,结构为{文件名1:{子字典1},文件名2:{子字典2},…},子字典的结构为{函数名1:行数1,函数名2:行数2,…},请看下面的例子:
fileFuncDict ={ 'test.c': {'TempADC': 4, 'DoNothing': 19, 'DoNothing1': 22},
'11test.c': {'LcdTest': 12, 'UartTest': 63, 'TempADC': 74, 'PWMTest': 100}}
使用下面的程序就可以得到子字典:
subDict=fileFuncDict ['test.c']
于是得到的subDict= {‘TempADC’: 4, ‘DoNothing’: 19, ‘DoNothing1’: 22}。再由下面的程序得到函数名所在行:
line = subDict['DoNothing']
得到line=19.将上面的2个句子合并成一句就是:
line = fileFuncDict['test.c']['DoNothing']
lexer_c_getfunc.scanFunction()获得函数名和行号并返回,被lexer_c_getfunc.lexer_analysis()调用,后者获得文件名和子字典并返回。然后我们回看第4章《4–xml格式的工程文件与树状列表部件》的《2.2打开已有工程时生成所有节点》的代码的一部分:
#3.1.1] C 文件还要添加函数名作为三级节点
c_getfunc =lexer_c_getfunc(str1)
_fname, _dict= c_getfunc.lexer_analysis()
self.fileFuncDict.setdefault(_fname, _dict)
funcs=list(_dict.keys())
for func in funcs:
subsubchild = QTreeWidgetItem(subchild1)
subsubchild.setText(0,func)
subsubchild.setIcon(0,QIcon(":/{0}.png".format("func2")))
2.2 类lexer_c_getfunc源代码
此处贴出lexer_c_getfunc类的源代码:
import os
KEYWORD_LIST_FUNC=['if','while','for','switch']
g_allFuncList=[]
class lexer_c_getfunc:
def __init__(self, fileFullName):
self.current_row = -1
self.current_line = 0
self.fullName=fileFullName
if not os.path.exists(fileFullName):
return None #文件名为空 或者 文件不存在
try:
fin = open(self.fullName, "r", encoding='utf-8', errors="ignore")
input_file = fin.read()
fin.close()
except:
fin.close()
return None #打开文件失败
self.input_str = input_file.splitlines(False)
self.max_line = len(self.input_str) - 1
def getchar(self): # 从整个文档中取出一个 char
self.current_row += 1
if self.current_row == len(self.input_str[self.current_line]):
self.current_line += 1
self.current_row = 0
while True:
if len(self.input_str[self.current_line])!=0:
break
self.current_line+=1
if self.current_line == self.max_line:
return 'SCANEOF'
return self.input_str[self.current_line][self.current_row]
def ungetchar(self): #往文档存回一个 char
self.current_row = self.current_row - 1
if self.current_row < 0:
self.current_line = self.current_line - 1
self.current_row = len(self.input_str[self.current_line]) - 1
return self.input_str[self.current_line][self.current_row]
def getFunctionNameInLine(self, strline):
for i in range(0, len(strline)):
if strline[i] == '(':
break
else:
return None
j=i-1
for i in range( j,-1, -1):
if ord(strline[i]) > 127: #非 ASCII 码
return None
if strline[i].isalnum() == False and strline[i] !='_': # 含有非函数名字符则停止
str1=strline[i+1: j+1]
if str1 in KEYWORD_LIST_FUNC: # 不是关键字
break
else: #函数名
return str1
return None
def scanFunction(self):
global g_allFuncList
if self.current_line == self.max_line:
return ('SCANEOF', self.max_line)
str1 = self.input_str[self.current_line].strip()
if len(str1)==0 : # 空行
self.current_line += 1
self.current_row = -1
return None
if '(' not in str1: # 没有 左括号
self.current_line += 1
self.current_row = -1
return None
#本行出现(,记录行号
lineOfLeft =self.current_line
while(True):
#查找‘)’ --> {
current_char = self.getchar()
if current_char == ')':#后面可能有注释 /**/ 或者 // 跳过 ;还有=跳过
while(True):
current_char = self.getchar()
if current_char == '{':#当前行中包含函数名,记录行号和获取函数名
str1 =self.getFunctionNameInLine(self.input_str[lineOfLeft])
if str1:
g_allFuncList.append(str1)
return (str1, lineOfLeft)
return None
elif current_char == '(':
lineOfLeft =self.current_line
continue
elif current_char == ';' or current_char == '=':#分号表示此处为函数调用,并非函数体跳过 =可能是函数指针数组
self.current_line += 1
self.current_row = -1
return None
elif current_char == '/':
next_char = self.getchar()
if next_char == '/': # 单行注释跳过当前行,下面已经是下一行
self.current_line += 1
self.current_row = -1
next_char = self.getchar()#换行的第一个是 { 认为是函数所在行
if next_char == '{': # 行首是 { ,将字符存回去 ,回到当前的while开始处
self.ungetchar()
continue
elif next_char == 'SCANEOF':
return ('SCANEOF', 0)
else:
return None
elif current_char == '*': # 块注释 /**/
next_char = self.getchar()
while True:
if next_char == '*':
end_char = self.getchar()
if end_char == '/':
break
if end_char == 'SCANEOF':
return ('SCANEOF', 0)
elif next_char == 'SCANEOF':
return ('SCANEOF', 0)
next_char = self.getchar()
def lexer_analysis(self):
[dirname,filename]=os.path.split(self.fullName)
funcDict={} #本字典 属于 子自典,key = 函数名,value = 行号
# 分析c文件,一直到文档结束
while True:
r = self.scanFunction()
if r is not None:
if r[0] == 'SCANEOF': # 文档结尾,结束查找
break
funcDict.setdefault(r[0], r[1]) #查找到函数名,记录所在行号
return (filename, funcDict)