Click here to Skip to main content
15,896,201 members
Articles / Web Development / ASP.NET

Creating DAL Components Using Custom ASP.NET Build Providers And Compiler Techniques

Rate me:
Please Sign up or sign in to vote.
4.86/5 (28 votes)
24 Oct 2006CPOL9 min read 107.3K   886   114  
This article describes how to create Data Access Layer Components (DALC) using ASP.NET build providers and a self-defined description language in C#, including an easy scanner, parser, and CodeDOM generator.
//
// Parago Media GmbH & Co. KG, J�rgen B�urle (jbaurle@parago.de)
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 
// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
// DEALINGS IN THE SOFTWARE.
//

using System;
using System.Collections.Generic;

namespace Parago.DALComp.Compiler {

	/// <summary>
	/// The Parser class represents the second phase of the small compiler application
	/// and analyzes the tokens created by the Tokenizer class.
	/// </summary>
	public class Parser {

		Tokenizer tokenizer;
		Token token;
		DALC dalc;
		Function function;
		public AST DAL;

		/// <summary>
		/// Initializes a new instance of the Parser class when given a Tokenizer 
		/// instance and starts parsing.
		/// </summary>
		public Parser(Tokenizer tokenizer) {
			this.tokenizer=tokenizer;
			token=new Token(TokenKind.EOT);
			DAL=new AST();
			ParseDAL();
		}

		/// <summary>
		/// dal = config dalc { dalc }
		/// </summary>
		void ParseDAL() {
			ParseConfig();
			do {
				ParseDALC();
			} while(Taste(TokenKind.KeywordDALC));
			Eat(TokenKind.EOT);
		}

		/// <summary>
		/// config = "Config" "{" config-setting { "," config-setting } "}"
		/// </summary>
		void ParseConfig() {
			Eat(TokenKind.KeywordConfig);
			Eat(TokenKind.CurlyBracketOpen);
			ParseConfigSetting();
			while(true) {
				if(!Taste(TokenKind.Comma))
					break;
				Eat();
				ParseConfigSetting();
			}
			Eat(TokenKind.CurlyBracketClose);
			if(DAL.Settings.Count!=3)
				Abort("The 'Config' section must contain values for 'Namespace', 'DatabaseType' and 'ConnectionString'");
			foreach(KeyValuePair<string, string> item in DAL.Settings)
				if(item.Value==String.Empty)
					Abort(String.Format("Value for {0} cannot be empty", item.Key));
				else if(item.Key=="DATABASETYPE"&&item.Value.ToUpper()!="MSSQL")
					Abort("The allowed value for 'DATABASETYPE' is currently restricted to 'MSSQL'");
		}

		/// <summary>
		/// config-setting = ( "Namespace" | "DatabaseType" | "Connectionstring" ) "=" string
		/// </summary>
		void ParseConfigSetting() {
			string value=Eat(TokenKind.Identifier).ToUpper();
			if(value!="NAMESPACE"&&value!="DATABASETYPE"&&value!="CONNECTIONSTRING")
				Abort("Values are currently only allowed for 'Namespace', 'DatabaseType' and 'ConnectionString'");
			if(DAL.Settings.ContainsKey(value))
				Abort(String.Format("Value for '{0}' is already defined", value));
			Eat(TokenKind.Equal);
			DAL.Settings.Add(value, Eat(TokenKind.String));
		}

		/// <summary>
		/// dalc = "DALC" dalc-name [ dalc-table ] "{" [ dalc-mapping ] dalc-function { dalc-function } "}"
		/// </summary>
		void ParseDALC() {
			dalc=new DALC();
			Eat(TokenKind.KeywordDALC);
			dalc.Name=Eat(TokenKind.Identifier);
			foreach(DALC item in DAL.DALCs)
				if(item.Name==dalc.Name)
					Abort(String.Format("DALC '{0}' is already defined", dalc.Name));
			if(Taste(TokenKind.ParenthesisOpen))
				ParseDALCTable();
			else
				dalc.Table=dalc.Name;
			Eat(TokenKind.CurlyBracketOpen);
			if(Taste(TokenKind.KeywordMapping))
				ParseDALCMapping();
			ParseDALCFunction();
			while(true) {
				if(!Taste(TokenKind.Identifier))
					break;
				ParseDALCFunction();
			}
			Eat(TokenKind.CurlyBracketClose);
			DAL.DALCs.Add(dalc);
		}

		/// <summary>
		/// dalc-table = "(" "=" ( identifier | string ) ")"
		/// </summary>
		void ParseDALCTable() {
			Eat(TokenKind.ParenthesisOpen);
			Eat(TokenKind.Equal);
			dalc.Table=EatAny(new TokenKind[] { TokenKind.Identifier, TokenKind.String });
			Eat(TokenKind.ParenthesisClose);
		}

		/// <summary>
		/// dalc-mapping = "Mapping" "{" dalc-mapping-field { "," dalc-mapping-field } "}"
		/// </summary>
		void ParseDALCMapping() {
			Eat(TokenKind.KeywordMapping);
			Eat(TokenKind.CurlyBracketOpen);
			ParseDALCMappingField();
			while(true) {
				if(!Taste(TokenKind.Comma))
					break;
				Eat();
				ParseDALCMappingField();
			}
			Eat(TokenKind.CurlyBracketClose);
		}

