Using Roslyn to build a simple C# interactive script engine

The .NET Compiler Platform (a.k.a. Roslyn) version 1.0-rc2 was recently released and got go-live license which means the API’s are to be considered fairly stable. In this post I’ll demonstrate how to build a simple C# interactive scripting* editor using the Scripting API (like the F# interactive window which been around in Visual Studio since…like forever). The idea is to be able to evaluate some code in a REPL but also have the possibility to submit code to it from an editor. Let’s start building the core component of the application and then put on a nice GUI on top of it.

The simple script engine looks like this:


public class CSharpScriptEngine
{
    private static ScriptState scriptState = null;
    public static object Execute(string code)
    {
        scriptState = scriptState == null ? CSharpScript.RunAsync(code).Result : scriptState.ContinueWithAsync(code).Result;
        if (scriptState.ReturnValue != null && !string.IsNullOrEmpty(scriptState.ReturnValue.ToString()))
            return scriptState.ReturnValue;
        return null;
    }
}

This code utilizes the Microsoft.CodeAnalysis.CSharp.Scripting package available at NuGet.
The `CSharpScript.RunAsync` method runs a C# script while `scriptState.ContinueWithAsync` continues the previously submitted code with the current submitted one.
With this in place we can try out the script engine:


class Program
{
    static void Main(string[] args)
    {
        CSharpScriptEngine.Execute(
            //This could be code submitted from the editor
            @"
            public class ScriptedClass
            {
                public String HelloWorld {get;set;}
                public ScriptedClass()
                {
                    HelloWorld = ""Hello Roslyn!"";
                }
            }");
        //And this from the REPL
        Console.WriteLine(CSharpScriptEngine.Execute("new ScriptedClass().HelloWorld"));
        Console.ReadKey();
    }
}
//Output: "Hello Roslyn!"

Let’s now put a simple editor and a REPL on top of the script engine. The REPL will be a self-hosted Web API console application to which the user can submit code from the editor but also write expressions in the command prompt. The setup of the REPL project is fairly straight-forward and I will only show the interesting parts for brevity (no error handling etc). The `Main` function sets up the Web API part and then listen for input from the user while the `Post` method in the controller listens for submitted code from the editor:


static void Main(string[] args)
{
    string baseAddress = "http://localhost:9000/"; 
    // Start OWIN host 
    using (WebApp.Start(url: baseAddress))
    {
        while (true)
        {
            var str = Console.ReadLine();
            Console.WriteLine(CSharpScriptEngine.Execute(str));
        }
    }
}

public class CodeController : ApiController
{
    // POST api/code 
    public void Post([FromBody]string code)
    {
        Console.WriteLine(CSharpScriptEngine.Execute(code));
    }
}

The code editor in this case is a simple Windows Form application with a multiline text box as the editor window.
When the user has written a script she selects it and presses Alt + Enter and the code is submitted to the REPL application:


private void CodeEdit_KeyDown(object sender, KeyEventArgs e)
{
    if (CodeEdit.SelectedText.Length > 0 && e.KeyCode == Keys.Enter && e.Alt)
    {
        string baseAddress = "http://localhost:9000/";
        using (var client = new HttpClient())
        {
            var response = client.PostAsJsonAsync(baseAddress + "api/code", CodeEdit.SelectedText).Result;
        }
    }
}

The code editor can now be used to run the scripts as shown above:
ScriptEditor
REPL

Conclusion

Roslyn makes it really easy to build tools around C#/VB.NET. In this post I have shown how you, with just a few lines of code, can build an interactive C# script application.
The complete application code is available here.
Learn more about Roslyn on its Github repo.

* The scripting API’s are not yet available in the v1.0-rc2 “Go-Live” release and aren’t available on NuGet. They can however be fetched from the Rosyln myget feed

This Post Has 18 Comments

  1. Hi Christian,

    Thanks for the post.
    I’m trying to get your sample running but I can’t find “Microsoft.CodeAnalysis.Scripting” package on nuguet?

    Kind Regards,
    Dustyn

    1. Hi Dustyn,
      My bad, the scripting API’s aren’t included in the main release but are still actively being investigated by the Roslyn team. You can still download the scripting API’s by adding https://www.myget.org/F/roslyn-nightly/ to your NuGet package sources (Tools/NuGet Package Manager/Package Sources). I’ll update the post.
      Thanks

  2. I was trying to download Microsoft.CodeAnalysis.Scripting version 1.1.0-rc1-20151014-15 (Prerelease Last Published: 10/15/2015) but got error ”
    Unable to resolve dependency ‘System.Reflection.Metadata (≥ 1.1.0-beta-23413)'”. Is there a way to have someone publish the required System.Reflection.Metadata version?

