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 p
recompilers
    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:
    
      - scriptFile - name of the script file which is being precompiled
- isPrimaryScript - flag indicating if the script being precompiled is
        a primary script or an included/imported script.
- context - a free-style dictionary containing information about various aspects of the runtime and compilation
        context. 
 This parameter can be useful only in very rare cases for
          "advanced precompiling".
 
        - context["NewDependencies"] - List<string> 
 Collection of file names that should be checked for the modification
          every time the primary script is executed and the decision needs to be
          made whether to load the script from the cache or compile it from the
          sources if any source file (dependency) has been modified since the
          last execution.
 
 
- context["NewSearchDirs"] - List<string>  
 Collection of extra directories that should be used for assembly and
          script probing during actual script compilation after precompilation is
          done.
 
 
- context["NewReferences"] - List<string> 
 Collection of extra referenced assemblies
          that should be used for assembly and script probing during actual
          script compilation after precompilation is done.
 
 
- context["NewCompilerOptions"] - string 
 Additional compiler options to be passed to the script compiler.
 
 
- context["SearchDirs"] - string[]
 Read-only. Collection of directories that are used for assembly and
          script probing during actual script compilation after precompilation is
          done. This is a fixed collection, which is defined by the global CS-Script configuration.
 
 
- context["CompilerOptions"] - string
 Read-only. String containing compiler options that are passed to the C# compiler during the compilation of
          the primary
          script.
 
 
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.
      
    
    
      - Create free-style script script.cs containing the loop iterating
        through script arguments and statement for displaying a message:
 
 
          
            
              | //css_precompiler freestyleusing 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.
 
 
- 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;
 }
 }
 
 |  
 
 
- 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.
 
 
- 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 fileusing 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
        interface | Classless Scripts