今天来说一下智能提示的初步想法。智能提示需要解决的问题有两个。第一个是迅速知道光标位置在与编辑中的代码相对应的抽象语法树中的位置。第二个是把当前用户可以输入的东西显示出来并且提供输入的便利。第一个问题里面有两个小问题,包括用你能达到的最快速度分析代码全文组成语法树并产生scope表,以及智能地在用户输入东西的时候临时对输入的那一小块(如何确定块的区域,这个根据不同的语言以及编辑的不同位置可能需要不同的算法)进行重新分析产生一棵小树。我们总是可以在全文分析没结束之前,使用上一次全文分析产生的scope表以及这棵小树来得到超过99%正确率的上下文。
那么今天要说的就是如何用C#进行高效的全文分析。我们知道全用LALR的话不仅难开发而且代码难调试难测试难修改,因此就算了。最好调试的代码是什么呢,显然是递归下降法写出来的。其实代码本来没多少层,所以递归下降最多也就递归十几层,也不会太多,总的来说性能还是可以接受的。但是每来一个语言就用一次递归下降还是很惨的。好在.net自带C#编译器,我们可以使用parser combinator来生成。关于什么是combinator,可以参考
这里。至于什么是parser combinator,我曾经用C++
实现了一个。
Parser combinator的好处是我们可以在C#里面把文法直接表达出来,然后变成一个语法分析器。不过直接执行combinator,性能会受到很大影响。怎么样才能把性能降低到跟手写的差不多呢?.NET给了我们三种武器,分别是CodeDom、Emit和Linq Expression。我比较倾向于CodeDom,CodeDom可以让我们写C#来拼出一颗巨大的代表一个C#程序的语法树,然后用自带的.net编译器去编译成dll或者cs文件。因此这个C#的parser combinator的目的就是要让我们用最美妙的语法来拼出目标语言的文法,最后根据文发来产生一份C#语法分析器的代码。我们可以每次运行的时候都编译出一个内存的dll,或者直接产生一个cs文件然后拖进我们的工程。
我目前可能会采取前一种方法:也就是用parser combinator来产生文法树,然后我提供一个函数来把它转换成一份对应的C#递归下降语法分析器的代码(跟yacc很像哈,虽然他用的是LALR),最后编译它。因此只需要在IDE第一次打开某个语言的代码文件的时候编译出这个语法分析器,在IDE关掉之前就都可以用了。
那语法分析器要产生什么语法树呢?这个还是要我们自己来解决的。不过我采取了一种比较偷懒的方法。我先写了一个语法树的基类(
vlpp.codeplex.com后Candidate\CodeBoxControl\CodeBoxControl\CodeProvider\*.cs),然后只要你给我一个这样子的虚类:
1 public abstract class ExpressionNode : CodeNode
2 {
3 }
4
5 public abstract class NumberNode : ExpressionNode
6 {
7 public int Number { get; set; }
8 }
9
10 public abstract class AddNode : ExpressionNode
11 {
12 public abstract ExpressionNode Left { get; set; }
13 public abstract ExpressionNode Right { get; set; }
14 }
那么你就可以用CodeNode.Create<AddNode>()或者CodeNode.Create<NumberNode>()来获得相应的实现了。至于CodeNode的声明是这样的:
1 public abstract class CodeNode
2 {
3 public virtual TextPosition Start { get; protected internal set; }
4 public virtual TextPosition End { get; protected internal set; }
5 public virtual CodeNode ParentNode { get; protected internal set; }
6 public virtual CodeNodeCollection Nodes { get; private set; }
7 public virtual ICodeScope OwningScope;
8 public virtual ICodeScope Scope;
9
10 public CodeNode();
11
12 public static T Create<T>()
13 where T : CodeNode;
14 }
因此当你往AddNode.Left赋值的时候,也就是等于在写CodeNode.Nodes["Left"],这就是Create<T>所提供的实现了。当然写进去了之后ParentNode和Scope属性就会立刻有效了。这种方法还是可以剩下你不少时间的。
今天就说到这里了,然后我就得去开发那个C#的parser combinator并且想好一个单元测试的对策(这也是一种练习哈),然后再继续写博客了。不过中秋节那一整个星期都要回家办点事情所以估计会暂停。
posted on 2010-09-17 08:43
陈梓瀚(vczh) 阅读(7394)
评论(5) 编辑 收藏 引用 所属分类:
开发自己的IDE