aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Scripting/Tango.Scripting/ScriptingEngine.cs
blob: b3f5348f8e4ba4e08961b3a16644aa79e55e94e2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
generated by cgit v1.3.1 (git 2.54.0) at 2026-06-22 17:24:30 +0000
 


>254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Tango.Scripting
{
    public class ScriptingEngine : IScriptingEngine
    {
        private class IncludeResult
        {
            public String File { get; set; }
            public List<String> Lines { get; set; }
        }

        public Task<String> Compile(Script script)
        {
            return Task.Factory.StartNew<String>(() =>
            {
                String code = script.Code;
                List<IncludeResult> includeResults = new List<IncludeResult>();

                try
                {
                    includeResults.Add(new IncludeResult() { File = script.File, Lines = script.Code.ToLines() });
                    code = ReplaceIncludes(script, code, script.WorkingFolder, includeResults);
                }
                catch (Exception ex)
                {
                    throw new CompilationException(new CompilationError() { Message = ex.Message });
                }

                var options = CreateOptions(script);

                var csharpScript = CSharpScript.Create(code, options: options, globalsType: script.GlobalObject.GetType());

                var results = csharpScript.Compile();

                List<CompilationError> errors = new List<CompilationError>();

                foreach (var result in results.Where(x => x.Severity == DiagnosticSeverity.Error))
                {
                    CompilationError error = new CompilationError();
                    error.Message = result.GetMessage();
                    error.Character = result.Location.GetMappedLineSpan().StartLinePosition.Character + 1;

                    int lineIndex = 0;
                    IncludeResult include = null;

                    foreach (var inc in includeResults)
                    {
                        for (int i = 0; i < inc.Lines.Count; i++)
                        {
                            if (inc.Lines[i] == code.ToLines()[result.Location.GetMappedLineSpan().StartLinePosition.Line])
                            {
                                include = inc;
                                lineIndex = i;
                                break;
                            }
                        }
                    }

                    if (include != null)
                    {
                        error.File = include.File;
                        error.Line = lineIndex + 1;
                    }

                    errors.Add(error);
                }

                if (errors.Count > 0)
                {
                    throw new CompilationException(errors.ToArray());
                }

                return code;
            });
        }

        public Task<ScriptSession> Run(Script script)
        {
            return Task.Factory.StartNew<ScriptSession>(() =>
            {
                var effectivCode = Compile(script).Result;

                var options = CreateOptions(script);

                var _cancaller = new CancellationTokenSource();

                Thread scriptThread = null;
                ScriptSession session = null;

                session = new ScriptSession(script, effectivCode, () =>
                {
                    scriptThread.Abort();
                });

                scriptThread = new Thread(() =>
                {
                    try
                    {
                        var result = CSharpScript.RunAsync(effectivCode, options: options, globals: script.GlobalObject, cancellationToken: _cancaller.Token).Result;
                        session.Completed(result.ReturnValue);
                    }
                    catch (ThreadAbortException)
                    {

                    }
                    catch (Exception ex)
                    {
                        session.Failed(ex.InnerException);
                    }
                });

                scriptThread.SetApartmentState(script.ApartmentState);
                scriptThread.IsBackground = true;
                scriptThread.Start();

                return session;
            });
        }

        public void Dispose()
        {
            throw new NotImplementedException();
        }

        #region Private Methods

        private List<String> GetIncludes(String code, String workingFolder)
        {
            if (Directory.Exists(workingFolder))
            {
                Environment.CurrentDirectory = workingFolder;
            }

            List<String> lines = code.ToLines();
            List<String> includeFiles = new List<string>();

            for (int i = 0; i < lines.Count; i++)
            {
                var line = lines[i];

                if (line.Trim().StartsWith("include"))
                {
                    String path = line.Replace("include", "").Trim().Replace("\"", "");

                    if (!File.Exists(path))
                    {
                        throw new FileNotFoundException("Could not locate include file '" + path + "'.");
                    }

                    includeFiles.Add(path);
                }
            }

            return includeFiles;
        }

        private String ReplaceIncludes(Script script, String code, String workingFolder, List<IncludeResult> includeResults)
        {
            if (Directory.Exists(workingFolder))
            {
                Environment.CurrentDirectory = workingFolder;
            }

            List<String> lines = code.ToLines().ToList();

            for (int i = 0; i < lines.Count; i++)
            {
                var line = lines[i];

                if (line.Trim().StartsWith("include"))
                {
                    String path = line.Replace("include", "").Trim().Replace("\"", "");

                    if (!File.Exists(path))
                    {
                        throw new FileNotFoundException("Could not locate include file '" + path + "'.");
                    }

                    String includeContent = File.ReadAllText(path);

                    includeResults.Add(new IncludeResult() { File = path, Lines = includeContent.ToLines() });

                    String content = ReplaceIncludes(script, includeContent, Path.GetDirectoryName(path), includeResults);

                    if (!String.IsNullOrWhiteSpace(script.EntryPoint))
                    {
                        if (content.Contains(script.EntryPoint + "("))
                        {
                            throw new InvalidProgramException(String.Format("Include file '{0}' contains an OnExecute method. Please remove it before trying to compile.", path));
                        }
                    }

                    lines[i] = content;
                }
            }

            code = ClearUsings(String.Join(Environment.NewLine, lines));

            return code;
        }

        private String ClearUsings(String code)
        {
            List<String> usings = new List<string>();

            List<String> lines = code.ToLines();

            foreach (var line in lines)
            {
                if (line.Trim().StartsWith("using"))
                {
                    usings.Add(line);
                }
            }

            lines.RemoveAll(x => x.Trim().StartsWith("using"));

            return String.Join(Environment.NewLine, usings.Distinct()) + Environment.NewLine + String.Join(Environment.NewLine, lines);
        }

        private ScriptOptions CreateOptions(Script script)
        {
            //My References.
            var options = ScriptOptions.Default;

            //My Assemblies.
            options = options.AddReferences(typeof(Form).Assembly.Location);
            options = options.AddReferences(typeof(Enumerable).Assembly.Location);
            options = options.AddReferences(typeof(ScriptingEngine).Assembly.Location);

            foreach (var asm in script.ReferenceAssemblies)
            {
                options = options.AddReferences(asm.File);
            }

            //Imports.
            options = options.AddImports(
            "System",
            "System.Collections.Generic",
            "System.Linq",
            "System.Text",
            "System.Diagnostics",
            "System.Windows.Forms",
            "System.Threading"
            );

            if (script.Imports.Count > 0)
            {
                options = options.AddImports(script.Imports);
            }

            return options;
        }

        #endregion
    }
}