Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.HLScript/3.3/Script.cs @ 10401

Last change on this file since 10401 was 10401, checked in by jkarder, 10 years ago

#2136:

  • renamed some HLScript files
  • fixed a bug where variables could be renamed while a script was running
  • the complete source code is now always visible
  • removed "show generated code" feature
File size: 8.2 KB
RevLine 
[10332]1using System;
2using System.CodeDom;
3using System.CodeDom.Compiler;
4using System.Collections.Generic;
5using System.Drawing;
6using System.IO;
7using System.Linq;
8using System.Reflection;
9using System.Text;
10using System.Text.RegularExpressions;
11using System.Threading;
12using HeuristicLab.Common;
13using HeuristicLab.Common.Resources;
14using HeuristicLab.Core;
15using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
16using Microsoft.CSharp;
17
18namespace HeuristicLab.HLScript {
[10401]19  [Item("Script", "A HeuristicLab script.")]
[10332]20  [Creatable("Scripts")]
21  [StorableClass]
[10401]22  public sealed class Script : NamedItem, IStorableContent {
[10332]23    #region Constants
24    private const string ExecuteMethodName = "Execute";
25    private const string CodeTemplate =
26@"// use 'vars' to access global variables in the variable store
27
28using System;
29
[10401]30namespace UserScripts {
31  public class UserScript : HeuristicLab.HLScript.UserScriptBase {
32    public override void Main() {
33      // type your code here
34    }
[10332]35
[10401]36    // further classes and methods
37
38  }
39}";
[10332]40    #endregion
41
42    #region Fields & Properties
[10401]43    private UserScriptBase compiledScript;
[10332]44
45    public string Filename { get; set; }
46
47    public static new Image StaticItemImage {
48      get { return VSImageLibrary.Script; }
49    }
50
51    [Storable]
52    private VariableStore variableStore;
53    public VariableStore VariableStore {
54      get { return variableStore; }
55    }
56
57    [Storable]
58    private string code;
59    public string Code {
60      get { return code; }
61      set {
62        if (value == code) return;
63        code = value;
[10401]64        compiledScript = null;
[10332]65        OnCodeChanged();
66      }
67    }
68
69    private string compilationUnitCode;
70    public string CompilationUnitCode {
71      get { return compilationUnitCode; }
72    }
73
74    private CompilerErrorCollection compileErrors;
75    public CompilerErrorCollection CompileErrors {
76      get { return compileErrors; }
77      private set {
78        compileErrors = value;
79        OnCompileErrorsChanged();
80      }
81    }
82    #endregion
83
84    #region Construction & Initialization
85    [StorableConstructor]
[10401]86    private Script(bool deserializing) : base(deserializing) { }
87    private Script(Script original, Cloner cloner)
[10332]88      : base(original, cloner) {
89      code = original.code;
[10358]90      variableStore = new VariableStore();
[10332]91      compilationUnitCode = original.compilationUnitCode;
92      if (original.compileErrors != null)
93        compileErrors = new CompilerErrorCollection(original.compileErrors);
94    }
95
[10401]96    public Script()
97      : base("Script", "A HeuristicLab script.") {
[10332]98      code = CodeTemplate;
99      variableStore = new VariableStore();
100    }
101
102    public override IDeepCloneable Clone(Cloner cloner) {
[10401]103      return new Script(this, cloner);
[10332]104    }
105    #endregion
106
107    private void RegisterScriptEvents() {
[10401]108      if (compiledScript == null) return;
109      compiledScript.ConsoleOutputChanged += compiledScript_ConsoleOutputChanged;
[10332]110    }
111
112    private void DeregisterScriptEvents() {
[10401]113      if (compiledScript == null) return;
114      compiledScript.ConsoleOutputChanged -= compiledScript_ConsoleOutputChanged;
[10332]115    }
116
117    #region Compilation
118    private CSharpCodeProvider codeProvider =
119      new CSharpCodeProvider(
120        new Dictionary<string, string> {
121          { "CompilerVersion", "v4.0" },  // support C# 4.0 syntax
122        });
123
124    private CompilerResults DoCompile() {
125      var parameters = new CompilerParameters {
126        GenerateExecutable = false,
127        GenerateInMemory = true,
128        IncludeDebugInformation = false
129      };
130      parameters.ReferencedAssemblies.AddRange(
131        GetAssemblies()
132        .Select(a => a.Location)
133        .ToArray());
134      var unit = CreateCompilationUnit();
135      var writer = new StringWriter();
136      codeProvider.GenerateCodeFromCompileUnit(
137        unit,
138        writer,
139        new CodeGeneratorOptions {
140          ElseOnClosing = true,
141          IndentString = "  ",
142        });
143      compilationUnitCode = writer.ToString();
144      return codeProvider.CompileAssemblyFromDom(parameters, unit);
145    }
146
147    public void Compile() {
148      var results = DoCompile();
[10401]149      compiledScript = null;
[10332]150      CompileErrors = results.Errors;
151      if (results.Errors.HasErrors) {
152        var sb = new StringBuilder();
153        foreach (CompilerError error in results.Errors) {
154          sb.Append(error.Line).Append(':')
155            .Append(error.Column).Append(": ")
156            .AppendLine(error.ErrorText);
157        }
158        throw new Exception(string.Format(
159          "Compilation of \"{0}\" failed:{1}{2}",
160          Name, Environment.NewLine,
161          sb.ToString()));
162      } else {
163        var assembly = results.CompiledAssembly;
164        var types = assembly.GetTypes();
165        DeregisterScriptEvents();
[10401]166        compiledScript = (UserScriptBase)Activator.CreateInstance(types[0]);
[10332]167        RegisterScriptEvents();
168      }
169    }
170
[10358]171    public IEnumerable<Assembly> GetAssemblies() {
172      var assemblies = new List<Assembly>();
173      foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) {
174        try {
175          if (File.Exists(a.Location)) assemblies.Add(a);
176        } catch (NotSupportedException) {
177          // NotSupportedException is thrown while accessing
178          // the Location property of the anonymously hosted
179          // dynamic methods assembly, which is related to
180          // LINQ queries
181        }
182      }
183      assemblies.Add(typeof(Microsoft.CSharp.RuntimeBinder.Binder).Assembly); // for dlr functionality
184      return assemblies;
185    }
186
[10332]187    private readonly Regex SafeTypeNameCharRegex = new Regex("[_a-zA-Z0-9]+");
188    private readonly Regex SafeTypeNameRegex = new Regex("[_a-zA-Z][_a-zA-Z0-9]*");
189
190    private CodeCompileUnit CreateCompilationUnit() {
[10401]191      var unit = new CodeSnippetCompileUnit(code);
[10332]192      return unit;
193    }
194
195    public string CompiledTypeName {
196      get {
197        var sb = new StringBuilder();
198        var strings = SafeTypeNameCharRegex.Matches(Name)
199                                           .Cast<Match>()
200                                           .Select(m => m.Value);
201        foreach (string s in strings)
202          sb.Append(s);
203        return SafeTypeNameRegex.Match(sb.ToString()).Value;
204      }
205    }
206    #endregion
207
208    private Thread scriptThread;
209    public void Execute() {
[10401]210      if (compiledScript == null) return;
211      var executeMethod = typeof(UserScriptBase).GetMethod(ExecuteMethodName, BindingFlags.NonPublic | BindingFlags.Instance);
[10332]212      if (executeMethod != null) {
213        scriptThread = new Thread(() => {
214          try {
215            OnScriptExecutionStarted();
[10401]216            executeMethod.Invoke(compiledScript, new[] { VariableStore });
[10332]217          } finally {
218            OnScriptExecutionFinished();
219          }
220        });
221        scriptThread.Start();
222      }
223    }
224
225    public void Kill() {
226      if (scriptThread.IsAlive)
227        scriptThread.Abort();
228    }
229
[10401]230    private void compiledScript_ConsoleOutputChanged(object sender, EventArgs<string> e) {
[10332]231      OnConsoleOutputChanged(e.Value);
232    }
233
234    public event EventHandler CodeChanged;
235    private void OnCodeChanged() {
236      var handler = CodeChanged;
237      if (handler != null) handler(this, EventArgs.Empty);
238    }
239
240    public event EventHandler CompileErrorsChanged;
241    private void OnCompileErrorsChanged() {
242      var handler = CompileErrorsChanged;
243      if (handler != null) handler(this, EventArgs.Empty);
244    }
245
246    public event EventHandler ScriptExecutionStarted;
247    private void OnScriptExecutionStarted() {
248      var handler = ScriptExecutionStarted;
249      if (handler != null) handler(this, EventArgs.Empty);
250    }
251
252    public event EventHandler ScriptExecutionFinished;
253    private void OnScriptExecutionFinished() {
254      var handler = ScriptExecutionFinished;
255      if (handler != null) handler(this, EventArgs.Empty);
256    }
257
258    public event EventHandler<EventArgs<string>> ConsoleOutputChanged;
259    private void OnConsoleOutputChanged(string args) {
260      var handler = ConsoleOutputChanged;
261      if (handler != null) handler(this, new EventArgs<string>(args));
262    }
263  }
264}
Note: See TracBrowser for help on using the repository browser.