diff --git a/src/CSScriptLib/src/CSScriptLib/CSScript.cs b/src/CSScriptLib/src/CSScriptLib/CSScript.cs
index 9f55df8b..4ff9ecc9 100644
--- a/src/CSScriptLib/src/CSScriptLib/CSScript.cs
+++ b/src/CSScriptLib/src/CSScriptLib/CSScript.cs
@@ -62,6 +62,8 @@ public static string GetCacheDirectory(string file)
///
public class Settings
{
+ internal const string DefaultEncodingName = "default";
+
///
/// Loads and returns the settings instance.
///
diff --git a/src/CSScriptLib/src/CSScriptLib/CSScriptLib.csproj b/src/CSScriptLib/src/CSScriptLib/CSScriptLib.csproj
index 98e6ee46..d07225b4 100644
--- a/src/CSScriptLib/src/CSScriptLib/CSScriptLib.csproj
+++ b/src/CSScriptLib/src/CSScriptLib/CSScriptLib.csproj
@@ -122,6 +122,7 @@ CS-Script now uses separate temporary directories for better isolation:
+
diff --git a/src/CSScriptLib/src/CSScriptLib/Evaluator.CodeDom.cs b/src/CSScriptLib/src/CSScriptLib/Evaluator.CodeDom.cs
index b7cb8768..9ee5204f 100644
--- a/src/CSScriptLib/src/CSScriptLib/Evaluator.CodeDom.cs
+++ b/src/CSScriptLib/src/CSScriptLib/Evaluator.CodeDom.cs
@@ -220,6 +220,49 @@ override protected (byte[] asm, byte[] pdb, Project project) Compile(string scri
return scriptCache[scriptHash];
}
+ /////////////////////////////////////
+ var lightParser = new CSharpParser(scriptFile ?? tempScriptFile, true);
+
+ if (lightParser.Precompilers.Any())
+ {
+ var actualSources = sources.ToList();
+
+ var precompiliationResult = base.PrecompileScript(scriptFile ?? tempScriptFile, lightParser);
+
+ if (precompiliationResult != null)
+ {
+ tempScriptFile ??= CSScript.GetScriptTempFile();
+ File.WriteAllText(tempScriptFile, precompiliationResult.Content);
+ actualSources[0] = tempScriptFile;
+ }
+
+ int index = 1;
+ foreach (string file in sources.Skip(1))
+ {
+ if (!file.EndsWith(Globals.InjectedAttributesPrefix))
+ {
+ var code = File.ReadAllText(file);
+
+ var precompResult = base.PrecompileImportedScript(code, lightParser);
+ if (precompResult != null)
+ {
+ var newFile = file.ChangeExtension(".g.cs");
+ File.WriteAllText(newFile, precompResult.Content);
+ actualSources[index] = newFile;
+ }
+ }
+ index++;
+ }
+
+ actualSources.AddRange(precompiliationResult?.NewIncludes ?? []);
+ sources = actualSources.Distinct().ToArray();
+
+ if (precompiliationResult?.NewIncludes?.Any() == true)
+ {
+ refs = refs.Concat(precompiliationResult.NewReferences).ToArray();
+ }
+ }
+
(byte[] asm, byte[] pdb) result = CompileAssemblyFromFileBatch_with_Csc(sources, refs, info?.AssemblyFile, this.IsDebug, info);
if (IsCachingEnabled)
diff --git a/src/CSScriptLib/src/CSScriptLib/Evaluator.Roslyn.cs b/src/CSScriptLib/src/CSScriptLib/Evaluator.Roslyn.cs
index 1b360470..1131c2a3 100644
--- a/src/CSScriptLib/src/CSScriptLib/Evaluator.Roslyn.cs
+++ b/src/CSScriptLib/src/CSScriptLib/Evaluator.Roslyn.cs
@@ -68,30 +68,6 @@
//
namespace CSScriptLib
{
- static class localExtensions
- {
- public static (string file, int line) Translate(this Dictionary<(int, int), (string, int)> mapping, int line)
- {
- foreach ((int start, int end) range in mapping.Keys)
- if (range.start <= line && line <= range.end)
- {
- (string file, int lineOffset) = mapping[range];
- return (file, line - range.start + lineOffset);
- }
-
- return ("", 0);
- }
-
- static public string[] SeparateUsingsFromCode(this string code)
- {
- SyntaxTree tree = CSharpSyntaxTree.ParseText(code);
- CompilationUnitSyntax root = tree.GetCompilationUnitRoot();
- int pos = root.Usings.FullSpan.End;
-
- return new[] { code.Substring(0, pos).TrimEnd(), code.Substring(pos) };
- }
- }
-
///
///
///
@@ -208,13 +184,32 @@ override protected (byte[] asm, byte[] pdb, Project project) Compile(string scri
////////////////////////////////////////
var mapping = new Dictionary<(int, int), (string, int)>();
+ var lightParser = new CSharpParser(scriptText, false);
+ List extraRefs = [];
- if (scriptFile == null && new CSharpParser(scriptText, false).Imports.Any())
+ if (scriptFile == null && (lightParser.Imports.Any() || lightParser.Precompilers.Any()))
{
tempScriptFile = CSScript.GetScriptTempFile();
File.WriteAllText(tempScriptFile, scriptText);
}
+ if (lightParser.Precompilers.Any())
+ {
+ var precompiliationResult = base.PrecompileScript(scriptFile ?? tempScriptFile, lightParser);
+
+ if (precompiliationResult != null)
+ {
+ tempScriptFile ??= CSScript.GetScriptTempFile();
+ File.WriteAllText(tempScriptFile, precompiliationResult.Content);
+ scriptText = precompiliationResult.Content;
+
+ if (precompiliationResult?.NewIncludes?.Any() == true)
+ {
+ extraRefs.AddRange(precompiliationResult.NewReferences);
+ }
+ }
+ }
+
if (scriptFile == null && tempScriptFile == null)
{
// if (!DisableReferencingFromCode && info?.CodeKind != SourceCodeKind.Script)
@@ -247,9 +242,17 @@ override protected (byte[] asm, byte[] pdb, Project project) Compile(string scri
{
var parts = File.ReadAllText(file).SeparateUsingsFromCode();
var usings = parts[0].GetLines();
- var code = parts[1].GetLines();
+ var code = parts[1];
+
+ if (!file.EndsWith(Globals.InjectedAttributesPrefix) && lightParser.Precompilers.Any())
+ {
+ var precompResult = base.PrecompileImportedScript(code, lightParser);
- importedSources[file] = (usings.Count(), code);
+ if (precompResult != null)
+ code = precompResult.Content;
+ }
+
+ importedSources[file] = (usings.Count(), code.GetLines());
add_code(file, usings, 0);
}
@@ -320,7 +323,7 @@ void add_code(string file, string[] codeLines, int lineOffset)
var explicitRefs = this.refAssemblies.Except(refs); // from code
- foreach (var asm in refs.Concat(explicitRefs))
+ foreach (var asm in refs.Concat(explicitRefs).Concat(extraRefs.Select(x => Assembly.LoadFrom(x))))
{
var metadata = ToMetadata(asm);
if (metadata != null)
@@ -671,4 +674,28 @@ public override IEvaluator Reset(bool referenceDomainAssemblies = true)
return this;
}
}
+
+ static class localExtensions
+ {
+ public static (string file, int line) Translate(this Dictionary<(int, int), (string, int)> mapping, int line)
+ {
+ foreach ((int start, int end) range in mapping.Keys)
+ if (range.start <= line && line <= range.end)
+ {
+ (string file, int lineOffset) = mapping[range];
+ return (file, line - range.start + lineOffset);
+ }
+
+ return ("", 0);
+ }
+
+ static public string[] SeparateUsingsFromCode(this string code)
+ {
+ SyntaxTree tree = CSharpSyntaxTree.ParseText(code);
+ CompilationUnitSyntax root = tree.GetCompilationUnitRoot();
+ int pos = root.Usings.FullSpan.End;
+
+ return new[] { code.Substring(0, pos).TrimEnd(), code.Substring(pos) };
+ }
+ }
}
\ No newline at end of file
diff --git a/src/CSScriptLib/src/CSScriptLib/EvaluatorBase.cs b/src/CSScriptLib/src/CSScriptLib/EvaluatorBase.cs
index cae8a3e8..e770d1a7 100644
--- a/src/CSScriptLib/src/CSScriptLib/EvaluatorBase.cs
+++ b/src/CSScriptLib/src/CSScriptLib/EvaluatorBase.cs
@@ -33,17 +33,22 @@
//using Microsoft.CodeAnalysis;
//using Microsoft.CodeAnalysis.CSharp.Scripting
using System;
+using System.Collections;
using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
+using System.Reflection.Metadata;
using System.Runtime.Loader;
using System.Runtime.Serialization;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using System.Xml.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Scripting;
using csscript;
using CSScripting;
using CSScripting.CodeDom;
@@ -1028,6 +1033,141 @@ public IEvaluator ReferenceAssembliesFromCode(string code, params string[] searc
return this;
}
+ Dictionary precompilersCache = new();
+
+ internal PrecompilationContext PrecompileImportedScript(string scriptCode, CSharpParser primaryScript)
+ {
+ string[] searchDirs = [this.GetType().Assembly.Location().GetDirName(), .. primaryScript.ExtraSearchDirs];
+
+ var retval = new PrecompilationContext { SearchDirs = searchDirs };
+
+ Hashtable contextData = new()
+ {
+ ["NewDependencies"] = retval.NewDependencies,
+ ["NewSearchDirs"] = retval.NewSearchDirs,
+ ["NewReferences"] = retval.NewReferences,
+ ["NewIncludes"] = retval.NewIncludes,
+ ["NewCompilerOptions"] = "",
+ ["SearchDirs"] = retval.SearchDirs,
+ };
+
+ var content = scriptCode;
+ var modified = false;
+
+ foreach (string file in primaryScript.Precompilers)
+ {
+ var precompilerPath = Precompiler.FindImlementationFile(file, searchDirs);
+ if (!precompilersCache.ContainsKey(precompilerPath))
+ throw new Exception("Precompiler " + file + " cache cannot be loaded."); // but it must exist since the script precompilers have been processed already
+
+ var precompiler = precompilersCache[precompilerPath];
+
+ bool result = ApplyPrecompilation(null, retval, contextData, ref content, precompilerPath);
+
+ if (result)
+ {
+ retval.Content = content;
+ retval.NewDependencies.Add(file);
+ modified = true;
+ }
+ }
+ return modified ? retval : null;
+ }
+
+ internal PrecompilationContext PrecompileScript(string script, CSharpParser parser)
+ {
+ string[] searchDirs = [this.GetType().Assembly.Location().GetDirName(), .. parser.ExtraSearchDirs];
+
+ var retval = new PrecompilationContext { SearchDirs = searchDirs };
+
+ Hashtable contextData = new()
+ {
+ ["NewDependencies"] = retval.NewDependencies,
+ ["NewSearchDirs"] = retval.NewSearchDirs,
+ ["NewReferences"] = retval.NewReferences,
+ ["NewIncludes"] = retval.NewIncludes,
+ ["NewCompilerOptions"] = "",
+ ["SearchDirs"] = retval.SearchDirs,
+ };
+
+ var content = parser.Code;
+ var modified = false;
+
+ foreach (string file in parser.Precompilers)
+ {
+ var precompilerPath = Precompiler.FindImlementationFile(file, searchDirs);
+
+ using (SimpleAsmProbing.For(searchDirs))
+ {
+ if (!precompilersCache.ContainsKey(precompilerPath))
+ {
+ (byte[] asm, byte[] pdb, Project project) precompilerInfo = Compile(null, file, null);
+
+ var precompilerAsm = Assembly.Load(precompilerInfo.asm, precompilerInfo.pdb);
+
+ var precompilerType = precompilerAsm.GetTypes().FirstOrDefault(x => x.Name.EndsWith("Precompiler"));
+ if (precompilerType == null)
+ throw new Exception("Precompiler " + file + " cannot be loaded. CreateInstance returned null.");
+
+ var methods = precompilerType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance)
+ .Where(x => x.Name == "Compile");
+
+ precompilersCache[precompilerPath] =
+ (
+ method: methods.FirstOrDefault(x => x.GetParameters().Count() == 1),
+ legacyMethod: methods.FirstOrDefault(x => x.GetParameters().Count() == 4)
+ );
+ }
+
+ bool result = ApplyPrecompilation(script, retval, contextData, ref content, precompilerPath);
+
+ if (result)
+ {
+ retval.Content = content;
+ retval.NewDependencies.Add(file);
+ modified = true;
+ }
+ }
+ }
+ return modified ? retval : null;
+ }
+
+ private bool ApplyPrecompilation(string script, PrecompilationContext retval, Hashtable contextData, ref string content, string precompilerPath)
+ {
+ var (method, legacyMethod) = precompilersCache[precompilerPath];
+
+ bool result;
+
+ if (method != null)
+ {
+ // bool Compile(dynamic context) bool
+ // Compile(PrecompilationContext context)
+ object compiler = null;
+ if (!method.IsStatic)
+ compiler = Activator.CreateInstance(method.DeclaringType);
+
+ retval.Content = content;
+
+ result = (bool)method.Invoke(compiler, [retval]);
+
+ if (result)
+ content = retval.Content;
+ }
+ else
+ {
+ // public static bool Compile(ref string scriptCode, string
+ // scriptFile, bool isPrimaryScript, Hashtable context)
+ var compile = (Precompiler.CompileMethod)Delegate.CreateDelegate(typeof(Precompiler.CompileMethod), legacyMethod);
+
+ result = compile(ref content,
+ script,
+ IsPrimaryScript: script != null,
+ contextData);
+ }
+
+ return result;
+ }
+
///
/// References the given assembly by the assembly path.
///
diff --git a/src/Tests.CSScriptLib/Evaluator.Api.Test.cs b/src/Tests.CSScriptLib/Evaluator.Api.Test.cs
index 78496b34..e61f03d3 100644
--- a/src/Tests.CSScriptLib/Evaluator.Api.Test.cs
+++ b/src/Tests.CSScriptLib/Evaluator.Api.Test.cs
@@ -65,13 +65,13 @@ public API_Roslyn()
string testTempFile(string fileName, [CallerMemberName] string caller = null)
{
var rootDir = "TestData".PathJoin(nameof(API_Roslyn), caller).GetFullPath().EnsureDir();
- if (fileName == "asm.hidden-from-xunit-dll-file")
- {
- File.AppendAllText(
- @"D:\dev\cs-script\src\Tests.CSScriptLib\bin\Debug\net10.0\TestData\test-error.log",
- rootDir.PathJoin(fileName) + Environment.NewLine);
- Debugger.Launch(); // asm.dll is a special file name that xUnit locks just because it was present in the local dir. So avoid using it in the tests.
- }
+ // if (fileName == "asm.hidden-from-xunit-dll-file")
+ // {
+ // File.AppendAllText(
+ // @"D:\dev\cs-script\src\Tests.CSScriptLib\bin\Debug\net10.0\TestData\test-error.log",
+ // rootDir.PathJoin(fileName) + Environment.NewLine);
+ // Debugger.Launch(); // asm.dll is a special file name that xUnit locks just because it was present in the local dir. So avoid using it in the tests.
+ // }
return Path.Combine(rootDir, fileName);
}
diff --git a/src/Tests.CSScriptLib/Evaluator.CodeDom.Test.cs b/src/Tests.CSScriptLib/Evaluator.CodeDom.Test.cs
index 7a2410cd..f0e6ccc9 100644
--- a/src/Tests.CSScriptLib/Evaluator.CodeDom.Test.cs
+++ b/src/Tests.CSScriptLib/Evaluator.CodeDom.Test.cs
@@ -290,5 +290,221 @@ public int Add(int a, int b)
// dynamic script = asm.CreateObject("*");
// var result = script.Sum(7, 3);
// }
+
+ [Fact]
+ public void Precompiler_for_script_code()
+ {
+ var pre = testTempFile("precompiler.cs");
+
+ File.WriteAllText(pre, @"using System;
+ using System.Collections;
+ public class Sample_Precompiler
+ {
+ public static bool Compile(ref string scriptCode, string scriptFile, bool isPrimaryScript, Hashtable context)
+ {
+ scriptCode = scriptCode.Replace(""Hello World"", ""Hello World!!!"");
+ return true;
+ }
+ }");
+
+ dynamic script = CSScript.CodeDomEvaluator
+ .LoadCode($@"//css_precompiler {pre}
+ public class Script
+ {{
+ public string foo()
+ => ""Hello World"";
+ }}");
+
+ var result = script.foo();
+
+ Assert.Equal("Hello World!!!", result);
+ }
+
+ [Fact]
+ public void Precompiler_alt_signature()
+ {
+ var pre = testTempFile("precompiler.cs");
+
+ File.WriteAllText(pre, $@"//css_ref {typeof(PrecompilationContext).Assembly.Location}
+ using System;
+ using System.Collections;
+ public class Sample_Precompiler
+ {{
+ public bool Compile(csscript.PrecompilationContext context)
+ {{
+ context.Content = context.Content.Replace(""Hello World"", ""Hello World!!!"");
+ return true;
+ }}
+ }}");
+
+ CSScript.EvaluatorConfig.DebugBuild = true;
+
+ dynamic script = CSScript.CodeDomEvaluator
+ .LoadCode($@"//css_precompiler {pre}
+ public class Script
+ {{
+ public string foo()
+ => ""Hello World"";
+ }}");
+
+ var result = script.foo();
+
+ Assert.Contains("Hello World!!!", result);
+ }
+
+ [Fact]
+ public void Precompiler_alt_signature2()
+ {
+ var pre = testTempFile("precompiler.cs");
+
+ File.WriteAllText(pre, @"using System;
+ using System.Collections;
+ public class Sample_Precompiler
+ {
+ public bool Compile(dynamic context)
+ {
+ context.Content = context.Content.Replace(""Hello World"", ""Hello World!!!"");
+ return true;
+ }
+ }");
+
+ CSScript.EvaluatorConfig.DebugBuild = true;
+
+ dynamic script = CSScript.CodeDomEvaluator
+ .LoadCode($@"//css_precompiler {pre}
+ public class Script
+ {{
+ public string foo()
+ => ""Hello World"";
+ }}");
+
+ var result = script.foo();
+
+ Assert.Contains("Hello World!!!", result);
+ }
+
+ [Fact]
+ public void Precompiler_imported_scripts()
+ {
+ var pre = testTempFile("precompiler.cs");
+ var importedScript = testTempFile("imported.cs");
+
+ File.WriteAllText(pre,
+ @"using System;
+ using System.Collections;
+ public class Sample_Precompiler
+ {
+ public bool Compile(csscript.PrecompilationContext context)
+ {
+ context.Content = context.Content.Replace(""Hello World"", ""Hello World!!!"");
+ return true;
+ }
+ }");
+
+ File.WriteAllText(importedScript,
+ @"public class Utils
+ {
+ public static string foo()=> ""Hello World"";
+ }");
+
+ dynamic script = CSScript.CodeDomEvaluator
+ .LoadCode($@"//css_precompiler {pre}
+ //css_include {importedScript}
+ public class Script
+ {{
+ public string foo()
+ => ""Hello World"";
+ public string foo2()
+ => Utils.foo();
+ }}");
+
+ var result = script.foo();
+ Assert.Contains("Hello World!!!", result);
+
+ result = script.foo2();
+ Assert.Contains("Hello World!!!", result);
+ }
+
+ [Fact]
+ public void Precompiler_imported_scripts2()
+ {
+ var pre = testTempFile("precompiler.cs");
+ var importedScript = testTempFile("imported.cs");
+
+ File.WriteAllText(pre,
+ @"using System;
+ using System.Collections;
+ public class Sample_Precompiler
+ {
+ public static bool Compile(ref string code, string scriptFile, bool isPrimaryScript, Hashtable context)
+ {
+ code = code.Replace(""Hello World"", ""Hello World!!!"");
+ return true;
+ }
+ }");
+
+ File.WriteAllText(importedScript,
+ @"public class Utils
+ {
+ public static string foo()=> ""Hello World"";
+ }");
+
+ // CSScript.EvaluatorConfig.DebugBuild = true;
+
+ dynamic script = CSScript.CodeDomEvaluator
+ .LoadCode($@"//css_precompiler {pre}
+ //css_include {importedScript}
+ public class Script
+ {{
+ public string foo()
+ => ""Hello World"";
+ public string foo2()
+ => Utils.foo();
+ }}");
+
+ var result = script.foo();
+ Assert.Contains("Hello World!!!", result);
+
+ result = script.foo2();
+ Assert.Contains("Hello World!!!", result);
+ }
+
+ [Fact]
+ public void Precompiler_for_script_file()
+ {
+ var preFile = testTempFile("precompiler.cs");
+ var scriptFile = testTempFile("primary_script.cs");
+
+ File.WriteAllText(preFile, $@"using System;
+ using System.Collections;
+ public class Sample_Precompiler
+ {{
+ public static bool Compile(ref string scriptCode, string scriptFile, bool isPrimaryScript, Hashtable context)
+ {{
+ scriptCode = scriptCode.Replace(""Hello World"", ""Hello World!!!"");
+ return true;
+ }}
+ }}");
+
+ File.WriteAllText(scriptFile, $@"//css_precompiler {preFile}
+ public class Script
+ {{
+ public string foo()
+ => ""Hello World"";
+ }}");
+
+ dynamic script = CSScript.CodeDomEvaluator
+ .LoadFile(scriptFile);
+
+ var result = script.foo();
+ Assert.Contains("Hello World!!!", result);
+
+ var scriptAsm = CSScript.CodeDomEvaluator
+ .CompileAssemblyFromFile(scriptFile, scriptFile + ".dll");
+ dynamic script2 = Assembly.LoadFrom(scriptAsm).CreateObject("*");
+
+ result = script2.foo();
+ Assert.Contains("Hello World!!!", result);
+ }
}
}
\ No newline at end of file
diff --git a/src/Tests.CSScriptLib/Evaluator.Roslyn.Tests.cs b/src/Tests.CSScriptLib/Evaluator.Roslyn.Tests.cs
index 8dc206e5..193cf813 100644
--- a/src/Tests.CSScriptLib/Evaluator.Roslyn.Tests.cs
+++ b/src/Tests.CSScriptLib/Evaluator.Roslyn.Tests.cs
@@ -152,6 +152,222 @@ public void call_LoadMethod()
Assert.Equal(5, result[1]);
}
+ [Fact]
+ public void Precompiler_for_script_code()
+ {
+ var pre = testTempFile("precompiler.cs");
+
+ File.WriteAllText(pre, $@"//css_ref {typeof(PrecompilationContext).Assembly.Location}
+ using System;
+ using System.Collections;
+ public class Sample_Precompiler
+ {{
+ public static bool Compile(ref string scriptCode, string scriptFile, bool isPrimaryScript, Hashtable context)
+ {{
+ scriptCode = scriptCode.Replace(""Hello World"", ""Hello World!!!"");
+ return true;
+ }}
+ }}");
+
+ dynamic script = CSScript.RoslynEvaluator
+ .LoadCode($@"//css_precompiler {pre}
+ public class Script
+ {{
+ public string foo()
+ => ""Hello World"";
+ }}");
+
+ var result = script.foo();
+
+ Assert.Equal("Hello World!!!", result);
+ }
+
+ [Fact]
+ public void Precompiler_alt_signature2()
+ {
+ var pre = testTempFile("precompiler.cs");
+
+ File.WriteAllText(pre, $@"using System;
+ using System.Collections;
+ public class Sample_Precompiler
+ {{
+ public bool Compile(dynamic context)
+ {{
+ context.Content = context.Content.Replace(""Hello World"", ""Hello World!!!"");
+ return true;
+ }}
+ }}");
+
+ CSScript.EvaluatorConfig.DebugBuild = true;
+
+ dynamic script = CSScript.RoslynEvaluator
+ .LoadCode($@"//css_precompiler {pre}
+ public class Script
+ {{
+ public string foo()
+ => ""Hello World"";
+ }}");
+
+ var result = script.foo();
+
+ Assert.Contains("Hello World!!!", result);
+ }
+
+ [Fact]
+ public void Precompiler_alt_signature()
+ {
+ var pre = testTempFile("precompiler.cs");
+
+ File.WriteAllText(pre, $@"using System;
+ using System.Collections;
+ public class Sample_Precompiler
+ {{
+ public bool Compile(dynamic context)
+ {{
+ context.Content = context.Content.Replace(""Hello World"", ""Hello World!!!"");
+ return true;
+ }}
+ }}");
+
+ CSScript.EvaluatorConfig.DebugBuild = true;
+
+ dynamic script = CSScript.RoslynEvaluator
+ .LoadCode($@"//css_precompiler {pre}
+ public class Script
+ {{
+ public string foo()
+ => ""Hello World"";
+ }}");
+
+ var result = script.foo();
+
+ Assert.Contains("Hello World!!!", result);
+ }
+
+ [Fact]
+ public void Precompiler_imported_scripts()
+ {
+ var pre = testTempFile("precompiler.cs");
+ var importedScript = testTempFile("imported.cs");
+
+ File.WriteAllText(pre,
+ @"using System;
+ using System.Collections;
+ public class Sample_Precompiler
+ {
+ public bool Compile(dynamic context)
+ {
+ context.Content = context.Content.Replace(""Hello World"", ""Hello World!!!"");
+ return true;
+ }
+ }");
+
+ File.WriteAllText(importedScript,
+ @"public class Utils
+ {
+ public static string foo()=> ""Hello World"";
+ }");
+
+ dynamic script = CSScript.RoslynEvaluator
+ .LoadCode($@"//css_precompiler {pre}
+ //css_include {importedScript}
+ public class Script
+ {{
+ public string foo()
+ => ""Hello World"";
+ public string foo2()
+ => Utils.foo();
+ }}");
+
+ var result = script.foo();
+ Assert.Contains("Hello World!!!", result);
+
+ result = script.foo2();
+ Assert.Contains("Hello World!!!", result);
+ }
+
+ [Fact]
+ public void Precompiler_imported_scripts2()
+ {
+ var pre = testTempFile("precompiler.cs");
+ var importedScript = testTempFile("imported.cs");
+
+ File.WriteAllText(pre,
+ @"using System;
+ using System.Collections;
+ public class Sample_Precompiler
+ {
+ public static bool Compile(ref string code, string scriptFile, bool isPrimaryScript, Hashtable context)
+ {
+ code = code.Replace(""Hello World"", ""Hello World!!!"");
+ return true;
+ }
+ }");
+
+ File.WriteAllText(importedScript,
+ @"public class Utils
+ {
+ public static string foo()=> ""Hello World"";
+ }");
+
+ // CSScript.EvaluatorConfig.DebugBuild = true;
+
+ dynamic script = CSScript.RoslynEvaluator
+ .LoadCode($@"//css_precompiler {pre}
+ //css_include {importedScript}
+ public class Script
+ {{
+ public string foo()
+ => ""Hello World"";
+ public string foo2()
+ => Utils.foo();
+ }}");
+
+ var result = script.foo();
+ Assert.Contains("Hello World!!!", result);
+
+ result = script.foo2();
+ Assert.Contains("Hello World!!!", result);
+ }
+
+ [Fact]
+ public void Precompiler_for_script_file()
+ {
+ var preFile = testTempFile("precompiler.cs");
+ var scriptFile = testTempFile("primary_script.cs");
+
+ File.WriteAllText(preFile, $@"using System;
+ using System.Collections;
+ public class Sample_Precompiler
+ {{
+ public static bool Compile(ref string scriptCode, string scriptFile, bool isPrimaryScript, Hashtable context)
+ {{
+ scriptCode = scriptCode.Replace(""Hello World"", ""Hello World!!!"");
+ return true;
+ }}
+ }}");
+
+ File.WriteAllText(scriptFile, $@"//css_precompiler {preFile}
+ public class Script
+ {{
+ public string foo()
+ => ""Hello World"";
+ }}");
+
+ dynamic script = CSScript.RoslynEvaluator
+ .LoadFile(scriptFile);
+
+ var result = script.foo();
+ Assert.Contains("Hello World!!!", result);
+
+ var scriptAsm = CSScript.RoslynEvaluator
+ .CompileAssemblyFromFile(scriptFile, scriptFile + ".dll");
+ dynamic script2 = Assembly.LoadFrom(scriptAsm).CreateObject("*");
+
+ result = script2.foo();
+ Assert.Contains("Hello World!!!", result);
+ }
+
public class Host : IScriptHost
{
public void WriteLine(string message)
diff --git a/src/Tests.cscs/FileParserTests.cs b/src/Tests.cscs/FileParserTests.cs
index d7d64489..f2bd97b3 100644
--- a/src/Tests.cscs/FileParserTests.cs
+++ b/src/Tests.cscs/FileParserTests.cs
@@ -2,11 +2,11 @@
using System.IO;
using System.Linq;
using System.Reflection;
+using static System.Reflection.BindingFlags;
using csscript;
using CSScripting;
using CSScriptLib;
using Xunit;
-using static System.Reflection.BindingFlags;
namespace Misc
{
@@ -19,7 +19,7 @@ namespace Misc
///
public class TestFolder
{
- public static string root = Assembly.GetExecutingAssembly().Location.GetDirName().PathJoin("test").EnsureDir();
+ public static string root = Assembly.GetExecutingAssembly().Location.GetDirName().PathJoin("test", "TestFolder").EnsureDir();
public TestFolder()
{
diff --git a/src/cscs/Precompiler.cs b/src/cscs/Precompiler.cs
index 3838901e..06409026 100644
--- a/src/cscs/Precompiler.cs
+++ b/src/cscs/Precompiler.cs
@@ -1,10 +1,16 @@
-using CSScripting;
using System;
using System.Collections;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
+using System.Linq;
+using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
+using System.Threading;
+using Microsoft.CodeAnalysis;
+using CSScripting;
+using CSScriptLib;
namespace csscript
{
@@ -51,11 +57,58 @@ public class PrecompilationContext
///
public string[] SearchDirs = new string[0];
+ ///
+ /// Gets or sets the precompilation content associated with this instance.
+ ///
public string Content;
+
+ ///
+ /// Gets or sets the path to the script file used for processing.
+ ///
+ /// This property should contain a valid file path. Ensure that the file exists and is
+ /// accessible before attempting to use it.
public string scriptFile;
+
+ ///
+ /// Gets or sets a value indicating whether this script is the primary script for the associated entity.
+ ///
public bool IsPrimaryScript;
}
+ class Precompiler
+ {
+ public delegate bool CompileMethod(ref string content, string scriptFile, bool IsPrimaryScript, Hashtable context);
+
+ public static string FindImlementationFile(string file, string[] searchDirs)
+ {
+ string retval = FindFile(file, searchDirs);
+
+ if (retval == null && !Path.HasExtension(file))
+ {
+ retval = FindFile(file + ".cs", searchDirs) ??
+ FindFile(file + ".dll", searchDirs);
+ }
+
+ return retval;
+ }
+
+ public static string FindFile(string file, string[] searchDirs)
+ {
+ if (File.Exists(file))
+ {
+ return Path.GetFullPath(file);
+ }
+ else if (!Path.IsPathRooted(file))
+ {
+ foreach (string dir in searchDirs)
+ if (File.Exists(Path.Combine(dir, file)))
+ return Path.Combine(dir, file);
+ }
+
+ return null;
+ }
+ }
+
internal class DefaultPrecompiler
{
public static bool Compile(ref string code, string scriptFile, bool IsPrimaryScript, Hashtable context)
@@ -343,7 +396,7 @@ internal static Result Process(string content, string consoleEncoding)
}
}
- if (!autoCodeInjected && entryPointInjectionPos != -1 && !Utils.IsNullOrWhiteSpace(lineText))
+ if (!autoCodeInjected && entryPointInjectionPos != -1 && lineText.HasText())
{
bracket_count += lineText.Split('{').Length - 1;
bracket_count -= lineText.Split('}').Length - 1;
diff --git a/src/cscs/Services.Roslyn.cs b/src/cscs/Services.Roslyn.cs
index 8d981644..a52721a8 100644
--- a/src/cscs/Services.Roslyn.cs
+++ b/src/cscs/Services.Roslyn.cs
@@ -51,7 +51,7 @@ public static CompilerResults CompileAssemblyFromFileBatch_with_roslyn(CompilerP
.EnsureDir();
string firstScript = fileNames.First();
- string attr_file = fileNames.FirstOrDefault(x => x.EndsWith(".attr.g.cs", StringComparison.OrdinalIgnoreCase) || x.EndsWith(".attr.g.vb", StringComparison.OrdinalIgnoreCase));
+ string attr_file = fileNames.FirstOrDefault(x => x.EndsWith(Globals.InjectedAttributesPrefix) || x.EndsWith(".attr.g.vb"));
string dbg_inject_file = fileNames.FirstOrDefault(x => x.GetFileName().StartsWith("dbg.inject.", StringComparison.OrdinalIgnoreCase));
string single_source = build_dir.PathJoin(firstScript.GetFileName());
diff --git a/src/cscs/Utils/CoreExtensions.cs b/src/cscs/Utils/CoreExtensions.cs
index 48bc6cf6..19ecaa86 100644
--- a/src/cscs/Utils/CoreExtensions.cs
+++ b/src/cscs/Utils/CoreExtensions.cs
@@ -1,19 +1,15 @@
-using csscript;
-using CSScripting;
-using CSScripting.CodeDom;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
-using System.Runtime.Serialization;
-using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using CSScripting;
#if class_lib
@@ -375,7 +371,7 @@ public static string EnquoteArg(this string text, char quotationCharacter = '"')
internal static bool IsTopLevelStatement(this SyntaxTree code)
=> //code.GetCompilationUnitRoot().Members.OfType().Any();
- code.GetRoot().DescendantNodes().OfType().Any();
+ code.GetRoot().DescendantNodes().OfType().Any();
///
/// Determines whether the specified assembly path corresponds to a runtime assembly.
diff --git a/src/cscs/Utils/Globals.cs b/src/cscs/Utils/Globals.cs
index 29ca8256..64a0e687 100644
--- a/src/cscs/Utils/Globals.cs
+++ b/src/cscs/Utils/Globals.cs
@@ -43,6 +43,7 @@ internal static void DbgLog(string line, string context = null)
public static bool DefaultRoslynCompilationToScript { get; set; } = true;
static internal string DynamicWrapperClassName = "DynamicClass";
+ static internal string InjectedAttributesPrefix = ".attr.g.cs";
static internal string RootClassName = "css_root";
// Roslyn still does not support anything else but `Submission#0` (17 Jul 2019) [update]
// Roslyn now does support alternative class names (1 Jan 2020)
diff --git a/src/cscs/Utils/Utils.cs b/src/cscs/Utils/Utils.cs
index 2278c6bb..ca4ffd72 100644
--- a/src/cscs/Utils/Utils.cs
+++ b/src/cscs/Utils/Utils.cs
@@ -1336,7 +1336,7 @@ internal static Dictionary> LoadPrecompilers(ExecuteOptions
if (precompilerFile != "" && precompilerFile != noDefaultPrecompilerSwitch)
{
- string sourceFile = FindImlementationFile(precompilerFile, options.searchDirs)
+ string sourceFile = Precompiler.FindImlementationFile(precompilerFile, options.searchDirs)
?? throw new ApplicationException("Cannot find Precompiler file " + precompilerFile);
Assembly asm;
@@ -1391,35 +1391,6 @@ internal static Dictionary> LoadPrecompilers(ExecuteOptions
return retval;
}
- public static string FindFile(string file, string[] searchDirs)
- {
- if (File.Exists(file))
- {
- return Path.GetFullPath(file);
- }
- else if (!Path.IsPathRooted(file))
- {
- foreach (string dir in searchDirs)
- if (File.Exists(Path.Combine(dir, file)))
- return Path.Combine(dir, file);
- }
-
- return null;
- }
-
- public static string FindImlementationFile(string file, string[] searchDirs)
- {
- string retval = FindFile(file, searchDirs);
-
- if (retval == null && !Path.HasExtension(file))
- {
- retval = FindFile(file + ".cs", searchDirs) ??
- FindFile(file + ".dll", searchDirs);
- }
-
- return retval;
- }
-
static void kill(string proc_name)
{
try
@@ -1457,7 +1428,7 @@ internal static int GenerateCompilationContext(CSharpParser parser, ExecuteOptio
{
if (file != "")
{
- sb.Append(FindImlementationFile(file, options.searchDirs))
+ sb.Append(Precompiler.FindImlementationFile(file, options.searchDirs))
.Append(',');
}
}
diff --git a/src/cscs/csscript.cs b/src/cscs/csscript.cs
index bb07d0de..b7f51c56 100644
--- a/src/cscs/csscript.cs
+++ b/src/cscs/csscript.cs
@@ -1896,7 +1896,7 @@ string Compile(string scriptFileName)
CompilerResults results;
if (generateExe)
{
- var exeCompatibleIjections = filesToInject.Where(x => !x.EndsWith(".attr.g.cs")).ToArray();
+ var exeCompatibleIjections = filesToInject.Where(x => !x.EndsWith(Globals.InjectedAttributesPrefix)).ToArray();
filesToCompile = filesToCompile.ConcatWith(exeCompatibleIjections);
results = CompileAssembly(compiler, compilerParams, filesToCompile);
}