Created
October 7, 2012 21:00
-
-
Save plaurin/3849578 to your computer and use it in GitHub Desktop.
Self Compiling LinqPad query
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void Main() | |
{ | |
Environment.CurrentDirectory = Path.GetDirectoryName(Util.CurrentQueryPath); | |
Compiler.CompileFiles(Options.CreateOnDiskDll( | |
@namespace: "LinqPad.QueriesCompiler", | |
outputFile: "LinqPad.QueriesCompiler.dll") | |
.AddCodeFile(CodeType.LinqProgramTypes, Util.CurrentQueryPath)) | |
.Dump("Successfully created assembly at " + DateTime.Now.ToLocalTime()); | |
} | |
// Define other methods and classes here | |
public static class Compiler | |
{ | |
public static Assembly CompileFiles(Options options) | |
{ | |
var outputPath = options.IsInMemory ? "" : options.OutputFile; | |
if (!options.CodeFiles.Any()) | |
throw new InvalidOperationException("Should add at least one file to compile."); | |
foreach (var codeFile in options.CodeFiles) | |
{ | |
codeFile.RawContent = File.ReadAllLines(codeFile.FilePath); | |
codeFile.Query = GetQuery(new FileInfo(codeFile.FilePath).DirectoryName, codeFile.RawContent); | |
GetCode(codeFile, options.Namespace); | |
} | |
return BuildAssembly(options.CodeFiles, options, outputPath); | |
} | |
public static Query GetQuery(string folder, IEnumerable<string> content) | |
{ | |
var xml = string.Join("\r\n", content.TakeWhile(l => l.Trim().StartsWith("<"))); | |
var queryElement = XDocument.Parse(xml).Element("Query"); | |
if (queryElement == null) throw new InvalidOperationException("Missing <Query> header definition"); | |
var query = new Query | |
{ | |
Kind = queryElement.Attribute("Kind").Value, | |
Namespaces = queryElement.Elements("Namespace").Select(n => n.Value).ToList(), | |
GACReferences = queryElement.Elements("GACReference").Select(n => n.Value).ToList(), | |
RelativeReferences = queryElement.Elements("Reference").Where(e => e.Attribute("Relative") != null) | |
.Select(n => n.Attribute("Relative").Value) | |
.Select(x => new FileInfo(Path.Combine(folder, x)).FullName) | |
.ToList(), | |
OtherReferences = queryElement.Elements("Reference").Where(e => e.Attribute("Relative") == null) | |
.Select(n => n.Value.Replace("<RuntimeDirectory>", RuntimeEnvironment.GetRuntimeDirectory())) | |
.Select(n => n.Replace("<ProgramFilesX86>", Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86))) | |
.ToList() | |
}; | |
return query; | |
} | |
public static IEnumerable<string> GetCode(IEnumerable<IEnumerable<string>> contents, Query query, string ns) | |
{ | |
return contents.Select(c => GetCode(c, query, ns)); | |
} | |
private static void GetCode(CodeFile codeFile, string @namespace) | |
{ | |
IEnumerable<string> result = null; | |
if (codeFile.Query.Kind != "Program" && codeFile.Query.Kind != "Statements") | |
throw new InvalidOperationException("Only queries of type C# program and C# statements are supported"); | |
var filteredContent = codeFile.RawContent.SkipWhile(l => l.Trim().StartsWith("<")); | |
switch (codeFile.Type) | |
{ | |
case CodeType.LinqStatements: | |
result = WrapInClass(WrapInMain(filteredContent)); | |
break; | |
case CodeType.LinqProgramTypes: | |
result = FilterTypes(filteredContent); | |
break; | |
case CodeType.LinqProgram: | |
result = WrapInClass(filteredContent); | |
break; | |
default: | |
throw new InvalidOperationException("Only queries of type C# program and C# statements are supported"); | |
} | |
codeFile.Content = GetCode(result, codeFile.Query, @namespace); | |
} | |
private static IEnumerable<string> WrapInClass(IEnumerable<string> inputCode) | |
{ | |
var s = new[] { "public class Program {" }; | |
var e = new[] { "}" }; | |
return s.Concat(inputCode).Concat(e); | |
} | |
private static IEnumerable<string> WrapInMain(IEnumerable<string> inputCode) | |
{ | |
var s = new[] { "public static void Main() {" }; | |
var e = new[] { "}" }; | |
return s.Concat(inputCode).Concat(e); | |
} | |
private static IEnumerable<string> FilterTypes(IEnumerable<string> inputCode) | |
{ | |
return inputCode.SkipWhile(l => l.Trim() != "// Define other methods and classes here"); | |
} | |
public static string GetCode(IEnumerable<string> content, Query query, string ns) | |
{ | |
var code = string.Join(Environment.NewLine, content); | |
var codeBuilder = new StringBuilder(); | |
codeBuilder.AppendLine("using " + string.Join(";\r\nusing ", query.Namespaces.Union(StandardNamespaces)) + ";"); | |
codeBuilder.AppendLine(string.Format("namespace {0} {{", ns)); | |
codeBuilder.AppendLine(code); | |
codeBuilder.AppendLine("}"); | |
return codeBuilder.ToString(); | |
} | |
public static Assembly BuildAssembly(IEnumerable<CodeFile> codeFiles, Options options, string outputPath) | |
{ | |
var providerOptions = new Dictionary<string, string> { { "CompilerVersion", "v4.0" } }; | |
var provider = new CSharpCodeProvider(providerOptions); | |
var assemblies = new[] | |
{ | |
codeFiles.SelectMany(c => c.Query.GACReferences.Select(s => Assembly.Load(s).Location)), | |
codeFiles.SelectMany(c => c.Query.RelativeReferences.Select(s => Assembly.LoadFrom(s).Location)), | |
codeFiles.SelectMany(c => c.Query.OtherReferences), | |
Assemblies | |
}; | |
var compilerparams = new CompilerParameters | |
{ | |
GenerateExecutable = !string.IsNullOrWhiteSpace(options.StartupObject), | |
OutputAssembly = options.IsInMemory ? null : outputPath, | |
GenerateInMemory = true, | |
IncludeDebugInformation = true, | |
MainClass = options.StartupObject | |
}; | |
compilerparams.ReferencedAssemblies.AddRange(assemblies.SelectMany(a => a).ToArray()); | |
var results = provider.CompileAssemblyFromSource(compilerparams, codeFiles.Select(f => f.Content).ToArray()); | |
if (results.Errors.HasErrors) | |
{ | |
var errors = new StringBuilder("Compiler Errors:\r\n"); | |
foreach (CompilerError error in results.Errors) | |
{ | |
errors.AppendFormat("File {0}, Line {1},{2}\t: {3}\r\n", | |
error.FileName, error.Line, error.Column, error.ErrorText); | |
} | |
throw new Exception("Errors compiling:\r\n" + errors + "\r\n\r\n" + | |
string.Join(Environment.NewLine, codeFiles.Select(f => f.FilePath + ":\r\n" + AddLineNumber(f.Content)))); | |
} | |
return results.CompiledAssembly; | |
} | |
private static string AddLineNumber(string code) | |
{ | |
var lines = code.Split(new[] { Environment.NewLine }, StringSplitOptions.None); | |
return string.Join(Environment.NewLine, lines.Select ((x, i) => (i + 1).ToString().PadLeft(4) + ": " + x)); | |
} | |
private static List<string> StandardNamespaces | |
{ | |
get | |
{ | |
return new List<string> | |
{ | |
"System", | |
"System.IO", | |
"System.Text", | |
"System.Text.RegularExpressions", | |
"System.Diagnostics", | |
"System.Threading", | |
"System.Reflection", | |
"System.Collections", | |
"System.Collections.Generic", | |
"System.Linq", | |
"System.Linq.Expressions", | |
"System.Data", | |
"System.Data.SqlClient", | |
"System.Data.Linq", | |
"System.Data.Linq.SqlClient", | |
"System.Xml", | |
"System.Xml.Linq", | |
"System.Xml.XPath" | |
}; | |
} | |
} | |
private static List<string> Assemblies | |
{ | |
get | |
{ | |
return new List<string> | |
{ | |
"System.dll", | |
"System.Core.dll", | |
"System.Data.dll", | |
"System.Xml.dll", | |
"System.Xml.Linq.dll", | |
"System.Data.Linq.dll", | |
"System.Drawing.dll", | |
"System.Data.DataSetExtensions.dll" | |
}; | |
} | |
} | |
} | |
public class Query | |
{ | |
public string Kind { get; set; } | |
public List<string> Namespaces { get; set; } | |
public List<string> GACReferences { get; set; } | |
public List<string> RelativeReferences { get; set; } | |
public List<string> OtherReferences { get; set; } | |
} | |
public enum CodeType | |
{ | |
LinqStatements, // Wrap inside class and Main() method | |
LinqProgramTypes, // Code after 'Define other methods and classes here' directly inside namespace | |
LinqProgram, // Wrap all code inside class | |
} | |
public class CodeFile | |
{ | |
public CodeType Type { get; set; } | |
public string FilePath { get; set; } | |
public string[] RawContent { get; set; } | |
public Query Query { get; set; } | |
public string Content { get; set; } | |
} | |
public class Options | |
{ | |
private Options(string @namespace) | |
{ | |
if (string.IsNullOrWhiteSpace(@namespace)) throw new ArgumentNullException("namespace"); | |
this.CodeFiles = new List<CodeFile>(); | |
this.Namespace = @namespace; | |
} | |
public static Options CreateInMemoryDll(string @namespace) | |
{ | |
var options = new Options(@namespace); | |
options.OutputFile = null; | |
options.IsInMemory = true; | |
options.StartupObject = null; | |
return options; | |
} | |
public static Options CreateOnDiskDll(string @namespace, string outputFile) | |
{ | |
if (string.IsNullOrWhiteSpace(outputFile)) throw new ArgumentNullException("outputFile"); | |
var options = new Options(@namespace); | |
options.OutputFile = outputFile; | |
options.IsInMemory = false; | |
options.StartupObject = null; | |
return options; | |
} | |
public static Options CreateOnDiskExe(string @namespace, string outputFile, string startupObject) | |
{ | |
if (string.IsNullOrWhiteSpace(outputFile)) throw new ArgumentNullException("outputFile"); | |
if (string.IsNullOrWhiteSpace(startupObject)) throw new ArgumentNullException("startupObject"); | |
var options = new Options(@namespace); | |
options.OutputFile = outputFile; | |
options.IsInMemory = false; | |
options.StartupObject = startupObject; | |
return options; | |
} | |
public Options AddCodeFile(CodeType type, string filePath) | |
{ | |
this.CodeFiles.Add(new CodeFile { Type = type, FilePath = filePath }); | |
return this; | |
} | |
public string Namespace { get; private set; } | |
public string OutputFile { get; private set; } | |
public List<CodeFile> CodeFiles { get; private set; } | |
public string StartupObject { get; private set; } | |
public bool IsInMemory { get; private set; } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Could you update it to use Roslyn so c# 6 features can be used ?