Click here to Skip to main content
15,895,142 members
Articles / Programming Languages / MSIL

Assembly Manipulation and C# / VB.NET Code Injection

Rate me:
Please Sign up or sign in to vote.
4.95/5 (200 votes)
26 Apr 2013MIT5 min read 926.7K   25K   486  
Reflexil is an assembly editor and runs as a plug-in for Reflector or JustDecompile. Reflexil is able to manipulate IL code and save the modified assemblies to disk. Reflexil also supports "on-the-fly" C#/VB.NET code injection.
// <file>
//     <copyright see="prj:///doc/copyright.txt"/>
//     <license see="prj:///doc/license.txt"/>
//     <owner name="Daniel Grunwald" email="daniel@danielgrunwald.de"/>
//     <version>$Revision: 1751 $</version>
// </file>

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ICSharpCode.NRefactory;
using ICSharpCode.NRefactory.Parser;
using ICSharpCode.NRefactory.Parser.CSharp;

namespace ICSharpCode.SharpDevelop.Dom.CSharp
{
	/// <summary>
	/// Supports getting the expression including context from the cursor position.
	/// </summary>
	public class CSharpExpressionFinder : IExpressionFinder
	{
		ParseInformation parseInformation;
		IProjectContent projectContent;
		
		public CSharpExpressionFinder(ParseInformation parseInformation)
		{
			this.parseInformation = parseInformation;
			if (parseInformation != null && parseInformation.MostRecentCompilationUnit != null) {
				projectContent = parseInformation.MostRecentCompilationUnit.ProjectContent;
			} else {
				projectContent = DefaultProjectContent.DummyProjectContent;
			}
		}
		
		ILexer lexer;
		Location targetPosition;
		List<int> lineOffsets;
		
		int LocationToOffset(Location location)
		{
			if (location.Line <= 0) return -1;
			return lineOffsets[location.Line - 1] + location.Column - 1;
		}
		
		Location OffsetToLocation(int offset)
		{
			int lineNumber = lineOffsets.BinarySearch(offset);
			if (lineNumber < 0) {
				lineNumber = (~lineNumber) - 1;
			}
			return new Location(offset - lineOffsets[lineNumber] + 1, lineNumber + 1);
		}
		
		enum FrameType
		{
			Global,
			/// <summary>
			/// "class C { * }"
			/// </summary>
			TypeDecl,
			/// <summary>
			/// "interface I { * }"
			/// </summary>
			Interface,
			/// <summary>
			/// "enum E { * }"
			/// </summary>
			Enum,
			/// <summary>
			/// "void Method(*) {}"
			/// </summary>
			ParameterList,
			/// <summary>
			/// "public string Property { * }"
			/// </summary>
			Property,
			/// <summary>
			/// "public event EventHandler SomethingChanged { * }"
			/// </summary>
			Event,
			/// <summary>
			/// "void Method() { * }"
			/// </summary>
			Statements,
			/// <summary>
			/// "if (*) {}"
			/// </summary>
			Expression,
			/// <summary>
			/// "new T { * }"
			/// </summary>
			ObjectInitializer,
			/// <summary>
			/// "[*] class ..."
			/// </summary>
			AttributeSection,
			/// <summary>
			/// "[Obsolete(*)] class ..."
			/// </summary>
			AttributeArguments,
			/// <summary>
			/// Type reference frame "typeof(*)"
			/// </summary>
			TypeReference,
			/// <summary>
			/// Type parameter declaration, "class C&lt;*gt;"
			/// </summary>
			TypeParameterDecl,
			/// <summary>
			/// The Frame is no longer active.
			/// </summary>
			Popped
		}
		
		/// <summary>
		/// Used to support a frame-type specific state machine. Used in TrackCurrentContext
		/// </summary>
		enum FrameState
		{
			/// <summary>
			/// the default state (all frame types)
			/// </summary>
			Normal,
			/// <summary>
			/// parsing an inheritance list (Global+TypeDecl)
			/// </summary>
			InheritanceList,
			/// <summary>
			/// parsing an event declaration (Interface+TypeDecl)
			/// </summary>
			EventDecl,
			/// <summary>
			/// parsing a field declaration (Interface+TypeDecl).
			/// Could also be a method or property declaration.
			/// </summary>
			FieldDecl,
			/// <summary>
			/// parsing a field declaration, the field name was already specified (Interface+TypeDecl).
			/// Could also be a method or property declaration.
			/// </summary>
			FieldDeclAfterIdentifier,
			/// <summary>
			/// parsing a method declaration (Interface+TypeDecl) or a delegate declaration (Global+TypeDecl)
			/// </summary>
			MethodDecl,
			/// <summary>
			/// parsing a field initializer (TypeDecl)
			/// </summary>
			Initializer,
			/// <summary>
			/// Between class/struct/enum keyword and body of the type declaration
			/// </summary>
			TypeDecl,
			/// <summary>
			/// Between "where" and start of the generic method/class
			/// </summary>
			Constraints,
			/// <summary>
			/// Between "new" and "(" / "{".
			/// </summary>
			ObjectCreation,
			/// <summary>
			/// In object initializer, in the value part (after "=")
			/// </summary>
			ObjectInitializerValue,
			/// <summary>
			/// After a keyword like "if","while","using" etc., but before the embedded statement.
			/// </summary>
			StatementWithEmbeddedStatement
		}
		
		/// <summary>
		/// When parsing the code, each block starting with one of the brackets "(", "[", "{" or "&lt;" (for generics)
		/// gets an instance of Frame.
		/// </summary>
		sealed class Frame
		{
			internal Frame parent;
			internal FrameType type;
			internal FrameType parenthesisChildType;
			internal FrameType curlyChildType;
			internal FrameType squareBracketChildType;
			internal FrameState state;
			internal char bracketType;
			internal ExpressionContext context;
			internal IReturnType expectedType;
			
			internal bool InExpressionMode {
				get {
					return type == FrameType.Statements
						|| type == FrameType.Expression
						|| type == FrameType.AttributeArguments
						|| type == FrameType.ObjectInitializer
						|| state == FrameState.Initializer
						|| state == FrameState.ObjectCreation;
				}
			}
			
			internal void SetContext(ExpressionContext context)
			{
				this.context = context;
				this.expectedType = null;
			}
			internal void SetExpectedType(IReturnType expectedType)
			{
				this.expectedType = expectedType;
				this.context = ExpressionContext.Default;
			}
			internal void SetDefaultContext()
			{
				if (state == FrameState.InheritanceList) {
					if (curlyChildType == FrameType.Enum) {
						SetContext(ExpressionContext.EnumBaseType);
					} else if (curlyChildType == FrameType.Interface) {
						SetContext(ExpressionContext.Interface);
					} else {
						SetContext(ExpressionContext.InheritableType);
					}
				} else if (state == FrameState.Constraints) {
					SetContext(ExpressionContext.Constraints);
				} else {
					switch (type) {
						case FrameType.Global:
							SetContext(ExpressionContext.Global);
							break;
						case FrameType.TypeDecl:
							SetContext(ExpressionContext.TypeDeclaration);
							break;
						case FrameType.Enum:
						case FrameType.TypeParameterDecl:
							SetContext(ExpressionContext.IdentifierExpected);
							break;
						case FrameType.Interface:
							SetContext(ExpressionContext.InterfaceDeclaration);
							break;
						case FrameType.Event:
							SetContext(ExpressionContext.EventDeclaration);
							break;
						case FrameType.Property:
							if (parent != null && parent.type == FrameType.Interface) {
								SetContext(ExpressionContext.InterfacePropertyDeclaration);
							} else {
								SetContext(ExpressionContext.PropertyDeclaration);
							}
							break;
						case FrameType.Statements:
							SetContext(ExpressionContext.MethodBody);
							break;
						case FrameType.ParameterList:
							SetContext(ExpressionContext.ParameterType);
							break;
						case FrameType.ObjectInitializer:
							if (state == FrameState.ObjectInitializerValue) {
								SetContext(ExpressionContext.Default);
							} else {
								SetContext(ExpressionContext.ObjectInitializer);
							}
							break;
						case FrameType.AttributeSection:
							SetContext(ExpressionContext.Attribute);
							break;
						case FrameType.TypeReference:
							SetContext(ExpressionContext.Type);
							break;
						default:
							SetContext(ExpressionContext.Default);
							break;
					}
				}
			}
			
			/// <summary>start of the expression currently being tracked</summary>
			internal Location lastExpressionStart;
			
			/// <summary>Position of the last "new" keyword</summary>
			internal Location lastNewTokenStart;
			
			public Frame() : this(null, '\0') {}
			
