我有一个我正在研究的C#应用程序远程加载它的代码,然后运行它(为了争论,你可以假设应用程序是安全的)。
代码是C#,但它作为XML文档发送,解析为字符串,然后编译和执行。
现在,我想做的事情 - 并且比我预期的要困难得多 - 能够解析整个文档,并且在编译之前,在每行执行之后插入其他命令。
例如,考虑代码:
using System;
using System.Collections.Generic;
using System.Linq;
namespace MyCode
{
static class MyProg
{
static void Run()
{
int i = 0;
i++;
Log(i);
}
}
}
解析后我想要的更像是:
using System;
using System.Collections.Generic;
using System.Linq;
namespace MyCode
{
static class MyProg
{
static void Run()
{
int i = 0;
MyAdditionalMethod();
i++;
MyAdditionalMethod();
Log(i);
MyAdditionalMethod();
}
}
}
请记住明显的陷阱 - 我不能在每个分号后都有它,因为这在吸气剂/定位器中不起作用,即:
转换:
public string MyString { get; set; }
至:
public string MyString { get; MyAdditionalMethod(); set; MyAdditionalMethod(); }
会失败的。与类级声明,使用语句等一样。此外,在许多情况下我也可以在花括号后添加MyAdditionalMethod() - 比如在委托中,在if语句之后,或方法声明等。
所以,我一直在研究CodeDOM,看起来它可能是一个解决方案,但很难弄清楚从哪里开始。我试图解析整个事情并创建一个我可以解析的树 - 虽然这有点难,考虑到我需要考虑的案例数量。
有谁知道其他任何解决方案?
有一些C#解析器,我建议使用Mono或SharpDevelop的东西,因为它们应该是最新的。我去玩了 NRefactory 来自SharpDevelop,如果你 下载 SharpDevelop的源代码有一个演示和一些UnitTests,它们是一个很好的介绍它的用法。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ICSharpCode.NRefactory;
using System.IO;
using ICSharpCode.NRefactory.Ast;
using ICSharpCode.NRefactory.Visitors;
using ICSharpCode.NRefactory.PrettyPrinter;
namespace Parse
{
class Program
{
static void Main(string[] args)
{
string code = @"using System;
using System.Collections.Generic;
using System.Linq;
namespace MyCode
{
static class MyProg
{
static void Run()
{
int i = 0;
i++;
Log(i);
}
}
}
";
IParser p = ParserFactory.CreateParser(SupportedLanguage.CSharp, new StringReader(code));
p.Parse();
//Output Original
CSharpOutputVisitor output = new CSharpOutputVisitor();
output.VisitCompilationUnit(p.CompilationUnit, null);
Console.Write(output.Text);
//Add custom method calls
AddMethodVisitor v = new AddMethodVisitor();
v.VisitCompilationUnit(p.CompilationUnit, null);
v.AddMethodCalls();
output = new CSharpOutputVisitor();
output.VisitCompilationUnit(p.CompilationUnit, null);
//Output result
Console.Write(output.Text);
Console.ReadLine();
}
}
//The vistor adds method calls after visiting by storing the nodes in a dictionary.
public class AddMethodVisitor : ConvertVisitorBase
{
private IdentifierExpression member = new IdentifierExpression("MyAdditionalMethod");
private Dictionary<INode, INode> expressions = new Dictionary<INode, INode>();
private void AddNode(INode original)
{
expressions.Add(original, new ExpressionStatement(new InvocationExpression(member)));
}
public override object VisitExpressionStatement(ExpressionStatement expressionStatement, object data)
{
AddNode(expressionStatement);
return base.VisitExpressionStatement(expressionStatement, data);
}
public override object VisitLocalVariableDeclaration(LocalVariableDeclaration localVariableDeclaration, object data)
{
AddNode(localVariableDeclaration);
return base.VisitLocalVariableDeclaration(localVariableDeclaration, data);
}
public void AddMethodCalls()
{
foreach (var e in expressions)
{
InsertAfterSibling(e.Key, e.Value);
}
}
}
}
您需要改进访问者以处理更多案例,但这是一个良好的开端。
或者你可以编译原文并使用一些IL操作 塞西尔 或尝试一些类似的AOP库 PostSharp。最后你可以看一下 .NET Profiling API。
您可以使用源到源程序转换系统。这样的工具解析代码,构建和AST,允许您应用转换,然后从AST重新生成文本。什么使源到源系统很好,你可以根据源语言语法而不是AST的分形细节来编写转换,这使得它们更容易在以后编写和理解。
您想要做的事情将通过一个非常简单的程序转换来建模
用我们的 DMS软件再造工具包:
rule insert_post_statement_call(s: stmt): stmt -> stmt =
" \s " -> " { \s ; MyAdditionalMethod(); }";
这条规则不是“文本”替代;相反,它由处理目标代码的解析器解析,因此实际上它代表两个AST,左侧和右侧(由“ - >”语法分隔。引号不是字符串引号;它们是目标语言语法的引用,以区别于规则语言本身的语法。引号内部是目标语言(例如,C#)带有转义符的文本,如\ s,表示整个语言元素(在本例中为stmt根据目标语言(例如C#)语法。左侧表示“匹配任何语句s”,因为s被定义为语法中的“stmt”。右侧表示“用一个语句替换语句”包含原始语句的块,以及要插入的新代码“。这都是使用语法作为指导语法树完成的;它不能将转换应用于任何不是语句的内容。[将语句重写为块的原因是因为这种方式是正确的 是 在语句有效的地方有效,去检查你的语法。]
实际上,您需要编写规则来处理其他特殊情况,但这主要是编写更多规则。您还需要将解析器/ transformer / prettyprinter打包为bundle,这需要一些程序粘合剂。这比尝试编写代码可靠地爬上树,匹配节点然后粉碎这些节点以获得您想要的东西要容易得多。更好的是,当你的语法(总是)必须调整时,重写规则会根据修改后的语法进行重新分析,并且仍然有效;无论你在做什么程序爬树,几乎可以肯定要打破。
随着您编写越来越多的转换,此功能变得越来越有价值。当您成功进行少量转换时,添加更多内容会很快变得有吸引力。
看到 这篇技术论文 有关DMS如何工作的更全面的讨论,以及它如何用于在实际工具中应用仪器转换,就像您想要的那样。本文描述了Semantic Designs销售的测试覆盖工具背后的基本思想。
你需要的是使用表达式树。来自MSDN的一些有用信息: