Scripting
Wednesday, 21st November 2007
Scripting with .NET is unbelievably easy.
I wanted to add scripting support to Brass, and have added it using .NET's excellent powers of reflection and its System.CodeDom.Compiler namespace.
The first thing I need to do is find out which language the source script is written in. I use the extension to check for this.
string ScriptFile = ...; // Name of main script file to compile. CodeDomProvider Provider = null; // Get the extension (eg "cs") string Extension = Path.GetExtension(ScriptFile).ToLowerInvariant(); if (Extension.Length > 0 && Extension[0] == '.') Extension = Extension.Substring(1); // Hunt through all available compilers and dig out one with a matching extension. foreach (CompilerInfo Info in CodeDomProvider.GetAllCompilerInfo()) { if (Info.IsCodeDomProviderTypeValid) { CodeDomProvider TestProvider = Info.CreateProvider(); if (TestProvider.FileExtension.ToLowerInvariant() == Extension) { Provider = TestProvider; break; } } } if (Provider == null) throw new CompilerExpection(source, "Script language not supported.");
Now that we have a compiler, we just set some settings, add some references, then compile the source files:
string[] ScriptFiles = ...; // Array of source file name(s) to compile. // Compiler settings: CompilerParameters Parameters = new CompilerParameters(); Parameters.GenerateExecutable = false; // Class lib, not .exe Parameters.GenerateInMemory = true; // Add references: Parameters.ReferencedAssemblies.Add("System.dll"); Parameters.ReferencedAssemblies.Add("Brass.exe"); // Compile! CompilerResults Results = Provider.CompileAssemblyFromFile(Parameters, ScriptFiles);
And that's it! In my case I now pass any errors back up to the assembler, and exit if there were any errors:
// Errors? foreach (CompilerError Error in Results.Errors) { Compiler.NotificationEventArgs Notification = new Compiler.NotificationEventArgs(compiler, Error.ErrorText, Error.FileName, Error.Line); if (Error.IsWarning) { compiler.OnWarningRaised(Notification); } else { compiler.OnErrorRaised(Notification); } } // Do nothing if there were errors. if (Results.Errors.HasErrors) return;
Now the task is passed on to reflection; I go through the compiled assembly, hunt down methods and wrap them up for use as native Brass functions and/or directives.
// Grab the public classes from the script. foreach (Type T in Results.CompiledAssembly.GetExportedTypes()) { // ... }
I've used this technique in the release of my PAL demo; a C# script file is used to encode an image to the 18×304 resolution and format required by the routine.
