diff --git a/ScriptCs.sln b/ScriptCs.sln index 79d00af5..d3b31a96 100644 --- a/ScriptCs.sln +++ b/ScriptCs.sln @@ -33,6 +33,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScriptCs.Engine.Roslyn", "s EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScriptCs.Engine.Roslyn.Tests", "test\ScriptCs.Engine.Roslyn.Tests\ScriptCs.Engine.Roslyn.Tests.csproj", "{28D11DE5-9F98-4E0A-8CCC-9CDC19110451}" EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "ScriptCs.Engine.FSharp", "src\ScriptCs.Engine.FSharp\ScriptCs.Engine.FSharp.fsproj", "{113CDD96-0824-491E-B332-62D4FDE02A1E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -67,6 +69,10 @@ Global {28D11DE5-9F98-4E0A-8CCC-9CDC19110451}.Debug|Any CPU.Build.0 = Debug|Any CPU {28D11DE5-9F98-4E0A-8CCC-9CDC19110451}.Release|Any CPU.ActiveCfg = Release|Any CPU {28D11DE5-9F98-4E0A-8CCC-9CDC19110451}.Release|Any CPU.Build.0 = Release|Any CPU + {113CDD96-0824-491E-B332-62D4FDE02A1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {113CDD96-0824-491E-B332-62D4FDE02A1E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {113CDD96-0824-491E-B332-62D4FDE02A1E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {113CDD96-0824-491E-B332-62D4FDE02A1E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/ScriptCs.Engine.FSharp/FSharpScriptEngine.fs b/src/ScriptCs.Engine.FSharp/FSharpScriptEngine.fs new file mode 100644 index 00000000..80b873f7 --- /dev/null +++ b/src/ScriptCs.Engine.FSharp/FSharpScriptEngine.fs @@ -0,0 +1,104 @@ +namespace ScriptCs.Engine.FSharp +open ScriptCs +open Common.Logging +open System +open System.IO +open System.Collections.Generic +open Microsoft.FSharp.Compiler.Interactive.Shell +open ExtCore +open System.Linq + +type Result = Success of String | Error of string | Incomplete + +type FSharpEngine(host:ScriptHost) = + let stdin = new StreamReader(System.IO.Stream.Null) + + let stdoutStream = new CompilerOutputStream() + let stdout = StreamWriter.Synchronized(new StreamWriter(stdoutStream, AutoFlush=true)) + + let stderrStream = new CompilerOutputStream() + let stderr = StreamWriter.Synchronized(new StreamWriter(stderrStream, AutoFlush=true)) + + let getOutput (session: FsiEvaluationSession) code = + + let tryget() = + let error = stderrStream.Read() + if error.Length > 0 then Error(error) else + Success(stdoutStream.Read()) + + try session.EvalInteraction(code) + if code.EndsWith ";;" then tryget() + else Incomplete + with ex -> Error ex.Message + + + let commonOptions = [| "fsi.exe"; "--nologo"; "--readline-";|] + let session = FsiEvaluationSession(commonOptions, stdin, stdout, stderr) + + let (>>=) (d1:#IDisposable) (d2:#IDisposable) = + {new IDisposable with member x.Dispose() = d1.Dispose(); d2.Dispose()} + + member x.Execute(code) = + getOutput session code + + member x.AddReference(ref) = + session.EvalInteraction(sprintf "#r @\"%s\"" ref) + + member x.SilentAddReference(ref) = + x.AddReference(ref) + stdoutStream.Read() |> ignore + + member x.ImportNamespace(namespace') = + session.EvalInteraction(sprintf "open %s" namespace') + + member x.SilentImportNamespace(namespace') = + x.ImportNamespace(namespace') + stdoutStream.Read() |> ignore + + interface IDisposable with + member x.Dispose() = + (stdin >>= stdoutStream >>= stdout >>= stderrStream >>= stderr).Dispose() + +type FSharpScriptEngine( scriptHostFactory:IScriptHostFactory, logger: ILog) = + let mutable baseDir = String.empty + let []sessionKey = "F# Session" + + interface IScriptEngine with + member x.BaseDirectory with get() = baseDir and set value = baseDir <- value + member x.Execute(code, args, references, namespaces, scriptPackSession) = + let distinctReferences = references.Union(scriptPackSession.References).Distinct() + let sessionState = + match scriptPackSession.State.TryGetValue sessionKey with + | false, _ -> let host = scriptHostFactory.CreateScriptHost(ScriptPackManager(scriptPackSession.Contexts), args) + logger.Debug("Creating session") + let session = new FSharpEngine(host) + + distinctReferences |> Seq.iter (fun ref -> logger.DebugFormat("Adding reference to {0}", ref) + session.SilentAddReference ref ) + + namespaces.Union(scriptPackSession.Namespaces).Distinct() + |> Seq.iter (fun ns -> logger.DebugFormat("Importing namespace {0}", ns) + session.SilentImportNamespace ns) + + let sessionState = SessionState<_>(References = distinctReferences, Session = session) + scriptPackSession.State.Add(sessionKey, sessionState) + sessionState + | true, res -> logger.Debug("Reusing existing session") + let sessionState = res :?> SessionState + + let newReferences = match sessionState.References with + | null -> distinctReferences + | refs when Seq.isEmpty refs -> distinctReferences + | refs -> distinctReferences.Except refs + newReferences |> Seq.iter (fun ref -> logger.DebugFormat("Adding reference to {0}", ref) + sessionState.Session.AddReference ref ) + sessionState + + match sessionState.Session.Execute(code) with + | Success result -> let cleaned = + result.Split([|"\r"; "\n";|], StringSplitOptions.RemoveEmptyEntries) + |> Array.filter (fun str -> not(str = "> ")) + |> String.concat "\r\n" + ScriptResult(ReturnValue = cleaned) + | Error e -> ScriptResult(CompileException = exn e ) + | Incomplete -> ScriptResult() \ No newline at end of file diff --git a/src/ScriptCs.Engine.FSharp/ScriptCs.Engine.FSharp.fsproj b/src/ScriptCs.Engine.FSharp/ScriptCs.Engine.FSharp.fsproj new file mode 100644 index 00000000..428f122b --- /dev/null +++ b/src/ScriptCs.Engine.FSharp/ScriptCs.Engine.FSharp.fsproj @@ -0,0 +1,84 @@ + + + + + Debug + AnyCPU + 2.0 + 113CDD96-0824-491E-B332-62D4FDE02A1E + Library + ScriptCs.Engine.FSharp + ScriptCs.Engine.FSharp + v4.5 + ScriptCs.Engine.FSharp + ..\ + true + 10.0.0 + + + true + full + false + false + bin\Debug\ + DEBUG;TRACE + 3 + bin\Debug\ScriptCs.Engine.FSharp.XML + + + pdbonly + true + true + bin\Release\ + TRACE + 3 + bin\Release\ScriptCs.Engine.FSharp.XML + + + 11 + + + + + + + + + + ScriptCs.Contracts + {6049e205-8b5f-4080-b023-70600e51fd64} + True + + + ..\..\packages\Common.Logging.2.1.2\lib\net40\Common.Logging.dll + True + + + ..\..\packages\ExtCore.0.8.29\lib\net40\ExtCore.dll + True + + + True + + + True + + + + + + + + ScriptCs.Core + {e590e710-e159-48e6-a3e6-1a83d3fe732c} + True + + + + \ No newline at end of file diff --git a/src/ScriptCs.Engine.FSharp/packages.config b/src/ScriptCs.Engine.FSharp/packages.config new file mode 100644 index 00000000..f0bd15c2 --- /dev/null +++ b/src/ScriptCs.Engine.FSharp/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/ScriptCs/Command/ExecuteReplCommand.cs b/src/ScriptCs/Command/ExecuteReplCommand.cs index 0c081711..d9e5cdb7 100644 --- a/src/ScriptCs/Command/ExecuteReplCommand.cs +++ b/src/ScriptCs/Command/ExecuteReplCommand.cs @@ -56,7 +56,7 @@ public CommandResult Execute() catch (Exception ex) { _logger.Error(ex.Message); - return CommandResult.Error; + return CommandResult.Error; } repl.Terminate(); return CommandResult.Success; @@ -108,4 +108,4 @@ private bool IsManagedAssembly(string path) return true; } } -} +} \ No newline at end of file diff --git a/src/ScriptCs/CompositionRoot.cs b/src/ScriptCs/CompositionRoot.cs index 0ed06d33..b49b49c6 100644 --- a/src/ScriptCs/CompositionRoot.cs +++ b/src/ScriptCs/CompositionRoot.cs @@ -8,6 +8,7 @@ using ScriptCs.Engine.Roslyn; using ScriptCs.Package; using ScriptCs.Package.InstallationProvider; +using ScriptCs.Engine.FSharp; namespace ScriptCs { @@ -58,11 +59,13 @@ public void Initialize() { builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As(); } else { builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As(); } builder.RegisterType().As(); diff --git a/src/ScriptCs/ScriptCs.csproj b/src/ScriptCs/ScriptCs.csproj index 5fc6d0e2..12136e5a 100644 --- a/src/ScriptCs/ScriptCs.csproj +++ b/src/ScriptCs/ScriptCs.csproj @@ -99,6 +99,10 @@ {e590e710-e159-48e6-a3e6-1a83d3fe732c} ScriptCs.Core + + {113cdd96-0824-491e-b332-62d4fde02a1e} + ScriptCs.Engine.FSharp + {e79ec231-e27d-4057-91c9-2d001a3a8c3b} ScriptCs.Engine.Roslyn diff --git a/src/ScriptCs/app.config b/src/ScriptCs/app.config index cb1b9770..6edf0e06 100644 --- a/src/ScriptCs/app.config +++ b/src/ScriptCs/app.config @@ -9,6 +9,10 @@ + + + + \ No newline at end of file