/// <summary> /// Collects scripts which declarations were used directly in the compiled assembly. /// These scripts are dependencies to the assembly so they must be evaluated first in order to re-use <paramref name="assembly"/> in future. /// </summary> /// <param name="assembly">Compiled assembly.</param> /// <param name="builder">Assembly factory.</param> /// <param name="previousSubmissions">All scripts referenced by the assembly compilation.</param> /// <returns></returns> private static Script[] CollectDependencies(Assembly assembly, PhpCompilationFactory builder, IEnumerable <Script> previousSubmissions) { // collect dependency scripts from referenced assemblies var dependencies = new HashSet <Script>(); foreach (var refname in assembly.GetReferencedAssemblies()) // TODO: only assemblies really used within the {assembly} -> optimizes caching { var refass = builder.TryGetSubmissionAssembly(refname); if (refass != null) { var refscript = previousSubmissions.First(s => s.AssemblyName.Name == refass.GetName().Name); Debug.Assert(refscript != null); if (refscript != null) { dependencies.Add(refscript); } } } // remove dependencies of dependencies -> not needed for checking var toremove = new HashSet <Script>(); foreach (var d in dependencies) { toremove.UnionWith(d.DependingSubmissions); } dependencies.ExceptWith(toremove); // return((dependencies.Count != 0) ? dependencies.ToArray() : Array.Empty <Script>()); }
/// <summary> /// Compiles <paramref name="code"/> and creates script. /// </summary> /// <param name="options">Compilation options.</param> /// <param name="code">Code to be compiled.</param> /// <param name="builder">Assembly builder.</param> /// <param name="previousSubmissions">Enumeration of scripts that were evaluated within current context. New submission may reference them.</param> /// <returns>New script reepresenting the compiled code.</returns> public static Script Create(Context.ScriptOptions options, string code, PhpCompilationFactory builder, IEnumerable <Script> previousSubmissions) { var tree = PhpSyntaxTree.ParseCode(code, new PhpParseOptions(kind: options.IsSubmission ? SourceCodeKind.Script : SourceCodeKind.Regular), PhpParseOptions.Default, options.Location.Path); var diagnostics = tree.Diagnostics; if (!HasErrors(diagnostics)) { // unique in-memory assembly name var name = builder.GetNewSubmissionName(); // list of scripts that were eval'ed in the context already, // our compilation may depend on them var dependingSubmissions = previousSubmissions.Where(s => !s.Image.IsDefaultOrEmpty); IEnumerable <MetadataReference> metadatareferences = dependingSubmissions.Select(s => MetadataReference.CreateFromImage(s.Image)); if (options.AdditionalReferences != null) { // add additional script references metadatareferences = metadatareferences.Concat(options.AdditionalReferences.Select(r => MetadataReference.CreateFromFile(r))); } // create the compilation object // TODO: add conditionally declared types into the compilation tables var compilation = (PhpCompilation)builder.CoreCompilation .WithAssemblyName(name.Name) .AddSyntaxTrees(tree) .AddReferences(metadatareferences); if (options.EmitDebugInformation) { compilation = compilation.WithPhpOptions(compilation.Options.WithOptimizationLevel(OptimizationLevel.Debug).WithDebugPlusMode(true)); } diagnostics = compilation.GetDeclarationDiagnostics(); if (!HasErrors(diagnostics)) { var peStream = new MemoryStream(); var pdbStream = options.EmitDebugInformation ? new MemoryStream() : null; var result = compilation.Emit(peStream, pdbStream); if (result.Success) { return(new Script(name, peStream, pdbStream, builder, previousSubmissions)); } else { diagnostics = result.Diagnostics; } } } // return(CreateInvalid(diagnostics)); }
private Script(AssemblyName assemblyName, MemoryStream peStream, MemoryStream pdbStream, PhpCompilationFactory builder, IEnumerable <Script> previousSubmissions) { _assemblyName = assemblyName; // peStream.Position = 0; if (pdbStream != null) { pdbStream.Position = 0; } var ass = builder.LoadFromStream(assemblyName, peStream, pdbStream); if (ass == null) { throw new ArgumentException(); } _image = peStream.ToArray().ToImmutableArray(); foreach (var t in ass.GetTypes()) { var attr = Pchp.Core.Reflection.ReflectionUtils.GetScriptAttribute(t); if (attr != null) { _script = t; _entryPoint = new Context.ScriptInfo(-1, attr.Path, t.GetTypeInfo()).Evaluate; break; } } if (_entryPoint == null) { throw new InvalidOperationException(); } // we have to "declare" the script, so its referenced symbols and compiled files are loaded into the context // this comes once after loading the assembly Context.AddScriptReference(ass); // find out highest dependent submissions, if any _previousSubmissions = CollectDependencies(ass, builder, previousSubmissions); }
/// <summary> /// Compiles <paramref name="code"/> and creates script. /// </summary> /// <param name="options">Compilation options.</param> /// <param name="code">Code to be compiled.</param> /// <param name="builder">Assembly builder.</param> /// <param name="previousSubmissions">Enumeration of scripts that were evaluated within current context. New submission may reference them.</param> /// <returns>New script reepresenting the compiled code.</returns> public static Script Create(Context.ScriptOptions options, string code, PhpCompilationFactory builder, IEnumerable <Script> previousSubmissions) { // use the language version of the requesting context Version languageVersion = null; bool shortOpenTags = false; var language = options.Context.TargetPhpLanguage; if (language != null) { shortOpenTags = language.ShortOpenTag; Version.TryParse(language.LanguageVersion, out languageVersion); } // parse the source code var tree = PhpSyntaxTree.ParseCode( SourceText.From(code, Encoding.UTF8), new PhpParseOptions( kind: options.IsSubmission ? SourceCodeKind.Script : SourceCodeKind.Regular, languageVersion: languageVersion, shortOpenTags: shortOpenTags), PhpParseOptions.Default, options.Location.Path); var diagnostics = tree.Diagnostics; if (!HasErrors(diagnostics)) { // TODO: collect required types from {tree}, remember as a script dependencies // TODO: perform class autoload (now before compilation, and then always before invocation) // unique in-memory assembly name var name = builder.GetNewSubmissionName(); // list of scripts that were eval'ed in the context already, // our compilation may depend on them var dependingSubmissions = previousSubmissions.Where(s => !s.Image.IsDefaultOrEmpty); IEnumerable <MetadataReference> metadatareferences = dependingSubmissions.Select(s => MetadataReference.CreateFromImage(s.Image)); if (options.AdditionalReferences != null) { // add additional script references metadatareferences = metadatareferences.Concat(options.AdditionalReferences.Select(r => MetadataReference.CreateFromFile(r))); } // create the compilation object // TODO: add conditionally declared types into the compilation tables var compilation = (PhpCompilation)builder.CoreCompilation .WithAssemblyName(name.Name) .AddSyntaxTrees(tree) .AddReferences(metadatareferences); var emitOptions = new EmitOptions(); var embeddedTexts = default(IEnumerable <EmbeddedText>); if (options.EmitDebugInformation) { compilation = compilation.WithPhpOptions(compilation.Options.WithOptimizationLevel(OptimizationLevel.Debug).WithDebugPlusMode(true)); //emitOptions = emitOptions.WithDebugInformationFormat(DebugInformationFormat.PortablePdb); //embeddedTexts = new[] { EmbeddedText.FromSource(tree.FilePath, tree.GetText()) }; } else { compilation = compilation.WithPhpOptions(compilation.Options.WithOptimizationLevel(OptimizationLevel.Release)); } diagnostics = compilation.GetDeclarationDiagnostics(); if (!HasErrors(diagnostics)) { var peStream = new MemoryStream(); var pdbStream = options.EmitDebugInformation ? new MemoryStream() : null; var result = compilation.Emit(peStream, pdbStream: pdbStream, options: emitOptions, embeddedTexts: embeddedTexts ); if (result.Success) { return(new Script(name, peStream, pdbStream, builder, previousSubmissions)); } else { diagnostics = result.Diagnostics; } } } // return(CreateInvalid(diagnostics)); }
/// <summary> /// Compiles <paramref name="code"/> and creates script. /// </summary> /// <param name="options">Compilation options.</param> /// <param name="code">Code to be compiled.</param> /// <param name="builder">Assembly builder.</param> /// <param name="previousSubmissions">Enumeration of scripts that were evaluated within current context. New submission may reference them.</param> /// <returns>New script reepresenting the compiled code.</returns> public static Script Create(Context.ScriptOptions options, string code, PhpCompilationFactory builder, IEnumerable <Script> previousSubmissions) { // use the language version of the requesting context var languageVersion = options.LanguageVersion; bool shortOpenTags = false; var language = options.Context.TargetPhpLanguage; if (language != null) { shortOpenTags = language.ShortOpenTag; languageVersion ??= language.LanguageVersion; } // unique in-memory assembly name var name = builder.GetNewSubmissionName(); // submission do not have the opening "<?php" script tag: var kind = options.IsSubmission ? SourceCodeKind.Script : SourceCodeKind.Regular; if (kind == SourceCodeKind.Script && options.EmitDebugInformation) { // since submission do not have the opening "<?php" tag, // add a comment with the opening tag, so source code editors don't get confused and colorize the code properly: code = $"#<?php\n{code}"; } // parse the source code var tree = PhpSyntaxTree.ParseCode( SourceText.From(code, Encoding.UTF8, SourceHashAlgorithm.Sha256), new PhpParseOptions( kind: kind, languageVersion: languageVersion, shortOpenTags: shortOpenTags), PhpParseOptions.Default, options.IsSubmission ? BuildSubmissionFileName(options.Location.Path, name.Name) : options.Location.Path ); var diagnostics = tree.Diagnostics; if (!HasErrors(diagnostics)) { // TODO: collect required types from {tree}, remember as a script dependencies // TODO: perform class autoload (now before compilation, and then always before invocation) // list of scripts that were eval'ed in the context already, // our compilation may depend on them var dependingSubmissions = previousSubmissions.Where(s => !s.Image.IsDefaultOrEmpty); IEnumerable <MetadataReference> metadatareferences = dependingSubmissions.Select(s => MetadataReference.CreateFromImage(s.Image)); if (options.AdditionalReferences != null) { // add additional script references metadatareferences = metadatareferences.Concat(options.AdditionalReferences.Select(r => MetadataReference.CreateFromFile(r))); } // create the compilation object // TODO: add conditionally declared types into the compilation tables var compilation = (PhpCompilation)builder.CoreCompilation .WithLangVersion(languageVersion) .WithAssemblyName(name.Name) .AddSyntaxTrees(tree) .AddReferences(metadatareferences); var emitOptions = new EmitOptions(); var embeddedTexts = default(IEnumerable <EmbeddedText>); if (options.EmitDebugInformation) { compilation = compilation.WithPhpOptions(compilation.Options.WithOptimizationLevel(OptimizationLevel.Debug).WithDebugPlusMode(true)); if (options.IsSubmission) { emitOptions = emitOptions.WithDebugInformationFormat(DebugInformationFormat.PortablePdb); embeddedTexts = new[] { EmbeddedText.FromSource(tree.FilePath, tree.GetText()) }; } } else { compilation = compilation.WithPhpOptions(compilation.Options.WithOptimizationLevel(OptimizationLevel.Release)); } diagnostics = compilation.GetDeclarationDiagnostics(); if (!HasErrors(diagnostics)) { var peStream = new MemoryStream(); var pdbStream = options.EmitDebugInformation ? new MemoryStream() : null; var result = compilation.Emit(peStream, pdbStream: pdbStream, options: emitOptions, embeddedTexts: embeddedTexts ); if (result.Success) { //if (pdbStream != null) //{ // // DEBUG DUMP // var fname = @"C:\Users\jmise\OneDrive\Desktop\" + Path.GetFileNameWithoutExtension(tree.FilePath); // File.WriteAllBytes(fname + ".dll", peStream.ToArray()); // File.WriteAllBytes(fname + ".pdb", pdbStream.ToArray()); //} return(new Script(name, peStream, pdbStream, builder, previousSubmissions)); } else { diagnostics = result.Diagnostics; } } } // return(CreateInvalid(diagnostics)); }