    1. Hi Wenning,
      I don’t know the reason for the error. Did you try to run the sample in the blog (source available here)? It uses version 1.0.0-rc2 of the scripting package and should work.

  3. Thank you for a great post. Unfortunately there seems like there has been some breaking changes, so that I can not get it to run. This line “var script = CSharpScript.Create(code, ScriptOptions.Default).WithPrevious(_previousInput);” fails because WithPrevious does not exist anymore. Do you know how to update the code to get it running?

    1. Thanks for your comment Roger!
      I don’t know how to update the code to get it running unfortunately but I will do some investigation!

  4. I did some repo diving, and…

    WithPrevious changes to ContinueWith: https://github.com/dotnet/roslyn/commit/c655c2ccaa48270ee46012c523ebdd5c29965fc3

    Run changes to RunAsync:
    https://github.com/dotnet/roslyn/commit/083258058150b83646fa3fc60ba0cd55c4d88eb5

    I also removed your _nextInputState variable because Roslyn now requires its type to be specified and I wasn’t sure what to tell it.

    And the `String` type is no longer available without a using declaration, so I changed it to `string`.

    The result, which runs just fine:

    public class CSharpScriptEngine
    {
    private static Script _previousInput;

    public static object Execute(string code)
    {
    Script script;
    if (_previousInput == null)
    { script = CSharpScript.Create(code); }
    else
    { script = _previousInput.ContinueWith(code); }

    var endState = script.RunAsync().Result;
    _previousInput = endState.Script;
    return endState.ReturnValue;
    }
    }

    class Program
    {
    static void Main(string[] args)
    {
    CSharpScriptEngine.Execute(
    //This could be code submitted from the editor
    @”
    public class ScriptedClass
    {
    public string HelloWorld {get;set;}
    public ScriptedClass()
    {
    HelloWorld = “”Hello Roslyn!””;
    }
    }”);
    //And this from the REPL
    Console.WriteLine(CSharpScriptEngine.Execute(“new ScriptedClass().HelloWorld”));
    Console.ReadKey();
    }
    }

    1. Great find! I’ll update the post.
      Thanks!

  5. Well, I’m using this code with very few changes and it’s pretty robust. I need to add some actual code analysis for my project, but the REPL doesn’t need many changes. This is the only place I’ve found how to do this, so thanks!

    I found a small gotcha: The following code (which is a static version of what I use now) will trigger a very strange `AccessViolationException` about not being able to access `System.RuntimeType`. tl;dr Roslyn doesn’t work well with anonymous types. That might seem obvious, but it bit me so I think it’s worth stating.

    public class CSharpScriptEngine
    {
    private static Script _previousInput;

    private static object _globals = new {};

    public static AggregateException Exception { get; private set; }

    public static object Execute(string code)
    {
    Script script;
    if (_previousInput == null)
    { script = CSharpScript.Create(code, null, _globals.GetType()); }
    else
    { script = _previousInput.ContinueWith(code); }

    var task = script.RunAsync(_globals);
    task.Wait();

    Exception = task.Exception;

    if (Exception == null)
    {
    var endState = task.Result;
    _previousInput = endState.Script;
    return endState.ReturnValue;
    }
    else
    { return null; }
    }
    }

    1. Thanks for this, awesome! :)

  6. I was also getting an AccessViolationException
    I found the problem was private static object _globals = new {};

    If you use a real class the problem goes away:

    class Globals{ public int X = 1; }
    private static Globals _globals = new Globals();

    1. Nice, thanks!

  7. Finally got around to fix the issues with the script engine code. In order to make it work I needed to upgrade to .NET 4.6.1 and switch package Microsoft.CodeAnalysis.Scripting.CSharp with Microsoft.CodeAnalysis.CSharp.Scripting. See above for the final version of the CSharpScriptEngine class.

  8. I Could not get the code working. CSharpScriptEngine.Execute gives an error stating it could not load the System.Runtime. Do you have a version of this code working with the latest Rosslyn

    1. Hi Andy,

      I had some issues with the latest version in the repo which I’ve fixed. They were not about issues loading System.Runtime though. Are you using Visual Studio 2013? If so, try running it with Visual Studio 2015 instead if you have it available.

  9. Hi, I was looking for the way to host c# scripting engine into my application and came here. It’s been a long time since you posted and it seems less likely that you see this but I’d still like to say I very much appreciated your article and the example. Thank you so much.

Leave a Reply