Using Roslyn you can parse c# code into AST and given a c# code snippet, it can be evaluated. You need the following binaries:
Microsoft.CodeAnalysis.CSharp
Microsoft.CodeAnalysis.CSharp.Scripting
CSharpSyntaxTree.ParseText converts a c# code (string) into a SyntaxTree. CSharpScript.EvaluateAsync can be used to evaluate a c# code snippet. There're other useful API for scripting, documented here, including inspecting defined variables, continuing with a previous state, etc.
Microsoft.CodeAnalysis.Scripting.CompilationErrorException: (1,5): error CS0246: The type or namespace name 'DateTime' could not be found (are you missing a using directive or an assembly reference?)
Since the code snippet needs to be "self-contained", namespace needs to be properly used. Below is a fully working example of the parsing and evaluating.
using System; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Scripting;
namespaceGettingStartedCS { classProgram { staticvoidMain(string[] args) { // demonstrate parsing SyntaxTree tree = CSharpSyntaxTree.ParseText(@"var x = new DateTime(2016,12,1);"); Console.WriteLine(tree.ToString()); // new DateTime(2016,12,1)
var result = Task.Run<object>(async () => { // CSharpScript.RunAsync can also be generic with typed ReturnValue var s = await CSharpScript.RunAsync(@"using System;");
// continuing with previous evaluation state s = await s.ContinueWithAsync(@"var x = ""my/"" + string.Join(""_"", ""a"", ""b"", ""c"") + "".ss"";"); s = await s.ContinueWithAsync(@"var y = ""my/"" + @x;"); s = await s.ContinueWithAsync(@"y // this just returns y, note there is NOT trailing semicolon");
// inspecting defined variables Console.WriteLine("inspecting defined variables:"); foreach (var variable in s.Variables) { Console.WriteLine("name: {0}, type: {1}, value: {2}", variable.Name, variable.Type.Name, variable.Value); } return s.ReturnValue; }).Result; Console.WriteLine("Result is: {0}", result); } } }
The above code give the output:
1 2 3 4 5
var x = new DateTime(2016,12,1); inspecting defined variables: name: x, type: String, value: my/a_b_c.ss name: y, type: String, value: my/my/a_b_c.ss Result is: my/my/a_b_c.ss
Same thing can be used in F#. I can successfully use it in a F# console application with nuget (via Visual Studio).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.CSharp open Microsoft.CodeAnalysis.CSharp.Scripting
let ast = CSharpSyntaxTree.ParseText("""var x = new DateTime(2016,12,1);""") printfn "%s" (ast.ToString())
let result = async { let! s = CSharpScript.RunAsync("""using System;""") |> Async.AwaitTask let! s = s.ContinueWithAsync("""var x = "my/" + string.Join("_", "a", "b", "c") + ".ss";""") |> Async.AwaitTask let! s = s.ContinueWithAsync("""var y = "my/" + @x;""") |> Async.AwaitTask let! s = s.ContinueWithAsync("""y""") |> Async.AwaitTask return s.ReturnValue } |> Async.RunSynchronously
printfn "%A" result
Problem with references in F# scripts
I have difficulty using it in a F# script file with paket. The problem is runtime error that it can't load System.Collections.Immutable or the loaded System.Collections.Immutable is missing a methd/class. It turns out paket pulls doesn stable release for the below direct dependencies that caused the issue. If I switch to use prerelease, the problem is gone.
Microsoft.CodeAnalysis.CSharp
Microsoft.CodeAnalysis.CSharp.Scripting
Here're the necessary setting for the fsx to work: