CS-Script 3.30.3


Precompilers


The idea of performing certain actions prior to the script execution is an frequent scenario. The example of such scenario is altering the script code just before passing it to the C# compiler (for compilation and further execution). Such a scenario is called Precompilation and CS-Script has a build-in functionality for to support it. This CS-Script functionality is called Precompilers and this section will provides an overview of it as well as some code samples.


A typical precompiler is a C# script (or a compiled assembly) which is capable of analyzing the C# code and altering it if required. Such script/assembly must have very specific interface in order to interact with the CS-Script engine.

Precompilation can also be implemented with pre-execution scripts. While this indeed possible such an implementation will not be very elegant nor maintainable. But nevertheless precompilers conceptually are nothing else but specialized pre-execution scripts. That is it, while everything that can be done with precompilers can also be done with pre-execution scripts use of precompilers offers much simple syntax what leads to the more "comfortable" development/maintenance.

The best way to explain precompilers is to have a close look at one. The following is an example of a very light precompiler.

public class Precompiler
{
    public static bool Compile(ref string code, string scriptFile, bool isPrimaryScript, Hashtable context)
    {
     if (code.StartsWith("#!"))
        {
         code = "//" + code; //comment the Linux hashbang line to keep C# compiler happy

            return true; //true as the code has been modified
        }

        return false; //false as the code has not been modified
    }
}

This particular precompiler comments the very top hashbang (shebang/"magic line") line of the script. This allows using hashbang string in the C# scripts on Linux without upsetting the C# compiler any yet without interfering with the shell handling the hashbang string in a traditional Linux way.
 
There are a few thing that make the code above a precompiler:

Now let's see what this particular precompiler does at runtime.

Just before executing the script the script engine loads the precompiler and calls its Campile method for the script being executed (primary script) as well as for the scripts being included/imported by the primary script. The script engine passes the script content (parameter code) of the script to be precompiled and it is up to the precompiler what to do with the script content.

Note that because you can specify multiple precompilers the code parameter may contain the code already modified by one of the precompilers.
 
The rest of the parameters of the Compile() method is an additional information about the execution context:

The example above is the precompiler which is embedded in the script engine and by default applied for all scripts being executed.

There are a few ways of configuring/engaging precompilers for a particular script:

Command-line argument:
cscs /precompiler[:<file 1>,<file N>] script.cs
file - precompiler (script or assembly)

Specifies custom precompiler file(s). If no file(s) specified prints the code template for the custom precompiler.
There is a special reserved word 'nodefault' to be used as a file name. It instructs script engine to prevent loading any built-in precompilers.


CS-Script directives in the script:
//css_precompiler <file 1>,<file 2>;
file - precompiler (script or assembly)

It is a logical equivalent of the command line argument /precompiler.
You can also use the directive alias //css_pc.

Tutorial 

This is the tutorial we will create the prcecompiler, which would allow execution of a free-style C# code containing no class nor method definition.

The tutorial is based on the <cs-script>\Samples\Precompilers\Script.cs sample.

The subject of this tutorial is related to the "classless scripts" and "autoclass" features.

  1. Create free-style script script.cs containing the loop iterating through script arguments and statement for displaying a message:

    //css_precompiler freestyle
    using System.Windows.Forms;

    for (int i = 0; i < args.Length; i++)
    Console.WriteLine(args[i]);

    MessageBox.Show("Hello World!");

    Note that the script is configured to be precompiled with the precompiler script freestyle.cs.

  2. Execute from command prompt the following command which creates the precompiler template with the name freestyle.cs:

    cscs /precompiler > freestyle.cs
    This will create the precompiler skeleton:
     
    using System;
    using System.Collections;

    public class Sample_Precompiler //precompiler class name must end with 'Precompiler'
    {
        public static bool Compile(ref string scriptCode, string scriptFile, bool isPrimaryScript, Hashtable context)
        {
            //if scriptCode needs to be altered assign scriptCode the new value and return true. Otherwise return false

            //scriptCode = "code after precompilation";
            //return true;

            return false;
        }
    }

  3. Modify the precompiler as follows

    using System;
    using System.Collections;
    using System.Text;
    using System.IO;

    public class FreestyleScriptPrecompiler
    {
        static public bool Compile(ref string content, string scriptFile, bool isPrimaryScript, Hashtable context)
        {
            if (!isPrimaryScript)
                return false;

            var code = new StringBuilder(4096);
            code.AppendLine("//Auto-generated file");
            code.AppendLine("using System;");
            code.AppendLine("using System.IO;");

            bool headerProcessed = false;

            string line;

            using (var sr = new StringReader(content))
                while ((line = sr.ReadLine()) != null)
                {
                    if (!headerProcessed && !line.TrimStart().StartsWith("using ")) //not using...; statement of the file header
                        if (!line.StartsWith("//") && line.Trim() != ""//not comments or empty line
                        {
                            headerProcessed = true;

                            code.AppendLine("   public class ScriptClass");
                            code.AppendLine("   {");
                            code.AppendLine("       static public int Main(string[] args)");
                            code.AppendLine("       {");
                        }

                    code.Append(line);
                    code.Append(Environment.NewLine);
                }

            code.AppendLine("       return 0;");
            code.AppendLine("   }");
            code.AppendLine("}");

            content = code.ToString();
            return true;
        }
    }

    The implementation of the precompiler is fairly simple as it simply wraps the content of the script file being executed into the class definition with some extra "using" statements.

  4. Execute the script script.cs as any normal script:
    cscs script.cs
    During the execution the precompiler will modify the content of the script file by injecting the missing declaration parts just before passing the content to the C# compiler. The injected code is indicated with the gray color in the following listing:

    //Auto-generated file
    using System;
    using System.IO;
    //css_precompiler freestyle
    using System.Windows.Forms;

    public class ScriptClass
    {
        static public int Main(string[] args)
        {
            for (int i = 0; i < args.Length; i++)
            {
                Console.WriteLine(args[i]);
            }

            MessageBox.Show("Hello World!");

            return 0;
        }
    }


Conclusion 


Precompilers is a powerful feature , which opens almost unlimited scripting opportunities. Replacing the magic string is just a very simple example but precompiling can do so much more. For example with precompiling you can easy implement C++ style #define for C# (even #define is a controversial feature). It is even possible to implement Domain Specific Language of a sort.


You can find more Precompilers samples in the <cs-script>\Samples\Precompilers directory. These samples doe not have very high practical value but they do show the idea.


See Also

Command-line interfaceClassless Scripts