Click here to Skip to main content
15,881,248 members
Articles / Programming Languages / Visual Basic

Template Based Code Generator

Rate me:
Please Sign up or sign in to vote.
4.66/5 (32 votes)
20 Nov 2007CPOL13 min read 93.1K   4.1K   116  
A template based, command-line oriented .NET code generator
using System;
using System.Collections.Generic;
using System.Text;
using Arebis.Parsing.MultiContent;
using System.IO;
using Arebis.Parsing;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Text.RegularExpressions;
using System.Reflection;
using Arebis.Reflection;
using System.Collections.Specialized;
using Arebis.Utils;
using Arebis.CodeGeneration;

namespace Arebis.CodeGenerator.Templated
{
	[System.Diagnostics.DebuggerStepThrough]
	public abstract class BaseCodeBuilder : ICodeBuilder
	{
		private static Regex RxLinesWithLinebreaks = new Regex("[^\\r\\n]*\\r?\\n?");

		private ITemplateInfo templateInfo;
		private string code;
		private Type compiledType;
		private List<CompilerError> compilerErrors;

		private List<string> filesToDelete = new List<string>();

		#region ICodeBuilder Members

		public ITemplateInfo TemplateInfo
		{
			get { return this.templateInfo; }
			set { this.templateInfo = value; }
		}

		public Type CompiledType
		{
			get { return this.compiledType; }
		}

		public IList<CompilerError> CompilerErrors
		{
			get { return this.compilerErrors; }
		}

		public string Code
		{
			get { return this.code; }
		}

		public bool Compile()
		{
			// Check precondition: templateInfo must be known:
			if (this.templateInfo == null)
				throw new InvalidOperationException("TemplateInfo property must be set before calling Compile method.");

			// Template substitution fields:
			string ns;
			string classname;
			string baseclassname;
			StringBuilder imports = new StringBuilder();
			StringBuilder fields = new StringBuilder();
			StringBuilder constructorparams = new StringBuilder();
			StringBuilder fieldinits = new StringBuilder();
			StringBuilder generatebody = new StringBuilder();
			StringBuilder scripts = new StringBuilder();
			string codeBehindFile = null;

			// Retrieve codeTemplate directive:
			NameValueCollection ctdir = templateInfo.GetCodeTemplateDirective();
			// Retrieve class name information:
			ClassName cnb = new ClassName(ctdir["ClassName"] ?? StringUtils.ToIdentifier(templateInfo.TemplateFileInfo.Name));
			if (cnb.NameSpace == String.Empty)
				cnb.NameSpace = GenerationLanguage.DefaultNameSpace;

			// Retrieve base information:
			ns = cnb.NameSpace;
			classname = cnb.Name;
			baseclassname = ctdir["Inherits"] ?? GenerationLanguage.DefaultBaseClass;
			codeBehindFile = templateInfo.FindFile(ctdir["CodeFile"]);

			foreach (NameValueCollection import in templateInfo.GetDirectives("Import"))
			{
				this.AppendImport(imports, import["Namespace"], import["Alias"]);
			}

			// Fill fields, fieldinits, constructorparams:
			foreach (NameValueCollection param in templateInfo.GetDirectives("Parameter"))
			{
				string n = param["Name"];
				string t = param["Type"] ?? "System.Object";

				fields.Append("\t\t");
				this.AppendField(fields, n, t);
				fieldinits.Append("\t\t\t");
				this.AppendFieldInitializer(fieldinits, n, t);
				this.AppendConstructorParam(constructorparams, n, t);
			}

			// Fill generatebody:
			bool writeLinePragma = Convert.ToBoolean(ctdir["LinePragmas"] ?? "True");
			foreach (ContentPart part in templateInfo.FileContent.Parts)
			{
				// Check for TemplateBody or Scriptlet:
				if (((TemplatePartTypes)part.Type != TemplatePartTypes.TemplateBody) && ((TemplatePartTypes)part.Type != TemplatePartTypes.Scriptlet))
					continue;

				// Add line pragma begin:
				if (writeLinePragma)
					this.AppendLinePragmaBegin(generatebody, part.File.Filename, part.StartLine);

				// Generate:
				if ((TemplatePartTypes)part.Type == TemplatePartTypes.TemplateBody)
				{
					foreach (string line in LinesWithBreaks(part.Content))
					{
						string content = ToContent(this.LineToWriteCall(line));
						//generatebody.Append("\t\t\t");
						generatebody.Append(content);
						generatebody.AppendLine();
					}
				}
				else if ((TemplatePartTypes)part.Type == TemplatePartTypes.Scriptlet)
				{
					foreach (string line in LinesWithBreaks(part.Content))
					{
						//generatebody.Append("\t\t\t");
						generatebody.Append(line);
					}
					generatebody.AppendLine();
				}

				// Add line pragma end:
				if (writeLinePragma)
					this.AppendLinePragmaEnd(generatebody);
			}

			// Fill scripts:
			foreach (ContentPart part in templateInfo.FileContent.FindPartsOfType(TemplatePartTypes.Script))
			{
				// Add line pragma begin:
				if (writeLinePragma)
					this.AppendLinePragmaBegin(scripts, part.File.Filename, part.StartLine);

				// Append script:
				scripts.AppendLine(part.Content);
				scripts.AppendLine();

				// Add line pragma end:
				if (writeLinePragma)
					this.AppendLinePragmaEnd(scripts);
			}

			// Build code:
			this.code = GetCodeTemplate();
			this.code = this.code.Replace("<%=templatefilename%>", templateInfo.TemplateFileInfo.FullName);
			this.code = this.code.Replace("<%=imports%>", imports.ToString());
			this.code = this.code.Replace("<%=namespace%>", ns);
			this.code = this.code.Replace("<%=classname%>", classname);
			this.code = this.code.Replace("<%=baseclassname%>", baseclassname);
			this.code = this.code.Replace("<%=fields%>", fields.ToString());
			this.code = this.code.Replace("<%=constructorparameters%>", constructorparams.ToString());
			this.code = this.code.Replace("<%=fieldinitialisations%>", fieldinits.ToString());
			this.code = this.code.Replace("<%=generatebody%>", generatebody.ToString());
			this.code = this.code.Replace("<%=scripts%>", scripts.ToString());

			// Save code file:
			string codeFile = templateInfo.TemplateFileInfo.FullName + ".gen";
			File.WriteAllText(codeFile, this.code);

			// Build assembly resolution path:
			List<string> referencepathlist = ((GenerationHost)templateInfo.Host).ReferencePath;
			referencepathlist.Add(templateInfo.TemplateFileInfo.Directory.FullName);
			referencepathlist.Add(templateInfo.TemplateFileInfo.Directory.FullName + "\\bin");
			string[] referencepath = referencepathlist.ToArray();

			// Collect references:
			List<string> references = new List<string>();
			references.Add(FileUtils.FindInPath(new FileInfo(typeof(Arebis.CodeGeneration.CodeTemplate).Assembly.Location).Name, referencepath));
			references.Add("System.dll");

			// Find references from settings:
			foreach (string path in templateInfo.Settings.GetValues("referenceassembly") ?? new string[0])
			{
				// Search for references:
				string fullpath = FileUtils.FindInPath(path, referencepath) ?? path;
				if (fullpath == null) continue;

				// Add reference to references:
				if (references.Contains(fullpath) == false) references.Add(fullpath);
			}

			// Find references from directives:
			foreach (NameValueCollection refer in templateInfo.GetDirectives("ReferenceAssembly"))
			{
				// Search for references:
				string path = refer["Path"];
				string fullpath = FileUtils.FindInPath(path, referencepath) ?? path;
				if (fullpath == null) continue;

				// Add reference to references:
				if (references.Contains(fullpath) == false) references.Add(fullpath);
			}

			// Log references:
			foreach (string item in references)
			{
				templateInfo.Host.Log("Added reference \"{0}\".", item);
			}

			// Generate assembly filename:
			string assemblyFile = 
				ctdir["AssemblyFile"] ??
				Path.Combine(Path.GetTempPath(), StringUtils.ToIdentifier(templateInfo.TemplateFileInfo.FullName) + ".dll");

			// Collect files to compile:
			List<string> codefiles = new List<string>();
			codefiles.Add(codeFile);
			if (codeBehindFile != null) codefiles.Add(codeBehindFile);
			foreach (NameValueCollection compileFileAttrs in templateInfo.GetDirectives("CompileFile"))
				codefiles.Add(templateInfo.FindFile(compileFileAttrs["Path"]));

			// Compile the template code:
			CodeDomProvider provider = this.GetCodeDomProvider();
			CompilerParameters pars = new CompilerParameters(references.ToArray(), assemblyFile, true);
			CompilerResults results;
			pars.GenerateExecutable = false;
			pars.GenerateInMemory = false;
			results = provider.CompileAssemblyFromFile(pars, codefiles.ToArray());

			// Delete the generated sourcefile:
			this.filesToDelete.Add(assemblyFile);
			this.filesToDelete.Add(assemblyFile.Replace(".dll", ".pdb"));
			if (Convert.ToBoolean(templateInfo.Settings["deletegeneratedsource"] ?? "True") == true)
				this.filesToDelete.Add(codeFile);

			// Check for compile errors & warnings:
			this.compilerErrors = new List<CompilerError>();
			foreach (CompilerError error in results.Errors)
			{
				compilerErrors.Add(error);
			}

			// Store compiled type & return success:
			if (results.Errors.HasErrors)
			{
				this.compiledType = null;
				return false;
			}
			else
			{
				this.compiledType = results.CompiledAssembly.GetType(ns + "." + classname);
				return true;
			}
		}

		#endregion

		public void Dispose()
		{
			if (this.filesToDelete != null)
			{
				foreach (string filename in this.filesToDelete)
				{
					try
					{
						if (File.Exists(filename)) File.Delete(filename);
					}
					catch (IOException) { 
					}
				}
				this.filesToDelete = null;
			}
		}

		protected abstract string GetCodeTemplate();

		protected abstract void AppendImport(StringBuilder imports, string nameSpace, string alias);

		protected abstract void AppendField(StringBuilder fields, string name, string type);

		protected abstract void AppendFieldInitializer(StringBuilder fieldinits, string name, string type);

		protected abstract void AppendConstructorParam(StringBuilder constructorparams, string name, string type);

		protected abstract void AppendLinePragmaBegin(StringBuilder code, string filename, int line);

		protected abstract void AppendLinePragmaEnd(StringBuilder code);

		/// <summary>
		/// Translates the string into a method call to render the line.
		/// </summary>
		protected abstract string LineToWriteCall(string line);

		protected abstract CodeDomProvider GetCodeDomProvider();

		private static IEnumerable<string> LinesWithBreaks(string text)
		{
			List<string> lines = new List<string>();
			foreach (Match match in RxLinesWithLinebreaks.Matches(text))
			{
				string line = match.Value;
				if (line == String.Empty) break;
				lines.Add(line);
			}
			return lines;
		}

		private static string ToContent(string text)
		{
			text = text.Replace("\\\r", "\\r");
			text = text.Replace("\\\n", "\\n");
			return text;
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect AREBIS
Belgium Belgium
Senior Software Architect and independent consultant.

Comments and Discussions