Skip to content

Instantly share code, notes, and snippets.

@jeremysimmons
Forked from plaurin/SelfCompile
Created January 30, 2018 15:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jeremysimmons/a2b305e82e91928d6aedc3473f3fe85a to your computer and use it in GitHub Desktop.
Save jeremysimmons/a2b305e82e91928d6aedc3473f3fe85a to your computer and use it in GitHub Desktop.
Self Compiling LinqPad query
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