前几天测试一个代码生成的软件,测试目的是将软件生成的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, refobject 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格式的):

本文转自 donjuan 博客园博客,原文链接: http://www.cnblogs.com/killmyday/archive/2010/05/22/1741601.html  ,如需转载请自行联系原作者

从Visual Studio里抓取抽象语法树(AST)相关推荐

  1. python compiler.ast_ast --- 抽象语法树 — Python 3.7.9 文档

    ast --- 抽象语法树¶ ast 模块帮助 Python 程序处理 Python 语法的抽象语法树.抽象语法或许会随着 Python 的更新发布而改变:该模块能够帮助理解当前语法在编程层面的样貌. ...

  2. ActiViz(VTK的C#库)C#64位VTK如何在Visual Studio里创建RenderWindowControl控件

    Visual Studio17里只支持x86位控件拖拽,不支持x64.那怎样在64位的组件form窗口中放RenderWindowControl控件呢?下面我们就详细介绍一下在Visual Studi ...

  3. 应用ast抽象语法树修改js函数

    原理:AST抽象语法树 目标:在每一个函数里面插入一个console.log()把函数传入的全部参数输出出来 关于:本文章是在基于我的个人理解且怕忘记知识所记录下来的给自己看并且分享自己的一个心得,文 ...

  4. Scala的抽象语法树打印小工具-小拉达

    为什么80%的码农都做不了架构师?>>>    最近做的两个项目,一个是VeriScala,另一个是Lickitung,都涉及到了Scala的抽象语法树(AST),前者是写macro ...

  5. 抽象语法树 -Abstract Syntax Tree

    什么是抽象语法树? 是源代码结构的一种抽象表示,以树状的形式表现编程语言的语法结构.树上的每个节点都表示源代码中的一种结构. 拆分成语法树 拆解一个简单的add函数 function add(a, b ...

  6. python 抽象语法树_抽象语法树(Abstract Syntax Tree)

    一般来说,程序中的一段源代码在执行之前会经历下面三个步骤 1 分词/词法分析 这个过程会将由字符组成的字符串分解成有意义的代码快,这些代码块被称为词法单元.例如 var a = 4:会被分解成 var ...

  7. ast抽象语法树_新抽象语法树(AST)给 PHP7 带来的变化

    本文大部分内容参照 AST 的 RFC 文档而成:https://wiki.php.net/rfc/abstract_syntax_tree,为了易于理解从源文档中节选部分进行介绍. 我的官方群点击此 ...

  8. 编译原理抽象语法树_平衡抽象原理

    编译原理抽象语法树 使代码复杂易读和理解的一件事是,方法内部的指令处于不同的抽象级别. 假设我们的应用程序仅允许登录用户查看其朋友的旅行. 如果用户不是朋友,则不会显示任何行程. 一个例子: publ ...

  9. hive遍历_Hive解析流程-抽象语法树生成

    HiveQL解析流程: Hive根据Antlr定义的词法.语法规则完成词法.语法分析将HQL解析为AST Tree: 遍历AST Tree,抽象出查询的基本组成单元Query Block: 遍历Que ...

最新文章

  1. 中国互联网+激光加工行业商业模式创新与投资机会深度研究报告
  2. python中yaml模块的使用_Python中Pyyaml模块的使用
  3. java的getshape()_JAVA(Swing)中的镜像形状
  4. ota编译及差分包制作
  5. qt drawrect绘制图形为什么不显示_Qt编写自定义控件45-柱状标尺控件
  6. uuid会重复吗_记一次订单号重复的事故,快看看你的 UUID 在并发下还正确吗?...
  7. ARM 汇编详解 -- 体系结构与编程
  8. asm MGMT库迁移
  9. 优化食物与食物营养结构相宜表(中国家庭健康工程推荐)
  10. stm32刷linux固件,STM32 固件烧录指南
  11. obsidian安装,主题设置,已经相关功能介绍
  12. 《Miss Talk》第07期:对话拓课云联合创始人兼CTO 王晓伟
  13. c语言getch 头文件,用getch()需要头文件吗?
  14. Total Commander 文件管理
  15. 学习Matlab第三天——画3D图
  16. 酒店、宾馆、饭店的区别
  17. Android APP启动时出现白屏或者黑屏怎么办?
  18. pyepics Device:PVs的集合
  19. 英语语言学论文选题 计算机,88个英语语言学、语言论文题目选题参考
  20. 全国各地区PPP项目数+投资额(2016-2021)

热门文章

  1. SAP系统日常管理 常见 t-code (二)
  2. javascript入门·简单运算和数据类型转换
  3. 如何成为SEO专家(10步指南)
  4. Java基础笔记17
  5. Linkedin 工程师如何优化他们的 Java 代码
  6. java对象转为json字符串
  7. zz SOA推荐书籍列表
  8. Redux 学习总结 (React)
  9. 【iOS Tips】002-实现“简单单例模式”的几种方法?
  10. 关于ASP.NET MVC的一些工作中遇到的问题