Skip to content

Instantly share code, notes, and snippets.

@ronnieoverby
Last active October 18, 2018 00:33
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 ronnieoverby/57c649eca6039985d242f575a2c1d22c to your computer and use it in GitHub Desktop.
Save ronnieoverby/57c649eca6039985d242f575a2c1d22c to your computer and use it in GitHub Desktop.
<Query Kind="Program">
<NuGetReference>Microsoft.CodeAnalysis.CSharp</NuGetReference>
<Namespace>LINQPad.ObjectModel</Namespace>
<Namespace>Microsoft.CodeAnalysis</Namespace>
<Namespace>Microsoft.CodeAnalysis.CSharp</Namespace>
<Namespace>Microsoft.CodeAnalysis.Emit</Namespace>
<Namespace>static LINQPad.Util</Namespace>
<Namespace>static System.IO.Path</Namespace>
<Namespace>static System.Reflection.Assembly</Namespace>
<Namespace>static System.Reflection.BindingFlags</Namespace>
<Namespace>Microsoft.CodeAnalysis.CSharp.Syntax</Namespace>
</Query>
void Main()
{
var query = LoadQuery("test");
GetQueryResult<int>(query, false).Dump();
}
T GetQueryResult<T>(Query query, bool dumpCode = false, IEnumerable<string> defaultReferences = null, IEnumerable<string> defaultNamespaceImports = null)
{
if (!query.IsCSharp)
throw new NotSupportedException("Query must be written in C#");
if (query.GetConnectionInfo() != null)
throw new NotSupportedException("Connected queries are not supported.");
var ClassName = CreateCSharpIdentifier();
const string Main = nameof(Main);
var namespaces = query.NamespaceImports.Union(defaultNamespaceImports ?? new[] {
"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.Transactions",
"System.Xml",
"System.Xml.Linq",
"System.Xml.XPath",
"LINQPad"
}).OrderBy(x => x);
string GetCode()
{
if (query.Language == QueryLanguage.Expression)
return $"public {typeof(T).FullName} {Main}() => {query.Text};";
if (query.Language == QueryLanguage.Program)
return query.Text;
throw new NotSupportedException($"Query language \"{query.Language}\" is not supported.");
}
var syntaxTree = CSharpSyntaxTree.ParseText(
$"{string.Concat(namespaces.Select(n => $"using {n};"))} public class {ClassName} {{ {GetCode()} \r\n}}");
if (dumpCode)
syntaxTree.GetRoot().NormalizeWhitespace().ToFullString();
var assemblyName = GetRandomFileName();
var assemblies = ((defaultReferences ?? new[] {
"mscorlib",
"netstandard",
"System",
"Microsoft.CSharp",
"System.Core",
"System.Data",
"System.Transactions",
"System.Xml",
"System.Xml.Linq",
"System.Data.Linq",
"System.Drawing",
"System.Data.DataSetExtensions",
"System.Text.Encoding",
"System.Threading.Tasks",
"LINQPad"
}).Select(Load)
).Concat(
query.FileReferences.Select(LoadFile)
).Concat(
from n in query.NuGetReferences
from r in n.GetAssemblyReferences()
select LoadFile(r)
).Concat(
query.GacReferences.Select(Load)
);
var compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { syntaxTree },
references: assemblies.Select(a => MetadataReference.CreateFromFile(a.Location)),
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
using (var ms = new MemoryStream())
{
var result = compilation.Emit(ms);
if (!result.Success)
{
result.Diagnostics.Where(diag =>
diag.IsWarningAsError ||
diag.Severity == DiagnosticSeverity.Error).Dump("Emit Failures");
throw new Exception("Emit failure");
}
var assembly = Load(ms.ToArray());
var type = assembly.GetType(ClassName);
var obj = Activator.CreateInstance(type);
var mainMethods = type.GetMethods(Public | NonPublic | Instance)
.Where(m => m.Name == Main && typeof(T).IsAssignableFrom( m.ReturnType ))
.Take(2).ToArray();
if (mainMethods.Length != 1)
throw new Exception($"Cannot find {Main} method with return type {typeof(T).FullName}");
var mainMethod = mainMethods.Single();
// mimic linqpad behavior of supplying default values to Main(…)
var defaultParams = from p in mainMethod.GetParameters()
select GetDefault(p.ParameterType);
var instance = (T)mainMethod.Invoke(obj, defaultParams.ToArray());
return instance;
}
}
Dictionary<Type, object> _defaultValues = new Dictionary<Type, object>();
object GetDefault(Type t)
{
if (!t.IsValueType)
return null;
if (_defaultValues.TryGetValue(t, out object value))
return value;
var exprDefault = Expression.Default(t);
var castObject = Expression.Convert(exprDefault, typeof(object));
var lambda = Expression.Lambda<Func<object>>(castObject);
var f = lambda.Compile();
return _defaultValues[t] = f();
}
Query LoadQuery(string nameOrPath)
{
var path = new Func<string>[]
{
() => nameOrPath,
() => Combine(MyQueriesFolder, ChangeExtension(GetFileName(nameOrPath), ".linq"))
}
.Select(f => f())
.FirstOrDefault(File.Exists);
if (path == null)
throw new FileNotFoundException(nameOrPath);
var query = Query.Load(path);
return query;
}
string CreateCSharpIdentifier() => $"_{Guid.NewGuid():n}";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment