Free cookie consent management tool by TermsFeed Policy Generator

source: branches/HLScript/HeuristicLab.HLScript/3.3/HLScript.cs @ 12756

Last change on this file since 12756 was 10358, checked in by jkarder, 11 years ago

#2136:

  • refactored HLScriptGeneration to separate outputs from different running HL scripts.
  • added persistence support for HLScripts and fixed cloning
  • added code completion for all types in the currently loaded assemblies
  • merged trunk changes
File size: 9.9 KB
Line 
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 {
19  [Item("HL Script", "A HeuristicLab script.")]
20  [Creatable("Scripts")]
21  [StorableClass]
22  public sealed class HLScript : NamedItem, IStorableContent {
23    #region Constants
24    private const string ScriptNamespaceName = "HeuristicLab.HLScript";
25    private const string ExecuteMethodName = "Execute";
26    private const string CodeTemplate =
27@"// use 'vars' to access global variables in the variable store
28
29using System;
30
31public override void Main() {
32  // type your code here
33}
34
35// further classes and methods";
36    #endregion
37
38    #region Fields & Properties
39    private HLScriptGeneration compiledHLScript;
40
41    public string Filename { get; set; }
42
43    public static new Image StaticItemImage {
44      get { return VSImageLibrary.Script; }
45    }
46
47    [Storable]
48    private VariableStore variableStore;
49    public VariableStore VariableStore {
50      get { return variableStore; }
51    }
52
53    [Storable]
54    private string code;
55    public string Code {
56      get { return code; }
57      set {
58        if (value == code) return;
59        code = value;
60        compiledHLScript = null;
61        OnCodeChanged();
62      }
63    }
64
65    private string compilationUnitCode;
66    public string CompilationUnitCode {
67      get { return compilationUnitCode; }
68    }
69
70    private CompilerErrorCollection compileErrors;
71    public CompilerErrorCollection CompileErrors {
72      get { return compileErrors; }
73      private set {
74        compileErrors = value;
75        OnCompileErrorsChanged();
76      }
77    }
78    #endregion
79
80    #region Construction & Initialization
81    [StorableConstructor]
82    private HLScript(bool deserializing) : base(deserializing) { }
83    private HLScript(HLScript original, Cloner cloner)
84      : base(original, cloner) {
85      code = original.code;
86      variableStore = new VariableStore();
87      compilationUnitCode = original.compilationUnitCode;
88      if (original.compileErrors != null)
89        compileErrors = new CompilerErrorCollection(original.compileErrors);
90    }
91
92    public HLScript()
93      : base("HL Script", "A HeuristicLab script.") {
94      code = CodeTemplate;
95      variableStore = new VariableStore();
96    }
97
98    public override IDeepCloneable Clone(Cloner cloner) {
99      return new HLScript(this, cloner);
100    }
101    #endregion
102
103    private void RegisterScriptEvents() {
104      if (compiledHLScript == null) return;
105      compiledHLScript.ConsoleOutputChanged += compiledHLScript_ConsoleOutputChanged;
106    }
107
108    private void DeregisterScriptEvents() {
109      if (compiledHLScript == null) return;
110      compiledHLScript.ConsoleOutputChanged -= compiledHLScript_ConsoleOutputChanged;
111    }
112
113    #region Compilation
114    private CSharpCodeProvider codeProvider =
115      new CSharpCodeProvider(
116        new Dictionary<string, string> {
117          { "CompilerVersion", "v4.0" },  // support C# 4.0 syntax
118        });
119
120    private CompilerResults DoCompile() {
121      var parameters = new CompilerParameters {
122        GenerateExecutable = false,
123        GenerateInMemory = true,
124        IncludeDebugInformation = false
125      };
126      parameters.ReferencedAssemblies.AddRange(
127        GetAssemblies()
128        .Select(a => a.Location)
129        .ToArray());
130      var unit = CreateCompilationUnit();
131      var writer = new StringWriter();
132      codeProvider.GenerateCodeFromCompileUnit(
133        unit,
134        writer,
135        new CodeGeneratorOptions {
136          ElseOnClosing = true,
137          IndentString = "  ",
138        });
139      compilationUnitCode = writer.ToString();
140      return codeProvider.CompileAssemblyFromDom(parameters, unit);
141    }
142
143    public void Compile() {
144      var results = DoCompile();
145      compiledHLScript = null;
146      CompileErrors = results.Errors;
147      if (results.Errors.HasErrors) {
148        var sb = new StringBuilder();
149        foreach (CompilerError error in results.Errors) {
150          sb.Append(error.Line).Append(':')
151            .Append(error.Column).Append(": ")
152            .AppendLine(error.ErrorText);
153        }
154        throw new Exception(string.Format(
155          "Compilation of \"{0}\" failed:{1}{2}",
156          Name, Environment.NewLine,
157          sb.ToString()));
158      } else {
159        var assembly = results.CompiledAssembly;
160        var types = assembly.GetTypes();
161        DeregisterScriptEvents();
162        compiledHLScript = (HLScriptGeneration)Activator.CreateInstance(types[0]);
163        RegisterScriptEvents();
164      }
165    }
166
167    public IEnumerable<Assembly> GetAssemblies() {
168      var assemblies = new List<Assembly>();
169      foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) {
170        try {
171          if (File.Exists(a.Location)) assemblies.Add(a);
172        } catch (NotSupportedException) {
173          // NotSupportedException is thrown while accessing
174          // the Location property of the anonymously hosted
175          // dynamic methods assembly, which is related to
176          // LINQ queries
177        }
178      }
179      assemblies.Add(typeof(Microsoft.CSharp.RuntimeBinder.Binder).Assembly); // for dlr functionality
180      return assemblies;
181    }
182
183    private readonly Regex LineSplitter = new Regex(@"\r\n|\r|\n");
184    private readonly Regex SafeTypeNameCharRegex = new Regex("[_a-zA-Z0-9]+");
185    private readonly Regex SafeTypeNameRegex = new Regex("[_a-zA-Z][_a-zA-Z0-9]*");
186    private readonly Regex NamespaceDeclarationRegex = new Regex(@"using\s+(@?[a-z_A-Z]\w+(?:\s*\.\s*@?[a-z_A-Z]\w*)*)\s*;");
187    private readonly Regex NamespaceRegex = new Regex(@"(@?[a-z_A-Z]\w+(?:\s*\.\s*@?[a-z_A-Z]\w*)*)");
188    private readonly Regex CommentRegex = new Regex(@"((/\*)[^/]+(\*/))|(//.*)");
189
190    private CodeCompileUnit CreateCompilationUnit() {
191      var ns = new CodeNamespace(ScriptNamespaceName);
192      ns.Types.Add(CreateScriptClass());
193      ns.Imports.AddRange(
194        GetNamespaces()
195        .Select(n => new CodeNamespaceImport(n))
196        .ToArray());
197      var unit = new CodeCompileUnit();
198      unit.Namespaces.Add(ns);
199      return unit;
200    }
201
202    private IEnumerable<string> GetNamespaces() {
203      var strings = NamespaceDeclarationRegex.Matches(CommentRegex.Replace(code, string.Empty))
204                                             .Cast<Match>()
205                                             .Select(m => m.Value);
206      foreach (var s in strings) {
207        var match = NamespaceRegex.Match(s.Replace("using", string.Empty));
208        yield return match.Value;
209      }
210    }
211
212    private IEnumerable<string> GetCodeLines() {
213      var lines = LineSplitter.Split(code);
214      foreach (var line in lines) {
215        string trimmedLine = line.Trim();
216        if (!NamespaceDeclarationRegex.IsMatch(trimmedLine))
217          yield return trimmedLine;
218      }
219    }
220
221    public string CompiledTypeName {
222      get {
223        var sb = new StringBuilder();
224        var strings = SafeTypeNameCharRegex.Matches(Name)
225                                           .Cast<Match>()
226                                           .Select(m => m.Value);
227        foreach (string s in strings)
228          sb.Append(s);
229        return SafeTypeNameRegex.Match(sb.ToString()).Value;
230      }
231    }
232
233    private CodeTypeDeclaration CreateScriptClass() {
234      var typeDecl = new CodeTypeDeclaration(CompiledTypeName) {
235        IsClass = true,
236        TypeAttributes = TypeAttributes.Public,
237      };
238      typeDecl.BaseTypes.Add(typeof(HLScriptGeneration));
239      typeDecl.Members.Add(new CodeSnippetTypeMember(string.Join(Environment.NewLine, GetCodeLines())));
240      return typeDecl;
241    }
242    #endregion
243
244    private Thread scriptThread;
245    public void Execute() {
246      if (compiledHLScript == null) return;
247      var executeMethod = typeof(HLScriptGeneration).GetMethod(ExecuteMethodName, BindingFlags.NonPublic | BindingFlags.Instance);
248      if (executeMethod != null) {
249        scriptThread = new Thread(() => {
250          try {
251            OnScriptExecutionStarted();
252            executeMethod.Invoke(compiledHLScript, new[] { VariableStore });
253          } finally {
254            OnScriptExecutionFinished();
255          }
256        });
257        scriptThread.Start();
258      }
259    }
260
261    public void Kill() {
262      if (scriptThread.IsAlive)
263        scriptThread.Abort();
264    }
265
266    private void compiledHLScript_ConsoleOutputChanged(object sender, EventArgs<string> e) {
267      OnConsoleOutputChanged(e.Value);
268    }
269
270    public event EventHandler CodeChanged;
271    private void OnCodeChanged() {
272      var handler = CodeChanged;
273      if (handler != null) handler(this, EventArgs.Empty);
274    }
275
276    public event EventHandler CompileErrorsChanged;
277    private void OnCompileErrorsChanged() {
278      var handler = CompileErrorsChanged;
279      if (handler != null) handler(this, EventArgs.Empty);
280    }
281
282    public event EventHandler ScriptExecutionStarted;
283    private void OnScriptExecutionStarted() {
284      var handler = ScriptExecutionStarted;
285      if (handler != null) handler(this, EventArgs.Empty);
286    }
287
288    public event EventHandler ScriptExecutionFinished;
289    private void OnScriptExecutionFinished() {
290      var handler = ScriptExecutionFinished;
291      if (handler != null) handler(this, EventArgs.Empty);
292    }
293
294    public event EventHandler<EventArgs<string>> ConsoleOutputChanged;
295    private void OnConsoleOutputChanged(string args) {
296      var handler = ConsoleOutputChanged;
297      if (handler != null) handler(this, new EventArgs<string>(args));
298    }
299  }
300}
Note: See TracBrowser for help on using the repository browser.