Algorithm 代码完成是如何工作的?

Algorithm 代码完成是如何工作的?,algorithm,autocomplete,code-completion,Algorithm,Autocomplete,Code Completion,许多编辑器和IDE都有代码完成。他们中的一些人非常“聪明”,其他人则不是。我对更聪明的类型感兴趣。例如,我见过一些IDE,它们只在函数a)在当前范围内可用b)返回值有效时才提供函数。(例如,在“5+foo[tab]”之后,它只提供返回可以添加到正确类型的整数或变量名中的内容的函数。)我还看到,它们将更常用或最长的选项放在列表的前面 我意识到你需要解析代码。但通常,当编辑当前代码无效时,其中会有语法错误。当某个内容不完整且包含错误时,如何解析它 还有一个时间限制。如果需要几秒钟才能列出一个列表,那

许多编辑器和IDE都有代码完成。他们中的一些人非常“聪明”,其他人则不是。我对更聪明的类型感兴趣。例如,我见过一些IDE,它们只在函数a)在当前范围内可用b)返回值有效时才提供函数。(例如,在“5+foo[tab]”之后,它只提供返回可以添加到正确类型的整数或变量名中的内容的函数。)我还看到,它们将更常用或最长的选项放在列表的前面

我意识到你需要解析代码。但通常,当编辑当前代码无效时,其中会有语法错误。当某个内容不完整且包含错误时,如何解析它

还有一个时间限制。如果需要几秒钟才能列出一个列表,那么完成是没有用的。有时,完成算法处理数千个类


这方面有哪些好的算法和数据结构?

我不能确切地说任何特定的实现使用了哪些算法,但我可以做出一些有根据的猜测。对于这个问题,A是一个非常有用的数据结构:IDE可以在项目中所有符号的内存中维护一个大的trie,每个节点上都有一些额外的元数据

键入字符时,它会沿着trie中的路径移动。特定trie节点的所有子代都是可能的补全。IDE然后只需要根据在当前上下文中有意义的内容过滤掉这些内容,但它只需要计算在tab completion弹出窗口中显示的尽可能多的内容

更高级的制表符完成需要更复杂的trie。例如,有一个功能,您只需要键入CamelCase符号的大写字母——例如,如果您键入SFN,它会在其选项卡完成窗口中显示符号
SomeFunctionName

计算trie(或其他数据结构)确实需要解析所有代码,以获得项目中所有符号的列表。VisualStudio将其存储在IntelliSense数据库中,该数据库是一个存储在项目旁边的
.ncb
文件,因此它不必在每次关闭和重新打开项目时都重新分析所有内容。当您第一次打开一个大型项目(比如,您刚刚从源代码管理同步的项目)时,VS将花时间解析所有内容并生成数据库

我不知道它如何处理增量更改。正如您所说的,当您编写代码时,90%的时间都是无效的语法,并且每当您空闲时重新划分所有内容都会对您的CPU带来巨大的负担,但好处微乎其微,特别是当您修改大量源文件所包含的头文件时


我怀疑它要么(a)只在您实际构建项目时(或者可能在您关闭/打开项目时)重新解析,要么(b)它进行某种本地解析,只在您刚刚以某种有限的方式编辑的地方解析代码,只是为了获得相关符号的名称。因为C++有着非常复杂的语法,如果使用重模板元编程之类的话,它可能在黑暗的角落里表现得很奇怪。

< P>我的UnRealScript语言服务产品中的智能信息引擎很复杂,但我会尽我所能给出最好的概览。VS2008SP1中的C语言服务是我的性能目标(有充分的理由)。它还没有出现,但它足够快/准确,我可以在键入单个字符后安全地提供建议,而无需等待ctrl+space或用户键入
(点)。[从事语言服务的人]获得的关于这个主题的信息越多,我使用他们的产品所获得的最终用户体验就越好。有很多产品我都有过不幸的工作经历,它们没有对细节给予如此密切的关注,因此我与IDE的斗争超过了编码

在我的语言服务中,它的布局如下:

  • 在光标处获取表达式。这将从成员访问表达式的开头走到光标所在的标识符的结尾。成员访问表达式的形式通常为
    aa.bb.cc
    ,但也可以包含方法调用,如
    aa.bb(3+2).cc
  • 获取光标周围的上下文。这是非常棘手的,因为它并不总是遵循与编译器相同的规则(长话短说),但在这里假设它遵循相同的规则。通常这意味着获取有关游标所在的方法/类的缓存信息
  • 假设上下文对象实现了
    IDeclarationProvider
    ,您可以在其中调用
    GetDeclarations()
    ,以获取范围中所有可见项的
    IEnumerable
    。在我的例子中,这个列表包含局部变量/参数(如果在方法中)、成员(字段和方法,只有在实例方法中才是静态的,没有基类型的私有成员)、全局变量(我正在处理的语言的类型和常量)和关键字。此列表中的项目名称为
    aa
    。作为评估#1中表达式的第一步,我们从上下文枚举中选择名为
    aa
    的项,为下一步提供
    IDeclaration
  • 接下来,我将操作符应用于表示
    aa
    IDeclaration
    ,以获得另一个
    IEnumerable
    ,其中包含
    aa
    的“成员”(在某种意义上)。由于
    运算符与
    ->
    运算符不同,因此我调用
    声明.GetMembers(“.”
    并期望
    IDeclaration
    对象正确应用列出的运算符
  • 这一直持续到我点击
    cc
    ,其中声明列表可能包含也可能不包含名为
    cc
    的对象。我相信您也知道,如果多个项目以
    cc
    开头,它们也应该出现。我用羚羊来解决这个问题
    class A
    {
        int x; // linked to A
    
        void foo() // linked to A
        {
            int local; // linked to foo()
    
        // foo() ends here because bar() is starting
        void bar() // linked to A
        {
            int local2; // linked to bar()
        }
    
        int y; // linked again to A