Click here to Skip to main content
15,896,606 members
Articles / Programming Languages / MSIL

RunSharp - Reflection.Emit Has Never Been Easier

Rate me:
Please Sign up or sign in to vote.
4.92/5 (48 votes)
10 Aug 2009MIT5 min read 118.5K   509   118  
RunSharp (or Run#) is a high-level wrapper around the Reflection.Emit API, allowing you to generate code at runtime quickly and easily.
/*
 * Copyright (c) 2009, Stefan Simek
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * 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;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;

namespace TriAxis.RunSharp
{
	using Operands;

	partial class CodeGen
	{
		#region Assignment
		public void Assign(Operand target, Operand value)
		{
			Assign(target, value, false);
		}

		public void Assign(Operand target, Operand value, bool allowExplicitConversion)
		{
			if ((object)target == null)
				throw new ArgumentNullException("target");

			BeforeStatement();

			target.Assign(value, allowExplicitConversion).Emit(this);
		}

		public void AssignAdd(Operand target, Operand value)
		{
			if ((object)target == null)
				throw new ArgumentNullException("target");

			BeforeStatement();

			target.AssignAdd(value).Emit(this);
		}

		public void AssignSubtract(Operand target, Operand value)
		{
			if ((object)target == null)
				throw new ArgumentNullException("target");

			BeforeStatement();

			target.AssignSubtract(value).Emit(this);
		}

		public void AssignMultiply(Operand target, Operand value)
		{
			if ((object)target == null)
				throw new ArgumentNullException("target");

			BeforeStatement();

			target.AssignMultiply(value).Emit(this);
		}

		public void AssignDivide(Operand target, Operand value)
		{
			if ((object)target == null)
				throw new ArgumentNullException("target");

			BeforeStatement();

			target.AssignDivide(value).Emit(this);
		}

		public void AssignModulus(Operand target, Operand value)
		{
			if ((object)target == null)
				throw new ArgumentNullException("target");

			BeforeStatement();

			target.AssignModulus(value).Emit(this);
		}

		public void AssignAnd(Operand target, Operand value)
		{
			if ((object)target == null)
				throw new ArgumentNullException("target");

			BeforeStatement();

			target.AssignAnd(value).Emit(this);
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", Justification = "Checked, OK")]
		public void AssignOr(Operand target, Operand value)
		{
			if ((object)target == null)
				throw new ArgumentNullException("target");

			BeforeStatement();

			target.AssignOr(value).Emit(this);
		}

		public void AssignXor(Operand target, Operand value)
		{
			if ((object)target == null)
				throw new ArgumentNullException("target");

			BeforeStatement();

			target.AssignXor(value).Emit(this);
		}

		public void AssignLeftShift(Operand target, Operand value)
		{
			if ((object)target == null)
				throw new ArgumentNullException("target");

			BeforeStatement();

			target.AssignLeftShift(value).Emit(this);
		}

		public void AssignRightShift(Operand target, Operand value)
		{
			if ((object)target == null)
				throw new ArgumentNullException("target");

			BeforeStatement();

			target.AssignRightShift(value).Emit(this);
		}

		public void Increment(Operand target)
		{
			if ((object)target == null)
				throw new ArgumentNullException("target");

			BeforeStatement();

			target.Increment().Emit(this);
		}

		public void Decrement(Operand target)
		{
			if ((object)target == null)
				throw new ArgumentNullException("target");

			BeforeStatement();

			target.Decrement().Emit(this);
		}
		#endregion

		#region Constructor chaining
		public void InvokeThis(params Operand[] args)
		{
			if (cg == null)
				throw new InvalidOperationException(Properties.Messages.ErrConstructorOnlyCall);
			if (chainCalled)
				throw new InvalidOperationException(Properties.Messages.ErrConstructorAlreadyChained);

			ApplicableFunction other = TypeInfo.FindConstructor(cg.Type, args);

			il.Emit(OpCodes.Ldarg_0);
			other.EmitArgs(this, args);
			il.Emit(OpCodes.Call, (ConstructorInfo)other.Method.Member);
			chainCalled = true;
		}

		public void InvokeBase(params Operand[] args)
		{
			if (cg == null)
				throw new InvalidOperationException(Properties.Messages.ErrConstructorOnlyCall);
			if (chainCalled)
				throw new InvalidOperationException(Properties.Messages.ErrConstructorAlreadyChained);
			if (cg.Type.TypeBuilder.IsValueType)
				throw new InvalidOperationException(Properties.Messages.ErrStructNoBaseCtor);

			ApplicableFunction other = TypeInfo.FindConstructor(cg.Type.BaseType, args);

			if (other == null)
				throw new InvalidOperationException(Properties.Messages.ErrMissingConstructor);

			il.Emit(OpCodes.Ldarg_0);
			other.EmitArgs(this, args);
			il.Emit(OpCodes.Call, (ConstructorInfo)other.Method.Member);
			chainCalled = true;

			// when the chain continues to base, we also need to call the common constructor
			il.Emit(OpCodes.Ldarg_0);
			il.Emit(OpCodes.Call, cg.Type.CommonConstructor().MethodBuilder);
		}
		#endregion

		void BeforeStatement()
		{
			if (!reachable)
				throw new InvalidOperationException(Properties.Messages.ErrCodeNotReachable);

			if (cg != null && !chainCalled && !cg.Type.TypeBuilder.IsValueType)
				InvokeBase();
		}

		void DoInvoke(Operand invocation)
		{
			BeforeStatement();

			invocation.EmitGet(this);
			if (invocation.Type != typeof(void))
				il.Emit(OpCodes.Pop);
		}

		#region Invocation
		public void Invoke(Type target, string method)
		{
			Invoke(target, method, Operand.EmptyArray);
		}

		public void Invoke(Type target, string method, params Operand[] args)
		{
			DoInvoke(Static.Invoke(target, method, args));
		}

		public void Invoke(Operand target, string method)
		{
			Invoke(target, method, Operand.EmptyArray);
		}

		public void Invoke(Operand target, string method, params Operand[] args)
		{
			if ((object)target == null)
				throw new ArgumentNullException("target");

			DoInvoke(target.Invoke(method, args));
		}

		public void InvokeDelegate(Operand targetDelegate)
		{
			InvokeDelegate(targetDelegate, Operand.EmptyArray);
		}

		public void InvokeDelegate(Operand targetDelegate, params Operand[] args)
		{
			if ((object)targetDelegate == null)
				throw new ArgumentNullException("targetDelegate");

			DoInvoke(targetDelegate.InvokeDelegate(args));
		}

		public void WriteLine(params Operand[] args)
		{
			Invoke(typeof(Console), "WriteLine", args);
		}
		#endregion

		#region Event subscription
		public void SubscribeEvent(Operand target, string eventName, Operand handler)
		{
			if ((object)target == null)
				throw new ArgumentNullException("target");
			if ((object)handler == null)
				throw new ArgumentNullException("handler");

			IMemberInfo evt = TypeInfo.FindEvent(target.Type, eventName, target.IsStaticTarget);
			MethodInfo mi = ((EventInfo)evt.Member).GetAddMethod();
			if (!target.IsStaticTarget)
				target.EmitGet(this);
			handler.EmitGet(this);
			EmitCallHelper(mi, target);
		}

		public void UnsubscribeEvent(Operand target, string eventName, Operand handler)
		{
			if ((object)target == null)
				throw new ArgumentNullException("target");
			if ((object)handler == null)
				throw new ArgumentNullException("handler");

			IMemberInfo evt = TypeInfo.FindEvent(target.Type, eventName, target.IsStaticTarget);
			MethodInfo mi = ((EventInfo)evt.Member).GetRemoveMethod();
			if (!target.IsStaticTarget)
				target.EmitGet(this);
			handler.EmitGet(this);
			EmitCallHelper(mi, target);
		}
		#endregion

		public void InitObj(Operand target)
		{
			if ((object)target == null)
				throw new ArgumentNullException("target");

			BeforeStatement();

			target.EmitAddressOf(this);
			il.Emit(OpCodes.Initobj, target.Type);
		}

		#region Flow Control
		interface IBreakable
		{
			Label GetBreakTarget();
		}

		interface IContinuable
		{
			Label GetContinueTarget();
		}

		public void Break()
		{
			BeforeStatement();

			bool useLeave = false;

			foreach (Block blk in blocks)
			{
				ExceptionBlock xb = blk as ExceptionBlock;

				if (xb != null)
				{
					if (xb.IsFinally)
						throw new InvalidOperationException(Properties.Messages.ErrInvalidFinallyBranch);

					useLeave = true;
				}

				IBreakable brkBlock = blk as IBreakable;

				if (brkBlock != null)
				{
					il.Emit(useLeave ? OpCodes.Leave : OpCodes.Br, brkBlock.GetBreakTarget());
					reachable = false;
					return;
				}
			}

			throw new InvalidOperationException(Properties.Messages.ErrInvalidBreak);
		}

		public void Continue()
		{
			BeforeStatement();

			bool useLeave = false;

			foreach (Block blk in blocks)
			{
				ExceptionBlock xb = blk as ExceptionBlock;

				if (xb != null)
				{
					if (xb.IsFinally)
						throw new InvalidOperationException(Properties.Messages.ErrInvalidFinallyBranch);

					useLeave = true;
				}

				IContinuable cntBlock = blk as IContinuable;

				if (cntBlock != null)
				{
					il.Emit(useLeave ? OpCodes.Leave : OpCodes.Br, cntBlock.GetContinueTarget());
					reachable = false;
					return;
				}
			}

			throw new InvalidOperationException(Properties.Messages.ErrInvalidContinue);
		}

		public void Return()
		{
			if (context.ReturnType != null && context.ReturnType != typeof(void))
				throw new InvalidOperationException(Properties.Messages.ErrMethodMustReturnValue);

			BeforeStatement();

			ExceptionBlock xb = GetAnyTryBlock();

			if (xb == null)
			{
				il.Emit(OpCodes.Ret);
			}
			else if (xb.IsFinally)
			{
				throw new InvalidOperationException(Properties.Messages.ErrInvalidFinallyBranch);
			}
			else
			{
				EnsureReturnVariable();
				il.Emit(OpCodes.Leave, retLabel);
			}

			reachable = false;
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "'Operand' required as type to use provided implicit conversions")]
		public void Return(Operand value)
		{
			if (context.ReturnType == null || context.ReturnType == typeof(void))
				throw new InvalidOperationException(Properties.Messages.ErrVoidMethodReturningValue);

			BeforeStatement();

			EmitGetHelper(value, context.ReturnType, false);

			ExceptionBlock xb = GetAnyTryBlock();

			if (xb == null)
			{
				il.Emit(OpCodes.Ret);
			}
			else if (xb.IsFinally)
			{
				throw new InvalidOperationException(Properties.Messages.ErrInvalidFinallyBranch);
			}
			else
			{
				EnsureReturnVariable();
				il.Emit(OpCodes.Stloc, retVar);
				il.Emit(OpCodes.Leave, retLabel);
			}
			reachable = false;
		}

		public void Throw()
		{
			BeforeStatement();

			il.Emit(OpCodes.Rethrow);
			reachable = false;
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "'Operand' required as type to use provided implicit conversions")]
		public void Throw(Operand exception)
		{
			BeforeStatement();

			EmitGetHelper(exception, typeof(Exception), false);
			il.Emit(OpCodes.Throw);
			reachable = false;
		}

		public void For(IStatement init, Operand test, IStatement iterator)
		{
			Begin(new LoopBlock(init, test, iterator));
		}

		public void While(Operand test)
		{
			Begin(new LoopBlock(null, test, null));
		}

		public Operand ForEach(Type elementType, Operand expression)
		{
			ForeachBlock fb = new ForeachBlock(elementType, expression);
			Begin(fb);
			return fb.Element;
		}

		public void If(Operand condition)
		{
			Begin(new IfBlock(condition));
		}

		public void Else()
		{
			IfBlock ifBlk = GetBlock() as IfBlock;
			if (ifBlk == null)
				throw new InvalidOperationException(Properties.Messages.ErrElseWithoutIf);

			blocks.Pop();
			Begin(new ElseBlock(ifBlk));
		}

		public void Try()
		{
			Begin(new ExceptionBlock());
		}

		ExceptionBlock GetTryBlock()
		{
			ExceptionBlock tryBlk = GetBlock() as ExceptionBlock;
			if (tryBlk == null)
				throw new InvalidOperationException(Properties.Messages.ErrInvalidExceptionStatement);
			return tryBlk;
		}

		ExceptionBlock GetAnyTryBlock()
		{
			foreach (Block blk in blocks)
			{
				ExceptionBlock tryBlk = blk as ExceptionBlock;

				if (tryBlk != null)
					return tryBlk;
			}

			return null;
		}

		public Operand Catch(Type exceptionType)
		{
			return GetTryBlock().BeginCatch(exceptionType);
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", Justification = "Intentional")]
		public void CatchAll()
		{
			GetTryBlock().BeginCatchAll();
		}

		public void Fault()
		{
			GetTryBlock().BeginFault();
		}

		public void Finally()
		{
			GetTryBlock().BeginFinally();
		}

		public void Switch(Operand expression)
		{
			Begin(new SwitchBlock(expression));
		}

		SwitchBlock GetSwitchBlock()
		{
			SwitchBlock switchBlk = GetBlock() as SwitchBlock;
			if (switchBlk == null)
				throw new InvalidOperationException(Properties.Messages.ErrInvalidSwitchStatement);
			return switchBlk;
		}

		public void Case(object value)
		{
			IConvertible conv = value as IConvertible;

			if (conv == null)
				throw new ArgumentException(Properties.Messages.ErrArgMustImplementIConvertible, "value");

			GetSwitchBlock().Case(conv);
		}

		public void DefaultCase()
		{
			GetSwitchBlock().Case(null);
		}
		#endregion

		Block GetBlock()
		{
			if (blocks.Count == 0)
				return null;

			return blocks.Peek();
		}

		Block GetBlockForVariable()
		{
			if (blocks.Count == 0)
				return null;

			Block b = blocks.Peek();
			b.EnsureScope();
			return b;
		}

		void Begin(Block b)
		{
			blocks.Push(b);
			b.g = this;
			b.Begin();
		}

		public void End()
		{
			if (blocks.Count == 0)
				throw new InvalidOperationException(Properties.Messages.ErrNoOpenBlocks);

			blocks.Peek().End();
			blocks.Pop();
		}

		abstract class Block
		{
			bool hasScope;
			internal CodeGen g;

			public void EnsureScope()
			{
				if (!hasScope)
				{
					g.il.BeginScope();
					hasScope = true;
				}
			}

			protected void EndScope()
			{
				if (hasScope)
				{
					g.il.EndScope();
					hasScope = false;
				}
			}

			public void Begin()
			{
				BeginImpl();
			}

			public void End()
			{
				EndImpl();
				EndScope();
			}

			protected abstract void BeginImpl();
			protected abstract void EndImpl();
		}

		class IfBlock : Block
		{
			Operand condition;

			public IfBlock(Operand condition)
			{
				if (condition.Type != typeof(bool))
					this.condition = condition.IsTrue();
				else
					this.condition = condition;
			}

			Label lbSkip;

			protected override void BeginImpl()
			{
				g.BeforeStatement();

				lbSkip = g.il.DefineLabel();
				condition.EmitBranch(g, BranchSet.Inverse, lbSkip);
			}

			protected override void EndImpl()
			{
				g.il.MarkLabel(lbSkip);
				g.reachable = true;
			}
		}

		class ElseBlock : Block
		{
			IfBlock ifBlk;
			Label lbSkip;
			bool canSkip;

			public ElseBlock(IfBlock ifBlk)
			{
				this.ifBlk = ifBlk;
			}

			protected override void BeginImpl()
			{
				if (canSkip = g.reachable)
				{
					lbSkip = g.il.DefineLabel();
					g.il.Emit(OpCodes.Br, lbSkip);
				}
				ifBlk.End();
			}

			protected override void EndImpl()
			{
				if (canSkip)
				{
					g.il.MarkLabel(lbSkip);
					g.reachable = true;
				}
			}
		}

		class LoopBlock : Block, IBreakable, IContinuable
		{
			IStatement init;
			Operand test;
			IStatement iter;

			public LoopBlock(IStatement init, Operand test, IStatement iter)
			{
				this.init = init;
				this.test = test;
				this.iter = iter;

				if (test.Type != typeof(bool))
					test = test.IsTrue();
			}

			Label lbLoop, lbTest, lbEnd, lbIter;
			bool endUsed = false, iterUsed = false;

			protected override void BeginImpl()
			{
				g.BeforeStatement();

				lbLoop = g.il.DefineLabel();
				lbTest = g.il.DefineLabel();
				if (init != null)
					init.Emit(g);
				g.il.Emit(OpCodes.Br, lbTest);
				g.il.MarkLabel(lbLoop);
			}

			protected override void EndImpl()
			{
				if (iter != null)
				{
					if (iterUsed)
						g.il.MarkLabel(lbIter);
				
					iter.Emit(g);
				}

				g.il.MarkLabel(lbTest);
				test.EmitBranch(g, BranchSet.Normal, lbLoop);

				if (endUsed)
					g.il.MarkLabel(lbEnd);

				g.reachable = true;
			}

			public Label GetBreakTarget()
			{
				if (!endUsed)
				{
					lbEnd = g.il.DefineLabel();
					endUsed = true;
				}
				return lbEnd;
			}

			public Label GetContinueTarget()
			{
				if (iter == null)
					return lbTest;

				if (!iterUsed)
				{
					lbIter = g.il.DefineLabel();
					iterUsed = true;
				}
				return lbIter;
			}
		}

		// TODO: proper implementation, including dispose
		class ForeachBlock : Block, IBreakable, IContinuable
		{
			Type elementType;
			Operand collection;

			public ForeachBlock(Type elementType, Operand collection)
			{
				this.elementType = elementType;
				this.collection = collection;
			}

			Operand enumerator, element;
			Label lbLoop, lbTest, lbEnd;
			bool endUsed = false;

			public Operand Element { get { return element; } }

			protected override void BeginImpl()
			{
				g.BeforeStatement();

				enumerator = g.Local();
				lbLoop = g.il.DefineLabel();
				lbTest = g.il.DefineLabel();

				if (typeof(IEnumerable).IsAssignableFrom(collection.Type))
					collection = collection.Cast(typeof(IEnumerable));

				g.Assign(enumerator, collection.Invoke("GetEnumerator"));
				g.il.Emit(OpCodes.Br, lbTest);
				g.il.MarkLabel(lbLoop);
				element = g.Local(elementType);
				g.Assign(element, enumerator.Property("Current"), true);
			}

			protected override void EndImpl()
			{
				g.il.MarkLabel(lbTest);
				enumerator.Invoke("MoveNext").EmitGet(g);

				g.il.Emit(OpCodes.Brtrue, lbLoop);

				if (endUsed)
					g.il.MarkLabel(lbEnd); 
				
				g.reachable = true;
			}

			public Label GetBreakTarget()
			{
				if (!endUsed)
				{
					lbEnd = g.il.DefineLabel();
					endUsed = true;
				}
				return lbEnd;
			}

			public Label GetContinueTarget()
			{
				return lbTest;
			}
		}

		class ExceptionBlock : Block
		{
			bool endReachable = false;
			bool isFinally = false;

			protected override void BeginImpl()
			{
				g.il.BeginExceptionBlock();
			}

			public void BeginCatchAll()
			{
				EndScope();

				if (g.reachable)
					endReachable = true;
				g.il.BeginCatchBlock(typeof(object));
				g.il.Emit(OpCodes.Pop);
				g.reachable = true;
			}

			public Operand BeginCatch(Type t)
			{
				EndScope();

				if (g.reachable)
					endReachable = true;

				g.il.BeginCatchBlock(t);
				LocalBuilder lb = g.il.DeclareLocal(t);
				g.il.Emit(OpCodes.Stloc, lb);
				g.reachable = true;

				return new _Local(g, lb);
			}

			public void BeginFault()
			{
				EndScope();

				g.il.BeginFaultBlock();
				g.reachable = true;
				isFinally = true;
			}

			public void BeginFinally()
			{
				EndScope();

				g.il.BeginFinallyBlock();
				g.reachable = true;
				isFinally = true;
			}

			protected override void EndImpl()
			{
				g.il.EndExceptionBlock();
				g.reachable = endReachable;
			}

			public bool IsFinally { get { return isFinally; } }
		}

		class SwitchBlock : Block, IBreakable
		{
			static Type[] validTypes = { 
				typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char), typeof(string)
			};
			static MethodInfo strCmp = typeof(string).GetMethod("Equals", BindingFlags.Public | BindingFlags.Static,
				null, new Type[] { typeof(string), typeof(string) }, null);

			Operand expression;
			Conversion conv;
			Type govType;
			Label lbDecision;
			Label lbEnd;
			Label lbDefault;
			LocalBuilder exp;
			bool defaultExists = false;
			bool endReachable = false;
			SortedList<IComparable, Label> cases = new SortedList<IComparable, Label>();

			public SwitchBlock(Operand expression)
			{
				this.expression = expression;

				Type exprType = expression.Type;
				if (Array.IndexOf(validTypes, exprType) != -1)
					govType = exprType;
				else if (exprType.IsEnum)
					govType = Enum.GetUnderlyingType(exprType);
				else
				{
					// if a single implicit coversion from expression to one of the valid types exists, it's ok
					foreach (Type t in validTypes)
					{
						Conversion tmp = Conversion.GetImplicit(expression, t, false);
						if (tmp.IsValid)
						{
							if (conv == null)
							{
								conv = tmp;
								govType = t;
							}
							else
								throw new AmbiguousMatchException(Properties.Messages.ErrAmbiguousSwitchExpression);
						}
					}
				}
			}

			protected override void BeginImpl()
			{
				lbDecision = g.il.DefineLabel();
				lbDefault = lbEnd = g.il.DefineLabel();

				expression.EmitGet(g);
				if (conv != null)
					conv.Emit(g, expression.Type, govType);
				exp = g.il.DeclareLocal(govType);
				g.il.Emit(OpCodes.Stloc, exp);
				g.il.Emit(OpCodes.Br, lbDecision);
				g.reachable = false;
			}

			public void Case(IConvertible value)
			{
				bool duplicate;

				// make sure the value is of the governing type
				IComparable val = value == null ? null : (IComparable)value.ToType(govType, System.Globalization.CultureInfo.InvariantCulture);

				if (value == null)
					duplicate = defaultExists;
				else
					duplicate = cases.ContainsKey(val);

				if (duplicate)
					throw new InvalidOperationException(Properties.Messages.ErrDuplicateCase);

				if (g.reachable)
					g.il.Emit(OpCodes.Br, lbEnd);

				EndScope();
				Label lb = g.il.DefineLabel();
				g.il.MarkLabel(lb);
				if (value == null)
				{
					defaultExists = true;
					lbDefault = lb;
				}
				else
				{
					cases[val] = lb;
				}
				g.reachable = true;
			}

			static int Diff(IConvertible val1, IConvertible val2)
			{
				ulong diff;

				switch (val1.GetTypeCode())
				{
					case TypeCode.UInt64:
						diff = val2.ToUInt64(null) - val1.ToUInt64(null);
						break;
					case TypeCode.Int64:
						diff = (ulong)(val2.ToInt64(null) - val1.ToInt64(null));
						break;
					case TypeCode.UInt32:
						diff = val2.ToUInt32(null) - val1.ToUInt32(null);
						break;
					default:
						diff = (ulong)(val2.ToInt32(null) - val1.ToInt32(null));
						break;
				}

				if (diff >= int.MaxValue)
					return int.MaxValue;
				else
					return (int)diff;
			}

			void Finish(List<Label> labels)
			{
				switch (labels.Count)
				{
					case 0: break;
					case 1:
						g.il.Emit(OpCodes.Beq, labels[0]);
						break;
					default:
						g.il.Emit(OpCodes.Sub);
						g.il.Emit(OpCodes.Switch, labels.ToArray());
						break;
				}
			}

			void EmitValue(IConvertible val)
			{
				switch (val.GetTypeCode())
				{
					case TypeCode.UInt64:
						g.EmitI8Helper(unchecked((long)val.ToUInt64(null)), false);
						break;
					case TypeCode.Int64:
						g.EmitI8Helper(val.ToInt64(null), true);
						break;
					case TypeCode.UInt32:
						g.EmitI4Helper(unchecked((int)val.ToUInt64(null)));
						break;
					default:
						g.EmitI4Helper(val.ToInt32(null));
						break;
				}
			}

			protected override void EndImpl()
			{
				if (g.reachable)
				{
					g.il.Emit(OpCodes.Br, lbEnd);
					endReachable = true;
				}

				EndScope();
				g.il.MarkLabel(lbDecision);

				if (govType == typeof(string))
				{
					foreach (KeyValuePair<IComparable, Label> kvp in cases)
					{
						g.il.Emit(OpCodes.Ldloc, exp);
						g.il.Emit(OpCodes.Ldstr, kvp.Key.ToString());
						g.il.Emit(OpCodes.Call, strCmp);
						g.il.Emit(OpCodes.Brtrue, kvp.Value);
					}
				}
				else
				{
					bool first = true;
					IConvertible prev = null;
					List<Label> labels = new List<Label>();

					foreach (KeyValuePair<IComparable, Label> kvp in cases)
					{
						IConvertible val = (IConvertible)kvp.Key;
						if (prev != null)
						{
							int diff = Diff(prev, val);
							if (diff > 3)
							{
								Finish(labels);
								labels.Clear();
								prev = null;
								first = true;
							}
							else while (diff-- > 1)
									labels.Add(lbDefault);
						}

						if (first)
						{
							g.il.Emit(OpCodes.Ldloc, exp);
							EmitValue(val);
							first = false;
						}

						labels.Add(kvp.Value);
						prev = val;
					}

					Finish(labels);
				}
				if (lbDefault != lbEnd)
					g.il.Emit(OpCodes.Br, lbDefault);
				g.il.MarkLabel(lbEnd);
				g.reachable = endReachable;
			}

			public Label GetBreakTarget()
			{
				endReachable = true;
				return lbEnd;
			}
		}
	}
}

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 MIT License


Written By
Web Developer
Slovakia Slovakia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions