本文旨在通过.NET Framework CodeDOM API展示交互式C#解释器开发的分步实现,使我们能够动态评估C#代码表达式和语句,以或多或少地类似于Python Interpreter来测试迷你代码片段或脚本。但是,视觉工作室有足够的能力做到这一点,那么为什么我们需要它呢? Visual Studio IDE编译器通常每次都会编译整个应用程序类,而不是在需要时编译一部分代码段。因此,该过程相对麻烦,费时,并且在文件系统上造成了不必要的额外开销。
在这方面,将雕刻出具有不同功能的多个类别。该项目的开发人员应该精通.NET,因为该项目是一个基于控制台的应用程序,在该程序中,解释器为我们提供了命令行方式的印象,以操作命令。因此,以下C#代码段是应用程序的入口点,从该入口点开始,通过合并所有其他辅助基本类的调用开始实际执行。在名为Program的入口点类中,我们发现横幅与自定义异常处理功能一起显示。
program.cs
// Entry Point
class Program {
static void Main(string[] args)
{
// verbose entry checking
if (args.Length > 0 && args[0].Equals("--verbose",
StringComparison.InvariantCultureIgnoreCase)) {
Interactive.Context.VerboseTrace = true;
}
Trace.Listeners.Add(new ConsoleTraceListener());
// Method call for welcome message
Program.WriteWelcomeMessage();
// Dispaly #:-> on the shell
while (Interactive.Context.Continue) {
Console.Write("#:->");
string text = Console.ReadLine();
if (text == null) {
return;
}
// Start the interactive shell by the function call
try {
string text2 = Interactive.Interpret(text);
if (text2 != null) {
Console.WriteLine(text2);
}
}
catch (TargetInvocationException ex) {
Program.WriteExceptionMessage(ex.InnerException);
}
catch (Exception ex2) {
Program.WriteExceptionMessage(ex2);
}
}
}
// Starts the Interactive shell, and display the welcome message
private static void WriteWelcomeMessage()
{
Version version = Assembly.GetExecutingAssembly().GetName().Version;
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("-----------------------------------------");
Console.WriteLine("\tC# Interactive Interpreter\n");
Console.WriteLine("-----------------------------------------");
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("help -- Help");
Console.WriteLine("quit -- Quit");
Console.WriteLine("clear-- Clear Screen");
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine();
}
// Exception handling section
private static void WriteExceptionMessage(Exception ex)
{
Console.WriteLine("Exception of type '"
+ ex.GetType().Name + "' was thrown: "
+ ex.Message);
if (Interactive.Context.VerboseTrace) {
Console.WriteLine("StackTrace:");
Console.WriteLine(ex.StackTrace);
if (ex.InnerException != null) {
Console.WriteLine("Inner Exception:");
Console.WriteLine(ex.InnerException.Message);
Console.WriteLine(ex.InnerException.StackTrace);
}
}
}
}
outString.cs outString类负责对Essentials变量进行初始化,这将有利于在shell上打印结果。
public class outString {
// Properties declaration
public string Value
{
get;
private set;
}
public outString(string value)
{
this.Value = value;
}
public override string ToString()
{
return this.Value;
}
}
Interactive.cs Interactive类在导入API,调用执行会话,正则表达式处理以及通过调用CodeDOM类处理编译器和解释器API以及方法的过程中封装了所有必需的代码。总体而言,这是解释语句的唯一类,就像引擎一样。
public static class Interactive {
private static readonly CodeDomProvider Compiler;
public static exeContext Context;
static Interactive()
{
// C# API calling
Interactive.Compiler = CodeDomProvider.CreateProvider("C#");
Interactive.Context = new exeContext();
}
// empty the context
public static void Reset()
{
Interactive.Context = new exeContext();
}
// Invoke the interpreter shell
public static string Interpret(string sourceCode)
{
return sourceCode.CompileCodeSnippet().Invoke();
}
// Compile the bunch input C# code
private static compiledCode CompileCodeSnippet(this string code)
{
if (Interactive.Context.MultiLine) {
exeContext expr_11 = Interactive.Context;
expr_11.MultiLineStatement += code;
code = Interactive.Context.MultiLineStatement;
}
return code.Statement() || code.TypeMember();
}
// Compile the Particular C# statement
private static compiledCode Statement(this string code)
{
return code.ExpressionStatement() || code.UsingStatement() || code.GenericStatement();
}
// Compile the "Using" statements
private static compiledCode UsingStatement(this string code)
{
compiledCode result = null;
if (code.TrimStart(new char[0]).StartsWith("using ")) {
string text = code.TrimEnd(new char[0]);
if (!text.EndsWith(";")) {
text += ";";
}
string usingStatement = text;
string source = Interactive.Program(null, null, null, null, usingStatement);
custStatement statement = new custStatement(code, source.CompileFromSource());
if (!statement.HasErrors) {
Interactive.Context.UsingStatements.Add(text);
result = statement;
}
}
return result;
}
// In case custom statment compilation
private static compiledCode GenericStatement(this string code)
{
compiledCode result = null;
string statement = code + ";";
string source = Interactive.Program(null, statement, null, null, null);
custStatement statement2 = new custStatement(code, source.CompileFromSource());
if (!statement2.HasErrors) {
Interactive.Context.CallStack.Add(code + ";");
result = statement2;
}
else {
if (!Interactive.Context.MultiLine && (statement2.Errors[0].ErrorNumber == "CS1513" || statement2.Errors[0].ErrorNumber == "CS1528")) {
Interactive.Context.MultiLine = true;
exeContext expr_A2 = Interactive.Context;
expr_A2.MultiLineStatement += code;
}
}
return result;
}
// Section to execute "Clear" command
private static compiledCode ExpressionStatement(this string expr)
{
string returnStatement = custProBuilds.ReturnStatement(expr);
custExpression expression = new custExpression(expr, Interactive.Program(null, null, returnStatement, null, null).CompileFromSource());
if (!expression.HasErrors && !expr.Trim().Equals("clear", StringComparison.OrdinalIgnoreCase)) {
string text = "__" + Guid.NewGuid().ToString().Replace("-", "");
Interactive.Context.CallStack.Add(string.Concat(new string[] {
"var ",
text,
" = ",
expr,
";" }));
}
return expression;
}
// Incorporate the "Program" class code
public static string Program(string typeDeclaration = null, string statement = null, string returnStatement = null, string memberDeclaration = null, string usingStatement = null)
{
return custProBuilds.Build(Interactive.Context, typeDeclaration, statement, returnStatement, memberDeclaration, usingStatement);
}
// Incorporate the class type defined members code
private static compiledCode TypeMember(this string source)
{
return source.TypeDeclaration() || source.MemberDeclaration() || source.FieldDeclaration();
}
// Incorporate the member declaration code
private static compiledCode MemberDeclaration(this string code)
{
custMemDecl memberDeclaration = new custMemDecl(code, Interactive.Program(null, null, null, code, null).CompileFromSource());
if (!memberDeclaration.HasErrors) {
Interactive.Context.MemberDeclarations.Add(code);
}
return memberDeclaration;
}
// Incorporate the type declaration code and add them
private static compiledCode TypeDeclaration(this string source)
{
string source2 = Interactive.Program(source, null, null, null, null);
custTypeDecl typeDeclaration = new custTypeDecl(source, source2.CompileFromSource());
if (!typeDeclaration.HasErrors) {
Interactive.Context.TypeDeclarations.Add(source);
}
return typeDeclaration;
}
// Incorporate the class fields code
private static compiledCode FieldDeclaration(this string code)
{
string text = code + ";";
string memberDeclaration = text;
custMemDecl memberDeclaration2 = new custMemDecl(code, Interactive.Program(null, null, null, memberDeclaration, null).CompileFromSource());
if (!memberDeclaration2.HasErrors) {
Interactive.Context.MemberDeclarations.Add(text);
}
return memberDeclaration2;
}
// Gather exception traces
private static string Invoke(this compiledCode compiledCode)
{
if (Interactive.Context.MultiLine && !compiledCode.HasErrors) {
Interactive.Context.MultiLine = false;
Interactive.Context.MultiLineStatement = "";
}
if (!Interactive.Context.MultiLine && compiledCode.HasErrors) {
Interactive.TraceErrorMessage(compiledCode);
}
if (!Interactive.Context.MultiLine && !compiledCode.HasErrors && (compiledCode is custExpression || compiledCode is custStatement)) {
Interactive.Context.MultiLine = false;
Interactive.Context.MultiLineStatement = "";
object result = Interactive.InvokeCompiledResult(compiledCode.Results);
if (compiledCode is custExpression) {
return result.FormatOutput();
}
}
return null;
}
// determine the error number in the code
private static void TraceErrorMessage(compiledCode compiledCode)
{
Trace.TraceError(compiledCode.Errors[0].ErrorText);
if (Interactive.Context.VerboseTrace) {
Trace.TraceError(compiledCode.Errors[0].ErrorNumber);
}
}
// Finally, invoke the concatenated code
private static object InvokeCompiledResult(CompilerResults results)
{
Assembly compiledAssembly = results.CompiledAssembly;
Type type = compiledAssembly.GetType("Wrapper");
object obj = Activator.CreateInstance(type, null);
MethodInfo method = type.GetMethod("Eval");
return method.Invoke(obj, null);
}
// method to compile the whole code by incorporating the library class
private static CompilerResults CompileFromSource(this string source)
{
CompilerParameters compilerParameters = new CompilerParameters{
GenerateExecutable = false,
GenerateInMemory = true
};
compilerParameters.ReferencedAssemblies.Add("System.Core.dll");
compilerParameters.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location);
compilerParameters.ReferencedAssemblies.Add("System.Xml.dll");
compilerParameters.ReferencedAssemblies.Add("System.Xml.Linq.dll");
compilerParameters.ReferencedAssemblies.Add("System.Windows.Forms.dll");
foreach(string current in exeContext.Assemblies)
{
compilerParameters.ReferencedAssemblies.Add(current);
}
return Interactive.Compiler.CompileAssemblyFromSource(compilerParameters, new string[] {
source });
}
}
exeContext.cs exeContext将所有类型声明,using语句中的类,程序集和调用堆栈加载到当前上下文中。
public class exeContext {
// Loads using, assembly, type declaration, and member function in linked list
public static List Assemblies = new List();
public IList CallStack = new List();
public IList TypeDeclarations = new List();
public List MemberDeclarations = new List();
public List UsingStatements = new List();
// properties for single, multiline, verbose, and context handling
public bool MultiLine
{
get;
set;
}
public string MultiLineStatement
{
get;
set;
}
public bool VerboseTrace
{
get;
set;
}
public bool Continue
{
get;
set;
}
public exeContext()
{
this.Continue = true;
this.MultiLineStatement = "";
}
// Load the executed assembly into the current context
public static void LoadAssembly(string name)
{
FileInfo fileInfo = new FileInfo(name);
FileInfo fileInfo2 = new FileInfo(Assembly.GetExecutingAssembly().Location);
if (fileInfo.DirectoryName != fileInfo2.DirectoryName) {
if (fileInfo2.DirectoryName != null) {
if (!File.Exists(Path.Combine(fileInfo2.DirectoryName, fileInfo.Name))) {
fileInfo.CopyTo(Path.Combine(fileInfo2.DirectoryName, fileInfo.Name), true);
}
exeContext.Assemblies.Add(fileInfo.Name);
return;
}
}
else {
exeContext.Assemblies.Add(name);
}
}
}
custProBuild.cs交互式编译中的另一个重要类,因为它采用字符串模式的代码指令将它们连接起来,最后以内联详细模式对其进行解释。
public class exeContext
{
// Loads using, assembly, type declaration, and member function in linked list
public static List Assemblies = new List();
public IList CallStack = new List();
public IList TypeDeclarations = new List();
public List MemberDeclarations = new List();
public List UsingStatements = new List();
// properties for single, multiline, verbose, and context handling
public bool MultiLine
{
get;
set;
}
public string MultiLineStatement
{
get;
set;
}
public bool VerboseTrace
{
get;
set;
}
public bool Continue
{
get;
set;
}
public exeContext()
{
this.Continue = true;
this.MultiLineStatement = "";
}
// Load the executed assembly into the current context
public static void LoadAssembly(string name)
{
FileInfo fileInfo = new FileInfo(name);
FileInfo fileInfo2 = new FileInfo(Assembly.GetExecutingAssembly().Location);
if (fileInfo.DirectoryName != fileInfo2.DirectoryName)
{
if (fileInfo2.DirectoryName != null)
{
if (!File.Exists(Path.Combine(fileInfo2.DirectoryName, fileInfo.Name)))
{
fileInfo.CopyTo(Path.Combine(fileInfo2.DirectoryName, fileInfo.Name), true);
}
exeContext.Assemblies.Add(fileInfo.Name);
return;
}
}
else
{
exeContext.Assemblies.Add(name);
}
}
}
custOutput.cs custOutput类通过生成计算结果的XML文件来显示解释后的C#代码的输出,然后以有效形式打印输出。此类接受各种类型的输入,包括数组,字典,XML,可枚举的字符串,并根据传递的值生成输出。
// Abstract class for an exception related properties declaration
public abstract class compiledCode
{
public string Source
{
get;
set;
}
public CompilerResults Results
{
get;
set;
}
public CompilerErrorCollection Errors
{
get;
set;
}
public bool HasErrors
{
get
{
return this.Errors.HasErrors;
}
}
protected compiledCode(string source, CompilerResults results)
{
this.Source = source;
this.Results = results;
this.Errors = this.Results.Errors;
}
public static compiledCode operator |(compiledCode a, compiledCode b)
{
if (a == null)
{
return b;
}
if (b == null)
{
return a;
}
if (!b.HasErrors)
{
return b;
}
return a;
}
public static bool operator false (compiledCode a)
{
return false;
}
public static bool operator true (compiledCode a)
{
return a != null && !a.HasErrors;
}
}
compileCode.cs
此外,compileCode类用于声明一些有助于解释代码并处理意外执行的必要属性。
// Abstract class for an exception related properties declaration
public abstract class compiledCode {
public string Source
{
get;
set;
}
public CompilerResults Results
{
get;
set;
}
public CompilerErrorCollection Errors
{
get;
set;
}
public bool HasErrors
{
get
{
return this.Errors.HasErrors;
}
}
protected compiledCode(string source, CompilerResults results)
{
this.Source = source;
this.Results = results;
this.Errors = this.Results.Errors;
}
public static compiledCode operator | (compiledCode a, compiledCode b)
{
if (a == null) {
return b;
}
if (b == null) {
return a;
}
if (!b.HasErrors) {
return b;
}
return a;
}
public static bool operator false(compiledCode a)
{
return false;
}
public static bool operator true(compiledCode a)
{
return a != null && !a.HasErrors;
}
}
其他类除了上述重要类之外,还有其他一些与自定义表达式,自定义成员和下面声明的类型相关的琐碎代码,它们彼此相互继承。
// Custom Expression Handling
public class custExpression : custStatement {
public custExpression(string source, CompilerResults results)
: base(source, results)
{
}
}
// Custom Member Declaration
public class custMemDecl : cusTypeMem {
public custMemDecl(string source, CompilerResults results)
: base(source, results)
{
}
}
// For Custom statement handling
public class custStatement : compiledCode {
public custStatement(string source, CompilerResults results)
: base(source, results)
{
}
}
// For custom type declaration
public class custTypeDecl : cusTypeMem {
public custTypeDecl(string source, CompilerResults compilerResults)
: base(source, compilerResults)
{
}
}
// Custom Members Declaration
public class cusTypeMem : compiledCode {
protected cusTypeMem(string source, CompilerResults results)
: base(source, results)
{
}
}
将上述类声明合并到Visual Studio 2015或更高版本的内聚项目解决方案中之后。成功构建和编译项目后,将显示以下命令,如下所示。
最后,现在我们可以享受C#代码指令的即时执行(就像我们执行Python指令代码一样),编写代码并按回车的动作,如下所示;
该构造是一个简单的C#解释器,可用于独立命令的命令行编译。它接受内联的C#代码指令,然后在运行中对其进行编译,最终像Python解释器一样在现场执行生成的程序集。