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)
posted @ 2022-12-15 10:44  汉塘阿德  阅读(78)  评论(0编辑  收藏  举报  来源