/// <summary> /// Compiles C# code. Optionally loads in-memory assembly and gets MethodInfo of the first static method for executing. /// Returns false if there are errors in code. /// </summary> /// <param name="code">C# code. If wrap is false - full code with class and usings; if true - one or more functions and other class-level declarations.</param> /// <param name="r">Receives results when compiled successfully.</param> /// <param name="wrap">Add default usings and wrap code into "[module: DefaultCharSet(CharSet.Unicode)]\r\npublic class __script__ {\r\n#line 1" ... "}".</param> /// <param name="load">Load in-memory assembly and get MethodInfo of the first static method for executing.</param> /// <remarks> /// Adds <see cref="MetaReferences.DefaultReferences"/>. If wrap is true, adds <see cref="c_defaultUsings"/>. /// /// Function's code does not throw exceptions, but the CodeAnalysis API may throw, although undocumented and never noticed. /// /// Thread-safe. /// </remarks> public static bool Compile(string code, out Result r, bool wrap, bool load) { if (wrap) { var b = new StringBuilder(); b.AppendLine(c_defaultUsings); b.AppendLine("[module: DefaultCharSet(CharSet.Unicode)]\r\npublic class __script__ {\r\n#line 1"); b.AppendLine(code).Append("}"); code = b.ToString(); } var tree = CSharpSyntaxTree.ParseText(code, new CSharpParseOptions(LanguageVersion.Latest), "", Encoding.UTF8); var refs = new MetaReferences().Refs; var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true); var compilation = CSharpCompilation.Create("test", new SyntaxTree[] { tree }, refs, options); var memStream = new MemoryStream(4096); var emitResult = compilation.Emit(memStream); r = new Result(); if (!emitResult.Success) { var sb = new StringBuilder(); foreach (var d in emitResult.Diagnostics) { if (d.Severity == DiagnosticSeverity.Error) { sb.AppendLine(d.ToString()); } } r.errors = sb.ToString(); return(false); } memStream.Position = 0; if (load) { r.assembly = Assembly.Load(memStream.ToArray()); r.method = r.assembly.GetTypes()[0].GetMethods(BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static)[0]; } else { r.stream = memStream; } return(true); }
/// <summary> /// Extracts meta comments from a single C# file. /// </summary> /// <param name="f"></param> /// <param name="isMain">If false, it is a file added through meta option 'c'.</param> void _ParseFile(FileNode f, bool isMain) { //var p1 = APerf.Create(); string code = f.GetText(cache: true); //p1.Next(); bool isScript = f.IsScript; if (_isMain = isMain) { Name = APath.GetFileName(f.Name, true); IsScript = isScript; Optimize = DefaultOptimize; WarningLevel = DefaultWarningLevel; NoWarnings = DefaultNoWarnings != null ? new List <string>(DefaultNoWarnings) : new List <string>(); Defines = new List <string>(); Role = DefaultRole(isScript); CodeFiles = new List <MetaCodeFile>(); References = new MetaReferences(); } CodeFiles.Add(new MetaCodeFile(f, code)); _fn = f; _code = code; int endOf = FindMetaComments(code); if (endOf > 0) { if (isMain) { EndOfMeta = endOf; } foreach (var t in EnumOptions(code, endOf)) { //var p1 = APerf.Create(); _ParseOption(t.Name(), t.Value(), t.nameStart, t.valueStart); //p1.Next(); var t1 = p1.TimeTotal; if(t1 > 5) AOutput.Write(t1, t.Name(), t.Value()); } } //p1.NW(); }
static bool _Compile(bool forRun, FileNode f, out CompResults r, FileNode projFolder) { var p1 = APerf.Create(); r = new CompResults(); var m = new MetaComments(); if (!m.Parse(f, projFolder, EMPFlags.PrintErrors)) { return(false); } var err = m.Errors; p1.Next('m'); bool needOutputFiles = m.Role != ERole.classFile; //if for run, don't compile if f role is classFile if (forRun && !needOutputFiles) { r.role = ERole.classFile; return(false); } XCompiled cache = XCompiled.OfCollection(f.Model); string outPath = null, outFile = null, fileName = null; if (needOutputFiles) { if (m.OutputPath != null) { outPath = m.OutputPath; fileName = m.Name + ".dll"; } else { outPath = cache.CacheDirectory; fileName = f.IdString; } outFile = outPath + "\\" + fileName; AFile.CreateDirectory(outPath); } if (m.PreBuild.f != null && !_RunPrePostBuildScript(false, m, outFile)) { return(false); } var po = m.CreateParseOptions(); var trees = new CSharpSyntaxTree[m.CodeFiles.Count]; for (int i = 0; i < trees.Length; i++) { var f1 = m.CodeFiles[i]; trees[i] = CSharpSyntaxTree.ParseText(f1.code, po, f1.f.FilePath, Encoding.UTF8) as CSharpSyntaxTree; //info: file path is used later in several places: in compilation error messages, run time stack traces (from PDB), Visual Studio debugger, etc. // Our AOutputServer.SetNotifications callback will convert file/line info to links. It supports compilation errors and run time stack traces. } //p1.Next('t'); string asmName = m.Name; if (m.Role == ERole.editorExtension) { asmName = asmName + "|" + (++c_versionCounter).ToString(); //AssemblyLoadContext.Default cannot load multiple assemblies with same name } var compilation = CSharpCompilation.Create(asmName, trees, m.References.Refs, m.CreateCompilationOptions()); //p1.Next('c'); #if PDB string pdbFile = null; #endif MemoryStream pdbStream = null; string xdFile = null; Stream xdStream = null; Stream resNat = null; ResourceDescription[] resMan = null; EmitOptions eOpt = null; if (needOutputFiles) { _AddAttributes(ref compilation, needVersionEtc: m.Role == ERole.miniProgram || m.Role == ERole.exeProgram); //create debug info always. It is used for run-time error links. #if PDB pdbStream = new MemoryStream(); if (m.OutputPath != null) { pdbFile = Path.ChangeExtension(outFile, "pdb"); eOpt = new EmitOptions(debugInformationFormat: DebugInformationFormat.Pdb, pdbFilePath: pdbFile); //eOpt = new EmitOptions(debugInformationFormat: DebugInformationFormat.PortablePdb, pdbFilePath: pdbFile); //smaller, but not all tools support it } else { //eOpt = new EmitOptions(debugInformationFormat: DebugInformationFormat.Embedded); //no, it is difficult to extract, because we load assembly from byte[] to avoid locking. We instead append portable PDB stream to the assembly stream. eOpt = new EmitOptions(debugInformationFormat: DebugInformationFormat.PortablePdb); //adds < 1 KB; almost the same compiling speed. Separate pdb file is 14 KB; 2 times slower compiling, slower loading. } #else if (m.OutputPath != null) { //we don't use classic pdb file becouse of this error after switching to .NET Core: // Unexpected error writing debug information -- 'The version of Windows PDB writer is older than required: 'diasymreader.dll'' eOpt = new EmitOptions(debugInformationFormat: DebugInformationFormat.Embedded); } else { pdbStream = new MemoryStream(); //eOpt = new EmitOptions(debugInformationFormat: DebugInformationFormat.Embedded); //no, it is difficult to extract, because we load assembly from byte[] to avoid locking. We instead append portable PDB stream to the assembly stream. eOpt = new EmitOptions(debugInformationFormat: DebugInformationFormat.PortablePdb); //adds < 1 KB; almost the same compiling speed. Separate pdb file is 14 KB; 2 times slower compiling, slower loading. } #endif if (m.XmlDocFile != null) { xdStream = AFile.WaitIfLocked(() => File.Create(xdFile = APath.Normalize(m.XmlDocFile, outPath))); } resMan = _CreateManagedResources(m); if (err.ErrorCount != 0) { err.PrintAll(); return(false); } if (m.Role == ERole.exeProgram || m.Role == ERole.classLibrary) { resNat = _CreateNativeResources(m, compilation); } if (err.ErrorCount != 0) { err.PrintAll(); return(false); } //EmbeddedText.FromX //it seems we can embed source code in PDB. Not tested. } //p1.Next(); var asmStream = new MemoryStream(8000); var emitResult = compilation.Emit(asmStream, pdbStream, xdStream, resNat, resMan, eOpt); if (needOutputFiles) { xdStream?.Dispose(); resNat?.Dispose(); //info: compiler disposes resMan } //p1.Next('e'); var diag = emitResult.Diagnostics; if (!diag.IsEmpty) { foreach (var d in diag) { if (d.Severity == DiagnosticSeverity.Hidden) { continue; } err.AddErrorOrWarning(d, f); if (d.Severity == DiagnosticSeverity.Error && d.Id == "CS0009") { MetaReferences.RemoveBadReference(d.GetMessage()); } } err.PrintAll(); } if (!emitResult.Success) { if (needOutputFiles) { AFile.Delete(outFile); #if PDB if (pdbFile != null) { AFile.Delete(pdbFile); } #endif if (xdFile != null) { AFile.Delete(xdFile); } } return(false); } if (needOutputFiles) { //If there is no [STAThread], will need MTA thread. if (m.Role == ERole.miniProgram || m.Role == ERole.exeProgram) { bool hasSTAThread = compilation.GetEntryPoint(default)?.GetAttributes().Any(o => o.ToString() == "System.STAThreadAttribute") ?? false;