			public Frame(Frame parent, char bracketType)
			{
				this.parent = parent;
				this.bracketType = bracketType;
				if (parent != null) {
					if (bracketType == '{') {
						this.type = parent.curlyChildType;
					} else if (bracketType == '(') {
						this.type = parent.parenthesisChildType;
					} else if (bracketType == '[') {
						this.type = parent.squareBracketChildType;
					} else {
						this.type = parent.type;
					}
				}
				ResetCurlyChildType();
				ResetParenthesisChildType();
				ResetSquareBracketChildType();
				SetDefaultContext();
			}
			
			public void ResetCurlyChildType()
			{
				if (state == FrameState.Initializer) {
					this.curlyChildType = FrameType.Expression;
				} else {
					switch (this.type) {
						case FrameType.Property:
						case FrameType.Event:
							this.curlyChildType = FrameType.Statements;
							break;
						default:
							this.curlyChildType = this.type;
							break;
					}
				}
			}
			
			public void ResetParenthesisChildType()
			{
				if (this.InExpressionMode) {
					this.parenthesisChildType = FrameType.Expression;
				} else if (type == FrameType.AttributeSection) {
					this.parenthesisChildType = FrameType.AttributeArguments;
				} else {
					this.parenthesisChildType = this.type;
				}
			}
			
			public void ResetSquareBracketChildType()
			{
				if (InExpressionMode)
					this.squareBracketChildType = FrameType.Expression;
				else
					this.squareBracketChildType = FrameType.AttributeSection;
			}
		}
		
		void Init(string text, int offset)
		{
			if (offset < 0 || offset > text.Length)
				throw new ArgumentOutOfRangeException("offset", offset, "offset must be between 0 and " + text.Length);
			lexer = ParserFactory.CreateLexer(SupportedLanguage.CSharp, new StringReader(text));
			lexer.SkipAllComments = true;
			lineOffsets = new List<int>();
			lineOffsets.Add(0);
			for (int i = 0; i < text.Length; i++) {
				if (i == offset) {
					targetPosition = new Location(offset - lineOffsets[lineOffsets.Count - 1] + 1, lineOffsets.Count);
				}
				if (text[i] == '\n') {
					lineOffsets.Add(i + 1);
				} else if (text[i] == '\r') {
					if (i + 1 < text.Length && text[i + 1] != '\n') {
						lineOffsets.Add(i + 1);
					}
				}
			}
			if (offset == text.Length) {
				targetPosition = new Location(offset - lineOffsets[lineOffsets.Count - 1] + 1, lineOffsets.Count);
			}
			
			frame = new Frame();
			lastToken = Tokens.EOF;
		}
		
		Frame frame;
		int lastToken;
		
		public ExpressionResult FindExpression(string text, int offset)
		{
			Init(text, offset);
			Token token;
			Location lastError = Location.Empty;
			lexer.Errors.Error = delegate (int errorLine, int errorCol, string errorMsg) {
				lastError = new Location(errorCol, errorLine);
			};
			while ((token = lexer.NextToken()) != null) {
				if (token.kind == Tokens.EOF) break;
				
				if (targetPosition <= token.Location) {
					break;
				}
				ApplyToken(token);
				if (targetPosition <= token.EndLocation) {
					if (token.kind == Tokens.Literal) {
						// do not return string literal as expression if offset was inside the literal,
						// or if it was at the end of the literal when the literal was not terminated correctly.
						if (targetPosition < token.EndLocation || lastError == token.Location) {
							frame.lastExpressionStart = Location.Empty;
						}
					}
					break;
				}
				lastToken = token.kind;
			}
			
			int tokenOffset;
			if (token == null || token.kind == Tokens.EOF)
				tokenOffset = text.Length;
			else
				tokenOffset = LocationToOffset(token.Location);
			int lastExpressionStartOffset = LocationToOffset(frame.lastExpressionStart);
			if (lastExpressionStartOffset >= 0) {
				if (offset < tokenOffset) {
					// offset is in front of this token
					return MakeResult(text, lastExpressionStartOffset, tokenOffset, frame.context);
				} else {
					// offset is IN this token
					return MakeResult(text, lastExpressionStartOffset, offset, frame.context);
				}
			} else {
				return new ExpressionResult(null, frame.context);
			}
		}
		
		void ApplyToken(Token token)
		{
			TrackCurrentFrameAndExpression(token);
			TrackCurrentContext(token);
		}
		
		void TrackCurrentFrameAndExpression(Token token)
		{
			while (frame.bracketType == '<' && !Tokens.ValidInsideTypeName[token.kind]) {
				frame.type = FrameType.Popped;
				frame = frame.parent;
			}
			switch (token.kind) {
				case Tokens.OpenCurlyBrace:
					frame.lastExpressionStart = Location.Empty;
					frame = new Frame(frame, '{');
					frame.parent.ResetCurlyChildType();
					break;
				case Tokens.CloseCurlyBrace:
					while (frame.parent != null) {
						if (frame.bracketType == '{') {
							frame.type = FrameType.Popped;
							frame = frame.parent;
							break;
						} else {
							frame.type = FrameType.Popped;
							frame = frame.parent;
						}
					}
					break;
				case Tokens.OpenParenthesis:
					if (frame.lastExpressionStart.IsEmpty)
						frame.lastExpressionStart = token.Location;
					frame = new Frame(frame, '(');
					frame.parent.ResetParenthesisChildType();
					break;
				case Tokens.OpenSquareBracket:
					frame = new Frame(frame, '[');
					frame.parent.ResetSquareBracketChildType();
					break;
				case Tokens.CloseParenthesis:
				case Tokens.CloseSquareBracket:
					if (frame.parent != null && (frame.bracketType == '(' || frame.bracketType == '[')) {
						frame.type = FrameType.Popped;
						frame = frame.parent;
					}
					break;
				case Tokens.LessThan:
					if (Tokens.ValidInsideTypeName[lastToken]) {
						frame = new Frame(frame, '<');
						if (frame.parent.InExpressionMode) {
							frame.SetContext(ExpressionContext.Default);
						} else if ((frame.parent.state == FrameState.TypeDecl
						            || frame.parent.state == FrameState.MethodDecl
						            || frame.parent.state == FrameState.FieldDeclAfterIdentifier)
						           && frame.parent.context == ExpressionContext.IdentifierExpected)
						{
							frame.type = FrameType.TypeParameterDecl;
							frame.SetContext(ExpressionContext.IdentifierExpected);
							frame.parent.SetContext(ExpressionContext.ConstraintsStart);
						} else {
							frame.SetContext(ExpressionContext.Type);
						}
					}
					break;
				case Tokens.GreaterThan:
					if (frame.parent != null && frame.bracketType == '<') {
						frame.type = FrameType.Popped;
						frame = frame.parent;
					} else {
						frame.lastExpressionStart = Location.Empty;
						frame.SetDefaultContext();
					}
					break;
				case Tokens.Question:
					// do not reset context - TrackCurrentContext will take care of this
					frame.lastExpressionStart = Location.Empty;
					break;
				case Tokens.Pointer:
				case Tokens.Dot:
				case Tokens.DoubleColon:
					// let the current expression continue
					break;
				default:
					if (Tokens.IdentifierTokens[token.kind]) {
						if (lastToken != Tokens.Dot && lastToken != Tokens.DoubleColon && lastToken != Tokens.Pointer) {
							if (Tokens.ValidInsideTypeName[lastToken]) {
								frame.SetDefaultContext();
							}
							frame.lastExpressionStart = token.Location;
						}
					} else if (Tokens.SimpleTypeName[token.kind] || Tokens.ExpressionStart[token.kind] || token.kind == Tokens.Literal) {
						frame.lastExpressionStart = token.Location;
					} else {
						frame.lastExpressionStart = Location.Empty;
						frame.SetDefaultContext();
					}
					break;
			}
		}
		
		void TrackCurrentContext(Token token)
		{
			if (frame.state == FrameState.ObjectCreation) {
				if (token.kind == Tokens.CloseParenthesis) {
					if (frame.context.IsObjectCreation) {
						frame.context = ExpressionContext.Default;
						frame.lastExpressionStart = frame.lastNewTokenStart;
					}
					// keep frame.state
				} else if (token.kind == Tokens.GreaterThan
				           || token.kind == Tokens.DoubleColon || token.kind == Tokens.Dot
				           || Tokens.SimpleTypeName[token.kind])
				{
					// keep frame.state == FrameState.ObjectCreationInType
				} else {
					frame.state = FrameState.Normal;
					frame.ResetCurlyChildType();
				}
			}
			
			switch (token.kind) {
				case Tokens.Using:
					if (frame.type == FrameType.Global) {
						frame.SetContext(ExpressionContext.Namespace);
						break;
					} else {
						goto case Tokens.For;
					}
				case Tokens.For:
				case Tokens.Foreach:
				case Tokens.Fixed:
				case Tokens.Catch:
					if (frame.type == FrameType.Statements) {
						frame.parenthesisChildType = FrameType.Statements;
						frame.state = FrameState.StatementWithEmbeddedStatement;
					}
					break;
				case Tokens.If:
				case Tokens.While:
				case Tokens.Switch:
				case Tokens.Lock:
					if (frame.type == FrameType.Statements) {
						frame.state = FrameState.StatementWithEmbeddedStatement;
					}
					break;
				case Tokens.Throw:
					frame.SetExpectedType(projectContent.SystemTypes.Exception);
					break;
				case Tokens.New:
					if (frame.InExpressionMode) {
						frame.SetContext(ExpressionContext.TypeDerivingFrom(frame.expectedType, true));
						frame.state = FrameState.ObjectCreation;
						frame.curlyChildType = FrameType.ObjectInitializer;
						frame.lastNewTokenStart = token.Location;
					}
					break;
				case Tokens.Namespace:
					frame.SetContext(ExpressionContext.IdentifierExpected);
					break;
				case Tokens.Assign:
					if (frame.type == FrameType.Global) {
						frame.SetContext(ExpressionContext.FullyQualifiedType);
						break;
					} else if (frame.type == FrameType.Enum) {
						frame.SetContext(ExpressionContext.Default);
						break;
					} else if (frame.type == FrameType.TypeDecl) {
						frame.SetContext(ExpressionContext.Default);
						frame.state = FrameState.Initializer;
						frame.ResetParenthesisChildType();
						frame.ResetSquareBracketChildType();
						frame.ResetCurlyChildType();
						break;
					} else if (frame.type == FrameType.ObjectInitializer) {
						frame.state = FrameState.ObjectInitializerValue;
						frame.SetDefaultContext();
						break;
					} else {
						goto default;
					}
				case Tokens.Colon:
					if (frame.state == FrameState.MethodDecl && lastToken == Tokens.CloseParenthesis) {
						frame.SetContext(ExpressionContext.BaseConstructorCall);
						frame.parenthesisChildType = FrameType.Expression;
					} else {
						if (frame.curlyChildType == FrameType.TypeDecl || frame.curlyChildType == FrameType.Interface || frame.curlyChildType == FrameType.Enum) {
							if (frame.state != FrameState.Constraints) {
								frame.state = FrameState.InheritanceList;
								frame.SetDefaultContext();
							}
						}
					}
					break;
				case Tokens.Class:
				case Tokens.Struct:
					if (frame.type == FrameType.Global || frame.type == FrameType.TypeDecl) {
						if (frame.state != FrameState.Constraints) {
							frame.state = FrameState.TypeDecl;
							frame.curlyChildType = FrameType.TypeDecl;
							frame.SetContext(ExpressionContext.IdentifierExpected);
						}
					}
					break;
				case Tokens.Interface:
					if (frame.type == FrameType.Global || frame.type == FrameType.TypeDecl) {
						frame.state = FrameState.TypeDecl;
						frame.curlyChildType = FrameType.Interface;
						frame.SetContext(ExpressionContext.IdentifierExpected);
					}
					break;
				case Tokens.Enum:
					if (frame.type == FrameType.Global || frame.type == FrameType.TypeDecl) {
						frame.state = FrameState.TypeDecl;
						frame.curlyChildType = FrameType.Enum;
						frame.SetContext(ExpressionContext.IdentifierExpected);
					}
					break;
				case Tokens.Delegate:
					if (frame.InExpressionMode) {
						frame.parenthesisChildType = FrameType.ParameterList;
						frame.curlyChildType = FrameType.Statements;
					} else if (frame.type == FrameType.Global || frame.type == FrameType.TypeDecl) {
						frame.parenthesisChildType = FrameType.ParameterList;
						frame.state = FrameState.MethodDecl;
						frame.SetContext(ExpressionContext.Type);
					}
					break;
				case Tokens.LambdaArrow:
					frame.curlyChildType = FrameType.Statements;
					break;
				case Tokens.Event:
					frame.SetContext(ExpressionContext.DelegateType);
					frame.curlyChildType = FrameType.Event;
					frame.state = FrameState.EventDecl;
					break;
				case Tokens.Comma:
					if (frame.state == FrameState.FieldDecl
					    || frame.state == FrameState.FieldDeclAfterIdentifier
					    || frame.state == FrameState.Initializer)
					{
						frame.state = FrameState.FieldDecl;
						frame.SetContext(ExpressionContext.IdentifierExpected);
					} else if (frame.state == FrameState.ObjectInitializerValue) {
						frame.state = FrameState.Normal;
						frame.SetDefaultContext();
					} else if (frame.type == FrameType.Statements) {
						frame.SetContext(ExpressionContext.IdentifierExpected);
					}
					break;
				case Tokens.Where:
					if (!frame.InExpressionMode && (frame.type == FrameType.Global || frame.type == FrameType.TypeDecl)) {
						frame.state = FrameState.Constraints;
						frame.SetDefaultContext();
					}
					break;
				case Tokens.CloseCurlyBrace:
				case Tokens.Semicolon:
					frame.state = FrameState.Normal;
					frame.SetDefaultContext();
					break;
				case Tokens.OpenParenthesis:
					if (frame.parent != null && (frame.parent.state == FrameState.FieldDeclAfterIdentifier
					                             || frame.parent.state == FrameState.FieldDecl))
					{
						frame.type = FrameType.ParameterList;
						frame.SetContext(ExpressionContext.FirstParameterType);
						frame.parent.state = FrameState.MethodDecl;
						frame.parent.curlyChildType = FrameType.Statements;
					}
					break;
				case Tokens.CloseParenthesis:
					if (frame.state == FrameState.StatementWithEmbeddedStatement) {
						frame.state = FrameState.Normal;
						frame.lastExpressionStart = token.EndLocation;
					}
					break;
				case Tokens.Question:
					// IdentifierExpected = this is after a type name = the ? was a nullable marker
					if (frame.context != ExpressionContext.IdentifierExpected) {
						frame.SetDefaultContext();
					}
					break;
				case Tokens.This:
					if (frame.state == FrameState.FieldDecl) {
						// this is an indexer declaration
						frame.squareBracketChildType = FrameType.ParameterList;
						frame.state = FrameState.FieldDeclAfterIdentifier;
					}
					break;
				case Tokens.Goto:
					frame.SetContext(ExpressionContext.IdentifierExpected);
					break;
				case Tokens.As:
				case Tokens.Is:
					frame.SetContext(ExpressionContext.Type);
					break;
				case Tokens.Typeof:
					frame.parenthesisChildType = FrameType.TypeReference;
					break;
				default:
					if (Tokens.SimpleTypeName[token.kind]) {
						if (frame.type == FrameType.Interface || frame.type == FrameType.TypeDecl) {
							if (frame.state == FrameState.Normal) {
								frame.state = FrameState.FieldDecl;
								frame.curlyChildType = FrameType.Property;
							} else if (frame.state == FrameState.FieldDecl && Tokens.IdentifierTokens[token.kind]) {
								frame.state = FrameState.FieldDeclAfterIdentifier;
							}
							if (frame.state != FrameState.ObjectCreation) {
								frame.SetContext(ExpressionContext.IdentifierExpected);
							}
						} else if (frame.type == FrameType.ParameterList
						           || frame.type == FrameType.Statements
						           || frame.type == FrameType.Global)
						{
							if (!frame.context.IsObjectCreation) {
								frame.SetContext(ExpressionContext.IdentifierExpected);
							}
						}
					}
					break;
			}
		}
		
