//-----------------------------------------------------------------------
//
// Created Sept. 2009 by Nicholas Armstrong. Available online at http://nicholasarmstrong.com
//
//
// Dynamically compiles C# code into an assembly and executes it.
//
//-----------------------------------------------------------------------
namespace NicholasArmstrong.Samples.ECE150.Loops
{
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using Microsoft.CSharp;
#region Delegates
///
/// Delegate for the 'Highlight' function.
///
/// The position of the box to highlight.
/// The colour to highlight the box.
public delegate void HighlightDelegate(int position, string color);
///
/// Delegate for the 'HighlightBox' function.
///
/// The box to highlight.
/// The colour to highlight the box.
public delegate void HighlightBoxDelegate(Box box, string color);
///
/// Delegate for the 'Wait' function.
///
/// The duration to wait, in milliseconds.
public delegate void WaitDelegate(int millisecondsTimeout);
#endregion
///
/// Dynamically compiles C# code into an assembly and executes it.
///
public class DynamicEval
{
#region Fields
///
/// Regular expression to change the 1-argument Highlight function into the two-argument version.
///
private static readonly Regex highlightOverloadRegex = new Regex("Highlight[(]([^,(]+)[)]");
///
/// Regular expression to change the 1-argument HighlightBox function into the two-argument version.
///
private static readonly Regex highlightBoxOverloadRegex = new Regex("HighlightBox[(]([^,(]+)[)]");
///
/// The 'Highlight()' function.
///
private HighlightDelegate highlightDelegate;
///
/// The 'HighlightBox()' function.
///
private HighlightBoxDelegate highlightBoxDelegate;
///
/// The 'Wait()' function.
///
private WaitDelegate waitDelegate;
///
/// The last compiled assembly.
///
private Assembly compiledAssembly;
///
/// Parameters needed for compilation.
///
private CompilerParameters cp;
///
/// The C# compiler service.
///
private CSharpCodeProvider csc;
///
/// The starting portions of the program that actually gets compiled.
///
private string programStartText;
///
/// The ending portions of the program that actually gets compiled.
///
private string programEndText;
#endregion
#region Constructor
///
/// Initializes a new instance of the DynamicEval class.
///
/// An implementation of the 'Highlight' function.
/// An implementation of the 'HighlightBox' function.
/// An implementation of the 'Wait' function.
public DynamicEval(HighlightDelegate highlightDelegate, HighlightBoxDelegate highlightBoxDelegate, WaitDelegate waitDelegate)
{
this.highlightDelegate = highlightDelegate;
this.highlightBoxDelegate = highlightBoxDelegate;
this.waitDelegate = waitDelegate;
this.Initialize();
}
#endregion
#region Methods
///
/// Initializes the dynamic evaluation class.
///
public void Initialize()
{
// Initialize Compiler
this.cp = new CompilerParameters();
this.cp.GenerateExecutable = false;
this.cp.GenerateInMemory = true;
this.cp.ReferencedAssemblies.Add("ECE150.Loops.dll");
this.cp.ReferencedAssemblies.Add("mscorlib.dll");
this.cp.ReferencedAssemblies.Add("System.dll");
this.cp.ReferencedAssemblies.Add(typeof(Queryable).Assembly.Location); // Reference LINQ libraries in System.Core
this.cp.ReferencedAssemblies.Add(typeof(Control).Assembly.Location); // Reference WPF's PresentationFramework.dll
this.cp.ReferencedAssemblies.Add(typeof(UIElement).Assembly.Location); // Reference WPF's PresentationCore.dll
this.cp.ReferencedAssemblies.Add(typeof(DependencyProperty).Assembly.Location); // Reference WPF's WindowsBase.dll
this.csc = new CSharpCodeProvider(new Dictionary() { { "CompilerVersion", "v3.5" } }); // Allow LINQ queries to be compiled
// Initialize static program text
StringBuilder s = new StringBuilder();
s.AppendLine("using System;");
s.AppendLine("using System.Collections.Generic;");
s.AppendLine("using System.Linq;");
s.AppendLine("namespace NicholasArmstrong.Samples.ECE150.Loops");
s.AppendLine("{");
s.AppendLine("public static class CompiledEval");
s.AppendLine("{");
s.AppendLine("public static void HighlightBoxes(HighlightDelegate Highlight, HighlightBoxDelegate HighlightBox, WaitDelegate Wait, int totalBoxes, int boxesPerColumn, int boxesPerRow, IEnumerable boxes)");
s.AppendLine("{");
s.AppendLine("int totalColumns = boxesPerRow;");
s.AppendLine("int totalRows = boxesPerColumn;");
this.programStartText = s.ToString();
this.programEndText = "}}}";
}
///
/// Compiles a snippet of code into an assembly.
///
/// The function text to compile.
/// Any error messages produced during compilation.
/// A value indicating whether compilation was successful.
public bool CompileSnippet(string functionText, out string errorMessages)
{
// Preprocess program
functionText = highlightOverloadRegex.Replace(functionText, "Highlight($1, String.Empty)");
functionText = highlightBoxOverloadRegex.Replace(functionText, "HighlightBox($1, String.Empty)");
// Construct program
string programText = this.programStartText + functionText + this.programEndText;
// Compile program
CompilerResults results = this.csc.CompileAssemblyFromSource(this.cp, programText);
// Collect error messages
errorMessages = String.Empty;
foreach (CompilerError error in results.Errors)
{
// Append error messages, make a rough attempt to hide some of the fancy work we're doing in behind the scenes to compile
errorMessages += error.ErrorText.Replace("HighlightDelegate", "Highlight").Replace("WaitDelegate", "Wait").Replace("Delegate", "Method").Replace("delegate", "method") + "\n";
}
if (errorMessages.Length > 0)
{
// Trim off the last newline
errorMessages = errorMessages.Substring(0, errorMessages.Length - 1);
}
this.compiledAssembly = results.Errors.HasErrors ? null : results.CompiledAssembly;
return !results.Errors.HasErrors;
}
///
/// Executes the compiled snippet of code with the provided parameters.
///
/// The total number of boxes in the grid.
/// The number of boxes in a grid column.
/// The number of boxes in a grid row.
/// The collection of boxes themselves (for foreach and LINQ support).
public void ExecuteSnippet(int totalBoxes, int boxesPerColumn, int boxesPerRow, IEnumerable boxes)
{
if (this.compiledAssembly == null)
{
return;
}
Type target = this.compiledAssembly.GetTypes()[0];
MethodInfo method = target.GetMethod("HighlightBoxes");
object[] parameters = new object[] { this.highlightDelegate, this.highlightBoxDelegate, this.waitDelegate, totalBoxes, boxesPerColumn, boxesPerRow, boxes };
method.Invoke(null, parameters);
}
#endregion
}
}