/// <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)); }
Context.IScript Context.IScriptingProvider.CreateScript(Context.ScriptOptions options, string code) { var context = ScriptingContext.EnsureContext(options.Context); var script = TryGetOrCreateScript(code, options, context); Debug.Assert(script != null); // context.Submissions.Add(script); // return(script); }
Script CacheLookupNoLock(List <Script> candidates, Context.ScriptOptions options, string code, ScriptingContext context) { foreach (var c in candidates) { // candidate requires that all its dependencies were loaded into context // TODO: resolve the compiled code dependencies - referenced types and declared functions - instead of "DependingSubmissions" if (c.DependingSubmissions.All(context.Submissions.Contains)) { return(c); } } return(null); }
Script CacheLookup(Context.ScriptOptions options, string code, ScriptingContext data) { if (_scripts.TryGetValue(code, out List <Script> candidates)) { foreach (var c in candidates) { // candidate requires that all its dependencies were loaded into context if (c.DependingSubmissions.All(data.Submissions.Contains)) { return(c); } } } return(null); }
Script /*!*/ TryGetOrCreateScript(string code, Context.ScriptOptions options, ScriptingContext context) { var script = default(Script); _scriptsLock.EnterUpgradeableReadLock(); try { if (!_scripts.TryGetValue(code, out var subsmissions)) { _scriptsLock.EnterWriteLock(); try { if (!_scripts.TryGetValue(code, out subsmissions)) { _scripts[code] = subsmissions = new List <Script>(1); } } finally { _scriptsLock.ExitWriteLock(); } } if ((script = CacheLookupNoLock(subsmissions, options, code, context)) == null) { _scriptsLock.EnterWriteLock(); try { if ((script = CacheLookupNoLock(subsmissions, options, code, context)) == null) { subsmissions.Add((script = Script.Create(options, code, _builder, context.Submissions))); } } finally { _scriptsLock.ExitWriteLock(); } } } finally { _scriptsLock.ExitUpgradeableReadLock(); } return(script); }
Context.IScript Context.IScriptingProvider.CreateScript(Context.ScriptOptions options, string code) { var data = ScriptingContext.EnsureContext(options.Context); var script = CacheLookup(options, code, data); if (script == null) { // TODO: rwlock cache[code] script = Script.Create(options, code, _builder, data.Submissions); EnsureCache(code).Add(script); } Debug.Assert(script != null); // data.Submissions.Add(script); // return(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) { // 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)); }