		public ExpressionResult FindFullExpression(string text, int offset)
		{
			return FindFullExpression(text, offset, null);
		}
		
		/// <summary>
		/// Like FindFullExpression, but text is a code snippet inside a type declaration.
		/// </summary>
		public ExpressionResult FindFullExpressionInTypeDeclaration(string text, int offset)
		{
			Frame root = new Frame();
			root.curlyChildType = FrameType.TypeDecl;
			Frame typeDecl = new Frame(root, '{');
			return FindFullExpression(text, offset, typeDecl);
		}
		
		
		/// <summary>
		/// Like FindFullExpression, but text is a code snippet inside a method body.
		/// </summary>
		public ExpressionResult FindFullExpressionInMethod(string text, int offset)
		{
			Frame root = new Frame();
			root.curlyChildType = FrameType.TypeDecl;
			Frame typeDecl = new Frame(root, '{');
			typeDecl.curlyChildType = FrameType.Statements;
			Frame methodBody = new Frame(typeDecl, '{');
			return FindFullExpression(text, offset, methodBody);
		}
		
		ExpressionResult FindFullExpression(string text, int offset, Frame initialFrame)
		{
			Init(text, offset);
			
			if (initialFrame != null) {
				frame = initialFrame;
			}
			
			const int SEARCHING_OFFSET = 0;
			const int SEARCHING_END = 1;
			int state = SEARCHING_OFFSET;
			Frame resultFrame = frame;
			int resultStartOffset = -1;
			int alternateResultStartOffset = -1;
			int resultEndOffset = -1;
			ExpressionContext prevContext = ExpressionContext.Default;
			ExpressionContext resultContext = ExpressionContext.Default;
			
			Token token;
			while ((token = lexer.NextToken()) != null) {
				if (token.kind == Tokens.EOF) break;
				
				if (state == SEARCHING_OFFSET) {
					if (targetPosition < token.Location) {
						resultFrame = frame;
						resultContext = frame.context;
						resultStartOffset = LocationToOffset(frame.lastExpressionStart);
						alternateResultStartOffset = LocationToOffset(frame.lastNewTokenStart);
						if (resultStartOffset < 0)
							break;
						resultEndOffset = LocationToOffset(token.Location);
						state = SEARCHING_END;
					}
				}
				prevContext = frame.context;
				ApplyToken(token);
				if (state == SEARCHING_OFFSET) {
					if (targetPosition < token.EndLocation) {
						resultFrame = frame;
						resultContext = prevContext;
						resultStartOffset = LocationToOffset(frame.lastExpressionStart);
						alternateResultStartOffset = LocationToOffset(frame.lastNewTokenStart);
						resultEndOffset = LocationToOffset(token.EndLocation);
						if (resultStartOffset < 0)
							break;
						state = SEARCHING_END;
					}
				} else if (state == SEARCHING_END) {
					int lastExpressionStartOffset = LocationToOffset(resultFrame.lastExpressionStart);
					if (lastExpressionStartOffset == alternateResultStartOffset && alternateResultStartOffset >= 0)
						resultStartOffset = lastExpressionStartOffset;
					if (resultFrame.type == FrameType.Popped ||
					    lastExpressionStartOffset != resultStartOffset ||
					    token.kind == Tokens.Dot || token.kind == Tokens.DoubleColon || token.kind == Tokens.Pointer)
					{
						
						// now we can change the context based on the next token
						if (frame == resultFrame && Tokens.IdentifierTokens[token.kind]) {
							// the expression got aborted because of an identifier. This means the
							// expression was a type reference
							resultContext = ExpressionContext.Type;
						} else if (resultFrame.bracketType == '<' && token.kind == Tokens.GreaterThan) {
							// expression was a type argument
							resultContext = ExpressionContext.Type;
							return MakeResult(text, resultStartOffset, resultEndOffset, resultContext);
						}
						if (frame == resultFrame || resultFrame.type == FrameType.Popped) {
							return MakeResult(text, resultStartOffset, resultEndOffset, resultContext);
						}
					} else {
						if (frame.bracketType != '<') {
							resultEndOffset = LocationToOffset(token.EndLocation);
						}
					}
				}
				lastToken = token.kind;
			}
			// offset is behind all tokens -> cannot find any expression
			return new ExpressionResult(null, frame.context);
		}
		
