C# Interactive Interpreter

This article is designed to showcase the step by step implementation of Interactive C# Interpreter Development by virtue of .NET framework codeDOM API’s which enables us to dynamically evaluate C# code expressions and statements for testing mini code fragments or scripts more or less like to Python Interpreter. However, the visual studio is capable enough to do so, then the question occurs why we need it? The visual studio IDE compiler typically compiles the whole application classes every time rather than a chunk of code segments if required. This process is, therefore, relatively cumbersome, time-consuming, and inflicts an unduly extra overhead on the file system.

In this regard, there are multiple classes will be carved-out with varied functionality. The aspirant is supposed to have a proficiency in .NET technology as this project is a console-based application where the interpreter gives us a command-line manner impression to operate the commands. Hence, the following C# code snippet is the entry point of the application wherefrom the actual execution will be begun by incorporating the calls of all other subsidiary essential classes. In the entry point class called Program, we found the banner to be displayed along with the custom exception handling functionality.

program.cs

filter_none

edit
close

play_arrow

link
brightness_4
code

// 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);
            }
        }
    }
}

chevron_right


outString.cs The outString class held responsible for the initialization of essentials variable which will be conducive to printing the result on the shell.

filter_none

edit
close

play_arrow

link
brightness_4
code

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;
    }
}

chevron_right


Interactive.cs The Interactive class encapsulates all the necessary code in importing APIs, Invoking the execution session, Regular expression handling and dealt with the compiler and interpreter API and the method by invoking CodeDOM class. Overall, this is the sole class to interpret the statement, just like an engine.



filter_none

edit
close

play_arrow

link
brightness_4
code

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 });
    }
}

chevron_right


exeContext.cs The exeContext loads all the type declaration, classes in using statements, assemblies, and call stack into the current context.

filter_none

edit
close

play_arrow

link
brightness_4
code

public class exeContext {
    // Loads using, assembly, type declaration, and member function in linked list
    public static List<string> Assemblies = new List<string>();
    public IList<string> CallStack = new List<string>();
    public IList<string> TypeDeclarations = new List<string>();
    public List<string> MemberDeclarations = new List<string>();
    public List<string> UsingStatements = new List<string>();
  
    // 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);
        }
    }
}

chevron_right


custProBuild.cs Another important one class in interactive compiles as it takes the code instruction in string mode concatenated them and finally interpreted it in inline verbose mode.

filter_none

edit
close

play_arrow

link
brightness_4
code

public class exeContext
{
    // Loads using, assembly, type declaration, and member function in linked list
    public static List<string> Assemblies = new List<string>();
    public IList<string> CallStack = new List<string>();
    public IList<string> TypeDeclarations = new List<string>();
    public List<string> MemberDeclarations = new List<string>();
    public List<string> UsingStatements = new List<string>();
    // 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);
        }
    }
}

chevron_right


custOutput.cs The custOutput class displayed the output of the interpreted C# codes by generating an XML file of the calculated resulted and then printed the output in the valid form. This class accepts input in various types including Array, dictionary, XML, enumerable, string and produce the output based on the passed value.

filter_none

edit
close

play_arrow

link
brightness_4
code

// 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;
    }
}

chevron_right


compileCode.cs
Furthermore, the compileCode class is used to declare some essential properties conducive during the interpretation of code and dealing with the occurrence of unexpected execption.

filter_none

edit
close

play_arrow

link
brightness_4
code

// 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;
    }
}

chevron_right


Other classes Apart from the above important classes, there are other trivial classes code related to custom expression, custom member, and types declared below which are mutually inherited to each other.

filter_none

edit
close

play_arrow

link
brightness_4
code

// 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)
    {
    }
}

chevron_right


After incorporating the aforesaid class declaration into a cohesive project solution of visual studio 2015 or later. The following command will be displayed as a resulted after successfully build and compile of the project as follows.

Finally, now we can enjoy the instant execution of C# code instructions (like as we execute the python instruction code), the movement we write the code and press enter as following;

This construct is a simple C# interpreter that enables the command-line compilation of standalone commands. It accepts an inline C# code instruction and later on compiles it on the fly, finally executing the resulting assembly on the spot like Python interpreter.




My Personal Notes arrow_drop_up

Check out this Author's contributed articles.

If you like GeeksforGeeks and would like to contribute, you can also write an article using contribute.geeksforgeeks.org or mail your article to contribute@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks.

Please Improve this article if you find anything incorrect by clicking on the "Improve Article" button below.



Improved By : nidhi_biet

Article Tags :

Be the First to upvote.


Please write to us at contribute@geeksforgeeks.org to report any issue with the above content.