📜  C#交互式口译员

📅  最后修改于: 2021-05-29 19:34:08             🧑  作者: Mango

本文旨在通过.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解释器一样在现场执行生成的程序集。