		ExpressionResult MakeResult(string text, int startOffset, int endOffset, ExpressionContext context)
		{
			return new ExpressionResult(text.Substring(startOffset, endOffset - startOffset),
			                            DomRegion.FromLocation(OffsetToLocation(startOffset), OffsetToLocation(endOffset)),
			                            context, null);
		}
		
		public string RemoveLastPart(string expression)
		{
			Init(expression, expression.Length - 1);
			int lastValidPos = 0;
			Token token;
			while ((token = lexer.NextToken()) != null) {
				if (token.kind == Tokens.EOF) break;
				
				if (frame.parent == null) {
					if (token.kind == Tokens.Dot || token.kind == Tokens.DoubleColon || token.kind == Tokens.Pointer
					    || token.kind == Tokens.OpenParenthesis || token.kind == Tokens.OpenSquareBracket)
					{
						lastValidPos = LocationToOffset(token.Location);
					}
				}
				ApplyToken(token);
				
				lastToken = token.kind;
			}
			return expression.Substring(0, lastValidPos);
		}
		
		#region Comment Filter and 'inside string watcher'
		
		// NOTE: FilterComments is not used anymore inside the ExpressionFinder, it should be moved
		// into another class / or removed completely if it is not required anymore.
		
		int initialOffset;
		public string FilterComments(string text, ref int offset)
		{
			if (text.Length <= offset)
				return null;
			this.initialOffset = offset;
			StringBuilder outText = new StringBuilder();
			int curOffset = 0;
			
			while (curOffset <= initialOffset) {
				char ch = text[curOffset];
				
				switch (ch) {
					case '@':
						if (curOffset + 1 < text.Length && text[curOffset + 1] == '"') {
							outText.Append(text[curOffset++]); // @
							outText.Append(text[curOffset++]); // "
							if (!ReadVerbatimString(outText, text, ref curOffset)) {
								return null;
							}
						}else{
							outText.Append(ch);
							++curOffset;
						}
						break;
					case '\'':
						outText.Append(ch);
						curOffset++;
						if(! ReadChar(outText, text, ref curOffset)) {
							return null;
						}
						break;
					case '"':
						outText.Append(ch);
						curOffset++;
						if (!ReadString(outText, text, ref curOffset)) {
							return null;
						}
						break;
					case '/':
						if (curOffset + 1 < text.Length && text[curOffset + 1] == '/') {
							offset    -= 2;
							curOffset += 2;
							if (!ReadToEOL(text, ref curOffset, ref offset)) {
								return null;
							}
						} else if (curOffset + 1 < text.Length && text[curOffset + 1] == '*') {
							offset    -= 2;
							curOffset += 2;
							if (!ReadMultiLineComment(text, ref curOffset, ref offset)) {
								return null;
							}
						} else {
							goto default;
						}
						break;
					case '#':
						if (!ReadToEOL(text, ref curOffset, ref offset)) {
							return null;
						}
						break;
					default:
						outText.Append(ch);
						++curOffset;
						break;
				}



			}
			
			return outText.ToString();

		}
		
		bool ReadToEOL(string text, ref int curOffset, ref int offset)
		{
			while (curOffset <= initialOffset) {
				char ch = text[curOffset++];
				--offset;
				if (ch == '\n') {
					return true;
				}
			}
			return false;
		}
		
		bool ReadChar(StringBuilder outText, string text, ref int curOffset)
		{
			if (curOffset > initialOffset)
				return false;
			char first = text[curOffset++];
			outText.Append(first);
			if (curOffset > initialOffset)
				return false;
			char second = text[curOffset++];
			outText.Append(second);
			if (first == '\\') {
				// character is escape sequence, so read one char more
				char next;
				do {
					if (curOffset > initialOffset)
						return false;
					next = text[curOffset++];
					outText.Append(next);
					// unicode or hexadecimal character literals can have more content characters
				} while((second == 'u' || second == 'x') && char.IsLetterOrDigit(next));
			}
			return text[curOffset - 1] == '\'';
		}
		
		bool ReadString(StringBuilder outText, string text, ref int curOffset)
		{
			while (curOffset <= initialOffset) {
				char ch = text[curOffset++];
				outText.Append(ch);
				if (ch == '"') {
					return true;
				} else if (ch == '\\') {
					if (curOffset <= initialOffset)
						outText.Append(text[curOffset++]);
				}
			}
			return false;
		}
		
		bool ReadVerbatimString(StringBuilder outText, string text, ref int curOffset)
		{
			while (curOffset <= initialOffset) {
				char ch = text[curOffset++];
				outText.Append(ch);
				if (ch == '"') {
					if (curOffset < text.Length && text[curOffset] == '"') {
						outText.Append(text[curOffset++]);
					} else {
						return true;
					}
				}


			}
			return false;

		}
		
		bool ReadMultiLineComment(string text, ref int curOffset, ref int offset)
		{
			while (curOffset <= initialOffset) {
				char ch = text[curOffset++];
				--offset;
				if (ch == '*') {
					if (curOffset < text.Length && text[curOffset] == '/') {
						++curOffset;
						--offset;
						return true;
					}
				}
			}
			return false;
		}
		#endregion
	}
}


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
Software Developer (Senior) Microsoft
United States United States
Sebastien Lebreton is a Software Engineer at Microsoft.

He is particularly interested in optimization, reverse engineering and distributed objects technologies.

Comments and Discussions