		/// <summary>
		/// dalc-mapping-field = ( identifier | string ) "=>" identifier
		/// </summary>
		void ParseDALCMappingField() {
			string fieldToMap=EatAny(new TokenKind[] { TokenKind.Identifier, TokenKind.String });
			if(String.IsNullOrEmpty(fieldToMap))
				Abort("Field to map cannot be empty");
			Eat(TokenKind.Assign);
			string field=Eat(TokenKind.Identifier);
			dalc.Mapping.Add(fieldToMap.ToUpper(), field);
		}

		/// <summary>
		/// dalc-function = identifier "(" [ dalc-function-parameter-list ] ")"
		/// </summary>
		void ParseDALCFunction() {
			function=new Function(Eat(TokenKind.Identifier));
			Eat(TokenKind.ParenthesisOpen);
			if(!Taste(TokenKind.ParenthesisClose))
				ParseDALCFunctionParameterList();
			Eat(TokenKind.ParenthesisClose);
			dalc.Functions.Add(function);
		}

		/// <summary>
		/// dalc-function-parameter-list = dalc-function-parameter { "," dalc-function-parameter }
		/// </summary>
		void ParseDALCFunctionParameterList() {
			ParseDALCFunctionParameter();
			while(true) {
				if(!Taste(TokenKind.Comma))
					break;
				Eat();
				ParseDALCFunctionParameter();
			}
		}

		/// <summary>
		/// dalc-function-parameter = ( "string" | "int" ) identifier "[" identifier | string "]"
		/// </summary>
		void ParseDALCFunctionParameter() {
			string type=Eat(TokenKind.Type);
			string name=Eat(TokenKind.Identifier);
			foreach(Parameter item in function.Parameters)
				if(item.Name==name)
					Abort(String.Format("Parameter name '{0}' is already defined", item.Name));
			Eat(TokenKind.BracketOpen);
			string field=EatAny(new TokenKind[] { TokenKind.Identifier, TokenKind.String });
			foreach(Parameter item in function.Parameters)
				if(item.Field==field)
					Abort(String.Format("Parameter field '{0}' is already defined", item.Field));
			Eat(TokenKind.BracketClose);
			function.Parameters.Add(new Parameter(type, name, field));
		}

		/// <summary>
		/// Looks ahead and returns the next token type.
		/// </summary>
		bool Taste(TokenKind type) {
			return tokenizer.PeekTokenType()==type;
		}

		/// <summary>
		/// Returns the next token.
		/// </summary>
		string Eat() {
			token=tokenizer.GetNextToken();
			return token.Value;
		}

		/// <summary>
		/// Returns the next token of type, otherwise aborts.
		/// </summary>
		string Eat(TokenKind type) {
			token=tokenizer.GetNextToken();
			if(token.Type!=type)
				Abort();
			return token.Value;
		}

		/// <summary>
		/// Returns the next token of any of the passed array of types, otherwise aborts.
		/// </summary>
		string EatAny(TokenKind[] types) {
			token=tokenizer.GetNextToken();
			foreach(TokenKind type in types)
				if(token.Type==type)
					return token.Value;
			Abort();
			return String.Empty;
		}

		/// <summary>
		/// Aborts the parsing.
		/// </summary>
		void Abort() {
			throw new Exception("DALComp syntax error");
		}

		/// <summary>
		/// Aborts the parsing with specific message.
		/// </summary>
		void Abort(string message) {
			throw new Exception(String.Format("DALComp syntax error: {0}", message));
		}

	}

	/// <summary>
	/// Represents the Abstract Syntax Tree (AST).
	/// </summary>
	public class AST {

		public Dictionary<string, string> Settings;
		public List<DALC> DALCs;

		public AST() {
			Settings=new Dictionary<string, string>();
			DALCs=new List<DALC>();
		}

	}

	/// <summary>
	/// Represents the DALC defintion with functions and field mappings.
	/// </summary>
	public class DALC {

		public string Name;
		public string Table;
		public Dictionary<string, string> Mapping;
		public List<Function> Functions;

		public DALC() {
			Mapping=new Dictionary<string, string>();
			Functions=new List<Function>();
		}

	}

	/// <summary>
	/// Represents a DALC function definition including the parameter list.
	/// </summary>
	public class Function {

		public string Name;
		public List<Parameter> Parameters;

		public Function(string name) {
			Name=name;
			Parameters=new List<Parameter>();
		}

	}

	/// <summary>
	/// Represents a function parameter definition including the parameter list.
	/// </summary>
	public class Parameter {

		public string Type;
		public string Name;
		public string Field;

		public Parameter(string type, string name, string field) {
			Type=type;
			Name=name;
			Field=field;
		}

	}

}

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
Software Developer (Senior)
Germany Germany
I’m a software developer based in Germany.

Homepage

Comments and Discussions