自Visual Studio里抓取抽象语法树(AST)

前方几乎上测试一个代码生成的软件,测试目的是将软件转的C#还是VB.NET源代码文件,和事先的基准C#要VB.NET源代码文件进行对比。如果实在变化的文书与标准文件来免一样的地方,就证明,软件来地下的编码失误(Bug)。

 

时的艺术是用片只文本读入内存,一行一行逐字逐字地比。当然啦,为了避免空格的题材,文件事先就以空格都勾掉了。但是,这种措施的问题是,很多时刻,软件生成的源代码文件被,虽然代码行的停放顺序不雷同,但是落实之效用是全然相同的。举个例子,在使Visual
Studio中修Winform程序时,在InitializeComponent()函数里面,先生成为创按钮的代码,然后再生成文本框的代码;与士大夫成为创文本框的代码,然后再生成按钮的代码的功效是完全相同的。

 

那么这样是不是可尝尝这种实现,将有限单文本读入内存中,然后用文件随代表码行排序后还对照?这样吗杀,因为您切莫克将调用构造对象的代码放在使用对象的代码后面。

 

于是乎,我们就想是不是会由此比实际的CodeDom与规则CodeDom来贯彻?一般的话,在.NET世界里,代码生成的功效都是由此CodeDom技术来促成的。CodeDom通俗点讲,就是一个泛的代码树—不指任何编程语言,可以以不同之言语生成器遍历CodeDom来变化不同语言的源代码文件—对CodeDom感兴趣之读者可协调参考MSDN上面的印证。

 

然这个方案要给大家否决了,因为前面几乎单本子的测试过程遭到,使用的凡文件相比的模式,已经变了累累基准源代码文件了。如果利用CodeDom技术,这即表示要呢眼前几百只基准源文件还转对应的规范CodeDom。

 

以此时节我想到用编译器来分析两独源代码文件,然后对比结果的纸上谈兵语法树来达到近似CodeDom的效益。我产生一定量只编译器可以支持之方案,一个凡是csc.exe,另外一个凡Visual Studio用来支撑实时语法高亮显示的编译器。

 

为了支持实时的语法高亮显示和智能感应功能,Visual
Studio实际上在后台线程运行编译器进行实时编译,在待实施语法高亮、智能感应、代码重构等功用时,Visual
Studio会查询后台编译器里保存之符表、抽象语法树来收获有关的实时信息。

 

可是是编译器和我们普通工作编译C#(这里以C#呢条例)的编译器csc.exe不是同一个东西。之所以要另外呢Visual
Studio单独实现一个编译器,因为

1.         在进行实时语法高亮显示,智能感应等力量时。编译器不是处理一个整体的源代码。这与csc.exe不一样,因为csc.exe处理的是完全的C#源代码。

 

2.         另外, csc.exe与支持语法高亮显示的编译器对于语法错误的姿态呢是匪一样的,csc.exe可以不耐任何语法错误,即只要闹语法错误发生,csc.exe可以拒绝处理后续之语义分析的干活。然而语法高亮编译器却不能够这样,毕竟使用它们的时节,程序员在编写源代码,有多不曾就的地方。即使输入的源文件代码来好多之语法错误,语法高亮编译器也急需会继续执行后续之编译任务(例如语义分析)。

 

3.         还有语法高亮编译器还需要可以实现增量编译的成效,即持续在的源代码行合并到以编译好之代码中。比如说,在调节过程遭到,你可在“立即”窗口里定义一个变量,然后可以当与一个表达式里又评估是变量和被调程序都部分变量的计算结果。

 

下面两只.NET Assembly是Visual Studio用来支持C#实时语法高亮等效果的(实际上,你还待一个Win32
C++的DLL文件,但是这个文件未会见受我之程序直接采用):

1.        
Microsoft.VisualStudio.CSharp.Services.Language.dll

2.        
Microsoft.VisualStudio.CSharp.Services.Language.Interop.dll

 

当时有限只文本只有安装了Visual
Studio才见面发,你既可以Visual
Studio的安装文件夹里,也可以在GAC里面找到其。

 

以就简单个DLL不是Visual Studio公开的API,所以她与Visual Studio绑定的很紧密,即你只能当Visual
Studio里使用其,不克以另外程序中使—除非你把Visual
Studio SDK里由Visual Studio提供的生涩的接口都实现了。

 

用自之主次为便只能坐Visual
Studio的插件(Add in)的款型落实,在Visual Studio里(我于是的Visual Studio 2010)创建一个新的Visual Studio
Add-Ins工程,将上面两只DLL文件引用进来。在Exec函数里面实现对应的逻辑就是吓了,下面是有关代码:

 

public void Exec(string commandName, vsCommandExecOption executeOption, ref object varIn, ref object varOut, ref bool handled)

{

    handled = false;

    if(executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)

    {

        if(commandName == "MyAddin1.Connect.MyAddin1")

        {

            // 实现自定义的逻辑

            TestCSharpCompiler();

            handled = true;

            return;

        }

    }

}

 

private void TestCSharpCompiler()

{

    // 获取当前Visual Studio的解决方案,如果Visual Studio还没有任何方案

    // 就是默认的空解决方案

    var solution = (Solution2)_applicationObject.Solution;

    // 创建一个新的“C# 命令行程序(C# Console Application)”工程

    var csTemplatePath = solution.GetProjectTemplate("ConsoleApplication.zip", "CSharp");

    // 工程名(Test Project)以及保存工程的文件夹路径(d:\temp\test)

    solution.AddFromTemplate(csTemplatePath, @"d:\temp\test", "Test Project", false);

    var project = solution.Projects.Item(1);

    // 将已有的文件(d:\temp\test.cs)添加到新创建的工程中

    project.ProjectItems.AddFromFileCopy(@"d:\temp\test.cs");

    // 激活编译器

    var host = new IDECompilerHost();

    var compiler = host.CreateCompiler(project);

    SourceFile source = null;

 

    // 工程里一般都有很多文件,找到感兴趣的源文件

    // 因为那个文件的抽象语法树是我要的东西

    foreach (var file in compiler.SourceFiles)

    {

        if (string.Compare(file.Key.Value, @"d:\temp\test\test.cs",

                           StringComparison.InvariantCultureIgnoreCase) == 0)

        {

            source = file.Value;

            break;

        }

    }

 

    // 获取语法树的根节点,一般就是源文件最外层的命名空间

    var tree = source.GetParseTree();

    IDECompilation compilation = (IDECompilation)compiler.GetCompilation();

    // 在语法树里获取第一个命名空间的节点

    compilation.CompileTypeOrNamespace(tree.RootNode);

    var node = tree.RootNode as NamespaceDeclarationNode;

    // 获取命名空间节点里面的类定义、或者子命名空间、或者其它

    // 可以定义在命名空间里面的元素的节点

    foreach (var child in node.NamespaceMemberDeclarations.Root.Children)

    {

        if (child is BinaryExpressionNode)

        {

            var bnode = child as BinaryExpressionNode;

            var left = bnode.Left as ClassDeclarationNode;

            var right = bnode.Right as ClassDeclarationNode;

 

            Trace.WriteLine(left.Identifier.Name.Text);

            Trace.WriteLine(right.Identifier.Name.Text);

        }

        else

        {

            Trace.WriteLine(child.AsName().Name.Text);

        }

    }

}

 

地方的代码只是做示范用之,里面解析的源代码(test.cs)已经包含到下的整体工程的源文件里了(工程文件是Visual
Studio 2010格式的): 

/Files/killmyday/MyAddinForAST.zip