Code refactoring
This commit is contained in:
Родитель
0b94b5a23d
Коммит
9a0648b397
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Spark.CSharp
|
||||
{
|
||||
public interface IScriptEngine
|
||||
{
|
||||
ScriptResult Execute(string code);
|
||||
}
|
||||
}
|
|
@ -1,175 +1,18 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Scripting;
|
||||
using Microsoft.CodeAnalysis.Scripting;
|
||||
using Microsoft.Spark.CSharp.Core;
|
||||
using Microsoft.Spark.CSharp.Sql;
|
||||
using Razorvine.Pickle;
|
||||
using Razorvine.Serpent;
|
||||
|
||||
namespace Microsoft.Spark.CSharp
|
||||
{
|
||||
public class SparkCLRHost
|
||||
{
|
||||
public SparkContext sc;
|
||||
public SqlContext sqlContext;
|
||||
}
|
||||
|
||||
internal class CSharpScriptEngine
|
||||
{
|
||||
private static ScriptState<object> previousState;
|
||||
private static int seq = 0;
|
||||
private static string dllDumpDirectory;
|
||||
|
||||
private static SparkConf sparkConf;
|
||||
private static SparkContext sc;
|
||||
private static SparkCLRHost host;
|
||||
|
||||
static CSharpScriptEngine()
|
||||
{
|
||||
sparkConf = new SparkConf();
|
||||
sc = new SparkContext(sparkConf);
|
||||
host = new SparkCLRHost
|
||||
{
|
||||
sc = sc,
|
||||
sqlContext = new SqlContext(sc)
|
||||
};
|
||||
|
||||
var sparkLocalDir = sparkConf.Get("spark.local.dir", Path.GetTempPath());
|
||||
dllDumpDirectory = Path.Combine(sparkLocalDir, Path.GetRandomFileName());
|
||||
Directory.CreateDirectory(dllDumpDirectory);
|
||||
}
|
||||
|
||||
internal static Script<object> CreateScript(string code)
|
||||
{
|
||||
var scriptOptions = ScriptOptions.Default
|
||||
.AddReferences("System")
|
||||
.AddReferences("System.Core")
|
||||
.AddReferences("Microsoft.CSharp")
|
||||
.AddReferences(typeof(SparkContext).Assembly)
|
||||
.AddReferences(typeof(Pickler).Assembly)
|
||||
.AddReferences(typeof(Parser).Assembly);
|
||||
|
||||
// var extraAssemblies = sc.Files.Where(f => f.ToLower().EndsWith(".dll") && !f.ToLower().StartsWith("hdfs://")).Select(Assembly.LoadFrom);
|
||||
// if (extraAssemblies.Any()) scriptOptions.AddReferences(extraAssemblies);
|
||||
|
||||
return CSharpScript.Create(code, globalsType: typeof(SparkCLRHost)).WithOptions(scriptOptions);
|
||||
}
|
||||
|
||||
internal static object Execute(string code)
|
||||
{
|
||||
Script<object> script;
|
||||
if (previousState == null)
|
||||
{
|
||||
script = CreateScript(code);
|
||||
|
||||
Environment.SetEnvironmentVariable("SPARKCLR_RUN_MODE", "R");
|
||||
if (sparkConf.Get("spark.master", "local").StartsWith("local", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
Environment.SetEnvironmentVariable("SPARKCLR_SCRIPT_COMPILATION_DIR", dllDumpDirectory);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
script = previousState.Script.ContinueWith(code);
|
||||
}
|
||||
|
||||
var diagnostics = script.Compile();
|
||||
bool hasErrors = Enumerable.Any(diagnostics, diagnostic => diagnostic.Severity == DiagnosticSeverity.Error);
|
||||
|
||||
if (hasErrors)
|
||||
{
|
||||
DisplayErrors(script.Compile());
|
||||
return null;
|
||||
}
|
||||
|
||||
var compilationDump = DumpCompilation(script.GetCompilation());
|
||||
if (new FileInfo(compilationDump).Length > 0)
|
||||
{
|
||||
sc.AddFile(new Uri(compilationDump).ToString());
|
||||
}
|
||||
|
||||
ScriptState<object> endState = null;
|
||||
if (previousState == null)
|
||||
{
|
||||
endState = script.RunAsync(host).Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
// "ContinueAsync" is a internal methold now, might be public in 1.2.0(https://github.com/dotnet/roslyn/issues/6612)
|
||||
const string methodName = "ContinueAsync";
|
||||
var m = script.GetType().GetMethod(methodName, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
if (m != null)
|
||||
{
|
||||
endState = ((Task<ScriptState<object>>)m.Invoke(script, new object[] { previousState, default(CancellationToken) })).Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException(string.Format("Can't find method {0}", methodName));
|
||||
}
|
||||
}
|
||||
previousState = endState;
|
||||
return endState.ReturnValue;
|
||||
}
|
||||
|
||||
internal static bool IsCompleteSubmission(string code)
|
||||
{
|
||||
var options = new CSharpParseOptions(LanguageVersion.CSharp6, DocumentationMode.Diagnose, SourceCodeKind.Script);
|
||||
SyntaxTree syntaxTree = SyntaxFactory.ParseSyntaxTree(code, options);
|
||||
return SyntaxFactory.IsCompleteSubmission(syntaxTree);
|
||||
}
|
||||
|
||||
private static void DisplayErrors(IEnumerable<Diagnostic> diagnostics)
|
||||
{
|
||||
// refer to http://source.roslyn.io/#Microsoft.CodeAnalysis.Scripting/Hosting/CommandLine/CommandLineRunner.cs,268
|
||||
try
|
||||
{
|
||||
foreach (var diagnostic in diagnostics)
|
||||
{
|
||||
Console.ForegroundColor = (diagnostic.Severity == DiagnosticSeverity.Error) ? ConsoleColor.Red : ConsoleColor.Yellow;
|
||||
Console.WriteLine(diagnostic.ToString());
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
|
||||
private static string DumpCompilation(Compilation compilation)
|
||||
{
|
||||
var dump = string.Format(@"{0}\ReplCompilation.{1}", dllDumpDirectory, seq++);
|
||||
using (FileStream stream = new FileStream(dump, FileMode.CreateNew))
|
||||
{
|
||||
compilation.Emit(stream);
|
||||
return dump;
|
||||
}
|
||||
}
|
||||
|
||||
public static void DeleteTempFiles()
|
||||
{
|
||||
// delete DLLs dump directory on exit
|
||||
if (Directory.Exists(dllDumpDirectory))
|
||||
{
|
||||
Directory.Delete(dllDumpDirectory, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Repl
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
CSharpScriptEngine.Execute(@"
|
||||
var sparkConf = new SparkConf();
|
||||
var sc = new SparkContext(sparkConf);
|
||||
var scriptEngine = new RoslynScriptEngine(sparkConf, sc);
|
||||
|
||||
scriptEngine.Execute(@"
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Spark.CSharp.Core;
|
||||
|
@ -184,11 +27,11 @@ namespace Microsoft.Spark.CSharp
|
|||
Console.Write("> ");
|
||||
var inputLines = new StringBuilder();
|
||||
bool cancelSubmission = false;
|
||||
ScriptResult scriptResult = null;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var line = Console.ReadLine();
|
||||
|
||||
if (string.IsNullOrEmpty(line))
|
||||
{
|
||||
cancelSubmission = true;
|
||||
|
@ -197,13 +40,13 @@ namespace Microsoft.Spark.CSharp
|
|||
|
||||
if (line.Trim().Equals(":quit", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
CSharpScriptEngine.DeleteTempFiles();
|
||||
scriptEngine.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
inputLines.AppendLine(line);
|
||||
|
||||
if (CSharpScriptEngine.IsCompleteSubmission(inputLines.ToString()))
|
||||
scriptResult = scriptEngine.Execute(inputLines.ToString());
|
||||
if (scriptResult.IsCompleteSubmission)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
@ -216,11 +59,30 @@ namespace Microsoft.Spark.CSharp
|
|||
continue;
|
||||
}
|
||||
|
||||
var returnValue = CSharpScriptEngine.Execute(inputLines.ToString());
|
||||
if (returnValue != null)
|
||||
if (scriptResult.CompileExceptionInfo != null)
|
||||
{
|
||||
Console.WriteLine(returnValue);
|
||||
DisplayException(scriptResult.CompileExceptionInfo.SourceException);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (scriptResult.ReturnValue != null)
|
||||
{
|
||||
Console.WriteLine(scriptResult.ReturnValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void DisplayException(Exception e)
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine(e.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,8 +69,11 @@
|
|||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="IScriptEngine.cs" />
|
||||
<Compile Include="Repl.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="RoslynScriptEngine.cs" />
|
||||
<Compile Include="ScriptResult.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
|
|
|
@ -0,0 +1,185 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Scripting;
|
||||
using Microsoft.CodeAnalysis.Scripting;
|
||||
using Microsoft.Spark.CSharp.Core;
|
||||
using Microsoft.Spark.CSharp.Sql;
|
||||
using Razorvine.Pickle;
|
||||
using Razorvine.Serpent;
|
||||
|
||||
namespace Microsoft.Spark.CSharp
|
||||
{
|
||||
public class SparkCLRHost
|
||||
{
|
||||
public SparkContext sc;
|
||||
public SqlContext sqlContext;
|
||||
}
|
||||
|
||||
public class RoslynScriptEngine : IScriptEngine
|
||||
{
|
||||
private ScriptState<object> previousState;
|
||||
private int seq = 0;
|
||||
private readonly string compilationDumpDirectory;
|
||||
|
||||
private readonly SparkConf sparkConf;
|
||||
private readonly SparkContext sc;
|
||||
private readonly SparkCLRHost host;
|
||||
|
||||
public RoslynScriptEngine(SparkConf sparkConf, SparkContext sc)
|
||||
{
|
||||
this.sparkConf = sparkConf;
|
||||
this.sc = sc;
|
||||
host = new SparkCLRHost
|
||||
{
|
||||
sc = sc,
|
||||
sqlContext = new SqlContext(sc)
|
||||
};
|
||||
|
||||
var sparkLocalDir = sparkConf.Get("spark.local.dir", Path.GetTempPath());
|
||||
compilationDumpDirectory = Path.Combine(sparkLocalDir, Path.GetRandomFileName());
|
||||
Directory.CreateDirectory(compilationDumpDirectory);
|
||||
}
|
||||
|
||||
internal Script<object> CreateScript(string code)
|
||||
{
|
||||
var scriptOptions = ScriptOptions.Default
|
||||
.AddReferences("System")
|
||||
.AddReferences("System.Core")
|
||||
.AddReferences("Microsoft.CSharp")
|
||||
.AddReferences(typeof(SparkContext).Assembly)
|
||||
.AddReferences(typeof(Pickler).Assembly)
|
||||
.AddReferences(typeof(Parser).Assembly);
|
||||
|
||||
// var extraAssemblies = sc.Files.Where(f => f.ToLower().EndsWith(".dll") && !f.ToLower().StartsWith("hdfs://")).Select(Assembly.LoadFrom);
|
||||
// if (extraAssemblies.Any()) scriptOptions.AddReferences(extraAssemblies);
|
||||
return CSharpScript.Create(code, globalsType: typeof(SparkCLRHost)).WithOptions(scriptOptions);
|
||||
}
|
||||
|
||||
public ScriptResult Execute(string code)
|
||||
{
|
||||
if (!IsCompleteSubmission(code))
|
||||
{
|
||||
return ScriptResult.Incomplete;
|
||||
}
|
||||
|
||||
Script<object> script;
|
||||
if (previousState == null)
|
||||
{
|
||||
script = CreateScript(code);
|
||||
|
||||
Environment.SetEnvironmentVariable("SPARKCLR_RUN_MODE", "R");
|
||||
if (sparkConf.Get("spark.master", "local").StartsWith("local", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
Environment.SetEnvironmentVariable("SPARKCLR_SCRIPT_COMPILATION_DIR", compilationDumpDirectory);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
script = previousState.Script.ContinueWith(code);
|
||||
}
|
||||
|
||||
var diagnostics = script.Compile();
|
||||
bool hasErrors = Enumerable.Any(diagnostics, diagnostic => diagnostic.Severity == DiagnosticSeverity.Error);
|
||||
|
||||
if (hasErrors)
|
||||
{
|
||||
DisplayDiagnostics(diagnostics);
|
||||
var message = new StringBuilder();
|
||||
foreach (var diagnostic in diagnostics)
|
||||
{
|
||||
message.Append("[").Append(diagnostic.Severity).Append("] ").Append(": ").Append(diagnostic).Append("\r\n");
|
||||
}
|
||||
return new ScriptResult(null, null, new Exception(message.ToString()));
|
||||
}
|
||||
|
||||
var compilationDump = DumpCompilation(script.GetCompilation());
|
||||
if (new FileInfo(compilationDump).Length > 0)
|
||||
{
|
||||
sc.AddFile(new Uri(compilationDump).ToString());
|
||||
}
|
||||
|
||||
ScriptState<object> endState = null;
|
||||
try
|
||||
{
|
||||
if (previousState == null)
|
||||
{
|
||||
endState = script.RunAsync(host).Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
// "ContinueAsync" is a internal methold now, might be public in 1.2.0(https://github.com/dotnet/roslyn/issues/6612)
|
||||
const string methodName = "ContinueAsync";
|
||||
var m = script.GetType()
|
||||
.GetMethod(methodName,
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
if (m != null)
|
||||
{
|
||||
endState =
|
||||
((Task<ScriptState<object>>)
|
||||
m.Invoke(script, new object[] {previousState, default(CancellationToken)})).Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException(string.Format("Can't find method {0}", methodName));
|
||||
}
|
||||
}
|
||||
previousState = endState;
|
||||
return new ScriptResult(endState.ReturnValue, null, null);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new ScriptResult(null, e, null);
|
||||
}
|
||||
}
|
||||
|
||||
internal void DisplayDiagnostics(IEnumerable<Diagnostic> diagnostics)
|
||||
{
|
||||
// refer to http://source.roslyn.io/#Microsoft.CodeAnalysis.Scripting/Hosting/CommandLine/CommandLineRunner.cs,268
|
||||
try
|
||||
{
|
||||
foreach (var diagnostic in diagnostics)
|
||||
{
|
||||
Console.ForegroundColor = (diagnostic.Severity == DiagnosticSeverity.Error) ? ConsoleColor.Red : ConsoleColor.Yellow;
|
||||
Console.WriteLine(diagnostic.ToString());
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
|
||||
internal bool IsCompleteSubmission(string code)
|
||||
{
|
||||
var options = new CSharpParseOptions(LanguageVersion.CSharp6, DocumentationMode.Diagnose, SourceCodeKind.Script);
|
||||
SyntaxTree syntaxTree = SyntaxFactory.ParseSyntaxTree(code, options);
|
||||
return SyntaxFactory.IsCompleteSubmission(syntaxTree);
|
||||
}
|
||||
|
||||
private string DumpCompilation(Compilation compilation)
|
||||
{
|
||||
var dump = string.Format(@"{0}\ReplCompilation.{1}", compilationDumpDirectory, seq++);
|
||||
using (var stream = new FileStream(dump, FileMode.CreateNew))
|
||||
{
|
||||
compilation.Emit(stream);
|
||||
return dump;
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
// delete DLLs dump directory on exit
|
||||
if (Directory.Exists(compilationDumpDirectory))
|
||||
{
|
||||
Directory.Delete(compilationDumpDirectory, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Spark.CSharp
|
||||
{
|
||||
public class ScriptResult
|
||||
{
|
||||
public static readonly ScriptResult Incomplete = new ScriptResult { IsCompleteSubmission = false };
|
||||
|
||||
public static readonly ScriptResult Empty = new ScriptResult();
|
||||
|
||||
public ScriptResult(
|
||||
object returnValue = null,
|
||||
Exception executionException = null,
|
||||
Exception compilationException = null)
|
||||
{
|
||||
if (returnValue != null)
|
||||
{
|
||||
ReturnValue = returnValue;
|
||||
}
|
||||
|
||||
if (executionException != null)
|
||||
{
|
||||
ExecuteExceptionInfo = ExceptionDispatchInfo.Capture(executionException);
|
||||
}
|
||||
|
||||
if (compilationException != null)
|
||||
{
|
||||
CompileExceptionInfo = ExceptionDispatchInfo.Capture(compilationException);
|
||||
}
|
||||
|
||||
IsCompleteSubmission = true;
|
||||
}
|
||||
|
||||
public object ReturnValue { get; private set; }
|
||||
|
||||
public bool IsCompleteSubmission { get; private set; }
|
||||
|
||||
public ExceptionDispatchInfo ExecuteExceptionInfo { get; private set; }
|
||||
|
||||
public ExceptionDispatchInfo CompileExceptionInfo { get; private set; }
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче